ADC模拟-数字转换器
简介
-
ADC(Analog-Digital Converter)模拟-数字转换器
-
ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
- 即对高低电平之间的任意电压进行量化。最终用一个变量表示,可以读取这个变量就可以知道引脚的具体电压为多少(ADC可以看作是一个电压表,将引脚的电压测出来,放在一个变量里面)
-
工作模式:
- 12位逐次逼近型ADC,1us转换时间
-
输入电压范围:0-3.3V,转换结果范围:0-4095
-
18个输入通道,可测量16个外部和2个内部信号源
-
规则组和注入组两个转换单元
-
模拟看门狗自动监测输入电压范围
-
STM32F103C8T6的ADC资源:ADC1、ADC2,10个外部输入通道
逐次逼近型ADC
逐次逼近型ADC是ADC模拟转换器的工作模式,进行逐次比较电压
上面的EOC是转换结束信号,START是开始转换,给一个输入脉冲开始转换,左边的CLOCK是ADC时钟
下面的VREF+和VREF-是DAC的参考电压
VCC和GND是整个芯片的供电
输入信号选择部分:
- IN0-IN7是8路输入通道,通过通道选择开关,输入比较器进行转换(相当于一个可以通过模拟信号的数据选择器。 1.设计多路AD通道时,只需要一个AD转换器,然后加一个多路INx选择开关就行。想转换哪一路,先拨一下开关,选中对应通道再开始转换)
- 下面的地址锁存和译码,即想选择哪个通道,就将通道号放在ADDA、ADDB、ADDC这三个引脚上,并给ALE一个锁存信号
- 给了锁存信号后,上面的通道选择开关就会自动拨好
寻找未知电压的编码
因为要得到通道选择开关传输来的未知电压的编码给三态锁存缓冲器,所以要测量他们的编码
为了最快找到未知电压的编码,通常选用二分法寻找
比如8位的ADC,则编码就是0-255
第一次比较时,给DAC输入255的一半进行比较,要是大了第二次就给255的一半128比较。看看谁大谁小,若还是大了,第三次就给128的一半,64。
若DAC电压小了,下一次就给64到128中间的值,然后继续这个过程
直到DAC输入的电压和外部通道输入的电压近视相等(这种一次次比较就是逐次逼近型名字的来源)
- 对于8位ADC,从高位到低位依次判断8次就能找到未知电压的编码,12位就要12次
在AD转换结束后,DAC的输入数据就是未知电压的编码
再接入三态锁存缓冲器进行输出,8位ADC就有8根线,12位有12根线
电压比较器解释:
-
电压比较器可以比较两个输入信号电压的大小关系,输出一个高低电平指示谁大谁小
-
左边两个输入端,连接通道选择开关的是连接待测电压,连接DAC的是连接DAC的电压输出端
-
左侧两个输入端电压要相等:
- 若DAC输出给比较器的电压大,则调小DAC数据,若DAC输出给比较器的电压小,则调大DAC数据,直到DAC输出的电压和外部通道输入的电压近视相等,
- 则DAC输入数据为外部电压的编码数据了
-
-
ADC框图
这里的ADCCLK是ADC的时钟,也就是逐次逼近型ADC的CLOCK
,用于驱动内部逐次比较的时钟
而ADC预分频器来自于RCC的
输入通道
输入通道分为16个外部通道和2个内部温度传感器
外部的是ADCx_INx(0-15),内部则是温度传感器和 V R E F I N T V_{REFINT} VREFINT
外部信号通过通道进来后,进入模拟多路开关,指定我们想要选择的通道, 右边是多路开关的输出,进入模数转换器
这里模数转换器执行的就是上面逐次逼近比较的过程
不过先不谈模数转换器,先看触发转换START信号
触发转换
触发转换即下面的内容
触发转换就是逐次逼近型ADC中START信号开始转换
触发ADC开始转换的信号有两种:
-
软件触发:即在程序中手动调用代码,就可以启动转换
-
硬件出发,即ADC框图中的触发源
- 上面EXTI_15的是注入组的触发源,下面EXTI_11是规则组的触发源
- 触发源来自于定时器
触发源有定时器的各个通道,还有TRGO定时器主模式的输出
硬件触发
比如从TIM3定1ms的时间,并且将TIM3的更新事件选择为TRGO输出,在ADC这里就选择开始触发信号为TIM3的TRGO
模数转换
这里模数转换器就是将输入信号转换为电压
模数转换器可以分为注入通道和规则通道两个组
其中规则组一次性可以转换16个通道的信号,注入组最多4个通道
-
注入组和规则组的作用:
-
举例:
- 普通ADC就是去吃饭时指定一个菜让老板做,老板做好后送给你。
- 通过规则组和注入组就是你指定一个菜单,菜单最多写16个菜,然后你直接在菜单上写好给老板,老板按照菜单顺序依次做好,并一次性给你端上来
-
规则组
但是规则组虽然有16个通道,但是其规则通道数据寄存器只有16位(很小)
- 举例就是桌子太小了,点了16个菜,但因为太小了使得前15个菜都被挤掉,只能得到第16个菜
所以要使用规则组转换的话,最好配合DMA来实现
DMA是数据转运帮手,能在每上一个菜后把这个菜挪到其他地方,防止被覆盖
注入组
规则组若是餐厅普通座位,那注入组就是餐厅的VIP座位,一次性可以点4个菜,而且因为数据寄存器位4*16位,可以同时上4个菜,不用担心数据被覆盖
存储转换结果和输出
在模数转换器转换完后,得到的电压结果就放在数据寄存器里面
通过读取寄存器就能指定ADC转换的结果了
而再上面还有模拟看门狗,可以用来存阈值高限和阈值底限。若超过了阈值,则看门狗乱叫,就会在上面的标志位和中断使能位申请一个模拟看门狗的中断,最后通向NVIC的ADC中断
ADC基本结构
- 左边是输入通道,即16个GPIO口和两个内部的通道,通过模拟多路开关进入AD转换器
- 触发控制提供了开始转换START信号,触发控制可以选择软件触发和硬件触发,让ADC对信息进行模数转换
- RCC提供了ADC时钟CLOCK,ADC逐次比较的过程由CLOCK时钟推动
- AD转换器有2个组:规则组和注入组。规则组最多选16个通道,注入组选4个
- 转换的结果可以存储在AD数据寄存器里面,其中规则组只有1个数据寄存器,注入组有4个数据寄存器
- 为了检测转换结果的范围,可以布置一个模拟看门狗来检测。若超出设定的阈值,就通过终端输出控制,向NVIC申请中断
- 另外规则组和注入组完成后会有个EOC信号,它会布置一个标志位,可以通向NVIC
- 右下角的开关控制可以通过在库函数中的ADC_Cmd函数,给ADC上电
输入通道
下面是ADC通道和引脚复用的关系,也可以用过引脚定义表看出
双ADC模式
ADC1和ADC2可以同步进行,一起配合组成同步模式、交叉模式等模式
- 交叉模式:ADC1和ADC2交叉的对一个通道进行采样,就像打拳,左手一拳右手一拳,快速交叉打拳
4种转换模式
单次转换,非扫描模式
可以在通道里面“点菜”,即写入你要转换的通道
在非扫描模式下,这个菜单只有序列1的位置有效
菜单同时选中一组的方式就退化为简单选择一个的方式
即在上图的序列1写入我们的通道2,然后就可以触发转换,ADC就会对通道2进行模数转换。转换完成后数据放在数据寄存器里,同时给EOC标志位置1,EOC标志位直1显示转换结束
若想再转换一次,需要再启动一次
连续转换,非扫描模式
非扫描模式下,这个菜单只有序列1的位置有效
与单次转换不同,在一次转换结束后不会停止,不会在EOC标志位判断是否结束,而是立刻开始下一轮的转换,一直持续下去
这样只用一开始触发一次,之后就会一直转换
想要读取AD值的时候,直接从数据寄存器取
单次转换,扫描模式
单次转换每次转换结束后停止,下次转换得重新触发才能开始
扫描模式就会用到上面的“菜单”列表,可以在里面写入要写的通道,每个位置是通道几都是任意指定,且可以重复
在初始化结构体里面会有个通道数目,即使用几个通道(16个通道用不完,用前几个就可以了)
为了防止数据被覆盖,要使用DMA防止数据被挪走
7个通道转换完成后产生EOC信号,表示转换结束
连续转换,扫描模式
在单次转换,扫描模式变了一下,在一次转换后立刻开始下一次转换
触发控制
下表为规则组的触发源
也就是ADC况图的
外部引脚/来自片上定时器的内部信号具体是引脚还是定时器,还需要用AFIO重映射来确定
软件控制位,即之前说的软件触发,通过右边000、001...这些寄存器来完成,使用库函数时直接给个参数就行
数据对齐
数据寄存器是16位的,存在一个数据对齐的问题
数据右对齐:
右对齐,就是12位的数据向右靠,高位多出来的几位补0
一般用右对齐,这样读取16位寄存器直接就是转换结果
数据左对齐:
左对齐,就是12位的数据向左靠,低位多出来的几位补0
选择左对齐,直接读的话得到的数据会比实际的大,因为左对齐实际上就是把数据左移了4次
转换时间
AD转换需要花时间的步骤:采样,保持(在量化、编码这一ADC逐次比较之前的一段时间,输入的电压可能会不断变化,若是一直变化则无法定为输入电压为多少。所以在量化编码前,要设置一个采样开关),量化,编码(ADC逐次比较的过程)
在量化、编码这一ADC逐次比较之前的一段时间,输入的电压可能会不断变化,若是一直变化则无法定为输入电压为多少。所以在量化编码前,要设置一个采样开关
先打开采样开关,收集一下外部的电压
比如可以用一个小容量的电容存储一下电压,存储好后断开采样开关,再进行后面的AD变换。这样能使转换期间电压一直保持不变
而采样保持的过程需要闭合采样开关,过一段时间在断开,这个时间就是采样时间(采样时间越大,越能避免一些毛刺信号的干扰)
- STM32 ADC的总转换时间为: T C O N V T_{CONV} TCONV = 采样时间 + 12.5个ADC周期(12个ADC,所以要花费12个周期,多的半个周期可能是做其他工作花的时间)
- 例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期: T C O N V T_{CONV} TCONV = 1.5 + 12.5 = 14个ADC周期 = 1μs
校准
ADC有一个内置自校准模式。这个是固定流程,在ADC初始化的最后加几条代码就行。
校准可大幅减小因内部电容器组的变化而造成的准精度误差。
校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差
建议在每次上电后执行一次校准
启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期
硬件电路
对于外围电路的设计如下图
1号:电位器产生可调电压电路
1号电路是电位器产生一个可调电压,电位器两端接3.3V和GND,这样中间的滑动端可以输出一个0-3.3V的可调电压输出
- 滑动端可以接一个PA0口,当滑动端往上滑时,电压增大,往下滑时,电压减小
- 要注意电位器电阻的阻值不要给太小,因为电阻直接连接电源和GND的。若是阻值太小,电阻就会比较费电,可能会发热冒烟,至少接KΩ级的电阻,这里接的是10k的电阻
2号:传感器输出电压的电路
上图的N1就是放传感器的
像是光敏电阻,热敏电阻,红外接收管,麦克风等等都可以等效为等效电阻。
所以2号电路这里这里可以通过和一个固定电阻串联分压,来得到一个反应电阻值电压的电路
- 这个固定电阻可以选择和传感器电阻相近的电阻,这样可以得到一个位于中间电压区域比较好的输出
传感器阻值变小时,下拉作用变强,输出端电压就下降,传感器阻值变大时,下拉作用变弱,输出端受上拉电阻的作用,电压升高
3号:电压转换电路
比如要测一个0-5V的VIN电压,但ADC只能接受0-3.3V的电压,就可以搭建3号电路这样的简易电压转换电路
3号这里使用电阻进行分压,上面阻值17K,下面阻值33K,一共50K
所以根据分压公式:中间电压 = VIN/50K*33K
最后可以得到的电压范围在0-3.3V之间,就可以进入ADC转换了
ADC库函数
初始化和配置
-
ADC_DeInit(ADC_TypeDef* ADCx);
:重置ADC外设到初始状态。 -
ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);
:根据配置结构体初始化ADC。 -
ADC_StructInit(ADC_InitTypeDef* ADC_InitStruct);
:初始化配置结构体到默认值。
启动转换
-
ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);
:使能或禁用ADC。 -
ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
:使能或禁用外部触发转换。 -
ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
:软件启动ADC转换。
读取结果
-
ADC_GetConversionValue(ADC_TypeDef* ADCx);
:获取常规通道的转换结果。 -
ADC_GetDualModeConversionValue(void);
:获取双ADC模式下的转换结果。 -
ADC_GetInjectedConversionValue(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel);
:获取注入通道的转换结果。
管理中断和标志
-
ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);
:使能或禁用ADC的中断请求。 -
ADC_GetITStatus(ADC_TypeDef* ADCx, uint16_t ADC_IT);
:获取中断状态。 -
ADC_ClearITPendingBit(ADC_TypeDef* ADCx, uint16_t ADC_IT);
:清除中断待处理位。 -
ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
:获取特定标志的状态。 -
ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
:清除特定标志。
校准和测试
-
ADC_ResetCalibration(ADC_TypeDef* ADCx);
:启动ADC校准序列。 -
ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
:获取校准序列的复位状态。 -
ADC_StartCalibration(ADC_TypeDef* ADCx);
:启动ADC校准过程。 -
ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);
:获取ADC校准过程的状态。
注入通道配置
-
ADC_InjectedChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
:配置注入通道。 -
ADC_InjectedSequencerLengthConfig(ADC_TypeDef* ADCx, uint8_t Length);
:配置注入序列长度。 -
ADC_AutoInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
:使能或禁用自动注入转换。 -
ADC_InjectedDiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
:使能或禁用注入离散模式。
外部触发和注入配置
-
ADC_ExternalTrigInjectedConvConfig(ADC_TypeDef* ADCx, uint32_t ADC_ExternalTrigInjecConv);
:配置外部触发注入转换。 -
ADC_ExternalTrigInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
:使能或禁用外部触发注入转换。 -
ADC_SoftwareStartInjectedConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
:软件启动注入转换。 -
ADC_GetSoftwareStartInjectedConvCmdStatus(ADC_TypeDef* ADCx);
:获取软件启动注入转换的状态。
模拟看门狗和温度传感器
-
ADC_AnalogWatchdogCmd(ADC_TypeDef* ADCx, uint32_t ADC_AnalogWatchdog);
:使能或禁用模拟看门狗。 -
ADC_AnalogWatchdogThresholdsConfig(ADC_TypeDef* ADCx, uint16_t HighThreshold, uint16_t LowThreshold);
:配置模拟看门狗阈值。 -
ADC_AnalogWatchdogSingleChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel);
:配置模拟看门狗单通道。 -
ADC_TempSensorVrefintCmd(FunctionalState NewState);
:使能或禁用温度传感器和内部参考电压。