学习目标
- 理解DMA数据传输过程
- 掌握DMA的初始化流程
- 理解源和目标的配置
- 理解数据传输特点
- 能够动态配置源数据
-
学习内容
需求
#define ARR_LEN 1024
char 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传输请求
```c
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);
- 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);
// 重置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);
// // 中断配置
// 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>
### 关心的内容
```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);
- 哪个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数据传输请求
```c
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);
请求数据传输前,进行动态配置:
- 在此需要注意的是,要分清楚当前是否是调用源的地址配置
完整代码
```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);
// 重置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);
}
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 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);
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);
// 重置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);
// 配置中断
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) {
}
}
练习
- 实现内存到内存数据传递。