GD32F103 DMA的用法

GD32F103 DMA的用法

文章目录

  • GD32F103 DMA的用法
    • @[TOC](文章目录)
    • 1. DMA的思想
    • 2. DMA的原理
    • 3. GD32的DMA概述
    • 4 .GD32的DMA框图
      • 1.外设与存储器握手
      • 2.DMA的地址生成
      • 3.DMA的循环模式
      • 4.DMA的中断
      • 5.DMA的请求映射
    • 3.一次典型的DMA工作流程
    • 4. DMA的dome 存储器到存储器(M2M)
    • 5. DMA的dome 外设(ADC)到存储器
      • 1.遥杆的原理
      • 2. 程序设计


1. DMA的思想

DMA是指直接内存访问(Direct Memory Access),它是一种计算机硬件机制,用于在不需要中央处理器(CPU)干预的情况下,实现高速数据传输。DMA 的思想是让外部设备(如硬盘、声卡等)直接与内存进行数据交换,从而提高数据传输效率,降低 CPU 的负担。通过 DMA 传输数据时,CPU 只需要在数据传输开始和结束时进行初始化和结束处理,无需全程参与数据传输过程。这样能有效降低系统延迟,提高整体性能。让CPU从繁琐的内存数据读写中解放出来。交给DMA去处理。减轻CPU的负担。
DMA主要是数据搬运。比如从外设到存储器。或者存储器到存储器。这些都需要CPU先设定好。

2. DMA的原理

GD32F103 DMA的用法_第1张图片
GD32F103 DMA的用法_第2张图片
GD32F103 DMA的用法_第3张图片
举例:比如数据从外设到存储器。CPU需要设置DMA传输的属性
GD32F103 DMA的用法_第4张图片

DMA传输的具体流程
GD32F103 DMA的用法_第5张图片
DMA的应用
GD32F103 DMA的用法_第6张图片

3. GD32的DMA概述

当DMA在不停地搬运数据。肯定在不抢占的总线。如果这样那么CPU也要用总线该怎么办呢(总线是共用的)?
在总线矩阵中实现了循环仲裁算法来分配DMA与CPU的访问权。

GD32F103 DMA的用法_第7张图片

4 .GD32的DMA框图

GD32F103 DMA的用法_第8张图片
STM32的DMA框图
GD32F103 DMA的用法_第9张图片
GD32F103 DMA的用法_第10张图片

1.外设与存储器握手

为了保证数据的有效传输外设与存储器要进行握手确认
GD32F103 DMA的用法_第11张图片
当多个DMA通道收到请求时。通过仲裁机制来决定。
GD32F103 DMA的用法_第12张图片

2.DMA的地址生成

在外设与存储器传输多个数据时。可以设置地址自增。相当于每传输完一次。地址相应的增加。从而不会出现地址覆盖。具体增加多少取决于数据宽度。
GD32F103 DMA的用法_第13张图片
但如果源宽度于目标宽度不一样会怎样呢。如果宽度一样。源数据是什么。目标就接收什么。
源宽度小于目标宽度。目标数据高位用0补充。
源宽度大于目标宽度。目标数据只接收低字节。
GD32F103 DMA的用法_第14张图片

3.DMA的循环模式

当所有的数据传输完成。把重装载值重新写入字节计数寄存器里。就就可以不停地循环传输。
GD32F103 DMA的用法_第15张图片
存储器到存储器传输
GD32F103 DMA的用法_第16张图片

4.DMA的中断

GD32F103 DMA的用法_第17张图片

5.DMA的请求映射

GD32F103 DMA的用法_第18张图片

3.一次典型的DMA工作流程

GD32F103 DMA的用法_第19张图片
GD32F103 DMA的用法_第20张图片

4. DMA的dome 存储器到存储器(M2M)

程序要求:利用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){
		;
	}
}

5. DMA的dome 外设(ADC)到存储器

当ADC转换完成通道DMA去ADC外设数据寄存器拿去放到存储器中。

1.遥杆的原理

GD32F103 DMA的用法_第21张图片
GD32F103 DMA的用法_第22张图片
GD32F103 DMA的用法_第23张图片

2. 程序设计

GD32F103 DMA的用法_第24张图片
选择DMA0的通道0刚好有ADC0。所以DMA的通道要根据手册来选择。
GD32F103 DMA的用法_第25张图片

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);
	}
}

你可能感兴趣的:(GD32F10X,单片机,stm32,嵌入式硬件)