职业技术学院网站建设项目,建设银行网站个人客户,举报网站平台,东莞松山湖中心医院简介 • ADC #xff08; Analog-Digital Converter #xff09;模拟 - 数字转换器 • ADC 可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量#xff0c;建立模拟电路到数字电路的桥梁 • 12 位逐次逼近型 ADC #xff0c; 1us 转换时间 #xff08;12位:分辨率…简介
• ADC Analog-Digital Converter 模拟 - 数字转换器 • ADC 可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量建立模拟电路到数字电路的桥梁 • 12 位逐次逼近型 ADC 1us 转换时间 12位:分辨率12位AD值表示范围就是0~2^12-1即量化结果的范围是0~4095位数越高量化结果越精细对应分辨率就越高 转换时间即转换频率即AD转换开始到产生结果需要花1us的时间对应AD转换的频率就是1MHz •输入电压范围0~3.3V转换结果范围0~4095输入电压和转换结果一一对应呈线性关系 • 18 个输入通道可测量 16 个外部和 2 个内部信号源 • 规则组和注入组两个转换单元 • 模拟看门狗自动监测输入电压范围 • STM32F103C8T6 ADC 资源 ADC1 、 ADC2 10 个外部输入通道 逐次逼近型ADC 首先这个ADC有八个输入引脚通过通道选择开关可以选择IN中其中一个进行下一步通道选择开关是靠地址锁存和译码控制的把通道的编号输入ADDAADDBADDC然后给一个锁存信号ALE上面对应的通路开关就可以自动拨好了相当于一个可以通过模拟信号的数据选择器因此如果想要转换多路信号只需要一个AD转换器加一个多路选择开关想转换哪路就选中那个对应通道然后再开始转换就行了。
接下来会把这个未知编码的电压输出到比较器中DAC会输出一个已知编码的电压将两者进行比较如果未知大于已知则会增大已知量如果未知小于已知则会减小已知量直到二者近似相等这样以来DAC输入的数据就是外部电压的编码数据了这个电压调节的过程就是这个逐词逼近SAR来完成的。
为了快速找到这个值我们会使用二分法来寻找并且这个过程如果使用二进制来表示的话会发现128、64、32这些值都是二进制每一位的位权这个判断过程相当于是对二进制从高位到低位依次判断是1还是0的过程。对于8位的ADC从高位到低位依次判断8次就能找到未知电压的编码了对于12位的ADC则需要判断12次。
然后DAC的输入数据就是未知电压的编码然后通过D0等口进行输出
EOCEnd of Convent转换结束信号
START开始转换给一个输入脉冲开始转换
CLOCKADC时钟每一步都需要时钟的推进
VREF和VREF-是DAC的参考电压写入一个数据255对应5V还是3V由它们决定
STM32的ADC GPIO端口由16个通道还分为两个通道第一个是注入通道第二个是规则通道
注入通道至多同时输入四个通道的数据但是有四个注入通道数据寄存器可以一次性把四个数据同时展示出来的
规则通道至多可以同时输入十六个通道的数据但是只有一个规则通道数据寄存器会出现数据覆盖只能把一个通道的数据展示出来如果想展示多个数据就需要搭配DMA转运数据。
开始触发注入组合规则组都有触发ADC开始转换的信号有两种一是软件触发在程序中调用代码启动转换二是硬件触发即图中的开始触发触发源主要来自定时器可以使用定时器中断来实现每过一定时间就触发一次ADC但是频繁进入中断对程序的正常执行有影响需要定时完成这种简单的任务的情况一般都会有硬件的支持比如这里给TIM3定一个1ms的时间把TIM3的更新事件选择为TRGO输出在ADC这选择开始触发信号为TIM3的TRGO这样TIM3的更新事件就能通过硬件自动触发ADC转换了。
ADCCLK:即CLK时钟
在RCC时钟树中ADCCLK最大14MHz而我们如果选择二分频即72/236超出范围而且选择4分频也是超出范围的只能选择6和8分频。 ADC基本结构 还可以布置一个模拟看门狗用于监测转换结果的范围如果超出所设定的阈值就会通过中断输出控制向NVIC申请中断在AD转换器转换完成之后还有个EOC信号它会置一个标志位然后通向NVIC申请中断。
输入通道 转换模式
单次转换、非扫描模式
简单的在序列1中指定我们想要转换的通道然后就可以触发转换ADC就会对这个通道进行模数转换过段时间转换完成后会把转换结果放在数据寄存器中同时给EOC标志位置1转换过程就结束了。我们判断这个EOC标志位如果转换完了就可以在数据器中读取结果了。
如果还想在启动转换想要转换其他通道则就需要把序列1中的通道更改为目标通道再触发转换。
指定通道在序列1-触发转换-转换结束-给标志位EOC置1-读结果 多次转换非扫描模式
相比单次转换多次转换只用触发转换一次ADC就会一直转换不用判断结束和不用多次触发转换想要读取AD值时就直接从数据寄存器去就可以了。 单次转换扫描模式
扫描模式下可以利用多个序列可以任意指定任意通道流程依然是触发转换然后按序列顺序开始给指定通道转换直到转换完最后一个序列指定的通道然后给EOC标志位置1。 连续转换扫描模式
不想解释.... 触发控制 数据对齐
ADC是12位的但是数据寄存器却是16位的所以就需要数据对齐
这里我们常用数据右对齐 转换时间 •AD转换的步骤采样保持量化编码 采样保持量化编码过程需要比较多的时间在量化编码过程中如果输入的电压发生变化就很难定位输入电压对应的编码了所以需要采样保持在量化编码之前需要打开采样开关收集一下外部的电压比如使用一个小容量的电容存储这个电压存储好之后断开采样开关再进行AD转换这样就实现了在量化编码过程中电压始终保持不变 •STM32 ADC的总转换时间为 TCONV 采样时间 12.5个ADC周期 •例如当ADCCLK14MHz采样时间为1.5个ADC周期 TCONV 1.5 12.5 14个ADC周期 1μs
即最短转换时间为1us因为ADCCLK最小为14MHz
校准
• ADC 有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间在每个电容器上都会计算出一个误差修正码 ( 数字值 ) 这个码用于消除在随后的转换中每个电容器上产生的误差 • 建议在每次上电后执行一次校准 • 启动校准前 ADC 必须处于关电状态超过至少两个 ADC 时钟周期 硬件电路 图一电位器产生可调电压的电路可给PA0产生一个0~3.3V的电压电阻阻值不可太小一般为10kΩ
图二传感器输出电压的电路 N1麦克风热敏电阻等可变电阻可以等效为一个可变电阻其阻值没法直接测量所以可以通过和一个固定电阻串联分压来得到一个可以反映电阻值电压的电路这个固定电阻一般选择和传感器阻值相近的电阻这样可以得到一个位于中间电压区域比较好的输出
图三简单的电压转换电路比如我们想测一个0~5V的Vin电压但是ADC只能接收最大为3.3V的电压根据图中两个电阻的分压可以得到PA2的电压范围就是0~3.3V就可以进入ADC转换了。输入电压差太多不适合用这个电路。
代码实操 先介绍相关库函数
ADCCLK的配置函数
用于配置ADCCLK分频器的可以对APB2的72MHz时钟选择2,、4、6、8分频输入到ADCCLK
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);ADC的库函数
老朋友
void ADC_DeInit(ADC_TypeDef* ADCx);
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);开关控制
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);用于开启DMA输出信号
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);中断输出控制
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);控制校准的函数 ADC初始化完成后依次调用即可
//复位校准
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
//获取复位校准状态
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
//开始校准
void ADC_StartCalibration(ADC_TypeDef* ADCx);
//获取开始校准状态
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);用于软件触发的函数即用软件控制触发控制
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);获取标志位状态参数写EOC的标志位判断EOC是否被置1即转换是否结束
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);有关间断的函数
//每隔几个通道间断一次
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
//是否开启间断
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);ADC规则组通道配置重要ADC号需要指定的通道序列几的位置指定通道的采样的时间
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);是否允许外部触发转换
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);ADC获取转换值重要即获取AD转换的数据寄存器
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);ADC获取双模式转换值双ADC获取转换值的函数
uint32_t ADC_GetDualModeConversionValue(void);注入组的函数不讲
void ADC_AutoInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_InjectedDiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_ExternalTrigInjectedConvConfig(ADC_TypeDef* ADCx, uint32_t ADC_ExternalTrigInjecConv);
void ADC_ExternalTrigInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
void ADC_SoftwareStartInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
FlagStatus ADC_GetSoftwareStartInjectedConvCmdStatus(ADC_TypeDef* ADCx);
void ADC_InjectedChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
void ADC_InjectedSequencerLengthConfig(ADC_TypeDef* ADCx, uint8_t Length);
void ADC_SetInjectedOffset(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel, uint16_t Offset);模拟看门狗
//是否启用模拟看门狗
void ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog);
//配置高低阈值
void ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold);
//配置看门的通道
void ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel);ADC温度传感器、内部参考电压控制
void ADC_TempSensorVrefintCmd(FunctionalState NewState);标志位
//获取标志位状态
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
//清除标志位
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
//获取中断状态
ITStatus ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT);
//清除中断挂起位
void ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT); 如图所示步骤编写代码 1、开启GPIO和ADC的时钟还有ADCCLK分频器 //开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//6分频RCC_ADCCLKConfig(RCC_PCLK2_Div6);
2、配置GPIO口配置为模拟输入模式 //配置GPIO口GPIO_InitTypeDef GPIO_InitStructure;//AIN模拟输入//在AIN模式下GPIO口无效即断开GPIO口//防止GPIO输入输出对模拟电压造成干扰(ACD专属模式)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);
3、配置多路开关把左边的通道接入到右边的规则组列表中 //选择规则组的输入通道//参数3序号数//参数4采样时间的参数需要更快的转换选择小点的参数//需要稳定的转换则选择更大的参数ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
4、配置ADC转换器 //初始化ADCADC_InitTypeDef ADC_InitStructure;//ADC工作模式(独立模式)ADC_InitStructure.ADC_Mode ADC_Mode_Independent;//数据对齐(右对齐)ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right;//外部触发转换选择(外部触发源选择)(None,内部软件触发)ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None;//连续转换模式(ENABLE-连续模式 or DISABLE-非连续模式)ADC_InitStructure.ADC_ContinuousConvMode DISABLE;//扫描模式(ENABLE-扫描模式 or DISABLE-非扫描模式)ADC_InitStructure.ADC_ScanConvMode DISABLE;//通道数目(指定在扫描模式下指定用到几个通道0~16)//在非扫描模式下填任何数值都没用ADC_InitStructure.ADC_NbrOfChannel 1;ADC_Init(ADC1, ADC_InitStructure);
5、开关控制 //开启ADC电源ADC_Cmd(ADC1,ENABLE);
6、还可以对ADC进行校准可以减小误差 //校准//复位校准ADC_ResetCalibration(ADC1);
上列语句是执行复位校准那应该如何判断复位校准完成了呢
这个函数是一个返回值那这个返回值和是否完成校准有什么关系呢 //获取复位校准状态 ADC_GetResetCalibrationStatus(ADC1);查看其函数定义 可以看到其获取的就是CR2寄存器里的RSTCAL标志位
再查看手册查询相关寄存器内容 则可得 while(ADC_GetResetCalibrationStatus(ADC1) SET);则校准的总代码 //校准//复位校准ADC_ResetCalibration(ADC1);//获取复位校准状态//标志位为1时表示正在进行复位校准//标志位为0时表示复位校准结束则我们要保证复位校准成功//当复位校准未完成就一直循环等待其完成while(ADC_GetResetCalibrationStatus(ADC1) SET);//开始校准ADC_StartCalibration(ADC1);//获取开始校准状态while(ADC_GetCalibrationStatus(ADC1) SET);
则初始化函数写好了
#include stm32f10x.h // Device headervoid AD_Init(void)
{//开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//6分频RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置GPIO口GPIO_InitTypeDef GPIO_InitStructure;//AIN模拟输入//在AIN模式下GPIO口无效即断开GPIO口//防止GPIO输入输出对模拟电压造成干扰(ACD专属模式)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);//选择规则组的输入通道//参数3序号数//参数4采样时间的参数需要更快的转换选择小点的参数//需要稳定的转换则选择更大的参数ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);//初始化ADC(单次转换非扫描模式)ADC_InitTypeDef ADC_InitStructure;//ADC工作模式(独立模式)ADC_InitStructure.ADC_Mode ADC_Mode_Independent;//数据对齐(右对齐)ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right;//外部触发转换选择(外部触发源选择)(None,内部软件触发)ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None;//连续转换模式(ENABLE-连续模式 or DISABLE-非连续模式)ADC_InitStructure.ADC_ContinuousConvMode DISABLE;//扫描模式(ENABLE-扫描模式 or DISABLE-非扫描模式)ADC_InitStructure.ADC_ScanConvMode DISABLE;//通道数目(指定在扫描模式下指定用到几个通道0~16)//在非扫描模式下填任何数值都没用ADC_InitStructure.ADC_NbrOfChannel 1;ADC_Init(ADC1, ADC_InitStructure);//开启ADC电源ADC_Cmd(ADC1,ENABLE);//校准//复位校准ADC_ResetCalibration(ADC1);//获取复位校准状态//标志位为1时表示正在进行复位校准//标志位为0时表示复位校准结束则我们要保证复位校准成功//当复位校准未完成就一直循环等待其完成while(ADC_GetResetCalibrationStatus(ADC1) SET);//开始校准ADC_StartCalibration(ADC1);//获取开始校准状态while(ADC_GetCalibrationStatus(ADC1) SET);
}获取结果函数
1、软件触发启动 //软件触发(启动)ADC_SoftwareStartConvCmd(ADC1, ENABLE);
2、等待转换完成EOC置1等待 //获取EOC标志位状态(等待)//与之前的判断复位校准是否完成的操作一致//但是与校准有所不同(需要看寄存器描述)//0:转换未完成 1:转换完成//在之前我们设置采样周期为55.5转换周期是固定的12.5//加在一起就是68个周期配置的ADCCLK是72MHz的6分频即12MHz//12MHz进行68个周期转换才能完成最终时间为1/12M*685.6μs//即while循环会等待5.6μswhile(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET);
3、读取ADC数据寄存器读取 //获取转换值(读取)return ADC_GetConversionValue(ADC1);
整体
//获取转换结果的函数
uint16_t AD_GetValue(void)
{//软件触发(启动)ADC_SoftwareStartConvCmd(ADC1, ENABLE);//获取EOC标志位状态(等待)//与之前的判断复位校准是否完成的操作一致//但是与校准有所不同(需要看寄存器描述)//0:转换未完成 1:转换完成//在之前我们设置采样周期为55.5转换周期是固定的12.5//加在一起就是68个周期配置的ADCCLK是72MHz的6分频即12MHz//12MHz进行68个周期转换才能完成最终时间为1/12M*685.6μs//即while循环会等待5.6μswhile(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET);//获取转换值(读取)return ADC_GetConversionValue(ADC1);
}
在主函数中调用
#include stm32f10x.h // Device header
#include Delay.h
#include OLED.h
#include AD.huint16_t ADValue;int main(void)
{OLED_Init();AD_Init();OLED_ShowString(1,1,ADValue);while(1){ADValue AD_GetValue();OLED_ShowNum(1,9,ADValue,4);}
}然后旋转电位器可以发现向左拧ADValue增大向右拧ADValue则减小。
改善
一、数据抖动
我们会发现数据末尾会发生抖动这是正常现象当我们想使用这个值进行判断再执行某些操作比如光线的AD值小于某阈值就开灯大于某阈值就关灯可能会出现假如值在阈值附近抖动导致我们的操作不稳定即LED亮灭不稳定我们可以使用迟滞比较的方法来完成设置两个阈值低于下阈值时开灯高于上阈值时关灯这样就可以避免输出抖动的问题了施密特触发器同一个原理。
如果数据跳变来厉害还可以采用滤波的方法使AD值更平滑点(均值滤波)
或者裁减分辨率把数据的尾数去掉。
二、显示电压
如之前所说输入电压和转换结果一一对应呈线性关系
我们可以利用这个线性关系输出电压的值不是完全准确的
#include stm32f10x.h // Device header
#include Delay.h
#include OLED.h
#include AD.huint16_t ADValue;
float Voltage;int main(void)
{OLED_Init();AD_Init();OLED_ShowString(1,1,ADValue:);OLED_ShowString(2,1,Voltage:0.00V);while(1){ADValue AD_GetValue();Voltage (float)ADValue / 4095 * 3.3;OLED_ShowNum(1,9,ADValue,4);OLED_ShowNum(2,9,Voltage,1);OLED_ShowNum(2,11,(uint16_t)(Voltage*100)%100,2);Delay_ms(100);}
}