STM32使用FREERTOS和HAL库实现MODBUS RTU通信

一、程序思路整理

  • 串口数据怎样断帧
  • 使用ModbusRTU最大的难点在于串口接收时,怎样判断一个命令的起始和截止点,也就是怎样断帧。modbus-RTU对于介质管理规定了2个重要的时间参数,以实现成帧、冲突管理等,如下图所示。

    这个图可以用于断帧,也就时判断是否接收到一个完整的帧,因此只需要使用一个定时器在每次收到一个字节后,就重启一个3.5字节定时器,如果这个3.5字节定时器中断了,就证明收到了一个Modbus报文,至于这个报文是不是正确的报文,可以在进一步根据帧格式进行校验。

    另外还规定了报文需要连续发送,字节间隔不得超过1.5字节时间(这作为补充知识点,对于断帧没有帮助)。

    综上,**我们可以通过检测在每个字节收到后,如果有超过3.5个字符的时间没有收到新的字节,那么就可以判定一个完整的MODBUSRTU命令已经发送。**这里又涉及到一个问题,怎样判断3.5个字符,这里又两种方案,一种是通过硬件定时器,另一种是通过FREERTOS自带的软件计时器。硬件计时器需要使用一个TIM,个人不倾向使用。但是如果使用软件计时器,涉及到一个问题,就是精度不够,FREERTOS软件计时器的精度是毫秒级别的。如果串口使用115200bits/s的波特率的话,那么3.5个字符的时间是不到1ms的。那么这里我们又要考虑,到底有没有必要使用极限的3.5个字符,也就是客户端在发送MODBUSRTU时,会不会间隔3.5个字符。实际上,我们在使用MODBUSRTU时,命令之间的间隔最少也是几十毫秒的,因此,我们完全可以通过软件计时器,计时几毫秒,比如说5ms来断帧。

  • 命令帧接收流程设计
    在FREERTOS调度开始后,开启串口接收中断,每接受到一个字符,进入中断回调函数。在中断回调函数中,复位软件计时器,并把接受到的字节取出,放入接受命令的缓存中,并记录接受到命令的字节数。
    当软件计时器触发回调函数时,说明一个完整的客户端命令已经接收完毕,这时,将完整的命令放入队列中。

  • 队列设计
    由于MODBUSRTU的命令帧的长度是不一致的,因此,需要记录下每一个命令帧的长度,也就是在串口接收函数中记录的接受到的字节数。因此,可以将队列设计为以下模式:前两个字节记录命令的总字节数,后面的是命令部分。
    因此,在串口中断回调函数中,将接受到的命令从队列缓存的第三位开始填充。在软件定时器回调函数中,将命令字节数填入到队列缓存的前两位;在入队之后,需要把字节数清零。
    以下是串口回调函数和软件定时器回调函数。

  • void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
    	BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    	    			
    	HAL_UART_Receive_IT(huart, &ModbusH.rec_single_data, 1);
    	ModbusH.rec_data[ModbusH.rec_data_location++] = ModbusH.rec_single_data;
    	ModbusH.rec_count++;
        			
    	xTimerResetFromISR(ModbusH.xMDTimer, &xHigherPriorityTaskWoken);
    	
    }
    

    void vMBTimerCallback(TimerHandle_t *pxTimer)
    {
    //将命令字节数填入
    ModbusH.rec_data[0] = (uint8_t)(ModbusH.rec_count >> 8);
    ModbusH.rec_data[1] = (uint8_t)ModbusH.rec_count;

    //加入队列
    xQueueSend(ModbusH.Queue_485_Handle, &ModbusH.rec_data, 0);
    
    ModbusH.rec_data_location = 2;
    ModbusH.rec_count = 0;
    memset(ModbusH.rec_data, 0, sizeof(ModbusH.rec_data));	
    

    }

    二、HAL库串口中断介绍

  • HAL_UART_Receive_IT函数介绍
    该函数原型为
    HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
    该函数的作用是打开接收中断,设置接收的字节数Size和接收位置。串口在接收到函数指定的字节数Size后,会关闭接收中断并且调用HAL_UART_RxCpltCallback函数。因此如果要一直保持中断接收,那么就需要在回调函数中再次调用HAL_UART_Receive_IT函数。

  • 何时打开串口中断
    因为在串口中断的回调函数中,需要调用xTimerResetFromISR这一FREERTOS函数来复位软件定时器,所以需要保证在进入串口中断回调函数时,FERRRTOS内核已运行,也就是执行了 osKernelStart()函数。因此,一个有效的方法就是在一个任务中,创建其他任务一级初始化一些硬件,包括中断等,此时由于任务已经运行,FREERTOS内核肯定已经初始化完毕。

  • 作者:ljyjunyucsnd

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32使用FREERTOS和HAL库实现MODBUS RTU通信

    发表回复