stm32f103c8t6学习笔记(学习B站up江科大自化协)-ADC_gd32f103c8t6和stm32f103c8t6-程序员宅基地

技术标签: stm32  学习  笔记  

ADC简介

        ADC,英文全称是Analog to Digital Convert,意为模拟数字转换器,简称模数转换器,或者叫AD转换器,STM32主要是数字电路,数字电路只有高低电平,没有几V电压的概念,如果想读取电压值需借助ADC模数转换器来实现。ADC读取引脚上的模拟电压,转化成一个数据存在寄存器里,将这个数据读取到变量中就可以进行显示、判断、记录等操作。

        数字到模拟的桥梁除了DAC还有PWM,PWM只有完全导通和完全断开两种状态,这两种状态下都没有功率损耗,在直流电机调速这种大概率应用场景,使用PWM来等效模拟量比DAC更好,PWM电路更简单常用。DAC的应用主要在波形的生成的领域,比如信号发生器、音频解码器等等。本型号的STM32没有DAC外设,自行了解即可。

        12位逼近型ADC和1us转换时间,设计到两个关键参数,第一个是分辨率,一般用多少位来表示,12位AD值的范围就是0 ~ 2^{12} - 1,就是量化结果是0 ~ 4095,位数越高量化结果越精细,对应分辨率越高。转换时间也就是转换频率,AD转换需要一小段时间,1us表示从AD转换开始到产生结果需要花1us的时间,对应AD转换频率就是1MHz,这也是STM32的最快转换频率。

        ADC的输入电压一般要求都是在芯片的负极和正极之间变化的,最低电压是负极0V,最高电压是正极3.3V,经过ADC转换之后最小值是0,最大值是4095,最大最小分别对应,之间的值也一一对应的线性关系,计算简单乘除系数即可。

        外部信号源就是16个GPIO口,在引脚上直接接模拟信号即可,不需要任何额外的电路,引脚就能直接测电压,较为方便。2个内部信号源是内部温度传感器和内部参考电压,温度传感器可以测量CPU的温度,内部参考电压是一个1.2V左右的基准电压,不随外部供电电压的变化而变化。如果芯片的供电不是标准的3.3V,测量外部引脚的电压可能不对,此时可读取这个基准电压进行校准,得到正确的电压值。

        ADC的增强功能,普通的AD转换流程,启动一次转换读一次值,再启动再读值。STM32的ADC可以列一个组,一次性启动一个组,连续转换多个值,有两个组,一个用于常规使用的规则组,一个是用于突发事件的注入组

        模拟看门狗自动检测输入电压范围。ADC一般可以用于测量光线强度,温度这些值,当温度高于或低于某个阈值会执行一些操作,这个判断就可以用模拟看门狗去自动执行,模拟看门狗可以用于检测指定收的某些通道,当AD值高于或低于阈值时就会申请中断,并可以在中断之中执行相应的操作,省去不断手动读值再用if判断的操作

        stm32f103c8t6只有ADC1和ADC2两个外设,10个外部输入通道,也就是它最多只能测量10个外部引脚的模拟信号。前面的16个外部信号源是这个系列最多有16个外部信号源,但是这个芯片引脚较少,有些引脚未被引出。如果需要更多通道可以选择其他型号

逐次逼近型ADC

        ADC0809的内部电路图,是一个独立的8位逐次逼近型ADC芯片。左边的IN0~IN7是输入的八路通道,通过通道选择开关选择一路,输入到比较器的上方进行转换,下边是地址锁存和译码,想选择哪个通道,就把通道号放在这三个脚上,给一个锁存信号,上面对应的通道选择开关就可以自动拨好了,这部分相当于可以通过模拟信号的数据选择器。ADC转换是一个很快的过程,给一个开始信号过几us便转换完成,如果想转换多路信号,只需一个AD转换器,加上一个多路选择开关,想转换哪一路拨动对应的开关,选中对应通道,然后开始转换即可。这里的ADC0809只有八个输入通道,STM32内部有18个输入通道,对应图中就是18路输入的多路开关

        对于判断电压对应的编码数据的方法,需要用到逐次逼近的方法来一一比较,图中三角形是一个电压比较器,可以判断两个输入信号电压的大小关系,输出一个高低电平指示谁大谁小,他的两个输入端,一个是待测的电压(上),另一个是DAC电压输出端,DAC是数模转换器,给他一个数据就可以输出对应的电压,DAC内部是使用加权电阻网络进行转换。

        将一个外部通道输入的位置编码的电压,和一个DAC输出的已知编码的电压,同时输入到电压比较器进行判断,如果DAC输出电压比较大,那就调小DAC数据,如果DAC输出比较小,那么就增大DAC数据,直到DAC输出电压和外部通道输入电压近似相等,这样DAC输入的数据就是外部电压的编码数据。

        电压调节的过程是由逐次逼近寄存器SAR完成的,为了最快找到未知电压的编码,通常会使用二分法进行查找,比如这里是8位的DAC,那编码就是0~255,第一次比较的时候,给DAC输入255的一半128进行比较,如果DAC电压大了,第二次比较的时候给128的一半64,如果还大第三次比就给32,如果这次小了那第四次就给32~64之间的值然后继续,以最快找到未知电压的编码。在这个过程中如果用二进制表示的话,会发现128 64 32 正好是二进制每一位的位权,这个判断过程相当于是对二进制从高位到低位依次判断是1还是0的过程。对于8位的ADC,从高位到低位判断8次就可以找到未知电压的编码。对于12位的ADC就需要判断12次。

        AD转换结束后,DAC的输入数据就是未知电压的编码,通过右边三态锁存缓冲器进行输出,八位就有8根线,十二位就有12根线。最右上角的EOC是End Of Convert转换结束信号。Start是开始转换,给一个输入脉冲开始转换,Clock是ADC时钟,因为ADC内部是一步一步进行判断的,需要时钟推动这个过程。

        下面VREF+和VREF-是DAC的参考电压,比如给一个数据255会对应5V还是3.3V将由这个参考电压决定,这个DAC的参考电压也决定了ADC的输入范围,所以他也是ADC参考电压。左边VCC和GND是整个芯片电路的供电,通常参考电压的正极VREF+和VCC是一样的,会接在一起,参考电压的负极和GND也是一样的,接在一起,一般情况下,ADC的电压输入范围就和ADC的供电一样

ADC框图

        左边是ADC的输入通道,包括16个GPIO口,IN0~IN15,和两个内部的通道,一个是内部温度传感器,另一个是VREFINT(V Reference Internal),内部参考电压。然后到达模拟多路开关,可以指定想要选择的通道,右边是多路开关的输出,进入到模拟至数字转换器。

        对于普通的ADC,多路开关一般只选中一个,选中某一个通道后开始转换,等待转换完成取出结果。但是在这里的较为高级,这里可以同时选中多个,而且转换的时候还会分成规则通道组和注入通道组两个组,其中规则组可以一次性选择最多16个通道,注入组最多可以选择4个。     
        举个例子,将ADC类比成去餐厅点菜,普通ADC是指定一个菜让老板去做,做好了端上来。这里的ADC是,指定一个菜单,最多可以填16个菜,直接将菜单给老板,就会按照菜单的书序依次做好,一次性给端上来,以此提高效率,菜单也可以写一个菜,这样就会简化成普通的模式。

        菜单分为两种,一种是规则组,可以同时上16个菜,但是规则组只有一个数据寄存器,可以理解成桌子比较小,只能上一个菜,如果上16个,那么前15个都会被挤掉,只能得到第十六个。对于规则组来说,如果使用这个最好配合DMA来实现,DMA是数据转运的小帮手,在每上一个菜后把这个菜移动到其他地方去,防止数据被覆盖。规则组虽然可以同时转换16个通道,但是数据寄存器只能存一个结果,如果不想之前的结果被覆盖,在转换完成之后要尽快把结果拿走。
        注入组相对比较高级,相当于餐厅的VIP座位,在这个座位上最多可以一次性点4个菜,并且数据寄存器有四个,可以同时上四个菜,不用担心数据被覆盖的问题

        左上角是VREF+和VREF-、VDDA和VSSA,前面两个是ADC的参考电压,决定了ADC输入电压的范围,后面两个是ADC的供电引脚,一般情况下VREF+要接VDDA,VREF-要接VSSA,在stm32f103c8t6芯片上没有VREF+和VREF-引脚,在内部已经和VDDA和VSSA接在一起。VDDA和VSSA是内部模拟部分的电源,比如ADC、RC振荡器,锁相环等。在这里VDDA接3.3V,VSSA接GND,所以ADC的输入范围就是0~3.3V。
 

        模数转换器执行逐次比较的过程,转换结果会放在数据寄存器,读取寄存器便可知道ADC转换的结果。

        右边ADCCLK是ADC的时钟,适用于驱动内部逐次比较的时钟。

        两个数据寄存器用于存放转换结果

         对于STM32的ADC,触发ADC开始转换的信号有两种,一种是软件触发,在程序中手动调用一条代码就可以启动转换,另一种是硬件触发,即下图的触发源(上边是注入组,下边是规则组),这些触发源主要来自于定时器,有定时器的各个通道,还有TRGO定时器主模式的输出。定时器可以通向ADC、DAC这些外设,用于触发转换,由于ADC需要过一段固定时间转换一次,每隔1ms转换一次,正常的思路就是用定时器,每隔1ms申请一次中断,在中断里手动开启一次转换,但是频繁进中断对程序有一定影响,不同中断之间优先级不同,会导致某些中断无法得到及时相应,如果触发ADC转换的中断不能及时响应,那么ADC的转换频率会收到影响。

        对于这种需要频繁进中断且只完成了简单工作的情况,一般会有硬件的支持。比如这里可以给TIM3定一个1ms的时间,并且把TIM3的更新时间选择TRGO输出,在ADC这里选择开始触发信号为TIM3的TRGO,这样TIM3的更新事件就可以通过硬件自动触发ADC转换,整个过程不需要进中断,节省了中断资源。这里还可以使用外部中断引脚来触发转换,可以在程序中进行配置。

         ADC预分频器来自于RCC,APB2时钟72MHz,通过ADC预分频器进行分频,得到ADCCLK,最大是14MHz,这个预分频器可以选择2、4、6、8分频,如果选择2分频,72M/2=36M超出范围,4分频后是18M也超,对于ADC预分频器只能选择6分频就是12M和8分频就是9M这两个值。

        上面的DMA请求,用于触发DMA进行数据的转运

        模拟看门狗,里面可以存一个阈值搞限和阈值低限,如果启动了模拟看门狗并且指定了看门通道,那看门狗就会关注它看门的通道,一旦超过这个阈值范围,就会在上边申请一个模拟看门狗的中断,最后通向NVIC。

        对于规则组和注入组,在转换完成之后也会有一个EOC转换完成的信号,在这里EOC是规则组完成的信号,JEOC是注入组完成的信号,这两个信号会在状态寄存器里置一个标志位,通过读取这个标志位可得知是不是转换结束。这两个标志位也可以取到NVIC申请中断,如果开启了NVIC对应的通道就会触发中断

 ADC基本结构

        ·左边是16个GPIO口(上)加两个内部通道(下)

        ·进入AD转换器,里边有规则组和注入组两个组,规则组最多可以选择16个通道,注入组最多可以选择4个通道,转换的结果可以存放在AD数据寄存器里,其中规则组只有1个数据寄存器,注入组有4个。

        ·下边有触发控制,提供了开始转换这个START信号,触发控制可以选择软件触发和硬件触发,硬件触发主要是定时器,也可以选择外部中断的引脚。

        ·底部右边是来自RCC的时钟CLOCK,ADC逐次比较的过程就是由这个时钟推动的。

        ·在上面可以布置一个模拟看门狗用于检测转换结果的范围,如果超过设定的阈值,就会通过中断输出控制,像NVIC申请中断。

        ·规则组和注入组转换完成之后会有个EOC信号,会置一个标志位,也会通向NVIC。

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

输入通道

        图中显示的是ADC通道和引脚复用的关系,这里有通道0~17,共18个通道,通道16对应ADC1的温度传感器,通道17对应ADC1内部的参考电压,只有ADC1有通道16和17,ADC2和ADC3是没有的。

        GPIO的引脚中ADC1和ADC2是完全相同的,ADC3中间会有些变化,不过stm32f103c8t6这个芯片没有ADC3,所以后面部分无需理会。这里的引脚是PA0~PA7,PB0~PB1,PC0~PC5,由于芯片没有PC0~PC5,所以下面对应的通道也不存在,

         如下图可见,ADC12_IN0对应的是PA0引脚,IN1对应PA1 引脚,IN2、3、4、5、6、7、8、9分别对应PA2~PB1, 所以这个芯片对应只能有16个外部输入通道,

        ADC12_IN0的意思是ADC1和ADC2的IN0都是在PA0上的,下边都是ADC12,说明ADC1和ADC2的引脚全都是相同的,既然相同,那么肯定有着他的特殊功能,叫做双ADC模式。

        双ADC模式较为复杂,双ADC模式就是ADC1 和ADC2一起工作,可以配合组成同步模式、交叉模式。比如交叉模式,ADC1和ADC2 交叉着对一个通道进行采样,可以进一步提高采样率。ADC1和ADC2也可以分开使用,分别对不同引脚进行采样

转换模式

一、单次转换,非扫描模式

        如图左边的列表是规则组里面的菜单,有16个空位分别是序列1~16,在此处可以写入想要转换的通道,在非扫描模式下,这个菜单只有第一个序列1的位置有效,此时菜单选中一组的方式就退化位简单的选中一个的方式了。在序列1可以指定我们想转换的通道,比如图中将通道2写到序列1的位置,之后触发转换,ADC就会对这个通道进行模数转换,转换完成之后,转换结果放在数据寄存器里,同时EOC标志位置1,完成转换过程。

        对EOC标志位进行判断,如果转换完了,就可以在数据寄存器里读取转换结果,如果想再启动一次转换,就需要再出发一次,转换结束置EOC标志位,读取结果。如果想换一个通道转换,那么在转换之前,把第一个位置的通道2改成其他通道,再启动转换即可。

二、连续转换,非扫描模式

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

三、单次转换,扫描模式

        单次转换,每触发一次,转换结束之后都会停下来,下次转换需再次触发才会开始。扫描模式会用到菜单列表,列表中每个位置是通道几可以任意指定,并且可以重复。在初始化结构体里面有个参数,就是通道数目,16个位置可以仅使用前几个,通道数目给7,那么只会看前七个位置,每次触发之后就对前七个位置进行AD转换,转换结果都放在数据寄存器里,为了防止数据被覆盖,需要用DMA及时将数据移走,7个通道转换完成之后,产生EOC信号,转换结束。

四、单次转换,扫描模式         

        在第三个  单次转换 扫描模式  的基础上稍微变化,一次转换完成后,立刻开始下一次的转换,和上面非扫描模式的单次和连续是类似的。

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

触发控制

        这个表是规则组的触发源,表里面有来自定时器的信号,还有来自引脚或定时器的信号,具体是引脚还是定时器,要用AFIO重映射来确定。软件控制位就是软件触发。关于这些触发信号如何选择,可以通过设置右边的寄存器来完成,使用库函数则直接给个参数即可。

数据对齐

        由于ADC是12位的,那么转换结果就是一个12位的数据,但是数据寄存器是16位的,那么就存在一个数据对齐的问题。

        第一种是数据右对齐,就是数据向右靠,高位多出来的几位就补0

        第二种事数据左对齐,12位的数据向左靠,低位多出来的几位补0

一般使用的都是第一种右对齐,这样的话读取16位的寄存器,直接就是转换结果 ,如果选择左对齐,直接读数据的话,得到的数据会比实际的大。数据左对齐实际上是将数据左移了4次,二进制有个特点,将数据左移一位,等效于将这个数据乘以2,这里左移了4位就相当于把结果乘上了16,直接读的话会比实际值大16倍。

        左对齐的用途:如果不想要右对齐那么高的分辨率,如果觉得0~4095太大了,就做个简单的判断,选择左对齐,将数据的高八位取出来,这样便舍弃了后四位的精度,那么这个12位的ADC便退化成8位的ADC

转换时间

        转换时间的参数一般不太敏感,一般AD转换都很快,如果不需要非常高速的转换频率,那转换时间就可以忽略了。AD转换的时候需要花小段时间, 在AD转换的步骤中,有4步分别是采样、保持、量化、编码,其中采样和保持可以放一起,量化和编码也可以放一起,总共是两大步。

        量化、编码就是之前ADC逐次比较的过程,比较花时间,一般位数越多花的时间越长。

        采样、保持的作用在于,因为AD转换需要一小段时间,如果在这一小段时间里,输入的电压还在不断的发生变化,那么没法办定位输入电压到底在哪,在量化编码之前需要设置一个采样开关,打开采样开关收集外部电压,可以使用一个小容量的电容存储这个电压,存储好之后再断开采样开关,进行后面的AD转换,在量化编码的期间电压始终保持不变,以便精确的定位位置电压的位置。保持采样的过程需要闭合采样开关,过一段时间再断开,这期间会产生一个采样时间

        这个采样时间引申出了一个公式,ADC总采样时间为T_{CONF}=采样时间+12.5个ADC周期,公式中的采样时间就是采样保持花费的时间,可以在程序中进行配置,采样时间越大越能避免一些毛刺信号的干扰,不过转换时间也会相应延长。12.5个ADC周期是量化编码花费的时间,因为是12位的ADC,所以需要花费12个周期,这里多了0.5个周期,可能是执行其他任务花的时间。

        ADC周期就是从RCC分频过来的ADCCLK,这个ADCCLK最大是14MHz,图中下边有个例子,当ADCCLK=14MHz时,采样时间为1.5个ADC周期,T_{CONF}=1.5+12.5=14个ADC周期,在14MHz的ADCCLK的情况下就等于1us,这是关于最快1us时间的来源,如果采样周期再长些,就达不到1us了,另外也可以把ADCCLK的时钟设置超过14MHz,这样的ADC在超频,使用的时间比1us还短,但是不能保证稳定性。

校准

        这个校准过程是固定的,我们只需要在ADC初始化的最后,加几条代码即可,详细的计算和校准方式可不管,了解即可

硬件电路

        第一个是电位器产生可调的电压,电位器的两个固定端一个接3.3V,另一个接GND,中间的滑动端就可以输出一个0~3.3V的可调的电压输出,可以接上ADC的输入通道,比如PA0口,当滑动端网上滑动时电压增大,往下滑动时电压减小,注意电阻的阻值不能给太小,电阻两端是直接跨接在电源正负极的,如果电阻太小会比较费电,再小可能发热冒烟,一般需要接kΩ级的电阻,这里是10k。

        第二个是传感器产生可调电压的电路,一般来说像是光敏电阻、热敏电阻、红外接收管、麦克风等,都可以等效为一个可变电阻,由于可变电阻没法测量,可以通过和一个固定电阻串联分压,得到一个反映电阻值电压的电路。当传感器阻值变小时,下拉作用变强,输出端电压下降,传感器阻值变大时,下拉作用变弱,输出端受上拉电阻的作用,电压就会升高,固定电阻一般可选择和传感器阻值相近的电阻,以便于得到一个中间区域电压比较好的输出。传感器和固定电阻的位置也可以交换,不过输出电压的极性将会因此相反。

        第三个是一个简单的电压转换电路,比如想测一个0~5V的VIN电压,但是ADC只能接收3.3V的电压,可搭建一个如上图所示的简易电路,使用电阻进行分压,上边阻值17k,下边阻值33k,加起来是50k,根据分压公式,中间的电压就是VIN/50*33,得到的电压范围就是0~3.3V,可以进入ADC转换,如果想采集5V、10V的电压可以使用这些电路,但是电压过高不建议使用这种电路,比较危险,简易使用一些专用的采集芯片,比如隔离放大器等等,做好高低电压的隔离,保证电路的安全

 接线图

        芯片上方PA0~PB1部分是ADC的10个通道,可以选择,其余的不是,不可以接模拟电压。

函数解析

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);	//用于给ADC上电
void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);	
//用于开启DMA输出信号 如果使用DMA转运数据就得调用这个函数
void ADC_ITConfig(ADC_TypeDef* ADCx, uint16_t ADC_IT, FunctionalState NewState);	
//中断输出控制 用于控制某个中断能不能通往NVIC


void ADC_ResetCalibration(ADC_TypeDef* ADCx);	//复位校准
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);	//获取复位校准状态
void ADC_StartCalibration(ADC_TypeDef* ADCx);	//开始校准
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);	//获取开始校准状态
//这部分函数在ADC初始化完成之后依次调用即可


void ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
//ADC软件开始转换控制 用于软件触发的函数 调用一下即可软件触发转换
FlagStatus ADC_GetSoftwareStartConvStatus(ADC_TypeDef* ADCx);
//ADC获取软件开始转换状态 无法用来判断转换是否结束 
//SWSTART这一位的作用是开始转换规则通道 由软件设置该位以启动转换 转换开始后硬件马上清除此位
//这里两个函数中的第一个函数是给SWSTART位置1 以开始转换 第二个函数是返回SWSTART的状态 
//由于SWSTART开始后就立刻清零了 所以这个函数的返回值跟转换是否结束没有关系
//这个函数其实没啥用 一般不用这个函数


void ADC_DiscModeChannelCountConfig(ADC_TypeDef* ADCx, uint8_t Number);
//配置每隔几个通道间断一次
void ADC_DiscModeCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
//是不是启用间断模式


void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel, uint8_t Rank, uint8_t ADC_SampleTime);
//ADC规则组通道配置 (较为重要)
//作用是给序列的每个配置填写指定通道  ADC_Channel是想指定的通道  
//Rank就是序列几的位置 ADC_SampleTime是指定通道的采样时间


void ADC_ExternalTrigConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);
//ADC外部触发转换控制 就是是否允许外部触发转换


uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
//ADC获取转换值(重要)


uint32_t ADC_GetDualModeConversionValue(void);	
//ADC获取双模式转换值 这个是双ADC模式读取转换结果的函数


//下列函数都带有injected 都是关于注入组的函数
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);
uint16_t ADC_GetInjectedConversionValue(ADC_TypeDef* ADCx, uint8_t ADC_InjectedChannel);


//对模拟看门狗进行配置
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);
//配置看门的通道


void ADC_TempSensorVrefintCmd(FunctionalState NewState);	//ADC温度传感器 内部参考电压控制
//用来开启内部两个通道 如果要用这两个通道 需要调用这个函数


FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);	
//获取标志位状态 参数给EOC的标志位 判断EOC标志位是否置1 
//如果转换结束 EOC标志位置1 调用这个函数判断标志位 能正确判断转换是否结束


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);	//清除中断挂起位 

AD单通道

单次转换非扫描
AD.c部分的代码
void AD_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);	//6分频 分频之后ADCCLK = 72MHz / 6 = 12MHz
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AIN;	
	//在AIN模式下 GPIO口是无效的 断开GPIO口防止输入输出对模拟电压造成干扰 AIN模式算是ADC的专属模式
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);	
	
	//选择规则组的输入通道
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
	//目前只有PA0一个通道 使用的是非扫描模式 所以指定的通道就放在序列1的位置
	//需要快的转换就选择小的参数 需要稳定的选择大参数 没要求则任选 此时采样时间为55.5个ADCCLK的周期
	
	//如果需要在序列2的位置写入其他通道 那就复制代码把序列数递增 每多一个序列数递增+1 并指定想要的通道
	//ADC_RegularChannelConfig(ADC1,ADC_Channel_0,2,ADC_SampleTime_55Cycles5);
	//每个通道可以选择不一样的采样时间 修改最后一个参数即可
	
	//初始化ADC
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
	//选择连续转换or单次转换 enable是连续模式 disable是单次模式
	ADC_InitStructure.ADC_DataAlign	= ADC_DataAlign_Right;	//数据对齐
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	//外部触发转换选择 这里不适用外部触发 none 使用软件触发
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;//选择工作在独立模式还是双ADC模式
	ADC_InitStructure.ADC_NbrOfChannel = 1;	//通道数目 指定在扫描模式下会用到几个通道 
	//这个参数在扫描模式下需要用 非扫描模式下整个列表只有第一个序列有效 写多少都没用
	ADC_InitStructure.ADC_ScanConvMode = DISABLE; 
	//扫描模式or非扫描模式 enable是扫描模式 disable是非扫描模式
	ADC_Init(ADC1,&ADC_InitStructure);
	
	//开启ADC的电源
	ADC_Cmd(ADC1,ENABLE);
	
	//这里ADC_Cmd函数放在校准函数前面是对的,up给的数据手册是2010年翻译的,是错误的
	// 2017年的数据手册已更正,实际上正确的表述应该是:ADC上电后最少两个周期才能校准
	
	
	ADC_ResetCalibration(ADC1);	//复位校准
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);	//获取复位校准状态
	//加上while循环 如果没有校准完成就在while空循环里等待
	//一旦标志位被硬件清0 这个空循环会自动跳出
	ADC_StartCalibration(ADC1);	//开始校准
	while(ADC_GetCalibrationStatus(ADC1) == SET);	//获取开始校准状态
	//这部分函数在ADC初始化完成之后依次调用即可
}
uint16_t AD_GetValue(void)
{
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发转换函数
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//第二个参数 规则组转换完成标志位
	//具体配置时间计算:通道的采样周期是55.5 转换周期是固定的12.5 加在一起是68个周期
	//前面配置的ADCCLK是72MHz的6分频 就是12MHz 12MHz进行68个周期才能转换完成
	//最终的时间是 1/12M * 68 = 5.6us 所以这个while循环大约会等待5.6us
	return ADC_GetConversionValue(ADC1); //返回值是ADC1的转换结果
	
}

main.c部分代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"

uint16_t ADvalue;
float Voltage;

int main()
{
	OLED_Init();
	AD_Init();
	
	OLED_ShowString(1,1,"ADvalue:");
	OLED_ShowString(2,1,"Voltage:0.00V");
	
	while(1)
	{
		ADvalue = AD_GetValue();	//启动等待读取一次性完成 返回值直接就是结果
	
		OLED_ShowNum(1,9,ADvalue,4);
		
		Voltage = (float)ADvalue / 4095 *3.3;	//因为advalue是整数 除以4095后会舍弃小数部分 
		//会导致计算错误 所以先类型强转为float
		//实际上ADvalue = 4096时才对应3.3V 会有一个数的偏差 所以AD值最大4095实际上对应是3.3小一点
		//无法达到满量程3.3V 受限于ADC的结构
		
		OLED_ShowNum(2,9,Voltage,1);
		OLED_ShowNum(2,11,(uint16_t)(Voltage *100) % 100,2);		
		//先乘以100倍 比如原来是1.23 现在就是123 然后对100取余 就是23 这样就把1.23的小数部分取出来
		//显示在第11列 由于浮点数是不能取余的 所以voltage乘以100后要()起来 然后进行强制类型转换变成整数
		
		Delay_ms(100);
	
	}
}
连续转换非扫描

        这个模式的好处是不需要连续触发,也不需要等待转换完成的。在AD.c的代码中稍作修改,将下列函数改成enable

	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;

连续转换模式只需要在一开始时触发一次即可,所以软件触发的函数可以挪到初始化的最后,在初始化完成之后触发一次即可。这时内部的ADC就会一次接着一次、连续不断的对我们指定的通道0进行转换,将结果存放在数据寄存器里,此时数据寄存器会不断刷新最新的转换结果,所以在AD_Getvalue里面就不需要判断标志位了,直接返回数据寄存器的值就行

        这种模式的好处是对CPU占用更小,不需要一直调用

uint16_t AD_GetValue(void)
{

	return ADC_GetConversionValue(ADC1); //返回值是ADC1的转换结果
	
}

AD多通道

接线图

        可以通过单次转换非扫描模式 来实现多通道,只需要在每次触发转换之前,手动更改一下列表第一个位置的通道即可。比如第一次转换先写入通道0之后触发、等待、读值,第二次转换再把通道0改成通道1,之后触发、转换、读值,第三次转换再改成通道2等等。在转换前先指定通道再启动转换,以此实现多通道转换的功能。

        这里的函数将选择通道的代码放到这里,并将传入参数改为指定的通道,这样调用函数时返回值就是指定参数的结果。

uint16_t AD_GetValue(uint8_t ADC_Channel)	
{
	//选择规则组的输入通道
	ADC_RegularChannelConfig(ADC1,ADC_Channel,1,ADC_SampleTime_55Cycles5);
	
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发转换函数
	while(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC) == RESET);//第二个参数 规则组转换完成标志位

	return ADC_GetConversionValue(ADC1); //返回值是ADC1的转换结果
	
}

        由于使用的通道是0、1、2、3,所以前面的初始化也需要开启对应的GPIO

main.c部分的代码

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"

uint16_t AD0,AD1,AD2,AD3;
int main()
{
	OLED_Init();
	AD_Init();
	
	OLED_ShowString(1,1,"AD0:");
	OLED_ShowString(2,1,"AD1:");
	OLED_ShowString(3,1,"AD2:");
	OLED_ShowString(4,1,"AD3:");
	
	while(1)
	{
		
		AD0 = AD_GetValue(ADC_Channel_0);
		AD1 = AD_GetValue(ADC_Channel_1);
		AD2 = AD_GetValue(ADC_Channel_2);
		AD3 = AD_GetValue(ADC_Channel_3);
		
		OLED_ShowNum(1,5,AD0,4);
		OLED_ShowNum(2,5,AD1,4);
		OLED_ShowNum(3,5,AD2,4);
		OLED_ShowNum(4,5,AD3,4);
		
		Delay_ms(10);
	
	}
}

        烧录完成之后,四个通道可以分别接收不一样的ADC

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_74859061/article/details/136856926

智能推荐

JavaScript学习笔记_curry函数未定义-程序员宅基地

文章浏览阅读343次。五种原始的变量类型1.Undefined--未定义类型 例:var v;2.String -- ' '或" "3.Boolean4.Number5.Null--空类型 例: var v=null;Number中:NaN -- not a number非数本身是一个数字,但是它和任何数字都不相等,代表非数,它和自己都不相等判断是不是NaN不能用=_curry函数未定义

兑换码编码方案实践_优惠券编码规则-程序员宅基地

文章浏览阅读1.2w次,点赞2次,收藏17次。兑换码编码设计当前各个业务系统,只要涉及到产品销售,就离不开大大小小的运营活动需求,其中最普遍的就是兑换码需求,无论是线下活动或者是线上活动,都能起到良好的宣传效果。兑换码:由一系列字符组成,每一个兑换码对应系统中的一组信息,可以是优惠信息(优惠券),也可以是相关奖品信息。在实际的运营活动中,要求兑换码是唯一的,每一个兑换码对应一个优惠信息,而且需求量往往比较大(实际上的需求只有预期_优惠券编码规则

c语言周林答案,C语言程序设计实训教程教学课件作者周林ch04结构化程序设计课件.ppt...-程序员宅基地

文章浏览阅读45次。C语言程序设计实训教程教学课件作者周林ch04结构化程序设计课件.ppt* * 4.1 选择结构程序设计 4.2 循环结构程序设计 4.3 辅助控制语句 第四章 结构化程序设计 4.1 选择结构程序设计 在现实生活中,需要进行判断和选择的情况是很多的: 如果你在家,我去拜访你 如果考试不及格,要补考 如果遇到红灯,要停车等待 第四章 结构化程序设计 在现实生活中,需要进行判断和选择的情况..._在现实生活中遇到过条件判断的问

幻数使用说明_ioctl-number.txt幻数说明-程序员宅基地

文章浏览阅读999次。幻数使用说明 在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情。 因为设备都是特定的,这里也没法说。关键在于怎样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径 。 命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,利_ioctl-number.txt幻数说明

ORB-SLAM3 + VScode:检测到 #include 错误。请更新 includePath。已为此翻译单元禁用波浪曲线_orb-slam3 include <system.h> 报错-程序员宅基地

文章浏览阅读399次。键盘按下“Shift+Ctrl+p” 输入: C++Configurations,选择JSON界面做如下改动:1.首先把 “/usr/include”,放在最前2.查看C++路径,终端输入gcc -v -E -x c++ - /usr/include/c++/5 /usr/include/x86_64-linux-gnu/c++/5 /usr/include/c++/5/backward /usr/lib/gcc/x86_64-linux-gnu/5/include /usr/local/_orb-slam3 include 报错

「Sqlserver」数据分析师有理由爱Sqlserver之十-Sqlserver自动化篇-程序员宅基地

文章浏览阅读129次。本系列的最后一篇,因未有精力写更多的入门教程,上篇已经抛出书单,有兴趣的朋友可阅读好书来成长,此系列主讲有理由爱Sqlserver的论证性文章,希望读者们看完后,可自行做出判断,Sqlserver是否真的合适自己,目的已达成。渴望自动化及使用场景笔者所最能接触到的群体为Excel、PowerBI用户群体,在Excel中,我们知道可以使用VBA、VSTO来给Excel带来自动化操作..._sqlsever 数据分析

随便推点

智慧校园智慧教育大数据平台(教育大脑)项目建设方案PPT_高校智慧大脑-程序员宅基地

文章浏览阅读294次,点赞6次,收藏4次。教育智脑)建立学校的全连接中台,对学校运营过程中的数据进行处理和标准化管理,挖掘数据的价值。能:一、原先孤立的系统聚合到一个统一的平台,实现单点登录,统一身份认证,方便管理;三、数据共享,盘活了教育大数据资源,通过对外提供数。的方式构建教育的通用服务能力平台,支撑教育核心服务能力的沉淀和共享。物联网将学校的各要素(人、机、料、法、环、测)全面互联,数据实时。智慧校园解决方案,赋能教学、管理和服务升级,智慧教育体系,该数据平台具有以下几大功。教育大数据平台底座:教育智脑。教育大数据平台,以中国联通。_高校智慧大脑

编程5大算法总结--概念加实例_算法概念实例-程序员宅基地

文章浏览阅读9.5k次,点赞2次,收藏27次。分治法,动态规划法,贪心算法这三者之间有类似之处,比如都需要将问题划分为一个个子问题,然后通过解决这些子问题来解决最终问题。但其实这三者之间的区别还是蛮大的。贪心是则可看成是链式结构回溯和分支界限为穷举式的搜索,其思想的差异是深度优先和广度优先一:分治算法一、基本概念在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两_算法概念实例

随笔—醒悟篇之考研调剂_考研调剂抑郁-程序员宅基地

文章浏览阅读5.6k次。考研篇emmmmm,这是我随笔篇章的第二更,原本计划是在中秋放假期间写好的,但是放假的时候被安排写一下单例模式,做了俩机试题目,还刷了下PAT的东西,emmmmm,最主要的还是因为我浪的很开心,没空出时间来写写东西。  距离我考研结束已经快两年了,距离今年的考研还有90天左右。  趁着这个机会回忆一下青春,这一篇会写的比较有趣,好玩,纯粹是为了记录一下当年考研中发生的有趣的事。  首先介绍..._考研调剂抑郁

SpringMVC_class org.springframework.web.filter.characterenco-程序员宅基地

文章浏览阅读438次。SpringMVC文章目录SpringMVC1、SpringMVC简介1.1 什么是MVC1.2 什么是SpringMVC1.3 SpringMVC的特点2、HelloWorld2.1 开发环境2.2 创建maven工程a>添加web模块b>打包方式:warc>引入依赖2.3 配置web.xml2.4 创建请求控制器2.5 创建SpringMVC的配置文件2.6 测试Helloworld2.7 总结3、@RequestMapping注解3.1 @RequestMapping注解的功能3._class org.springframework.web.filter.characterencodingfilter is not a jakart

gdb: Don‘t know how to run. Try “help target“._don't know how to run. try "help target".-程序员宅基地

文章浏览阅读4.9k次。gdb 远程调试的一个问题:Don't know how to run. Try "help target".它在抱怨不知道怎么跑,目标是什么. 你需要为它指定target remote 或target extended-remote例如:target extended-remote 192.168.1.136:1234指明target 是某IP的某端口完整示例如下:targ..._don't know how to run. try "help target".

c语言程序设计教程 郭浩志,C语言程序设计教程答案杨路明郭浩志-程序员宅基地

文章浏览阅读85次。习题 11、算法描述主要是用两种基本方法:第一是自然语言描述,第二是使用专用工具进行算法描述2、c 语言程序的结构如下:1、c 语言程序由函数组成,每个程序必须具有一个 main 函数作为程序的主控函数。2、“/*“与“*/“之间的内容构成 c 语言程序的注释部分。3、用预处理命令#include 可以包含有关文件的信息。4、大小写字母在 c 语言中是有区别的。5、除 main 函数和标准库函数以..._c语言语法0x1e