STM32-FreeModbus 移植

STM32-FreeModbus 移植

Written By Tomy Stark.
E-mail: ro7enkranz@qq.com

Note: 转载请注明本文出处链接、作者

一、工具准备清单

硬件

  • 一块带有 RS485 通信接口的 STM32 开发板
  • USB 转 485 通讯转换器(推荐带 FT232 芯片的转换器,更加稳定, CH340 稳定性欠佳。)

软件

  • FreeMODBUS(Modbus 协议栈)
  • Modbus Poll(Modbus RTU 上位机调试软件)
  • MODBUS调试助手(来自安富莱论坛,简单易用,可替代 Modbus Poll 部分功能)
  • STM32CubeMX(ST 官方的 MCU 配置工具)
  • TrueStudio(这里我以 TrueStudio 这个 IDE 为例,其它 IDE 例如 Keil MDK 也可)

以上部分软件也可从我的微云网盘下载:https://share.weiyun.com/5ec0Utd

二、提取移植文件

将从 FreeMODBUS 官网下载的 freemodbus-v1.6.zip 解压后,将其中的 modbus 目录和 demo\BARE 目录下的 port 目录完整复制到自己的工程目录下。

为方便管理,在这里我将 modbus 目录重命名为 FreeModbus_Core ,将 port 目录重命名为 FreeModbus_Port

三、STM32CubeMX - 设置串口

注意这里的串口设置:

  • 波特率:9600 Bits/s
  • 字长:8 Bits
  • 校验:None
  • 停止位:2

根据 Modbus RTU 的标准,在没有校验的情况下,需要 2 个停止位。

串口设置
串口中断使能

四、STM32CubeMX - 设置定时器

时钟树设置

由时钟树的设置图可知当前 MCU 运行时提供给定时器的时钟为 48 MHz ,我们需要设置定时器,以最终能为 FreeModbus 协议栈 提供一个 50 us 的时钟信号基准,该设置会对 Modbus RTU 协议中的 T3.5 (3.5个字符时间)产生影响。

定时器设置

定时器选择了 TIM16 ,原因很简单,因为只需要提供一个 50 us 的时基,所以使用通用定时器即可。 T3.5 的计算公式如下:

T3_5计算公式

定时器中断使能

五、STM32CubeMX - 库选择

库选择

六、搞定串口

portserial.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include "port.h"
#include "usart.h"

/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

/* ----------------------- static functions ---------------------------------*/
void prvvUARTTxReadyISR( void );
void prvvUARTRxISR( void );

/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable
* transmitter empty interrupts.
*/
if (xRxEnable) {
/* Enable the UART Data Register not empty Interrupt */
LL_USART_EnableIT_RXNE(USART1);
} else {
/* Disable the UART Data Register not empty Interrupt */
LL_USART_DisableIT_RXNE(USART1);
}

if (xTxEnable) {
/* Enable the UART Transmit data register empty Interrupt */
LL_USART_EnableIT_TXE(USART1);
} else {
/* Disable the UART Transmit data register empty Interrupt */
LL_USART_DisableIT_TXE(USART1);
}
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
/* 由于已经在系统初始化中已经完成串口初始化,故在此不需要再一次进行初始化 */
return TRUE;
}

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
/* Put a byte in the UARTs transmit buffer. This function is called
* by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been
* called. */
LL_USART_TransmitData8(USART1, (uint8_t)ucByte);
return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
/* Return the byte in the UARTs receive buffer. This function is called
* by the protocol stack after pxMBFrameCBByteReceived( ) has been called.
*/
*pucByte = LL_USART_ReceiveData8(USART1);
return TRUE;
}

/* Create an interrupt handler for the transmit buffer empty interrupt
* (or an equivalent) for your target processor. This function should then
* call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
* a new character can be sent. The protocol stack will then call
* xMBPortSerialPutByte( ) to send the character.
*/
void prvvUARTTxReadyISR( void )
{
pxMBFrameCBTransmitterEmpty( );
}

/* Create an interrupt handler for the receive interrupt for your target
* processor. This function should then call pxMBFrameCBByteReceived( ). The
* protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
* character.
*/
void prvvUARTRxISR( void )
{
pxMBFrameCBByteReceived( );
}

七、搞定定时器

porttimer.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/* ----------------------- Platform includes --------------------------------*/
#include "port.h"
#include "tim.h"
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"

/* ----------------------- static functions ---------------------------------*/
//static void prvvTIMERExpiredISR( void );

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
/* 系统初始化已经完成定时器的初始化,故在此不在进行初始化 */
return TRUE;
}


inline void
vMBPortTimersEnable( void )
{
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
LL_TIM_ClearFlag_UPDATE(TIM16);
LL_TIM_EnableIT_UPDATE(TIM16);
LL_TIM_SetCounter(TIM16, 0);
LL_TIM_EnableCounter(TIM16);
}

inline void
vMBPortTimersDisable( void )
{
/* Disable any pending timers. */
LL_TIM_DisableCounter(TIM16);
LL_TIM_SetCounter(TIM16, 0);
LL_TIM_DisableIT_UPDATE(TIM16);
LL_TIM_ClearFlag_UPDATE(TIM16);
}

/* Create an ISR which is called whenever the timer has expired. This function
* must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
* the timer has expired.
*/
void prvvTIMERExpiredISR( void )
{
( void )pxMBPortCBTimerExpired( );
}

八、搞定中断服务程序

APP_IRQ.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef APP_IRQ_H_
#define APP_IRQ_H_

#include "mb.h"

#define REG_HOLDING_START 0x0000
#define REG_HOLDING_NREGS 1

extern uint16_t usRegHoldingStart;
extern uint16_t usRegHoldingBuf[REG_HOLDING_NREGS];

extern void prvvTIMERExpiredISR(void);
extern void prvvUARTTxReadyISR(void);
extern void prvvUARTRxISR(void);

#endif /* APP_IRQ_H_ */
APP_IRQ.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include "APP_IRQ.h"

/* 定义保持寄存器的地址起始值和存储数组 */
uint16_t usRegHoldingStart = REG_HOLDING_START;
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] = { 0x1234 };

/*******************************************************************************
* @name : eMBRegHoldingCB
* @brief : 保持寄存器处理函数,保持寄存器可读,可读可写
* @param : pucRegBuffer 读操作时--返回数据指针,写操作时--输入数据指针
* usAddress 寄存器起始地址
* usNRegs 寄存器长度
* eMode 操作方式,读或者写
* @retval : eStatus 寄存器状态
*******************************************************************************/
eMBErrorCode eMBRegHoldingCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode)
{
eMBErrorCode eStatus = MB_ENOERR;
/* 偏移量 */
int16_t iRegIndex;

/* 判断寄存器是不是在范围内 */
if (((int16_t) usAddress >= REG_HOLDING_START) && (usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS)) {
/* 计算偏移量 */
iRegIndex = (int16_t) (usAddress - REG_HOLDING_START);

switch (eMode)
{
case MB_REG_READ: /* 读处理函数 */
while (usNRegs > 0)
{
*pucRegBuffer++ = (uint8_t)(usRegHoldingBuf[iRegIndex] >> 8U);
*pucRegBuffer++ = (uint8_t)(usRegHoldingBuf[iRegIndex] & 0xFF);
iRegIndex++;
usNRegs--;
}
break;

case MB_REG_WRITE: /* 写处理函数 */
while (usNRegs > 0)
{
usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8U;
usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
}
break;
}
} else {
eStatus = MB_ENOREG;
}

return eStatus;
}

void TIM16_IRQHandler(void)
{
if ((LL_TIM_IsActiveFlag_UPDATE(TIM16) != RESET) && (LL_TIM_IsEnabledIT_UPDATE(TIM16) != RESET)) {
LL_TIM_ClearFlag_UPDATE(TIM16);

prvvTIMERExpiredISR();
}
}

void USART1_IRQHandler(void)
{
if ((LL_USART_IsActiveFlag_RXNE(USART1) != RESET) && (LL_USART_IsEnabledIT_RXNE(USART1) != RESET)) {
prvvUARTRxISR();
}

if ((LL_USART_IsActiveFlag_TXE(USART1) != RESET) && (LL_USART_IsEnabledIT_TXE(USART1) != RESET)) {
prvvUARTTxReadyISR();
}
}

九、搞定开关中断配置

port.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#ifndef _PORT_H
#define _PORT_H

#include <assert.h>
#include <inttypes.h>
#include "stm32f0xx.h" /* 头文件对应 MCU 系列 */

#define INLINE inline
#define PR_BEGIN_EXTERN_C extern "C" {
#define PR_END_EXTERN_C }

#define ENTER_CRITICAL_SECTION() __disable_irq() /* 进入临界区,关闭中断 */
#define EXIT_CRITICAL_SECTION() __enable_irq() /* 退出临界区,开启中断 */

typedef uint8_t BOOL;

typedef unsigned char UCHAR;
typedef char CHAR;

typedef uint16_t USHORT;
typedef int16_t SHORT;

typedef uint32_t ULONG;
typedef int32_t LONG;

#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif

#endif

十、搞定 FreeModbus 函数裁剪

在这里可以选择 FreeModbus 的工作模式:ASCIIRTUTCP

同时也可对 FreeModbus 协议栈进行函数裁剪,对于不需要的功能,通过宏开关关闭即可

mbconfig.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#ifndef _MB_CONFIG_H
#define _MB_CONFIG_H

#ifdef __cplusplus
PR_BEGIN_EXTERN_C
#endif
/* ----------------------- Defines ------------------------------------------*/
/*! \defgroup modbus_cfg Modbus Configuration
*
* Most modules in the protocol stack are completly optional and can be
* excluded. This is specially important if target resources are very small
* and program memory space should be saved.<br>
*
* All of these settings are available in the file <code>mbconfig.h</code>
*/
/*! \addtogroup modbus_cfg
* @{
*/
/*! \brief If Modbus ASCII support is enabled. */
#define MB_ASCII_ENABLED ( 0 )

/*! \brief If Modbus RTU support is enabled. */
#define MB_RTU_ENABLED ( 1 )

/*! \brief If Modbus TCP support is enabled. */
#define MB_TCP_ENABLED ( 0 )

/*! \brief The character timeout value for Modbus ASCII.
*
* The character timeout value is not fixed for Modbus ASCII and is therefore
* a configuration option. It should be set to the maximum expected delay
* time of the network.
*/
#define MB_ASCII_TIMEOUT_SEC ( 1 )

/*! \brief Timeout to wait in ASCII prior to enabling transmitter.
*
* If defined the function calls vMBPortSerialDelay with the argument
* MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS to allow for a delay before
* the serial transmitter is enabled. This is required because some
* targets are so fast that there is no time between receiving and
* transmitting the frame. If the master is to slow with enabling its
* receiver then he will not receive the response correctly.
*/
#ifndef MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS
#define MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS ( 0 )
#endif

/*! \brief Maximum number of Modbus functions codes the protocol stack
* should support.
*
* The maximum number of supported Modbus functions must be greater than
* the sum of all enabled functions in this file and custom function
* handlers. If set to small adding more functions will fail.
*/
#define MB_FUNC_HANDLERS_MAX ( 16 )

/*! \brief Number of bytes which should be allocated for the <em>Report Slave ID
* </em>command.
*
* This number limits the maximum size of the additional segment in the
* report slave id function. See eMBSetSlaveID( ) for more information on
* how to set this value. It is only used if MB_FUNC_OTHER_REP_SLAVEID_ENABLED
* is set to <code>1</code>.
*/
#define MB_FUNC_OTHER_REP_SLAVEID_BUF ( 32 )

/*! \brief If the <em>Report Slave ID</em> function should be enabled. */
#define MB_FUNC_OTHER_REP_SLAVEID_ENABLED ( 1 )

/*! \brief If the <em>Read Input Registers</em> function should be enabled. */
#define MB_FUNC_READ_INPUT_ENABLED ( 0 )

/*! \brief If the <em>Read Holding Registers</em> function should be enabled. */
#define MB_FUNC_READ_HOLDING_ENABLED ( 1 )

/*! \brief If the <em>Write Single Register</em> function should be enabled. */
#define MB_FUNC_WRITE_HOLDING_ENABLED ( 0 )

/*! \brief If the <em>Write Multiple registers</em> function should be enabled. */
#define MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED ( 0 )

/*! \brief If the <em>Read Coils</em> function should be enabled. */
#define MB_FUNC_READ_COILS_ENABLED ( 0 )

/*! \brief If the <em>Write Coils</em> function should be enabled. */
#define MB_FUNC_WRITE_COIL_ENABLED ( 0 )

/*! \brief If the <em>Write Multiple Coils</em> function should be enabled. */
#define MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED ( 0 )

/*! \brief If the <em>Read Discrete Inputs</em> function should be enabled. */
#define MB_FUNC_READ_DISCRETE_INPUTS_ENABLED ( 0 )

/*! \brief If the <em>Read/Write Multiple Registers</em> function should be enabled. */
#define MB_FUNC_READWRITE_HOLDING_ENABLED ( 0 )

#ifdef __cplusplus
PR_END_EXTERN_C
#endif
#endif

十一、搞定寄存器地址差一的问题

FreeModbus 协议栈中所有的 usRegAddress++; 注释掉即可。

十二、搞定主函数

因为篇幅有限,部分由 STM32CubeMX 生成的代码已被我略去,请注意。

main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "APP_IRQ.h"
#include "mb.h"

int main(void)
{
uint16_t cnt = 0U;

/* 初始化串口和定时器 */
MX_USART1_UART_Init();
MX_TIM16_Init();

/* Init the Modbus Protocol Stack. */
(void)eMBInit(MB_RTU, 0x64, 0, 9600, MB_PAR_NONE);

/* Enable the Modbus Protocol Stack. */
(void)eMBEnable();

while (1) {
/* 将 cnt 赋值给保持寄存器缓冲区 */
usRegHoldingBuf[0] = cnt;

(void)eMBPoll();
}
}

评论