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