IDLE中断必须手动使能,否则DMA无法识别帧结束;需在HAL_UART_Receive_DMA()后调用__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE),并在IDLE中断中先停DMA、再读计数器计算长度。

直接说结论:DMA收发不定长串口数据时,IDLE中断不是“可选项”,而是必须启用的帧边界信号源;不配它,DMA就只能当个盲搬工,永远不知道一帧在哪结束。
为什么IDLE中断必须手动使能,且不能靠CubeMX自动生成
CubeMX默认只勾选USART_IT_RXNE和USART_IT_TC,但UART_IT_IDLE从不自动打钩——哪怕你开了DMA接收。这是因为IDLE中断在硬件上是独立标志位(UART_FLAG_IDLE),需显式调用__HAL_UART_ENABLE_IT()开启,否则即使总线空闲,也不会触发中断。
- 常见错误现象:
HAL_UART_Receive_DMA()跑起来了,缓冲区有数据,但USARTx_IRQHandler里永远进不到IDLE分支 - 根本原因:漏了这行代码:
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); - 注意:必须在
HAL_UART_Receive_DMA()之后调用,否则可能被HAL初始化流程覆盖 - 某些芯片(如H7系列)还需确认
CR1寄存器的IDLEIE位是否真被置位,可用调试器查看huart1.Instance->CR1
IDLE中断服务里必须先停DMA再读计数器
DMA在IDLE发生瞬间仍在搬运最后一个字节,若不暂停就直接查NDTR,结果会偏小(少1~2字节)。这是几乎所有初学者踩的第一个坑。
- 正确顺序:
HAL_UART_DMAStop()→ 读__HAL_DMA_GET_COUNTER()→ 计算长度 → 处理数据 →HAL_UART_Receive_DMA()重启 - 计算公式固定为:
len = RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx),别用NDTR寄存器直读,HAL宏已做兼容处理 - 别在中断里调
memset()清缓冲区:耗时且可能打断下帧接收;改用指针偏移或双缓冲 - H7等带D-Cache的芯片,处理前要加
SCB_InvalidateDCache_by_Addr()(地址需对齐32字节),否则可能读到旧缓存值
DMA模式必须设为循环模式(Circular),但逻辑上要当普通模式用
循环模式不是为了“重复收同一段”,而是防止DMA传输完成(TC)后自动停摆——那样IDLE来了也收不到后续数据。但循环模式本身会导致缓冲区回绕,必须靠IDLE中断来切分有效数据段。
立即学习“C++免费学习笔记(深入)”;
- 配置要点:
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;,且hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;(外设地址固定) - 风险点:若两帧数据间隔极短(IDLE不触发,DMA会把两帧连成一片;此时需协议层加帧头校验,不能全依赖硬件
- 缓冲区大小建议≥最大单帧长度×2,避免
IDLE处理慢导致覆盖(尤其H7主频高但中断优先级没设好时) - 别用
Normal模式配IDLE:DMA传完一次就停,IDLE来了也无数据可收
最易被忽略的是IDLE中断的“惰性触发”特性:它只在收到至少1字节后才可能产生。如果设备刚上电、串口线悬空或对方迟迟不发数据,IDLE永远不会来——这时需要配合超时机制兜底,比如用HAL_GetTick()监控最后一次IDLE时间,超过阈值强制解析当前缓冲区内容。


















