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 headervoid 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.初始化ADCADC_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);//复位完成,硬件置0ADC_StartCalibration(ADC1);//开始校准,给CR2_CAL置1while(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 headervoid 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.初始化ADCADC_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);//复位完成,硬件置0ADC_StartCalibration(ADC1);//开始校准,给CR2_CAL置1while(ADC_GetCalibrationStatus(ADC1) == SET);//校准完成,硬件置0ADC_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 headervoid 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.初始化ADCADC_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);//复位完成,硬件置0ADC_StartCalibration(ADC1);//开始校准,给CR2_CAL置1while(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 headervoid 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.初始化ADCADC_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);//复位完成,硬件置0ADC_StartCalibration(ADC1);//开始校准,给CR2_CAL置1while(ADC_GetCalibrationStatus(ADC1) == SET);//校准完成,硬件置0ADC_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文件的代码,需要手动修改通道的在软件触发之后,这是不允许的,因为等规则组里面的配置完整后,在给触发信号这样正确。