当前位置: 首页 > news >正文

掌握DMA基于GD32F407VE的天空星的配置

掌握DMA基于GD32F407VE的天空星的配置

1. 什么是DMA?

DMA(Direct Memory Access,直接存储器存取)是一种能够在无需CPU参与的情况下,将数据从一个地址空间复制到另一个地址空间的高效传输硬件机制。

1.1 基础概念类比

为了更好地理解DMA,我们可以用一个生动的类比:

组件类比作用
CPU公司员工处理各种任务,如计算、决策和指挥,但不适合花时间亲自搬运每个文件
RAM办公桌存储需要处理的信息和任务,便于员工随时取用
DMA控制器快递员负责在公司(RAM)和仓库(外设)之间搬运数据,让员工不必亲自处理

1.2 为什么需要DMA?

不使用DMA的情况

  • CPU需要亲自搬运数据,手头工作被中断,效率低下

使用DMA的情况

  • CPU发出指令后,DMA控制器自动处理数据搬运
  • CPU可以继续其他工作,不被搬运任务打断

2. DMA的核心特性

2.1 三种传输方式

  1. 存储器到外设 - 如内存数据发送到串口
  2. 外设到存储器 - 如从串口接收数据到内存
  3. 存储器到存储器 - 仅DMA1支持

2.2 优先级机制

  • 软件优先级:低、中、高、超高
  • 硬件优先级:通道号越低,优先级越高

3. DMA内存到内存传输

3.1 基本配置

// 内存拷贝到内存(必须使用DMA1, 随便选一个通道)
#define DMA_PERIPH_CH   DMA1, DMA_CH0
#define ARR_LEN 1024
char src[ARR_LEN] = "hello";
char dst[ARR_LEN] = {0};
void DMA_config() {// 时钟使能rcu_periph_clock_enable(RCU_DMA1);// 重置dma_deinit(DMA_PERIPH_CH);dma_single_data_parameter_struct init_struct;// 结构体参数初始化dma_single_data_para_struct_init(&init_struct);// ============ 内存到内存拷贝// 方向: 内存到内存   如果是内存到内存, periph作为内存的源头init_struct.direction = DMA_MEMORY_TO_MEMORY;;// 内存源头periph   内存到内存periph成为内存的源头init_struct.periph_addr = (uint32_t)src;init_struct.periph_inc = DMA_PERIPH_INCREASE_ENABLE; // 增长// 内存目的init_struct.memory0_addr = (uint32_t)dst;init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 增长// 搬运一个数据的大小init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT; // 8位// 搬运数据个数init_struct.number = ARR_LEN;// 优先级init_struct.priority = DMA_PRIORITY_HIGH;// DMA初始化, 设置搬运规则dma_single_data_mode_init(DMA_PERIPH_CH, &init_struct);// ================= 中断相关nvic_irq_enable(DMA1_Channel0_IRQn, 2, 2);  // 优先级dma_interrupt_enable(DMA_PERIPH_CH, DMA_INT_FTF); // 启动中断
}
//中断函数
void DMA1_Channel0_IRQHandler() {if (SET == dma_interrupt_flag_get(DMA_PERIPH_CH, DMA_INT_FLAG_FTF)) {dma_interrupt_flag_clear(DMA_PERIPH_CH, DMA_INT_FLAG_FTF);printf("DMA1_Channel0_IRQHandler dst = %s\n", dst);}
}

3.2 启动传输与等待完成

//中断函数完成传输后执行中断
void DMA1_Channel0_IRQHandler() {if (SET == dma_interrupt_flag_get(DMA_PERIPH_CH, DMA_INT_FLAG_FTF)) {//清除中断dma_interrupt_flag_clear(DMA_PERIPH_CH, DMA_INT_FLAG_FTF);//输出内容printf("DMA1_Channel0_IRQHandler dst = %s\n", dst);}
}

3.3 动态配置数据源

void USART0_on_recv(uint8_t* data, uint32_t len) {printf("recv[%d]:%s\n", len, data);// 动态指定内存源头  如果是内存到内存, periph作为内存的源头dma_periph_address_config(DMA_PERIPH_CH, (uint32_t)data);// 指定搬运个数dma_transfer_number_config(DMA_PERIPH_CH, len + 1); // +1, 字符串增加一个结束符// 通知DMA干活,它开开始搬运dma_channel_enable(DMA_PERIPH_CH);
}

4. DMA内存到外设传输(以USART发送为例)

4.1 配置步骤

#define USART0_DATA_ADDR          (uint32_t)&USART_DATA(USART0) // 外设地址
#define USART0_TX_DMA_RCU         RCU_DMA1
#define USART0_TX_DMA_PERIPH_CH   DMA1, DMA_CH7
#define USART0_TX_DMA_PERIPH_SUB  DMA_SUBPERI4
//  DMA  内存到外设  函数定义
static void DMA_tx_config() {// 时钟使能rcu_periph_clock_enable(USART0_TX_DMA_RCU);// 重置dma_deinit(USART0_TX_DMA_PERIPH_CH);dma_single_data_parameter_struct init_struct;// 结构体参数初始化dma_single_data_para_struct_init(&init_struct);// ============ 内存到外设拷贝// 方向: 内存到外设   init_struct.direction = DMA_MEMORY_TO_PERIPH;// 源头:内存
//	init_struct.memory0_addr = (uint32_t)?;  // 内存内容动态变化,不固定init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 增长// 目的:外设init_struct.periph_addr = USART0_DATA_ADDR;init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 不增长// 搬运一个数据的大小init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT; // 8位// 搬运数据个数
//	init_struct.number = ?; // 动态变化// 优先级init_struct.priority = DMA_PRIORITY_HIGH;// DMA初始化, 设置搬运规则dma_single_data_mode_init(USART0_TX_DMA_PERIPH_CH, &init_struct);// 设置子外设dma_channel_subperipheral_select(USART0_TX_DMA_PERIPH_CH, USART0_TX_DMA_PERIPH_SUB);
}

4.2 DMA发送数据

#include "USART0.h"
#include <string.h>
#define USART0_DATA_ADDR          (uint32_t)&USART_DATA(USART0) // 外设地址
#define USART0_TX_DMA_RCU         RCU_DMA1
#define USART0_TX_DMA_PERIPH_CH   DMA1, DMA_CH7
#define USART0_TX_DMA_PERIPH_SUB  DMA_SUBPERI4
//  DMA  内存到外设  函数定义
static void DMA_tx_config() {// 时钟使能rcu_periph_clock_enable(USART0_TX_DMA_RCU);// 重置dma_deinit(USART0_TX_DMA_PERIPH_CH);dma_single_data_parameter_struct init_struct;// 结构体参数初始化dma_single_data_para_struct_init(&init_struct);// ============ 内存到外设拷贝// 方向: 内存到外设   init_struct.direction = DMA_MEMORY_TO_PERIPH;// 源头:内存
//	init_struct.memory0_addr = (uint32_t)?;  // 内存内容动态变化,不固定init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 增长// 目的:外设init_struct.periph_addr = USART0_DATA_ADDR;init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 不增长// 搬运一个数据的大小init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT; // 8位// 搬运数据个数
//	init_struct.number = ?; // 动态变化// 优先级init_struct.priority = DMA_PRIORITY_HIGH;// DMA初始化, 设置搬运规则dma_single_data_mode_init(USART0_TX_DMA_PERIPH_CH, &init_struct);// 设置子外设dma_channel_subperipheral_select(USART0_TX_DMA_PERIPH_CH, USART0_TX_DMA_PERIPH_SUB);
}
void USART0_init() {DMA_tx_config(); //  DMA  内存到外设  函数调用// 串口0 TX 复用功能   GPIO_output_af(USART0_TX_RCU, USART0_TX_PORT, USART0_TX_PIN, GPIO_OTYPE_PP, USART0_TX_AF);// 串口0 RX 复用功能  GPIO_output_af(USART0_RX_RCU, USART0_RX_PORT, USART0_RX_PIN, GPIO_OTYPE_PP, USART0_RX_AF);// =========== 串口配置// 时钟使能rcu_periph_clock_enable(RCU_USART0);// 重置串口(可选)usart_deinit(USART0);// 设置波特率(必须要配置)usart_baudrate_set(USART0, USART0_BAUDRATE);// 允许串口发送usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);// ==============++ 串口允许DMA发送usart_dma_transmit_config(USART0, USART_TRANSMIT_DMA_ENABLE);// 允许串口接收usart_receive_config(USART0, USART_RECEIVE_ENABLE);// ================中断配置// 中断优先级,使能中断请求  nvic_irq_enable在misc.h中// 参数1: 中断类型枚举常量, 在 gd32f4xx.h第159行// 参数2: 抢占优先级// 参数3: 响应优先级nvic_irq_enable(USART0_IRQn, USART0_PRIORITY);// (使能串口中断)RBNE, 有数据可读,自动触发中断,配置中断触发条件usart_interrupt_enable(USART0, USART_INT_RBNE);// IDLE, 空闲触发中断,数据读取完毕(或发送完毕),即可触发中断usart_interrupt_enable(USART0, USART_INT_IDLE);// 使能串口usart_enable(USART0);
}// 发送1个byte数据
void USART0_send_byte(uint8_t byte){
#if 0 // 上面为非DMAusart_data_transmit(USART0, byte);// 等待发送完成, 没有完成,说明返回值为RESET, 一直循环,直到变为SET退出循环// USART_FLAG_TBE: transmit data buffer empty  发送缓冲区为空// 发送缓冲区为空, 硬件置1,说明发送完成   SET就是1while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
#else // 下面为DMAUSART0_send_data(&byte, 1);
#endif
}// 发送多个byte数据
void USART0_send_data(uint8_t* data, uint32_t len){if (data == NULL) return;
#if 0for(uint32_t i = 0; i < len; i++) {USART0_send_byte(data[i]); // 发送单个字符}
#else// 指定DMA内存地址dma_memory_address_config(USART0_TX_DMA_PERIPH_CH, DMA_MEMORY_0, (uint32_t)data);// 指定DMA发送个数dma_transfer_number_config(USART0_TX_DMA_PERIPH_CH, len);// 通知DMA干活,它开开始搬运dma_channel_enable(USART0_TX_DMA_PERIPH_CH);// 等待搬运完成   只有没有搬完RESET,进入循环while(RESET == dma_flag_get(USART0_TX_DMA_PERIPH_CH, DMA_FLAG_FTF));// 循环的外面,清除标志位, 不清除不能重复搬运dma_flag_clear(USART0_TX_DMA_PERIPH_CH, DMA_FLAG_FTF);
#endif
}// 发送字符串 (结尾标记\0)
void USART0_send_string(char *data){if (data == NULL) return;
#if 0	for(uint32_t i = 0; data[i] != '\0'; i++) {USART0_send_byte(data[i]); // 发送单个字符}
#elseUSART0_send_data((uint8_t*)data, strlen(data)); // 需要#include <string.h>
#endif
}
#if USART0_PRINTF
#include <stdio.h>
// 重写fputc, printf即可使用
int fputc(int ch, FILE *f) {USART0_send_byte(ch); // 串口发送1个字节return ch;
}
#endif
/*
1. 串口中断处理函数,函数名,不能乱写,因为汇编启动文件已经规定好
USART0_IRQHandler  在 startup_gd32f407_427.s 的124行 
2. 触发中断的条件有很多,一定要通过标志位区分,处理完也要清零
STC8 接收逻辑
if(COM1.RX_Cnt >= COM_RX1_Lenth)	COM1.RX_Cnt = 0;
RX1_Buffer[COM1.RX_Cnt++] = SBUF;
*/
#define RX0_LENTH	128		// 最大大小,数组长度
static uint8_t RX0_Buffer[RX0_LENTH + 1]; // +1,多一个位置给字符串结束符
static uint32_t  rx0_cnt = 0; // 元素个数void USART0_IRQHandler() {// RBNE(有数据可读) 触发的中断,硬件置1(SET)if (SET == usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)) {// 软件清零, 清零后,才能反复读取usart_interrupt_flag_clear(USART0, USART_INT_FLAG_RBNE);// 读取1个字节uint8_t data = usart_data_receive(USART0);if(rx0_cnt >= RX0_LENTH)	rx0_cnt = 0; // 越界处理// 来一个数据,保存到一个数组中RX0_Buffer[rx0_cnt++] = data;}// IDLE, 空闲触发中断,数据读取完毕(或发送完毕)if (SET == usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE)) { // 清除标志位usart_data_receive(USART0); // 读取一次,不是为了读数据,为了清除空闲的标志位// %s 打印只针对字符串,如果内容为16进制,%s打印会乱码RX0_Buffer[rx0_cnt] = '\0'; // 字符串结束符, 如果内容为16进制,这个处理没有意义#if USART0_RECV_CALLBACK// 收到串口0数据,回调函数USART0_on_recv(RX0_Buffer, rx0_cnt); // 函数调用#endifrx0_cnt = 0; // 元素个数置零}
}

5. DMA外设到内存传输(以USART接收为例)

5.1 配置步骤

#define RX0_LENTH	128		// 最大大小,数组长度
static uint8_t RX0_Buffer[RX0_LENTH + 1]; // +1,多一个位置给字符串结束符
static uint32_t  rx0_cnt = 0; // 元素个数
#define USART0_DATA_ADDR          (uint32_t)&USART_DATA(USART0) // 外设地址
#define USART0_RX_DMA_RCU         RCU_DMA1
#define USART0_RX_DMA_PERIPH_CH   DMA1, DMA_CH5
#define USART0_RX_DMA_PERIPH_SUB  DMA_SUBPERI4
//  DMA  外设到内存  函数定义
static void DMA_rx_config() {// 时钟使能rcu_periph_clock_enable(USART0_RX_DMA_RCU);// 重置dma_deinit(USART0_RX_DMA_PERIPH_CH);dma_single_data_parameter_struct init_struct;// 结构体参数初始化dma_single_data_para_struct_init(&init_struct);// ============ 外设到内存拷贝// 方向: 外设到内存  init_struct.direction = DMA_PERIPH_TO_MEMORY;// 源头:外设init_struct.periph_addr = USART0_DATA_ADDR;init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 不增长// 目的:内存init_struct.memory0_addr = (uint32_t)RX0_Buffer;  // 指定的数组init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 增长// 搬运一个数据的大小init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT; // 8位// 搬运数据个数init_struct.number = RX0_LENTH; // 最大的搬运个数,实际有可能少于它// 优先级init_struct.priority = DMA_PRIORITY_HIGH;// DMA初始化, 设置搬运规则dma_single_data_mode_init(USART0_RX_DMA_PERIPH_CH, &init_struct);// 设置子外设dma_channel_subperipheral_select(USART0_RX_DMA_PERIPH_CH, USART0_RX_DMA_PERIPH_SUB);// 通知DMA干活,它开始搬运,可以用DMA接收数据// 这里不要等待搬运完成,因为不知道啥时候有数据来dma_channel_enable(USART0_RX_DMA_PERIPH_CH);
}
void USART0_init() {DMA_tx_config(); //  DMA  内存到外设  函数调用DMA_rx_config(); //  DMA  外设到内存  函数定义// 串口0 TX 复用功能   GPIO_output_af(USART0_TX_RCU, USART0_TX_PORT, USART0_TX_PIN, GPIO_OTYPE_PP, USART0_TX_AF);// 串口0 RX 复用功能  GPIO_output_af(USART0_RX_RCU, USART0_RX_PORT, USART0_RX_PIN, GPIO_OTYPE_PP, USART0_RX_AF);// =========== 串口配置// 时钟使能rcu_periph_clock_enable(RCU_USART0);// 重置串口(可选)usart_deinit(USART0);// 设置波特率(必须要配置)usart_baudrate_set(USART0, USART0_BAUDRATE);// 允许串口发送usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);// ==============++ 串口允许DMA发送usart_dma_transmit_config(USART0, USART_TRANSMIT_DMA_ENABLE);// 允许串口接收usart_receive_config(USART0, USART_RECEIVE_ENABLE);// ==============++ 串口允许DMA接收usart_dma_receive_config(USART0, USART_RECEIVE_DMA_ENABLE);// ================中断配置// 中断优先级,使能中断请求  nvic_irq_enable在misc.h中// 参数1: 中断类型枚举常量, 在 gd32f4xx.h第159行// 参数2: 抢占优先级// 参数3: 响应优先级nvic_irq_enable(USART0_IRQn, USART0_PRIORITY);#if 0  // 非DMA才用// (使能串口中断)RBNE, 有数据可读,自动触发中断,配置中断触发条件usart_interrupt_enable(USART0, USART_INT_RBNE);#endif// IDLE, 空闲触发中断,数据读取完毕(或发送完毕),即可触发中断usart_interrupt_enable(USART0, USART_INT_IDLE); // 如果用DMA,空闲了,说明数据全部搬运完成,再处理数据// 使能串口usart_enable(USART0);
}
// 发送1个byte数据
void USART0_send_byte(uint8_t byte){
#if 0 // 上面为非DMAusart_data_transmit(USART0, byte);// 等待发送完成, 没有完成,说明返回值为RESET, 一直循环,直到变为SET退出循环// USART_FLAG_TBE: transmit data buffer empty  发送缓冲区为空// 发送缓冲区为空, 硬件置1,说明发送完成   SET就是1while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
#else // 下面为DMAUSART0_send_data(&byte, 1);
#endif
}
// 发送多个byte数据
void USART0_send_data(uint8_t* data, uint32_t len){if (data == NULL) return;
#if 0for(uint32_t i = 0; i < len; i++) {USART0_send_byte(data[i]); // 发送单个字符}
#else// 指定DMA内存地址dma_memory_address_config(USART0_TX_DMA_PERIPH_CH, DMA_MEMORY_0, (uint32_t)data);// 指定DMA发送个数dma_transfer_number_config(USART0_TX_DMA_PERIPH_CH, len);// 通知DMA干活,它开开始搬运dma_channel_enable(USART0_TX_DMA_PERIPH_CH);// 等待搬运完成   只有没有搬完RESET,进入循环while(RESET == dma_flag_get(USART0_TX_DMA_PERIPH_CH, DMA_FLAG_FTF));// 循环的外面,清除标志位, 不清除不能重复搬运dma_flag_clear(USART0_TX_DMA_PERIPH_CH, DMA_FLAG_FTF);
#endif
}
// 发送字符串 (结尾标记\0)
void USART0_send_string(char *data){if (data == NULL) return;
#if 0	for(uint32_t i = 0; data[i] != '\0'; i++) {USART0_send_byte(data[i]); // 发送单个字符}
#elseUSART0_send_data((uint8_t*)data, strlen(data)); // 需要#include <string.h>
#endif
}
#if USART0_PRINTF
#include <stdio.h>
// 重写fputc, printf即可使用
int fputc(int ch, FILE *f) {USART0_send_byte(ch); // 串口发送1个字节return ch;
}
#endif
/*
1. 串口中断处理函数,函数名,不能乱写,因为汇编启动文件已经规定好
USART0_IRQHandler  在 startup_gd32f407_427.s 的124行 
2. 触发中断的条件有很多,一定要通过标志位区分,处理完也要清零
STC8 接收逻辑
if(COM1.RX_Cnt >= COM_RX1_Lenth)	COM1.RX_Cnt = 0;
RX1_Buffer[COM1.RX_Cnt++] = SBUF;
*/
void USART0_IRQHandler() {
#if 0 // 非DMA// RBNE(有数据可读) 触发的中断,硬件置1(SET)if (SET == usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)) {// 软件清零, 清零后,才能反复读取usart_interrupt_flag_clear(USART0, USART_INT_FLAG_RBNE);// 读取1个字节uint8_t data = usart_data_receive(USART0);if(rx0_cnt >= RX0_LENTH)	rx0_cnt = 0; // 越界处理// 来一个数据,保存到一个数组中RX0_Buffer[rx0_cnt++] = data;}// IDLE, 空闲触发中断,数据读取完毕(或发送完毕)if (SET == usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE)) { // 清除标志位usart_data_receive(USART0); // 读取一次,不是为了读数据,为了清除空闲的标志位// %s 打印只针对字符串,如果内容为16进制,%s打印会乱码RX0_Buffer[rx0_cnt] = '\0'; // 字符串结束符, 如果内容为16进制,这个处理没有意义#if USART0_RECV_CALLBACK// 收到串口0数据,回调函数USART0_on_recv(RX0_Buffer, rx0_cnt); // 函数调用#endifrx0_cnt = 0; // 元素个数置零}
#else // DMAif (SET == usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE)) { // 清除标志位usart_data_receive(USART0); // 读取一次,不是为了读数据,为了清除空闲的标志位// 禁用DMAdma_channel_disable(USART0_RX_DMA_PERIPH_CH);// 获取剩余没有搬运的个数uint32_t left_num = dma_transfer_number_get(USART0_RX_DMA_PERIPH_CH);// 搬运的个数 = 总个数 - 剩余没有搬运的个数rx0_cnt = RX0_LENTH - left_num;// %s 打印只针对字符串,如果内容为16进制,%s打印会乱码RX0_Buffer[rx0_cnt] = '\0'; // 字符串结束符, 如果内容为16进制,这个处理没有意义#if USART0_RECV_CALLBACK// 收到串口0数据,回调函数USART0_on_recv(RX0_Buffer, rx0_cnt); // 函数调用#endifrx0_cnt = 0; // 元素个数置零// 清除标志位, 放在启动前面dma_flag_clear(USART0_RX_DMA_PERIPH_CH, DMA_FLAG_FTF);// 启动DMAdma_channel_enable(USART0_RX_DMA_PERIPH_CH);	}
#endif

5.2 处理接收数据


/*
1. 串口中断处理函数,函数名,不能乱写,因为汇编启动文件已经规定好
USART0_IRQHandler  在 startup_gd32f407_427.s 的124行 
2. 触发中断的条件有很多,一定要通过标志位区分,处理完也要清零
STC8 接收逻辑
if(COM1.RX_Cnt >= COM_RX1_Lenth)	COM1.RX_Cnt = 0;
RX1_Buffer[COM1.RX_Cnt++] = SBUF;
*/void USART0_IRQHandler() {if (SET == usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE)) { // 清除标志位usart_data_receive(USART0); // 读取一次,不是为了读数据,为了清除空闲的标志位// 禁用DMAdma_channel_disable(USART0_RX_DMA_PERIPH_CH);// 获取剩余没有搬运的个数uint32_t left_num = dma_transfer_number_get(USART0_RX_DMA_PERIPH_CH);// 搬运的个数 = 总个数 - 剩余没有搬运的个数rx0_cnt = RX0_LENTH - left_num;// %s 打印只针对字符串,如果内容为16进制,%s打印会乱码RX0_Buffer[rx0_cnt] = '\0'; // 字符串结束符, 如果内容为16进制,这个处理没有意义#if USART0_RECV_CALLBACK// 收到串口0数据,回调函数USART0_on_recv(RX0_Buffer, rx0_cnt); // 函数调用#endifrx0_cnt = 0; // 元素个数置零// 清除标志位, 放在启动前面dma_flag_clear(USART0_RX_DMA_PERIPH_CH, DMA_FLAG_FTF);// 启动DMAdma_channel_enable(USART0_RX_DMA_PERIPH_CH);	}
}

6. 完整封装示例:DMA版USART0

USART0.h文件

#ifndef __USART0_H__
#define __USART0_H__#include "gd32f4xx.h"
#include "systick.h"
#include "gpio_cfg.h"
#include <stdio.h>
// ============================================== DMA
// DMA发送功能开关 
#define USART0_TX_DMA_ENABLE      1
// DMA接收功能开关 
#define USART0_RX_DMA_ENABLE      1
#define USART0_DATA_ADDR          (uint32_t)&USART_DATA(USART0) // 外设地址#define USART0_TX_DMA_RCU         RCU_DMA1
#define USART0_TX_DMA_PERIPH_CH   DMA1, DMA_CH7
#define USART0_TX_DMA_PERIPH_SUB  DMA_SUBPERI4#define USART0_RX_DMA_RCU         RCU_DMA1
#define USART0_RX_DMA_PERIPH_CH   DMA1, DMA_CH5
#define USART0_RX_DMA_PERIPH_SUB  DMA_SUBPERI4// ============================================== 非DMA
// 功能开关, printf配置开关
#define USART0_PRINTF			1
// 开关打开为1,同时,在合适位置定义函数void USART0_on_recv(uint8_t* data, uint32_t len)
#define USART0_RECV_CALLBACK	1	
#if USART0_RECV_CALLBACK
// 收到串口0数据,回调函数
void USART0_on_recv(uint8_t* data, uint32_t len);
#endif
// PA9 	USART0_TX	AF7
#define USART0_TX_RCU 	RCU_GPIOA
#define USART0_TX_PORT 	GPIOA
#define USART0_TX_PIN 	GPIO_PIN_9
#define USART0_TX_AF 	GPIO_AF_7
// PA10 	USART0_RX	AF7
#define USART0_RX_RCU 	RCU_GPIOA
#define USART0_RX_PORT 	GPIOA
#define USART0_RX_PIN 	GPIO_PIN_10
#define USART0_RX_AF 	GPIO_AF_7
// 波特率
#define USART0_BAUDRATE 115200UL
// 优先级
#define USART0_PRIORITY	0,0
// 初始化
void USART0_init(); 
// 发送1个byte数据
void USART0_send_byte(uint8_t byte);
// 发送多个byte数据
void USART0_send_data(uint8_t* data, uint32_t len);
// 发送字符串 (结尾标记\0)
void USART0_send_string(char *data);
#endif

USART0.c文件

#include "USART0.h"
#include <string.h>#define RX0_LENTH	128		// 最大大小,数组长度
static uint8_t RX0_Buffer[RX0_LENTH + 1]; // +1,多一个位置给字符串结束符
static uint32_t  rx0_cnt = 0; // 元素个数//  DMA  内存到外设  函数定义
static void DMA_tx_config() {// 时钟使能rcu_periph_clock_enable(USART0_TX_DMA_RCU);// 重置dma_deinit(USART0_TX_DMA_PERIPH_CH);dma_single_data_parameter_struct init_struct;// 结构体参数初始化dma_single_data_para_struct_init(&init_struct);// ============ 内存到外设拷贝// 方向: 内存到外设   init_struct.direction = DMA_MEMORY_TO_PERIPH;// 源头:内存
//	init_struct.memory0_addr = (uint32_t)?;  // 内存内容动态变化,不固定init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 增长// 目的:外设init_struct.periph_addr = USART0_DATA_ADDR;init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 不增长// 搬运一个数据的大小init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT; // 8位// 搬运数据个数
//	init_struct.number = ?; // 动态变化// 优先级init_struct.priority = DMA_PRIORITY_HIGH;// DMA初始化, 设置搬运规则dma_single_data_mode_init(USART0_TX_DMA_PERIPH_CH, &init_struct);// 设置子外设dma_channel_subperipheral_select(USART0_TX_DMA_PERIPH_CH, USART0_TX_DMA_PERIPH_SUB);
}//  DMA  外设到内存  函数定义
static void DMA_rx_config() {// 时钟使能rcu_periph_clock_enable(USART0_RX_DMA_RCU);// 重置dma_deinit(USART0_RX_DMA_PERIPH_CH);dma_single_data_parameter_struct init_struct;// 结构体参数初始化dma_single_data_para_struct_init(&init_struct);// ============ 外设到内存拷贝// 方向: 外设到内存  init_struct.direction = DMA_PERIPH_TO_MEMORY;// 源头:外设init_struct.periph_addr = USART0_DATA_ADDR;init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 不增长// 目的:内存init_struct.memory0_addr = (uint32_t)RX0_Buffer;  // 指定的数组init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 增长// 搬运一个数据的大小init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT; // 8位// 搬运数据个数init_struct.number = RX0_LENTH; // 最大的搬运个数,实际有可能少于它// 优先级init_struct.priority = DMA_PRIORITY_HIGH;// DMA初始化, 设置搬运规则dma_single_data_mode_init(USART0_RX_DMA_PERIPH_CH, &init_struct);// 设置子外设dma_channel_subperipheral_select(USART0_RX_DMA_PERIPH_CH, USART0_RX_DMA_PERIPH_SUB);// 通知DMA干活,它开始搬运,可以用DMA接收数据// 这里不要等待搬运完成,因为不知道啥时候有数据来dma_channel_enable(USART0_RX_DMA_PERIPH_CH);
}void USART0_init() {#if USART0_TX_DMA_ENABLEDMA_tx_config(); //  DMA  内存到外设  函数调用#endif#if USART0_RX_DMA_ENABLEDMA_rx_config(); //  DMA  外设到内存  函数定义#endif// 串口0 TX 复用功能   GPIO_output_af(USART0_TX_RCU, USART0_TX_PORT, USART0_TX_PIN, GPIO_OTYPE_PP, USART0_TX_AF);// 串口0 RX 复用功能  GPIO_output_af(USART0_RX_RCU, USART0_RX_PORT, USART0_RX_PIN, GPIO_OTYPE_PP, USART0_RX_AF);// =========== 串口配置// 时钟使能rcu_periph_clock_enable(RCU_USART0);// 重置串口(可选)usart_deinit(USART0);// 设置波特率(必须要配置)usart_baudrate_set(USART0, USART0_BAUDRATE);// 允许串口发送usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);// ==============++ 串口允许DMA发送#if USART0_TX_DMA_ENABLEusart_dma_transmit_config(USART0, USART_TRANSMIT_DMA_ENABLE);#endif// 允许串口接收usart_receive_config(USART0, USART_RECEIVE_ENABLE);// ==============++ 串口允许DMA接收#if USART0_RX_DMA_ENABLEusart_dma_receive_config(USART0, USART_RECEIVE_DMA_ENABLE);#endif// ================中断配置// 中断优先级,使能中断请求  nvic_irq_enable在misc.h中// 参数1: 中断类型枚举常量, 在 gd32f4xx.h第159行// 参数2: 抢占优先级// 参数3: 响应优先级nvic_irq_enable(USART0_IRQn, USART0_PRIORITY);#if !USART0_RX_DMA_ENABLE  // 非DMA才用// (使能串口中断)RBNE, 有数据可读,自动触发中断,配置中断触发条件usart_interrupt_enable(USART0, USART_INT_RBNE);#endif// IDLE, 空闲触发中断,数据读取完毕(或发送完毕),即可触发中断usart_interrupt_enable(USART0, USART_INT_IDLE); // 如果用DMA,空闲了,说明数据全部搬运完成,再处理数据// 使能串口usart_enable(USART0);
}// 发送1个byte数据
void USART0_send_byte(uint8_t byte){
#if !USART0_TX_DMA_ENABLE // 上面为非DMAusart_data_transmit(USART0, byte);// 等待发送完成, 没有完成,说明返回值为RESET, 一直循环,直到变为SET退出循环// USART_FLAG_TBE: transmit data buffer empty  发送缓冲区为空// 发送缓冲区为空, 硬件置1,说明发送完成   SET就是1while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
#else // 下面为DMAUSART0_send_data(&byte, 1);
#endif
}// 发送多个byte数据
void USART0_send_data(uint8_t* data, uint32_t len){if (data == NULL) return;
#if !USART0_TX_DMA_ENABLEfor(uint32_t i = 0; i < len; i++) {USART0_send_byte(data[i]); // 发送单个字符}
#else// 指定DMA内存地址dma_memory_address_config(USART0_TX_DMA_PERIPH_CH, DMA_MEMORY_0, (uint32_t)data);// 指定DMA发送个数dma_transfer_number_config(USART0_TX_DMA_PERIPH_CH, len);// 通知DMA干活,它开开始搬运dma_channel_enable(USART0_TX_DMA_PERIPH_CH);// 等待搬运完成   只有没有搬完RESET,进入循环while(RESET == dma_flag_get(USART0_TX_DMA_PERIPH_CH, DMA_FLAG_FTF));// 循环的外面,清除标志位, 不清除不能重复搬运dma_flag_clear(USART0_TX_DMA_PERIPH_CH, DMA_FLAG_FTF);
#endif
}// 发送字符串 (结尾标记\0)
void USART0_send_string(char *data){if (data == NULL) return;
#if !USART0_TX_DMA_ENABLEfor(uint32_t i = 0; data[i] != '\0'; i++) {USART0_send_byte(data[i]); // 发送单个字符}
#elseUSART0_send_data((uint8_t*)data, strlen(data)); // 需要#include <string.h>
#endif
}#if USART0_PRINTF
#include <stdio.h>
// 重写fputc, printf即可使用
int fputc(int ch, FILE *f) {USART0_send_byte(ch); // 串口发送1个字节return ch;
}
#endif/*
1. 串口中断处理函数,函数名,不能乱写,因为汇编启动文件已经规定好
USART0_IRQHandler  在 startup_gd32f407_427.s 的124行 
2. 触发中断的条件有很多,一定要通过标志位区分,处理完也要清零
STC8 接收逻辑
if(COM1.RX_Cnt >= COM_RX1_Lenth)	COM1.RX_Cnt = 0;
RX1_Buffer[COM1.RX_Cnt++] = SBUF;
*/void USART0_IRQHandler() {
#if !USART0_RX_DMA_ENABLE // 非DMA// RBNE(有数据可读) 触发的中断,硬件置1(SET)if (SET == usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)) {// 软件清零, 清零后,才能反复读取usart_interrupt_flag_clear(USART0, USART_INT_FLAG_RBNE);// 读取1个字节uint8_t data = usart_data_receive(USART0);if(rx0_cnt >= RX0_LENTH)	rx0_cnt = 0; // 越界处理// 来一个数据,保存到一个数组中RX0_Buffer[rx0_cnt++] = data;}// IDLE, 空闲触发中断,数据读取完毕(或发送完毕)if (SET == usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE)) { // 清除标志位usart_data_receive(USART0); // 读取一次,不是为了读数据,为了清除空闲的标志位// %s 打印只针对字符串,如果内容为16进制,%s打印会乱码RX0_Buffer[rx0_cnt] = '\0'; // 字符串结束符, 如果内容为16进制,这个处理没有意义#if USART0_RECV_CALLBACK// 收到串口0数据,回调函数USART0_on_recv(RX0_Buffer, rx0_cnt); // 函数调用#endifrx0_cnt = 0; // 元素个数置零}
#else // DMAif (SET == usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE)) { // 清除标志位usart_data_receive(USART0); // 读取一次,不是为了读数据,为了清除空闲的标志位// 禁用DMAdma_channel_disable(USART0_RX_DMA_PERIPH_CH);// 获取剩余没有搬运的个数uint32_t left_num = dma_transfer_number_get(USART0_RX_DMA_PERIPH_CH);// 搬运的个数 = 总个数 - 剩余没有搬运的个数rx0_cnt = RX0_LENTH - left_num;// %s 打印只针对字符串,如果内容为16进制,%s打印会乱码RX0_Buffer[rx0_cnt] = '\0'; // 字符串结束符, 如果内容为16进制,这个处理没有意义#if USART0_RECV_CALLBACK// 收到串口0数据,回调函数USART0_on_recv(RX0_Buffer, rx0_cnt); // 函数调用#endifrx0_cnt = 0; // 元素个数置零// 清除标志位, 放在启动前面dma_flag_clear(USART0_RX_DMA_PERIPH_CH, DMA_FLAG_FTF);// 启动DMAdma_channel_enable(USART0_RX_DMA_PERIPH_CH);	}
#endif
}

7. 总结

DMA技术通过将数据搬运任务从CPU中解放出来,显著提高了系统的整体效率。关键优势包括:

  1. 提高CPU利用率:CPU可以专注于计算任务,不被数据传输打断
  2. 提高数据传输效率:专门的DMA控制器优化了数据传输流程
  3. 降低系统功耗:减少CPU参与数据搬运的时间

在实际应用中,合理使用DMA可以大幅提升嵌入式系统的性能,特别是在需要大量数据传输的场景中,如音频处理、图像传输、网络通信等。

http://www.dtcms.com/a/464806.html

相关文章:

  • 基于腾讯云的物联网导盲助手设计与实现(论文+源码)
  • Vue3打造高效前端埋点系统
  • 框架--Maven
  • 【Java集合】
  • 停止Conda开机自动运行方法
  • 湘潭市高新建设局施工报建网站wordpress 宕机
  • 复杂结构数据挖掘(二)关联规则挖掘 Association rule mining
  • Windows 上安装 PostgreSQL
  • 基于JETSON/x86+FPGA+AI的5G远程驾驶座舱时延验证方案
  • 支持向量机(SVM)完全解读
  • 单片机学习日记
  • 重庆网站制作多少钱app设计开发哪家好
  • AI大模型学习(17)python-flask AI大模型和图片处理工具的从一张图到多平台适配的简单方法
  • 如何通过 7 种解决方案将文件从PC无线传输到Android
  • Word 为每一页设置不同页边距(VBA 宏)
  • wordpiece、unigram、sentencepiece基本原理
  • css word-spacing属性
  • 使用 python-docx 库操作 word 文档(2):在word文档中插入各种内容
  • 中企动力销售工作内容白城网站seo
  • 从0死磕全栈之Next.js 企业级 `next.config.js` 配置详解:打造高性能、安全、可维护的中大型项目
  • 在JavaScript中,const和var的区别
  • 【SDR课堂第36讲】RFSOC PS软件开发入门指南(一)
  • 学做网站中国设计网站导航
  • [嵌入式系统-84]:NPU/TPU/LPU有指令集吗?
  • 光伏安全协议-安全责任协议书8篇
  • Java 单元测试全攻略:JUnit 生命周期、覆盖率提升、自动化框架与 Mock 技术
  • SaaS多租户数据隔离实战:MyBatis拦截器实现行级安全方案
  • 【深入理解计算机网络08】网络层之IPv4
  • 网站的标签wordpress 导航栏居中
  • 解决电脑提示“0xc000007b错误”的简单指南