一、为什么需要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。
-
CubeMX配置:
- 使能USART1异步模式,波特率115200。
- 添加DMA请求:USART1_RX,模式Circular(循环模式),数据宽度Byte。
- 使能USART1全局中断。
-
代码初始化:
- 启动DMA接收:
HAL_UART_Receive_DMA(&huart1, rx_buf, BUF_SIZE); - 使能空闲中断:
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
- 启动DMA接收:
-
中断处理:
- 在
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+空闲中断的高效性。