| 
 
| 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
 
 
 | 
 |