目录
ADC简介
-
ADC(Analog-Digital Converter)模拟-数字转换器
-
ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
-
12位逐次逼近型ADC,1us转换时间。12位是分辨率,1us是转换频率,就是1MHz。
-
输入电压范围:0 ~ 3.3V,转换结果范围:0~4095
-
18个输入通道,可测量16个外部和2个内部信号源。2个内部的是温度传感器和内部参考电压。温度传感器可以测量CPU温度。内部参考电压是一个1.2V左右的基准电压。这个基准电压是不随外部供电电压变化而变化的。
-
规则组和注入组两个转换单元
-
模拟看门狗自动监测输入电压范围
-
STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道,没有DAC转换
存在ADC当然也存在DAC,数字电路到模拟电路的桥梁。使用DAC可以将数字信号转化为模拟信号,其实之前学习的PWM就是数字到模拟的桥梁,这种方式也完成了数字信号输出模拟信号。因为PWM只有完全导通和完全断开两种状态,这两种状态都没有功率损耗,所以在直流电机调速这种大功率的应用场景使用PWM来等效模拟量比DAC本身更好用。并且PWM更加简单常用。DAC的应用主要是在波形生成等领域,比如信号发生器,音频解码芯片。
STM32F103C8T6只有两个ADC资源和10个外部引脚的模拟信号,上面说的16个外部信号源是这个系列最多有16个外部信号源。但是C8T6引脚较少,所以其他信号源没有。
逐次逼近型ADC

上图是ADC0809芯片的原理图,这个芯片是逐次逼近型的ADC转换和C8T6是一样的原理。
ADC0809是一个独立的8位逐次逼近型ADC,单片机内部没有集成ADC时需要外挂ADC芯片,ADC0809就是这么一款经典的ADC芯片。现在很多单片机内部已经集成了ADC外设,就不需要外挂芯片,可以直接测量电压。
IN7~IN0:8路模拟输入。
ADDA、ADDB、ADDC、ALE:地址锁存,选择当前的模拟输入引脚。相当于38译码器。
CLOCK:ADC时钟线,ADC需要时钟来推动这个过程。
START:开始AD转换。
EOC:转换结束标志位。
内部DAC:加权电阻网络,用于产生和输入模拟信号进行比较的模拟信号。
OE:输出使能,控制三态门输出。
D7~D0:输出的8位数字信号。
VREF(+)、VREF(-):参考电压。
内部存在一个电压比较器,可以判断两个输入信号电压的大小关系。左侧接入的通道是待测电压,下面DAC是电压输出端,输出到比较器。DAC的电压我们知道,通过这个电压和待测电压的比较,然后再调节DAC,逐次逼近待测电压。这样DAC的电压就是待测电压了。DAC电压调节的过程就是图中SAR来完成的。通常使用二分法来完成。最终电压通过锁存器输出,输出的值就是待测的值。图中输出的是8位,就有8根线,C8T6是12位就有12根线。
上面EOC是End Of Convert,转化结束信号。START是开始转换,给一个输入脉冲,开始转换。
CLOCK是ADC时钟线,,因为ADC内部是一步一步进行判断的,所以需要时钟来推动这个过程。VREF(+)和VREF(-)是DAC的参考电压。255对应是5V还是3.3V就由这个参考电压决定。这个电压也决定了ADC的输入范围,所以也是ADC的参考电压。
VCC和GND是整个芯片的供电,通常参考电压的正极和VCC是一样的,参考电压的负极和GND是一样的。
STM32的ADC

上图是ADC的结构图。
最左边是ADC的输入通道,包括16个GPIO口,IN0 ~ IN15,稍右边还有两个内部的通道,一个内部温度传感器,另一个是VREFINT(V Reference Internal),内部参考电压。总共是18个输入通道。
然后通过模拟多路开关指定我们想要选择的通道,然后输出到模数转换器。这里就没有给内部细节执行流程了,大致和上面说的转换流程是一样的。转换结果直接放到上面的数据寄存器中。读取寄存器就可以知道ADC转换的结果了。
普通的ADC转换只能转换一路ADC,这里可以转换多路。分为两组,规则通道组和注入通道组,规则通道组一次性最多选中16个通道,注入通道组最多选中4个通道。
注入通道【使用不多】:最多一次性选4路通道,配合4个16位寄存器,就可以一次性转换4路模拟数据。
规则通道【常用】:最多一次性选16路通道,但只有1个16位寄存器,存在新来的数据覆盖上一个数据的问题,此时要么尽快将数据取走,要是使用DMA帮助转运数据,进而可以实现一次性转换16路模拟数据。当然,一次就选一个通道,就是普通的ADC功能。
左下角是触发转化的部分,就是上面说的START信号。对于STM32,触发方式有两种。
软件触发:在程序中手动调一句代码。
硬件触发:上图所示的就是硬件触发源,分为注入组的触发源和规则组的触发源。主要来自于定时器TIMx,定时器TRGO主模式的输出,也可以外部中断引脚EXTI。
正常思路是:定时器每隔1ms产生一次中断 --> 中断函数中开启触发转换信号 --> ADC完成一次转换。缺点是需要频繁进入中断,消耗软件资源。但是得益于上图的硬件电路设计,stm32可以直接使用定时器主模式触发ADC转换,硬件全自动无需申请中断,可以极大地减轻CPU负担。
左上部分有VREF(+)和VREF(-)是ADC的参考电压,VDDA和VSSA是ADC的供电引脚,一般VREF(+)要接VDDA,VREF(-)要接VSSA
VDDA和VSSA在引脚定义中也可以看到
VDDA和VSSA是内部模拟部分的电源,如ADC,RC振荡器,锁相环等。这里的VDDA接了3.3V,VSSA接GND。
接着看框图,转换器右边的ADCCLK是ADC的时钟。
来自ADC的预分频器,这个ADC的预分频器则来自于"RCC时钟树"。具体可以查看时钟树的电路,APB2时钟输出72MHz然后通过ADC预分频器进行预分频得到ADCCLK,由于ADCCLK最大14MHz,所以只能选择6分频/8分频 。
接着看框图,ADCCLK上面是DMA请求,这个是用于触发DMA进行数据转运的。
模拟看门狗 :一旦高于上阈值或低于下阈值,就会申请模拟看门狗的中断,最终进入NVIC。
EOC :规则或注入通道转换完成信号。
JEOC :注入通道转换完成信号。
这两个信号会在状态寄存器里置一个标志位,通过读取这个标志位就能知道是不是转换结束。同时这两个标志位也可以去到NVIC申请中断。

输入通道
STM32有16个外部通道,这些通道对应的GPIO口如下
只有ADC1存在内部的两个ADC,其他两个不存在。c8t6不存在ADC3。
通过引脚定义表也可以知道对应哪些引脚,但是写的是ADC12,这表示ADC1和ADC2复用的一个引脚,为什么还要ADC2呢?
对于c8t6这个型号来说,ADC1和ADC2共用引脚,不仅可以单独使用,可以组成更加复杂的双ADC模式。双ADC模式通过配合可以组成同步模式、交叉模式(ADC1和ADC2交叉对同一个通道进行采样,以提高采样率)等。
规则组的转换模式
stm32的ADC最多同时支持16个通道,那么ADC每次扫描1个通道还是多个通道,便是选择非扫描模式/扫描模式 ;而对于单个通道的ADC转换来说,触发一次ADC是只转换一次,还是自动的进行连续转换,便是选择 单次转换/连续转换 。上面这两种选择进行组合,便产生了规则组的4种转换模式:
单次转换、非扫描模式
触发一次仅转换一次;仅序列1有效,但可以任意指定需要转换的通道。此时ADC选择一组的方式退化成只能选择一个。读取数据时,需要等待EOC标志位置1,然后从数据寄存器读取结果。如要再进行转换,就需要再次触发转换。
连续转换、非扫描模式
相比于上一个模式,仅需要一次触发,ADC就会在一次转换完成后立刻进入下一次转换,实现不断地自动进行转换。此时就不需要读EOC看转换是否完成,直接想读数据的时候就读。
单次转换、扫描模式
相比于第一种模式,可以一次性转换多个通道,不过还是触发一次、所有通道只转换一次。此时需要指定多少个序列,每个序列用的哪个通道。因为数据寄存器只有一个,所以此时需要DMA配合转运数据。n个通道转化完成后产生EOC信号就标志转换结束。
连续转换、扫描模式
不仅可以一次性转换多个通道,还可以实现触发一次、自动不间断转换。
上面所说的序列其实就是DMA模式下,ADC的采集顺序。
间断模式
每几次转换就暂停一下,需要再次触发才可以继续......
数据对齐

因为ADC是12位的,而寄存器宽度为16位,所有便有了数据对齐方式的选择。
右对齐【常用】:读出的值就是实际值。
左对齐:有时候不需要太大的分辨率,便将12位ADC的转换数据左对齐,然后只取高8位。
转换时间

低速采样可以忽略转换频率,高速采样必须考虑转换时间 的损耗。AD转换的步骤主要为:采样,保持,量化,编码。"采样"时间越长,越可以消除一些毛刺信号的干扰;而"量化、编码"是逐次比较的过程,消耗的时间则比"采样、保持"更长。"采样、保持"主要是因为"量化、编码"需要一段时间,所以"采样、保持"需要维持自己一段时间电压,不让这个电压不停的变化。
12.5个ADC周期因为是12位的ADC,这个周期就是ADCCLK。多出的0.5个周期在做其他事情。
校准
ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差
建议在每次上电后执行一次校准
启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期
校准过程的代码是固定的,只需要在ADC初始化之后加几句代码即可。
代码
第一步,开启RCC时钟,包括ADC和GPIO的时钟。ADCCLK的配置
第二部,配置GPIO,将需要用到的GPIO配置为模拟输入模式
第三步,配置多路开关,将左边的通道接入到右边的规则组列表中
第四步,配置ADC转换器,在库函数在用结构体就可以配置好,可以配置AD转换器和AD数据寄存器。ADC的单次还是连续转换,扫描还是非扫描,有几个通道,触发源,数据对齐方式等。
第五步,如果需要配置模拟看门狗,有几个函数用来配置阈值和监测通道的。
第六步,开关控制没开启ADC。开启之后可以进行一下ADC校准。
库函数:
c
void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);
用来配置ADCCLK分频器的,可以对APB2的72MHz时钟选择2,4,6,8分频输入到ADCCLK。
c
void ADC_DeInit(ADC_TypeDef* ADCx);
void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
void ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);
ADC_DeInit
恢复缺省配置,ADC_Init
初始化,ADC_StructInit
结构体初始化。
c
void ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
用来给ADC上电,就是ADC的开关控制。
c
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);
开启DMA输出信号,如果使用DMA转运数据就需要调用这个函数。
c
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);
中断输出控制
c
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
void ADC_StartCalibration(ADC_TypeDef* ADCx);
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);
分别是复位校准,获取复位校准状态,开始校准,获取开始校准状态
c
void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
ADC软件开始转换控制,这个是用于软件触发的函数
c
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);
ADC获取软件开始转换状态,功能是给SWSTART位置1,以开始转换。返回SWSTART的状态,但是手册上说,在置1后立刻由硬件置0。这个函数大多数情况下没用,而是用下面的。
c
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
获取标志位状态,参数给EOC的标志位,判断EOC标志位是不是置1,如果置1,那么转换结束。
c
void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
用来配置间断模式。
c
void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
ADC规则组通道配置,给序列的每个位置填写指定的通道
c
void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
ADC外部触发转换控制,就是是否允许外部触发转换。
c
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
ADC获取转换值,获取AD转化的数据寄存器,读取转换结果就要调用这个函数。
c
uint32_t ADC_GetDualModeConversionValue(void);
ADC获取双模式转换值,这是双ADC模式读取转换结果的函数。
c
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);
上面三个函数都是对模拟看门口进行配置的。是否启动模拟看门狗,配置高低阈值,配置看门的通道。
c
void ADC_TempSensorVrefintCmd(FunctionalState NewState);
ADC温度传感器内部参考电压控制,用来开启内部的两个通道,如果要使用这两个通道,就要调用这个函数。
软件触发单次转换非扫描模式
c
#include "Config.h"
void AD_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
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);
//哪个ADC,用通道几,使用第几个序列,采样时间
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_13Cycles5);//ADC规则组通道配置,给序列的每个位置填写指定的通道
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//独立模式还是双ADC模式
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐方式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//外部触发转换选择,即触发控制的触发源,这里选择软件触发
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;//连续转换模式,选择连续转换还是单次转换
ADC_InitStructure.ADC_ScanConvMode = DISABLE;//扫描转换模式,选择扫描模式还是非扫描模式
ADC_InitStructure.ADC_NbrOfChannel = 1;//通道数目,在扫描模式下用到几个通道
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);//复位校准
while(ADC_GetResetCalibrationStatus(ADC1) == SET);//等待复位校准完成
ADC_StartCalibration(ADC1);//开始校准
while(ADC_GetCalibrationStatus(ADC1) == SET);//等待校准完成
}
uint16_t AD_GetValue()
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
return ADC_GetConversionValue(ADC1);
}