1、STM32的DMA简介

直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。
两个DMA控制器有12个通道 (DMA1有7个通道,DMA2有5个通道) ,每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。

2、DMA的主要特性

1、12个独立的可配置的通道(请求):DMA1有7个通道,DMA2有5个通道
2、每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
3、在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推)。
4、独立数据源和目标数据区的传输宽度(字节、半字、全字)模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
5、支持循环的缓冲器管理
6、每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。
7、存储器和存储器间的传输
8、外设和存储器、存储器和外设之间的传输
9、闪存、SRAM、外设的SRAM、APB1、APB2 和AHB外设均可作为访问的源和目标。
10、可编程的数据传输数目:最大为65535

如图为DMA的发起申请通道,本次使用的是ADC1进行对数据的传输,因此使用的为DMA的通道一。
下图为DMA1各个通道的请求现象。

下图为DMA2的各个通道的请求现象。

本次利用DMA要解决的文问题:

因为在使用多路规则通道转换ADC数据时转换好的数据只能存储在一个仅有的数据寄存器之中,如果不对ADC_DR寄存器中的内容尽早转移走,则将会被新转换好的数据覆盖。因此在使用多路ADC数据的转换过程中我们则需要运用DMA进行对数据的搬运。保证数据不会被覆盖而得到我们想要的数据。

代码编写分析

1、首先对于编写多路ADC的代码首先还是要对我们用到的相应外设进行初始化。
对GPIO,ADC,DMA进行初始化
因此第一步要开启相应这些寄存器的时钟,并且对ADC进行分频因为ADC最大支持14MHZ的频率。APB2总线的时钟频率为72MHZ

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE );RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);RCC_ADCCLKConfig(RCC_PCLK2_Div8);//对apb2的时钟进行8分频

2、首先是对GPIOA进行初始化设置,构造GPIO的结构体
本次使用到的为GPIOA中的0,1,2,3四路通道,因此要对相应的4个引脚进行使能

GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AIN;//将GPIOA0的引脚设置为模拟输入模式,这时候GPIOA0是无效的GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;//使能多个GPIO将能够进行多路的ADC数值采集GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);

3、之后因为要用相应的GPIO作为ADC的输入引脚因此要对ADC进行配置,(细节可以参考上一次的ADC单通道配置)

ADC_InitTypeDef ADC_InitStruct;ADC_InitStruct.ADC_ContinuousConvMode=ENABLE;//连续转换的模式设置ADC_InitStruct.ADC_DataAlign=ADC_DataAlign_Right;//配置ADC存储模式为右对齐ADC_InitStruct.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//是否配置硬件的中断函数,选择noneADC_InitStruct.ADC_Mode=ADC_Mode_Independent;//配置模式为独立模式,其余模式都是双ADC模式ADC_InitStruct.ADC_NbrOfChannel=4;//通道数目ADC_InitStruct.ADC_ScanConvMode=ENABLE;//ADC的扫描模式关闭ADC_Init(ADC1,&ADC_InitStruct);ADC_Cmd(ADC1,ENABLE);//开启adc的电源 开始转换

4、之后对本次使用到的ADC规则通道进行配置因为使用到了4路通道因此要进行4次设置,用到更多的通道对其再添加即可。

ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//ADC规则组通道配置ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);//ADC规则组进行选择,ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);//如果用到了别的通道,ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);//可以继续对其进行定义

5、接下来则是对ADC的校准

ADC_ResetCalibration(ADC1);//复位校准while(ADC_GetResetCalibrationStatus(ADC1) == SET);//获得校准复位的状态  获取的是CR2中的RSTCAL的数值ADC_StartCalibration(ADC1);//ADC1开始校准while(ADC_GetCalibrationStatus(ADC1) == SET);

6、接下来为本次的配置重点对DMA配置的讲解
首先要对DMA通道进行初始化设置,将DMAyChannelx寄存器的初始化为其默认值。因为本次用到的ADC1通道挂载于DAM1中的通道1中因此要对DMA1的通道1进行初始化并对本次使用到的结构体进行定义。

DMA_DeInit(DMA1_Channel1);DMA_InitTypeDef DAM_InitStruct;

7、接下来是对本次使用到的结构体进行配置
首先是对本次传输过程中所使用到的DMA缓冲区的大小进行设定,因为我们本次使用到的只有4路通道因此我们只需要将缓冲区的数值设定为4即可。

DAM_InitStruct.DMA_BufferSize=4;//开设DMA在传输过程中的缓冲区的大小

8、
DAM_InitStruct.DMA_DIR=
本函数主要是对数据的传输方向进行配置因为传输的方向为外设采集数据传输到DMA因此使用DMA_DIR_PeripheralSRC进行配置(其他情况使用DMA_DIR_PeripheralDST)

DAM_InitStruct.DMA_DIR=DMA_DIR_PeripheralSRC;//数据的传输方向,由外设到内存传输数据

9、 DAM_InitStruct.DMA_M2M本函数主要是设置是否有内存对内存的数据进行传输,而本次没有用到,因此将其设置为disable

DAM_InitStruct.DMA_M2M=DMA_M2M_Disable;//DMA通道没有设置内存到内存的传输

10、DAM_InitStruct.DMA_MemoryBaseAddr函数主要用来对内存的地址进行分配,分配ADC转换好的数据将其存入我们所分配的地址当中。因此我在本次的头函数中定义了ADC_ConvertedValue【4】这一数组用来存储数据

DAM_InitStruct.DMA_MemoryBaseAddr=(u32)ADC_ConvertedValue;//分配内存的地址

11、DAM_InitStruct.DMA_MemoryDataSize函数主要用于对内存中数据的宽度进行设置

DAM_InitStruct.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//设置内存存储的数据宽度16位

12、DAM_InitStruct.DMA_MemoryInc为内存寄存器的地址是否要进行增加。因为我们采样的数据有4组如果不进行数据地址的偏移我们需要的数据将被覆盖因此要对数据地址移位进行使能。

DAM_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable;//内存地址进行增加,因为需要进行对数据的存储

13、DAM_InitStruct.DMA_Mode为配置DMA的转换模式是否进行连续的转换,还是转换一次就停止转换,因为我们要实时采集ADC4个通道的数据数值因此需要设置为循环模式。

DAM_InitStruct.DMA_Mode=DMA_Mode_Circular;//设置传输模式位连续不断的循环模式

14、DAM_InitStruct.DMA_PeripheralBaseAddr设置使用到的外设的基地址所用的数据将从本地址进行读取通过查阅数据手册我们可以得知ADC1的首地址为0x4001244C,也可以使用寄存器直接指向ADC1的地址。

DAM_InitStruct.DMA_PeripheralBaseAddr=( u32 ) ( & ( ADC1->DR ) );//DMA的外设基地址

15、DAM_InitStruct.DMA_PeripheralDataSize设置外设的数据长度,要与内存所设置的数据长度保持一致。

DAM_InitStruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//设置外设数据字长

16、DAM_InitStruct.DMA_PeripheralInc是否要对外设的地址进行偏移,因为我们要读取的数据为固定数值因此无需对地址进行偏移。

DAM_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设的基地址是否进行增加,否

17、对DMA使用的方式优先级进行设置,因为我们本次只使用到了ADC这一功能因此无需对优先级进行设置,不配置也行,在本次的项目中我随便配置为了最高级。

DAM_InitStruct.DMA_Priority=DMA_Priority_High;//设置DMA的转换的优先级为高优先级

18、初始化完成,对DMA进行使能,并对ADC的DMA进行使能

DMA_Init(DMA1_Channel1,&DAM_InitStruct);ADC_DMACmd(ADC1,ENABLE);//使能ADC1的DMA

19、使用软件开启ADC转换,配置完成。

ADC_SoftwareStartConvCmd(ADC1,ENABLE);//开启ADC的转换

参考代码

adc.c

#ifndef __ADC__H#define __ADC__Hvoid AD_Init();#endif

adc.h

#include "stm32f10x.h"#include "bsp_usart.h"uint16_t ADC_ConvertedValue[4];//定义转换的数据的存储空间;//数据存储空间=通道数*通道的转换次数void AD_Init(){RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE );RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);RCC_ADCCLKConfig(RCC_PCLK2_Div8);//对apb2的时钟进行6分频GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AIN;//将GPIOA0的引脚设置为模拟输入模式,这时候GPIOA0是无效的GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;//使能多个GPIO将能够进行多路的ADC数值采集GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//ADC规则组通道配置ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);//ADC规则组进行选择,ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);//如果用到了别的通道,ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);//可以继续对其进行定义//ADC_RegularChannelConfig(ADC1,ADC_Channel_4,5,ADC_SampleTime_1Cycles5);ADC_InitTypeDef ADC_InitStruct;ADC_InitStruct.ADC_ContinuousConvMode=ENABLE;//连续转换的模式设置ADC_InitStruct.ADC_DataAlign=ADC_DataAlign_Right;//配置ADC存储模式为右对齐ADC_InitStruct.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//是否配置硬件的中断函数,选择noneADC_InitStruct.ADC_Mode=ADC_Mode_Independent;//配置模式为独立模式,其余模式都是双ADC模式ADC_InitStruct.ADC_NbrOfChannel=4;//通道数目ADC_InitStruct.ADC_ScanConvMode=ENABLE;//ADC的扫描模式关闭ADC_Init(ADC1,&ADC_InitStruct);ADC_Cmd(ADC1,ENABLE);//开启adc的电源 开始转换/*接下来时ADC的校准*/ADC_ResetCalibration(ADC1);//复位校准while(ADC_GetResetCalibrationStatus(ADC1) == SET);//获得校准复位的状态  获取的是CR2中的RSTCAL的数值ADC_StartCalibration(ADC1);//ADC1开始校准while(ADC_GetCalibrationStatus(ADC1) == SET);//DMA初始化DMA_DeInit(DMA1_Channel1);DMA_InitTypeDef DAM_InitStruct;DAM_InitStruct.DMA_BufferSize=4;//开设DMA在传输过程中的缓冲区的大小DAM_InitStruct.DMA_DIR=DMA_DIR_PeripheralSRC;//数据的传输方向,由外设到内存传输数据DAM_InitStruct.DMA_M2M=DMA_M2M_Disable;//DMA通道没有设置内存到内存的传输DAM_InitStruct.DMA_MemoryBaseAddr=(u32)ADC_ConvertedValue;//分配内存的地址DAM_InitStruct.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//设置内存存储的数据宽度16位DAM_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable;//内存地址进行增加,因为需要进行对数据的存储DAM_InitStruct.DMA_Mode=DMA_Mode_Circular;//设置传输模式位连续不断的循环模式DAM_InitStruct.DMA_PeripheralBaseAddr=( u32 ) ( & ( ADC1->DR ) );//DMA的外设基地址DAM_InitStruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//设置外设数据字长DAM_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//外设的基地址是否进行增加,否DAM_InitStruct.DMA_Priority=DMA_Priority_High;//设置DMA的转换的优先级为高优先级DMA_Init(DMA1_Channel1,&DAM_InitStruct);//DMA_ITConfig(DMA1_Channel1,DMA_IT_TC,ENABLE);//DMA_IT_TC位中断的标志DMA_Cmd(DMA1_Channel1,ENABLE);//使能DMA1通道一ADC_DMACmd(ADC1,ENABLE);//使能ADC1的DMAADC_SoftwareStartConvCmd(ADC1,ENABLE);//开启ADC的转换