一、为什么需要DMA+空闲中断?

传统串口接收每字节触发中断,高波特率下CPU频繁进出中断,效率低下。DMA可自动搬运数据,但DMA传输完成中断仅适用于固定长度。空闲中断(IDLE) 在总线空闲时触发,完美解决不定长数据包的边界识别问题。

  • DMA:负责数据搬运,解放CPU
  • 空闲中断:检测一帧数据结束(总线空闲超过1个字符时间)
  • 组合优势:零拷贝、低中断开销、支持任意长度数据包

二、硬件与软件环境

  • MCU:STM32F407ZGT6(其他系列类似)
  • 工具:STM32CubeMX + HAL库
  • 串口:USART1(PA9/PA10),波特率115200
  • DMA:DMA2 Stream2 Channel4(USART1_RX)

三、CubeMX配置步骤

  1. 使能USART1:异步模式,波特率115200,8N1
  2. 开启USART1全局中断:NVIC中勾选USART1 interrupt
  3. 添加DMA请求:USART1_RX,模式Normal(非循环),优先级中
  4. 生成代码:确保HAL_UART_Receive_DMA被调用

注意:DMA模式选择Normal而非Circular,便于每次接收后重新配置。

四、核心代码实现

4.1 初始化与启动

#define RX_BUFFER_SIZE 256
uint8_t rx_buffer[RX_BUFFER_SIZE];
volatile uint16_t rx_len = 0;
volatile uint8_t rx_flag = 0;

void UART_DMA_Init(void)
{
    // 启动DMA接收(需在main中调用一次)
    HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
    // 使能空闲中断
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
}

4.2 空闲中断处理

在stm32f4xx_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); // 清除标志
        // 停止DMA以获取已接收长度
        HAL_UART_DMAStop(&huart1);
        // 计算接收长度:总大小 - 剩余计数
        rx_len = RX_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
        rx_flag = 1; // 置位标志供主循环处理
        // 重新启动DMA接收
        HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
    }
}

4.3 主循环处理数据

while (1)
{
    if(rx_flag)
    {
        // 处理rx_buffer中rx_len字节数据
        ProcessData(rx_buffer, rx_len);
        rx_flag = 0;
    }
}

五、踩坑记录与解决方案

坑1:空闲中断标志清除不彻底

  • 现象:中断反复进入,数据错乱
  • 原因:仅用__HAL_UART_CLEAR_IDLEFLAG可能不够,需先读SR再读DR
  • 解决:使用__HAL_UART_CLEAR_IDLEFLAG宏(内部已处理),或手动:__IO uint32_t tmp = huart1.Instance->SR; tmp = huart1.Instance->DR;

坑2:DMA停止后未重启导致后续数据丢失

  • 现象:只能接收第一包数据
  • 原因:HAL_UART_DMAStop后DMA被禁用,未重新启动
  • 解决:在中断末尾重新调用HAL_UART_Receive_DMA

坑3:高波特率下数据覆盖

  • 现象:长数据包尾部丢失
  • 原因:主循环处理不及时,DMA缓冲区被新数据覆盖
  • 解决:增大缓冲区(如512字节),或使用双缓冲/环形队列

坑4:HAL库版本差异

  • 现象:__HAL_DMA_GET_COUNTER编译报错
  • 原因:不同HAL版本宏定义不同
  • 解决:直接访问hdma_usart1_rx.Instance->NDTR

六、优化建议

  • 使用环形缓冲区+空闲中断,避免频繁启停DMA
  • 添加超时机制,防止半包数据滞留
  • 对关键数据包增加校验和或帧头帧尾
  • 调试时用逻辑分析仪观察时序,确认空闲中断触发点

七、总结

DMA+空闲中断是STM32串口不定长收包的黄金组合。掌握其配置要点和常见陷阱,能显著提升通信可靠性与系统实时性。建议在实际项目中封装为独立模块,便于复用与维护。