基于 GD32 的 USART + DMA + 环形队列 接收方案

目录

一、环形队列(软件环)实现

二、USART + DMA + 空闲中断


在嵌入式项目中,串口通信是最常见的外设之一。而在数据量较大或实时性要求高的场景下,结合 DMA环形队列 可以大幅提升系统的性能与可靠性。本文将以 GD32F4 系列 MCU 为例,手把手教你从零开始搭建一个串口接收模块,覆盖以下内容:

  1. 环形队列(软件环)的基本实现

  2. USART + DMA + 空闲中断 的初始化

  3. 中断服务函数:搬移数据到环形队列

  4. 在主循环/任务中读取并按“包”处理数据

一、环形队列(软件环)实现

/*******************************************************************************
 *                                环形队列接口                                 *
 * 本模块提供简单的循环队列(环形缓冲区)操作,用于暂存字节流并解析协议包。
 *******************************************************************************/

typedef enum {
    QUEUE_OK = 0,    // 操作成功
    QUEUE_OVERLOAD,  // 队列已满
    QUEUE_EMPTY      // 队列为空
} QueueStatus_t;

typedef struct {
    uint32_t head;     // 队头索引(有效数据的起始下标)
    uint32_t tail;     // 队尾索引(下一个写入位置)
    uint32_t size;     // 缓冲区容量
    uint8_t *buffer;   // 存储数组指针
} QueueType_t;

/**
 * @brief  初始化环形队列
 * @param  q   队列对象指针
 * @param  buf 队列存储数组
 * @param  size 数组长度(必须大于 0)
 */
void QueueInit(QueueType_t *q, uint8_t *buf, uint32_t size) {
    q->buffer = buf;
    q->size   = size;
    q->head   = 0;
    q->tail   = 0;
}

/**
 * @brief  向队列压入一个字节
 * @param  q    队列对象指针
 * @param  data 待压入的数据字节
 * @return 队列状态:QUEUE_OK 或 QUEUE_OVERLOAD
 */
QueueStatus_t QueuePush(QueueType_t *q, uint8_t data) {
    uint32_t next = (q->tail + 1) % q->size;
    if (next == q->head) {
        return QUEUE_OVERLOAD; // 队列已满
    }
    q->buffer[q->tail] = data;
    q->tail = next;
    return QUEUE_OK;
}

/**
 * @brief  从队列弹出一个字节
 * @param  q      队列对象指针
 * @param  pdata  存放弹出数据的地址
 * @return 队列状态:QUEUE_OK 或 QUEUE_EMPTY
 */
QueueStatus_t QueuePop(QueueType_t *q, uint8_t *pdata) {
    if (q->head == q->tail) {
        return QUEUE_EMPTY; // 队列为空
    }
    *pdata = q->buffer[q->head];
    q->head = (q->head + 1) % q->size;
    return QUEUE_OK;
}

二、USART + DMA + 空闲中断

/*******************************************************************************
 *                           USB→UART + DMA + 协议定义                          *
 *******************************************************************************/

typedef struct {
    uint32_t uartNo;
    rcu_periph_enum rcuUart;
    rcu_periph_enum rcuGpio;
    uint32_t gpio;
    uint32_t txPin;
    uint32_t rxPin;
    uint8_t irq;
    uint32_t dmaNo;
    rcu_periph_enum rcuDma;
    dma_channel_enum dmaCh;
} UartHwInfo_t;

// 串口硬件配置信息
static UartHwInfo_t g_uartHwInfo = {
    USART0, RCU_USART0, RCU_GPIOA, GPIOA,
    GPIO_PIN_9, GPIO_PIN_10, USART0_IRQn,
    DMA0, RCU_DMA0, DMA_CH4
};

#define USART0_DATA_ADDR    (USART0 + 0x04)   // 串口数据寄存器地址
#define FRAME_HEAD_0        0x55             // 包头字节0
#define FRAME_HEAD_1        0xAA             // 包头字节1
#define CTRL_DATA_LEN       3                // 数据域长度(LED编号+状态 共3字节)
#define PACKET_LEN          (CTRL_DATA_LEN + 4) // 整包长度 = 帧头2 + 长度1 + 功能1 + 数据3 + 校验1
#define MAX_QUEUE_SIZE      64               // 环形队列容量
#define LED_CTRL_CODE       0x06             // 功能字:LED控制

// DMA 中转缓冲区:接收一个完整包
static uint8_t  g_dmaBuf[PACKET_LEN];
static bool     g_pktRcvd = false;        // 标记:收到完整包

// 环形队列缓冲与对象
static uint8_t     g_queueBuf[MAX_QUEUE_SIZE];
static QueueType_t g_rcvQueue;

typedef struct {
    uint8_t ledNo;     // LED 编号
    uint8_t ledState;  // LED 状态(0 关, 非0 开)
} LedCtrlInfo_t;

/*******************************************************************************
 *                                硬件初始化                                  *
 *******************************************************************************/

// GPIO 配置
static void Usb2ComGpioInit(void) {
    rcu_periph_clock_enable(g_uartHwInfo.rcuGpio);
    gpio_init(g_uartHwInfo.gpio, GPIO_MODE_AF_PP, GPIO_OSPEED_10MHZ, g_uartHwInfo.txPin);
    gpio_init(g_uartHwInfo.gpio, GPIO_MODE_IPU,  GPIO_OSPEED_10MHZ, g_uartHwInfo.rxPin);
}

// UART 波特率、收发及中断配置
static void Usb2ComUartInit(uint32_t baudRate) {
    rcu_periph_clock_enable(g_uartHwInfo.rcuUart);
    usart_deinit(g_uartHwInfo.uartNo);
    usart_baudrate_set(g_uartHwInfo.uartNo, baudRate);
    usart_transmit_config(g_uartHwInfo.uartNo, USART_TRANSMIT_ENABLE);
    usart_receive_config(g_uartHwInfo.uartNo, USART_RECEIVE_ENABLE);
    usart_interrupt_enable(g_uartHwInfo.uartNo, USART_INT_IDLE);
    nvic_irq_enable(g_uartHwInfo.irq, 0, 0);
    usart_enable(g_uartHwInfo.uartNo);
}

// DMA 接收配置
static void Usb2ComDmaInit(void) {
    rcu_periph_clock_enable(g_uartHwInfo.rcuDma);
    dma_deinit(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh);

    dma_parameter_struct dmaStruct;
    dmaStruct.direction    = DMA_PERIPHERAL_TO_MEMORY;        // 外设->内存
    dmaStruct.periph_addr  = USART0_DATA_ADDR;                // 源:串口数据寄存器
    dmaStruct.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;     // 源地址不递增
    dmaStruct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;       // 每次读1字节
    dmaStruct.memory_addr  = (uint32_t)g_dmaBuf;              // 目的:DMA缓冲区
    dmaStruct.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;      // 目的地址递增
    dmaStruct.memory_width = DMA_MEMORY_WIDTH_8BIT;           // 每次写1字节
    dmaStruct.number       = PACKET_LEN;                      // 接收字节数 = 一个包长度
    dmaStruct.priority     = DMA_PRIORITY_HIGH;

    dma_init(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh, &dmaStruct);
    usart_dma_receive_config(g_uartHwInfo.uartNo, USART_RECEIVE_DMA_ENABLE);
    dma_channel_enable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh);
}

// 总体初始化接口:GPIO + UART + DMA + 队列
void Usb2ComDrvInit(void) {
    // 初始化环形队列
    QueueInit(&g_rcvQueue, g_queueBuf, MAX_QUEUE_SIZE);

    Usb2ComGpioInit();
    Usb2ComUartInit(115200);
    Usb2ComDmaInit();
}

/*******************************************************************************
 *                             校验 & LED 控制                                *
 *******************************************************************************/

// 异或校验:对 data[0..len-1] 做 XOR
static uint8_t CalXorSum(const uint8_t *data, uint32_t len) {
    uint8_t sum = 0;
    for (uint32_t i = 0; i < len; i++) {
        sum ^= data[i];
    }
    return sum;
}

// 执行 LED 控制:根据状态打开或关闭 LED
static void CtrlLed(const LedCtrlInfo_t *info) {
    if (info->ledState) TurnOnLed(info->ledNo);
    else                TurnOffLed(info->ledNo);
}
/*******************************************************************************
 *                         USART 中断 & DMA 完成标志                            *
 *******************************************************************************/

void USART0_IRQHandler(void) {
    // 判断是否为空闲中断(收包完成)
    if (usart_interrupt_flag_get(g_uartHwInfo.uartNo, USART_INT_FLAG_IDLE) != RESET) {
        usart_interrupt_flag_clear(g_uartHwInfo.uartNo, USART_INT_FLAG_IDLE);
        usart_data_receive(g_uartHwInfo.uartNo);  // 清除残余标志

        // 如果 DMA 恰好传输了一个完整包长度的字节数
        if (PACKET_LEN == (PACKET_LEN - dma_transfer_number_get(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh))) {
            g_pktRcvd = true; // 标记包接收完成
        }
        // 重启 DMA,准备接收下一个包
        dma_channel_disable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh);
        dma_transfer_number_config(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh, PACKET_LEN);
        dma_channel_enable(g_uartHwInfo.dmaNo, g_uartHwInfo.dmaCh);
    }
}

/*******************************************************************************
 *                             主任务:包处理                                 *
 *******************************************************************************/

void Usb2ComTask(void) {
    if (!g_pktRcvd) return;  // 没有新包就退出
    g_pktRcvd = false;       // 清标志,准备下一次接收

    // 将 DMA 缓冲区中的一个整包写入环形队列
    for (uint32_t i = 0; i < PACKET_LEN; i++) {
        if (QueuePush(&g_rcvQueue, g_dmaBuf[i]) == QUEUE_OVERLOAD) {
            // 若队列满,直接丢弃后续字节
            break;
        }
    }

    // 循环处理队列中所有完整包
    uint8_t temp[PACKET_LEN];
    while ((g_rcvQueue.tail + g_rcvQueue.size - g_rcvQueue.head) % g_rcvQueue.size >= PACKET_LEN) {
        // 从队列头依次弹出 PACKET_LEN 字节到 temp
        for (uint32_t i = 0; i < PACKET_LEN; i++) {
            QueuePop(&g_rcvQueue, &temp[i]);
        }
        // 校验帧头
        if (temp[0] != FRAME_HEAD_0 || temp[1] != FRAME_HEAD_1) continue;
        // 校验异或
        if (CalXorSum(temp, PACKET_LEN - 1) != temp[PACKET_LEN - 1]) continue;
        // 解析功能字并控制 LED
        if (temp[3] == LED_CTRL_CODE) {
            LedCtrlInfo_t info = { temp[4], temp[5] };
            CtrlLed(&info);
        }
    }
}
  • QueueInit / QueuePush / QueuePop:环形队列基本操作,管理字节的缓冲、避免覆盖。

  • DMA 接收缓冲 g_dmaBuf:只接收一个包长度的数据。

  • USART0_IRQHandler:识别空闲中断,标记包就绪,重启 DMA。

  • Usb2ComTask:将接收到的一整包数据推入队列,然后循环检查队列中是否有完整包,依次弹出、校验帧头和异或、解析 LED 控制命令。

  • 校验逻辑CalXorSum(data, len) 做 XOR,和尾部校验字对比。

  • LED 控制CtrlLed() 根据 ledState 点亮或熄灭指定 LED。

三、原理流程图:


文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。

### GD32 USART DMA 接收数据 示例教程 #### 配置USARTDMA 为了使GD32微控制器能够通过USART接口并借助DMA完成高效的数据接收,需要先配置好相应的USART参数以及DMA通道。这包括但不限于波特率、数据位长度、停止位数量及校验方式的选择[^1]。 对于USART的具体设定,可以参照如下代码片段来初始化串口: ```c void usart_dma_init(void){ /* 初始化结构体 */ gd32_usart_struct usart_initpara; rcu_periph_clock_enable(RCU_USARTx); rcu_periph_clock_enable(RCU_DMA); usart_deinit(USARTx); usart_initpara.baud_rate = 115200; usart_initpara.word_length = USART_WL_8BIT; usart_initpara.stop_bits = USART_STB_1BIT; usart_initpara.parity = USART_PM_NONE; usart_initpara.half_duplex_select = USART_HDSEL_DISABLE; usart_initpararts_cts_enable = USART_RTS_CTS_DISABLE; usart_init(USARTx,&usart_initpara); } ``` 接着要准备DMA的相关设置,确保其能与上述已定义好的USART实例配合无间地运作。这里给出一段用于启动DMA接收模式下的典型编程逻辑: ```c /* 设置DMA参数 */ static void dma_config(void){ dma_parameter_struct dma_initstruct; dma_deinit(DMAy_Channelx); dma_initstruct.direction=DMA_MEMORY_TO_PERIPHERAL; dma_initstruct.memory_addr=(uint32_t)rx_buffer; dma_initstruct.periph_addr=(uint32_t)&(USARTx->DATA_REG); dma_initstruct.priority=DMA_PRIORITY_HIGH; dma_initstruct.number=RX_BUFFER_SIZE; dma_initstruct.memory_width=DMA_MEMORY_WIDTH_8BIT; dma_initstruct.periph_width=DMA_PERIPH_WIDTH_8BIT; dma_initstruct.memory_inc=DMA_MEMORY_INCREASE_ENABLE; dma_initstruct.periph_inc=DMA_PERIPH_INCREASE_DISABLE; dma_init(DMAy_Channelx,&dma_initstruct); } // 开启DMA接收 void start_dma_receive(){ usart_dma_receive_config(USARTx,DMA_RECEIVE_ENABLE); dma_channel_enable(DMAy_Channelx); } ``` 以上两段C语言代码分别完成了USART硬件资源的基础搭建及其对应的DMA传输路径规划工作。 当一切就绪之后,在应用程序入口处调用这些函数即可开启基于DMA机制的异步数据采集流程。值得注意的是,实际操作过程中还需关注可能遇到的各种异常状况处理策略,比如溢出保护或是错误恢复措施等[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值