STM32无需while循环实现UART接收的方法咨询(已用UART_RECEIVE_DMA)
基于DMA的UART非阻塞接收实现方案
你遇到的问题本质是UART_Receive属于阻塞式API:它仅执行一次,只能接收固定长度的字节,收完就停止,所以必须套在循环里才能持续接收,但这样会阻塞主程序的其他逻辑。结合你已经在使用的UART接收DMA,下面给你一套非阻塞、能随时获取触发数据的实现方案:
核心思路
利用DMA的后台数据传输能力,配合UART空闲中断(或DMA传输中断)实现无阻塞接收:DMA在后台自动搬运串口数据到缓冲区,当串口出现空闲(即一段数据传输完成)时触发中断,在中断里处理已接收的数据,同时让DMA持续保持接收状态,主循环可以完全独立执行其他任务,随时从缓存中读取数据。
具体实现步骤(以STM32 HAL库为例)
1. 初始化配置
- 完成UART基本参数(波特率、数据位、停止位等)和DMA接收通道的初始化
- 定义接收缓冲区(DMA用的连续缓冲区+环形缓冲区用于暂存数据)
- 开启UART空闲中断,启动DMA循环接收
#define UART_RX_DMA_BUF_SIZE 128 #define RING_BUF_SIZE 256 // DMA接收缓冲区(连续内存) uint8_t uart_rx_dma_buf[UART_RX_DMA_BUF_SIZE]; // 环形缓冲区结构体 typedef struct { uint8_t buf[RING_BUF_SIZE]; uint16_t head; uint16_t tail; uint16_t count; } RingBuffer; RingBuffer uart_ring_buf = {0};
初始化代码:
// 启动DMA接收,让DMA持续监听UART_RX引脚 HAL_UART_Receive_DMA(&huart1, uart_rx_dma_buf, UART_RX_DMA_BUF_SIZE); // 开启UART空闲中断:当串口无数据传输时触发 __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
2. 中断回调与数据处理
在UART中断服务函数中捕获空闲中断,处理已接收的数据并重启DMA:
void USART1_IRQHandler(void) { // 检查是否触发空闲中断 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET) { __HAL_UART_CLEAR_IDLEFLAG(&huart1); // 清除中断标志 HAL_UART_DMAStop(&huart1); // 暂停DMA,获取已接收字节数 // 计算实际接收长度:缓冲区总大小 - DMA剩余待传输字节数 uint16_t recv_len = UART_RX_DMA_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx); // 将DMA缓冲区的数据写入环形缓冲区 if (recv_len > 0) { RingBuffer_Write(&uart_ring_buf, uart_rx_dma_buf, recv_len); } // 重启DMA接收,保持持续监听 HAL_UART_Receive_DMA(&huart1, uart_rx_dma_buf, UART_RX_DMA_BUF_SIZE); } // 调用HAL库默认中断处理 HAL_UART_IRQHandler(&huart1); }
3. 主循环非阻塞读取数据
主循环中可以随时从环形缓冲区读取数据,完全不影响其他任务执行:
int main(void) { // 系统初始化(略) System_Init(); while (1) { uint8_t single_byte; // 非阻塞读取环形缓冲区的数据 while (RingBuffer_Read(&uart_ring_buf, &single_byte, 1) == 0) { // 处理单个字节数据,比如解析协议、打印等 Process_Received_Data(single_byte); } // 执行其他任务,比如按键扫描、LED控制、传感器读取等 Run_Other_Tasks(); } }
4. 环形缓冲区核心函数实现
// 向环形缓冲区写入数据 int RingBuffer_Write(RingBuffer *rb, uint8_t *data, uint16_t len) { if (rb->count + len > RING_BUF_SIZE) { return -1; // 缓冲区溢出,返回错误 } for (uint16_t i = 0; i < len; i++) { rb->buf[rb->head] = data[i]; rb->head = (rb->head + 1) % RING_BUF_SIZE; rb->count++; } return 0; } // 从环形缓冲区读取单个字节 int RingBuffer_Read(RingBuffer *rb, uint8_t *data, uint16_t len) { if (rb->count == 0) { return -1; // 缓冲区为空,返回错误 } *data = rb->buf[rb->tail]; rb->tail = (rb->tail + 1) % RING_BUF_SIZE; rb->count--; return 0; }
关键注意事项
- 临界区保护:如果主循环和中断同时操作环形缓冲区,需要在写入/读取时禁用中断(
__disable_irq()/__enable_irq()),避免数据错乱 - 缓冲区大小:根据实际数据传输量调整DMA缓冲区和环形缓冲区的大小,防止溢出
- 中断优先级:合理设置UART中断优先级,避免影响其他高优先级任务
内容的提问来源于stack exchange,提问作者user19798123
相关产品推荐
相关产品推荐

