DMA是指直接内存访问(Direct Memory Access),它是一种计算机硬件机制,用于在不需要中央处理器(CPU)干预的情况下,实现高速数据传输。DMA 的思想是让外部设备(如硬盘、声卡等)直接与内存进行数据交换,从而提高数据传输效率,降低 CPU 的负担。通过 DMA 传输数据时,CPU 只需要在数据传输开始和结束时进行初始化和结束处理,无需全程参与数据传输过程。这样能有效降低系统延迟,提高整体性能。让CPU从繁琐的内存数据读写中解放出来。交给DMA去处理。减轻CPU的负担。
DMA主要是数据搬运。比如从外设到存储器。或者存储器到存储器。这些都需要CPU先设定好。
举例:比如数据从外设到存储器。CPU需要设置DMA传输的属性
当DMA在不停地搬运数据。肯定在不抢占的总线。如果这样那么CPU也要用总线该怎么办呢(总线是共用的)?
在总线矩阵中实现了循环仲裁算法来分配DMA与CPU的访问权。
为了保证数据的有效传输外设与存储器要进行握手确认
当多个DMA通道收到请求时。通过仲裁机制来决定。
在外设与存储器传输多个数据时。可以设置地址自增。相当于每传输完一次。地址相应的增加。从而不会出现地址覆盖。具体增加多少取决于数据宽度。
但如果源宽度于目标宽度不一样会怎样呢。如果宽度一样。源数据是什么。目标就接收什么。
源宽度小于目标宽度。目标数据高位用0补充。
源宽度大于目标宽度。目标数据只接收低字节。
当所有的数据传输完成。把重装载值重新写入字节计数寄存器里。就就可以不停地循环传输。
存储器到存储器传输
程序要求:利用DMA0的通道1与通道2。把源地址数据分别传输到两个目标地址里(通道1与通道2)
先传输通道1不开启循环。然后设置通道2.传输通道2.也不开启循环。
dma_test.h
#ifndef _DMA_TEST_H
#define _DMA_TEST_H
#include "gd32f10x.h"
extern uint8_t source[5];
extern uint8_t destination_1[5];
extern uint8_t destination_2[5];
void test_dma(void);
#endif
dma_test.c
#include "dma_test.h"
void test_dma(void){
/*
初始化DMA0的通道DMA_CH1
使用DMA0的通道DMA_CH1传输数据到destination_1
初始化DMA0的通道DMA_CH2
使用DMA0的通道DMA_CH2传输数据到destination_2
*/
dma_parameter_struct dma_init_struct;
// 初始化DMA0的通道DMA_CH1
rcu_periph_clock_enable(RCU_DMA0);
dma_deinit(DMA0, DMA_CH1);
dma_struct_para_init(&dma_init_struct);
dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY; // 传输方向
dma_init_struct.memory_addr = (uint32_t)destination_1; // 目标地址1
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 目标地址自增使能
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
dma_init_struct.number = 5;
dma_init_struct.periph_addr = (uint32_t)source; // 源地址
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_ENABLE; // 源地址自增使能
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; // 外设数据宽度
dma_init(DMA0, DMA_CH1, &dma_init_struct); // 配置dma0、dma_ch1
dma_circulation_disable(DMA0, DMA_CH1); // 禁止循环dma
dma_memory_to_memory_enable(DMA0, DMA_CH1);// 使能M2M模式
dma_channel_enable(DMA0, DMA_CH1); // 开启dma0 的通道1
//初始化dma0的dma_ch2
dma_deinit(DMA0, DMA_CH2);
dma_init_struct.memory_addr = (uint32_t)destination_2; // 目标地址2
dma_init(DMA0, DMA_CH2, &dma_init_struct); // 配置dma0、dma_ch1
dma_circulation_disable(DMA0, DMA_CH2); // 禁止循环dma
dma_memory_to_memory_enable(DMA0, DMA_CH2);// 使能M2M模式
dma_channel_enable(DMA0, DMA_CH2); // 开启dma
}
main.c
#include
#include "systick.h"
#include "dma_test.h"
uint8_t source[5] ={0x12, 0xac, 0x55, 0xab, 0x11};
uint8_t destination_1[5];
uint8_t destination_2[5];
int main(){
systick_config();
//先显示destination_1和destination_2
//TODO:打印传输前的值
//进行dma m2m 将source里面的数据给转移到destination_1、destination_2
test_dma();
//再显示destination_1和destination_2,判断是否dma成功
//TODO:打印传输后的值
while(1){
;
}
}
当ADC转换完成通道DMA去ADC外设数据寄存器拿去放到存储器中。
选择DMA0的通道0刚好有ADC0。所以DMA的通道要根据手册来选择。
rocker_driver.h
#ifndef _ROCKER_DRIVE_H
#define _ROCKER_DRIVE_H
#include "gd32f10x.h"
#include "systick.h"
extern uint16_t rocker_data[2];
void rocker_init(void); //初始化摇杆
void rcu_config(void);
void gpio_config(void);
void adc_config(void);
void dma_config(void);
#endif
rocker_driver.c
#include "rocker_driver.h"
//初始化摇杆
void rocker_init(void){
rcu_config();
gpio_config();
adc_config();
dma_config();
}
// 外设时钟使能配置
void rcu_config(void){
rcu_periph_clock_enable(RCU_GPIOA); // 开启GPIOA的时钟
rcu_periph_clock_enable(RCU_AF); // 开启复用的时钟
rcu_periph_clock_enable(RCU_ADC0); // 开启ADC0的时钟
rcu_periph_clock_enable(RCU_DMA0); // 开启DMA0的时钟
rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV8);//配置adc的时钟
}
// gpio配置
void gpio_config(void){
gpio_init(GPIOA, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_6|GPIO_PIN_7); // PA6,PA7配置为模拟输入
}
void adc_config(void){
adc_deinit(ADC0); // adc0 reset
adc_mode_config(ADC_MODE_FREE); // 所有ADC独立工作
adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE); // 开启adc0的循环模式
adc_special_function_config(ADC0, ADC_SCAN_MODE, ENABLE); // 开启扫描模式
adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT); //右对齐
// ADC序列的通道数量及通道号的设置
adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 2); // 设置两个通道
adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_6, ADC_SAMPLETIME_55POINT5); // 通道6第一个转换
adc_regular_channel_config(ADC0, 1, ADC_CHANNEL_7, ADC_SAMPLETIME_55POINT5); // 通道7第二个转换
// ADC的触发方式
adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE);
adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);
// 使能adc、校正adc
adc_enable(ADC0);
delay_1ms(2);
adc_calibration_enable(ADC0); // ADC0校准
// 将DMA ADC功能使能
adc_dma_mode_enable(ADC0);
adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL); // 使能软件触发
}
void dma_config(void){
dma_parameter_struct dma_data_parameter;
dma_deinit(DMA0, DMA_CH0);
//设置dma的一些属性
dma_data_parameter.periph_addr = (uint32_t)(&ADC_RDATA(ADC0)); // adc0的外设地址
dma_data_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 外设地址不自增
dma_data_parameter.memory_addr = (uint32_t)rocker_data; // 存储器地址
dma_data_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 存储器地址自增
dma_data_parameter.periph_width = DMA_PERIPHERAL_WIDTH_16BIT; // 外设数据宽度 16位
dma_data_parameter.memory_width = DMA_MEMORY_WIDTH_16BIT; // 存储器数据宽度 16位
dma_data_parameter.direction = DMA_PERIPHERAL_TO_MEMORY; // 传输方式(外设到存储器)
dma_data_parameter.number = 2; // 传输通道个数
dma_data_parameter.priority = DMA_PRIORITY_HIGH; // 通道优先级
dma_init(DMA0, DMA_CH0, &dma_data_parameter);
dma_circulation_enable(DMA0, DMA_CH0); // DMA循环
dma_channel_enable(DMA0, DMA_CH0); // 开启DMA0的通道0
}
main.c
```cpp
#include
#include "systick.h"
#include "i2c.h"
#include "oled_i2c.h"
#include "rocker_driver.h"
uint16_t rocker_data[2]; //用来存放摇杆的x、y数值
int main(){
systick_config();
i2c_init();
oled_init();
oled_clear_all();
char temp_string[20]; // 存放数值转为字符串的结果
oled_show_string(20,0, (uint8_t *)"ROCKER Test", 16);
rocker_init();
//先显示destination_1和destination_2
while(1){
// 显示摇杆的X轴的值
sprintf(temp_string, "X : %5d", rocker_data[0]);
oled_show_string(1,2, (uint8_t *)temp_string, 16);
// 显示摇杆的Y轴的值
sprintf(temp_string, "Y : %5d", rocker_data[1]);
oled_show_string(1,4, (uint8_t *)temp_string, 16);
}
}