STM32之ADC(获取某个端口电压并显示)


文章目录

  • 一、ADC简介
  • 1、逐次逼近型ADC
  • 2、ADC模块框图
  • 3、ADC基本结构
  • 4、转换模式
  • 5、触发控制
  • 6、数据对齐
  • 7、通道采样时间
  • 8、校准
  • 二、代码
  • 1、一些函数
  • 2、ADC初始化
  • 3、实验获取PA1的电压并显示
  • 一、ADC简介

    ADC (Analog-Digital Converter) 模拟-数字转换器
    ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。12位
    逐次逼近型ADC**,1us转换时间
    输入电压范围∶0-3.3V,转换结果范围∶0~4095。18个输入通道,可测量16个外部和2个内部信号源规则组和注入组两个转换单元
    模拟看门狗自动监测输入电压范围**

    12位ADC的值,量化的范围就是0 – 2^12-1 ,即0-4095。位数越高。量化结果就越精细。对应分辨率就越高。
    关于这个模拟看门狗,我们可以设定阈值。当AD值高于它设定的上阈值或者低于下阈值时,它就会申请中断,你就可以在中断函数里执行相应的操作。

    1、逐次逼近型ADC

    STM32的ADC的原理和这个一样,这个是ADC0809的内部结构

    首先进行通道开关的选择。在左方有个通道选择开关。选择的通道的数字是存在下方的ADDA,ADDB,ADDC中。你想选择IN0-IN7中的哪一个通道,就把那个通道号放在这三个里面。ALE是锁存信号,给ALE置位,上面这里对应的通路开关就可以自动拨好了。

    然后来到了比较器的上面一根线。
    在比较器中用逐次逼近的方法来一一比较
    比较器的作用就是判断两个输入信号电压的大小关系。
    比较器的上面一根线是一个外部通道输入的,未知编码的电压。
    比较器的下面一根线是一个DAC输出的,已知编码的电压。
    如果DAC输出的电压比较小。我就增大DAC数据。直到DAC输出的电压和外部通道输入的电压近视相等。这样DAC输入的数据就是外部电压的编码数据了。(这里就是输入到8位三态锁存缓冲器)

    而这个增大或减小去调整DAC的数据的过程,是那个逐次逼近寄存器SAR在起作用。
    它通常会使用二分法进行查找,比如在这里是8位的ADC,就是0-255。第一次比较的时候,我们就给DAC输入255的一半进行比较。第二次比较的时候。再就给128的一半64。就这么继续往下比较。而且128,64,32正好是二进制每一位的位权。也就是说,这个比较的过程,就是对二进制从高位到低位依次判断是1是0的过程。那对于8位的ADC,从高位到低依次判断8次就能找到未知电压的编码。12位的就是判断12次。

    EOC是End Of Gonvert转换结束信号
    START是开始转换。给一个输入脉冲,开始转换。
    CLOCK是ADC时钟。

    下面。VRVF+和VREF-是DAC的参考电压。
    比如你给一个数据255。是对应5V还是3.3V呢,就由这个参考电压决定。

    这个DAC的参考电压也决定了ADC的输入范围,所以它也是ADC参考电压。
    通常参考电压的正极和VCC是一样的,会接一起。参考电的负极和GND也是一样的。也接在一起。

    2、ADC模块框图


    3、ADC基本结构


    左边是输入通道,16个GPO口,外加两个内部的通道。
    然后进入AD转换器
    AD转换器里有两个组,一个是规则组,一个是注入组
    规则组最多可以选中16个通道。注入组最多可以选择4个通道。
    然后转换的结果可以存放在AD数据寄存器里,规则组有1个数据寄存器,注入组有4个数据寄存器。
    然后下面这里有触发控制。提供了开始转换的START信号。
    触发控制可以选择硬件触发和软件触发,硬件触发主要来自于定时器和外部中断触发。
    右边这里是来自RCC的ADC时钟CLOCK,ADC逐次比较的过程就是由这个时钟推动的
    有三个方式可以申请中断,首先规则组和注入组转换完成会有EOC信号,会置一个标志位。其次是看门狗,如果超过阈值,就通过中断输出控制,像NVIC申请中断。

    4、转换模式


    规则组

    触发—进入第一个序列位置(选中1个)—- 然后就转换完毕了
    此时生成一个EOC的信号
    如果要转换就要再次触发


    触发—进入第一个序列位置(选中1个)—- 此次转换完毕 —–生成EOC —– 不需要触发继续重复上述过程


    这个模式也是触发一次用一次
    但是一次有好几个序列
    所以要给一个通道数目,就是有几个序列能用,有几个就从序列1开始往下排几个
    每次触发后,就依次对这前7个(通道数目)进行AD转换,且为了防止数据覆盖,要及时用DMA挪走数据
    那7个通道转换完成之后,产生EOC信号,转换结束。


    就是上述过程,触发了就一直一直转

    5、触发控制


    6、数据对齐


    一般情况下都是右对齐,前面加0。
    也有的情况是左对齐

    7、通道采样时间


    8、校准

    1、ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差。
    2、 建议在每次上电后执行一次校准。
    3、 启动校准前,ADC必须处于关电状态超过至少两个ADC时钟周期。

    二、代码

    我使用的板子是正点原子的迷你板
    STM32的型号是 STM32F103RCT
    有三个ADC


    我们在这里使用ADC1的通道1

    1、一些函数





    ADC规则组通道配置

    void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
    

    是否允许外部触发转换

    void ADC_ExternalTrigInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
    

    获取转换值

    uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
    

    双ADC模式读取转换结果

    uint32_t ADC_GetDualModeConversionValue(void);
    

    2、ADC初始化

    #include "stm32f10x.h"   
    
    void Adc_Init()
    {
    	GPIO_InitTypeDef GPIO_InitStructure;
    	ADC_InitTypeDef ADC_InitStructure; 
    	
    	//1、开启时钟ADC1的时钟和GPIOA的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE );
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE );
    	//2、配置分频,因为最大不能超过14MHz 72/6=12
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
    	//3、配置GPIOA和ADC结构体 PA1 作为模拟通道输入引脚                         
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚
    	GPIO_Init(GPIOA, &GPIO_InitStructure);	
    	
    	ADC_DeInit(ADC1); //复位ADC1
    	
    	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//ADC工作模式:ADC1和ADC2工作在独立模式
    	ADC_InitStructure.ADC_ScanConvMode = DISABLE;	//模数转换工作在单通道模式
    	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;	//模数转换工作在单次转换模式
    	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//转换由软件而不是外部触发启动
    	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
    	ADC_InitStructure.ADC_NbrOfChannel = 1;	//顺序进行规则转换的ADC通道的数目
    	ADC_Init(ADC1, &ADC_InitStructure);	//根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   
    	//4、使能ADC1
    	ADC_Cmd(ADC1, ENABLE);
    	//5、校准并等待校准结束
    	ADC_ResetCalibration(ADC1);	//使能复位校准  
    	 
    	while(ADC_GetResetCalibrationStatus(ADC1));	//等待复位校准结束
    	
    	ADC_StartCalibration(ADC1);	 //开启AD校准
     
    	while(ADC_GetCalibrationStatus(ADC1));	 //等待校准结束
    	
    }
    

    关于分频

    3、实验获取PA1的电压并显示

    首先PA1是ADC通道1
    所以这个函数中的ADC_Channel要选择如图所示

    void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime)
    

    adc.c

    #include "stm32f10x.h"   
    
    void Adc_Init()
    {
    	GPIO_InitTypeDef GPIO_InitStructure;
    	ADC_InitTypeDef ADC_InitStructure; 
    	
    	//1、开启时钟ADC1的时钟和GPIOA的时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE );
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE );
    	//2、配置分频,因为最大不能超过14MHz 72/6=12
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
    	//3、配置GPIOA和ADC结构体 PA1 作为模拟通道输入引脚                         
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;		//模拟输入引脚
    	GPIO_Init(GPIOA, &GPIO_InitStructure);	
    	
    	ADC_DeInit(ADC1); //复位ADC1
    	
    	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;	//ADC工作模式:ADC1和ADC2工作在独立模式
    	ADC_InitStructure.ADC_ScanConvMode = DISABLE;	//模数转换工作在单通道模式
    	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;	//模数转换工作在单次转换模式
    	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;	//转换由软件而不是外部触发启动
    	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;	//ADC数据右对齐
    	ADC_InitStructure.ADC_NbrOfChannel = 1;	//顺序进行规则转换的ADC通道的数目
    	ADC_Init(ADC1, &ADC_InitStructure);	//根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   
    	//4、使能ADC1
    	ADC_Cmd(ADC1, ENABLE);
    	//5、校准并等待校准结束
    	ADC_ResetCalibration(ADC1);	//使能复位校准  
    	 
    	while(ADC_GetResetCalibrationStatus(ADC1));	//等待复位校准结束
    	
    	ADC_StartCalibration(ADC1);	 //开启AD校准
     
    	while(ADC_GetCalibrationStatus(ADC1));	 //等待校准结束
    	
    }
    
    uint16_t AD_GetValue(u8 ch)
    {
    		//设置指定ADC的规则组通道,一个序列,采样时间
    	ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );	//ADC1,ADC通道,采样时间为239.5周期	  		
    	 
    	ADC_SoftwareStartConvCmd(ADC1, ENABLE);		//使能指定的ADC1的软件转换启动功能	
    	 
    	while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
    
    	return ADC_GetConversionValue(ADC1);	//返回最近一次ADC1规则组的转换结果
    }
    
    //多获取几次数据求平均值更稳定
    uint16_t Get_Adc_Average(u8 ch,u8 times)
    {
    	u32 temp_val=0;
    	u8 t;
    	for(t=0;t<times;t++)
    	{
    		temp_val+=AD_GetValue(ch);
    		delay_ms(5);
    	}
    	return temp_val/times;
    } 	 
    
    
    

    main.c

    #include "stm32f10x.h"
    
    #include "delay.h"
    #include "ADC.h"
    #include "lcd.h"
    
    	
     int main(void)
     {	
    	float temp;
    	u16 adcx;
    	delay_init();	    	 //延时函数初始化	  
    	LCD_Init();
    	Adc_Init(); 
    	 	LCD_ShowString(10,70,200,16,16,"AD_GetValue");	
    		LCD_ShowString(10,100,200,16,16,"AD_Average");	
    		LCD_ShowString(10,150,200,16,16,"ADC_CH1_VOL:0.000V");
      while(1)
    	{
    		LCD_ShowxNum(110,70,AD_GetValue(ADC_Channel_1),4,16,0);
    		LCD_ShowxNum(110,100,Get_Adc_Average(ADC_Channel_1,10),4,16,0);
    		
    		adcx=Get_Adc_Average(ADC_Channel_1,10);
    		temp=(float)adcx*(3.3/4096);
    		adcx=temp;
    		LCD_ShowxNum(106,150,adcx,1,16,0);//显示整数
    		temp-=adcx;
    		temp*=1000;
    		LCD_ShowxNum(122,150,temp,3,16,0X80); //显示小数		
    	}
     }
    

    接3.3v和接GND以及不接是浮空,电平不稳定一直变


    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32之ADC(获取某个端口电压并显示)

    发表评论