无人问津也好,技不如人也罢,都应静下心来,去做该做的事。
最近在学STM32,所以也开贴记录一下主要内容,省的过目即忘。视频教程为江科大(改名江协科技),网站jiangxiekeji.com
本期开始介绍STM32的ADC——模数转换器,对于GPIO来说,它只能读取引脚的高低电平,要么是高电平,要么是低电平,只有两个值。而使用ADC,我们就可以对这个高电平和低电平之间的任意电压进行量化,最终用一个变量来表示,读取这个变量,就可以知道引脚的具体电压到底是多少了。
所以ADC其实就是一个电压表,把引脚的电压值测出来,放在一个变量里。这就是ADC的作用。
ADC简介
STM32主要是数字电路,数字电路只有高低电平,没有几V电压的概念。所以如果想读取电压值,就需要借助ADC模数转换器来实现了。ADC读取引脚上的模拟电压,转换为一个数据,存在寄存器里,我们再把这个数据读取到变量里来。就可以进行显示、判断、记录等等操作了。
DAC则是数字电路到模拟电路的桥梁,之前学的PWM也有DAC这个功能,尤其在直流电机调速这些大功率的场景,使用PWM来等效模拟量,是比DAC更好的选择。所以可以看出PWM还是挤占了DAC的很多应用空间,目前DAC的应用主要是在波形生成这些领域,比如信号发生器、音频解码芯片等,这些领域PWM还是不好替代的。STM32F103C8T6没有DAC这个外设。
ADC的两个关键参数:分辨率,一般用多少位来表示,12位AD值,它的表示范围就是0-~2^12-1,就是量化结果的范围是0~4095,转换起来也很简单,除于一个固定系数就好;转换时间,AD转换是需要花一小段时间的,这里1us就表示从AD转换开始到产生结果,需要花1us的时间,对应AD转换的频率就是1MHz,这个就是STM32ADC的最快转换频率。
18个输入通道,外部信号源就是16个GPIO口,在引脚上直接接模拟信号就行了,不需要任何额外的电路,引脚就直接能测电压。2个内部信号源是内部温度传感器和内部参考电压,温度传感器可以测量CPU的温度,比如你电脑可以显示一个CPU温度,就可以用ADC读取这个温度传感器来测量;内部参考电压是一个1.2V左右的基准电压,这个基准电压是不随外部供电电压变化而变化的。如果芯片的供电电压不是标准的3.3V,那测量外部引脚的电压可能就不对,这时就可以读取这个基准电压进行校准,这样就能得到正确的电压值了。
规则组和注入组两个转换单元,这个就是STM32 ADC的增强功能了。普通的AD转换流程是,启动一次转换、读一次值,然后再启动、再读值,这样的流程。但是STM32的ADC比较高级,可以列一个组,一次性启动一个组,连续转换多个值。并且有两个组,一个是用于常规使用的规则组;一个是用于突发事件的注入组。
模拟看门狗自动监测输入电压范围,这个ADC,一般可以用于测量光线强度、温度这些值,并且经常会有个需求,就是如果光线高于某个阈值、低于某个阈值,或者温度高于某个阈值、低于某个阈值时,执行一些操作。这个高于某个阈值、低于某个阈值的判断,就可以用模拟看门狗来自动执行。模拟看门狗可以监测指定的某些通道,当AD值高于它设定的上阈值或者低于下阈值时,它就会申请中断,你就可以在中断函数里执行相应的操作,这样你就不用不断地手动读值,再用IF进行判断了。
程序现象
共有两个程序,第一个程序是AD单通道,在面包板上接一个电位器,用这个电位器产生一个0-3.3V连续变化的模拟电压信号,接到STM32的PA0,之后用STM32内部的ADC读取电压数据,
显示在屏幕上。这里屏幕第一行显示的是AD转换后的原始数据,第二行是经过处理后实际的电压值。往左拧,AD值减小,电压值也减,;AD值最小是0,对应的电压就是0V;往右拧,AD值变大,对应电压值也变大。STM32的ADC是12位的,所以AD结果最大值是4095,也就是2^12-1,对应电压是3.3V。
第二个是AD多通道
在第一个实验的基础上接多三个模块,把它们的AO(模拟电压输出端),分别接在了A1、A2、A3脚,加上刚才的电位器,总共4个输出通道。然后测出来的4个AD数据分别显示在屏幕上。
第一个电位器,看第一行的AD0。往右拧增大,和第一个程序一样;光敏电阻,看第二行的AD1,遮挡一下光敏电阻,光线减小,AD值增大;热敏电阻,看第三行的AD2,用手热一下这个热敏电阻,温度升高,AD值减小;最后是反射式红外传感器,手靠近,有反光,AD值减小。
逐次逼近型ADC
这里用ADC0809的框图介绍,因为STM32的ADC原理和这个是一样的。它是一个独立的8位逐次逼近型ADC芯片,以前单片机性能没这么强,需要外挂一个ADC芯片进行AD转换,现在很多都集成到芯片内部了,但原理是一样的。
看下结构吧,首先左边这里IN0~IN7,是8路输入通道,通过通道选择开关选择一路信号到比较器。左下的地址锁存和译码就是你想选中哪个通道,就把通道号放在这三个脚上,然后给一个锁存信号,上面这里对应的通路开关就可以自动拨好了。比较器的两个输入端,一个是待测信号,另一个是DAC的电压输出端,给它一个数据,它就可以输出数据对应的电压。DAC内部是使用加权电阻网络来实现的转换。比较器比较后,如果DAC输出的电压比较大,我就调小DAC数据,直到DAC输出的电压和外部通道输入的电压几乎相等,这样DAC输入的数据就是外部电压的编码数据了。通常用二分法来逼近,这个逼近的过程对二进制来说,就是从寄存器高位到低位依次判断是1还是0的过程。
STM32的ADC框图
在这里,左边是ADC的输入通道,包括16个GPIO、IN0-IN15,和两个内部的通道,一个是内部温度传感器;另一个是VREFIN(V Reference Internal),内部参考电压。总共是18个输入通道,然后送到模拟多路开关,可以指定我们想要选择的通道。然后输出到模数转换器,执行的我们上面讲过的逐次比较的过程,结果会直接放在上面这个数据寄存器里。我们读取数据寄存器就能知道ADC转换的结果了。这里转换时分成两个组,规则通道组和注入通道组,其中规则组可以一次性最多选中16个通道;注入组最多可以选中4个通道。比如你去餐厅点菜,普通的ADC是,你指定一个菜,老板给你做,然后做好了送给你;规则通道这里就是,你指定一个菜單,这个菜单最多可以填16个菜,老板就按照菜单的顺序依次做好,一次性给你端上来,当然你的菜单可以只写一个菜,这样就简化为普通模式。规则组只有一个寄存器,相当于桌子很小,只能上一个菜,你如果上16个菜,那不好意思,前15个菜都会被挤掉。所以对于规则组转换来说,如果使用这个菜单的话,最好配合DMA来实现。DMA是一个数据转运小帮手,它可以在每上一个菜时,把这个菜挪到其他地方去,防止被覆盖。规则组虽然可以同时转换16个通道,但是数据寄存器只能存一个结果,如果不想之前的结果被覆盖,那在转换完成之后,就要尽快把结果拿走;注入组就相当于餐厅的VIP座位,在这个座位上,一次性最多可以点4个菜,并目这里数据寄存器有4个,是可以同时上4个菜的,就不用担心数据覆盖。
一般情况下,我们使用规则组就完全足够了,如果要使用规则组的菜单,那就再配合DMA转运数据,就不用担心数据覆盖问题。接下来以规则组为主。
那对于STM32的ADC来说,触发ADC开始转换的信号有两种:
一种是软件触发,就是你在程序中手动调用一条代码,就河以启动转换了。
另一种是硬件触发,就是这里的这些触发源,这些触发源主要是来自定时器。有定时器的各个通道,还有TRGO定时器主模式的输出。
那因为ADC经常需要过一个固定时间段转换一次,比如每隔1ms转换一次,正常的思路就是,用定时器,每隔1s申请一次中断,在中断里手动开始一次转换,这样也是可以的,但是频繁进中断会对主程序有影响。所以对于这种需要频繁进中断,并目在中断里只完成了简单工作的情况,一般都会有硬件的支持。比如这里,就可以给TIM3定个1ms的时间,并且把TIM3的更新事件选择为TRGO输出,然后在ADC这里,选择开始触发信号为TIM3的TRGO,然后在ADC这里,选择开始触发信号为TIM3的TRGO,这样TIM3的更新事件就能通过硬件自动触发ADC转换了。这个过程不需要进中断,节省时间和资源。当然这里还可以选择外部中断引脚来触发转换,都可以在程序中配置。
STM32的ADC时钟源只能选择6分频、12MHz或8分频、9MHz
模拟看门狗,它里面可以存一个阈值高限和阈值低限,如果启动了模拟看门狗,并指定了看门的通道,那这个看门狗就会关注它看门的通道,一但超过这个阈值范围了,它就会乱叫,申请一个模拟看门狗的中断,最后通向NVIC。然后对于规则组和注入组而言呢,它们转换完成之后,也会有一个EOC转换完成的信号。在这里,EOC是规则组完成的信号,JEOC是注入组完成的信号。这两个信号会在状态寄存器里置一个标志位,我们读取这个标志位,就能知道是不是转换结束了。同时这两个标志位也可以去到NVIC,申请中断。如果开启了NVIC对应的通道,它们就会触发中断。
ADC基本结构
左边是输入通道,16个GPIO口,外加两个内部的通道。然后进入AD转换器,AD转换器里有两个组,一个是规则组,一个是注入组,规则组最多可以选中16个通道,注入组最大可以选择4个通道。然后转换的结果可以存放在AD数据寄存器里,其中规则组只有1个数据寄存器,注入组有4个。然后下面这里有触发控制, 提供了开始转换这个START信号,触发控制可以选择软件触发和硬件触发。硬件触发主要是来自于定时器,当然也可以选择外部中断的引脚。右边这里是来自于RCC的ADC时钟CLOCK,ADC逐次比较的过程就是由这个时钟推动的。然后上面,可以布置一个模拟看门狗用于监测转换结果的范围,如果超出设定的阈值,就通过中断输出控制,向NVIC申请中断。另外,规则组和注入组转换完成后会有个EOC信号,它会置一个标志位,当然也可以通向NVIC。最后右下角这里还有个开关控制,在库函数中,就是ADC_Cmd函数,用于给ADC上电的。
ADC输入通道
ADC1和ADC2的输入引脚是相同的,ADC1和ADC2可以分开工作,也一起工作。一起工作叫双ADC模式,比较复杂,了解即可。它俩可以配合组成同步模式、交叉模式等等模式。比如交叉模式,ADC1和ADC2交叉地对一个通道进行采样,这样就可以进一步提高采样率。
这里只有ADC1有通道16和17,ADC2和ADC3是没有的。
规则组的四种转换模式
分别是单次转换非扫描模式、连续转换非扫描模式、单次转换扫描模式和连续转换扫描模式。
那在我们ADC初始化的结构体里,会有两个参数,一个是选择单次转换还是连续转换的;另一个是选择扫描模式还是非扫描模式的。
下面的列表就是规则组里的菜单,你可以在这里“点菜”,就是写入你要转换的通道。在非扫描的模式下,这个菜单就只有第一个序列1的位置有效。在这里我们可以在序列1的位置指定我们想转换的通道,比如通道2,写到这个位置。然后,我们就可以触发转换,ADC就会对这个通道2进行模数转换,过段时间转换完成后,转换结果放在数据寄存器里,同时给EOC标志位置1,转换过程就结束了。我们判这个EOC标志位,如果转换完了,就可以在数据寄存器里读取结果。如果我们想再启动一次转换,那就需要再触发一次,转换结束,置EOC标志位,读结果。如果想换一个通道转换,那在转换之前,把第一个位置的通道2改成其他通道,然后再启动转换就行。这就是单次转换,非扫描模式。没有用到菜单列表。
接下来我们看一下连续转换,非扫描模式 ,首先,它还是非扫描模式,所以菜单列表就只用第一个。然后它与上一种单次转换不同的是,它在一次转换结束后不会停止,而是立刻开始下一轮的转换,然后一直持续下去。这样就只需要最开始触发一次,之后就可以一直转换了。这个模式的好处就是,开始转换之后不需要等待一段时间。因为它一直都在转换,所以你就不需要手动开始转换了,也不用判断是否结束,想要读AD值的时候,直接从数据寄存器取就是了。
然后继续看。单次转换,扫描模式 , 这个模式也是单次转换,所以每触发一次,转换结末后,就会停下来。下次转换就得再触发才能开始。然后它是扫描模式,这就会用到这个菜单列表了,你可以在这个菜单里点菜,比如第一个菜是通道2,第二个菜是通道5,等等等等。菜单里每个位置是通道几可以任意指定,并且也是可以重复的。然后初始化结构体里还会有个参数,就是通道数目,因为这16个位置你可以不用完,只用前几个,那你就需要再给一个通道数目的参数,告诉它,我有几个通道。比如这里指定通道数目为7,那它就只看前7个位置。然后每次触发之后,它就依次对这前7个位置进行AD转换,转换结果都放在数据寄存器里,这里为了防止数据被覆盖,就需要用DMA及时将数据挪走。那7个通道转换完成之后,产生EOC信号,转换结束。然后再触发下一次,就又开始新一轮的转换。这就是单次转换,扫描模式的工作流程。
那最后再看一下连续转换,扫描模式 ,就是一次转换完成后,立刻开始下一次的转换。
触发控制
数据对齐
我们这个ADC是12位的,它的转换结果就是一个12位的数据。但是这个数据寄存器是16位的,所以就存在一个数据对齐的问题。
数据右对齐:就是12位的数据向右靠,高位多出来的补零。一般用右对齐。这样读取这个16位寄存器,直接就是转换结果。
数据左对齐用法:如果你不需要那么高分辨率,那你就可以选择左对齐。然后再把这个数据的高8位取出来,这样就舍去了后面4位的精度。
转换时间
转换时间这个参数,我们一般不太敏感,因为AD转换都很快。如果不需要非常高速的转换频率,那转换时间就可以忽略了。
AD转换是需要时间的,主要花在采样、保持、量化、编码这四个步骤上。
为什么需要采样保持?这是因为,我们的AD转换,就是后面的量化编码,是需要一小段时间的,如果在这一小段时间里,输入的电压还在不断变化。那就没法定位输入电压到底在哪了是吧,所以在量化编码之前,我们需要设置一个采样开关。先打开采样开关,收集下外部的电压,比如可以用一个小容量的电容存储一下这个电压,存储好了之后,断开采样开关,再进行后面的AD转换。这样在量化编码的期间,电压始终保持不变。这样才能精确地定位未知电压的位置。
那采样保持的过程,需要闭合采样开关,过一段时间再断开,这里就会产生一个采样时间。这个采样时间可以在STM32中进行配置,采样时间越大,越能避免一些毛刺信号的干扰。不过转换时间也会相应延长。因为STM32的ADC是12位的,所以后面的量化编码最少也要12个ADC周期。
校准
这个校准过程是固定的,我们只需要在ADC初始化的最后,加几条代码就行了。至于怎么计算、怎么校准,不用管。
硬件电路
下面是三种常见的电路,第一个是一个电位器产生可调的电压,这里电位器的两个固定端,一端接3.3V,另一端接GND。这样中间的滑动端就可以输出一个0~3.3V可调的电压输出了。我们这里可以接ADC的输入通道,比做如PA0口。当滑动端往上滑时,电压增大,往下滑时,电压减小。需注意电阻RP1一般至少要接KΩ级的电阻,比如这里接的是10K的电阻。
中间的是传感器输出电压的电路,一般来说,像光敏电阻、热敏电阻、红外接收管、麦克风等等,都可等效为一个可变电阻。那电阻值没法直接测量,所以这里就可以通过和一个固定电阻串联分压,来得到一个反应电阻值电压的电路。那这里,传感器阻值变小时,下拉作用变强,输出端电压就下降。这个固定电阻R1一般可以选择和传感器阻值相近的电阻。这样可以得到一个位于中间电压区域比较好的输出。当然这里传感器和固定电阻的位置也可以换过来,这样的话,输出电压的极性就反过来了。这就是这个分压方法来输出传感器阻值的电路。
右边的电路是一个简单的电压转换电路,比如你想测一个0~5V的VIN电压,但是ADC只能接收0~3.3V的电压。那就可以搭建一个这样的简易转换电路,在这里还是使用电阻分压,上面阻值17K,下面阻值33K,加起是50K。所以根据分压公式,中间的电压就是VIN/50Kx33K。最后得到的电压范围就是0~3.3V,就可以进入ADC转换了。如果想采集5V、10V这种,是可以用这种电路,再高电压就不建议使用了。