UART+ DMA +FIFO+ 空闲中断 (IDLE) + Queue接收数据
摘要:针对STM32串口高波特率下CPU占用率高的问题,提出DMA+空闲中断+消息队列组合方案。DMA负责数据搬运降低CPU负载,空闲中断检测数据包结束,通过消息队列通知任务处理数据。方案实现了硬件自动搬运、实时响应和任务解耦,特别适合FIFO深度较浅的芯片。工作流程包括DMA初始化、自动搬运、空闲中断触发、队列通知和任务处理五个阶段,通过中断服务程序和主任务协同完成高效数据接收与处理。
摘要:针对STM32串口高波特率下CPU占用率高的问题,提出DMA+空闲中断+消息队列组合方案。DMA负责数据搬运降低CPU负载,空闲中断检测数据包结束,通过消息队列通知任务处理数据。方案实现了硬件自动搬运、实时响应和任务解耦,特别适合FIFO深度较浅的芯片。工作流程包括DMA初始化、自动搬运、空闲中断触发、队列通知和任务处理五个阶段,通过中断服务程序和主任务协同完成高效数据接收与处理。
目录
一、为什么使用这套组合?
| 方案 | 痛点 |
| 仅用中断 (RXNE) | 每收 1 字节进出一次中断,波特率高了 CPU 占用率暴涨(都在干重复活)。 |
| 仅用 DMA | DMA 像个机器人,只管搬运。它不知道对方发完了没有,无法实现实时处理。 |
| DMA + 空闲中断 (IDLE) | 完美。 数据由 DMA 搬运(省 CPU),对方发完一串后触发空闲中断(保实时性)。 |
| 加入消息队列 (RTOS) | 解耦。 中断只负责发信号,复杂的数据解析交给低优先级任务,防止阻塞系统。 |
在硬件 FIFO 深度较浅的芯片(如 STM32F407 ),FIFO 通常是指通过软件实现的环形缓冲区(Ring Buffer)
二、这个方案的完整工作流

在 STM32F407 上,一个典型的接收流程如下:
1.准备阶段:
开辟一个数组作为 DMA 接收区,开启串口的 DMA 传输和 IDLE 中断。
2.数据到来:
数据直接由 DMA 从串口引脚搬运到内存,CPU 此时可以去处理显示屏、电机控制或其他任务,完全不参与搬运。
3.传输结束:
对方停止发送,串口总线空闲超过特定时间,硬件会自动把 SR 寄存器里的 IDLE 位置 1。
4.中断触发:
- 触发
USARTx_IRQHandler。 - 在中断里读取 DMA 寄存器,算出这波到底收了多少字节。
- 发送队列信号:将“数据长度”或“数据就绪标志”塞进队列。
5.任务处理:
主任务收到队列消息,从 DMA 缓冲区(或你拷贝出来的 Ring Buffer)里读取并处理。
当外部数据传入时,每个接收到的字节都会通过 DMA 自动传输到 rx_buffer 缓冲区。DMA 内部配置的 CNDTR 寄存器在每次数据传输完成后自动递减 1。DMA_BufferSize初始值可以自己设置
三、代码逻辑
1.中断处理函数
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) {
// 1. 必须软件清除 IDLE 标志位(F4 的规则是读 SR 再读 DR)
volatile uint32_t temp;
temp = USART1->SR;
temp = USART1->DR;
// 2. 停止 DMA 搬运,计算收到了多少字节
// 剩余量 = DMA_GetCurrentMemoryTarget(DMA2_Stream2); // 或是读 CNDTR
// 接收长度 = 总大小 - 剩余大小
uint16_t rx_len = RX_BUF_SIZE - DMA_GetCurrDataCounter(DMA2_Stream2);
// 3. 发送消息队列通知任务
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// 注意:这里我们可以把长度 rx_len 直接发过去
xQueueSendFromISR(uart_event_queue, &rx_len, &xHigherPriorityTaskWoken);
// 4. 重置 DMA 计数器,准备迎接下一波
DMA_Cmd(DMA2_Stream2, DISABLE);
DMA_SetCurrDataCounter(DMA2_Stream2, RX_BUF_SIZE);
DMA_Cmd(DMA2_Stream2, ENABLE);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
2.主任务
将缓冲区数据存储到比较安全的位置
void vUartTask(void *pvParameters) {
uint16_t packet_len;
while(1) {
// 阻塞等待“门铃”响,拿到的是数据长度
if (xQueueReceive(uart_event_queue, &packet_len, portMAX_DELAY)) {
// 直接处理 rx_buffer 里的前 packet_len 个字节
// 或者将其拷贝到你的 Ring Buffer 进行解析
Parse_Protocol(rx_buffer, packet_len);
}
}
}
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)