使用Platformio平台的libopencm3开发框架来开发STM32G0,以下为FreeRTOS和FreeModbus库使用。

1 新建项目

  • 建立freertos_modbus项目

在PIO的Home页面新建项目,项目名称freertos_modbus,选择开发板为 MonkeyPi_STM32_G070RB,开发框架选择libopencm3;

  • 项目建立完成后在src目录下新建main.c主程序文件;
  • 修改下载和调试方式,这里开发板使用的是DAPLink仿真器,因此修改platformio.ini文件如下:
1
2
upload_protocol = cmsis-dap
debug_tool = cmsis-dap

2 编写程序

直接在之前的FreeRTOS工程上进行添加;

2.1 添加 freeModbus 库

从git仓库下载源码: https://github.com/cwalter-at/freemodbus

将下载的源码中的mobus文件夹放置到工程的lib目录下,然后在modbus目录新建library.json文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"name": "FreeModbus",
"version": "master",
"repository":{
"type":"git",
"url":"https://github.com/cwalter-at/freemodbus"
},
"build": {
"flags": [
"-Iascii",
"-Ifunctions",
"-Iinclude",
"-Irtu",
"-Itcp"
],
"srcFilter": [
"+<*>"
]
}
}

然后从FreeModbus源码中的 demo\BARE\port中复制文件到工程的src\modbus_port文件夹下,最后的文件夹结构如下:

2.2 移植
  • portevent:
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
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "FreeRTOS.h"
#include "task.h"

/* ----------------------- Variables ----------------------------------------*/
static eMBEventType eQueuedEvent;
static BOOL xEventInQueue;
static uint32_t modbus_last_active_time = 0;

uint32_t get_modbus_last_active_time(void)
{
return modbus_last_active_time;
}

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortEventInit( void )
{
xEventInQueue = FALSE;
return TRUE;
}

BOOL
xMBPortEventPost( eMBEventType eEvent )
{
xEventInQueue = TRUE;
eQueuedEvent = eEvent;

if (eEvent == EV_EXECUTE) {
modbus_last_active_time = xTaskGetTickCount();
}
return TRUE;
}

BOOL
xMBPortEventGet( eMBEventType * eEvent )
{
BOOL xEventHappened = FALSE;

if( xEventInQueue )
{
*eEvent = eQueuedEvent;
xEventInQueue = FALSE;
xEventHappened = TRUE;
}
return xEventHappened;
}
  • portserial

这里使用RS485,因此需要对RS485使能端口进行配置,其他为串口的配置,然后在发送和接收中断时候调用modbus相关接口进行处理:

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
#include "port.h"

#include "FreeRTOS.h"
#include "queue.h"

#include <libopencm3/cm3/nvic.h>
#include <libopencm3/stm32/usart.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>

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

/* ----------------------- static functions ---------------------------------*/

xQueueHandle uart_queue;

#define RS485_1_CLOCK RCC_GPIOB
#define RS485_1_EN_PORT GPIOB
#define RS485_1_EN_PIN GPIO8

static void rs485_delay(int n)
{
while (--n) {
__asm__ volatile ("nop");
}
}

static inline void rs485_1_rx_mode(void)
{
gpio_clear(RS485_1_EN_PORT, RS485_1_EN_PIN);
}

static inline void rs485_1_tx_mode(void)
{
gpio_set(RS485_1_EN_PORT, RS485_1_EN_PIN);
}

static inline void rs485_gpio_init(void)
{
rcc_periph_clock_enable(RS485_1_CLOCK);
gpio_mode_setup(RS485_1_EN_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, RS485_1_EN_PIN);

rs485_1_rx_mode();
}

/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable
* transmitter empty interrupts.
*/
if (xRxEnable) {
rs485_delay(10000);
rs485_1_rx_mode();
rs485_delay(10000);
usart_enable_rx_interrupt(USART1);
}
else {
usart_disable_rx_interrupt(USART1);
}

if (xTxEnable) {
rs485_delay(10000);
rs485_1_tx_mode();
rs485_delay(10000);
usart_enable_tx_interrupt(USART1);
}
else {
usart_disable_tx_interrupt(USART1);

}
}

BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
nvic_enable_irq(NVIC_USART1_IRQ);

rcc_periph_clock_enable(RCC_GPIOB);
gpio_mode_setup(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO6 | GPIO7);
gpio_set_af(GPIOB, GPIO_AF0, GPIO6 | GPIO7);

rcc_periph_clock_enable(RCC_USART1);

/* Set up USART/UART parameters using the libopencm3 helper functions */
usart_set_baudrate(USART1, ulBaudRate);
usart_set_databits(USART1, ucDataBits);
usart_set_stopbits(USART1, USART_STOPBITS_1);
usart_set_mode(USART1, USART_MODE_TX_RX);

switch (eParity) {
case MB_PAR_ODD:
usart_set_parity(USART1, USART_PARITY_ODD);
break;
case MB_PAR_EVEN:
usart_set_parity(USART1, USART_PARITY_EVEN);
break;
default:
usart_set_parity(USART1, USART_PARITY_NONE);
break;
}

usart_set_flow_control(USART1, USART_FLOWCONTROL_NONE);

usart_enable(USART1);

rs485_gpio_init();

return TRUE;
}

BOOL
xMBPortSerialPutByte( CHAR ucByte )
{

usart_send_blocking(USART1, (uint16_t) ucByte);

return TRUE;
}

BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
*pucByte = usart_recv(USART1);

return TRUE;
}


uint32_t uart1_isr, uart1_icr;

void usart1_isr(void)
{

/* Check if we were called because of RXNE. */
if (((USART_CR1(USART1) & USART_CR1_RXNEIE) != 0) &&
((USART_ISR(USART1) & USART_ISR_RXNE) != 0)) {

/* Retrieve the data from the peripheral. */
// usart_recv(USART1);

pxMBFrameCBByteReceived();

}


/* Check if we were called because of TXE. */
if (((USART_CR1(USART1) & USART_CR1_TXEIE) != 0) &&
((USART_ISR(USART1) & USART_ISR_TXE) != 0)) {

/* Put data into the transmit register. */
//usart_send(USART1, data);

pxMBFrameCBTransmitterEmpty();

}

}
  • porttimer
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
#include "port.h"

#include <libopencm3/cm3/nvic.h>
#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/timer.h>

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

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

/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
rcc_periph_clock_enable(RCC_TIM2);
nvic_enable_irq(NVIC_TIM2_IRQ);
rcc_periph_reset_pulse(RST_TIM2);

timer_set_mode(TIM2, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);

timer_set_prescaler(TIM2, (rcc_apb1_frequency/ 20000));

timer_disable_preload(TIM2);
timer_continuous_mode(TIM2);

timer_set_period(TIM2, usTim1Timerout50us);
timer_enable_counter(TIM2);

timer_enable_irq(TIM2, TIM_DIER_UIE);

return TRUE;
}


inline void
vMBPortTimersEnable( )
{
timer_set_counter(TIM2, 0);
timer_enable_counter(TIM2);
}

inline void
vMBPortTimersDisable( )
{
timer_disable_counter(TIM2);
}

/* 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.
*/
static void prvvTIMERExpiredISR( void )
{
( void )pxMBPortCBTimerExpired( );
}

void
vMBPortTimersDelay( USHORT usTimeOutMS )
{
vTaskDelay(pdMS_TO_TICKS(usTimeOutMS));
}

void tim2_isr(void)
{
if (timer_get_flag(TIM2, TIM_SR_UIF)) {

/* Clear compare interrupt flag. */
timer_clear_flag(TIM2, TIM_SR_UIF);

prvvTIMERExpiredISR();

}
}

开启定时器和中断,用于modbus时序控制;

2.3 使用

在src目录新建 modbus_cb.h 和 modbus_cb.c 两个文件,实现寄存器、线圈的读写回调:

1
2
3
4
5
6
7
8
9
10
11
/// CMD4
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs );

/// CMD6、3、16
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode );

/// CMD1、5、15
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode );

/// CMD4
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete );

基本的实现示例如下:

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
#include "modbus_cb.h"
#include "stdbool.h"

extern log(const char* fmt, ...);

// 输入寄存器
#define REG_INPUT_SIZE 32
uint16_t REG_INPUT_BUF[REG_INPUT_SIZE];

// 保持寄存器
#define REG_HOLD_SIZE 32
uint16_t REG_HOLD_BUF[REG_HOLD_SIZE];

// 线圈寄存器
#define REG_COILS_SIZE 16
uint8_t REG_COILS_BUF[REG_COILS_SIZE];

// 离散量
#define REG_DISC_SIZE 8
uint8_t REG_DISC_BUF[REG_DISC_SIZE];

/// CMD4
eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
USHORT usRegIndex = usAddress - 1;

// 非法检测
if((usRegIndex + usNRegs) > REG_INPUT_SIZE)
{
return MB_ENOREG;
}

log(" CMD4, 寄存器输入.");

// 填充数据
REG_INPUT_BUF[0] = 0x01;
REG_INPUT_BUF[1] = 0x02;

// 循环读取
while ( usNRegs > 0 ) {
*pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( REG_INPUT_BUF[usRegIndex] & 0xFF );
usRegIndex++;
usNRegs--;
}

return MB_ENOERR;
}

/// CMD6、3、16
eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{
USHORT usRegIndex = usAddress - 1;

// 非法检测
if((usRegIndex + usNRegs) > REG_HOLD_SIZE) {
return MB_ENOREG;
}

log(" CMD3,6,16, 保持寄存器读写.");

// 写寄存器
if (eMode == MB_REG_WRITE) {
while ( usNRegs > 0 ) {
uint16_t value;

value = (pucRegBuffer[0] << 8) | pucRegBuffer[1];

log(" 写寄存器值:%d", value);

pucRegBuffer += 2;
usRegIndex++;
usNRegs--;

}

}
// 读寄存器
else {

log(" 读寄存器.");

REG_HOLD_BUF[0] = 0x32;
REG_HOLD_BUF[1] = 0x33;

while ( usNRegs > 0 ) {
*pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( REG_HOLD_BUF[usRegIndex] & 0xFF );
usRegIndex++;
usNRegs--;
}
}

return MB_ENOERR;
}

/// CMD1、5、15
eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{

USHORT usRegIndex = usAddress - 1;
USHORT usCoilGroups = ((usNCoils - 1) / 8 + 1);
UCHAR ucStatus = 0;
UCHAR ucBits = 0;
UCHAR ucDisp = 0;

// 非法检测
if ((usRegIndex + usNCoils) > REG_COILS_SIZE) {
return MB_ENOREG;
}

log(" CMD1,5,15, 线圈读写.");

// 写线圈
if (eMode == MB_REG_WRITE) {

while (usCoilGroups--) {

ucStatus = *pucRegBuffer++;
ucBits = 8;

while((usNCoils) != 0 && (ucBits) != 0) {
bool flag = ucStatus & 0x01;

switch (usRegIndex) {

case 0:
log(" 线圈0 : %d", flag);//
break;

case 1:
log(" 线圈1 : %d", flag);
break;

default:

break;

}

usRegIndex++;
ucStatus >>= 1;
usNCoils--;
ucBits--;
}

}
}
// 读线圈
else {

REG_COILS_BUF[0] = 1;
REG_COILS_BUF[1] = 0;

while (usCoilGroups--) {
ucDisp = 0;
ucBits = 8;
ucStatus = 0;

while((usNCoils) != 0 && (ucBits) != 0) {
ucStatus |= (REG_COILS_BUF[usRegIndex++] << (ucDisp++));
usNCoils--;
ucBits--;
}

*pucRegBuffer++ = ucStatus;
}
}

return MB_ENOERR;
}

/// CMD4
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
USHORT usRegIndex = usAddress - 1;
USHORT usCoilGroups = ((usNDiscrete - 1) / 8 + 1);
UCHAR ucStatus = 0;
UCHAR ucBits = 0;
UCHAR ucDisp = 0;

// 非法检测
if ((usRegIndex + usNDiscrete) > REG_DISC_SIZE) {
return MB_ENOREG;
}

log(" CMD4, 离散寄存器写入.");

// 读离散输入
while (usCoilGroups--) {
ucDisp = 0;
ucBits = 8;
ucStatus = 0;

while((usNDiscrete != 0) && (ucBits != 0))
{
switch (usRegIndex) {
case 0:
ucStatus = 0x10;
break;
}

usRegIndex++;
ucDisp++;
usNDiscrete--;
ucBits--;
}
*pucRegBuffer++ = ucStatus;
}

return MB_ENOERR;
}

在main中创建modbus任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void task_modbus_handle(void *param)
{

eMBErrorCode eStatus;

log(" task modbus start.");

eStatus = eMBInit( MB_RTU, 0x01, 0, 9600, MB_PAR_NONE );

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

(void)eStatus;

for( ;; ) {
( void )eMBPoll();
vTaskDelay(pdMS_TO_TICKS(10));
}

}

3 烧写测试

将开发板连接到USB转485模块,然后使用modbus poll程序进行测试: