You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 04:45:31