ADC模数转换器

  • 1、ADC的简介
  • 2、逐次逼近型ADC
  • 3、采样时间和转换时间
  • 4、STM32中ADC模块
  • 5、编程案列
  • 5.1、AD单通道
  • 5.2、AD多通道
  • 1、ADC的简介

    ADC就是一个模数转换器,将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁。说的直观一点就是一个电压表,用于测量电压的片上外设。其中转换电压的范围0~3.3v


    其中这些传感器在有外界的刺激的情况下,将会改变自身电阻的大小(如下图N1电阻),进而改变输出电压的大小。而电压的大小通过模拟输出口AO口输出。单片机可以通过ADC获取外部模块的电压值,进而做出一些动作。

    那么单片机是怎样通过ADC进行对外部电压的测量并转换为数字量保存在内存中喃?

    答案:比较器将ADC的结果寄存器里面的数据(DAC转换)和模拟信号进行比较,
    ①若ADC的结果寄存器里面的数据<模拟信号,则增加ADC的结果寄存器里面的数据。
    ②若ADC的结果寄存器里面的数据>模拟信号,则减小ADC的结果寄存器里面的数据。
    ③若ADC的结果寄存器里面的数据=模拟信号,则将数据保存到DMA中。

    STM32F103C8T6有2个ADC,假如ADC的结果寄存器是2位,那么分辨率是多少喃?如下图

    若结果寄存器是4位喃?是8位喃?

    2、逐次逼近型ADC

    我们先来了解一下ADC的工作原理,如下图所示:

    如图:当闭合采样开关,模拟信号输入,对电容充电。当充满电后,断开采样开关,比较器通过比较来给结果寄存器增加数据/减少数据,直到和模拟信号的值相差到规定的数值以内,然后将数据保存并转换。如下图为模拟信号为2.21v。


    则结果寄存器的数据最后为1010。

    3、采样时间和转换时间

    采样时间就是对电容充满电所需要的时间,而转换时间就是通过比较,确定结果寄存器里面的数值的时间。
    而ADC挂载在APB2总线上,但是ADC的特性规定,ADC的时钟<14MHz。所以APB2时钟出来后还要被分频才能够给ADC通过时钟源。


  • 转换时间 = 12.5的时钟周期。
  • 采样时间 = 电容充电的时间
    电容充电的时间越长,采样的数据就越精确,误差就越小。
    但是ADC的精度本来就是有限的,只要我们采样的误差<1/4*ADC分辨率即可。
    电容充电的时间由电路中的电阻决定的,电阻越大,电流越小,充电时间越长,采样时间越长。

    假设ADC的时钟源频率为14MH在,则
    但是在编程中采样时间是规定了8个挡位的,所以我们只能选择和采样时间相近的那个挡位。挡位>采样时间,数据越精确。挡位<采样时间,数据变化的越快
  • 问:最理想的情况下ADC每砂最多执行多少次转换


    所以:理想情况下1us转换1次数据,则1s转换1000000次。

    4、STM32中ADC模块

    STM32F103C8T6单片机中有2个ADC模块:ADC1、ADC2,结果寄存器都为12位。也就是将3.3v的电压分为了4095份。且每个ADC片上外设都有10外部测量通道和2个内部信号源。我们可以通过外部通道测量外部模块的电压

    这么多的通道是怎样管理的喃?
    答案是:通过规则组(常规序列)和注入组(注入序列)进行管理

    +如上图所示:规则组和注入组就像是一个盒子(用来存放需要测量的物件),ADC就像是一个工人。
    ① 规则组:规则组里面最多能对16个通道采集转换(盒子里面最多能放16个物件等待被测量),而存放采集转换的数据的盒子只有一个。所以只有等第一个的数据被DMA挪走后,第二个数据才能放入盒子里面
    ② 注入组:注入组里面最多能对4个通道采集转换,而又有4个盒子专门存放数据。
    ③触发控制:让ADC开始采集转换的信号(让工人开始测量),如下图所示,有定时器启动和软件启动。而软件启动就是就是给寄存器置1

    规则组的转换模式:
    ①单次转换非扫描模式

    在此模式下,规则组只有第一个盒子里面的通道才有效(即序列1),我们可以在序列1的盒子里面放入我们需要转换的通道,然后给规则组一个触发信号,开始采集转换,转换完成后数据保存在数据寄存器里面,同时给ECO置1。需要进行第二次转换,那么需要在启动一次触发信号,开启转换。

    ②连续转换非扫描模式

    和单次转换非扫描模式不同的是,给规则组一个触发信号,在第一次转换完成后立马进入第二次转换,不断的进行下去。单片机需要获取数据,只需要读取数据寄存器里面的数据即可

    ③单次转换扫描模式


    在规则组里面的多个盒子里面放入我们需要转换的通道,然后告诉ADC有多少个盒子,转换完成后依次存放在数据寄存器里面。ECO置1,需要第二次转换则需要启动触发信号。

    ④连续转换扫描模式

    数据对齐:
    ADC转换后的数据是12位的,而数据寄存器是16位的,所以数据寄存器有4个空出来的位置补零。一般的情况下使用右对齐即可

    数据校准
    校准都是固定的,只需要在初始化后加上校准代码即可,不用理解。

    5、编程案列

    与之相关的标准库编程接口:

    5.1、AD单通道

    ADC.c文件的代码如下;

    /*
    	ADC单通道实验,通过通道1(PA0)对自身输出电压进行采集,通过电位器改变电压变化,最终在OLED上面显示出来
    */
    #include "stm32f10x.h"                  // Device header
    
    void AD_Init(void)
    {
    	//1.开启时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
    	
    	//2.对ADC时钟进行分频
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72MHZ/6 = 12MHz
    	
    	//3.对通道1(PA0)进行配置
    	GPIO_InitTypeDef GPIOInitStruct;
    	GPIOInitStruct.GPIO_Mode = GPIO_Mode_AIN;//模拟输入模式,ADC的专属模式
    	GPIOInitStruct.GPIO_Pin = GPIO_Pin_0;
    
    	GPIO_Init(GPIOA,&GPIOInitStruct);
    	
    	//4.对ADC规则组进行配置
    	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//ADC几,通道,序列,采样时间
    	
    	//5.初始化ADC
    	ADC_InitTypeDef ADC_InitStruct;
    	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;//工作模式:独立模式/双ADC模式,这里的独立模式
    	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//ADC触发选择,选择软件触发
    	ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;//连续/单次模式,这里选择单次
    	ADC_InitStruct.ADC_ScanConvMode = DISABLE;//扫描/非扫描,这里选择非扫描
    	ADC_InitStruct.ADC_NbrOfChannel = 1;//在扫描模式下的盒子数目
    	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐,右对齐
    	
    	ADC_Init(ADC1,&ADC_InitStruct);
    	
    	//6.开启ADC电源
    	ADC_Cmd(ADC1,ENABLE);
    	
    	//7.校准
    	ADC_ResetCalibration(ADC1);//将校准复位,给CR2_RSTCAL置1,进行复位
    	while(ADC_GetResetCalibrationStatus(ADC1) == SET);//复位完成,硬件置0
    	ADC_StartCalibration(ADC1);//开始校准,给CR2_CAL置1
    	while(ADC_GetCalibrationStatus(ADC1) == SET);//校准完成,硬件置0
    	
    }
    
    uint16_t AD_GetValue(void)//获取数据寄存器里面的数据
    {
    	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发
    	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待ECO置1,代表转换完成
    	return ADC_GetConversionValue(ADC1);//获取ADCx->DR里面的数据,ECO自动清除
    }
    

    【注】如果使用连续非扫描模式那么ADC.c文件的代码如下:

    #include "stm32f10x.h"                  // Device header
    
    void AD_Init(void)
    {
    	//1.开启时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
    	
    	//2.对ADC时钟进行分频
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72MHZ/6 = 12MHz
    	
    	//3.对通道1(PA0)进行配置
    	GPIO_InitTypeDef GPIOInitStruct;
    	GPIOInitStruct.GPIO_Mode = GPIO_Mode_AIN;//模拟输入模式,ADC的专属模式
    	GPIOInitStruct.GPIO_Pin = GPIO_Pin_0;
    
    	GPIO_Init(GPIOA,&GPIOInitStruct);
    	
    	//4.对ADC规则组进行配置
    	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//ADC几,通道,序列,采样时间
    	
    	//5.初始化ADC
    	ADC_InitTypeDef ADC_InitStruct;
    	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;//工作模式:独立模式/双ADC模式,这里的独立模式
    	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//ADC触发选择,选择软件触发
    	ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;//连续/单次模式,这里选择单次
    	ADC_InitStruct.ADC_ScanConvMode = DISABLE;//扫描/非扫描,这里选择非扫描
    	ADC_InitStruct.ADC_NbrOfChannel = 1;//在扫描模式下的盒子数目
    	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐,右对齐
    	
    	ADC_Init(ADC1,&ADC_InitStruct);
    	
    	//6.开启ADC电源
    	ADC_Cmd(ADC1,ENABLE);
    	
    	//7.校准
    	ADC_ResetCalibration(ADC1);//将校准复位,给CR2_RSTCAL置1,进行复位
    	while(ADC_GetResetCalibrationStatus(ADC1) == SET);//复位完成,硬件置0
    	ADC_StartCalibration(ADC1);//开始校准,给CR2_CAL置1
    	while(ADC_GetCalibrationStatus(ADC1) == SET);//校准完成,硬件置0
    	
    	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发
    	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待ECO置1,代表转换完成
    	
    }
    
    uint16_t AD_GetValue(void)//获取数据寄存器里面的数据
    {
    //	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发
    //	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待ECO置1,代表转换完成
    	return ADC_GetConversionValue(ADC1);//获取ADCx->DR里面的数据,ECO自动清除
    }
    

    主程序文件的代码如下:

    /*
    	ADC单通道的简单使用
    */
    
    #include "stm32f10x.h"                 
    #include "OLED.h"
    #include "ADC.h"
    #include "Delay.h"
    
    uint16_t Value;
    float Voltage;				//定义电压变量
    
    int main(void)
    {
    	AD_Init();
    	OLED_Init();
    	OLED_Clear();
    	OLED_ShowString(1,1,"Value:");
    	OLED_ShowString(2,1,"Voltage:0.00V");
    	while(1)
    	{
    		Value = AD_GetValue();//获取数据
    		Voltage = (Value * 3.3) / 4095;//转换为电压值
    		
    		OLED_ShowNum(1,7,Value,4);//显示数据
    		OLED_ShowNum(2,9,Voltage,1);//显示电压值的整数部分
    		OLED_ShowNum(2,11,(uint16_t)(Voltage*100) % 100,2);//显示电压值的小数部分
    		Delay_ms(100);
    	}
    }
    

    5.2、AD多通道

    我们还没有学习DMA,使用如果不使用DMA对多通道转换的数据进行挪动的话,那么会出现数据的覆盖。我们通过单次转换非扫描模式模拟单次扫描模式。就是在第一次转换完成后,将第一序列的盒子里面的通道通过手动修改。
    ADC.c文件的代码如下:

    #include "stm32f10x.h"                  // Device header
    
    void AD_Init(void)
    {
    	//1.开启时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
    	
    	//2.对ADC时钟进行分频
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72MHZ/6 = 12MHz
    	
    	//3.对通道1(PA0)进行配置
    	GPIO_InitTypeDef GPIOInitStruct;
    	GPIOInitStruct.GPIO_Mode = GPIO_Mode_AIN;//模拟输入模式,ADC的专属模式
    	GPIOInitStruct.GPIO_Pin = GPIO_Pin_0 |GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
    
    	GPIO_Init(GPIOA,&GPIOInitStruct);
    	
    	//4.对ADC规则组进行配置
    //	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//ADC几,通道,序列,采样时间
    	
    	//5.初始化ADC
    	ADC_InitTypeDef ADC_InitStruct;
    	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;//工作模式:独立模式/双ADC模式,这里的独立模式
    	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//ADC触发选择,选择软件触发
    	ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;//连续/单次模式,这里选择单次
    	ADC_InitStruct.ADC_ScanConvMode = DISABLE;//扫描/非扫描,这里选择非扫描
    	ADC_InitStruct.ADC_NbrOfChannel = 1;//在扫描模式下的盒子数目
    	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐,右对齐
    	
    	ADC_Init(ADC1,&ADC_InitStruct);
    	
    	//6.开启ADC电源
    	ADC_Cmd(ADC1,ENABLE);
    	
    	//7.校准
    	ADC_ResetCalibration(ADC1);//将校准复位,给CR2_RSTCAL置1,进行复位
    	while(ADC_GetResetCalibrationStatus(ADC1) == SET);//复位完成,硬件置0
    	ADC_StartCalibration(ADC1);//开始校准,给CR2_CAL置1
    	while(ADC_GetCalibrationStatus(ADC1) == SET);//校准完成,硬件置0
    }
    
    uint16_t AD_GetValue(uint8_t ADC_Channel)//获取数据寄存器里面的数据,指定通道
    {
    	ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);//ADC几,通道,序列,采样时间
    	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发
    	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待ECO置1,代表转换完成
    	return ADC_GetConversionValue(ADC1);//获取ADCx->DR里面的数据,ECO自动清除
    }
    

    主程序文件的代码如下:

    /*
    	ADC单通道的简单使用
    */
    
    #include "stm32f10x.h"                 
    #include "OLED.h"
    #include "ADC.h"
    #include "Delay.h"
    
    uint16_t Value1,Value2,Value3,Value4;
    
    int main(void)
    {
    	AD_Init();
    	OLED_Init();
    	OLED_Clear();
    	
    	OLED_ShowString(1,1,"Value1:");
    	OLED_ShowString(2,1,"Value2:");
    	OLED_ShowString(3,1,"Value3:");
    	OLED_ShowString(4,1,"Value4:");
    	
    	while(1)
    	{
    		Value1 = AD_GetValue(ADC_Channel_0);//获取通道1数据,手动修改第一序列的通道
    		Value2 = AD_GetValue(ADC_Channel_1);//获取通道2数据
    		Value3 = AD_GetValue(ADC_Channel_2);//获取通道3数据
    		Value4 = AD_GetValue(ADC_Channel_3);//获取通道4数据
    		
    		OLED_ShowNum(1,8,Value1,4);
    		OLED_ShowNum(2,8,Value2,4);
    		OLED_ShowNum(3,8,Value3,4);
    		OLED_ShowNum(4,8,Value4,4);
    
    		Delay_ms(100);
    	}
    }
    

    【注】此方法不能在连续非扫描情况下模拟连续扫描模式。
    因为:连续非扫描模式下,只需要一次的触发信号,如果模拟连续扫描模式,则需要手动修改通道,而修改通道后没有触发信号来进行触发。

    #include "stm32f10x.h"                  // Device header
    
    void AD_Init(void)
    {
    	//1.开启时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
    	
    	//2.对ADC时钟进行分频
    	RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72MHZ/6 = 12MHz
    	
    	//3.对通道1(PA0)进行配置
    	GPIO_InitTypeDef GPIOInitStruct;
    	GPIOInitStruct.GPIO_Mode = GPIO_Mode_AIN;//模拟输入模式,ADC的专属模式
    	GPIOInitStruct.GPIO_Pin = GPIO_Pin_0 |GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
    
    	GPIO_Init(GPIOA,&GPIOInitStruct);
    	
    	//4.对ADC规则组进行配置
    //	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//ADC几,通道,序列,采样时间
    	
    	//5.初始化ADC
    	ADC_InitTypeDef ADC_InitStruct;
    	ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;//工作模式:独立模式/双ADC模式,这里的独立模式
    	ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//ADC触发选择,选择软件触发
    	ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;//连续/单次模式,这里选择单次
    	ADC_InitStruct.ADC_ScanConvMode = DISABLE;//扫描/非扫描,这里选择非扫描
    	ADC_InitStruct.ADC_NbrOfChannel = 1;//在扫描模式下的盒子数目
    	ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐,右对齐
    	
    	ADC_Init(ADC1,&ADC_InitStruct);
    	
    	//6.开启ADC电源
    	ADC_Cmd(ADC1,ENABLE);
    	
    	//7.校准
    	ADC_ResetCalibration(ADC1);//将校准复位,给CR2_RSTCAL置1,进行复位
    	while(ADC_GetResetCalibrationStatus(ADC1) == SET);//复位完成,硬件置0
    	ADC_StartCalibration(ADC1);//开始校准,给CR2_CAL置1
    	while(ADC_GetCalibrationStatus(ADC1) == SET);//校准完成,硬件置0
    	
    	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发
    	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待ECO置1,代表转换完成
    }
    
    uint16_t AD_GetValue(uint8_t ADC_Channel)//获取数据寄存器里面的数据
    {
    	ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);//ADC几,通道,序列,采样时间
    //	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发
    //	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//等待ECO置1,代表转换完成
    	return ADC_GetConversionValue(ADC1);//获取ADCx->DR里面的数据,ECO自动清除
    }
    

    上面的代码为连续非扫描情况下模拟连续扫描模式的ADC.c文件的代码,需要手动修改通道的在软件触发之后,这是不允许的,因为等规则组里面的配置完整后,在给触发信号这样正确。

    作者:浅陌pa

    物联沃分享整理
    物联沃-IOTWORD物联网 » ADC模数转换器详解

    发表回复