• 【无标题】


    GD32F103 DMA的用法


    1. DMA的思想

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

    2. DMA的原理

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    举例:比如数据从外设到存储器。CPU需要设置DMA传输的属性
    在这里插入图片描述

    DMA传输的具体流程
    在这里插入图片描述
    DMA的应用
    在这里插入图片描述

    3. GD32的DMA概述

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

    在这里插入图片描述

    4 .GD32的DMA框图

    在这里插入图片描述
    STM32的DMA框图
    在这里插入图片描述
    在这里插入图片描述

    1.外设与存储器握手

    为了保证数据的有效传输外设与存储器要进行握手确认
    在这里插入图片描述
    当多个DMA通道收到请求时。通过仲裁机制来决定。
    在这里插入图片描述

    2.DMA的地址生成

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

    3.DMA的循环模式

    当所有的数据传输完成。把重装载值重新写入字节计数寄存器里。就就可以不停地循环传输。
    在这里插入图片描述
    存储器到存储器传输
    在这里插入图片描述

    4.DMA的中断

    在这里插入图片描述

    5.DMA的请求映射

    在这里插入图片描述

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

    在这里插入图片描述
    在这里插入图片描述

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    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){
    		;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

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

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

    1.遥杆的原理

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    2. 程序设计

    在这里插入图片描述
    选择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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
  • 相关阅读:
    Bigemap 在土地图利用环境生态行业中的应用
    如何在 R 中执行幂回归
    26、支付宝支付
    原型——重构数组方法chunk,新写css方法, 函数柯里化——全网最优原型继承继承
    关于我在学习LFU的时候,在开源项目捡了个漏这件事。
    密码学·常用网址
    虹科分享 | 如何将工程从CANoe转移到CanEasy?
    php费尔康框架phalcon(费尔康)框架学习笔记
    【RPA进阶】 高级数据操作
    [附源码]Python计算机毕业设计Django拉勾教育课程管理系统
  • 原文地址:https://blog.csdn.net/qq_41328470/article/details/133839234