串口DMA模式与不定长数据收发
串口:DMA模式与收发不定长数据
一、两个正交的维度
串口收发涉及两个可以自由组合的维度:
数据传输方式(3种,CPU参与程度从高到低):阻塞 / 中断(_IT) / DMA(_DMA)
×
接收长度判断方式(2种):定长(Receive) / 不定长(ReceiveToIdle)
二、维度1:数据传输方式
DMA模式配置步骤
CubeMX中打开UART配置界面 →
DMA Settings→ 点击Add,分别为TX和RX添加DMA通道。关键参数说明:
方向:TX方向为"内存→外设",RX方向为"外设→内存"
地址自增:外设端(寄存器)不自增(只有1字节长度);内存端自增(依次存到数组不同位置)
数据宽度:一般为1字节
模式:Normal(正常模式)即可,Circular(循环模式)留待更复杂场景
生成代码后,把发送/接收函数的后缀从
_IT改为_DMA即可,其余HAL库已封装好。注意:即使用了DMA,回调(如
RxCpltCallback)仍然是通过中断触发的——只是触发源从"串口中断"变成了"DMA传输完成中断"。
三、维度2:接收长度判断方式
定长接收(之前学过)
函数:
HAL_UART_Receive/_IT/_DMA使用前需提前告诉函数要接收的字节数,收满才触发
HAL_UART_RxCpltCallback。
不定长接收(本期新内容)
函数:
HAL_UARTEx_ReceiveToIdle/_IT/_DMA原理:依靠串口空闲中断——只要RX引脚上一段时间没有新数据进来(即从"忙碌"转为"空闲"),就认为一帧数据接收完成,触发回调。这个触发条件与接收字节数无关。
与定长接收的区别:
第三个参数含义不同:不是"要接收的长度",而是"一次最多能接收的长度"(数组要开大一点,或用
sizeof获取长度,防止越界)。回调函数不同:不是
HAL_UART_RxCpltCallback,而是HAL_UARTEx_RxEventCallback,多了一个Size入参,用来告诉你本次实际收到了几个字节。别忘了在回调函数里重新启动一次接收。
好习惯:在回调里先判断是哪个串口(
huart指针)触发的,再处理。
四、DMA + 不定长接收 的坑:传输过半中断
使用 HAL_UARTEx_ReceiveToIdle_DMA 时,除了空闲中断会触发 RxEventCallback,DMA的"传输过半中断"也会触发同一个回调——即接收字节数达到设定最大长度的一半时,就会提前触发一次回调,此时数据实际上还没收完,容易被误当作"一帧完整数据"处理。
解决方法:关闭DMA的传输过半中断。
HAL_DMA_DisableIT(&hdma_usart2_rx, DMA_IT_HT);
第一个参数:DMA通道指针(这里是UART2的RX DMA通道)
第二个参数:要关闭的中断类型,
DMA_IT_HT即"过半中断"(Half Transfer)注意:程序开始时、以及每次重启接收后都要关闭一次。
之所以之前测试没发现这个问题,是因为接收缓冲区开得比较大、发送的数据比较短,没达到"过半"的阈值。
五、常用函数速查表
六、一句话总结
想省CPU → 把
_IT改成_DMA(并在CubeMX里加DMA通道);
想收不定长数据 → 把Receive改成ReceiveToIdle,回调函数从RxCpltCallback改成RxEventCallback;
两者可以叠加使用(ReceiveToIdle_DMA),但要记得关闭DMA的"传输过半中断"(DMA_IT_HT),否则数据未收完就可能被提前处理。