【自学嵌入式:stm32单片机】ADC模数转换器

ADC模数转换器

详细内容也可看这篇做参考:https://www.cnblogs.com/qinruiqian/p/19023950

ADC(Analog-Digital Converter)模拟-数字转换器

  • ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁
  • 12位(分辨率)逐次逼近型ADC(0~ 2^12 -1),1us(1Mhz)转换时间(转换频率),就是量化结果的范围是0~ 4095,位数越高,量化结果就越精细,对应分辨率就越高,
  • 输入电压范围:0~3.3V,转换结果范围:0~4095
  • 18个输入通道,可测量16个外部(模电输入,这个系列最多有16个信号源)和2个内部信号源(内部温度传感器(读取CPU的温度)和内部参考电压(1.2V左右,不随外部供电电压变化而变化,所以芯片宫殿不是标准3.3V,那测量外部引脚的电压可能就不对,这时就可以读取这个基准电压进行校准,这样就能得到正确的电压值))
  • 规则组和注入组两个转换单元(STM32 ADC增强功能,普通ADC转换,启动一次,读取值、再读值,这样的流程,但是STM32的ADC就比较高级,可以列一个组,一次性启动一个组,连续转换多个值,并且有两个组,一个是用于常规使用的规则组,一个是用于突发事件的注入组)
  • 模拟看门狗自动监测输入电压范围(这个ADC一般可以用于测量光线强度、温度这些值,并且经常会有个需求,如果光线高于某个阈值、低于某个阈值,或者温度高于某个闽值、低于某个阈值时,执行一些操作,这个高于某个阈值、低于某个阀值的判断,就可以用模拟看门狗来自动执行,模拟看门狗可以监测指定的某些通道,当AD值高于它设定的上阈值或者低于下阈值时,它就会申请中断,你就可以在中断函数里执行相应的操作,这样你就不用不断地手动读值,再用if进行判断了)
  • STM32F103C8T6 ADC资源:ADC1、ADC2,10个外部输入通道(最多测10个模拟信号)

主次逼近型ADC

image

首先左边这里IN0~IN7,是8路输入通道,通过通道选择开关,选中一路,输入到这个点进行转换,如下:
image
下面这里是地址锁存和译码,就是你想选中哪个通道,就把通道号放在这三个脚上,如下:
image

然后给一个锁存信号,上面这里对应的通路开关就可以自动拨好了,这部分就相当于一个可以通过模拟信号的数据选择器,因为ADC转换是一个很快的过程,你给个开始信号,过几个us就转换完成了,所以说如果你想转换多路信号,那不必设计多个AD转换器,只需要一个AD转换器,然后加一个多路选择开关,想转换哪一路,就先拨一下开关,选中对应通道,然后再开始转换就行了,这就是这个输入通道选择的部分,这个ADC0809只有8个输入通道,我们STM32内部的ADC是有18个输入通道的,所以对应这里,就是一个18路输入的多路开关,那然后输入信号选好了,到这里来,如下:
image
怎么才能知道这个电屈对应的编码数据是多少呢,这就需要我们用逐次逼近的方法来一一比较了,首先这是一个电压比较器(如下),它可以判断两个输入信号电压的大小关系
image
输出一个高低电平指示谁大谁小,它的两个输入端,一个是待测的电压,如下:
image
另一个是这里DAC的电压输出端,如下
image

DAC是数模转换器,给它一个数据,它就能输出数据对应的电压,DAC内部是使用加权电阻网络来实现的转换,具体可看:https://www.cnblogs.com/qinruiqian/p/19023950
那现在,我们有了一个外部通道输入的,未知编码的电压,和一个DAC输出的已知编码的电压,它俩同时输入到电压比较器,进行大小判断,如果DAC输出的电压比较大,我就调小DAC数据,如果DAC输出的电压比较小,我就增大DAC数据,直到DAC输出的电压和外部通道输入的电压近似相等,这样DAC输入的数据就是外部电压的编码数据了,这个电压调节的过程就是这个逐次逼近SAR来完成的,为了最快找到未知电压的编码,通常我们会使用二分法进行寻找,比如这里是8位的ADC,那编码就是从0~255
image

如上图,第一次比较的时候,我们就给DAC输入255的一半,进行比较,那就是128,然后看看谁大谁小,如果DAC电压大了,第二次比较的时候,再就给128的一半,64,如果还大,第三次比较的时候就给32,如果这次DAC电压小了,那第四次就给32到64中间的值,然后继续,这样依次进行下去,就能最快地找到未知电压的编码,并且这个过程,如果你用二进制来表示的话,你会发现,128、64、32这些数据,正好是二进制每一位的位权,这个判断过程就相当于是,对二进制从高位,到低位依次判断是1还是0的过程,这就是逐次逼近型名字的来源,那对于8位的ADC,从高位到低位依次判断8次就能找到末知电压的编码了,对于12位的ADC,就需要依次判断12次,这就是逐次逼逅的过程,那然后,AD转换结束后,DAC的输入数据,就是未知电压的编码,通过这里(如下)进行输出,8位就有8根线,12位就有12根线
image

image

最后上面这里,EOC是End OfConvert,转换结束信号,START是开始转换,给一个输入脉冲,开始转换,CLOCK是ADC时钟,因为ADC内部是一步一步进行判断的,所以需要时钟来推动这个过程,下面,VREF+和VREF-是DAC的参考电压,比如你给一个数据255,是对应5V还还是3.3V呢,就由这个参考电压决定,这个DAC的参考电压也决定了ADC的输入范围,所以它也是ADC参考电压,最后左边是整个芯片电路的供电,VCC和GND,通常参考电压的正极和VCC是一样的,会接在起,参考电压的负极和GND也是一样的,也接在一起,所以一般情况下,ADC输入电压的范围就和ADC的供电是一样的

STM32的ADC框图

image
image

在这里,左边是ADC的输入通道,包括16个GPIO回,IN0~IN15,和两个内部的通道,一个是内部温度传感器,另一个是VREFINT(VReference lnternal)内部参考电压,总共是18个输入通道,
image

然后到达这里,这是一个模拟多路开关,可以指定我们想要选择的通道,
image

右边是多路开关的输出,进入到模数转换器,这里模数转换器就是执行的我们刚才讲过的逐次比较的过程
image
转换结果会直接放在这个数据寄存器里,我们读取寄存器就能知道ADC转换的结果了
image

然后在这里,对于普通的ADC,多路开关一般都是只选中一个的,就是选中某一个通道、开始转换、等待转换完成”取出结果,但是这里就比较高级了,它可以同时选中多个,而且在转换的时候,还分成了两个组,规则通道组和入通道组,其中规则组可以一次性最多选中16个通道,注入组最多可以选中4个通道,相当于从餐馆只能点一个菜做一个菜变成用一个菜单一次性列16个菜点菜,规则组菜单,可以同时上16个菜,规则组只有一个数据寄存器,就是桌子比较小,最多只能放一个菜,你如果上16个菜,前15个菜都会被挤掉,你只能得到第16个菜,所以对于规则组转换来说,如果使用这个菜单的话,最好配合DMA来实现,DMA是一个数据转运小帮手,它可以在每上一个菜之后,把这个菜挪到其他地方去,防被覆盖。
这个规则组虽然可以同时转换16个通道,但是数据寄存器只能存一个结果,如果不想之前的结果被覆盖,那在转换完成之后,就要尽快把结果拿走
注入组相当于是餐厅的VIP座位,在这个座位上,一次性最多可以点4个菜,并且这里数据寄存器有4个,是可以同时上4个菜的,对于注入组而言,就不用担心数据覆盖的问题了,一般情况下,使用规则组就完全足够了,如果要使用规则组的菜单,那就再配合DMA转运数据,这样就不用担心数据覆盖的问题了,接下来将规则组的操作
image
首先,左下角这里是触发转换的部分
image
也就是这里的START信号,开始转换
那对于STM32的ADC,触发ADC开始转换的信号有两种,一种是软件触发,就是你在程序中手动调用一条代码,就可以启动转换了
image
另一种是硬件触发,就是这里的这些触发源(如上图),上面这些是注入组的触发源,下面这些是规则组的触发源
image

如上图,这些触发源主要是来自于定时器,有定时器的各个通道,还有TRGO定时器主模式的输出,定时器可以通向ADC、DAC这些外设,用于触发转换,那因为ADC经常需要过一个固定时间段转换一次,比如每隔1ms转换一次,正常的思路就是,用定时器,每隔1ms申请一次中断,在中断里手动开始一次转换,这样也是可以的,但是频繁进中断对我们的程序是有一定影响的,影响CPU资源,中断优先级不同,触发ADC的中断因为优先级不能及时响应,那我们ADC的转换频率就肯定会产生影响了,所以对于这种需要频繁进中断,并且在中断里只完成了简单工作的情况,一般都会有硬件的支持
image

比如这里,就可以给TIM3定个1ms的时间,并且把TIM3的更新事件选择为TRGO输出,然后在ADC这里,选择开始触发信号为TIM3的TRGO,这样TIM3的更新事件就能通过硬件自动触发ADC转换了,整个过程不需要进入中断,节省了中断资源,这就是定时器触发的作用
image

当然这里还可以选择外部中断引脚来触发转换,都可以在程序中配置
image
然后接着看,左上角这里是VREF+、VREF-、VDDA和VSSA,上面两个是ADC的参考电压,决定了ADC输入电压的范围,下面两个是ADC的供电引脚,一般情况下,VREF+要接VDDA,VREF-要接VSSA,在我们这个芯片上,没有VREF+和VREF-的引脚,它在内部就已经和VDDA和VSSA接在一起了
image

VDDA和VSSA在这个引脚定义里也可以看到,VDDA和VSSA是内部模拟部分的电源,比如ADC、RC振荡器、锁相环等,在这里VDDA接3.3V,WSSA接GND,所以ADC的输入电范围就是0~3.3V
image

然后继续看,右边这里是ADCCLK是ADC的时钟
image
也就是这里的CLOCK,是用于驱动内部逐次比较的时钟
image
这个是来自ADC预分频器
image

这个ADG预分频器是来源于RCC的,APB2时钟72MHz,然后通过ADC预分频器进行分频,得到ADCCLK,ADCCLK最大是14MHZ,它可以选择2、4、6、8分频,如果选择2分频,72M/2=36M,超出允许范围了,4分频之后是18M,也超了,所以对于ADC预分频器。只能选择6分频,结果是12M,和8分频,结果是9M,这两个值
image

然后回到这里,这是ADC的时钟
image
继续看上面这里是DMA请求,这个就是用于触发DMA进行数据转运的,我们下节再讲
image
然后是两个数据寄存器,用于存放转换结果的
image

上面这里,还有模拟看门狗,它里面可以存一个阀值高限和阈值低限,如果启动了模拟看门狗,并指定了看门的通道,那这个看门狗就会关注它看门的通道,一但超过这个阀值范围了,它就会乱叫,就会在上面,申请一个模拟看门狗的中断,最后通向NMIC
image

(蓝色线对应的流程)然后对于规则组和注入组而言呢,它们转换完成之后,也会有一个EOC转换完成的信号,在这里,EOC是规则组的完成信号,JEOC是注入组完成的信号,这两个信号会在状态寄存器里置一个标志位,我们读取这个标志位,就能知道是不是转换结束了,同时这两个标志位也可以去到NVIC,申请中断,如果开启了NVIC对应的通道,它们就会触发中断

ADC基本结构

image
右下角有个开关控制,在库函数中,就是ADC_Cmd函数,用于给ADC上电的。

输入通道

下表就是ADC通道和引脚复用的关系表(我们这个芯片STM32F103C8T6没有ADC3)

通道 ADC1 ADC2 ADC3
通道0 PA0 PA0 PA0
通道1 PA1 PA1 PA1
通道2 PA2 PA2 PA2
通道3 PA3 PA3 PA3
通道4 PA4 PA4 PF6
通道5 PA5 PA5 PF7
通道6 PA6 PA6 PF8
通道7 PA7 PA7 PF9
通道8 PB0 PB0 PF10
通道9 PB1 PB1
通道10 PC0 PC0 PC0
通道11 PC1 PC1 PC1
通道12 PC2 PC2 PC2
通道13 PC3 PC3 PC3
通道14 PC4 PC4
通道15 PC5 PC5
通道16 温度传感器
通道17 内部参考电压

上表也能从引脚定义表看出来
image
ADC12_IN0的意思是,ADC1和ADC2的IN0都是在PA0上的,然后下面全都是ADC12,这说明ADC1和ADG2的引脚全都是相同的,它用在ADC的高级功能,双ADC模式,双ADC模式就是ADC1和ADC2一起工作,它俩可以配合组成同步模式、交又模式等等模式,比如交又模式,ADG1和ADC2交又地对通道进行采样,这样就可以进一步提高采样率,当然ADC1和ADC2也是可以分开使用的,可以分别对不同的引脚进行采样,这样也是可以的

规则组的四种转换模式

ADC初始化的结构体里,会有两个参数,一个是选择单次转换还是连续转换的,另一个是选择扫描模式还是非扫描模式的,这两个参数组合起来,就有如下4种转换方式

单次转换,非扫描模式

image
图中的列表就是规则组里的菜单,有16个空位,分别是序列1到序列16,你可以在这里“点菜”,就是写入你要转换的通道,在非扫描的模式下,这个菜单就只有第一个序列1的位置有效,这时,菜单同时选中一组的方式就退化为简单地选中一个的方式了,在这里我们可以在序列1的位置指定我们想转换的通道,比如通道2,然后就可以触发转换,ADC就会对这个通道2进行模数转换,过一小段时间后,转换完成,转换结果放在数据寄存器里,同时给EOC标志位置1,整个转换过程就结束了,我们判断这个EOC标志位,如果转换完了,那我们就可以在数据寄存器里读取结果了,如果我们想再启动一次转换,那就需要再触发一次,转换结束,置EOC标志位,读结果,如果想换一个通道转换,那在转换之前,把第一个位置的通道2改成其他通道,然后再启动转换,这样就行了。这就是单次转换,非扫描的转换模式

连续转换,非扫描模式

image
首先,它还是非扫描模式,所以菜单列表就只用第一个,然后它与上一种单次转换不同的是,它在一次转换结束后不会停止,而是立刻开始下一轮的转换,然后一直持续下去,这样就只需要最开始触发一次,之后就可以一直转换了。这个模式的好处就是,开始转换之后不需要等待一段时间的,因为它一直都在转换,所以你就不需要手动开始转换了,也不用判断是否结束的,想要读AD值的时候,直接从数据寄存器取就是了,这就是连续转换,非扫描的模式。

单次转换,扫描模式

image
这个模式也是单次转换,所以每触发一次,转换结束后,就会停下来,下次转换就得再触发才能开始,然后它是扫描模式,这就会用到这个菜单列表了,你可以在这个菜单里点菜,比如第一个菜是通道2,第二个菜是通道5,……
这里每个位置是通道几可以任意指定,并且也是可以重复的,然后初始化结构体里还会有个参数,就是通道数目,因为这16个位置你可以不用完,只用前几个,那你就需要再给一个通道数目的参数,告诉它,我有几个通道,比如这里指定通道数目为7,那它就只看前7个位置,然后每次触发之后,它就依次对这前7个位置进行AD转换,转换结果都放在数据寄存器里,这里为了防止数据被覆盖,就需要用DMA及时将数据挪走,那7个通道转换完成之后,产生EC信号,转换结束,然后再触发下一次,就又开始新一轮的转换,这就是单次转换,扫描模式的工作流程。

连续转换,扫描模式

image
它就是在“单次转换,扫描模式”的基础上变了一点,就是一次转换完成后,立刻开始下一次的转换。

间断模式

image

当然在扫描模式的情况下,还可以有一种模式,叫间断模式,它的作用是,在扫描的过程中,每隔几个转换,就暂停一次,需要再次触发,才能继续。

触发控制

image
在这个表里,有来自定时器的信号,还有这个来自引脚或定时器的信号,这个具体是引脚还是定时器,需要用AFIO重映射来确定,最后是软件控制位,也就是软件触发,这些触发信号怎么选择,可以通过设置右边这个寄存器来完成,使用库函数的话,直接给一个参数就行了。

数据对齐

image
我们这个ADC是12位的,它的转换结果就是一个12位的数据,但是这个数据寄存器是16位的,所以就存在一个数据对齐的问题,数据右对齐,12位数据向右靠,左侧空下的补0,数据左对齐是12位数据向左靠,右侧低位空下的补0,一般使用数据右对齐,这样读取这个16位寄存器,直接就是转换结果,如果选择左对齐,直接读的话,得到的数据会比实际的大,因为数据左对齐实际上就是把数据左移了4次,二进制有个特点,就是数据左移一次,就等效于把这个数据乘2,那这里左移4次,就相当于把结果乘16了,所以直接读的话会比实际值大16倍,左对齐的作用是,如果你不想要这么高的分辨率,你觉得0~4095数太大了,我就做个简单的判断,不需要这么高分辨率,那你就可以选择左对齐,然后再把这个数据的高8位取出来,这样就舍弃掉了后面4位的精度,这个12位的ADC就退化成了8位的ADC了。
一般用右对齐,如果需要裁剪一些分辨率,大不了先把数据用右对齐取出来再做处理,这也是可以的,就是多算了一步而已。

转换时间

  • AD转换的步骤:采样,保持,量化,编码,其中采样和保持可以放在一起,量化和编码可以放在一起,总共是这两个大步骤,量化编码好理解,就是我们之前讲过的,ADC逐次比较的过程,这个是要花一段时间的,一般位数越多,花的时间就越长。为什么需要采样保持,这是因为,我们的AD转换,就是后面的量化编码,是需要一小段时间的,如果在这一小段时间里,输入电压不断变化,那就没法定位输入电压到底在哪了,所以在量化编码之前,我们需要设置一个采样开关,先打开采样开关,收集一下外部的电压,
    image
    比如可以用一个小容量的电容存储一下这个电压,存储好了之后,断开采样开关,再进行后面的AD转换,这样在量化编码的期间,电压始终保持不变,这样才能精确地定位末知电压的位置,这就是采样保持电路,那采样保持的过程,需要闭合采样开关,过一段时间再断开,这里就会产生一个采样时间

  • STM32 ADC的总转换时间为:

\[T_{\text{CONV}}=\text{采样时间}+12.5\text{个ADC周期} \]

采样时间是采样保持花费的时间,这个可以在程序中进行配置,采样时问越大,越能避免一些毛刺信号的干扰,不过转换时间也会相应延长,12.5个ADC周期是量化编码花费的时间,因为是12位的ADC,所以需要花费12个周期,这里多了半个周期,可能是做其他一些东西花的时间,ADC周期就是从RCC分频过来的ADCCLK,这个ADCCLK最大是14MHz

  • 例如:当ADCCLK=14MHz,采样时间为1.5个ADC周期

\[T_{\text{CONV}}=1.5 + 12.5 = 14\text{个ADC周期} = 1μs \]

如果不需要高速的转换频率,那转换时间可以忽略了。另外你也可以把ADCCLK的时钟设置超过14MHZ,这样的话ADC就是在超频了,那转换时间可以比1us还短,不过这样稳定性就没法保证了。

校准

  • ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差

  • 建议在每次上电后执行一次校准

  • 启动校准前, ADC必须处于关电状态超过至少两个ADC时钟周期

硬件电路

image
对于ADC的外围电路,给了三个电路图,第一个是一个电位器产生一个可调的电压,这里电位器的两个固定端,一端接3.3V,另一端接GND,这样中间的滑动端就可以输出一个0~3.3V可调的电压输出了,我们这里可以接ADC的输入通道,比如PA0口,当滑动端往上滑时,电压增大,往下滑时,电压减小,另外注意一下这个电阻的阻值不要给太小,因为这个电阻两端也是直接跨接在电源正负极的,如果阻值大小,那这个电阻就会比较费电,再小就有可能发热冒烟了,一般至少要接KΩ级的电阻,比如这里接的是10K的电阻,这是电位器产生可调电压的电路。
image

然后中间的电路是传感器输出电压的电路,一般来说,像光敏电阻、热敏电阻、红外接收管、麦克风等等,都可以等效为一个可变电阻,那电阻值没法直接测量,所以这里就可以通过和一个固定电阻串联分压来得到一个反应电阻值电压的电路。那这里,传感器阻值变小时,下拉作用变强,输出端电压就下降,传感器阻值变大时,下拉作用变弱,输出端受上拉电阻的作用,电压就会升高,这个固定电阻一般可以选择和传感器阻值相近的电阻,这样可以得到一个位于中间电压区域比较好的输出,当然这里传感器和固定电阻的位置也可以换过来,这样的话,输出电压的极性就反过来了,这就是这个分压方法来输出传感器阻值的电路。

最后一个电路,这是一个简单的电压转换电路,比如你想测一个0~5V的VIN电压,但是ADC只能接收0~3.3V的电压,那就可以搭建一个这样的简易转换电路,在这里还是使用电阻进行分压,上面阻值17K,下面阻值33K,加一起是50K,所以根据分压公式,中间的电压就是VIN/50Kx33K,最后得到的电压范围就是0~3.3V,就可以进入ADC转换了,这就是这个简单的电压转换电路,如果你想采集5V,10V这些电压的话,可以使用这个电路,但是如果你电压再高一些,就不建议使用这个电路了,那可能会比较危险,高电压采集最好使用一些专用的采集芯片,比如隔离放大器等等,做好高低电压的隔离。

posted @ 2025-08-14 22:14  秦瑞迁  阅读(141)  评论(0)    收藏  举报