学习目标
- 加强理解DMA数据传输过程
- 加强掌握DMA的初始化流程
- 掌握DMA数据表查询
- 理解源和目标的配置
- 理解数据传输特点
- 能够动态配置源数据
学习内容
需求
```c uint8_t data; 串口接收(&data);
data有数据了
实现串口的数据接收,要求采用dma的方式。
<a name="CZdgS"></a>
### 数据交互流程
![image.png](https://cdn.nlark.com/yuque/0/2023/png/27903758/1700722229113-bcfb93d1-e2a9-494a-9b3c-db00ec1be748.png#averageHue=%23fbe2c3&clientId=ucf437be5-16f9-4&from=paste&height=403&id=u33f91ca2&originHeight=504&originWidth=915&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=32837&status=done&style=none&taskId=u8b430d54-0e2f-4961-928f-d7edf0e7094&title=&width=732)
1. CPU配置好DMA
2. 外部数据发送给串口外设
3. 串口外设触发中断
4. CPU处理中断逻辑,通知DMA干活
5. DMA请求源数据
6. DMA获取源数据
7. DMA将获取的源数据交给目标
<a name="ayYkU"></a>
### 开发流程
<a name="Ee1EY"></a>
#### 依赖引入
添加标准库中的`gd32f4xx_dma.c`文件
<a name="KGhto"></a>
#### DMA初始化
```c
/**************** DMA p2m *******************/
// 时钟
rcu_periph_clock_enable(RCU_DMA1);
// 重置dma
dma_deinit(DMA1, DMA_CH5);
//////// dma 配置
dma_single_data_parameter_struct dsdps;
dma_single_data_para_struct_init(&dsdps);
// 方向
dsdps.direction = DMA_PERIPH_TO_MEMORY;
// 内存: dst
dsdps.memory0_addr = (uint32_t)(g_recv_buff);
dsdps.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
// 外设: src
dsdps.periph_addr = (uint32_t)(&USART_DATA(USART0));
dsdps.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
// 数据长度
dsdps.number = USART_RECEIVE_LENGTH;
// dst数据宽度
dsdps.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
// 传输优先级
dsdps.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_single_data_mode_init(DMA1, DMA_CH5, &dsdps);
//////// 配置 dma 子连接
dma_channel_subperipheral_select(DMA1, DMA_CH5, DMA_SUBPERI4);
// 默认开启接收
dma_flag_clear(DMA1, DMA_CH5, DMA_FLAG_FTF);
dma_channel_enable(DMA1, DMA_CH5);
- 配置时钟
- 初始化dma通道
- 配置dma与外设间的子链接
-
接收中断
if (usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE) == SET) {
//读取缓冲区,清空缓冲区
usart_data_receive(USART0);
// 停止DMA传输,防止数据污染
dma_channel_disable(DMA1, DMA_CH5);
// 计算接收的数据长度
g_recv_length = USART_RECEIVE_LENGTH - dma_transfer_number_get(DMA1, DMA_CH5);
if(g_recv_length) {
g_recv_buff[g_recv_length] = '\0';
// TODO: g_recv_buff为接收的数据,g_recv_length为接收的长度
printf("rcv: %s\r\n", g_recv_buff);
}
g_recv_length = 0;
dma_flag_clear(DMA1, DMA_CH5, DMA_FLAG_FTF);
dma_channel_enable(DMA1, DMA_CH5);
}
- dma_channel_enable: 请求dma数据传输
-
串口外设DMA开启
// DMA接收功能配置
usart_dma_receive_config(usartx, USART_RECEIVE_DMA_ENABLE);
-
完整代码
```c
include “gd32f4xx.h”
include “systick.h”
include
include
include “main.h”
void Usart0_recv(uint8_t* data, uint32_t len) { printf(“recv: %s\r\n”, data); }
static void DMA_tx_config() { uint32_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);
// 重置dma
dma_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;
// 外设: dst
dsdps.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);
}
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);
// DMA接收功能配置
usart_dma_receive_config(usartx, USART_RECEIVE_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; }
define USART_RECEIVE_LENGTH 1024
//串口接收缓冲区大小 uint8_t g_recv_buff[USART_RECEIVE_LENGTH]; // 接收缓冲区 //接收到字符存放的位置 uint32_t g_recv_length = 0;
void USART0_IRQHandler(void) { if (usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE) == SET) { //读取缓冲区,清空缓冲区 usart_data_receive(USART0);
// 停止DMA传输,防止数据污染
dma_channel_disable(DMA1, DMA_CH5);
// 计算接收的数据长度
g_recv_length = USART_RECEIVE_LENGTH - dma_transfer_number_get(DMA1, DMA_CH5);
if(g_recv_length) {
g_recv_buff[g_recv_length] = '\0';
// TODO: g_recv_buff为接收的数据,g_recv_length为接收的长度
printf("rcv: %s\r\n", g_recv_buff);
}
g_recv_length = 0;
dma_flag_clear(DMA1, DMA_CH5, DMA_FLAG_FTF);
dma_channel_enable(DMA1, DMA_CH5);
}
}
static void DMA_rx_config() { /** DMA p2m */ // 时钟 rcu_periph_clock_enable(RCU_DMA1); // 重置dma dma_deinit(DMA1, DMA_CH5);
//////// dma 配置
dma_single_data_parameter_struct dsdps;
dma_single_data_para_struct_init(&dsdps);
// 方向
dsdps.direction = DMA_PERIPH_TO_MEMORY;
// 内存: dst
dsdps.memory0_addr = (uint32_t)(g_recv_buff);
dsdps.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
// 外设: src
dsdps.periph_addr = (uint32_t)(&USART_DATA(USART0));
dsdps.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
// 数据长度
dsdps.number = USART_RECEIVE_LENGTH;
// dst数据宽度
dsdps.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
// 传输优先级
dsdps.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_single_data_mode_init(DMA1, DMA_CH5, &dsdps);
//////// 配置 dma 子连接
dma_channel_subperipheral_select(DMA1, DMA_CH5, DMA_SUBPERI4);
// 默认开启接收
dma_flag_clear(DMA1, DMA_CH5, DMA_FLAG_FTF);
dma_channel_enable(DMA1, DMA_CH5);
}
int main(void) { nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); systick_config(); Usart_config();
DMA_tx_config();
DMA_rx_config();
uint8_t cnt = 0;
while(1) {
// dma_send_byte(cnt++);
//printf("hello %d\r\n", cnt++);
delay_1ms(1000);
}
}
<a name="BZwJW"></a>
### 关心的内容
```c
uint32_t dmax = DMA1;
uint32_t dmax_rcu = RCU_DMA1;
uint32_t dmax_ch = DMA_CH5;
uint32_t damx_sub = DMA_SUBPERI4;
uint32_t dmax_dirction = DMA_PERIPH_TO_MEMORY;
uint32_t dmax_src = (uint32_t)(&USART_DATA(USART0));
uint32_t dmax_src_inc = DMA_PERIPH_INCREASE_DISABLE;
uint32_t dmax_src_width = DMA_PERIPH_WIDTH_8BIT;
uint32_t dmax_src_len = USART_RECEIVE_LENGTH;
uint32_t dmax_dst = (uint32_t)(g_recv_buff);
uint32_t dmax_dst_inc = DMA_MEMORY_INCREASE_ENABLE;
/**************** DMA p2m *******************/
// 时钟
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 = DMA_PERIPH_TO_MEMORY;
// 内存: dst
dsdps.memory0_addr = dmax_dst;
dsdps.memory_inc = dmax_dst_inc;
// 外设: src
dsdps.periph_addr = dmax_src;
dsdps.periph_inc = dmax_src_inc;
// 数据长度
dsdps.number = dmax_src_len;
// dst数据宽度
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_flag_clear(dmax, dmax_ch, DMA_FLAG_FTF);
dma_channel_enable(dmax, dmax_ch);
- 哪个dma:DMA0或者DMA1
- dma的哪个通道:ch0到ch7
- dma的哪个子级:0到7
- 传输方向是什么:内存到内存,内存到外设,外设到内存
- 源地址是多少?源的数据长度是多少(几个byte)?源的数据宽度是多少(几个bit)?
- 目标地址是什么?
- 从源地址向目标地址传输数据时,源地址的指针是否需要增长。
- 从源地址向目标地址传输数据时,目标地址的指针是否需要增长。
串口接收理解
地址和目标地址
源地址和目标地址都是提前配置好的,当传输时,就会从源地址将数据传递给目标地址。自增长
每传输一个字节数据,地址是否需要增长。这里包含了源地址是否需要增长,也包含了目标地址是否需要增长。
串口中,寄存器地址不变,大小不变,我们只是向这个里面放数据,所以不需要增长。数据宽度
数据宽度表示一次传递多上个bit数据长度
传输了多少个数据宽度的数据。
练习题
- 实现dma的串口接收