一.概要

基本概念:
DMA是Direct Memory Access的首字母缩写,是一种完全由硬件执行数据交换的工作方式。DMA控制器从CPU接管对总线的控制,不经过CPU直接在内存和外设之间进行批量数据交换。DMA控制器向内存发出地址和控制信号,修改地址,对传送的字的个数计数,并且以中断方式向CPU报告传送操作的结束。 DMA方式一般用于高速传送成组数据。

DMA传输的三大要素:
传输源:DMA控制器从传输源读出数据;
传输目标:DMA控制器将数据传输的目标;
触发信号:用于触发一次数据传输的动作,执行一个单位的传输源至传输目标的数据传输;可以用来控制传输的时机。

DMA的主要优点:
由于CPU根本不参加传送操作,因此就省去了CPU取指令、取数、送数等操作。在数据传送过程中,没有保存现场、恢复现场之类的工作。内存地址修改、传送字个数的计数等等,也不是由软件实现,而是用硬件线路直接实现的。所以DMA方式能满足高速I/O设备的要求,也有利于CPU效率的发挥。

二.GD32F103C8T6单片机DMA外设特点

传输数据长度可编程配置,最大到65536;
7个通道,并且每个通道都可配置(DMA0有7个通道);
AHB和APB外设,片上闪存和SRAM都可以作为访问的源端和目的端;
每个通道连接固定的硬件DMA请求;
支持软件优先级(低、中、高、极高)和硬件优先级(通道号越低,优先级越高);
存储器和外设的数据传输宽度可配置:字节,半字,字;
存储器和外设的数据传输支持固定寻址和增量式寻址;
支持循环传输模式;
支持外设到存储器,存储器到外设,存储器到存储器的数据传输;
每个通道有3种类型的事件标志和独立的中断;
支持中断的使能和清除。

三.GD32单片机DMA内部结构图

零基础国产GD32单片机编程入门(十六)DMA详解及ADC-DMA方式采集含源码-LMLPHP
DMA控制器由4部分组成:

AHB 从接口配置DMA
AHB主接口进行数据传输
仲裁器进行DMA请求的优先级管理
数据处理和计数

四.DMA各通道请求

多个外设请求被映射到同一个DMA通道。这些请求信号在经过逻辑或后进入DMA。通过配置对应外设的寄存器,每个外设的请求均可以独立的开启或关闭。用户必须确保同一时间,在同一个通道上仅有一个外设的请求被开启。
零基础国产GD32单片机编程入门(十六)DMA详解及ADC-DMA方式采集含源码-LMLPHP
零基础国产GD32单片机编程入门(十六)DMA详解及ADC-DMA方式采集含源码-LMLPHP
以ADC0为例,可以映射到DMA的Channel0

DMA的传输模式:

循环模式:用于处理一个环形的缓冲区,每轮传输结束时数据传输的配置会自动地更新为初始状态,DMA传输会连续不断地进行。 一般采用循环模式。

普通模式:在DMA传输结束时,DMA通道被自动关闭,进一步的DMA请求将不被满足。

五.GD32F103C8T6单片机ADC-DMA采集例程

STLINK接GD32F103C8T6开发板,STLINK接电脑USB口。

零基础国产GD32单片机编程入门(十六)DMA详解及ADC-DMA方式采集含源码-LMLPHP

GD32F103C8T6开发板的PA4 引脚上的进行 ADC 电压采集,杜邦线连接 PA4 引脚与 VDD(3.3V),应该能读到单片机供电的电压值。

主要代码:

uint16_t adc_value;//ADC采样值
uint16_t Vol_Value;//电压值
/*!
    \brief      configure the DMA peripheral
    \param[in]  none
    \param[out] none
    \retval     none
*/
void dma_config(void)
{
    /* ADC_DMA_channel configuration */
    dma_parameter_struct dma_data_parameter;
    
    /* ADC DMA_channel configuration */
    dma_deinit(DMA0, DMA_CH0);//ADC0需要映射到通道0
    
    /* initialize DMA single data mode */
    dma_data_parameter.periph_addr  = (uint32_t)(&ADC_RDATA(ADC0));
    dma_data_parameter.periph_inc   = DMA_PERIPH_INCREASE_DISABLE;
    dma_data_parameter.memory_addr  = (uint32_t)(&adc_value);//采样值存储到变量
    dma_data_parameter.memory_inc   = DMA_MEMORY_INCREASE_DISABLE;
    dma_data_parameter.periph_width = DMA_PERIPHERAL_WIDTH_16BIT;
    dma_data_parameter.memory_width = DMA_MEMORY_WIDTH_16BIT;  
    dma_data_parameter.direction    = DMA_PERIPHERAL_TO_MEMORY;
    dma_data_parameter.number       = 1;
    dma_data_parameter.priority     = DMA_PRIORITY_HIGH;
    dma_init(DMA0, DMA_CH0, &dma_data_parameter);
    dma_circulation_enable(DMA0, DMA_CH0);//循环模式使能
  
    /* enable DMA channel */
    dma_channel_enable(DMA0, DMA_CH0);//使能DMA0通道0
}




void adc_config(void)
{
    /* reset ADC */
    adc_deinit(ADC0);
    /* ADC mode config */
    adc_mode_config(ADC_MODE_FREE);
    /* ADC contineous function enable */
    adc_special_function_config(ADC0, ADC_CONTINUOUS_MODE, ENABLE);
    /* ADC scan mode disable */
    adc_special_function_config(ADC0, ADC_SCAN_MODE, DISABLE);
    /* ADC data alignment config */
    adc_data_alignment_config(ADC0, ADC_DATAALIGN_RIGHT);
    /* ADC channel length config */
    adc_channel_length_config(ADC0, ADC_REGULAR_CHANNEL, 1);
 
    /* ADC regular channel config */
    adc_regular_channel_config(ADC0, 0, ADC_CHANNEL_4, ADC_SAMPLETIME_55POINT5);
    /* ADC trigger config */
    adc_external_trigger_source_config(ADC0, ADC_REGULAR_CHANNEL, ADC0_1_2_EXTTRIG_REGULAR_NONE);
    adc_external_trigger_config(ADC0, ADC_REGULAR_CHANNEL, ENABLE);
    
    /* enable ADC interface */
    adc_enable(ADC0);
    delay_1ms(1);
    /* ADC calibration and reset calibration */
    adc_calibration_enable(ADC0);
    /* ADC DMA function enable */
    adc_dma_mode_enable(ADC0);//DMA使能
    /* ADC software trigger enable */
    adc_software_trigger_enable(ADC0, ADC_REGULAR_CHANNEL); 
}

int main(void)
{
	rcu_apb2_clock_config(RCU_APB2_CKAHB_DIV1);//设置主频108M(#define __SYSTEM_CLOCK_108M_PLL_HXTAL         (uint32_t)(108000000)),8M外部晶振  (#define HXTAL_VALUE    ((uint32_t)8000000))
  	systick_config();//配置1ms SysTick
	rcu_periph_clock_enable(RCU_AF);//AF时钟使能 
	delay_1ms(1000);
	
	//配置PA4 ADC引脚
	rcu_periph_clock_enable(RCU_GPIOA);
	gpio_init(GPIOA, GPIO_MODE_AIN, GPIO_OSPEED_50MHZ, GPIO_PIN_4); 
	/* enable ADC1 clock */
	rcu_periph_clock_enable(RCU_ADC0);
	/* enable DMA0 clock */
	rcu_periph_clock_enable(RCU_DMA0);
	/* config ADC clock */
	rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV8);
	//配置ADC
	dma_config();//DMA配置
	adc_config();//ADC配置
	while(1)
	{

		Vol_Value=adc_value*3300/4095;//读取ADC值,并转换成电压值
        delay_1ms(500);//等待500ms
		
	}
		
}

实验结果:
用Keil运行程序,全速运行,查看Keil调试Watch界面,如下图,PA4引脚的采样值adc_value
为0xfff,电压值为3300mV。
零基础国产GD32单片机编程入门(十六)DMA详解及ADC-DMA方式采集含源码-LMLPHP

六.工程源代码下载

通过网盘分享的文件:18.ADC_DMA实验.zip
链接: https://pan.baidu.com/s/1gXpLC8ddlfVSGXG4pkT8dA 提取码: ny7i
如果链接失效,可以联系博主给最新链接
程序下载下来之后解压就行
CSDN代码

七.小结

使用DMA进行数据收发能够提高数据传输的效率和可靠性。其次,使用DMA进行串口数据收发可以减轻CPU的负担。

09-13 04:59