STM32学习:通过DMA读取ADC规则通道多通道转换数据

本文详细介绍了STM32 DMA在多路ADC数据采集中的应用,包括DMA通道配置、初始化流程、数据传输方向设置、内存管理及优先级设置。通过DMA避免了ADC数据被覆盖,确保了连续、高效的数值采集和存储。
摘要由CSDN通过智能技术生成

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各个通道的请求现象。
DMA1通道
下图为DMA2的各个通道的请求现象。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;//是否配置硬件的中断函数,选择none
	ADC_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__H
void 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;//是否配置硬件的中断函数,选择none
	ADC_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的DMA
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//开启ADC的转换
``` int main(void) { //程序入口 u16 i=0; //定义16位无符号整型变量i,初始值为0 u32 ADC_CURRENT,ADC_REF; //定义32位无符号整型变量ADC_CURRENT和ADC_REF u16 ADC_CURRENT_TEMP[10],ADC_REF_TEMP[10]; //定义10个16位无符号整型数组变量ADC_CURRENT_TEMP和ADC_REF_TEMP LED_GPIO_Config(); //调用LED_GPIO_Config函数,进行LED灯的初始化配置 LED1_ON; //LED1亮 Adc_Init(); //调用Adc_Init函数,进行ADC的初始化配置 SysTick_Init(); //调用SysTick_Init函数,进行SysTick定时器的初始化配置 while(1) //进入循环 { ADC_CURRENT=0; //ADC_CURRENT清零 ADC_REF=0; //ADC_REF清零 for(i=0;i<9;i++) //循环9次 { ADC_CURRENT_TEMP[i]=ADC_CURRENT_TEMP[i+1]; //将ADC_CURRENT_TEMP数组中i+1位置的值赋给i位置 ADC_REF_TEMP[i]=ADC_REF_TEMP[i+1]; //将ADC_REF_TEMP数组中i+1位置的值赋给i位置 } Delay(500000); //延时500000个单位,这里的单位是一个空循环 ADC_CURRENT_TEMP[9]=ADC_ConvertedValue[1]; //将ADC_ConvertedValue数组中第1个元素的值赋给ADC_CURRENT_TEMP数组中第9个位置 ADC_REF_TEMP[9]=ADC_ConvertedValue[0]; //将ADC_ConvertedValue数组中第0个元素的值赋给ADC_REF_TEMP数组中第9个位置 for(i=0;i<10;i++) //循环10次 { ADC_CURRENT+=ADC_CURRENT_TEMP[i]; //将ADC_CURRENT_TEMP数组中第i个位置的值加到ADC_CURRENT上 ADC_REF+=ADC_REF_TEMP[i]; //将ADC_REF_TEMP数组中第i个位置的值加到ADC_REF上 } if(ADC_CURRENT*2>ADC_REF) //如果ADC_CURRENT乘以2大于ADC_REF //{CURRENT=((ADC_CURRENT*2-ADC_REF)*150)/4096;} //CURRENT等于((ADC_CURRENT乘以2减去ADC_REF)乘以150)除以4096 {CURRENT=((ADC_CURRENT*2-ADC_REF)*165)/4096;} //CURRENT等于((ADC_CURRENT乘以2减去ADC_REF)乘以165)除以4096 if(ADC_CURRENT*2<ADC_REF) //如果ADC_CURRENT乘以2小于ADC_REF //{CURRENT=((ADC_REF-ADC_CURRENT*2)*150)/4096;} //CURRENT等于((ADC_REF减去ADC_CURRENT乘以2)乘以150)除以4096 {CURRENT=((ADC_REF-ADC_CURRENT*2)*165)/4096;} //CURRENT等于((ADC_REF减去ADC_CURRENT乘以2)乘以165)除以4096 if(ADC_CURRENT*2==ADC_REF) //如果ADC_CURRENT乘以2等于ADC_REF {CURRENT=0;} if(CURRENT>2000) //如果CURRENT大于2000 {CURRENT=2000;} //CURRENT等于2000 a=CURRENT/1000; //a等于CURRENT除以1000的商 b=(CURRENT/100)%10; //b等于(CURRENT除以100的余数)除以10的余数 c=(CURRENT/10)%10; //c等于(CURRENT除以10的余数)除以10的余数 d=CURRENT%10; //d等于CURRENT除以10的余数 } } ```
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值