STM32---ADC模数转换详解

对于STM32的GPIO来说,只能读取引脚的高低电平,要么低电平,要么高电平,只有两个值,而使用了ADC之后,我们就可以对这个高电平和低电平之间的任意电压进行量化,最终用一个变量来表示,读取这个变量,所以ADC其实就是一个电压表,把引脚的电压值测出来,放在一个变量里。

12位逐次逼近型ADC,ADC 的转换时间跟 ADC 的输入时钟和采样时间有关,公式为:Tconv  =  采样时间  + 12.5 个周期。当 ADCLK  =  14MHZ  (最高),采样时间设置为 1.5 周期(最快),那么总的转换时间(最短)Tconv = 1.5 周期 + 12.5 周期 = 14 周期 = 1us。(1us转换时间)
输入电压范围:0~3.3V,转换结果范围∶0~4095  
                                                                           18个输入通道,可测量16个外部和2个内部信号源(内部温度传感器和内部参考电压1.2V)            规则组和注入组两个转换单元
模拟看门狗自动监测输入电压范围,如当AD值高于它设定的上阈值或者低于下阈值时
它就会申请中断,你就可以在中断函数里执行相应的操作。如下图:
STM32---ADC模数转换详解_第1张图片

下面,我们来配置一下单通道的ADC          

 第一步,RCC开启时钟。 

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);

STM32 的 ADC 多达 18 个通道,其中外部的 16 个通道是 ADCx_IN0 、ADCx_IN1...ADCx_IN5。这 16 个通道对应着不同的GPIO 口,具体是哪一个 GPIO 口可以从手册查询到。所以这里要打开GPIO外设时钟,而且还要打开ADC时钟,ADC 输入时钟 ADC_CLK 由 PCLK2(一般设置 PCLK2=HCLK=72M) 经过分频产生,最大是 14M,分频因子由 RCC 时钟配置寄存器 RCC_CFGR 的位 15:14 的ADCPRE[1:0]设置,可以是 2/4/6/8 分频,所以一般我们选择RCC_PCLK2_Div6(6分频)。ADC逐次比较的过程就是由这个时钟推动的。       

STM32---ADC模数转换详解_第2张图片

 STM32---ADC模数转换详解_第3张图片

 STM32---ADC模数转换详解_第4张图片

第二步,初始化GPIO。

    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

第三步,初始化ADC。

    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ScanConvMode = DISABLE;
    ADC_InitStructure.ADC_NbrOfChannel = 1;              
    ADC_Init(ADC1, &ADC_InitStructure);

 这里对ADC初始化涉及到的参数做一个分析:

单次转换还是连续转换,选择扫描模式还是非扫描模式,通道数目,数据对齐
STM32---ADC模数转换详解_第5张图片

 这个表就是规则组里的菜单,有16个空位,分别是序列1到序列16,你可以在这里点菜,就是写入你要转换的通道。在非扫描的模武下,这个菜单就只有第一个序列1的位置有效,这时,菜单同时选中一组的方式就退化为简单地选中一个的方式了,比如,我在序列1的位置写入通道2,然后,我们就可以触发转换,ADC就会对这个通道2进行模数转换。过一小段时间,转换完成,转换结果放在数据寄存器里,同时给EOC标志位置1。我们判断这个EOC标志位,如果转换完了,我们就可以在数据寄存器里读取结果了。单次转换,非扫描模式下,如果我们想再启动一次转换,那就需要再触发一次。如果想换一个通道就要在转换之前,把第一个位置的通道2换成其他通道。
 

 STM32---ADC模数转换详解_第6张图片

连续转换,非扫描模式,它与上一种单次转换不同的是,它在一次转换结束后不会停止,只需要最开始给触发一次,之后就可以一直转换了。STM32---ADC模数转换详解_第7张图片 

扫描模式,这就会用到这个菜单列表了,你可以在这个菜单里面点菜,比如,第一个菜是通道2,第二个菜是通道5.....,这里的菜单位置是通道几可以任意指定,并且也是可以重复的。

这里通道数目有7个,转换结果都放在数据寄存器里面,为了防止数据被覆盖,就需要DMA及时将数据移走,那7个通道转换完成之后,产生EOC信号,转换结束。
STM32---ADC模数转换详解_第8张图片

连续转换,扫描模式,同理。 

STM32---ADC模数转换详解_第9张图片

我们的ADC是12位的,所以转换结果就是12位的数据,但是这个寄存器是16位的,所以就存在数据对齐的问题。

右对齐,就是12位数据向右靠,高位多出来的补0,左对齐,就是12位数据向左靠,低位多出来的补0,一般使用右对齐,这样读取这个16位寄存器,直接就是转换结果。如果选择左对齐,直接读的话,得到的数据会比实际的大(因为数据左对齐把数据左移了4次相当于把结果乘16了)。如果0-4095范围太大,只是做一个简单的判断,不需要高分辨率,可以选择左对齐,然后把高8位数据读取出来,舍弃后4位的精度,即8位ADC。
STM32---ADC模数转换详解_第10张图片

第四步,选择规则组还是注入组

这里选用规则组

ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);

STM32---ADC模数转换详解_第11张图片

规则组最多可以选中16个通道,注入组最多可以选择4个通道,然后转换的结果可以存放在AD数据寄存器里,然后下面这里有触发控制,提供了开始转换这个START信号,触发控制可以选择软件触发和硬件触发。硬件触发主要是来自于定时器,当然也可以选择外部中断的引脚。 

STM32---ADC模数转换详解_第12张图片

STM32---ADC模数转换详解_第13张图片

 这个表就是规则组的触发源,硬件触发来自于定时器还是外部中断的引脚,需要AFIO重映射来决定。最后一个是软件触发控制位,即软件触发。STM32---ADC模数转换详解_第14张图片

第五步,开关控制

ADC_Cmd(ADC1, ENABLE);  //用于给ADC上电

第六步,校准

    ADC_ResetCalibration(ADC1);
    while (ADC_GetResetCalibrationStatus(ADC1) == SET);
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1) == SET);
STM32---ADC模数转换详解_第15张图片

至此,ADC初始化完成,开始获取ADC转换结果:

uint16_t AD_GetValue(void)
{
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
    return ADC_GetConversionValue(ADC1);
}
最后,将ADC的转换结果赋给一个变量。

ADValue = AD_GetValue();

至此,ADC模数转换完成。

完结!

                                                        ADC.C文件代码

#include "stm32f10x.h"                  // Device header

void ADC_Init_ (void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);  //开启ADC1的时钟

	RCC_ADCCLKConfig(RCC_PCLK2_Div6);					 //ADC转换时钟预分频,ADC最大14MHZ(来自RCC预分频而来,可以分为 2分频 4分频 6分频 8分频)
	
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AIN;//模拟输入
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0;	//通道0的GPIO
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_239Cycles5);//ADC1规则组,通道0,序列1,采样时间239.5
	
	ADC_InitTypeDef ADC_InitStruct;
	ADC_InitStruct.ADC_Mode=ADC_Mode_Independent;      //独立ADC模式     选择是单ADC模式还是双ADC模式
	ADC_InitStruct.ADC_DataAlign=ADC_DataAlign_Right;  //右对齐			(ADC数据寄存器)
	ADC_InitStruct.ADC_ContinuousConvMode=DISABLE; 	   //单次模式
	ADC_InitStruct.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;
	ADC_InitStruct.ADC_NbrOfChannel=1;				   //通道数目(在扫描模式下总共会用到多少通道)
	ADC_InitStruct.ADC_ScanConvMode=DISABLE;		   //非扫描模式    (选择是否扫描转换模式)
	ADC_Init(ADC1,&ADC_InitStruct);

	ADC_Cmd(ADC1, ENABLE);		//开启ADC(上电)


	ADC_ResetCalibration(ADC1); //复位校准
    while( ADC_GetResetCalibrationStatus(ADC1)==SET);//判断是否复位校准完成 返回SET则未校准完成,继续等待
	ADC_StartCalibration(ADC1); //开始校准
	while(ADC_GetCalibrationStatus(ADC1)==SET);     //判断是否校准完成 返回SET则未校准完成,继续等待
	
}

uint16_t Get_Value (void)
{
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);             //软件触发 (开始转换)
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==RESET);//判断AEOC标志位是否置一(置一表示转换完成) 转换完成会返回SET跳出循环 (该函数注入组和注入组都会触发EOC标志位)
	/*-------------------------------------------
	具体等待:239.5+12.5=252个周期,72MHZ/6=12MHZ,(1/12MHZ)*252=21us; 
	----------------------------------------------*/
	return  ADC_GetConversionValue(ADC1);              //读取转换数据且自动清除EOC转换结束标志位(自动清楚EOC标志位,不需要手动清除转换结束标志位)
	
}

                                                          ADC.H文件代码

#ifndef __ADC_H__
#define __ADC_H__

void ADC_Init_ (void);
uint16_t Get_Value (void);

#endif 

                                                                main.c

#include "stm32f10x.h"                  // Device header 
#include "Delay.h"
#include "OLED.H"
#include "ADC.H"
char ValueBuff[]={0x00,0x00,0x00,0x00,0x00};//字符型数组 ValueBuff[4]等于 ‘0’
uint16_t  Value,Temp;

int main(void)
{
	OLED_Init();
	ADC_Init_();
	OLED_ShowString(1,1,"0000V");
	while(1)
	{	
		Temp=Get_Value();
		Value=Temp/4095.0*330.0; //Value最好设置为float型变量,不然会截掉小数部分
/*	**
	除法会产生小数(后面加小数点会先转换成双精度浮点型数据进行运算),
	如果没加小数点Temp/4095,Temp<4095时永远都小于1则由于没加小数点unsigned short int数据会截掉小数点后面的数据即一直是0.
*/


		ValueBuff[0]=Value/100%10+'0';
		ValueBuff[1]='.';
		ValueBuff[2]=Value/10%10+'0';
	    ValueBuff[3]=Value/1%10+'0';
		OLED_ShowString(1,1,ValueBuff);
		OLED_ShowNum(2,1,Temp,4);
		OLED_ShowNum(3,1,Value,4);
		Delay_ms(100);
	}
}



你可能感兴趣的:(单片机,stm32,嵌入式硬件)