一、为什么需要DMA+空闲中断?
在嵌入式开发中,串口接收变长数据帧是高频需求。传统逐字节中断方式存在两个痛点:
- CPU占用高:每个字节触发一次中断,高波特率下频繁进出中断消耗大量资源。
- 帧边界难判定:没有固定长度或结束符时,难以判断一帧何时结束。
DMA+空闲中断方案完美解决上述问题:
- DMA自动搬运数据到缓冲区,CPU无需干预。
- 串口空闲中断(IDLE)在总线空闲时触发,标志一帧数据接收完成。
- 结合两者,可实现高效、可靠的变长帧接收。
二、原理讲解
2.1 串口空闲中断(IDLE)
当串口接收完一帧数据后,总线保持空闲状态(无新数据)超过一个字节传输时间,硬件置位IDLE标志并触发中断(若使能)。利用此特性可检测帧结束。
2.2 DMA接收机制
配置DMA为循环模式或普通模式,将USART数据寄存器(DR)自动搬运到用户缓冲区。DMA传输完成中断(TC)可通知缓冲区满,但变长帧更依赖IDLE中断。
2.3 协同工作流程
- 初始化DMA接收,缓冲区首地址和长度设定。
- 每接收一个字节,DMA自动存储并递减剩余计数。
- 一帧结束触发IDLE中断,在中断中计算已接收数据长度(缓冲区总长 - DMA剩余计数)。
- 处理数据后,重新配置DMA以接收下一帧。
三、CubeMX配置步骤
以STM32F407 + USART1为例:
- 使能USART1:异步模式,波特率115200,8N1。
- 使能USART1全局中断:NVIC中勾选USART1中断。
- 添加DMA通道:USART1_RX,模式设为Normal(普通模式),优先级中。
- 生成代码:HAL库初始化代码自动生成。
四、完整代码示例
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 启动DMA接收与空闲中断
在main初始化后调用:
HAL_UART_Receive_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
4.3 空闲中断处理
在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);
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.4 主循环处理数据
while (1)
{
if(rx_flag)
{
// 处理rx_buffer中rx_len字节数据
process_data(rx_buffer, rx_len);
rx_flag = 0;
}
}
五、踩坑记录与注意事项
5.1 DMA计数器读取时机
- 坑:在IDLE中断中直接读取DMA计数器可能因DMA未停止而得到错误值。
-
解:先调用
HAL_UART_DMAStop()停止DMA,再读取__HAL_DMA_GET_COUNTER()。
5.2 清除IDLE标志的正确方式
-
坑:使用
__HAL_UART_CLEAR_IDLEFLAG()可能无效,需先读SR再读DR。 -
解:HAL库已封装该宏,但确保在中断中调用。若无效,可手动:
__HAL_UART_CLEAR_PEFLAG(&huart1);
5.3 重新启动DMA的时机
- 坑:在中断中处理完数据后未及时重启DMA,导致后续数据丢失。
-
解:在中断末尾立即调用
HAL_UART_Receive_DMA()重启接收。
5.4 缓冲区溢出风险
- 坑:一帧数据超过缓冲区大小,DMA传输完成中断触发但IDLE未触发,数据被覆盖。
- 解:使能DMA传输完成中断,在TC中断中处理满帧情况,或增大缓冲区。
5.5 中断优先级配置
- 坑:USART中断优先级低于DMA,导致IDLE响应延迟。
- 解:设置USART中断优先级高于DMA,确保及时响应。
5.6 使用HAL_UARTEx_ReceiveToIdle_DMA(推荐)
对于STM32HAL库较新版本(如F4系列1.27.0以上),可直接使用HAL_UARTEx_ReceiveToIdle_DMA(),该函数内部集成空闲中断处理,简化代码。
HAL_UARTEx_ReceiveToIdle_DMA(&huart1, rx_buffer, RX_BUFFER_SIZE);
并在回调函数中处理:
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if(huart->Instance == USART1)
{
rx_len = Size;
rx_flag = 1;
}
}
六、总结
DMA+空闲中断是STM32串口变长帧接收的高效方案。关键点:正确配置DMA和IDLE中断、中断中先停DMA再读计数器、及时重启接收、注意缓冲区大小与优先级。推荐使用HAL库的HAL_UARTEx_ReceiveToIdle_DMA简化开发。掌握这些细节,可显著提升串口通信的稳定性和CPU效率。
暂无评论,来抢沙发~