[研电赛技术支持] GD32F470的DMA usart与dma环形接收回应

[复制链接]
131|0
Zuocidian 发表于 2025-10-10 08:02 | 显示全部楼层 |阅读模式
1.dma驱动流程
开时钟:rcu_periph_clock_enable(RCU_DMA1)
让 DMA1 外设上电可用。

通道复位:dma_deinit(DMA1, DMA_CH2)
清上次配置,避免残留状态。

描述一次“搬运任务”(single-data 参数)

外设地址:&USART_DATA(USART0)(固定,不自增)

目标地址:buf(内存起点,递增)

位宽:8bit

环形模式:DMA_CIRCULAR_MODE_ENABLE(收完 len 字节后从头再写)

方向:PERIPH_TO_MEMORY

计数:len(缓冲区总大小)

优先级:高

映射子外设:dma_channel_subperipheral_select(..., DMA_SUBPERI4)
把 CH2 连到 USART0_RX(按芯片手册对应)。

写入配置并开通道:dma_single_data_mode_init(...) + dma_channel_enable(...)
DMA 进入“随时可收”状态。

让 USART0 使用 DMA 接收:usart_dma_receive_config(USART0, ENABLE)
从此收到的每个字节都会被 DMA 自动搬进 buf。

到这一步:CPU 不搬字节,DMA 按“环形”把数据不断写入 buf[0..len-1],写指针每到末尾就回到 0

#include "dma.h"

void dam1_usart0_channel_2_rx(uint8_t *buf, uint16_t len)
{
    rcu_periph_clock_enable(RCU_DMA1);                 // 1) 打开 DMA1 外设时钟

    dma_deinit(DMA1, DMA_CH2);                         // 2) 复位 DMA1 通道2,清掉上一次配置

    dma_single_data_parameter_struct dma_init_struct;   // 3) 准备一次“单数据模式”的配置结构体

    dma_init_struct.periph_addr = (uint32_t)&USART_DATA(USART0); // 4) 外设源地址=USART0 数据寄存器
    dma_init_struct.periph_inc  = DMA_PERIPH_INCREASE_DISABLE;   //    外设地址不自增(始终读同一寄存器)
    dma_init_struct.memory0_addr = (uint32_t)buf;                // 5) 目标内存首地址=你的接收缓冲区
    dma_init_struct.memory_inc   = DMA_MEMORY_INCREASE_ENABLE;   //    每收1字节,内存地址自增写入下一格
    dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT; // 6) 源/目标位宽=8bit(串口字节)

    dma_init_struct.circular_mode = DMA_CIRCULAR_MODE_ENABLE;     // 7) 环形模式:写满len后从头再写
    dma_init_struct.direction     = DMA_PERIPH_TO_MEMORY;         // 8) 方向:外设→内存
    dma_init_struct.number        = len;                          // 9) 本次循环的总计数=len(缓冲区大小)
    dma_init_struct.priority      = DMA_PRIORITY_HIGH;            // 10) 优先级:高

    dma_channel_subperipheral_select(DMA1, DMA_CH2, DMA_SUBPERI4);// 11) 将通道2 绑定到 USART0_RX(按芯片手册)
    dma_single_data_mode_init(DMA1, DMA_CH2, &dma_init_struct);   // 12) 应用上述配置到通道2
    dma_channel_enable(DMA1, DMA_CH2);                            // 13) 使能通道:DMA开始可接收

    usart_dma_receive_config(USART0, USART_RECEIVE_DMA_ENABLE);   // 14) 打通 USART0→DMA 的接收通路
}



2.USART 基础初始化(usart0_init)
开时钟:RCU_GPIOA、RCU_USART0

配置引脚:PA9/PA10 设为 AF7,推挽输出,上拉输入

配置参数:波特率 baudrate,数据位 8,停止位 1,校验 None(8N1)

开启收发:usart_receive_config()、usart_transmit_config()、usart_enable()

打开中断:启用 USART_INT_IDLE,并在 NVIC 中 nvic_irq_enable(USART0_IRQn, …)


#include "usart.h"

#define RX_BUF_SZ 128
extern uint8_t rx_buffer[RX_BUF_SZ];   // 由 DMA 写入的环形接收缓冲区(定义在 Function.c)
static volatile uint16_t rx_tail = 0;  // 已处理到的位置(本文件未用到,留给主循环/示例)
static volatile uint8_t  idle_hit = 0; // IDLE 发生标记:USART ISR 置1,主循环消费后清0

void usart0_init(uint32_t baudrate)
{
    // --- 1) GPIO 与 USART 外设时钟 ---
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_USART0);

    // --- 2) 配置引脚为复用功能(PA9=TX, PA10=RX)---
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_9);
    gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_10);
    gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,
                            GPIO_PIN_9 | GPIO_PIN_10);
    gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_9 | GPIO_PIN_10); // AF7 = USART0

    // --- 3) USART 参数:波特率/数据位/停止位/校验 ---
    usart_deinit(USART0);
    usart_baudrate_set(USART0, baudrate);
    usart_word_length_set(USART0, USART_WL_8BIT);
    usart_stop_bit_set(USART0, USART_STB_1BIT);
    usart_parity_config(USART0, USART_PM_NONE);

    // --- 4) 允许收发 & 使能 USART ---
    usart_receive_config(USART0, USART_RECEIVE_ENABLE);
    usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
    usart_enable(USART0);

    // --- 5) 关键:打开“空闲线”中断,用来提示一段数据结束(帧边界候选)---
    usart_interrupt_enable(USART0, USART_INT_IDLE);

    // --- 6) NVIC 打开 USART0 的中断入口(优先级按项目需要调整)---
    nvic_irq_enable(USART0_IRQn, 1U, 0U);
}

void usart0_send_byte(uint8_t ch)
{
    // 等待发送数据寄存器空,再发一个字节(阻塞式发送)
    while (usart_flag_get(USART0, USART_FLAG_TBE) == 0) ;
    usart_data_transmit(USART0, ch);
}

void usart0_send_buff(uint8_t *buf, uint16_t len)
{
    // 简单阻塞循环发送缓冲区
    while (len--) {
        usart0_send_byte(*buf++);
    }
}

void usart0_rx_dma(uint8_t *buf, uint16_t len)
{
    // 只在 USART 侧打开“DMA接收开关”
    // 真正的 DMA 通道/环形/地址配置在你的 dam1_usart0_channel_2_rx() 里完成
    usart_dma_receive_config(USART0, ENABLE);
}

// === USART0 中断服务:仅处理“空闲线”事件,做到极短 ===
void USART0_IRQHandler(void)
{
    // 只关心 IDLE 中断,不处理其它事件(ORE/RXNE/TC等)
    if (RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE))
    {
        // 按手册:清 IDLE = 先读 STAT 再读 DATA
        (void)usart_flag_get(USART0, USART_FLAG_IDLE); // 读 STAT(取出 IDLE 标志)
        (void)usart_data_receive(USART0);              // 读 DATA(完成成对清除)

        idle_hit = 1;   // 仅置标记,提示主循环“可以结算这波新增数据了”
    }
}

// === 主循环使用的小工具:消费一次 IDLE 事件并返回当前“写指针 head” ===
// 返回 1 表示这次确实有 IDLE 事件(已清空标记);0 表示没有新事件
uint8_t usart0_idle_take_and_get_head(uint16_t *head_out)
{
    if (!idle_hit) return 0;       // 没有新 IDLE,直接返回
    idle_hit = 0;                  // 消费本次事件(清零,避免重复结算)

    // DMA 剩余计数 CHCNT:配置的总数减去已搬运的数
    // 折算出当前 DMA 写指针 head(落在 0..RX_BUF_SZ 之间)
    uint16_t remain = DMA_CHCNT(DMA1, DMA_CH2);
    *head_out = RX_BUF_SZ - remain;

    return 1;
}



主程序


/************************************************************
* 版权:2025CIMC Copyright。
* 文件:Function.c
* 作者: Lingyu Meng
* 平台: 2025CIMC IHD-V04
* 版本: Lingyu Meng     2025/2/16     V0.01    original
************************************************************/


/************************* 头文件 *************************/

#include "Function.h"

/************************* 宏定义 *************************/


/************************ 变量定义 ************************/
#define RX_BUF_SZ 128
uint8_t rx_buffer[RX_BUF_SZ]; // 供 DMA 写入(环形)

#define LINE_BUF_SZ 64
static uint8_t line_buf[LINE_BUF_SZ]; // 行缓冲:把零散字节先攒起来
static uint16_t line_len = 0;                  // 当前已攒的字节数
static uint8_t prev_was_cr = 0;                  // 上一个字节是不是 '\r'
/************************ 函数定义 ************************/

// 占位:当解析到一整行(不含\r\n)时先回显验证
static void on_line(const uint8_t *data, uint16_t len)
{
        const char *tag = "<LINE> ";
        usart0_send_buff((uint8_t *)tag, 7); // 打标签,只有识别到 \r\n 才会输出

        usart0_send_buff((uint8_t *)data, len);
        const uint8_t crlf[2] = {'\r', '\n'};
        usart0_send_buff((uint8_t *)crlf, 2);
}

// 把一个字节推进行缓冲;遇到 \r\n 时调用 on_line(...)
static inline void linebuf_push(uint8_t ch)
{
        if (prev_was_cr)
        {
                if (ch == '\n')
                {
                        // 命中 \r\n :吐出一整行(不包含 \r\n)
                        on_line(line_buf, line_len);
                        line_len = 0;
                        prev_was_cr = 0;
                        return;
                }
                else
                {
                        // 上一个是 '\r',但这次不是 '\n':把 '\r' 当普通字符塞回去
                        if (line_len < LINE_BUF_SZ)
                                line_buf[line_len++] = '\r';
                        prev_was_cr = 0;
                        // 再继续处理当前字符(fall through)
                }
        }

        if (ch == '\r')
        {
                prev_was_cr = 1; // 先记下来,等下一个字节看看是不是 '\n'
                return;
        }

        // 普通字符:追加;若超长,简单丢弃并复位本行(防止被恶意超长拖垮)
        if (line_len < LINE_BUF_SZ)
        {
                line_buf[line_len++] = ch;
        }
        else
        {
                // 超长:丢弃本行,复位
                line_len = 0;
                prev_was_cr = 0;
        }
}

// 把一段连续内存 [p, p+len) 喂给行缓冲
static inline void feed_segment_to_linebuf(const uint8_t *p, uint16_t len)
{
        for (uint16_t i = 0; i < len; ++i)
        {
                linebuf_push(p[i]);
        }
}

/************************************************************
* Function :       System_Init
* Comment  :       用于初始化MCU
* Parameter:       null
* Return   :       null
* Author   :       Lingyu Meng
* Date     :       2025-02-30 V0.1 original
************************************************************/

void System_Init(void)
{
        systick_config();     // 时钟配置
        usart0_init(115200);
        dam1_usart0_channel_2_rx(rx_buffer,sizeof(rx_buffer));

}

// 轮询
// void UsrFunction(void)
// {
//         static uint8_t last_cnt = 64;
//         static uint8_t curr_cnt ;
//         static uint16_t recv_len;
//         static uint16_t start_pos;
//         while(1)
//         {
//                 curr_cnt = DMA_CHCNT(DMA1,DMA_CH2);
//                 if (curr_cnt != last_cnt)
//                 {
//                         if (last_cnt >= curr_cnt)
//                                 recv_len = last_cnt - curr_cnt;
//                         else
//                                 recv_len = last_cnt + (sizeof(rx_buffer) - curr_cnt);

//                         start_pos = (sizeof(rx_buffer) - last_cnt) % sizeof(rx_buffer);

//                         if (start_pos + recv_len <= sizeof(rx_buffer))
//                         {
//                                 usart0_send_buff(&rx_buffer[start_pos], recv_len);
//                         }
//                         else
//                         {
//                                 uint16_t first_part = sizeof(rx_buffer) - start_pos;
//                                 uint16_t second_part = recv_len - first_part;
//                                 usart0_send_buff(&rx_buffer[start_pos], first_part);
//                                 usart0_send_buff(&rx_buffer[0], second_part);
//                         }
//                         last_cnt = curr_cnt;
//                 }
               
//         }
// }
void UsrFunction(void)
{
        static uint16_t rx_tail = 0; // 我们已经处理到的位置
       

        while (1)
        {
                uint16_t head;
                if (usart0_idle_take_and_get_head(&head)) // 有一次 IDLE 事件
                {
                        if (head != rx_tail)
                        {
                                if (head > rx_tail)
                                {
                                        // [rx_tail, head) 连续一段:回显验证
                                        feed_segment_to_linebuf(&rx_buffer[rx_tail], head - rx_tail);
                                }
                                else
                                {
                                        // 跨环:先到末尾,再从0到head
                                        feed_segment_to_linebuf(&rx_buffer[rx_tail], RX_BUF_SZ - rx_tail);
                                        if (head)
                                                feed_segment_to_linebuf(&rx_buffer[0], head);
                                }
                                rx_tail = head; // 推进处理进度
                        }
                }

                // 你的其它循环任务...
        }
}

/****************************End*****************************/



————————————————
版权声明:本文为CSDN博主「lanhua1304403542」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lanhua1304403542/article/details/151936051

您需要登录后才可以回帖 登录 | 注册

本版积分规则

87

主题

270

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部