关于ModBus协议的基础知识就不在详述了,只需要大致的知道它是基于RS485协议的一个软件层协议就行,基本上现在都采用RTU传输模式。网上类似的资料也是多的数不胜数,不过大多都是基于标准库的,也有少部分基于HAL库的,但是找不到基于操作系统的。为了实用性考虑,便决定打算移植一个基于FreeRTOS的代码,当前ST公司主推HAL库,对于STM32F4以及F7系列的而言,标准库已经不再更新了,想了想,还是要适应时代的潮流,便也采用了HAL库。简单的分享一下移植过程中的经验。
一、操作系统定时器
首先对于FreeRTOS而言,SYS从时基来源SYSTICK选择其他计时器,这里选择TIM6计时器,用来进行配置Timebase Source。以防止与操作系统的软件定时器产生冲突。
二、FreeModbus端口
A. <port.h>修改
1、向EXIT_CRITICAL_SECTION添加中断启用/禁用代码。
2、包含“stm32f4xx_hal.h”头文件。
为关键部分添加中断启用/禁用功能。
#ifndef _PORT_H #define _PORT_H #include <assert.h> #include "stm32f4xx_hal.h" #define INLINE inline #define PR_BEGIN_EXTERN_C extern "C" { #define PR_END_EXTERN_C } #define ENTER_CRITICAL_SECTION() ( __disable_irq()) #define EXIT_CRITICAL_SECTION() ( __enable_irq()) typedef uint8_t BOOL; typedef unsigned char UCHAR; typedef char CHAR; typedef uint16_t USHORT; typedef int16_t SHORT; typedef uint32_t ULONG; typedef int32_t LONG; #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif #endif
B.<porttimer.c>修改
Modbus RTU 根据规范进行帧分类 ,所以需要设置 3.5个 字符空白时间操作的计时器,这里选择TIM7为计时器,定时器用于测量3.5个字符的时间,分隔帧。“xMBPortTimersInit” 在函数中初始化计时器 ,定时器频率需要配置为20kHz。
xMBPortTimersInit( USHORT usTim1Timerout50us ) { TIM_MasterConfigTypeDef sMasterConfig; htim7.Instance = TIM7; htim7.Init.Prescaler = (HAL_RCC_GetPCLK1Freq() / 1000000) - 1; htim7.Init.CounterMode = TIM_COUNTERMODE_UP; htim7.Init.Period = 50 - 1; timeout = usTim1Timerout50us; if (HAL_TIM_Base_Init(&htim7) != HAL_OK) { return FALSE; } sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; if (HAL_TIMEx_MasterConfigSynchronization(&htim7, &sMasterConfig) != HAL_OK) { return FALSE; } return TRUE;
“vMBPortTimersEnable”用于启用计时器。
“vMBPortTimersDisable”用于停止计时器。
void vMBPortTimersEnable( ) { downcounter = timeout; HAL_TIM_Base_Start_IT(&htim7); } void vMBPortTimersDisable( ) { HAL_TIM_Base_Stop_IT(&htim7); }
需要注意的是“prvvTIMERExpiredISR”,这个是定时器中断函数,但是在移植过程中,发现其与FreeRTOS产生了冲突,所以不使用它,从TIM7中断处理程序调用“pxMBPortCBTimerExpired”。
/* static void prvvTIMERExpiredISR( void ) { ( void )pxMBPortCBTimerExpired( ); } */
C. <stm32f4xx_it.c>修改
这是RTU端口最重要的部分。需要修改中断处理程序的两个部分。
首先,需要使“pxMBFrameCBByteReceived”“pxMBFrameCBTransmitterEmpty”在接收数据时可以被“USART2_IRQHandler”中断调用。如果无法及时发送或接收数据,TIM7定时器中断处理程序将调用“pxMBPortCBTimerExpired”。
void USART2_IRQHandler(void) { /* USER CODE BEGIN USART2_IRQn 0 */ uint32_t tmp_flag = __HAL_UART_GET_FLAG(&huart2, UART_FLAG_RXNE); uint32_t tmp_it_source = __HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_RXNE); if((tmp_flag != RESET) && (tmp_it_source != RESET)) { pxMBFrameCBByteReceived(); __HAL_UART_CLEAR_PEFLAG(&huart2); return; } if((__HAL_UART_GET_FLAG(&huart2, UART_FLAG_TXE) != RESET) &&(__HAL_UART_GET_IT_SOURCE(&huart2, UART_IT_TXE) != RESET)) { pxMBFrameCBTransmitterEmpty(); return ; } /* USER CODE END USART2_IRQn 0 */ HAL_UART_IRQHandler(&huart2); /* USER CODE BEGIN USART2_IRQn 1 */ /* USER CODE END USART2_IRQn 1 */ }
三、测试
主要代码则在<mbtask.c>中,功能是处理对具有来自地址1000的8个数据的输入寄存器的请求。每100 ms从输入寄存器读取8个值进行测试。输入寄存器地址从1000开始。
四、总结
移植整个工程真的是花费了相当多的时间。在慢慢摸索的过程中,为了提高整个工程的效率,将“mbconfig.h”中库配置为仅使用于ModBus RTU
/*! \brief If Modbus ASCII support is enabled. */ #define MB_ASCII_ENABLED ( 0 ) /*! \brief If Modbus RTU support is enabled. */ #define MB_RTU_ENABLED ( 1 ) /*! \brief If Modbus TCP support is enabled. */ #define MB_TCP_ENABLED ( 0 )
而最令人头疼的是,FreeRTOS与ModBus的冲突问题。且由于HAL库更偏向于上层的原因,在使用USART的时候,写入需要中断,发信号通知传输缓冲器为空并且接收缓冲器不为空。但是HAL库并不支持这样的回调,所以只得重新从底层采用寄存器控制。
xMBPortSerialGetByte( CHAR * pucByte ) { /* Return the byte in the UARTs receive buffer. This function is called * by the protocol stack after pxMBFrameCBByteReceived( ) has been called. */ *pucByte = (uint8_t)(huart2.Instance->DR & (uint8_t)0x00FF); return TRUE; }
不过最终还是完成了整个工程,感觉收获也是相当多。现将代码分享出来,大家可以参考参考,共同进步学习。