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

在嵌入式开发中,串口不定长数据接收是常见需求。传统逐字节中断方式CPU开销大,而DMA+空闲中断组合能实现高效接收:DMA自动搬运数据,空闲中断在总线空闲时触发,一次性处理整包数据。

  • DMA:无需CPU干预,自动将串口数据搬运到内存缓冲区。
  • 空闲中断:当串口总线空闲(超过一个字节时间无数据)时触发,标志一帧数据结束。

看似完美,但HAL库的封装隐藏了几个坑,稍不注意就会导致数据丢失或错乱。

二、隐藏坑分析

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

HAL库的HAL_UART_IRQHandler中,空闲中断标志(IDLE)的清除需要先读SR再读DR。但很多开发者直接调用__HAL_UART_CLEAR_IDLEFLAG,若未正确清除,会导致中断持续触发或丢失。

坑2:DMA接收长度计算错误

使用HAL_UART_Receive_DMA启动接收后,DMA计数器(NDTR)记录剩余可接收字节数。接收长度应为缓冲区大小 - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx)。但若在中断中未及时停止DMA,或重新启动时未重置计数器,长度计算会出错。

坑3:缓冲区覆盖与数据竞争

空闲中断中处理数据时,若未及时停止DMA或切换缓冲区,新数据可能覆盖未处理的数据。尤其在高速通信时,处理时间过长会导致丢包。

坑4:HAL库版本差异

不同HAL版本中,HAL_UART_Receive_DMA的行为略有不同,部分版本在接收完成后自动关闭DMA,导致后续接收失败。

三、配置步骤

以STM32F4系列为例,使用USART1(PA9/PA10),DMA2 Stream2 Channel4。

  1. CubeMX配置:

    • 使能USART1异步模式,波特率115200。
    • 添加DMA请求:USART1_RX,模式Circular(循环模式),数据宽度Byte。
    • 使能USART1全局中断。
  2. 代码初始化:

    • 启动DMA接收:HAL_UART_Receive_DMA(&huart1, rx_buf, BUF_SIZE);
    • 使能空闲中断:__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
  3. 中断处理:

    • 在USART1_IRQHandler中调用HAL_UART_IRQHandler(&huart1);
    • 重写HAL_UART_IdleCallback或直接在IRQ中判断空闲标志。

四、完整代码示例

#define RX_BUF_SIZE 256
uint8_t rx_buf[RX_BUF_SIZE];
volatile uint16_t rx_len = 0;
volatile uint8_t rx_flag = 0;

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);          // 停止DMA,防止数据覆盖
        rx_len = RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
        rx_flag = 1;                        // 标记接收完成
        // 重新启动DMA接收
        HAL_UART_Receive_DMA(&huart1, rx_buf, RX_BUF_SIZE);
    }
}

// 主循环中处理数据
if (rx_flag)
{
    rx_flag = 0;
    // 处理rx_buf中rx_len字节的数据
    process_data(rx_buf, rx_len);
}

关键点:

  • 使用HAL_UART_DMAStop停止DMA,确保长度计算准确。
  • 重新启动DMA前,确保缓冲区数据已处理或拷贝。
  • 若使用循环模式,可省略停止DMA,但需注意数据覆盖风险。

五、注意事项

  • 及时清除空闲标志:必须在中断中先读SR再读DR,HAL库的__HAL_UART_CLEAR_IDLEFLAG已封装此操作。
  • DMA模式选择:循环模式适合连续接收,但需双缓冲区或及时处理;普通模式需每次重新启动。
  • 中断优先级:DMA中断优先级应低于串口空闲中断,避免数据竞争。
  • 缓冲区管理:建议使用双缓冲区或环形队列,避免处理数据时被新数据覆盖。
  • HAL库版本:检查HAL_UART_Receive_DMA是否在接收完成后自动关闭DMA,必要时手动重启。
  • 错误处理:添加DMA传输错误和串口错误回调,增强鲁棒性。

通过以上分析与修复,可稳定实现不定长串口收包,充分发挥DMA+空闲中断的高效性。