原理讲解

串口不定长数据接收是嵌入式开发中的常见需求。传统逐字节中断方式占用CPU资源高,而DMA+空闲中断方案能大幅提升效率。其核心原理:

  • DMA传输:将串口接收数据寄存器(DR)自动搬运到内存缓冲区,无需CPU干预。
  • 空闲中断(IDLE):当串口总线空闲(即一个字节传输时间后无新数据)时触发中断,标志一帧数据接收完成。
  • 组合优势:DMA负责数据搬运,空闲中断负责帧结束判断,CPU仅在帧结束时处理数据,实现高效不定长接收。

配置步骤(以STM32CubeMX为例)

  1. 使能串口:选择USART1,模式为Asynchronous,配置波特率等参数。
  2. 配置DMA:在DMA Settings中添加USART1_RX,模式为Circular(循环模式),数据宽度Byte,优先级中。
  3. 使能中断:在NVIC Settings中勾选USART1全局中断。
  4. 生成代码:生成初始化代码,HAL库会自动配置DMA和串口。

完整代码示例

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;

2. 启动DMA接收与空闲中断

在main函数初始化后调用:

HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);

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);
    uint16_t temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
    rx_len = RX_BUFFER_SIZE - temp;
    rx_flag = 1;
    HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
  }
}

4. 主循环处理数据

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

注意事项与踩坑记录

  • DMA模式选择:必须使用循环模式(Circular),否则每次接收后需重新启动DMA。若用普通模式,需在空闲中断中重新配置DMA,但易丢失数据。
  • 空闲中断标志清除:必须使用__HAL_UART_CLEAR_IDLEFLAG清除标志,且需先读SR再读DR,HAL库宏已处理。
  • DMA停止与重启:在空闲中断中先HAL_UART_DMAStop再计算长度,然后重新启动DMA。注意停止DMA会清除计数器,需在停止前读取剩余计数。
  • 缓冲区覆盖风险:若数据处理时间过长,新数据可能覆盖未处理缓冲区。建议使用双缓冲区或环形队列。
  • 中断优先级:确保USART中断优先级高于DMA流中断,避免冲突。
  • HAL库版本差异:不同版本HAL库的DMA停止函数可能影响计数器,建议测试验证。
  • 错误处理:添加DMA传输错误中断处理,防止死锁。

通过以上配置与代码,可稳定实现不定长串口数据接收,显著降低CPU负载。实际项目中建议结合环形缓冲区与消息队列,进一步提升可靠性。