起因
- 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/下![[学习记录]FreeModbus移植 - 图1](/uploads/projects/cug_miapal@blog/79862e4eff7be4123a257df807c3748f.webp)
 - 由于
demo文件夹中是各种不同硬件平台的移植源代码和例程,我们不使用,将其屏蔽掉![[学习记录]FreeModbus移植 - 图2](/uploads/projects/cug_miapal@blog/addb8e7d82b2df76fddfb1f320698600.webp)
 - 协议源代码都在
modbus文件夹下,将有.h文件的路径添加到include path;因为我们不使用 modbus over tcp ,所以将tcp文件夹屏蔽掉![[学习记录]FreeModbus移植 - 图3](/uploads/projects/cug_miapal@blog/5663277574751b2b4450bbcbed2316aa.webp)
 demo文件夹中有一个BARE文件夹,这是一个平台无关的空示例,包括移植文件和示例文件,我们将其中的port文件夹拷贝出来(别忘记添加到include path),后面移植完成以后再参考demo.c文件写自己的应用代码![[学习记录]FreeModbus移植 - 图4](/uploads/projects/cug_miapal@blog/6010a22e2f6918ef6ab177229b3159f0.webp)
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中将指定的串口外设参数设置好
![[学习记录]FreeModbus移植 - 图5](/uploads/projects/cug_miapal@blog/064967f273dc3afb3be19a14aa5c5d7a.webp)
![[学习记录]FreeModbus移植 - 图6](/uploads/projects/cug_miapal@blog/119240c78d2d68082b3a27f343a1f639.webp)
 portserial.c文件中串口通信相关的接口函数,需要我们用HAL库实现- 串口中断使能
 
voidvMBPortSerialEnable( 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);}}
- 串口初始化
 
BOOLxMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity ){//忽略参数UNUSED(ucPORT);UNUSED(ulBaudRate);UNUSED(ucDataBits);UNUSED(eParity);//以MX配置为准MX_USART1_UART_Init();return TRUE;}
- 串口发送
 
BOOLxMBPortSerialPutByte( 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;}
- 串口接收
 
BOOLxMBPortSerialGetByte( 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中将指定的定时器外设参数设置好
![[学习记录]FreeModbus移植 - 图7](/uploads/projects/cug_miapal@blog/fb0a491b32eff39e2f3090a66a882014.webp)
![[学习记录]FreeModbus移植 - 图8](/uploads/projects/cug_miapal@blog/d240f71e80c8afe8d6383803b4aec901.webp)
 porttimer.c文件中定时器相关的接口函数,需要我们用HAL库实现- 定时器初始化
 
BOOLxMBPortTimersInit( 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 voidvMBPortTimersEnable( ){/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */__HAL_TIM_SET_COUNTER(&htim3, 0);HAL_TIM_Base_Start(&htim3);}
- 定时器失能
 
inline voidvMBPortTimersDisable( ){/* 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);}}eMBErrorCodeeMBRegInputCB( 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;}eMBErrorCodeeMBRegHoldingCB( 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;}//暂时不支持线圈操作eMBErrorCodeeMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,eMBRegisterMode eMode ){return MB_ENOREG;}eMBErrorCodeeMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete ){return MB_ENOREG;}
参考资料
STM32移植FreeModbus详细过程(MDK)
STM32F103C8T6 FreeRTOS+FreeModbus移植
- 这个是别人移植好的,有一点问题,改过来以后就可以直接用 
- 定时器周期在
.ioc文件中写死了,如果要用ASCII的话得从35-1改为20000-1 - 定时器初始化时,要先清标志再开中断,否则会错误进入一次,请做如下修改
 
 - 定时器周期在
 
//porttimer.cBOOLxMBPortTimersInit( USHORT usTim1Timerout50us ){__HAL_TIM_CLEAR_FLAG(&htim3, TIM_FLAG_UPDATE);__HAL_TIM_ENABLE_IT(&htim3, TIM_IT_UPDATE);return TRUE;}
