DMA+AD

#include "stm32f10x.h"                  // Device header

//这里代码使用的是ADC的扫描模式,加DMA数据转运,执行流程如图: 
//首先要扫描PA0到PA3这4个通道,所以点菜菜单函数ADC_RegularChannelConfig需调用。

uint16_t AD_Value[4];

void AD_Init(void)
{
	
//第一步、开启时钟,
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//开启ADC1的时钟,ADC都是APB2上的设备,所以这里用APB2开启时钟的函数。
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//还需要开启PA0口的时钟
	//以上时钟就配置好了,还需要配置ADCCLK
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//DMA是AHB总线的设备,所以要用AHB开启时钟的函数,第一个参数在函数中的定义是:对于互联型设备,这个参数可以是下面这些值的组合;对于其他设备是这下面的组合。互联型是STM32F105/107的型号,F103的在其它设备下面的参数表里选RCC_AHBPeriph_DMA1。第二个参数ENABLE,开启DMA1的时钟。

	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//函数有四个参数分别是2、4、6、8分频,配置好之后,ADC的clock=PCLK2/2、4、6、8,这里PCLK2就是APB2时钟的意思。
	//这里选择6分频,分频之后,ADCCLK=72MHz/6=12MHz
	
//第二步、配置GPIO
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AIN;//这里选择模拟输入这个模式,在GPIO_Mode_AIN模式下,GPIO是无效的,断开GPIO口,防止GPIO口的输入输出对模拟电压造成干扰。GPIO_Mode_AIN模式就是ADC的专属模式。
	GPIO_InitStruct.GPIO_Pin= GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
	GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//1:adc。2:指定通道,通道0~通道17. 3:Rank,规则组序列器里的次序,在1~16之间。4:指定通道的采样时间,采样时间参数根据需求调整,需要更快的转换,就选择小的参数;需要更稳定的转换,就选择大的参数。这里选择参数是采样时间为55.5个ADCCLK的周期。
	ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);//1:adc。2:指定通道,通道0~通道17. 3:Rank,规则组序列器里的次序,在1~16之间。4:指定通道的采样时间,采样时间参数根据需求调整,需要更快的转换,就选择小的参数;需要更稳定的转换,就选择大的参数。这里选择参数是采样时间为55.5个ADCCLK的周期。
	ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);//1:adc。2:指定通道,通道0~通道17. 3:Rank,规则组序列器里的次序,在1~16之间。4:指定通道的采样时间,采样时间参数根据需求调整,需要更快的转换,就选择小的参数;需要更稳定的转换,就选择大的参数。这里选择参数是采样时间为55.5个ADCCLK的周期。
	ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);//1:adc。2:指定通道,通道0~通道17. 3:Rank,规则组序列器里的次序,在1~16之间。4:指定通道的采样时间,采样时间参数根据需求调整,需要更快的转换,就选择小的参数;需要更稳定的转换,就选择大的参数。这里选择参数是采样时间为55.5个ADCCLK的周期。
//调用4次ADC_RegularChannelConfig函数,点四个菜:通道0放在序列1的位置;通道1放在序列2的位置;通道2放在序列3的位置;通道3放在序列4的位置;
//菜单上的1~4号空位,填上了0~3这4个通道。通道和次序可以任意修改,修改后最终结果存放的顺序也会相应变化。
	

//第四步、用结构体初始化ADC。这里使用ADC,单次转换,扫描模式,
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//ADC的工作模式,这个参数是配置ADC是工作在独立模式还是双ADC模式,这里选择独立模式。
	ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//数据对齐,这里介绍是指定ADC数据是左对齐还是右对齐,ADC_DataAlign_Right右对齐;ADC_DataAlign_Left左对齐 
	ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//外部触发转换选择,就是触发控制的触发源,定义用于启动规则组转换的外部触发源。参数对应结构框图中的外部触发源选择。这里选择 ADC_ExternalTrigConv_None 参数,就是不使用外部触发,也就是使用内部软件触发的意思。
	ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//连续/单次转换模式,这个参数可以选择是ENABLE连续转换模式还是DISABLE单次转换模式。
	ADC_InitStructure.ADC_ScanConvMode=ENABLE;//扫描/非扫描转换模式,这个参数可以选择是ENABLE扫描模式(多通道)还是DISABLE非扫描模式(单通道)。
	ADC_InitStructure.ADC_NbrOfChannel=4;//通道数目,这个是在指定扫描模式下,总共会有几个通道需要扫描,参数必须在1~16之间。这里点了4个菜。
	ADC_Init(ADC1,&ADC_InitStructure);

	

//第二步,参数初始化配置。直接调用DMA_Init,初始化各个参数,包括外设和存储器站点的起始地址、数据宽度、地址是否自增、方向、传输计数器、是否需要自动重装、选择触发源、通道优先级。这些所有的参数,通过一个结构体,就可以配置好了。
	DMA_InitTypeDef DMA_InitStructure;
	//外设站点的:
	DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;//起始地址,外设站点的基地址,这里是端菜的源头,厨师把菜做好就放在ADC_DR寄存器里。所以端菜的源头地址就填ADC_DR寄存器的地址。ADC1的DR寄存器地址就是0x4001 244C,所以可以直接填0x4001244C。但是库函数已经算好了具体地址,一般寄存器地址也不需要算好后填实际值,所以可以对ADC1->DR取地址,再强转为uint32_t。也就是(uint32_t)&ADC1->DR 这样得到的结果就是0x4001244C,这样外设地址就填好了。
	DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//数据宽度,这里需要DR寄存器低16位的数据,所以数据宽度就是HalfWord,半字,16位来转运。
	DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//是否自增,外设地址是否自增,这里不自增,始终转运同一个位置的数据。
	DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)AD_Value;//起始地址,存储器站点的基地址,也就是端菜的目的地,这里需要把数据存在SRAM数组里,所以现在代码块最前面定义一个数组AD_Value,然后在这里把AD_Value数组的地址作为目的地,把数组的地址强转为uint32_t,
	DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//数据宽度,和外设站点一样也选择HalfWord参数,以半字传输。
	DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//是否自增,存储器地址是自增的,每转运一次挪一个坑。这里选择地址自增。
	DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向,函数定义解释是,指定外设站点是源端还是目的地。
	DMA_InitStructure.DMA_BufferSize=4;//传输数量给4,因为有4个ADC通道,所以传输4次。
	DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//传输模式,这里可以给正常的单次模式,也可以自动重装的循环模式。
	DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//选择是否存储到存储器,其实就是选择硬件触发还是软件触发。这里不使用软件触发,需要硬件触发,触发源为ADC1。厨师每个菜做好了,叫DMA一下,DMA再去端菜,这样才是合适的时机。
	DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级,按照参数要求,给一个优先级。函数定义解释,指定通道的软件优先级。
	DMA_Init(DMA1_Channel1,&DMA_InitStructure);//所有的参数都配置到DMA1的通道1里去。这里通道就不能任意选择了,需要看DMA1请求映像图。可以看到ADC1的硬件触发只接在了DMA1的通道1上。所以这里必须要使用DMA1的通道1,其它通道都不行。
	DMA_Cmd(DMA1_Channel1,ENABLE);//直接使能
	//DMA转运的三个条件:第一个,传输计数器不为0,满足;第三个,DMA使能,满足;但是第二个,触发源有信号,目前是不满足的,因为这里是硬件触发,ADC还没启动,不会有触发信号。所以这里DMA使能之后,不会立刻工作。最后在ADC使能之前还有一个事需要做,就是开启ADC到DMA的输出。
//开启ADC到DMA的输出,有三个硬件触发源,具体使用哪个,取决于哪个的DMA输出给开启了。
	ADC_DMACmd(ADC1,ENABLE);//这个函数用来开启DMA触发信号的。

//第五步、开启ADC电源
	ADC_Cmd(ADC1,ENABLE);//ADC准备就绪
	

	
//在开启电源之后,根据手册的建议,还需要对ADC进行校准
//1.复位校准
	ADC_ResetCalibration(ADC1);//复位校准
//2.等待复位校准完成
	while(ADC_GetResetCalibrationStatus(ADC1)==SET);//这个函数是返回复位校准的状态,要等待复位完成的话,还需要加一个while循环,若没校准完成的话,就在这个while空循环里一直等待。
	//获取的标志位和是否校准完成的对应关系需参考寄存器说明,ADC_GetResetCalibrationStatus函数定义中返回值的说明是,ADC复位校准寄存器的状态,SET或RESET,在此函数代码中也可以看出来,它获取的就是CR2寄存器里的RSTCAL标志位。在ADC的CR2寄存器里RSTCAL复位校准位的说明是该位由软件设置并由硬件清除,在校准寄存器被初始化后该位将被清除。所以该标志位的用法就是软件置改位为1,那硬件就会开始复位校准,当复位校准完成后,该位就会由硬件自动清0.
	//第一条代码ADC_ResetCalibration(ADC1);开始复位校准,就是将RSTCAL标志位置1,然后获取复位校准状态,就是读取RSTCAL标志位这一位,所以在读取这一位的时候,如果它是1,那就需要一直空循环等待。如果它变为0了,那就说明复位校准完成,可以跳出等待了。
	//所以while的条件就是,获取标志位是不是==SET,如果等于SET,while条件为真,就会一直空循环。一旦标志位被硬件清0了,这个空循环就会自动跳出来。这样就实现了等待复位校准完成的效果。这里==SET也是可以省略的,因为返回值SET直接作为条件和是不是==SET作为条件效果是一样的
//3.开始校准
	ADC_StartCalibration(ADC1);//调用函数就开始校准了,之后内部电路就会自动进行校准。
//4.等待校准完成
	while(ADC_GetCalibrationStatus(ADC1)==SET);//调用函数获取校准状态,也将函数放于while循环内,循环条件是,校准标志位是不是==SET,这样就可以等待校准是否完成了。

}

//以上设置使ADC已处于准备就绪的状态,以下设计启动转换,获取结果的函数
//以上,ADC和DMA配合工作的配置就完成了。

void AD_GetValue(void)//函数不需要参数和返回值了
{
//函数里面:因为现在ADC还是单次模式,所以还需要软件触发一下ADC开始。其他的不需要。
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//调用函数就可以触发,ADC就已经开始进行转换了。转换需要一段时间,

//因为DMA也是正常的单次模式,所以在触发ADC之前,需要再重新写入一下传输计数器。
	//函数里面需要重新给传输计数器赋值,传输计数器赋值,必须要先给DMA失能,所以这里,调用DMA_Cmd函数,
	DMA_Cmd(DMA1_Channel1,DISABLE);//第二个参数给DISABLE。然后就可以给传输计数器赋值了。
	DMA_SetCurrDataCounter(DMA1_Channel1,4);//第一个参数,选择DMA和通道。第二个参数传输次数给4.
	//最后,再次调用DMA_Cmd函数,
	DMA_Cmd(DMA1_Channel1,ENABLE);//第二个参数给ENABLE
	//这样DMA传输的3个条件又重新满足了。DMA就会再次开始转运

//最后,等待ADC转换和DMA转运完成。因为转运总是在转换之后的,所以可以写入等待DMA完成的代码。,等待ADC转换完成的代码就不需要了。
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);
	DMA_ClearFlag(DMA1_FLAG_TC1);

	
}//这样当调用一下AD_GetValue函数,ADC开始转换,连续扫描4个通道,DMA也同步进行转运,AD转换结果依次放在AD_Value数组里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值