一、为什么需要DMA+空闲中断?
传统串口接收通常采用单字节中断方式,每收到一个字节就触发一次中断,CPU频繁进出中断导致效率低下,尤其在高速或大数据量场景下容易丢包。DMA(直接存储器访问)可以在不占用CPU的情况下自动将串口数据搬运到内存,而空闲中断(IDLE)则在总线空闲时触发,完美解决“不定长数据”的边界判定问题。
- DMA优势:零CPU干预搬运数据,降低中断负载。
- 空闲中断优势:一帧数据接收完成后(总线空闲)立即通知CPU,无需预先知道长度。
- 组合效果:高效、可靠地接收任意长度数据帧。
二、原理详解
2.1 DMA工作模式
串口接收使用DMA时,通常配置为循环模式(Circular)或普通模式(Normal)。循环模式下DMA缓冲区自动回绕,适合连续数据流;普通模式需在每次接收后重新启动。本文采用循环模式,配合空闲中断实现不定长接收。
2.2 空闲中断(IDLE)触发机制
当USART检测到总线从忙碌变为空闲(即接收完一帧数据后,线路保持高电平超过一个字符时间),硬件置位IDLE标志并触发中断。此时DMA已将该帧数据全部搬运至缓冲区,CPU只需读取DMA剩余计数即可算出数据长度。
2.3 数据长度计算
设DMA缓冲区大小为BUF_SIZE,当前DMA剩余传输数量为__HAL_DMA_GET_COUNTER(&hdma_usart1_rx),则已接收数据长度 = BUF_SIZE - 剩余计数。
三、CubeMX配置步骤
- 使能USART1,模式为Asynchronous,波特率115200。
- 在USART1的DMA Settings中添加RX通道,模式设为Circular,优先级High。
- 开启USART1全局中断(NVIC中使能USART1中断)。
- 生成代码,HAL库会自动初始化DMA和USART。
四、完整代码示例
4.1 定义缓冲区与变量
#define RX_BUF_SIZE 256
uint8_t rx_buf[RX_BUF_SIZE];
volatile uint8_t rx_len = 0;
volatile uint8_t rx_flag = 0;
4.2 启动DMA接收与空闲中断
在main()初始化后调用:
HAL_UART_Receive_DMA(&huart1, rx_buf, RX_BUF_SIZE);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
4.3 空闲中断处理函数
在stm32f1xx_it.c的USART1_IRQHandler中添加:
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);
HAL_UART_DMAStop(&huart1);
uint8_t temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
rx_len = RX_BUF_SIZE - temp;
rx_flag = 1;
HAL_UART_Receive_DMA(&huart1, rx_buf, RX_BUF_SIZE);
}
}
4.4 主循环处理数据
while (1)
{
if (rx_flag)
{
rx_flag = 0;
// 处理rx_buf中rx_len字节的数据
process_data(rx_buf, rx_len);
}
}
五、踩坑记录与注意事项
-
坑1:清除IDLE标志的顺序
必须先读SR再读DR才能清除IDLE标志,HAL库的__HAL_UART_CLEAR_IDLEFLAG已封装此操作,但需确保在DMA停止后调用,否则可能丢失数据。 -
坑2:DMA停止与重启的时机
在IDLE中断中先HAL_UART_DMAStop再计算长度,然后重新启动DMA。若顺序颠倒,新数据可能覆盖未处理的数据。 -
坑3:缓冲区溢出
循环模式下若数据处理不及时,新数据会覆盖旧数据。建议增大缓冲区或提高处理速度,也可使用双缓冲机制。 -
坑4:中断优先级冲突
DMA中断与USART中断优先级需合理配置,避免USART中断被DMA中断打断导致IDLE标志处理延迟。 -
坑5:HAL库版本差异
不同HAL库版本中HAL_UART_DMAStop的行为可能不同,建议测试确认。部分版本需手动清除DMA传输完成标志。 -
注意:数据对齐
确保DMA缓冲区地址对齐,避免非对齐访问导致性能下降或错误。 -
注意:空闲中断的误触发
在噪声环境下可能误触发IDLE,可在处理函数中增加长度校验(如长度>0)。
六、总结
DMA+空闲中断是STM32串口不定长接收的经典方案,兼顾效率与灵活性。掌握其原理与配置细节后,可轻松应对大多数串口通信场景。实际开发中注意缓冲区管理、中断优先级和HAL库特性,即可避开常见陷阱。
暂无评论,来抢沙发~