UART Transmit DMA发送完成未触发UART_IRQ_Handler(调试模式下正常)
问题解答
关于__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_TC)的作用
这行代码是HAL库为了避免历史遗留标志干扰本次传输做的防护设计:
- UART的TC(传输完成)标志一旦置位,除非软件主动清除,否则会一直保留在状态寄存器中。如果上一次UART传输结束后没有清理该标志,本次DMA传输刚启动、还没发完数据的时候,就会因为已有标志直接触发TC中断,导致上层逻辑误判传输完成。这行代码的作用就是在启动本次传输前,清空上一次传输留下的TC标志,保证只有本次传输完成产生的TC标志才会触发中断。
异常现象的原因
这个现象不是操作失误,是该版本HAL库的UART_Transmit_DMA()实现存在时序漏洞:
源码中HAL_DMA_Start_IT()的调用在清TC标志之前,如果传输数据包很短、DMA优先级较高、总线空闲,会出现以下时序:
- 调用
HAL_DMA_Start_IT()启动DMA传输 - DMA快速把所有数据写入UART的DR寄存器,UART完成所有数据的发送,TC标志置位
- 程序才走到
__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_TC),直接把本次传输产生的TC标志清空 - 后续即使DMA回调打开了UART TC中断,也没有对应的标志触发中断
加断点后功能恢复的原因也符合这个逻辑:断点卡在清标志操作时,给了DMA和UART足够的时间完成传输,DMA完成回调先执行、打开了UART TC中断,之后清标志只会清掉旧的标志,等本次传输新的TC标志置位后就可以正常触发中断。
修复方案
- 方案1:调整
UART_Transmit_DMA()内部的执行顺序,把__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_TC)挪到HAL_DMA_Start_IT()之前执行,可以直接修改HAL库源码,也可以自行重写该函数的实现,从根源上避免时序漏洞。 - 方案2:如果不需要严格判断UART物理层发送完成,可以直接复用DMA的传输完成中断作为发送结束信号,注意在DMA完成中断中加1~2字节的发送延时,避免最后一个字节还在移位寄存器中发送时就操作UART外设。
内容的提问来源于stack exchange,提问作者Salvatore Gabriele La Greca
相关产品推荐
相关产品推荐

