STM32F407移植FreemodbusV1.6

准备工作

工具介绍

软件

  • keil5 : 用于代码编译调试
    不多介绍,应该大家都知道。

  • cubemx : 代码生成
    STM32CubeMX 是 STMicroelectronics(意法半导体)为简化其 STM32 微控制器系列的项目创建与初始化配置而开发的一款免费软件工具。STM32CubeMX 提供了图形化的用户界面,使得开发者可以快速地进行项目搭建,并且支持多种主流的 IDE(集成开发环境)。

  • vscode : 代码编辑(我喜欢的工具,万能ide)
    Visual Studio Code(VSCode)是一款跨平台的轻量级源代码编辑器,以其高性能和丰富的功能受到开发者欢迎。它内置了 Git 支持,提供了智能代码补全与导航,以及强大的调试功能。VSCode 还拥有一个活跃的插件市场,支持高度定制化的用户界面,并且能够处理多种编程语言。此外,它还支持实时协作和内置终端集成,使其成为一个全面且灵活的开发工具。

  • modbus poll && slave
    用于观察调试写的程序功能是否正常。

  • FreeModBus源码下载
    github https://github.com/RT-Thread-packages/freemodbus 这是基于国产之光的rt-thread创建的工程,本次计划使用freertos来替换。

硬件

  • STM32F407ZGT6 使用的是普中麒麟f407开发板。只要是st主流的单片机都可以使用本次的源码,其它品牌单片机需要自行适配。
  • 两个串口转usb 其实不用也可以,直接使用杜邦线将主机和从机连接起来在keil调试中观察也行。

创建工程

CUBEMX工程创建

  • 打开cubemx选择型号
    根据自己型号选择
  • 设置时钟
    时钟源都选择外部时钟

    选择调试接口和基准时钟-应为freertos要占用systick所以这里使用TIM6
    image.png
    输入168按回车
    image.png

串口设置 - usart1~3 设置一样
image.png
image.png

关闭hal库自己的串口回调-因为感觉运行太慢了-自己写更方便
image.png
开启freertos
image.png
创建3个任务
image.png
生成项目
image.png
image.png

keil工程整合

将下载的源码全部复制到生成项目根目录下
image.png
打开MDK-ARM中的keil项目-添加文件modbus下的functions和rtu中所有源文件以及mb.c和mb_m.c
image.png
添加master - port文件夹下所有_m结尾的.c
image.png
添加slave - port文件夹下所有非_m结尾的.c 除开porttcp.c
image.png
添加头文件包含路径
image.png
点击编译会出现错误-应为这是rtthread的文件,我们后续在vscode中修改方便查看
image.png

注释修改rt-thread代码

代码注释

在vscode中注释掉rt-thread的代码然后后续模仿他添加freertos的代码。
首先vscode需要安装 Keil Assistant这个插件添加keil程序位置
image.png
image.png
使用vscode打开整个工程文件夹
image.png
image.png

点击编译通过根据错误提示按ctrl加点击跳转到错误位置注释修改
image.png
所有要修改的文件都在port文件夹里
image.png

修改过程我就不上图了,比较多慢慢注释.在port.h中加入main.h 和加入RT_ASSERT 宏定义,修改 user_mb_app.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
/* -----------------------Slave Defines -------------------------------------*/
#define S_DISCRETE_INPUT_START 0
#define S_DISCRETE_INPUT_NDISCRETES 16
#define S_COIL_START 0
#define S_COIL_NCOILS 64
#define S_REG_INPUT_START 0
#define S_REG_INPUT_NREGS 100
#define S_REG_HOLDING_START 0
#define S_REG_HOLDING_NREGS 100
/* salve mode: holding register's all address */
#define S_HD_RESERVE 0
/* salve mode: input register's all address */
#define S_IN_RESERVE 0
/* salve mode: coil's all address */
#define S_CO_RESERVE 0
/* salve mode: discrete's all address */
#define S_DI_RESERVE 0

/* -----------------------Master Defines -------------------------------------*/
#define M_DISCRETE_INPUT_START 0
#define M_DISCRETE_INPUT_NDISCRETES 16
#define M_COIL_START 0
#define M_COIL_NCOILS 64
#define M_REG_INPUT_START 0
#define M_REG_INPUT_NREGS 100
#define M_REG_HOLDING_START 0
#define M_REG_HOLDING_NREGS 100
/* master mode: holding register's all address */
#define M_HD_RESERVE 0
/* master mode: input register's all address */
#define M_IN_RESERVE 0
/* master mode: coil's all address */
#define M_CO_RESERVE 0
/* master mode: discrete's all address */
#define M_DI_RESERVE 0

模板程序下载

我移除rt-thread 编译无报错的文件,可以对比查看
github https://github.com/freedom413/ModBusDemo/tree/dev

从机移植

port.c

实现了freemodbus的进入临界区实现。
port.h中添加freertos 的头文件

1
2
3
4
5
6
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
#include "semphr.h"
#include "timers.h"

然后在port.c中直接调用freertos的函数,更改代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*判断是否进入在中断中*/
#ifndef IS_IRQ() /*IS_IRQ Start*/
extern __asm uint32_t vPortGetIPSR(void); //调用FreeRTOS API
__inline BOOL IS_IRQ(void) //使用内联函数提高速度
{
return vPortGetIPSR()?TRUE:FALSE;
}
#endif /*IS_IRQ() End*/




void EnterCriticalSection(void)
{
taskENTER_CRITICAL();
}

void ExitCriticalSection(void)
{
taskEXIT_CRITICAL();
}

portevent.c

实现freemodbus的事件标志

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
/* ----------------------- Variables ----------------------------------------*/
static EventGroupHandle_t xSlaveOsEvent;
/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortEventInit(void)
{
xSlaveOsEvent = xEventGroupCreate();
if (xSlaveOsEvent != NULL)
{
MODBUS_DEBUG("xMBPortEventInit Success!\r\n");
return TRUE;
}
MODBUS_DEBUG("xMBPortEventInit Faild !\r\n");
return FALSE;

}

BOOL xMBPortEventPost(eMBEventType eEvent)
{
BaseType_t flag;
MODBUS_DEBUG("Post eEvent=%d!\r\n", eEvent);
if (xSlaveOsEvent != NULL)
{
if (IS_IRQ())
{
xEventGroupSetBitsFromISR(xSlaveOsEvent, eEvent, &flag);
}
else
{
xEventGroupSetBits(xSlaveOsEvent, eEvent);
}
return TRUE;
}
return FALSE;

}

BOOL xMBPortEventGet(eMBEventType *eEvent)
{
uint32_t recvedEvent;
/* waiting forever OS event */
recvedEvent = xEventGroupWaitBits(xSlaveOsEvent,
EV_READY | EV_FRAME_RECEIVED | EV_EXECUTE | EV_FRAME_SENT,
pdTRUE,
pdFALSE,
portMAX_DELAY);
switch (recvedEvent)
{
case EV_READY:
*eEvent = EV_READY;
break;
case EV_FRAME_RECEIVED:
*eEvent = EV_FRAME_RECEIVED;
break;
case EV_EXECUTE:
*eEvent = EV_EXECUTE;
break;
case EV_FRAME_SENT:
*eEvent = EV_FRAME_SENT;
break;
default:
return FALSE;
}
return TRUE;
}

portserial.c

实现freemodbus的串口初始化,串口收发控制,这里写的相对复杂,但理解起来也不难。
其中 static void prvvUARTTxReadyISR(void)static void prvvUARTRxISR(void)分别表示在注册的串口发送完成以及接收到数据时调用。

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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/*----------------------- 静态变量 ---------------------------------*/
/*软件模拟串行传输 IRQ 处理程序线程*/
static TaskHandle_t thread_serial_soft_trans_irq = NULL;
/*串行事件*/
static EventGroupHandle_t event_serial;
/*Modbus 从站串行设备*/
static UART_HandleTypeDef *serial;


/*定义从机串口接收fifo*/
Fifo_t g_Slave_serial_rx_fifo;
/* 定义从机串口接收fifo最大长度 */
#define g_Slave_serial_rx_fifo_maxSize (256)
static uint8_t s_rx_buff[g_Slave_serial_rx_fifo_maxSize] = {0};

/* ----------------------- Defines ------------------------------------------*/

/* 定义串口发送事件位 */
#define EVENT_SERIAL_TRANS_START (1 << 0)

/* ----------------------- static functions ---------------------------------*/
/* freemodbus库回调函数声明*/
static void prvvUARTTxReadyISR(void);

static void prvvUARTRxISR(void);

/*自定义函数声明*/
static BOOL stm32_putc(CHAR c);
static BOOL stm32_getc(CHAR *c);
static void Slave_RxCpltCallback(UART_HandleTypeDef *huart);

/*串口传输任务声明*/
static void serial_soft_trans_irq(void *parameter);

/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits,
eMBParity eParity)
{
/* set serial name */
if (ucPORT == 1)
{
serial = &huart1;
MODBUS_DEBUG("Slave using uart1!\r\n");
}
else if (ucPORT == 2)
{
serial = &huart2;
MODBUS_DEBUG("Slave using uart2!\r\n");
}
else if (ucPORT == 3)
{
serial = &huart3;
MODBUS_DEBUG("Slave using uart3!\r\n");
}

/* set serial configure */
serial->Init.StopBits = UART_STOPBITS_1;
serial->Init.BaudRate = ulBaudRate;
switch (eParity)
{
case MB_PAR_NONE:
{
serial->Init.WordLength = UART_WORDLENGTH_8B;
serial->Init.Parity = UART_PARITY_NONE;
break;
}
case MB_PAR_ODD:
{
serial->Init.WordLength = UART_WORDLENGTH_9B;
serial->Init.Parity = UART_PARITY_ODD;
break;
}
case MB_PAR_EVEN:
{
serial->Init.WordLength = UART_WORDLENGTH_9B;
serial->Init.Parity = UART_PARITY_EVEN;
break;
}
}
if (HAL_UART_Init(serial) != HAL_OK)
{
Error_Handler();
}
__HAL_UART_DISABLE_IT(serial, UART_IT_RXNE);
__HAL_UART_DISABLE_IT(serial, UART_IT_TC);
/*registe recieve callback*/
HAL_UART_RegisterCallback(serial, HAL_UART_RX_COMPLETE_CB_ID,
Slave_RxCpltCallback);
/* software initialize */

/* fifo初始化 */
FifoInit(&g_Slave_serial_rx_fifo, s_rx_buff, g_Slave_serial_rx_fifo_maxSize);

/* 创建串口事件组 */
event_serial = xEventGroupCreate();
if (NULL != event_serial)
{
MODBUS_DEBUG("Create Slave event_serial Event success!\r\n");
}
else
{
MODBUS_DEBUG("Create Slave event_serial Event Faild!\r\n");
}

/* 创建串口处理任务 */
BaseType_t xReturn =
xTaskCreate((TaskFunction_t)serial_soft_trans_irq,
(const char *)"slave trans",
(uint16_t)128,
(void *)NULL,
(UBaseType_t)12,
(TaskHandle_t *)&thread_serial_soft_trans_irq);

if (xReturn == pdPASS)
{
MODBUS_DEBUG("xTaskCreate slave trans success\r\n");
}
else
{
MODBUS_DEBUG("xTaskCreate slave trans Faild!\r\n");
}
return TRUE;
}

void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{
__HAL_UART_CLEAR_FLAG(serial, UART_FLAG_RXNE);
__HAL_UART_CLEAR_FLAG(serial, UART_FLAG_TC);
if (xRxEnable)
{
/* enable RX interrupt */
__HAL_UART_ENABLE_IT(serial, UART_IT_RXNE);
}
else
{
/* disable RX interrupt */
__HAL_UART_DISABLE_IT(serial, UART_IT_RXNE);
}
if (xTxEnable)
{
/* start serial transmit */
xEventGroupSetBits(event_serial, EVENT_SERIAL_TRANS_START);
}
else
{
/* stop serial transmit */
xEventGroupClearBits(event_serial, EVENT_SERIAL_TRANS_START);
}
}

void vMBPortClose(void)
{
__HAL_UART_DISABLE(serial);
}
BOOL xMBPortSerialPutByte(CHAR ucByte)
{
return stm32_putc(ucByte);
}

BOOL xMBPortSerialGetByte(CHAR *pucByte)
{
if(FifoOut(&g_Slave_serial_rx_fifo,(uint8_t *)pucByte,1) != 0)
{
return TRUE;
}else
{
return FALSE;
}
}

/*
* 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.
*/
static 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.
*/
static void prvvUARTRxISR(void)
{
pxMBFrameCBByteReceived();
}
/**
* Software simulation serial transmit IRQ handler.
*
* @param parameter parameter
*/
static void serial_soft_trans_irq(void *parameter)
{
while (1)
{
/* waiting for serial transmit start */
xEventGroupWaitBits(event_serial,
EVENT_SERIAL_TRANS_START,
pdFALSE,
pdFALSE,
portMAX_DELAY);
/* execute modbus callback */
prvvUARTTxReadyISR();
}
}

/* 从机串口接收中断回调*/
static void Slave_RxCpltCallback(UART_HandleTypeDef *huart)
{
CHAR ch;
while (1)
{
if (stm32_getc(&ch))
{
FifoIn(&g_Slave_serial_rx_fifo, (uint8_t *)&ch, 1);
}
else
{
break;
}
}
prvvUARTRxISR();
}

/*UART阻塞式发送*/
static BOOL stm32_putc(CHAR c)
{
serial->Instance->DR = c;
while (!(serial->Instance->SR & UART_FLAG_TC))
;
return TRUE;
}
/*UART接收*/
static BOOL stm32_getc(CHAR *c)
{
if (serial->Instance->SR & UART_FLAG_RXNE)
{
*c = serial->Instance->DR & 0xff; /* 读取DR寄存器可以清除RXNE标志位*/
return TRUE;
}
return FALSE;
}

porttimer.c

实现freemodbus的帧间隔检测。
其中当波特率大于19200帧间隔固体为1750us 也就是35个50us。当波特率小于19200通过公式换算出是多少个50us。其判断源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* If baudrate > 19200 then we should use the fixed timer values
* t35 = 1750us. Otherwise t35 must be 3.5 times the character time.
*/
if( ulBaudRate > 19200 )
{
usTimerT35_50us = 35; /* 1800us. */
}
else
{
/* The timer reload value for a character is given by:
*
* ChTimeValue = Ticks_per_1s / ( Baudrate / 11 )
* = 11 * Ticks_per_1s / Baudrate
* = 220000 / Baudrate
* The reload for t3.5 is 1.5 times this value and similary
* for t3.5.
*/
usTimerT35_50us = ( 7UL * 220000UL ) / ( 2UL * ulBaudRate );
}
if( xMBPortTimersInit( ( USHORT ) usTimerT35_50us ) != TRUE )
{
eStatus = MB_EPORTERR;
}

整个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
48
49
50
51
52
53
54
/* ----------------------- static functions ---------------------------------*/
static TimerHandle_t timer;

void prvvTIMERExpiredISR(void);
void timer_timeout_ind(TimerHandle_t parameter);

/* ----------------------- Start implementation -----------------------------*/
BOOL xMBPortTimersInit(USHORT usTim1Timerout50us /* modbus 会根据初始化的波特率来计算出间隔时间是多少个50us*/)
{
timer = xTimerCreate(
"Slave timer",
(50 * usTim1Timerout50us/*计算总us数*/) / (1000/*us->ms */ * 1000 / configTICK_RATE_HZ /* 计算 1 tick的时间*/) + 1/*向上取整ms*/,
pdFALSE,
(void *)2,
timer_timeout_ind);
if (timer == NULL)
return FALSE;
return TRUE;
}

void vMBPortTimersEnable()
{
if (IS_IRQ())
{
xTimerStartFromISR((TimerHandle_t)timer, 0);
}
else
{
xTimerStart((TimerHandle_t)timer, 0);
}
}
void vMBPortTimersDisable()
{
if (IS_IRQ())
{
xTimerStopFromISR((TimerHandle_t)timer, 0);
}
else
{
xTimerStop((TimerHandle_t)timer, 0);
}
}

void prvvTIMERExpiredISR(void)
{
pxMBPortCBTimerExpired(); /*freemodbus库回调函数*/
}

/*freertos 软件定时器回调*/
void timer_timeout_ind(TimerHandle_t parameter)
{
prvvTIMERExpiredISR();
}

主机移植

其实主机和从机的移植代码基本一致,主要表现在portevent_m.c中此源码中添加了关于主机收到从机响应的事件标志,还有防止主机处理数据被打断的信号量。那portserial_m.c的代码就不展示了

portevent.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
/**
* This function is initialize the OS resource for modbus master.
* Note:The resource is define by OS.If you not use OS this function can be empty.
*
*/
void vMBMasterOsResInit(void)
{
xMasterRunRes = xSemaphoreCreateBinary();
}

/**
* This function is take Mobus Master running resource.
* Note:The resource is define by Operating System.If you not use OS this function can be just return TRUE.
*
* @param lTimeOut the waiting time.
*
* @return resource taked result
*/
BOOL xMBMasterRunResTake(LONG lTimeOut)
{
/*If waiting time is -1 .It will wait forever */
if (lTimeOut == -1)
{
return xSemaphoreTake(xMasterRunRes, portMAX_DELAY);
}

return xSemaphoreTake(xMasterRunRes, lTimeOut);
}

/**
* This function is release Mobus Master running resource.
* Note:The resource is define by Operating System.If you not use OS this function can be empty.
*
*/
void vMBMasterRunResRelease(void)
{
/* release resource */
xSemaphoreGive(xMasterRunRes);
}

主机处理流程事件,以及请求从机响应事件的标志位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef enum
{
EV_READY = 1<<0,/*!< 启动已完成。*/
EV_FRAME_RECEIVED = 1<<1,/*!< 已接收帧。*/
EV_EXECUTE = 1<<2,/*!< 执行函数。*/
EV_FRAME_SENT = 1<<3/*!< 帧已发送。*/
} eMBEventType;

typedef enum
{
EV_MASTER_READY = 1<<0,/*!< 启动已完成。*/
EV_MASTER_FRAME_RECEIVED = 1<<1,/*!< 已接收帧。*/
EV_MASTER_EXECUTE = 1<<2,/*!< 执行函数。*/
EV_MASTER_FRAME_SENT = 1<<3,/*!< 帧已发送。*/
EV_MASTER_ERROR_PROCESS = 1<<4,/*!< 帧错误过程。*/
EV_MASTER_PROCESS_SUCESS = 1<<5,/*!< 请求进程成功。*/
EV_MASTER_ERROR_RESPOND_TIMEOUT = 1<<6,/*!< 请求响应超时。*/
EV_MASTER_ERROR_RECEIVE_DATA = 1<<7,/*!< 请求接收数据错误。*/
EV_MASTER_ERROR_EXECUTE_FUNCTION = 1<<8,/*!< 请求执行函数错误。*/
} eMBMasterEventType;

事件代码如下

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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
* This is modbus master respond timeout error process callback function.
* @note There functions will block modbus master poll while execute OS waiting.
* So,for real-time of system.Do not execute too much waiting process.
*
* @param ucDestAddress destination salve address
* @param pucPDUData PDU buffer data
* @param ucPDULength PDU buffer length
*
*/
void vMBMasterErrorCBRespondTimeout(UCHAR ucDestAddress, const UCHAR *pucPDUData,
USHORT ucPDULength)
{
/**
* @note This code is use OS's event mechanism for modbus master protocol stack.
* If you don't use OS, you can change it.
*/
xEventGroupSetBits(xMasterOsEvent, EV_MASTER_ERROR_RESPOND_TIMEOUT);

/* You can add your code under here. */
}

/**
* This is modbus master receive data error process callback function.
* @note There functions will block modbus master poll while execute OS waiting.
* So,for real-time of system.Do not execute too much waiting process.
*
* @param ucDestAddress destination salve address
* @param pucPDUData PDU buffer data
* @param ucPDULength PDU buffer length
*
*/
void vMBMasterErrorCBReceiveData(UCHAR ucDestAddress, const UCHAR *pucPDUData,
USHORT ucPDULength)
{
/**
* @note This code is use OS's event mechanism for modbus master protocol stack.
* If you don't use OS, you can change it.
*/
xEventGroupSetBits(xMasterOsEvent, EV_MASTER_ERROR_RECEIVE_DATA);

/* You can add your code under here. */
}

/**
* This is modbus master execute function error process callback function.
* @note There functions will block modbus master poll while execute OS waiting.
* So,for real-time of system.Do not execute too much waiting process.
*
* @param ucDestAddress destination salve address
* @param pucPDUData PDU buffer data
* @param ucPDULength PDU buffer length
*
*/
void vMBMasterErrorCBExecuteFunction(UCHAR ucDestAddress, const UCHAR *pucPDUData,
USHORT ucPDULength)
{
/**
* @note This code is use OS's event mechanism for modbus master protocol stack.
* If you don't use OS, you can change it.
*/
xEventGroupSetBits(xMasterOsEvent, EV_MASTER_ERROR_EXECUTE_FUNCTION);

/* You can add your code under here. */
}

/**
* This is modbus master request process success callback function.
* @note There functions will block modbus master poll while execute OS waiting.
* So,for real-time of system.Do not execute too much waiting process.
*
*/
void vMBMasterCBRequestScuuess(void)
{
/**
* @note This code is use OS's event mechanism for modbus master protocol stack.
* If you don't use OS, you can change it.
*/
xEventGroupSetBits(xMasterOsEvent, EV_MASTER_PROCESS_SUCESS);

/* You can add your code under here. */
}

/**
* This function is wait for modbus master request finish and return result.
* Waiting result include request process success, request respond timeout,
* receive data error and execute function error.You can use the above callback function.
* @note If you are use OS, you can use OS's event mechanism. Otherwise you have to run
* much user custom delay for waiting.
*
* @return request error code
*/
eMBMasterReqErrCode eMBMasterWaitRequestFinish(void)
{
eMBMasterReqErrCode eErrStatus = MB_MRE_NO_ERR;
uint32_t recvedEvent;
/* waiting for OS event */
recvedEvent = xEventGroupWaitBits(
xMasterOsEvent, /* 事件对象句柄 */
EV_MASTER_PROCESS_SUCESS | EV_MASTER_ERROR_RESPOND_TIMEOUT |
EV_MASTER_ERROR_RECEIVE_DATA |
EV_MASTER_ERROR_EXECUTE_FUNCTION, /* 接收任务感兴趣的事件
*/
pdTRUE, /* 退出时清除事件�? */
pdFALSE, /* 满足感兴趣的所有事�? */
portMAX_DELAY); /* 指定超时事件,无限等待 */
/* the enum type couldn't convert to int type */
switch (recvedEvent)
{
case EV_MASTER_PROCESS_SUCESS:
break;
case EV_MASTER_ERROR_RESPOND_TIMEOUT:
{
eErrStatus = MB_MRE_TIMEDOUT;
break;
}
case EV_MASTER_ERROR_RECEIVE_DATA:
{
eErrStatus = MB_MRE_REV_DATA;
break;
}
case EV_MASTER_ERROR_EXECUTE_FUNCTION:
{
eErrStatus = MB_MRE_EXE_FUN;
break;
}
}
return eErrStatus;
}

porttimer_m.c

在porttimer_m.c中多了三个函数用于开启最小帧间隔时间1750us、转换延时时间、信号量等待超时时间计时,代码如下

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
void vMBMasterPortTimersT35Enable()
{

uint32_t timer_tick = (50 * usT35TimeOut50us) / (1000 * 1000 / configTICK_RATE_HZ) + 1;
vMBMasterSetCurTimerMode(MB_TMODE_T35);
if (IS_IRQ())
{
xTimerChangePeriodFromISR((TimerHandle_t)timer, timer_tick, &pxHigherPriorityTaskWoken);
}
else
{
xTimerChangePeriod((TimerHandle_t)timer, timer_tick, 0);
}
}

void vMBMasterPortTimersConvertDelayEnable()
{
uint32_t timer_tick = MB_MASTER_DELAY_MS_CONVERT * configTICK_RATE_HZ / 1000;
vMBMasterSetCurTimerMode(MB_TMODE_CONVERT_DELAY);
if (IS_IRQ())
{
xTimerChangePeriodFromISR((TimerHandle_t)timer, timer_tick, &pxHigherPriorityTaskWoken);
}
else
{
xTimerChangePeriod((TimerHandle_t)timer, timer_tick, 0);
}
}

void vMBMasterPortTimersRespondTimeoutEnable()
{
uint32_t timer_tick = MB_MASTER_TIMEOUT_MS_RESPOND * configTICK_RATE_HZ / 1000;
vMBMasterSetCurTimerMode(MB_TMODE_RESPOND_TIMEOUT);
if (IS_IRQ())
{
xTimerChangePeriodFromISR((TimerHandle_t)timer, timer_tick, &pxHigherPriorityTaskWoken);
}
else
{
xTimerChangePeriod((TimerHandle_t)timer, timer_tick, 0);
}
}

在主程序调用

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
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
eMBInit(MB_RTU, 0x01, 3, 9600, MB_PAR_NONE);
eMBEnable();
eMBMasterInit(MB_RTU, 2, 9600, MB_PAR_NONE);
eMBMasterEnable();
/* USER CODE END Init */
}
/* USER CODE END Header_MasterTaskFun */
void MasterTaskFun(void *argument)
{
/* USER CODE BEGIN MasterTaskFun */
/* Infinite loop */
for(;;)
{
eMBMasterPoll();
}
/* USER CODE END MasterTaskFun */
}

/* USER CODE END Header_Slave_TaskFun */
__weak void Slave_TaskFun(void *argument)
{
/* USER CODE BEGIN Slave_TaskFun */
/* Infinite loop */
for(;;)
{
eMBPoll();
}
/* USER CODE END Slave_TaskFun */
}

//每间隔1s将从机保持寄存器加1
/* USER CODE END Header_MasterSendTaskFun */
void MasterSendTaskFun(void *argument)
{
/* USER CODE BEGIN MasterSendTaskFun */
uint16_t data[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
/* Infinite loop */
for(;;)
{
eMBMasterReqWriteMultipleHoldingRegister(1, 0, 10, data, 100);
for (uint16_t i = 0; i < sizeof(data)/sizeof(data[0]); i++)
{
data[i] += 1;
}

osDelay(1000);
}
/* USER CODE END MasterSendTaskFun */
}

至此从机和主机都已经移植好了,我的完整源码也先放在下面
完整移植代码仓库 https://github.com/freedom413/ModBusDemo.git 包含modbus poll 和 modbus slave

测试

我们来打开modbus poll软件了测试一下从机功能是否完好,在这之前先设置一下从机保持寄存器的默认值
在user_mb_app.c中给保持寄存器赋初始值

1
2
3
//Slave mode:HoldingRegister variables
USHORT usSRegHoldStart = S_REG_HOLDING_START;
USHORT usSRegHoldBuf[S_REG_HOLDING_NREGS] = {1,2,3,4,5,6,7,8,9,10,11,12} ;

然后设置mobus poll(模拟主机)软件的连接参数
image.png
可以看到已经读出了我们设置的数据了,证明从机没有问题
image.png

我们在来看看主机呢,主机写从机的代码已经专门创建了一个任务来写1号id从机的保持寄存器每秒加一,直接打开modbus slave(模拟从机)
image.png
可以看到保持寄存确实在每秒加1,证明主机的移植也是没有问题的
image.png

哈哈了🙆‍♀️💘下次准备记录一下使用标准库移植freertos,然后写一些常见的组件,如任务管理、队列、信号量、事件组、以及任务通知。又立了一个flag😒。

end 你好,祝你开开心心,拜拜了🤞