起因

  • 2021.06.24 联调时发现4G模块数传不正常,后来发现是由于MQTT协议只支持ASCII数据传输,不支持二进制数据;
  • 之前使用的 Modbus RTU 协议是二进制数据,因此打算改用 Modbus ASCII 协议。
  • 此时设备端使用的第三方 Modbus Middleware 不支持ASCII协议,需要更换,经过调研后选用了FreeModbus,在此顺便记录一下移植过程。

FreeModbus源代码获取

详细移植过程

移植步骤参考FreeModbus API文档

1. 添加源代码

  • 在STM32CubeIDE上创建一个名为FreeModbusF103的新工程,并配置好UART1等外设
  • 将下载的源代码拷贝到./Middlewares/Third_Party/
    [学习记录]FreeModbus移植 - 图1
  • 由于demo文件夹中是各种不同硬件平台的移植源代码和例程,我们不使用,将其屏蔽掉
    [学习记录]FreeModbus移植 - 图2
  • 协议源代码都在modbus文件夹下,将有.h文件的路径添加到include path;因为我们不使用 modbus over tcp ,所以将tcp文件夹屏蔽掉
    [学习记录]FreeModbus移植 - 图3
  • demo文件夹中有一个BARE文件夹,这是一个平台无关的空示例,包括移植文件和示例文件,我们将其中的port文件夹拷贝出来(别忘记添加到include path),后面移植完成以后再参考demo.c文件写自己的应用代码
    [学习记录]FreeModbus移植 - 图4

2. port.h移植

  • 添加外设头文件及宏定义
  1. #include "usart.h"
  2. #include "tim.h"
  3. #define MB_UART (&huart1)
  4. #define MB_TIM (&htim3)
  • 移植临界区操作宏函数
  1. #define ENTER_CRITICAL_SECTION( ) taskENTER_CRITICAL()
  2. #define EXIT_CRITICAL_SECTION( ) taskEXIT_CRITICAL()
  • 添加中断服务函数接口
  1. extern void modbus_serial_irq(void);
  2. extern void modbus_timer_irq(void);
  • 到这里头文件移植就完成了

3. portserial.c接口移植

  • 在CubeMX中将指定的串口外设参数设置好
    [学习记录]FreeModbus移植 - 图5
    [学习记录]FreeModbus移植 - 图6
  • portserial.c文件中串口通信相关的接口函数,需要我们用HAL库实现
  • 串口中断使能
  1. void
  2. vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
  3. {
  4. /* If xRXEnable enable serial receive interrupts. If xTxENable enable
  5. * transmitter empty interrupts.
  6. */
  7. if(xRxEnable)
  8. {
  9. __HAL_UART_ENABLE_IT(MB_UART,UART_IT_RXNE);
  10. }
  11. else
  12. {
  13. __HAL_UART_DISABLE_IT(MB_UART,UART_IT_RXNE);
  14. }
  15. if(xTxEnable)
  16. {
  17. __HAL_UART_ENABLE_IT(MB_UART,UART_IT_TXE);
  18. }
  19. else
  20. {
  21. __HAL_UART_DISABLE_IT(MB_UART,UART_IT_TXE);
  22. }
  23. }
  • 串口初始化
  1. BOOL
  2. xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
  3. {
  4. //忽略参数
  5. UNUSED(ucPORT);
  6. UNUSED(ulBaudRate);
  7. UNUSED(ucDataBits);
  8. UNUSED(eParity);
  9. //以MX配置为准
  10. MX_USART1_UART_Init();
  11. return TRUE;
  12. }
  • 串口发送
  1. BOOL
  2. xMBPortSerialPutByte( CHAR ucByte )
  3. {
  4. /* Put a byte in the UARTs transmit buffer. This function is called
  5. * by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been
  6. * called. */
  7. return (HAL_UART_Transmit(MB_UART, (uint8_t*)&ucByte, 1, 1) == HAL_OK) ? TRUE : FALSE;
  8. }
  • 串口接收
  1. BOOL
  2. xMBPortSerialGetByte( CHAR * pucByte )
  3. {
  4. /* Return the byte in the UARTs receive buffer. This function is called
  5. * by the protocol stack after pxMBFrameCBByteReceived( ) has been called.
  6. */
  7. return (HAL_UART_Receive(MB_UART, (uint8_t*)pucByte, 1, 1) == HAL_OK) ? TRUE : FALSE;
  8. }
  • 添加中断处理函数
  1. //中断处理
  2. void modbus_serial_irq(void){
  3. if(__HAL_UART_GET_IT_SOURCE(MB_UART, UART_IT_RXNE) != RESET){
  4. prvvUARTRxISR();
  5. __HAL_UART_CLEAR_FLAG(MB_UART, UART_IT_RXNE);
  6. }
  7. if(__HAL_UART_GET_IT_SOURCE(MB_UART, UART_IT_TXE) != RESET){
  8. prvvUARTTxReadyISR();
  9. __HAL_UART_CLEAR_FLAG(MB_UART, UART_IT_TXE);
  10. }
  11. }
  • 到这里串口的移植就完成了

4. porttimer.c接口移植

  • 在CubeMX中将指定的定时器外设参数设置好
    [学习记录]FreeModbus移植 - 图7
    [学习记录]FreeModbus移植 - 图8
  • porttimer.c文件中定时器相关的接口函数,需要我们用HAL库实现
  • 定时器初始化
  1. BOOL
  2. xMBPortTimersInit( USHORT usTim1Timerout50us )
  3. {
  4. MX_TIM3_Init();
  5. __HAL_TIM_SET_AUTORELOAD(MB_TIM,usTim1Timerout50us-1);
  6. __HAL_TIM_CLEAR_FLAG(MB_TIM, TIM_FLAG_UPDATE);
  7. __HAL_TIM_ENABLE_IT(MB_TIM, TIM_IT_UPDATE);
  8. return TRUE;
  9. }
  • 定时器使能
  1. inline void
  2. vMBPortTimersEnable( )
  3. {
  4. /* Enable the timer with the timeout passed to xMBPortTimersInit( ) */
  5. __HAL_TIM_SET_COUNTER(&htim3, 0);
  6. HAL_TIM_Base_Start(&htim3);
  7. }
  • 定时器失能
  1. inline void
  2. vMBPortTimersDisable( )
  3. {
  4. /* Disable any pending timers. */
  5. HAL_TIM_Base_Stop(&htim3);
  6. }
  • 添加中断处理函数
  1. //中断处理
  2. void modbus_timer_irq(void){
  3. if(__HAL_TIM_GET_IT_SOURCE(MB_TIM, TIM_IT_UPDATE) != RESET){
  4. prvvTIMERExpiredISR();
  5. __HAL_TIM_CLEAR_FLAG(MB_TIM, TIM_IT_UPDATE);
  6. }
  7. }
  • 到这里定时器的移植就完成了

5. 添加中断处理

  • stm32f1xx_it.c中生成的串口、定时器中断处理中添加我们上面写好的中断处理函数
  1. /**
  2. * @brief This function handles USART1 global interrupt.
  3. */
  4. void USART1_IRQHandler(void)
  5. {
  6. /* USER CODE BEGIN USART1_IRQn 0 */
  7. modbus_serial_irq();
  8. /* USER CODE END USART1_IRQn 0 */
  9. HAL_UART_IRQHandler(&huart1);
  10. /* USER CODE BEGIN USART1_IRQn 1 */
  11. /* USER CODE END USART1_IRQn 1 */
  12. }
  1. /**
  2. * @brief This function handles TIM3 global interrupt.
  3. */
  4. void TIM3_IRQHandler(void)
  5. {
  6. /* USER CODE BEGIN TIM3_IRQn 0 */
  7. modbus_timer_irq();
  8. /* USER CODE END TIM3_IRQn 0 */
  9. HAL_TIM_IRQHandler(&htim3);
  10. /* USER CODE BEGIN TIM3_IRQn 1 */
  11. /* USER CODE END TIM3_IRQn 1 */
  12. }
  • 到这里中断处理的移植就完成了

6. 移植寄存器操作接口并创建协议栈线程

  • 这个部分可以在用户的应用代码中编写,这里集中写在portregs.c作为示范
  • 在CubeMX中添加FreeRTOS线程StartFreeModbusTask的定义,协议栈就会自动创建运行,响应串口的请求
  • ASCII和RTU的源代码可以在modbus/mbconfig.h中进行裁剪
  1. /*
  2. * portregs.c
  3. *
  4. * Created on: Jun 27, 2021
  5. * Author: soliber
  6. */
  7. /* ----------------------- Modbus includes ----------------------------------*/
  8. #include "mb.h"
  9. #include "mbport.h"
  10. /* ----------------------- Defines ------------------------------------------*/
  11. #define REG_INPUT_START 1 //输入寄存器PLC地址
  12. #define REG_INPUT_NREGS 50 //输入寄存器数量
  13. #define REG_HOLDING_START 1 //保持寄存器PLC地址
  14. #define REG_HOLDING_NREGS 50 //保持寄存器数量
  15. /* ----------------------- Static variables ---------------------------------*/
  16. static USHORT usRegInputStart = REG_INPUT_START;
  17. static USHORT usRegInputBuf[REG_INPUT_NREGS];
  18. static USHORT usRegHoldingStart = REG_HOLDING_START;
  19. static USHORT usRegHoldingBuf[REG_HOLDING_NREGS];
  20. //modbus进程
  21. void StartFreeModbusTask(void *argument)
  22. {
  23. //初始化协议栈
  24. eMBInit( MB_ASCII, 0x01, 0, 115200, MB_PAR_NONE );
  25. //使能协议栈
  26. eMBEnable();
  27. //轮询处理
  28. for(;;)
  29. {
  30. (void)eMBPoll();
  31. //测试:输入寄存器自动变化
  32. usRegInputBuf[0]++;
  33. usRegInputBuf[1]+=2;
  34. usRegInputBuf[2]+=3;
  35. usRegInputBuf[3]+=4;
  36. usRegInputBuf[4]+=5;
  37. usRegInputBuf[5]+=6;
  38. usRegInputBuf[6]+=7;
  39. usRegInputBuf[7]+=8;
  40. osDelay(1);
  41. }
  42. }
  43. eMBErrorCode
  44. eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
  45. {
  46. eMBErrorCode eStatus = MB_ENOERR;
  47. int iRegIndex;
  48. if( ( usAddress >= REG_INPUT_START )
  49. && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
  50. {
  51. iRegIndex = ( int )( usAddress - usRegInputStart );
  52. while( usNRegs > 0 )
  53. {
  54. *pucRegBuffer++ =
  55. ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
  56. *pucRegBuffer++ =
  57. ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
  58. iRegIndex++;
  59. usNRegs--;
  60. }
  61. }
  62. else
  63. {
  64. eStatus = MB_ENOREG;
  65. }
  66. return eStatus;
  67. }
  68. eMBErrorCode
  69. eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
  70. eMBRegisterMode eMode )
  71. {
  72. eMBErrorCode eStatus = MB_ENOERR;
  73. int iRegIndex;
  74. uint16_t getdata;
  75. if( ( usAddress >= REG_HOLDING_START) &&
  76. ( usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS ) )
  77. {
  78. iRegIndex = ( int )( usAddress - usRegHoldingStart );
  79. switch ( eMode )
  80. {
  81. /* Pass current register values to the protocol stack. */
  82. case MB_REG_READ:
  83. while( usNRegs > 0 )
  84. {
  85. *pucRegBuffer++ = ( UCHAR ) ( usRegHoldingBuf[iRegIndex] >> 8 );
  86. *pucRegBuffer++ = ( UCHAR ) ( usRegHoldingBuf[iRegIndex] & 0xFF );
  87. iRegIndex++;
  88. usNRegs--;
  89. }
  90. break;
  91. /* Update current register values with new values from the
  92. * protocol stack. */
  93. case MB_REG_WRITE:
  94. while( usNRegs > 0 )
  95. {
  96. getdata = *pucRegBuffer++ << 8;
  97. getdata |= *pucRegBuffer++;
  98. usRegHoldingBuf[iRegIndex] = getdata;
  99. iRegIndex++;
  100. usNRegs--;
  101. }
  102. }
  103. }
  104. else
  105. {
  106. eStatus = MB_ENOREG;
  107. }
  108. return eStatus;
  109. }
  110. //暂时不支持线圈操作
  111. eMBErrorCode
  112. eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
  113. eMBRegisterMode eMode )
  114. {
  115. return MB_ENOREG;
  116. }
  117. eMBErrorCode
  118. eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
  119. {
  120. return MB_ENOREG;
  121. }

参考资料

STM32移植FreeModbus详细过程(MDK)
STM32F103C8T6 FreeRTOS+FreeModbus移植

  • 这个是别人移植好的,有一点问题,改过来以后就可以直接用
    1. 定时器周期在.ioc文件中写死了,如果要用ASCII的话得从35-1改为20000-1
    2. 定时器初始化时,要先清标志再开中断,否则会错误进入一次,请做如下修改
  1. //porttimer.c
  2. BOOL
  3. xMBPortTimersInit( USHORT usTim1Timerout50us )
  4. {
  5. __HAL_TIM_CLEAR_FLAG(&htim3, TIM_FLAG_UPDATE);
  6. __HAL_TIM_ENABLE_IT(&htim3, TIM_IT_UPDATE);
  7. return TRUE;
  8. }

FreeMODBUS库的扩展与增强(1)- 移植到STM32单片机的基本流程