起因
- 2021.06.24 联调时发现4G模块数传不正常,后来发现是由于MQTT协议只支持ASCII数据传输,不支持二进制数据;
- 之前使用的 Modbus RTU 协议是二进制数据,因此打算改用 Modbus ASCII 协议。
- 此时设备端使用的第三方 Modbus Middleware 不支持ASCII协议,需要更换,经过调研后选用了FreeModbus,在此顺便记录一下移植过程。
FreeModbus源代码获取
- 关于FreeModbus,同时这个网站还提供了一些说明和例程,可供参考
- Embedded-Experts提供的下载
详细移植过程
移植步骤参考FreeModbus API文档
1. 添加源代码
- 在STM32CubeIDE上创建一个名为
FreeModbusF103
的新工程,并配置好UART1等外设 - 将下载的源代码拷贝到
./Middlewares/Third_Party/
下 - 由于
demo
文件夹中是各种不同硬件平台的移植源代码和例程,我们不使用,将其屏蔽掉 - 协议源代码都在
modbus
文件夹下,将有.h
文件的路径添加到include path;因为我们不使用 modbus over tcp ,所以将tcp
文件夹屏蔽掉 demo
文件夹中有一个BARE
文件夹,这是一个平台无关的空示例,包括移植文件和示例文件,我们将其中的port
文件夹拷贝出来(别忘记添加到include path),后面移植完成以后再参考demo.c
文件写自己的应用代码
2. port.h移植
- 添加外设头文件及宏定义
#include "usart.h"
#include "tim.h"
#define MB_UART (&huart1)
#define MB_TIM (&htim3)
- 移植临界区操作宏函数
#define ENTER_CRITICAL_SECTION( ) taskENTER_CRITICAL()
#define EXIT_CRITICAL_SECTION( ) taskEXIT_CRITICAL()
- 添加中断服务函数接口
extern void modbus_serial_irq(void);
extern void modbus_timer_irq(void);
- 到这里头文件移植就完成了
3. portserial.c接口移植
- 在CubeMX中将指定的串口外设参数设置好
portserial.c
文件中串口通信相关的接口函数,需要我们用HAL库实现- 串口中断使能
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable
* transmitter empty interrupts.
*/
if(xRxEnable)
{
__HAL_UART_ENABLE_IT(MB_UART,UART_IT_RXNE);
}
else
{
__HAL_UART_DISABLE_IT(MB_UART,UART_IT_RXNE);
}
if(xTxEnable)
{
__HAL_UART_ENABLE_IT(MB_UART,UART_IT_TXE);
}
else
{
__HAL_UART_DISABLE_IT(MB_UART,UART_IT_TXE);
}
}
- 串口初始化
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
//忽略参数
UNUSED(ucPORT);
UNUSED(ulBaudRate);
UNUSED(ucDataBits);
UNUSED(eParity);
//以MX配置为准
MX_USART1_UART_Init();
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. */
return (HAL_UART_Transmit(MB_UART, (uint8_t*)&ucByte, 1, 1) == HAL_OK) ? TRUE : FALSE;
}
- 串口接收
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.
*/
return (HAL_UART_Receive(MB_UART, (uint8_t*)pucByte, 1, 1) == HAL_OK) ? TRUE : FALSE;
}
- 添加中断处理函数
//中断处理
void modbus_serial_irq(void){
if(__HAL_UART_GET_IT_SOURCE(MB_UART, UART_IT_RXNE) != RESET){
prvvUARTRxISR();
__HAL_UART_CLEAR_FLAG(MB_UART, UART_IT_RXNE);
}
if(__HAL_UART_GET_IT_SOURCE(MB_UART, UART_IT_TXE) != RESET){
prvvUARTTxReadyISR();
__HAL_UART_CLEAR_FLAG(MB_UART, UART_IT_TXE);
}
}
- 到这里串口的移植就完成了
4. porttimer.c接口移植
- 在CubeMX中将指定的定时器外设参数设置好
porttimer.c
文件中定时器相关的接口函数,需要我们用HAL库实现- 定时器初始化
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
MX_TIM3_Init();
__HAL_TIM_SET_AUTORELOAD(MB_TIM,usTim1Timerout50us-1);
__HAL_TIM_CLEAR_FLAG(MB_TIM, TIM_FLAG_UPDATE);
__HAL_TIM_ENABLE_IT(MB_TIM, TIM_IT_UPDATE);
return TRUE;
}
- 定时器使能
inline void
vMBPortTimersEnable( )
{
/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
__HAL_TIM_SET_COUNTER(&htim3, 0);
HAL_TIM_Base_Start(&htim3);
}
- 定时器失能
inline void
vMBPortTimersDisable( )
{
/* Disable any pending timers. */
HAL_TIM_Base_Stop(&htim3);
}
- 添加中断处理函数
//中断处理
void modbus_timer_irq(void){
if(__HAL_TIM_GET_IT_SOURCE(MB_TIM, TIM_IT_UPDATE) != RESET){
prvvTIMERExpiredISR();
__HAL_TIM_CLEAR_FLAG(MB_TIM, TIM_IT_UPDATE);
}
}
- 到这里定时器的移植就完成了
5. 添加中断处理
- 在
stm32f1xx_it.c
中生成的串口、定时器中断处理中添加我们上面写好的中断处理函数
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
modbus_serial_irq();
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
/**
* @brief This function handles TIM3 global interrupt.
*/
void TIM3_IRQHandler(void)
{
/* USER CODE BEGIN TIM3_IRQn 0 */
modbus_timer_irq();
/* USER CODE END TIM3_IRQn 0 */
HAL_TIM_IRQHandler(&htim3);
/* USER CODE BEGIN TIM3_IRQn 1 */
/* USER CODE END TIM3_IRQn 1 */
}
- 到这里中断处理的移植就完成了
6. 移植寄存器操作接口并创建协议栈线程
- 这个部分可以在用户的应用代码中编写,这里集中写在
portregs.c
作为示范 - 在CubeMX中添加FreeRTOS线程
StartFreeModbusTask
的定义,协议栈就会自动创建运行,响应串口的请求 - ASCII和RTU的源代码可以在
modbus/mbconfig.h
中进行裁剪
/*
* portregs.c
*
* Created on: Jun 27, 2021
* Author: soliber
*/
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"
/* ----------------------- Defines ------------------------------------------*/
#define REG_INPUT_START 1 //输入寄存器PLC地址
#define REG_INPUT_NREGS 50 //输入寄存器数量
#define REG_HOLDING_START 1 //保持寄存器PLC地址
#define REG_HOLDING_NREGS 50 //保持寄存器数量
/* ----------------------- Static variables ---------------------------------*/
static USHORT usRegInputStart = REG_INPUT_START;
static USHORT usRegInputBuf[REG_INPUT_NREGS];
static USHORT usRegHoldingStart = REG_HOLDING_START;
static USHORT usRegHoldingBuf[REG_HOLDING_NREGS];
//modbus进程
void StartFreeModbusTask(void *argument)
{
//初始化协议栈
eMBInit( MB_ASCII, 0x01, 0, 115200, MB_PAR_NONE );
//使能协议栈
eMBEnable();
//轮询处理
for(;;)
{
(void)eMBPoll();
//测试:输入寄存器自动变化
usRegInputBuf[0]++;
usRegInputBuf[1]+=2;
usRegInputBuf[2]+=3;
usRegInputBuf[3]+=4;
usRegInputBuf[4]+=5;
usRegInputBuf[5]+=6;
usRegInputBuf[6]+=7;
usRegInputBuf[7]+=8;
osDelay(1);
}
}
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
if( ( usAddress >= REG_INPUT_START )
&& ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
{
iRegIndex = ( int )( usAddress - usRegInputStart );
while( usNRegs > 0 )
{
*pucRegBuffer++ =
( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
*pucRegBuffer++ =
( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
eMBRegisterMode eMode )
{
eMBErrorCode eStatus = MB_ENOERR;
int iRegIndex;
uint16_t getdata;
if( ( usAddress >= REG_HOLDING_START) &&
( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )
{
iRegIndex = ( int )( usAddress - usRegHoldingStart );
switch ( eMode )
{
/* Pass current register values to the protocol stack. */
case MB_REG_READ:
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( UCHAR ) ( usRegHoldingBuf[iRegIndex] >> 8 );
*pucRegBuffer++ = ( UCHAR ) ( usRegHoldingBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
break;
/* Update current register values with new values from the
* protocol stack. */
case MB_REG_WRITE:
while( usNRegs > 0 )
{
getdata = *pucRegBuffer++ << 8;
getdata |= *pucRegBuffer++;
usRegHoldingBuf[iRegIndex] = getdata;
iRegIndex++;
usNRegs--;
}
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
//暂时不支持线圈操作
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
eMBRegisterMode eMode )
{
return MB_ENOREG;
}
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
return MB_ENOREG;
}
参考资料
STM32移植FreeModbus详细过程(MDK)
STM32F103C8T6 FreeRTOS+FreeModbus移植
- 这个是别人移植好的,有一点问题,改过来以后就可以直接用
- 定时器周期在
.ioc
文件中写死了,如果要用ASCII的话得从35-1
改为20000-1
- 定时器初始化时,要先清标志再开中断,否则会错误进入一次,请做如下修改
- 定时器周期在
//porttimer.c
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
__HAL_TIM_CLEAR_FLAG(&htim3, TIM_FLAG_UPDATE);
__HAL_TIM_ENABLE_IT(&htim3, TIM_IT_UPDATE);
return TRUE;
}