目录
ADC简介
ADC(Analog-Digital Converter)模拟-数字转换器(AD转换器)
ADC可以将引脚上连续变化的模拟电压转换位内存中存储的数字变量,建立模拟电路到数字电路的桥梁
1、STM32主要是数字电路,数字电路只有高低电平,没有几V电压的概念,所以若想读取电压值,就需要借助ADC模数转换器来实现了。ADC读取引脚上的模拟电压,转换为一个数据,存在寄存器里,再把这个数据读取到变量里来,就可以进行显示、判断、纪记录等等操作。ADC可以将模拟信号转换为数字信号,是模拟电路到数字电路的桥梁。2、DAC,数字-模拟转换器,使用DAC就可以将数字变量转化为模拟电压。
3、PWM也是一个数字到模拟的桥梁,使用PWM来控制LED的亮度,电机的速度,这就是DAC的功能,同时PWM只有完全导通和完全断开两种状态,在这两种状态上都没有功率损耗,所以在直流电机调速这种大功率的引用场景,使用PWM来等效模拟量,是比DAC更好的选择。并且PWM电路更加简单更加常用,所以可以看出PWM还是挤占了DAC的很多应用空间。目前DAC的应用场景主要是在波形生成这些领域,比如信号发生器,音频解码芯片等。
12位逐次逼近型ADC,1us转换时间
1、逐次逼近型这是ADC的工作模式
2、ADC的两个关键参数,第一个分辨率,一般用多少位来表示,12位AD值,它的表示范围就是0~2^12-1,就是量化结果的范围是0~4095,位数越高,量化结果越精细,对应分辨率就越高。
3、第二个转换时间,就是转换频率,AD转换是需要花一小段时间的,这里的1us就是表示从AD转换开始,到产生结果,需要花1us的时间,对应AD转换的频率就是1MHz,这个就是STM32 ADC的最快转换频率。若是需要转换一个频率非常高的信号,就要考虑这个转换频率是不是够用。如果信号频率比较低,这个最大1MHz的转换频率也够用了。
输入电压范围:0~3.3V,转换结果范围:0~4095
ADC的输入电压,一般要求都是要在芯片供电的负极和正极之间变化的,最低电压就是负极0V,最高电压是正极3.3V,经过ADC转换之后,最小值就是0,最大值是4095.0V对应0,3.3V对应4095,中间都是一一对应的线性关系
18个输入通道,可1测量16个外部和2个内部信号源
1、外部信号源就是16个GPIO口,在引脚上直接接模拟信号就行,不需要任何额外的电路,引脚就直接能测电压。
2、两个内部信号源是内部温度传感器和内部参考电压,温度传感器可以测量CPU的温度,内部参考电压是一个1.2V左右的基准电压,这个基准电压是不随外部供电电压变化而变化的,所以如果芯片的供电不是标准的3.3V,测量外部引脚的电压可能就不对,这时就可以读取这个基准电压进行校准,这样就能得到正确的电压值。
规则组和注入组两个转换单元
STM32 ADC的增强功能,普通的AD转换流程是,启动一次转换,读一次值,然后再启动,再读值,这样的流程。STM32的ADC 就比较高级,可以列一个组,一次性启动一个组,连续转换多个值,并且有两个组,一个是用于常规使用的规则组,一个是用于突发事件的注入组。
模拟看门狗自动检测输入电压范围
这个ADC,一般可以用于测量光线强度,温度这些值,并且经常会有个需求,就是如果光线高于某个阈值,低于某个阈值,或者温度高于某个阈值、低于某个阈值时,执行一些操作,高于某个阈值、低于某个阈值的判断,就可以用模拟看门狗来自动执行,模拟看门狗,可以检测指定的某些通道,当AD值高于它设定的上阈值或者低于下阈值时,就会申请中断,就可以在中断函数里执行相应的操作。
STM32F103C8T6 ADC资源:ADC1、ADC2、10个外部输入通道,它最多只能测量10个外部引脚的模拟信号
AD转换的步骤:采样,保持,量化,编码
STM32 ADC的总转换时间为
Tconv=采样时间 + 12.5个ADC周期
例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期
Tconv=1.5+12.5=14个ADC周期=1us
校准:
ADC有一个内置自校准模式,校准可大幅减小因内部电容器组的变化而造成的准精度误差,校准期间,在每个电容器上都会计算出以恶搞误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差。
建议在每次上电后执行一次校准
启动校准前,ADC必须处于关电状态超过至少两个ADC时钟周期
AD单通道
实验现象
将电位器(滑动变阻器)往左拧,AD值减小,电压值也减小
AD值最小是0,对应的电压就是0V。往右宁,AD值变大,对应电压值也变大。
STM32的ADC是12位的,所以AD结果最大值是4095,也就是2的12次方减1,对应的电压是3.3V对GPIO来说,它只能读取引脚的高低电平,要么是高电平,要么是低电平,只有两个值,使用ADC之后,就可以对这个高电平和低电平之间的任意电压进行量化,最终用一个变量来表示,读取这个变量,就可以知道引脚的具体电压到底是多少。所以ADC就是一个电压表,把引脚的电压值测出来,放在一个变量里,这就是ADC的作用。
步骤
第一步:开始RCC时钟,包括ADC和GPIO的时钟,另外ADCCLK的分频器,也需要配置一下。
第二步:配置GPIO,把需要用到的GPIO配置成模拟输入模式
第三步:配置这里的多路开关,把左边的通道接入到右边的规则组列表里
第四步:配置ADC转换器(使用结构体来配置)
第五步:开关控制,调用ADC_Cmd函数,开启ADC
函数说明
RCC_ADCCLKConfig();
这个函数是用来配置ADCCLK分频器的,它可以对APB2的72MHz时钟选择2,4,6,8分频,输入到ADCCLK,在RCC库函数里面ADC_Init();
初始化ADC_Cmd();
用于给ADC上电
用于控制校准的函数,在ADC初始化完成之后,依次调用就行
ADC_ResetCalibration();
复位校准ADC_GetResetCalibrationStatus();
获取复位校准状态ADC_StartCalibration();
开始校准ADC_GetCalibrationStatus();
获取开始校准状态
ADC_SoftwareStartConvCmd();
ADC获取软件开始转换状态,用于软件触发的函数ADC_GetFlagStatus();
获取标志位状态,参数给EOC的标志位,判断标志位EOC标志位是不是置1了,如果转换结束,EOC标志位置1,然后调用这个函数,判断标志位,判断转换是否结束的方法ADC_RegularChannelConfig();
ADC规则组通道配置ADC_ExternalTrigConvCmd();
ADC外部触发转换控制,就是是否允许外部触发转换ADC_GetConversionValue();
ADC获取转换值,获取AD转换的数据寄存器,读取转换结果就要使用这个函数
AD.c文件
#include "stm32f10x.h" // Device header
//AD初始化
void AD_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//开启ADC1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIOA的时钟
/*设置ADC时钟*/
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; //模拟输入
/*
在AIN模式下,GPIO口是无效的,断开GPIO,防止GPIO口的输入输出对模拟电压照成干扰
AIN模式可以说是ADC的专属模式
*/
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);//将PA0引脚初始化为模拟输入
/*规则组通道配置*/
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//规则组序列1的位置,配置为通道0
/*ADC初始化*/
ADC_InitTypeDef ADC_InitStructure; //定义结构体变量
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent; //模式,选择独立模式,即单独使用ADC1
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right; //数据对齐,选择右对齐
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; //外部触发,使用软件触发,不需要外部触发
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE; //连续转换,失能,每转换一次规则组序列后停止
ADC_InitStructure.ADC_ScanConvMode=DISABLE; //扫描模式,失能,只转换规则组的序列1这一个位置
ADC_InitStructure.ADC_NbrOfChannel= 1; //通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
ADC_Init(ADC1,&ADC_InitStructure); //将结构体变量交给ADC_Init,配置ADC1
/*ADC使能*/
ADC_Cmd(ADC1,ENABLE); //使能ADC1,ADC开始运行
/*ADC校准*/
ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
}
//获取AD转换的值,范围0~4095
uint16_t AD_GetValue(void)
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE); //软件触发AD转换一次
while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)== RESET ); //等待EOC标志位,即等待AD转换结束
return ADC_GetConversionValue(ADC1); //读数据寄存器,得到AD转换的结果
}
AD.h
#ifndef _AD_H
#define _AD_H
void AD_Init(void);
uint16_t AD_GetValue(void);
#endif
main.c
#include "stm32f10x.h" // Device header
#include "delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t ADValue; //定义AD值变量
float Voltage; //定义电压变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
AD_Init(); //AD初始化
/*显示静态字符串*/
OLED_ShowString(1,1,"ADValue:");
OLED_ShowString(2,1,"Voltage:0.00V");
while(1)
{
ADValue = AD_GetValue(); //获取AD转换的值
Voltage = (float)ADValue /4095 * 3.3; //将AD值线性变换到0~3.3的范围,表示电压
OLED_ShowNum(1,9,ADValue,4); //显示AD值
OLED_ShowNum(2,9,Voltage,1); //显示电压值的整数部分
OLED_ShowNum(2,11,(uint16_t)(Voltage * 100) % 100,2); //显示电压值的小数部分
Delay_ms(100); //延时100ms,手动增加一些转换的间隔时间
}
}
AD多通道
再外接三个模块,光敏电阻,热敏电阻和反射红外模块,将他们的AO、模拟电压输出端,分别接在了A1、A2、A3引脚,加上电位器,总共4个输出通道,测出的4个AD数据分别显示在屏幕上。
第一个电位器AD0,往左拧变小,往右拧增大。
第二个光敏电阻AD1,遮挡光敏电阻,光线减小,AD值增大,移开,光线增大,AD减小。
第三个热敏电阻AD2,用手热一下热敏电阻,温度升高,AD值减小,移开,温度降低,AD值增大
第四个反射红外传感器AD3,手靠近,有反光,AD值减小,移开,没有反光,AD值增大
AD.c
#include "stm32f10x.h" // Device header
//AD初始化
void AD_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); //开启ADC1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
/*设置ADC时钟*/
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; //模拟输入
/*
在AIN模式下,GPIO口是无效的,断开GPIO,防止GPIO口的输入输出对模拟电压照成干扰
AIN模式可以说是ADC的专属模式
*/
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0 |GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure); //将PA0、PA1、PA2和PA3引脚初始化为模拟输入
/*不在此处配置规则组序列,而是在每次AD转换前配置,这样可以灵活更改AD转换的通道*/
//ADC初始化
ADC_InitTypeDef ADC_InitStructure; //定义结构体变量
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent; //模式,选择独立模式,即单独使用ADC1
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right; //数据对齐,选择右对齐
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; //外部触发,使用软件触发,不需要外部触发
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE; //连续转换,失能,每转换一次规则组序列后停止
ADC_InitStructure.ADC_ScanConvMode=DISABLE; //扫描模式,失能,只转换规则组的序列1这一个位置
ADC_InitStructure.ADC_NbrOfChannel= 1; //通道数,为1,仅在扫描模式下,才需要指定大于1的数,在非扫描模式下,只能是1
ADC_Init(ADC1,&ADC_InitStructure); //将结构体变量交给ADC_Init,配置ADC1
/*ADC使能*/
ADC_Cmd(ADC1,ENABLE); //使能ADC1,ADC开始运行
/*ADC校准*/
ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
}
/**
* 函 数:获取AD转换的值
* 参 数:ADC_Channel 指定AD转换的通道,范围:ADC_Channel_x,其中x可以是0/1/2/3
* 返 回 值:AD转换的值,范围:0~4095
*/
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);//在每次转换前,根据函数形参灵活更改规则组的通道1
ADC_SoftwareStartConvCmd(ADC1,ENABLE); //软件触发AD转换一次
while (ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)== RESET ); //等待EOC标志位,即等待AD转换结束
return ADC_GetConversionValue(ADC1); //读数据寄存器,得到AD转换的结果
}
AD.h
#ifndef _AD_H
#define _AD_H
void AD_Init(void);
uint16_t AD_GetValue(uint8_t ADC_Channel);
#endif
main.c
#include "stm32f10x.h" // Device header
#include "delay.h"
#include "OLED.h"
#include "AD.h"
uint16_t AD0,AD1,AD2,AD3; //定义AD值变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
AD_Init(); //AD初始化
/*显示静态字符串*/
OLED_ShowString(1,1,"AD0:");
OLED_ShowString(2,1,"AD1:");
OLED_ShowString(3,1,"AD2:");
OLED_ShowString(4,1,"AD3:");
while(1)
{
AD0 = AD_GetValue(ADC_Channel_0); //单次启动ADC,转换通道0
AD1 = AD_GetValue(ADC_Channel_1); //单次启动ADC,转换通道1
AD2 = AD_GetValue(ADC_Channel_2); //单次启动ADC,转换通道2
AD3 = AD_GetValue(ADC_Channel_3); //单次启动ADC,转换通道3
OLED_ShowNum(1,5,AD0,4); //显示通道0的转换结果AD0
OLED_ShowNum(2,5,AD1,4); //显示通道1的转换结果AD1
OLED_ShowNum(3,5,AD2,4); //显示通道2的转换结果AD2
OLED_ShowNum(4,5,AD3,4); //显示通道3的转换结果AD3
Delay_ms(100); //延时100ms,手动增加一些转换的间隔时间
}
}