学习目标
- 加强理解DMA数据传输过程
- 加强掌握DMA的初始化流程
- 掌握DMA数据表查询
- 理解源和目标的配置
- 理解数据传输特点
-
学习内容
需求
uint8_t data = 0x01;串口发送(data);
数据交互流程

CPU配置好DMA
- CPU通知DMA干活
- DMA请求源数据
- DMA获取源数据
- DMA将获取的源数据交给目标
开发流程
依赖引入
添加标准库中的gd32f4xx_dma.c文件DMA初始化
```c /* DMA m2p */ // 时钟 rcu_periph_clock_enable(RCU_DMA1); // 重置dma dma_deinit(DMA1, DMA_CH7);
//////// dma 配置 dma_single_data_parameter_struct dsdps; dma_single_data_para_struct_init(&dsdps); // 方向 dsdps.direction = DMA_MEMORY_TO_PERIPH; // 内存: src // dsdps.memory0_addr = (uint32_t)src; dsdps.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 外设: dst dsdps.periph_addr = (uint32_t)(&USART_DATA(USART0)); dsdps.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 数据长度 // dsdps.number = ARR_LEN; // 数据宽度 dsdps.periph_memory_width = DMA_MEMORY_WIDTH_8BIT; // 传输优先级 dsdps.priority = DMA_PRIORITY_ULTRA_HIGH; dma_single_data_mode_init(DMA1, DMA_CH7, &dsdps);
//////// 配置 dma 子连接 dma_channel_subperipheral_select(DMA1, DMA_CH7, DMA_SUBPERI4);
1. 配置时钟2. 初始化dma通道3. 配置dma与外设间的子链接<a name="ZK7mP"></a>#### DMA传输请求```c// 数据来源 和 长度dma_memory_address_config(DMA1, DMA_CH7, DMA_MEMORY_0, (uint32_t)(&data));dma_transfer_number_config(DMA1, DMA_CH7, len);// 触发传输dma_channel_enable(DMA1, DMA_CH7);// 等待DMA传输完成while(RESET == dma_flag_get(DMA1, DMA_CH7, DMA_FLAG_FTF));// 清理标记dma_flag_clear(DMA1, DMA_CH7, DMA_FLAG_FTF);
- dma_channel_enable: 请求dma数据传输
-
串口外设DMA开启
// DMA发送功能配置usart_dma_transmit_config(usartx, USART_TRANSMIT_DMA_ENABLE);
-
发送功能
static void dma_send_byte(uint8_t data) {// 数据来源 和 长度dma_memory_address_config(DMA1, DMA_CH7, DMA_MEMORY_0, (uint32_t)(&data));dma_transfer_number_config(DMA1, DMA_CH7, 1);// 触发传输dma_channel_enable(DMA1, DMA_CH7);// 等待DMA传输完成while(RESET == dma_flag_get(DMA1, DMA_CH7, DMA_FLAG_FTF));// 清理标记dma_flag_clear(DMA1, DMA_CH7, DMA_FLAG_FTF);}
static void dma_send(uint8_t* data, uint32_t len) {// 数据来源 和 长度dma_memory_address_config(DMA1, DMA_CH7, DMA_MEMORY_0, (uint32_t)(&data));dma_transfer_number_config(DMA1, DMA_CH7, len);// 触发传输dma_channel_enable(DMA1, DMA_CH7);// 等待DMA传输完成while(RESET == dma_flag_get(DMA1, DMA_CH7, DMA_FLAG_FTF));// 清理标记dma_flag_clear(DMA1, DMA_CH7, DMA_FLAG_FTF);}
static void dma_send_string(const char* str) {dma_send((uint8_t*)str, strlen(str));}
完整代码
```c
include “gd32f4xx.h”
include “systick.h”
include
include
include “main.h”
static void DMA_config() { /* DMA m2p */ // 时钟 rcu_periph_clock_enable(RCU_DMA1); // 重置dma dma_deinit(DMA1, DMA_CH7);
//////// dma 配置dma_single_data_parameter_struct dsdps;dma_single_data_para_struct_init(&dsdps);// 方向dsdps.direction = DMA_MEMORY_TO_PERIPH;// 内存: src// dsdps.memory0_addr = (uint32_t)src;dsdps.memory_inc = DMA_MEMORY_INCREASE_ENABLE;// 外设: dstdsdps.periph_addr = (uint32_t)(&USART_DATA(USART0));dsdps.periph_inc = DMA_PERIPH_INCREASE_DISABLE;// // 数据长度// dsdps.number = ARR_LEN;// dst数据宽度dsdps.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;// 传输优先级dsdps.priority = DMA_PRIORITY_ULTRA_HIGH;dma_single_data_mode_init(DMA1, DMA_CH7, &dsdps);//////// 配置 dma 子连接dma_channel_subperipheral_select(DMA1, DMA_CH7, DMA_SUBPERI4);
}
static void dma_send_byte(uint8_t data) { // 数据来源 和 长度 dma_memory_address_config(DMA1, DMA_CH7, DMA_MEMORY_0, (uint32_t)(&data)); dma_transfer_number_config(DMA1, DMA_CH7, 1);
// 触发传输dma_channel_enable(DMA1, DMA_CH7);// 等待DMA传输完成while(RESET == dma_flag_get(DMA1, DMA_CH7, DMA_FLAG_FTF));// 清理标记dma_flag_clear(DMA1, DMA_CH7, DMA_FLAG_FTF);
}
static void dma_send(uint8_t* data, uint32_t len) { // 数据来源 和 长度 dma_memory_address_config(DMA1, DMA_CH7, DMA_MEMORY_0, (uint32_t)(&data)); dma_transfer_number_config(DMA1, DMA_CH7, len);
// 触发传输dma_channel_enable(DMA1, DMA_CH7);// 等待DMA传输完成while(RESET == dma_flag_get(DMA1, DMA_CH7, DMA_FLAG_FTF));// 清理标记dma_flag_clear(DMA1, DMA_CH7, DMA_FLAG_FTF);
}
static void dma_send_string(const char str) { dma_send((uint8_t)str, strlen(str)); }
static void Usart_config() { // 哪个串口 uint32_t usartx = USART0; uint32_t usartx_rcu = RCU_USART0; uint32_t usartx_irq = USART0_IRQn;
// 波特率uint32_t usartx_p_baudrate = 115200;// tx和rx,用了哪个引脚uint32_t usartx_tx_port_rcu = RCU_GPIOA;uint32_t usartx_tx_port = GPIOA;uint32_t usartx_tx_pin = GPIO_PIN_9;// 复用功能编号uint32_t usartx_tx_af = GPIO_AF_7;// tx和rx,用了哪个引脚uint32_t usartx_rx_port_rcu = RCU_GPIOA;uint32_t usartx_rx_port = GPIOA;uint32_t usartx_rx_pin = GPIO_PIN_10;// 复用功能编号uint32_t usartx_rx_af = GPIO_AF_7;/*************** gpio *****************/// TX// 配置时钟rcu_periph_clock_enable(usartx_tx_port_rcu);// 配置模式: 复用功能gpio_mode_set(usartx_tx_port, GPIO_MODE_AF, GPIO_PUPD_NONE, usartx_tx_pin);// 配置复用功能gpio_af_set(usartx_tx_port, usartx_tx_af, usartx_tx_pin);gpio_output_options_set(usartx_tx_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, usartx_tx_pin);// RX// 配置时钟rcu_periph_clock_enable(usartx_rx_port_rcu);gpio_mode_set(usartx_rx_port, GPIO_MODE_AF, GPIO_PUPD_NONE, usartx_rx_pin);gpio_af_set(usartx_rx_port, usartx_rx_af, usartx_rx_pin);// 配置输出参数gpio_output_options_set(usartx_rx_port, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, usartx_rx_pin);/*************** usart ****************/// 串口时钟rcu_periph_clock_enable(usartx_rcu);// USART复位usart_deinit(usartx);// 波特率usart_baudrate_set(usartx, usartx_p_baudrate);// 校验位usart_parity_config(usartx, USART_PM_NONE);// 数据位数usart_word_length_set(usartx, USART_WL_8BIT);// 停止位usart_stop_bit_set(usartx, USART_STB_1BIT);// 先发送高位还是低位usart_data_first_config(usartx, USART_MSBF_LSB);// 发送功能配置usart_transmit_config(usartx, USART_TRANSMIT_ENABLE);// 接收功能配置usart_receive_config(usartx, USART_RECEIVE_ENABLE);// 接收中断配置nvic_irq_enable(usartx_irq, 2, 2);usart_interrupt_enable(usartx, USART_INT_RBNE);usart_interrupt_enable(usartx, USART_INT_IDLE);// DMA发送功能配置usart_dma_transmit_config(usartx, USART_TRANSMIT_DMA_ENABLE);// 使能串口usart_enable(usartx);
}
static void send_byte(uint8_t data) { //通过USART发送 usart_data_transmit(USART0, data);
//判断缓冲区是否已经空了//FlagStatus state = usart_flag_get(USART_NUM,USART_FLAG_TBE);while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
}
static void send_string(char data) { while(data && data){ send_byte((uint8_t)(*data)); data++; } }
int fputc(int ch, FILE *f){ send_byte((uint8_t)ch); return ch; }
int main(void) { nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); systick_config(); Usart_config();
DMA_config();uint8_t cnt = 0;while(1) {// dma_send_byte(cnt++);printf("hello %d\r\n", cnt++);delay_1ms(1000);}
}
<a name="Wqo9o"></a>### 关心的内容```cuint32_t dmax = DMA1;uint32_t dmax_rcu = RCU_DMA1;uint32_t dmax_ch = DMA_CH7;uint32_t damx_sub = DMA_SUBPERI4;uint32_t dmax_dirction = DMA_MEMORY_TO_PERIPH;// uint32_t dmax_src = (uint32_t)src;uint32_t dmax_src_inc = DMA_MEMORY_INCREASE_ENABLE;uint32_t dmax_src_width = DMA_MEMORY_WIDTH_8BIT;// uint32_t dmax_src_len = ARR_LEN;uint32_t dmax_dst = (uint32_t)(&USART_DATA(USART0));uint32_t dmax_dst_inc = DMA_PERIPH_INCREASE_DISABLE;/***************** DMA m2p *******************/// 时钟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;// 内存: src// dsdps.memory0_addr = (uint32_t)src;dsdps.memory_inc = dmax_src_inc;// 外设: dstdsdps.periph_addr = dmax_dst;dsdps.periph_inc = dmax_dst_inc;// // 数据长度// dsdps.number = ARR_LEN;// 数据宽度dsdps.periph_memory_width = dmax_src_width;// 传输优先级dsdps.priority = DMA_PRIORITY_ULTRA_HIGH;dma_single_data_mode_init(dmax, dmax_ch, &dsdps);//////// 配置 dma 子连接dma_channel_subperipheral_select(dmax, dmax_ch, damx_sub);
- 哪个dma:DMA0或者DMA1
- dma的哪个通道:ch0到ch7
- dma的哪个子级:0到7
- 传输方向是什么:内存到内存,内存到外设,外设到内存
- 源地址是多少?源的数据长度是多少(几个byte)?源的数据宽度是多少(几个bit)?
- 目标地址是什么?
- 从源地址向目标地址传输数据时,源地址的指针是否需要增长。
- 从源地址向目标地址传输数据时,目标地址的指针是否需要增长。
串口发送理解
源地址和目标地址
源地址和目标地址都是提前配置好的,当传输时,就会从源地址将数据传递给目标地址。自增长
每传输一个字节数据,地址是否需要增长。这里包含了源地址是否需要增长,也包含了目标地址是否需要增长。
串口中,寄存器地址不变,大小不变,我们只是向这个里面放数据,所以不需要增长。数据宽度
数据宽度表示一次传递多上个bit数据长度
传输了多少个数据宽度的数据。练习
- 实现串口数据的发送
