使用STM32的HAL库实现UART串口不定长数据的接收
使用STM32的UART接收数据的时候,经常会遇到接收长度不固定的数据,比如一帧数据可能是10个字节,也可能是12个字节。这种数据称为不定长数据。
现有的很多通信协议是不定长的,比如modbus-rtu,其不同指令的长度是不同的,还比如与ESP8266通信使用的AT指令,其数据长度也不是固定的。
对于上面所说的不定长数据,可以使用状态机的方式判断一帧数据的结束,即在UART接收中断中按照通信协议的格式进行判断,确定哪个字节为帧的结尾。但是这种方式编程比较复杂。
有一个更简单的方法,就是判断一帧数据的时间间隔。默认两帧数据之间是有一定时间间隔的,比如10ms,那么当收到一个字节的数据后,5ms内没有收到新的字节,就认为一帧数据的结束。这种方法能够大大简化UART接收中断的处理函数。只需要在主流程里判断这一帧数据是否合法即可,能够很好的减低编程难度,也提高了MCU的处理效率。
那么如何实现这种编程方式呢?这里介绍一种通过HAL库实现的方式。
不定长数据接收方式
在STM32中最通用的方式就是借助UART的IDLE中断来实现,即结合UART的接收DMA和IDLE中断来实现。这种方式适用于STM32的所有芯片。
UART的IDLE中断是:当一个空闲帧被检测到时产生的中断。空闲帧如下图所示,为持续一个字节数据的高电平,就是空闲帧。也就是持续1个字节总线上没有数据就是一个空闲帧。
通过设置UART使用DMA进行接收,使得UART能够自动接收数据,并放入缓冲区中。当STM32检测到IDLE中断后,通过回调函数对接收到的数据进行处理。这样不用每接收到一个字节就处理,提高了MCU的运行效率。
HAL库函数
在STM32的HAL库中提供了现成的函数
HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
这个函数使用DMA接收数据,当接收到Size个数据或者遇到IDLE中断时完成接收,然后调用下面的回调函数。
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
库函数使用注意实现
因此对一帧数据的处理函数要放到 HAL_UARTEx_RxEventCallback() 函数中。注意这个函数是在中断程序中,因此其处理函数要尽量的短。而且其参数Size 在DMA为循环模式时,指的是在接收缓冲区中下一个字节的写入索引位置,这个要注意。
在使用HAL_UARTEx_RxEventCallback() 回调函数的时候也要注意,这个函数在DMA的"半传输"中断和"传输完成"中断的时候也会进入此回调函数。比如说使用HAL_UARTEx_ReceiveToIdle_DMA() 函数接收20个字节,那么在接收到10个字节的时候会触发DMA"半传输中断",接收到20个字节的时候会触发DMA的"传输完成"中断。DMA的"传输完成"中断不会产生影响,因为此时接收也已经完成了。但是"半传输"中断会影响处理,比如接收到不定长数据为12个字节,那么在收到10个字节的时候会进入HAL_UARTEx_RxEventCallback() 回调函数,在接受到12个字节的时候还会进入HAL_UARTEx_RxEventCallback() 回调函数。
防止DMA中断影响的方法
最简单的处理方法是关闭DMA的这2个中断,可以在调用HAL_UARTEx_ReceiveToIdle_DMA() 函数后,关闭这2个中断,参考如下代码:
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,recvbuf,20);
__HAL_DMA_DISABLE_IT(huart1.hdmarx,DMA_IT_HT); //关闭半传输中断
__HAL_DMA_DISABLE_IT(huart1.hdmarx,DMA_IT_TC); //关闭传输完成中断
这种方法既适用于将UART的DMA接收设置为循环接收,也适用于将DMA设置为单次接收模式。
如果不想关闭中断,而且DMA接收模式时单次模式,那么也可以在回调函数中进行判断,判断方法如下:
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart->RxState == HAL_UART_STATE_READY){
// 接收完成
}else{
// 半传输中断
}
}
在"半传输"中断进入时,接收没有完成,因此huart->RxState 的值是HAL_UART_STATE_BUSY_RX ,因此可以通过判断huart->RxState 的值是否等于HAL_UART_STATE_READY ,得知是因哪种事件进入的回调函数。
以上就是STM32使用HAL库实现UART不定长数据接收的一种方法,使用起来很简便。但是使用IDLE中断判断1帧数据结束还有有一定问题的,比如说在终端发送数据的时候,一帧数据中字节与字节之间的时间间隔比1个字节长了,就会造成误判。所以在使用的时候还是要注意区分的。在新出的STM32处理器中,针对这一点提供也新的解决方案,就是包含一个时间间隔可以设置的功能,这个将在下一次进行分享。