关于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
#endifB.<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;
}不过最终还是完成了整个工程,感觉收获也是相当多。现将代码分享出来,大家可以参考参考,共同进步学习。
我要赚赏金
