原理讲解
串口不定长数据接收是嵌入式开发中的常见需求。传统逐字节中断方式占用CPU资源高,而DMA+空闲中断方案能大幅提升效率。其核心原理:
- DMA传输:将串口接收数据寄存器(DR)自动搬运到内存缓冲区,无需CPU干预。
- 空闲中断(IDLE):当串口总线空闲(即一个字节传输时间后无新数据)时触发中断,标志一帧数据接收完成。
- 组合优势:DMA负责数据搬运,空闲中断负责帧结束判断,CPU仅在帧结束时处理数据,实现高效不定长接收。
配置步骤(以STM32CubeMX为例)
- 使能串口:选择USART1,模式为Asynchronous,配置波特率等参数。
- 配置DMA:在DMA Settings中添加USART1_RX,模式为Circular(循环模式),数据宽度Byte,优先级中。
- 使能中断:在NVIC Settings中勾选USART1全局中断。
- 生成代码:生成初始化代码,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负载。实际项目中建议结合环形缓冲区与消息队列,进一步提升可靠性。