一、为什么需要DMA+空闲中断?
串口接收不定长数据是嵌入式开发中的经典需求。传统轮询方式占用CPU且效率低,而中断方式每字节触发一次中断,在高波特率下CPU负载极高。DMA+空闲中断的组合完美解决了这一问题:
- DMA:自动将串口数据搬运到内存,无需CPU干预,大幅降低负载。
- 空闲中断:当串口总线在接收完一帧数据后出现空闲(超过1个字节时间无新数据),触发中断,通知CPU一帧接收完成。
两者结合,既能高效接收,又能准确判断帧结束,是处理Modbus、GPS、自定义协议等不定长帧的首选方案。
二、原理详解
2.1 DMA传输机制
STM32的DMA控制器可在外设与内存间直接传输数据。串口接收时,配置DMA为外设到内存模式,循环模式(Circular)或普通模式(Normal)。循环模式下,DMA自动重装载计数器,适合持续接收;普通模式需手动重启。
2.2 空闲中断触发条件
当串口检测到接收引脚(RX)上出现一个字节时间的高电平(即无起始位),且之前有数据接收时,硬件置位IDLE标志并触发中断。此时DMA已搬运完当前帧的所有字节,CPU可在中断中读取已接收长度。
2.3 关键寄存器与HAL库接口
-
__HAL_UART_GET_FLAG(&huart, UART_FLAG_IDLE):检查空闲标志。 -
__HAL_UART_CLEAR_IDLEFLAG(&huart):清除空闲标志(需先读SR再读DR)。 -
HAL_UARTEx_ReceiveToIdle_DMA():HAL库提供的扩展函数,内部使能空闲中断和DMA,并自动处理长度计算(STM32CubeMX生成代码时可选)。
三、CubeMX配置步骤
- 使能USART1,模式为Asynchronous,波特率115200。
- 在NVIC Settings中使能USART1全局中断。
- 在DMA Settings中添加USART1_RX的DMA请求,模式选Circular(推荐)或Normal,优先级Medium。
- 生成代码时勾选“Generate peripheral initialization as a pair of .c/.h files”。
四、完整代码实现
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;
4.2 启动接收(在main初始化后调用)
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT); // 关闭半传输中断,避免频繁触发
4.3 空闲中断回调函数
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if (huart->Instance == USART1)
{
rx_len = Size;
rx_flag = 1;
// 重新启动DMA接收,准备下一帧
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);
}
}
4.4 主循环处理数据
while (1)
{
if (rx_flag)
{
rx_flag = 0;
// 处理rx_buffer中长度为rx_len的数据
process_frame(rx_buffer, rx_len);
}
}
五、踩坑记录与注意事项
5.1 空闲中断标志清除问题
HAL库的HAL_UARTEx_ReceiveToIdle_DMA内部已处理标志清除,但若手动实现,必须按顺序:先读SR寄存器,再读DR寄存器,才能清除IDLE标志。否则中断会持续触发。
5.2 DMA循环模式下的数据覆盖
循环模式下,DMA会不断覆盖缓冲区。若处理速度跟不上接收速度,旧数据会被新数据覆盖。解决方案:
- 增大缓冲区。
- 使用双缓冲区(Ping-Pong)或及时处理。
- 在回调中立即拷贝数据到另一缓冲区。
5.3 半传输中断的干扰
HAL_UARTEx_ReceiveToIdle_DMA默认使能了DMA半传输中断(HT),当接收数据达到缓冲区一半时会触发HAL_UARTEx_RxEventCallback,导致误判帧结束。必须在启动后关闭HT中断:
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);
5.4 错误处理与重启
若发生溢出错误(ORE),HAL库可能停止接收。需在HAL_UART_ErrorCallback中重新启动DMA接收:
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
__HAL_DMA_DISABLE_IT(&hdma_usart1_rx, DMA_IT_HT);
}
}
5.5 缓冲区对齐与Cache一致性
若使用带D-Cache的MCU(如STM32H7),DMA写入的数据可能留在Cache中,CPU读取时得到旧数据。需将缓冲区放在非Cache区域(如使用__attribute__((section(".dma_buffer"))))或在读取前调用SCB_InvalidateDCache_by_Addr()。
5.6 中断优先级配置
确保USART1中断优先级高于或等于DMA中断,避免因优先级反转导致数据丢失。
六、总结
DMA+空闲中断是STM32串口接收不定长数据的高效方案。通过合理配置DMA模式、关闭半传输中断、正确处理空闲标志和错误恢复,可构建稳定可靠的通信框架。实际项目中,建议结合环形缓冲区或消息队列,将数据接收与处理解耦,进一步提升系统鲁棒性。
暂无评论,来抢沙发~