学习目标
- 理解DMA数据传输过程
- 掌握DMA的初始化流程
- 理解源和目标的配置
- 理解数据传输特点
- 能够动态配置源数据
-
学习内容
需求
#define ARR_LEN 1024char src[ARR_LEN] = "hello\0";char dst[ARR_LEN] = {0};
将src这个数组的值,赋值到dst这个数组中,不可以采取直接赋值的方式,需要通过DMA将数据进行传递。
数据交互流程

CPU配置好DMA
- CPU通知DMA干活
- DMA请求源数据
- DMA获取源数据
- DMA将获取的源数据交给目标
开发流程
依赖引入
添加标准库中的gd32f4xx_dma.c文件DMA初始
```c /* DMA m2m */ // 时钟 rcu_periph_clock_enable(RCU_DMA1); // 重置dma dma_deinit(DMA1, DMA_CH0);
//////// dma 配置
dma_single_data_parameter_struct dsdps;
dma_single_data_para_struct_init(&dsdps);
// 方向
dsdps.direction = DMA_MEMORY_TO_MEMORY;
// 内存
dsdps.memory0_addr = (uint32_t)dst;
dsdps.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
// 外设
dsdps.periph_addr = (uint32_t)src;
dsdps.periph_inc = DMA_PERIPH_INCREASE_ENABLE;
// 数据长度
dsdps.number = ARR_LEN;
// 数据宽度
dsdps.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
dma_single_data_mode_init(DMA1, DMA_CH0, &dsdps);
1. 配置时钟2. 初始化dma通道<a name="ZK7mP"></a>#### DMA传输请求```cdma_channel_enable(DMA1, DMA_CH0);while(RESET == dma_flag_get(DMA1, DMA_CH0, DMA_FLAG_FTF));dma_flag_clear(DMA1, DMA_CH0, DMA_FLAG_FTF);
- dma_channel_enable: 请求dma数据传输
- DMA_FLAG_FTF:为传输完成标记
完整代码
```cinclude “gd32f4xx.h”
include “systick.h”
include
include
include “main.h”
include “Usart0.h”
define ARR_LEN 1024
char src[ARR_LEN] = “hello\0”; char dst[ARR_LEN] = {0};
void Usart0_recv(uint8_t* data, uint32_t len) { printf(“recv: %s\r\n”, data);
dma_channel_enable(DMA1, DMA_CH0);while(RESET == dma_flag_get(DMA1, DMA_CH0, DMA_FLAG_FTF));dma_flag_clear(DMA1, DMA_CH0, DMA_FLAG_FTF);printf("dst: %s\n", dst);
}
static void DMA_config() {
/***************** DMA m2m *******************/// 时钟rcu_periph_clock_enable(RCU_DMA1);// 重置dmadma_deinit(DMA1, DMA_CH0);//////// dma 配置dma_single_data_parameter_struct dsdps;dma_single_data_para_struct_init(&dsdps);// 方向dsdps.direction = DMA_MEMORY_TO_MEMORY;// 内存dsdps.memory0_addr = (uint32_t)dst;dsdps.memory_inc = DMA_MEMORY_INCREASE_ENABLE;// 外设dsdps.periph_addr = (uint32_t)src;dsdps.periph_inc = DMA_PERIPH_INCREASE_ENABLE;// 数据长度dsdps.number = ARR_LEN;// 数据宽度dsdps.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;dma_single_data_mode_init(DMA1, DMA_CH0, &dsdps);// // 中断配置// nvic_irq_enable(DMA1_Channel0_IRQn, 2, 2);// // 中断使能// dma_interrupt_enable(DMA1, DMA_CH0, DMA_CHXCTL_FTFIE);// // 开启DMA通道// dma_channel_enable(DMA1, DMA_CH0);
}
//void DMA1_Channel0_IRQHandler() { // // 判断中断标记 // if(SET == dma_interrupt_flag_get(DMA1, DMA_CH0, DMA_INT_FLAG_FTF)) { // printf(“dst: %s\n”, dst); // } // // 清理标记位 // dma_interrupt_flag_clear(DMA1, DMA_CH0, DMA_INT_FLAG_FTF); //}
int main(void) { nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); systick_config(); Usart0_init();
DMA_config();while(1) {}
}
<a name="EFWCI"></a>### 关心的内容```cuint32_t dmax = DMA1;uint32_t dmax_rcu = RCU_DMA1;uint32_t dmax_ch = DMA_CH0;uint32_t dmax_dirction = DMA_MEMORY_TO_MEMORY;uint32_t dmax_src = (uint32_t)src;uint32_t dmax_src_inc = DMA_PERIPH_INCREASE_ENABLE;uint32_t dmax_src_width = DMA_PERIPH_WIDTH_8BIT;uint32_t dmax_src_len = ARR_LEN;uint32_t dmax_dst = (uint32_t)dst;uint32_t dmax_dst_inc = DMA_MEMORY_INCREASE_ENABLE;/***************** DMA m2m *******************/// 时钟rcu_periph_clock_enable(dmax_rcu);// 重置dmadma_deinit(dmax, dmax_ch);//////// dma 配置dma_single_data_parameter_struct dsdps;dma_single_data_para_struct_init(&dsdps);// 方向dsdps.direction = dmax_dirction;// 内存dsdps.memory0_addr = dmax_dst;dsdps.memory_inc = dmax_dst_inc;// 外设dsdps.periph_addr = dmax_src;dsdps.periph_inc = dmax_src_inc;// 数据长度dsdps.number = dmax_src_len;// 数据宽度dsdps.periph_memory_width = dmax_src_width;dma_single_data_mode_init(dmax, dmax_ch, &dsdps);
- 哪个dma:DMA0或者DMA1
- dma的哪个通道:ch0到ch7
- 传输方向是什么:内存到内存,内存到外设,外设到内存
- 源地址是多少?源的数据长度是多少(几个byte)?源的数据宽度是多少(几个bit)?
- 目标地址是什么?
- 从源地址向目标地址传输数据时,源地址的指针是否需要增长。
- 从源地址向目标地址传输数据时,目标地址的指针是否需要增长。
数据传输理解
源地址和目标地址
源地址和目标地址都是提前配置好的,当传输时,就会从源地址将数据传递给目标地址。自增长
每传输一个字节数据,地址是否需要增长。这里包含了源地址是否需要增长,也包含了目标地址是否需要增长。数据宽度
数据宽度表示一次传递多上个bit数据长度
传输了多少个数据宽度的数据。源的数据未定
这里主要指的是在配置DMA的源和目标的时候,我们无法给出一个具体的源地址和源的数据长度。
通常我们要解决的也是初始化和数据传输请求。DMA初始化
```c uint32_t dmax = DMA1; uint32_t dmax_rcu = RCU_DMA1; uint32_t dmax_ch = DMA_CH0;
uint32_t dmax_dirction = DMA_MEMORY_TO_MEMORY;
//uint32_t dmax_src = (uint32_t)src; uint32_t dmax_src_inc = DMA_PERIPH_INCREASE_ENABLE; uint32_t dmax_src_width = DMA_PERIPH_WIDTH_8BIT; //uint32_t dmax_src_len = ARR_LEN;
uint32_t dmax_dst = (uint32_t)dst; uint32_t dmax_dst_inc = DMA_MEMORY_INCREASE_ENABLE; /* DMA m2m */ // 时钟 rcu_periph_clock_enable(dmax_rcu); // 重置dma dma_deinit(dmax, dmax_ch);
//////// dma 配置
dma_single_data_parameter_struct dsdps;
dma_single_data_para_struct_init(&dsdps);
// 方向
dsdps.direction = dmax_dirction;
// 内存
dsdps.memory0_addr = dmax_dst;
dsdps.memory_inc = dmax_dst_inc;
// 外设
//dsdps.periph_addr = dmax_src;
dsdps.periph_inc = dmax_src_inc;
// 数据长度
//dsdps.number = dmax_src_len;
// 数据宽度
dsdps.periph_memory_width = dmax_src_width;
dma_single_data_mode_init(dmax, dmax_ch, &dsdps);
初始化时,不再初始化源地址和要传输的长度。<a name="A9RDs"></a>#### DMA数据传输请求```cdma_periph_address_config(DMA1, DMA_CH0,(uint32_t)data);dma_transfer_number_config(DMA1, DMA_CH0, len);dma_channel_enable(DMA1, DMA_CH0);while(RESET == dma_flag_get(DMA1, DMA_CH0, DMA_FLAG_FTF));dma_flag_clear(DMA1, DMA_CH0, DMA_FLAG_FTF);
请求数据传输前,进行动态配置:
- 在此需要注意的是,要分清楚当前是否是调用源的地址配置
完整代码
```cinclude “gd32f4xx.h”
include “systick.h”
include
include
include “main.h”
include “Usart0.h”
define ARR_LEN 1024
char dst[ARR_LEN] = {0};
void Usart0_recv(uint8_t* data, uint32_t len) { printf(“recv: %s\r\n”, data);
dma_periph_address_config(DMA1, DMA_CH0,(uint32_t)data);dma_transfer_number_config(DMA1, DMA_CH0, len);dma_channel_enable(DMA1, DMA_CH0);while(RESET == dma_flag_get(DMA1, DMA_CH0, DMA_FLAG_FTF));dma_flag_clear(DMA1, DMA_CH0, DMA_FLAG_FTF);dst[len] = '\0';printf("dst: %s\n", dst);
}
static void DMA_config() {
uint32_t dmax = DMA1;uint32_t dmax_rcu = RCU_DMA1;uint32_t dmax_ch = DMA_CH0;uint32_t dmax_dirction = DMA_MEMORY_TO_MEMORY;//uint32_t dmax_src = (uint32_t)src;uint32_t dmax_src_inc = DMA_PERIPH_INCREASE_ENABLE;uint32_t dmax_src_width = DMA_PERIPH_WIDTH_8BIT;//uint32_t dmax_src_len = ARR_LEN;uint32_t dmax_dst = (uint32_t)dst;uint32_t dmax_dst_inc = DMA_MEMORY_INCREASE_ENABLE;/***************** DMA m2m *******************/// 时钟rcu_periph_clock_enable(dmax_rcu);// 重置dmadma_deinit(dmax, dmax_ch);//////// dma 配置dma_single_data_parameter_struct dsdps;dma_single_data_para_struct_init(&dsdps);// 方向dsdps.direction = dmax_dirction;// 内存dsdps.memory0_addr = dmax_dst;dsdps.memory_inc = dmax_dst_inc;// 外设//dsdps.periph_addr = dmax_src;dsdps.periph_inc = dmax_src_inc;// 数据长度//dsdps.number = dmax_src_len;// 数据宽度dsdps.periph_memory_width = dmax_src_width;dma_single_data_mode_init(dmax, dmax_ch, &dsdps);
}
int main(void) { nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); systick_config(); Usart0_init();
DMA_config();while(1) {}
}
<a name="ZBJS5"></a>### DMA中断通常,dma数据传输完成,我们也是可以知道的。可以通过中断的方式获取。<a name="KFYdv"></a>#### 开启中断```c// 中断配置nvic_irq_enable(DMA1_Channel0_IRQn, 2, 2);// 中断使能dma_interrupt_enable(DMA1, DMA_CH0, DMA_CHXCTL_FTFIE);// 开启DMA通道dma_channel_enable(DMA1, DMA_CH0);
DMA_CHXCTL_FTFIE: 表示中断数据传输完成标记
中断函数实现
```c void DMA1_Channel0_IRQHandler() { // 判断中断标记 if(SET == dma_interrupt_flag_get(DMA1, DMA_CH0, DMA_INT_FLAG_FTF)) {
printf("INT dst: %s\n", dst);
} // 清理标记位 dma_interrupt_flag_clear(DMA1, DMA_CH0, DMA_INT_FLAG_FTF); }
- DMA_INT_FLAG_FTF: 表示中断传输完成标记,需要在标记触发时做业务逻辑- 同时,需要配合寄存器,清除标记<a name="bo4Fk"></a>#### 完整代码```c#include "gd32f4xx.h"#include "systick.h"#include <stdio.h>#include <string.h>#include "main.h"#include "Usart0.h"#define ARR_LEN 1024char src[ARR_LEN] = "hello\0";char dst[ARR_LEN] = {0};void Usart0_recv(uint8_t* data, uint32_t len) {printf("recv: %s\r\n", data);memcpy(src, data, len);src[len] = '\0';dma_channel_enable(DMA1, DMA_CH0);printf("dst: %s\n", dst);}static void DMA_config() {uint32_t dmax = DMA1;uint32_t dmax_rcu = RCU_DMA1;uint32_t dmax_ch = DMA_CH0;uint32_t dmax_dirction = DMA_MEMORY_TO_MEMORY;uint32_t dmax_src = (uint32_t)src;uint32_t dmax_src_inc = DMA_PERIPH_INCREASE_ENABLE;uint32_t dmax_src_width = DMA_PERIPH_WIDTH_8BIT;uint32_t dmax_src_len = ARR_LEN;uint32_t dmax_dst = (uint32_t)dst;uint32_t dmax_dst_inc = DMA_MEMORY_INCREASE_ENABLE;/***************** DMA m2m *******************/// 时钟rcu_periph_clock_enable(dmax_rcu);// 重置dmadma_deinit(dmax, dmax_ch);//////// dma 配置dma_single_data_parameter_struct dsdps;dma_single_data_para_struct_init(&dsdps);// 方向dsdps.direction = dmax_dirction;// 内存dsdps.memory0_addr = dmax_dst;dsdps.memory_inc = dmax_dst_inc;// 外设dsdps.periph_addr = dmax_src;dsdps.periph_inc = dmax_src_inc;// 数据长度dsdps.number = dmax_src_len;// 数据宽度dsdps.periph_memory_width = dmax_src_width;dma_single_data_mode_init(dmax, dmax_ch, &dsdps);// 配置中断nvic_irq_enable(DMA1_Channel0_IRQn, 2, 2);dma_interrupt_enable(dmax, dmax_ch, DMA_CHXCTL_FTFIE);}void DMA1_Channel0_IRQHandler() {// 判断中断标记if(SET == dma_interrupt_flag_get(DMA1, DMA_CH0, DMA_INT_FLAG_FTF)) {printf("INT dst: %s\n", dst);}// 清理标记位dma_interrupt_flag_clear(DMA1, DMA_CH0, DMA_INT_FLAG_FTF);}int main(void){nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);systick_config();Usart0_init();DMA_config();while(1) {}}
练习
- 实现内存到内存数据传递。
