超级版主
 
- 积分
- 5639
- 金钱
- 5639
- 注册时间
- 2019-5-8
- 在线时间
- 1508 小时
|
本帖最后由 正点原子运营 于 2026-4-28 11:36 编辑
第三十三章 ADC实验
1)实验平台:正点原子STM32H7R7开发板
2)章节摘自【正点原子】STM32H7R7开发指南 V1.1
3)购买链接: https://detail.tmall.com/item.htm?id=820823382459
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h7rx.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子STM32开发板技术交流群:756580169
本章,我们将介绍STM32H7R7的ADC(Analog-to-digital converters,模数转换器)功能。我们通过四个实验来学习ADC,分别是单通道ADC采集实验、单通道ADC采集(DMA读取)实验、多通道ADC采集(DMA读取)实验和单通道ADC过采样(16位分辨率)实验。
本章分为如下几个小节:
33.1 ADC简介
33.2 单通道ADC采集实验
33.3 单通道ADC采集(DMA读取)实验
33.4 多通道ADC采集(DMA读取)实验
33.5 单通道ADC过采样(16位分辨率)实验
33.1 ADC简介
STM32H7R7xx系列有2个ADC,都可以独立工作,其中ADC1和ADC2还可以组成双重模式(提高采样率)。
STM32H7R7的ADC主要特性我们可以总结为以下几条:
1、可配置12位、10位、8位、6位分辨率,降低分辨率可以缩短转换时间,转换时间越短,可以做到的采样率就越高。
2、每个ADC支持多达19个的采集通道,其中有17个外部通道和2个内部通道。17个外部通道中,有6路快速通道和11路慢速通道,慢速和快速的区别主要是支持的最高采样率不同,慢速通道要比快速通道低。这些通道的A/D转换可以单次、连续、扫描或间断模式执行。
3、ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。
4、ADC具有四条专用的内部通道,一路用于内部温度传感器(VSENSE)、一路用于内部参考电压、一路用于监控外部VBAT电源引脚、一道用于监控内部VDDCORE电源。
5、支持过采样,过采样比2 ~ 256x可调,可编程数据移位高达8位。
6、每个ADC支持三路模拟看门狗。
7、支持单独输入和差分输入(可按通道进行编程)。
8、ADC输入范围:VREF– ≤ VIN ≤ VREF+。由VREF- 、VREF+ 、VDDA 和VSSA 这四个外部引脚决定。一般我们把VSSA 和VREF- 接地,把 VREF+ 和VDDA接到3.3V,所以得到ADC 的输入电压范围是:0~3.3V。注意不要接超出这个范围的电压进来,否则容易烧坏芯片。
9、自校准(偏移校准和线性度校准)。
10、最多4条注入转换序列,16条常规转换序列。
上面我们列出的一些特性都是ADC重要的特性,其它特性请查看参考手册。
下面来介绍ADC(仅限ADC1或ADC2)的框图:
图33.1.1 ADC框图
图中,我们标记了11处位置,分别如下:
① VREF+电压
VREF+ 是正模拟参考电压输入,选择范围是1.62V~3.6V,开发板上我们一般给VREF+接入的电压时3.3V,所以得到开发板上的ADC测量范围是0~3.3V。
② ADC的双时钟域架构
ADC有两种时钟源可以选择,分别是:
(1)adc_hclk(属于同步时钟),来自AHB总线的系统时钟,ADC1和ADC2处在300MHz的 AHB1总线时钟。可以通过ADCx_CCR寄存器的CKMODE[1:0]位来选择不同分频的AHB1总线时钟,有以下的四种情况:
CKMODE[1:0]=00,这是异步时钟模式选择的配置,适用于下面要讲的adc_ker_ck时钟。
CKMODE[1:0]=01,adc_hclk/1(同步时钟模式)
CKMODE[1:0]=10,adc_hclk/2(同步时钟模式)
CKMODE[1:0]=11,adc_hclk/4(同步时钟模式)
比如我们选择4分频的adc_hclk,得到的频率是75MHz,但是数据手册对ADC时钟频率最高是125MHz,说明这样配置就超频了。我们也可以降低AHB总线时钟,但是这样会影响我们其它外设的性能,所以为了系统能达到最优的性能,我们一般不会选择adc_hclk作为ADC的时钟源,于是我们选择下面要说的这种时钟源。
(2)adc_ker_ck(属于异步时钟),可以通过RCC_CCIPR1寄存器的ADCSEL [1:0]位来选择不同的时钟源,前提是前面提到的CKMODE[1:0]=00。ADC异步时钟模式下可以选择以下的时钟源:
ADCSEL [1:0]=00,pll2_p_ck作为ADC时钟源(复位后的默认值)
ADCSEL [1:0]=01,pll3_r_ck作为ADC时钟源
ADCSEL [1:0]=10,per_ck作为ADC时钟源
实际的例程中我们选择per_ck作为ADC时钟源,而per_ck 时钟可为 hse_ck、hsi_ker_ck 或 csi_ker_ck,通过RCC_CCIPR1寄存器的CKPERSEL位选择,默认选择hsi_ker_ck作为per_ck的时钟源。hsi_ker_ck时钟源就是来自频率为64MHz的高速内部RC振荡器(HSI)。
选择了adc_ker_ck时钟源作为ADC的时钟,则可以通过ADCx_CCR寄存器的PRESC[3:0]位进行分频,可以是1、2、4、6、8、10、12、16、32、64、128、256这12种分频系数。
上面的分析请结合下面的ADC时钟方案图理解。
图33.1.2 ADC时钟方案
③ 输入通道
ADC总共有19个输入通道。注意:STM32H7R7的ADC支持单端/差分转换,由寄存器ADCx_DIFSEL控制,该寄存器默认是0(单端模式),配置为1(则为差分模式)。因为H7R7的ADC支持差分通道输入,因此有ADCx_INP[18:0]和ADCx_INN[18:0]两组通道。其中,INP是差分正向输入,INN是差分反向输入。ADC_INP[0:5]和 ADC_INN[0:5]是快速模拟输入。ADC_INP[6:18]和 ADC_INN[6:18]是慢速模拟输入。如果我们使用单端输入,则只有ADCx_INP[18:0]有效,ADCx_INN[18:0]在内部自动接VSSA。
ADC连接4路内部模拟输入,分别是:
表33.1.1 ADC内部通道
ADC的内部通道包括:一路用于内部温度传感器(VSENSE)、一路用于内部参考电压、一路用于监控外部VBAT电源引脚、一道用于监控内部VDDCORE电源。
为了方便大家查询ADC通道和IO的对应关系,给大家整理了表33.1.2。大多数情况,我们都是使用单端模式。前面也说了单端模式下,ADCx_INN[18:0]在内部自动接VSSA,所以表33.1.2中的通道0~通道18指的是ADCx_INP[18:0]。
表33.1.2 ADC通道表
④ 转换序列
可以将转换分为两组:常规转换组和注入转换组。常规转换组最多允许16个通道进行转换。注入转换组最多允许4个通道进行转换。
如何理解常规转换组和注入转换组?常规转换组相当于你正常运行的程序,而注入转换组就相当于中断。在你程序正常执行的时候,中断是可以打断你的执行的,获得优先执行的权利。所以注入转换组可以打断常规转换组的转换,获得优先转换的权利,在注入转换组转换完成后,常规转换组才得以继续转换。
为了便于理解,请看常规转换组和注入转换组的转换优先级对比图,如图33.1.3所示:
图33.1.3 常规转换组和注入转换组的转换优先级对比图
常规转换组最多允许16个通道进行转换,注入转换组最多允许4个通道进行转换,那么转换的顺序怎么设置的?我们把这个转换顺序分别称为常规序列和注入序列。
(1)常规序列
常规序列在ADCx_SQRy寄存器中设置,每个ADC都有4个SQR寄存器,比如ADC1的SQR寄存器有ADC1_SQR1~ ADC1_SQR4。这四个寄存器怎么来设置常规序列的呢?下面通过表33.1.3给大家说明。
表33.1.3 常规序列寄存器控制关系汇总表
从上表可以知道,当我们想设置ADC的某个输入通道在常规序列的第1个转换,只需要把相应的输入通道号的值写入SQR1寄存器中的SQ1[4:0]位即可。例如想让输入通道5先进行转换,那么就可以把5这个数值写入SQ1[4:0]位。如果还想让输入通道8在第2个转换,那么就可以把8这个数值写入SQ2[4:0]位。最后还要设置你的这个规则序列的输入通道个数,只需把通道个数写入SQR1的SQL[3:0]位。注意:写入0到SQL[3:0]位,表示这个常规序列有1个通道的意思,而不是0个通道。
(2)注入序列
注入序列和常规序列差不多,决定的是注入转换组的顺序。注入组最大允许4个通道输入,它的注入序列由JSQR寄存器配置。注入序列寄存器JSQR控制关系如表33.1.4所示:
表33.1.4 注入序列寄存器控制关系汇总表
注入序列的长度写入JL [ 1 : 0 ]位,范围是0~3。注意:写入0表示这个注入序列有一个通道,而不是0个通道。
⑤ 触发源
ADC的触发转换有两种方法:分别是通过软件或外部事件(也就是硬件)触发转换。
我们先来看看通过软件触发转换的方法,常规通道由ADCx_CR寄存器的ADSTART位触发,注入通道由ADCx_CR寄存器的JADSTART位触发。方法是:通过对ADCx_CR寄存器的ADSTART(JADSTART)位写1开始转换,转换结束由硬件清零该位,这个控制ADC转换的方式非常简单。
另一种就是通过外部事件触发转换的方法,如定时器和输入引脚触发等等,具体请看《STM32H7Rx参考手册_V6(英文版).pdf》的1083页和1084页的表229。外部事件触发转换可分为:常规通道的外部触发和注入通道的外部触发两种。
adc_ext_trg[20:0],对应的就是常规通道的外部触发,共有21路。
adc_jext_trg[20:0],对应的就是注入通道的外部触发,共有21路。
如果选择硬件触发,则需要选择相应的硬件触发事件和触发边沿等,然后由外部硬件事件来触发ADC的采集(外部事件触发配置ADSTART位为1)。
硬件触发事件由ADCx_CFGR寄存器的EXTSEL[4:0]和ADCx_JSQR寄存器的 JEXTSEL[4:0]位来选择,分别是常规转换组和注入转换组的触发源选择。而触发边沿是通过ADCx_CFGR寄存器的EXTEN[1:0]和ADCx_JSQR寄存器的JEXTEN[1:0]位来选择。
⑥ 转换时间
STM32H7R7的ADC总转换时间的计算公式如下:
TCONV = 采样时间(TSMPL) + 逐次逼近时间(TSAR) 采样时间(TSMPL)可通过ADCx_SMPR1和ADCx_SMPR2寄存器中的SMP[2:0]位编程,ADC_SMPR1控制的是通道0~9,ADC_SMPR2控制的是通道10~19。所有通道都可以通过编程来控制使用不同的采样时间,可选采样时间值如下:
SMP = 000:2.5 个 ADC 时钟周期
SMP = 001:6.5 个 ADC 时钟周期
SMP = 010:12.5 个 ADC 时钟周期
SMP = 011:24.5 个 ADC 时钟周期
SMP = 100:47.5 个 ADC 时钟周期
SMP = 101:92.5 个 ADC 时钟周期
SMP = 110:247.5 个 ADC 时钟周期
SMP = 111:640.5 个 ADC 时钟周期
逐次逼近时间(TSAR)是由分辨率决定的,分辨率通过对ADCx_CFGR寄存器的RES[1:0]位进行编程,可将分辨率配置为12位、10位、8位、6位。而逐次逼近时间和分辨率的对应关系如下表所示:
表33.1.5 TSAR与分辨率的对应关系
举个例子,我们配置SMP = 111,即设置最大采样周期,然后采用12位分辨率,那么得到:
TCONV = 640.5个ADC时钟周期 + 12.5个ADC时钟周期 = 653个ADC时钟周期 表格中,FADC的频率是30MHZ,我们的例程中ADC的时钟源是异步时钟源adc_ker_ck。adc_ker_ck来源于PLL2P,大小为600MHz,经过ADC分频器进行8分频后传入ADC,所以ADC的时钟为75MHZ。我们就以FADC的频率为75MHZ来举例,可得到:
⑦ 参考电压
选择参考电压,我们可以设置参考电压来自外部的Vref+,也可以设置参考电压来自内部的稳压器。
⑧ ADC的核心
ADC的核心是一个12位的逐次逼近型ADC转换器,它根据我们设置好的参考电压、输入通道、启动条件等,执行模数转换。
⑨ 数据寄存器
常规转换组的转换结果会存放到ADCx_DR寄存器的RDATA[15:0]位中,注入转换组的转换结果会存放到ADCx_JDRy寄存器的JDATA1~4[16:0]位中。如果是使用双重模式,常规通道的数据则是存放在ADC_CDR寄存器。转换结果CPU可以通过AHB总线读取,同时也可以产生相关中断(adc_it)。
常规数据寄存器(ADCx_DR)(x=1~4)
常规数据寄存器ADC_DR是一个16位的寄存器。因为ADC的最大分辨率是12位,如果使用过采样,则分辨率可达16位,所以允许数据对齐方式。由ADC_CFGR2寄存器的OVSS[3:0]位和LSHIFT[3:0]位设置数据对齐方式。
细心的朋友可能发现,常规转换组最多有16个输入通道,而ADC常则数据寄存器只有一个,如果一个常则转换组用到好几个通道,数据怎么读取?如果使用多通道转换,那么这些通道的数据也会存放在DR里面,按照常规转换组的顺序,上一个通道转换的数据,会被下一个通道转换的数据覆盖掉,所以当通道转换完成后要及时把数据取走。比较常用的方法是使用DMA模式。当常规转换组的通道转换结束时,就会产生DMA请求,这样就可以及时把转换的数据搬运到用户指定的目的地址存放。如果没有使用DMA传输,可以通过判断ADC_ISR寄存器相关位来得到当前ADC的转换状态,从而进行控制。
注入数据寄存器(ADCx_JDRy)(x=1~3)(y=1~4)
每个ADC注入数据寄存器有4个,注入转换组最多有4个输入通道,刚好每个通道都有自己对应的数据寄存器。ADC_JDRx寄存器是32位的,低16位有效,高16位保留,数据也同样需要选择对齐方式。也是由ADC_CFGR2寄存器的OVSS[3:0]位设置数据对齐方式。
通用常则数据寄存器ADC_CDR
常规数据寄存器ADC_DR仅适用于独立模式下,常规转换组的转换数据存储,而通用常规数据寄存器ADC_CDR适用于双重模式下,常规转换组的转换数据存储。在双重模式下,一般会配合DMA来传输数据。
⑩ 中断
对于每个ADC都可在下列情况下产生中断:
表33.1.5 每个ADC的ADC中断
在表33.1.5中,前面5个中断都很好理解,我们从模拟看门狗中断介绍。
模拟看门狗中断发生条件:首先通过ADC_TRx寄存器设置低阈值和高阈值,然后开启了模拟看门狗中断后,当被ADC转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断。例如我们设置高阈值是3.0V,那么模拟电压超过3.0V的时候,就会产生模拟看门狗中断,低阈值的情况类似。
采样阶段结束:如果位EOSMP被硬件置1,则说明采样阶段结束(仅限常规转换),然后可以通过对EOSMP位写1来清零该位。如果EOSMPIE位置1,可以产生采样阶段结束中断。
上溢:如果常规转换后的数据未在新转换数据可用之前(由CPU或DMA)读取,会由溢出标志(OVR)指示缓冲区溢出事件。如果OVRIE位置1,可以产生一个溢出中断。
此外,我们还要知道常规组和注入组的转换结束后,除了产生中断外,还可以产生DMA请求,把转换好的数据存储在内存里面,防止读取不及时数据被覆盖。
⑪ 单次转换模式和连续转换模式
单次转换模式和连续转换模式在框图中是没有标号,为了更好地学习后续的内容,这里简单给大家讲讲。
(1)单次转换模式
通过将ADC_CFGR寄存器的CONT位置0选择单次转换模式。该模式下,ADC只执行一次转换。单次转换由ADC_CR寄存器的ADSTART位(只适用于常规转换组)或者JADSTART位(只适用于注入转换组)启动,也可以通过外部事件触发启动(适用于常规转换组或注入转换组),并且触发外部事件之前,ADSTART位或JADSTART位必须置1。
在常规序列中,每次转换完成后:转换数据存储在16位ADCx_DR寄存器中、EOC(常规转换结束)标志置 1、EOCIE位置1时将产生中断。
在注入序列中,每次转换完成后:转换数据存储在四个16位ADC_JDR1寄存器的其中一个寄存器中、JEOC(注入转换结束)标志置1、JEOCIE 位置1时将产生中断。
常规序列完成后:EOS(常规序列结束)标志置1、EOSIE位置1时将产生中断。
注入序列完成后:JEOS(注入序列结束)标志置1、JEOSIE 位置1时将产生中断。
随后,ADC会停止工作,直至发生新的外部常规或注入触发,或者将ADSTART或JADSTART位再次置1。
(2)连续转换模式
通过将ADC_CFGR寄存器的CONT位置1选择连续转换模式。该模式仅适用于常规转换组。在连续转换模式下,如果发生软件或者硬件常规触发事件,ADC会将常规转换组的所有通道执行一次,随后会自动重启并持续执行序列的每个转换。CONT位为1时,可通过外部触发或将ADCx_CR寄存器中的ADSTART位置1来启动此模式。
在常规序列中,每次转换完成后:转换数据存储在16位ADCx_DR寄存器中、EOC(转换结束)标志置1、EOCIE 位置1时将产生中断。
转换序列完成后:EOS(序列结束)标志置 1、EOSIE位置1时将产生中断。
随后,会立即重启新序列,ADC会继续重复执行转换序列。
注意:注入通道不能连续转换,唯一例外的是,在连续转换模式下(使用JAUTO位)注入通道配置为在常规通道后的自动转换。
到这里,我们基本上介绍了ADC的大多数基础的知识点,其它知识后面用到会继续补充,如果还有不懂的内容,请参考《STM32H7Rx参考手册_V6(英文版).pdf》的第27章。
33.2 单通道ADC采集实验
本实验我们来学习单通道ADC采集实验。本实验使用常规转换组单通道的单次转换模式,并且通过软件触发,即通过对ADCx_CR寄存器的ADSTART位写1启动转换。下面先带大家来了解本实验要配置的寄存器。
33.2.1 ADC寄存器
这里,我们只介绍本实验需要用到的寄存器的关键位,其他寄存器后续用到会继续介绍。
ADC通用控制寄存器(ADC_CCR)
ADC通用控制寄存器描述如图33.2.1.1所示:
图33.2.1.1 ADCx_CCR寄存器
该寄存器本章只需要用到PRESC[3:0]这四个位,用于设置ADC时钟的预分频系数(即对adc_ker_ck的分频系数),本实验中,设置PRESC[3:0] = 1000,即8分频。。
adc_ker_ck的时钟源由RCC_CCIPR1寄存器的ADCSEL[1:0]位配置。本章的实验我们都设置ADCSEL[1:0]=0,即选择pll2_p_ck作为时钟源,而pll2_p_ck是由PLL2时钟的P分频得到,我们系统设置的PLL2P时钟为600MHz。因此:
adc_ker_ck=pll2_p_ck=600MHz。 又由于ADC的输入时钟频率不能大于125M,所以,我们需要设置PRESC[3:0]=4,即可得到ADC输入时钟频率为:
adc_ker_ck/2^PRESC[3:0]=600/8=75MHz。 ADC控制寄存器(ADC_CR)
ADC控制寄存器描述如图33.2.1.2所示:
图33.2.1.2 ADC_CR寄存器
该寄存器我们用到多个位,这里就不全部列出来讲解了,而是抽出几个重要的位进行针对性的介绍,详情请参考《STM32H7Rx参考手册_V6(英文版).pdf》第27.7.3节,1165页。
ADEN位用于使能ADC转换器。需要设置该位为1,ADC才可以正常工作。
ADSTART位用于启动ADC常规通道的转换序列。当使用硬件触发时(EXTEN[1:0]!=0),设置该位为1,必须在相应的硬件触发事件产生时,才会启动ADC转换。而当不使用硬件触发时(EXTEN[1:0]=0),设置该位为1则可以立即启动ADC转换。
ADCAL位用与控制/读取ADC校准状态。设置该位为1时,可以启动ADC校准,等校准完成以后,硬件会自动清零该位。因此在设置该位为1以后,通过判断该位是否变为0,即可判断校准是否完成。
ADC配置寄存器(ADC_CFGR)
ADC配置寄存器描述如图33.2.1.3所示:
图33.2.1.3 ADC_CFGR寄存器
RES[1:0]位用于设置ADC转换的分辨率:0表示12位;1表示10位;2表示8位;3表示6位。本实验使用12位分辨率,因此设置RES[1:0]=0即可。
EXTEN[1:0]位用于设置常规通道的外部触发方式和极性。本实验使用软件触发,因此设置EXTEN[1:0]=00即可,即禁止外部触发。
OVRMOD位用于设置是否使能覆写功能。当设置该位为0时,如果上一次转换的数据未及时读取,新的转换结果将被丢弃;当设置该位为1时,如果上一次转换的数据未及时读取,将会被新的结果覆盖。本实验该位设置为1。
CONT位用于设置转换模式。当CONT=0时,表示单次转换模式;当CONT=1时,表示连续转换模式。本实验该位设置为0。
ADC常规序列寄存器1(ADC_SQR1)
ADC常规序列寄存器1描述如图33.2.1.4所示:
图33.2.1.4 ADC_SQR1寄存器
L[3:0]位用于存储常规序列的长度,取值范围:0~15,表示常规序列长度为:1~16。本实验只用到1个通道,L[3:0]=0即可。
SQ1[4:0]~SQ4[4:0]同于设置常规序列中第1~4个转换通道,第5~16个转换通道的设置请查看ADCx_SQR2和ADCx_SQR4寄存器。设置过程非常简单,忘记了请参考前面给大家整理出来的常规序列寄存器控制关系汇总表。
ADC采样时间寄存器2(ADC_SMPR2)
ADC采样时间寄存器2描述如图33.2.1.5所示:
图33.2.1.5 ADC_SMPR2寄存器
该寄存器用于设置ADC通道10~18的采样时间,而ADC_SMPR1设置ADC通道0~9的采样时间。STM32H7R7的ADC总转换时间的计算方法前面已经介绍过了。建议采样时间尽量长一点,以获得较高的准确度,但是这样会降低ADC的转换速率,所以大家在实际应用中自行结合自身情况设置。
ADC常规数据寄存器(ADC_ DR)
ADC常规数据寄存器描述如图33.2.1.6所示:
图33.2.1.6 ADC_ DR寄存器
常规序列中的AD转化结果都将被存在这个寄存器里面,我们读取该寄存器,即可得到ADC转换后的结果,而注入通道的转换结果被保存在ADC_JDRy(y=1~4)里面。
ADC中断和状态寄存器(ADC_ ISR)
ADC中断和状态寄存器描述如图33.2.1.7所示:
图33.2.1.7 ADC_ ISR寄存器
该寄存器保存了ADC转换时的各种状态。本实验我们通过EOC位的状态来判断ADC转换是否完成,如果查询到EOC位被硬件置1,就可以从ADC_DR寄存器中读取转换结果,否则需要等待转换完成。
至此,本章要用到的ADC相关寄存器全部介绍完毕了,对于未介绍的部分,请大家参考《STM32H7Rx参考手册_V6(英文版).pdf》第27章相关内容。
33.2.2 硬件设计
1. 例程功能
LCD上不断刷新显示ADC1的通道9(PB0)上面输入电压采样的数字量和模拟量。LED0闪烁,提示程序运行。
2. 硬件资源
1)LED灯
LED0: LED0 – PD14
2)串口1(PB14/PB15连接在板载USB转串口芯片CH340上面)
3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(包括MCU屏和RGB屏,都支持)
4)ADC1 :通道9 – PB0
3. 原理图
ADC属于STM32H7R7内部资源,实际上我们只需要软件设置就可以正常工作,可以将待测量的电压源连接到ADC通道上,以便ADC测量。我们只需要1跟杜邦线,一端接到P14的ADC排针上,另外一端就接你要测试的电压点。一定要保证测试点的电压在0~3.3V的电压范围,否则可能烧坏我们的ADC,甚至是整个主控芯片。
33.2.3 程序设计
33.2.3.1 ADC的HAL库驱动
ADC在HAL库中的驱动代码在stm32h7rsxx_hal_adc.c和stm32h7rsxx_hal_adc_ex.c文件(及其头文件)中。
1. HAL_ADC_Init函数
ADC的初始化函数,其声明如下:
- HAL_StatusTypeDef HAL_ADC_Init(ADC_HandleTypeDef *hadc);
复制代码 函数描述:
用于初始化ADC。
函数形参:
形参1是ADC_HandleTypeDef结构体类型指针变量,其定义如下:
- typedef struct
- {
- ADC_TypeDef *Instance; /* ADC寄存器基地址 */
- ADC_InitTypeDef Init; /* ADC参数初始化结构体变量 */
- DMA_HandleTypeDef *DMA_Handle; /* DMA配置结构体 */
- HAL_LockTypeDef Lock; /* ADC锁定对象 */
- __IO uint32_t State; /* ADC工作状态 */
- __IO uint32_t ErrorCode; /* ADC错误代码 */
- ADC_InjectionConfigTypeDef InjectionConfig ; /* ADC注入通道配置结构,用于配置注入通道的转换顺序,数据格式等 */
- }ADC_HandleTypeDef;
复制代码 该结构体定义和其他外设比较类似,我们着重看第二个成员变量Init含义,它是结构体
ADC_InitTypeDef类型,结构体ADC_InitTypeDef定义为:
- typedef struct {
- uint32_t ClockPrescaler; /* 设置预分频系数,即PRESC[3:0]位 */
- uint32_t Resolution; /* 配置ADC的分辨率 */
- uint32_t DataAlign; /* ADC数据对齐方式 */
- uint32_t ScanConvMode; /* 扫描模式 */
- uint32_t EOCSelection; /* 转换完成标志位 */
- FunctionalState LowPowerAutoWait; /* 低功耗自动延时 */
- FunctionalState ContinuousConvMode; /* 设置单次转换模式还是连续转换模式 */
- uint32_t NbrOfConversion; /* 设置转换通道数目,赋值范围是1~16 */
- FunctionalState DiscontinuousConvMode; /* 设置常规转换组不连续模式 */
- uint32_t NbrOfDiscConversion; /* 常规转换组不连续模式转换通道的数目 */
- uint32_t ExternalTrigConv; /* ADC外部触发源选择*/
- uint32_t ExternalTrigConvEdge; /* ADC外部触发极性*/
- uint32_t SamplingMode; /* ADC组常规转换的采样模式 */
- FunctionalState DMAContinuousRequests; /* DMA请求是单次模式或连续模式执行 */
- uint32_t Overrun; /* 发生溢出时,进行的操作 */
- FunctionalState OversamplingMode; /* 过采样模式 */
- ADC_OversamplingTypeDef Oversampling; /* 过采样的参数配置 */
- } ADC_InitTypeDef;
复制代码 1) ClockPrescaler:ADC预分频系数选择,可选的分频系数为 1、2、4、6、8、10、12、16、32、64、128、256。ADC最大时钟配置为36MHZ。
2) Resolution:配置ADC的分辨率,可选的分辨率有16 位、12 位、10 位和 8 位。分辨率越高,转换数据精度越高,转换时间也越长;反之分辨率越低,转换数据精度越低,转换时间也越短。
3) DataAlign:配置ADC数据对齐方式,左对齐或者右对齐。
4) ScanConvMode:配置是否使用扫描。如果是单通道转换使用ADC_SCAN_DISABLE,如果是多通道转换使用ADC_SCAN_ENABLE。
5) EOCSelection:可选参数为ADC_EOC_SINGLE_CONV和ADC_EOC_SEQ_CONV,指定转换结束时是否产生EOS中断或事件标志。
6) LowPowerAutoWait:配置是否使用低功耗自动延迟等待模式,可选参数为 ENABLE和 DISABLE,当使能时,仅当一组内所有之前的数据已处理完毕时,才开始新的转换,适用于低频应用。该模式仅用于ADC的轮询模式,不可用于DMA以及中断。
7) ContinuousConvMode:可选参数为ENABLE和DISABLE,配置自动连续转换还是单次转换。使用ENABLE配置为使能自动连续转换;使用DISABLE配置为单次转换,转换一次后停止需要手动控制才重新启动转换。
8) NbrOfConversion:设置常规转换通道数目,范围是:1~16。
9) DiscontinuousConvMode:配置是否使用不连续的采样模式,比如要转换的通道有1、2、5、7、8、9,那么第一次触发会进行通道 1 与通道 2,下次触发就是转换通道 5 与通道 7,这样不连续的转换,依次类推。此参数只有将 ScanConvMode 使能,还有ContinuousConvMode失能的情况下才有效,不可同时使能。
10) NbrOfDiscConversion:不连续采样通道数。
11) ExternalTrigConv:外部触发方式的选择,如果使用软件触发,那么外部触发会关闭。
12) ExternalTrigConvEdge:外部触发极性选择,如果使用外部触发,可以选择触发的极性,可选有禁止触发检测、上升沿触发检测、下降沿触发检测以及上升沿和下降沿均可触发检测。
13) SamplingMode:指定ADC组常规转换的采样模式。
14) DMAContinuousRequests:指定DMA请求是否以单次模式(DMA)执行(当达到转换数量时,传输停止)或连续模式(DMA传输无限制,无论转换次数多少)。
15) Overrun:当有新的数据溢出时,可以选择覆盖写入或者是丢弃新的数据。
16) OversamplingMode:是否使用过采样模式。
17) Oversampling:配置过采样模式的参数。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
2. HAL_ADCEx_Calibration_Start函数
ADC的自校准函数,其声明如下:
- HAL_StatusTypeDef HAL_ADCEx_Calibration_Start(ADC_HandleTypeDef *hadc,
- uint32_t SingleDiff);
复制代码 函数描述:
首先调用HAL_ADC_Init函数配置了相关的功能后,再调用此函数进行ADC自校准功能。
函数形参:
形参1是ADC_HandleTypeDef结构体类型指针变量。
形参2是单端或差分模式选择,有以下两种:
1)ADC_SINGLE_ENDED表示单端输入模式。
2)ADC_DIFFERENTIAL_ENDED表示差分输入模式。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
3. HAL_ADC_ConfigChannel函数
ADC通道配置函数,其声明如下:
- HAL_StatusTypeDef HAL_ADC_ConfigChannel(ADC_HandleTypeDef *hadc,
- const ADC_ChannelConfTypeDef *sConfig);
复制代码 函数描述:
调用了HAL_ADC_Init函数配置了相关的功能后,就可以调用此函数配置ADC具体通道。
函数形参:
形参1是ADC_HandleTypeDef结构体类型指针变量。
形参2是ADC_ChannelConfTypeDef结构体类型指针变量,用于配置ADC采样时间,使用的通道号,单端或者差分方式的配置等。该结构体定义如下:
- typedef struct {
- uint32_t Channel; /* ADC转换通道*/
- uint32_t Rank; /* ADC转换顺序 */
- uint32_t SamplingTime; /* ADC采样周期 */
- uint32_t SingleDiff; /* 输入信号线的类型*/
- uint32_t OffsetNumber; /* 采用偏移量的通道 */
- uint32_t Offset; /* 偏移量 */
- uint32_t OffsetSign; /* 定义偏移量符号,是要减去还是要加上 */
- FunctionalState OffsetSignedSaturation; /* 转换数据格式为有符号位数据 */
- } ADC_ChannelConfTypeDef;
复制代码 1) Channel:ADC转换通道,范围:0~18。
2) Rank:在常规转换中的常规组的转换顺序,可以选择1~16。
3) SamplingTime:ADC的采样周期,最大640.5个ADC时钟周期,要求尽量大以减少误差。
4) SingleDiff:选择通道单端输入还是差分输入。
5) OffsetNumber:选择使用偏移量的通道。
6) Offset:定义要从原始数据减去的偏移量。根据ADC的分辨率不同,支持的最大偏移量也不同,例如分辨率是16bit,,最大的偏移量为0xFFFF。
7) OffsetRightShift:采样值进行右移的位数。
8) OffsetSignedSaturation:是否使能ADC采样值的最高位为符号位。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
4. HAL_ADC_Start函数
ADC转换启动函数,其声明如下:
- HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef *hadc);
复制代码 函数描述:
当配置好ADC的基础的功能后,就调用此函数启动ADC。
函数形参:
形参1是ADC_HandleTypeDef结构体类型指针变量。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
5. HAL_ADC_PollForConversion函数
等待ADC常规组转换完成函数,其声明如下:
- HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef *hadc,
- uint32_t Timeout);
复制代码 函数描述:
一般先调用HAL_ADC_Start函数启动转换,再调用该函数等待转换完成,然后再调用HAL_ADC_GetValue函数来获取当前的转换值。
函数形参:
形参1是ADC_HandleTypeDef结构体类型指针变量。
形参2是等待转换的等待时间,单位是毫秒(ms)。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
6. HAL_ADC_GetValue函数
获取常规组ADC转换值函数,其声明如下:
- uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef *hadc);
复制代码 函数描述:
一般先调用HAL_ADC_Start函数启动转换,再调用HAL_ADC_PollForConversion函数等待转换完成,然后再调用HAL_ADC_GetValue函数来获取当前的转换值。
函数形参:ADC_HandleTypeDef结构体类型指针变量。
函数返回值:当前的转换值,uint32_t类型数据。
单通道ADC采集配置步骤
1)开启ADCx和ADC通道对应的IO时钟,并配置该IO为模拟功能
首先开启ADCx的时钟,然后配置GPIO为模拟模式。本实验我们默认用到ADC1通道9,对应IO是PB0,它们的时钟开启方法如下:
- __HAL_RCC_ADC12_CLK_ENABLE (); /* 使能ADC1 & ADC2 时钟 */
- __HAL_RCC_GPIOB_CLK_ENABLE(); /* 开启GPIOB时钟 */
复制代码 2)初始化ADCx, 配置其工作参数
通过HAL_ADC_Init函数来设置ADCx时钟分频系数、分辨率、模式、扫描方式、对齐方式等信息。
注意:该函数会调用:HAL_ADC_MspInit回调函数来存放ADC及GPIO时钟使能、GPIO初始化等代码。
3)配置ADC通道并启动AD转换器
在HAL库中,通过HAL_ADC_ConfigChannel函数来设置配置ADC的通道,根据需求设置通道、序列、采样时间和校准配置单端输入模式或差分输入模式等。
配置好ADC通道之后,通过HAL_ADC_Start函数启动AD转换器。
4)读取ADC值
这里选择查询方式读取,在读取ADC值之前需要调用HAL_ADC_PollForConversion等待上一次转换结束。然后就可以通过HAL_ADC_GetValue来读取ADC值。
33.2.3.2 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。ADC驱动源码包括两个文件:adc.c和adc.h。
adc.h文件针对ADC及通道引脚定义了一些宏定义,具体如下:
- /* ADC定义 */
- #define ADC_ADCX ADC1
- #define ADC_ADCX_CLK_ENABLE()
- do { __HAL_RCC_ADC12_CLK_ENABLE(); } while (0)
- #define ADC_ADCX_CHY ADC_CHANNEL_9
- #define ADC_ADCX_CHY_GPIO_PORT GPIOB
- #define ADC_ADCX_CHY_GPIO_PIN GPIO_PIN_0
- #define ADC_ADCX_CHY_GPIO_CLK_ENABLE()
- do { __HAL_RCC_GPIOB_CLK_ENABLE(); } while (0)
复制代码 ADC的通道与引脚的对应关系在STM32中文数据手册可以查到,我们这里使用ADC1的通道9,在数据手册中的表格为:
表33.2.3.3.1 ADC1通道9对应引脚查看表
下面直接开始介绍adc.c的程序,首先是ADC初始化函数。
- /**
- * @brief 初始化ADC
- * [url=home.php?mod=space&uid=271674]@param[/url] 无
- * @retval 无
- */
- void adc_init(void)
- {
- /* 初始化ADC */
- g_adc_handle.Instance = ADC_ADCX;
- g_adc_handle.Init.ClockPrescaler=ADC_CLOCK_ASYNC_DIV8;/* 时钟源和预分频系数 */
- g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B; /* 分辨率 */
- g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 数据对齐 */
- g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; /* 扫描转换模式 */
- g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV; /* EOC */
- g_adc_handle.Init.LowPowerAutoWait = DISABLE; /* 低功耗自动延迟 */
- g_adc_handle.Init.ContinuousConvMode = DISABLE; /* 连续转换模式 */
- g_adc_handle.Init.NbrOfConversion = 1; /* 连续转换模式下的转换数量 */
- g_adc_handle.Init.DiscontinuousConvMode = DISABLE; /* 非连续转换模式 */
- g_adc_handle.Init.NbrOfDiscConversion = 1; /* 非连续转换模式下的转换数量 */
- g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
- /* 规则组转换外部触发源 */
- g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
- /* 规则组转换外部触发源的有效沿 */
- g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;
- /* 采样模式 */
- g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
- /* 管理转换数据 */
- g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; /* 溢出行为 */
- g_adc_handle.Init.OversamplingMode = DISABLE; /* 过采样模式 */
- HAL_ADC_Init(&g_adc_handle);
-
- /* ADC自动自校准 */
- HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);
- }
复制代码 该函数调用HAL_ADC_Init函数配置了ADC的基础功能参数。
下面是获得ADC转换后的结果函数,其定义如下:
- /**
- * @brief 获取ADC结果
- * @param channel: ADC通道
- * @retval ADC结果
- */
- uint32_t adc_get_result(uint32_t channel)
- {
- ADC_ChannelConfTypeDef adc_channel_conf_struct = {0};
- uint32_t result;
-
- /* 配置ADC通道 */
- adc_channel_conf_struct.Channel = channel; /* 通道 */
- adc_channel_conf_struct.Rank = ADC_REGULAR_RANK_1; /* Rank编号 */
- adc_channel_conf_struct.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
- /* 采样时间 */
- adc_channel_conf_struct.SingleDiff = ADC_SINGLE_ENDED; /* 采样输入模式 */
- adc_channel_conf_struct.OffsetNumber = ADC_OFFSET_NONE;/* 偏移号 */
- adc_channel_conf_struct.Offset = 0; /* 偏移量 */
- adc_channel_conf_struct.OffsetSign = ADC_OFFSET_SIGN_NEGATIVE;/* 偏移应用 */
- HAL_ADC_ConfigChannel(&g_adc_handle, &adc_channel_conf_struct);
-
- /* 开启ADC转换 */
- HAL_ADC_Start(&g_adc_handle);
-
- /* 等待ADC转换结束 */
- HAL_ADC_PollForConversion(&g_adc_handle, HAL_MAX_DELAY);
-
- /* 获取ADC转换结果 */
- result = HAL_ADC_GetValue(&g_adc_handle);
-
- /* 停止ADC转换 */
- HAL_ADC_Stop(&g_adc_handle);
-
- return result;
- }
复制代码 该函数先是调用HAL_ADC_ConfigChannel函数选择ADC通道、设置转换序列号和采样时间等,接着调用HAL_ADC_Start启动转换,然后调用HAL_ADC_PollForConversion函数等待转换完成,最后调用HAL_ADC_GetValue函数获取转换结果。
下面介绍的是获取ADC某通道的转换多次后的平均值函数,函数定义如下:
- /**
- * @brief 均值滤波获取ADC结果
- * @param channel: ADC通道
- * @param times: 均值滤波的原始数据个数
- * @retval ADC结果
- */
- uint32_t adc_get_result_average(uint32_t channel, uint8_t times)
- {
- uint32_t sum_result = 0;
- uint8_t index;
- uint32_t result;
-
- for (index=0; index<times; index++)
- {
- sum_result += adc_get_result(channel);
- }
-
- result = sum_result / times;
-
- return result;
- }
复制代码 该函数用于多次获取ADC值,取平均,用来提高准确度。
最后在main函数里面编写如下代码:
- int main(void)
- {
- uint32_t adc_result;
- uint32_t voltage;
- sys_mpu_config(); /* 配置MPU */
- sys_cache_enable(); /* 使能Cache */
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(300, 6, 2); /* 配置时钟,600MHz */
- delay_init(600); /* 初始化延时 */
- usart_init(115200); /* 初始化串口 */
- led_init(); /* 初始化LED */
- hyperram_init(); /* 初始化HyperRAM */
- lcd_init(); /* 初始化LCD */
- adc_init(); /* 初始化ADC */
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "ADC TEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 110, 200, 16, 16,"ADC1_CH9_VAL:0", BLUE);
- lcd_show_string(30, 130, 200, 16, 16,"ADC1_CH9_VOL:0.000V", BLUE);
- /* 先在固定位置显示小数点 */
- while (1)
- {
- adc_result = adc_get_result_average(ADC_ADCX_CHY, 20);
- /* 获取通道9的转换值,20次取平均 */
- lcd_show_xnum(142, 110, adc_result, 5, 16, 0, BLUE);
- /* 显示ADC采样后的原始值 */
- voltage = (adc_result * 3300) / 65535;
- /* 计算实际电压值(扩大1000倍) */
- lcd_show_xnum(142, 130, voltage/1000, 1, 16, 0, BLUE);
- lcd_show_xnum(158, 130, voltage%1000, 3, 16, 0X80, BLUE);
- /* 显示小数部分(前面转换为了整形显示),这里显示的就是111. */
- LED0_TOGGLE();
- delay_ms(100);
- }
- }
复制代码 此部分代码,我们在TFTLCD模块上显示一些提示信息后,将每隔100ms读取一次ADC通道9的值,并显示读到的ADC值(数字量),以及其转换成模拟量后的电压值。同时控制LED0闪烁,以提示程序正在运行。ADC值的显示简单介绍一下:首先我们在液晶固定位置显示了小数点,然后后面计算步骤中,先计算出整数部分在小数点前面显示,然后计算出小数部分,在小数点后面显示。这样就在液晶上面显示转换结果的整数和小数部分。
33.2.4 下载验证
下载代码后,可以看到LCD显示如图33.2.4.1所示:
图33.2.4.1单通道ADC采集实验测试图
大家可以用杜邦线将ADC排针接到其它待测量的电压点,看看测量到的电压值是否准确?但是要注意:一定要保证测试点的电压在0~3.3V的电压范围,否则可能烧坏我们的ADC,甚至是整个主控芯片。
33.3 单通道ADC采集(DMA读取)实验
本实验我们来学习单通道ADC采集(DMA读取)实验。本实验使用常规转换组单通道的连续转换模式,并且通过软件触发,即通过对ADCx_CR寄存器的ADSTART位写1启动转换。由于使用连续转换模式,所以使用DMA读取转换结果的方式。下面先带大家来了解本实验要配置的寄存器。
33.3.1 ADC & DMA寄存器
本实验我们很多的设置和单通道ADC采集实验是一样的,所以下面介绍寄存器的时候我们不会继续全部都介绍,而是针对性选择与单通道ADC采集实验不同设置的ADC_CFGR寄存器进行介绍,其他的配置基本一样的。
ADC配置寄存器(ADC_CFGR)
ADC配置寄存器描述如图33.3.1.1所示:
图33.3.1.1 ADC_CFGR寄存器
CONT位用于设置单次转换模式还是连续转换模式,本实验我们使用连续转换模式,所以CONT位置1即可,其他请参考上一个实验的配置。
33.3.2 硬件设计
1. 例程功能
使用ADC1采集(DMA读取)通道9(PB0)上面的电压,在LCD模块上面显示ADC转换值以及换算成电压后的电压值。LED0闪烁,提示程序运行。
2. 硬件资源
1)LED灯
LED0: LED0 – PD14
2)串口1(PB14/PB15连接在板载USB转串口芯片CH340上面)
3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(包括MCU屏和RGB屏,都支持)
4)ADC1 :通道9 – PB0
5)GPDMA1 –Channel10
3. 原理图
ADC和DMA属于STM32H7R7内部资源,实际上我们只需要软件设置就可以正常工作,另外还需要将待测量的电压源连接到ADC通道上,以便ADC测量。本实验,我们通过ADC1的通道9(PB0)来采集外部电压值,并通过DMA来读取。
有的朋友可能还想测试其他地方的电压值,我们只需要1跟杜邦线,一端接到P14的ADC排针上,另外一端就接你要测试的电压点。一定要保证测试点的电压在0~3.3V的电压范围,否则可能烧坏我们的ADC,甚至是整个主控芯片。
33.3.3 程序设计
33.3.3.1 ADC & DMA的HAL库驱动
单通道ADC采集实验已经介绍本实验要用到的ADC的HAL库API函数,这里我们要介绍启动使用中断的DMA传输函数和启动ADC(DMA传输)方式函数。
1. HAL_ADC_Start_DMA函数
启动ADC(DMA传输)方式函数,其声明如下:
- HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc,
- uint32_t *pData, uint32_t Length);
复制代码 函数描述:
用于启动ADC(DMA传输方式)函数。
函数形参:
形参1是ADC_HandleTypeDef结构体类型指针变量。
形参2是ADC 采样数据传输的目的地址。
形参3是要传输的数据项数目。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
单通道ADC采集(DMA读取)配置步骤
1)开启ADCx和通道输出的GPIO时钟,配置该IO口的复用功能输出
首先开启ADCx的时钟,然后配置GPIO为模拟模式。本实验我们默认用到ADC1通道9,对应IO是PB0,它们的时钟开启方法如下:
- __HAL_RCC_ADC12_CLK_ENABLE (); /* 使能ADC1时钟 */
- __HAL_RCC_GPIOB_CLK_ENABLE(); /* 开启GPIOB时钟 */
复制代码 2)初始化ADCx, 配置其工作参数
通过HAL_ADC_Init函数来设置ADCx时钟分频系数、分辨率、模式、扫描方式、对齐方式等信息。
注意:该函数会调用:HAL_ADC_MspInit回调函数来存放ADC及GPIO时钟使能、GPIO初始化等代码。我们也可以不存放在这个函数里,本实验就没用到这个MSP回调函数。
3)配置ADC通道并启动AD转换器
在HAL库中,通过HAL_ADC_ConfigChannel函数来设置配置ADC的通道,根据需求设置通道、序列、采样时间和校准配置单端输入模式或差分输入模式等。
配置好ADC通道之后,通过HAL_ADC_Start_DMA函数启动ADC DMA转换。
4)初始化DMA
通过HAL_DMA_Init函数初始化DMA,包括配置通道,外设地址,存储器地址,传输数据量等。
HAL库为了处理各类外设的DMA请求,在调用相关函数之前,需要调用一个宏定义标识符,来连接DMA和外设句柄。这个宏定义为__HAL_LINKDMA。
5)使能DMA对应数据流中断,配置DMA中断优先级并开启中断,启动ADC和DMA
通过HAL_ADC_Start_DMA函数开启ADC转换,通过DMA传输结果。
通过HAL_NVIC_EnableIRQ函数使能DMA数据流中断。
通过HAL_NVIC_SetPriority函数设置中断优先级。
6)编写中断服务函数
DMA中断对于每个数据流几乎都有一个中断服务函数,比如GPDMA1_Channel0的中断服务函数为GPDMA1_Channel0_IRQHandler。简单的做法就是在,对应的中断服务函数里面,通过判断相关的中断标志位的方式,完成中断逻辑代码,最后清除该中断标志位。
还可以通过调用HAL库提供的DMA中断公用处理函数HAL_DMA_IRQHandler,然后定重新义相关的中断回调处理函数。
33.3.3.2 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。ADC驱动源码包括两个文件:adc.c和adc.h。
由于本实验用到DMA,所以在adc.h头文件定义了以下一些宏定义:
- #define ADC_ADCX ADC1
- #define ADC_ADCX_CLK_ENABLE(); do{ __HAL_RCC_ADC12_CLK_ENABLE(); } while (0)
- #define ADC_ADCX_CHY ADC_CHANNEL_9
- #define ADC_ADCX_CHY_GPIO_PORT GPIOB
- #define ADC_ADCX_CHY_GPIO_PIN GPIO_PIN_0
- #define ADC_ADCX_CHY_GPIO_CLK_ENABLE() do { __HAL_RCC_GPIOB_CLK_ENABLE(); } while (0)
- #define ADC_ADCX_DMACX GPDMA1_Channel0
- #define ADC_ADCX_DMA_CLK_ENABLE() do { __HAL_RCC_GPDMA1_CLK_ENABLE(); } while (0)
- #define ADC_ADCX_DMACX_REQ GPDMA1_REQUEST_ADC1
- #define ADC_ADCX_DMACX_IRQn GPDMA1_Channel0_IRQn
- #define ADC_ADCX_DMACX_IRQHandler GPDMA1_Channel0_IRQHandler
复制代码 下面给大家介绍adc.c文件里面的函数,首先是ADC DMA读取初始化函数。
- /**
- * @brief 初始化ADC DMA读取
- * @param memory_base: 读取目标内存基地址
- * @retval 无
- */
- void adc_dma_init(uint32_t memory_base)
- {
- /* 初始化ADC */
- g_adc_handle.Instance = ADC_ADCX;
- g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;
- /* 时钟源和预分频系数 */
- g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B; /* 分辨率 */
- g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 数据对齐 */
- g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; /* 扫描转换模式 */
- g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV; /* EOC */
- g_adc_handle.Init.LowPowerAutoWait = DISABLE; /* 低功耗自动延迟 */
- g_adc_handle.Init.ContinuousConvMode = ENABLE; /* 连续转换模式 */
- g_adc_handle.Init.NbrOfConversion = 1; /* 连续转换模式下的转换数量 */
- g_adc_handle.Init.DiscontinuousConvMode = DISABLE; /* 非连续转换模式 */
- g_adc_handle.Init.NbrOfDiscConversion = 1; /* 非连续转换模式下的转换数量 */
- g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
- /* 规则组转换外部触发源 */
- g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
- /* 规则组转换外部触发源的有效沿 */
- g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;
- /* 采样模式 */
- g_adc_handle.Init.ConversionDataManagement =
- ADC_CONVERSIONDATA_DMA_CIRCULAR; /* 管理转换数据 */
- g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; /* 溢出行为 */
- g_adc_handle.Init.OversamplingMode = DISABLE; /* 过采样模式 */
- HAL_ADC_Init(&g_adc_handle);
-
- /* ADC自动自校准 */
- HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);
-
- g_adc_dma_memory_base = memory_base;
- }
复制代码 该函数主要是ADC的配置。首先我们用HAL_ADC_Init函数对ADC进行初始化,但是ADC_HandleTypeDef结构体的配置与单通道ADC采集实验有所不同,我们需要使能连续转换模式。
下面介绍的是能一次ADC DMA传输函数,其定义如下:
- /**
- * @brief 开启ADC DMA读取
- * @param length: DMA读取次数
- * @retval 无
- */
- void adc_dma_enable(uint32_t length)
- {
- ADC_ChannelConfTypeDef adc_channel_conf_struct = {0};
-
- /* 停止ADC DMA转换 */
- HAL_ADC_Stop_DMA(&g_adc_handle);
-
- /* 配置ADC通道 */
- adc_channel_conf_struct.Channel = ADC_ADCX_CHY; /* 通道 */
- adc_channel_conf_struct.Rank = ADC_REGULAR_RANK_1; /* Rank编号 */
- adc_channel_conf_struct.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
- /* 采样时间 */
- adc_channel_conf_struct.SingleDiff = ADC_SINGLE_ENDED; /* 采样输入模式 */
- adc_channel_conf_struct.OffsetNumber = ADC_OFFSET_NONE; /* 偏移号 */
- adc_channel_conf_struct.Offset = 0; /* 偏移量 */
- adc_channel_conf_struct.OffsetSign = ADC_OFFSET_SIGN_NEGATIVE;/* 偏移应用 */
- HAL_ADC_ConfigChannel(&g_adc_handle, &adc_channel_conf_struct);
-
- /* 开启ADC DMA转换 */
- HAL_ADC_Start_DMA(&g_adc_handle,(uint32_t*)g_adc_dma_memory_base, length);
- }
复制代码 下面介绍的是ADC DMA采集中断服务函数,函数定义如下:
- /**
- * @brief DMA中断服务函数
- * @param 无
- * @retval 无
- */
- void ADC_ADCX_DMACX_IRQHandler(void)
- {
- HAL_DMA_IRQHandler(&g_dma_handle);
- }
复制代码 最后在main.c里面编写如下代码:
- #define ADC_DMA_BUF_SIZE 50
- uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE];
- extern uint8_t g_adc_dma_sta;
- int main(void)
- {
- uint32_t adc_result;
- uint32_t voltage;
- uint16_t index;
- uint32_t result_sum;
-
- sys_mpu_config(); /* 配置MPU */
- sys_cache_enable(); /* 使能Cache */
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(300, 6, 2); /* 配置时钟,600MHz */
- delay_init(600); /* 初始化延时 */
- usart_init(115200); /* 初始化串口 */
- led_init(); /* 初始化LED */
- hyperram_init(); /* 初始化HyperRAM */
- lcd_init(); /* 初始化LCD */
- adc_dma_init((uint32_t)g_adc_dma_buf); /* 初始化ADC DMA传输 */
-
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "ADC DMA TEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH9_VAL: 0", BLUE);
- lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH9_VOL: 0.000V", BLUE);
-
- adc_dma_enable(ADC_DMA_BUF_SIZE); /* 开启ADC DMA传输 */
-
- while (1)
- {
- if (g_adc_dma_sta == 1)
- {
- g_adc_dma_sta = 0;
- SCB_InvalidateDCache();
-
- result_sum = 0;
- for (index = 0; index < ADC_DMA_BUF_SIZE; index++)
- /* 对DMA读取的多个ADC数据进行均值滤波 */
- {
- result_sum += g_adc_dma_buf[index];
- }
- adc_result = result_sum / ADC_DMA_BUF_SIZE;
- lcd_show_xnum(142, 110, adc_result, 5, 16, 0, BLUE);
-
- voltage = (adc_result * VREFINT_CAL_VREF) / 4095;
- /* 计算实际电压值(扩大1000倍) */
- lcd_show_xnum(142, 130, voltage / 1000, 1, 16, 0, BLUE);
- lcd_show_xnum(158, 130, voltage % 1000, 3, 16, 0x80, BLUE);
-
- adc_dma_enable(ADC_DMA_BUF_SIZE);
- }
-
- LED0_TOGGLE();
-
- delay_ms(100);
- }
- }
复制代码 此部分代码,和单通道ADC采集实验十分相似,只是这里使能了DMA传输数据,DMA传输的数据存放在g_adc_dma_buf数组里,这里我们对数组的数据取平均值,减少误差。在LCD屏显示结果的处理和单通道ADC采集实验一样。首先我们在液晶固定位置显示了小数点,然后后面计算步骤中,先计算出整数部分在小数点前面显示,然后计算出小数部分,在小数点后面显示。这样就在液晶上面显示转换结果的整数和小数部分。
33.3.4 下载验证
下载代码后,可以看到LCD显示如图33.3.4.1所示:
图33.3.4.1 单通道ADC采集(DMA读取)实验测试图
这里的实验效果和单通道ADC采集实验是一样的。 大家也可以用杜邦线将ADC排针接到其它待测量的电压点,看看测量到的电压值是否准确?但是要注意:一定要保证测试点的电压在0~3.3V的电压范围,否则可能烧坏我们的ADC,甚至是整个主控芯片。
33.4 多通道ADC采集(DMA读取)实验
本实验我们来学习多通道ADC采集(DMA读取)实验。本实验使用常规转换组多通道的连续转换模式,并且通过软件触发,即通过对ADC_CR寄存器的ADSTART位写1启动转换。由于使用连续转换模式,所以使用DMA读取转换结果的方式。下面先带大家来了解本实验要配置的寄存器。
33.4.1 ADC寄存器
本实验我们很多的设置和单通道ADC采集(DMA读取)实验是一样的,所以下面介绍寄存器的时候我们不会继续全部都介绍,而是针对性选择与单通道ADC采集(DMA读取)实验不同设置的ADC_SQR寄存器进行介绍,其他的配置基本一样的。另外我们用到DMA读取数据,配置上和单通道ADC采集(DMA读取)实验是一样的。
ADCx常规序列寄存器有四个(ADC_SQR1~ ADC_SQR4),具体怎么配置,需要看我们用多少个通道,比如本实验我们使用2个通道同时采集ADC数据,具体配置如下:
ADC常规序列寄存器1(ADC_SQR1)
ADC常规序列寄存器1描述如图33.4.1.1所示:
图33.4.1.1 ADC_SQR1寄存器
L[3:0]位用于设置常规序列的长度,取值范围:0~15,表示常规序列长度为1~16。本实验使用到6个通道,所以设置这几个位的值为5即可。
SQ1[4:0]~SQ4[4:0]位设置常规组序列的第1~4个转换编号,第5~16个转换编号的设置请查看ADC_SQR2和ADC_SQR4寄存器。设置过程非常简单,忘记了请参考前面给大家整理出来的常规序列寄存器控制关系汇总表。
下面我们来看看本实验是怎么设置的:SQ1[4:0]位赋值为3、SQ2[4:0]位赋值为7,即常规序列1和2分别对应的通道是3和通道7。
33.4.2 硬件设计
1. 例程功能
使用ADC1采集(GPDMA读取)通道3\7的电压,在LCD模块上面显示对应的ADC转换值以及换算成电压后的电压值。可以使用杜邦线连接PA6\PA7到你想测量的电压源(0~3.3V),然后通过TFTLCD显示的电压值。LED0闪烁,提示程序运行。
2. 硬件资源
1)LED灯
LED0 : LED0 – PD14
2)串口1(PB14/PB15连接在板载USB转串口芯片CH340上面)
3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(包括MCU屏和RGB屏,都支持)
4)ADC1 : 通道3–PA6、通道7–PA7
5)GPDMA( 通道10)
3. 原理图
ADC和DMA属于STM32H7R7内部资源,实际上我们只需要软件设置就可以正常工作,另外还需要将待测量的电压源连接到ADC通道上,以便ADC测量。本实验,我们通过ADC1的通道3和通道7来采集外部电压值,并通过DMA来读取。
33.4.3 程序设计
33.4.3.1 ADC的HAL库驱动
本实验用到的ADC的HAL库API函数前面都介绍过,具体调用情况请看程序解析部分。下面介绍多通道ADC采集(DMA读取)配置步骤。
多通道ADC采集(DMA读取)配置步骤
1)开启ADCx和ADC通道对应的IO时钟,并配置该IO为模拟功能
首先开启ADCx的时钟,然后配置GPIO为模拟模式。本实验我们默认用到ADC1通道3和通道7,对应IO是PA6和PA7,它们的时钟开启方法如下:
- __HAL_RCC_ADC12_CLK_ENABLE(); /* 使能ADC1 时钟 */
- __HAL_RCC_GPIOA_CLK_ENABLE(); /* 开启GPIOA时钟 */
复制代码 2)初始化ADCx, 配置其工作参数
通过HAL_ADC_Init函数来设置ADCx时钟分频系数、分辨率、模式、扫描方式、对齐方式等信息。
注意:该函数会调用:HAL_ADC_MspInit回调函数来完成对ADC底层以及其输入通道IO的初始化,包括:ADC及GPIO时钟使能、GPIO模式设置等。这里为了后续的方便管理和理解,我们用adc_nch_dma_init函数来完成GPIO时钟使能、GPIO模式设置等功能,所以没有重定义该回调函数。
3)配置ADC通道
在HAL库中,通过HAL_ADC_ConfigChannel函数来设置配置ADC的通道,根据需求设置通道、序列、采样时间和校准配置单端输入模式或差分输入模式等。这里配置多通道输出,需要多次调用该函数。
配置好ADC通道之后,通过HAL_ADC_Start_DMA函数启动ADC DMA转换。
4)初始化DMA
通过HAL_DMA_Init函数初始化DMA,包括配置通道,外设地址,存储器地址,传输数据量等。
HAL库为了处理各类外设的DMA请求,在调用相关函数之前,需要调用一个宏定义标识符,来连接DMA和外设句柄。这个宏定义为__HAL_LINKDMA。
5)使能DMA对应数据流中断,配置DMA中断优先级并开启中断,启动ADC和DMA
通过HAL_ADC_Start_DMA函数开启ADC转换,通过DMA传输结果。
通过HAL_DMA_Start_IT函数启动DMA读取,使能DMA中断。
通过HAL_NVIC_EnableIRQ函数使能DMA数据流中断。
通过HAL_NVIC_SetPriority函数设置中断优先级。
6)编写中断服务函数
GPDMA中断对于每个数据流都有一个中断服务函数,比如GPDMA1_Channel10的中断服务函数为GPDMA1_Channel10_IRQHandler。HAL库提供了通用DMA中断处理函数HAL_DMA_IRQHandler,在该函数内部,会对DMA传输状态进行分析,然后调用相应的中断处理回调函数。
33.4.3.2 程序解析
在本实验中adc.h头文件只是添加了一些函数声明,下面开始介绍adc.c的函数,本实验只增加了一个函数,ADC的N通道(2通道) DMA读取初始化函数,其定义如下:
- /**
- * @brief 初始化多通道ADC DMA读取
- * @param memory_base: 读取目标内存基地址
- * @retval 无
- */
- void adc_nch_dma_init(uint32_t memory_base)
- {
- /* 初始化ADC */
- g_adc_handle.Instance = ADC_ADCX;
- g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;
- /* 时钟源和预分频系数 */
- g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B; /* 分辨率 */
- g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 数据对齐 */
- g_adc_handle.Init.ScanConvMode = ADC_SCAN_ENABLE; /* 扫描转换模式 */
- g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV; /* EOC */
- g_adc_handle.Init.LowPowerAutoWait = DISABLE; /* 低功耗自动延迟 */
- g_adc_handle.Init.ContinuousConvMode = ENABLE; /* 连续转换模式 */
- g_adc_handle.Init.NbrOfConversion = ADC_ADCX_CH_NUM;
- /* 连续转换模式下的转换数量 */
- g_adc_handle.Init.DiscontinuousConvMode = DISABLE; /* 非连续转换模式 */
- g_adc_handle.Init.NbrOfDiscConversion = 1; /* 非连续转换模式下的转换数量 */
- g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
- /* 规则组转换外部触发源 */
- g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
- /* 规则组转换外部触发源的有效沿 */
- g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;
- /* 采样模式 */
- g_adc_handle.Init.ConversionDataManagement =
- ADC_CONVERSIONDATA_DMA_CIRCULAR; /* 管理转换数据 */
- g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; /* 溢出行为 */
- g_adc_handle.Init.OversamplingMode = DISABLE; /* 过采样模式 */
- HAL_ADC_Init(&g_adc_handle);
-
- /* ADC自动自校准 */
- HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);
-
- g_adc_dma_memory_base = memory_base;
- }
复制代码 该函数比较长,很多配置和单通道ADC采集(DMA读取)实验是一样的,就不全部讲解,下面对不同点说明一下。
在adc_nch_dma_enable函数中,直接把2个通道对应的GPIO口初始化放在函数内部实现。ADC_HandleTypeDef结构体的配置与单通道ADC采集(DMA读取)实验主要不同的是我们使用了2个转换通道,然后需要对每个通道ADC_ChannelConfTypeDef结构体进行配置,用HAL_ADC_ConfigChannel函数来初始化。
最后在main.c里面编写如下代码:
- #define ADC_BUF_SIZE (50 * ADC_ADCX_CH_NUM)
- uint16_t g_adc_buf[ADC_BUF_SIZE];
- extern uint8_t g_adc_dma_sta;
- int main(void)
- {
- uint16_t adc_result[ADC_ADCX_CH_NUM];
- uint16_t voltage;
- uint16_t index;
- uint32_t result_sum;
- uint8_t ch_index;
-
- sys_mpu_config(); /* 配置MPU */
- sys_cache_enable(); /* 使能Cache */
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(300, 6, 2); /* 配置时钟,600MHz */
- delay_init(600); /* 初始化延时 */
- usart_init(115200); /* 初始化串口 */
- led_init(); /* 初始化LED */
- hyperram_init(); /* 初始化HyperRAM */
- lcd_init(); /* 初始化LCD */
- adc_nch_dma_init((uint32_t)g_adc_buf); /* 初始化多通道ADC DMA读取 */
-
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "ADC NCH DMA TEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH3_VAL: 0", BLUE);
- lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH3_VOL: 0.000V", BLUE);
- lcd_show_string(30, 150, 200, 16, 16, "ADC1_CH7_VAL: 0", BLUE);
- lcd_show_string(30, 170, 200, 16, 16, "ADC1_CH7_VOL: 0.000V", BLUE);
-
- /* 开启多通道ADC DMA传输 */
- adc_nch_dma_enable(ADC_BUF_SIZE);
-
- while (1)
- {
- if (g_adc_dma_sta == 1)
- {
- g_adc_dma_sta = 0;
-
- for (ch_index=0; ch_index<ADC_ADCX_CH_NUM; ch_index++)
- {
- /* 对DMA读取的多个ADC数据进行均值滤波 */
- result_sum = 0;
- for (index = 0; index < (ADC_BUF_SIZE / ADC_ADCX_CH_NUM);
- index++)
- {
- result_sum += g_adc_buf[(ADC_ADCX_CH_NUM * index) +
- ch_index];
- }
- adc_result[ch_index] = result_sum / (ADC_BUF_SIZE /
- ADC_ADCX_CH_NUM);
- }
-
- /* 计算实际电压值(扩大1000倍) */
- voltage = (adc_result[0] * VREFINT_CAL_VREF) / 4095;
- lcd_show_xnum(142, 110, adc_result[0], 5, 16, 0, BLUE);
- lcd_show_xnum(142, 130, voltage / 1000, 1, 16, 0, BLUE);
- lcd_show_xnum(158, 130, voltage % 1000, 3, 16, 0x80, BLUE);
-
- /* 计算实际电压值(扩大1000倍) */
- voltage = (adc_result[1] * VREFINT_CAL_VREF) / 4095;
- lcd_show_xnum(142, 150, adc_result[1], 5, 16, 0, BLUE);
- lcd_show_xnum(142, 170, voltage / 1000, 1, 16, 0, BLUE);
- lcd_show_xnum(158, 170, voltage % 1000, 3, 16, 0x80, BLUE);
-
- adc_nch_dma_enable(ADC_BUF_SIZE);
- }
-
- LED0_TOGGLE();
-
- delay_ms(100);
- }
- }
复制代码 这里使用了DMA传输数据,DMA传输的数据存放在g_adc_buf数组里,该数组的大小是50 * ADC_ADCX_CH_NUM,2个通道,每个通道使用50个uint16_t大小的空间存放ADC的结果。例程中该数据存放数据的分配是ADC1_CH3的数据存放在g_adc_buf [0]到g_adc_buf [49],ADC1_CH7的数据存放在g_adc_buf [50]到g_adc_buf [99],后面的以此类推。
然后对数组的每个通道的数据取平均值,减少误差。在LCD屏显示结果的处理和单通道ADC采集实验一样。首先我们在液晶固定位置显示了小数点,然后后面计算步骤中,先计算出整数部分在小数点前面显示,然后计算出小数部分,在小数点后面显示。这样就在液晶上面显示转换结果的整数和小数部分。
33.4.4 下载验证
下载代码后,LED0闪烁,提示程序运行。可以看到LCD显示如图33.4.4.1所示:
图33.4.4.1 多通道ADC采集(DMA读取)实验测试图
使用ADC1采集(DMA读取)通道3/7的电压,在LCD模块上面显示对应的ADC转换值以及换算成电压后的电压值。可以使用杜邦线连接PA6\PA7到你想测量的电压源(0~3.3V)。
注意:一定要保证测试点的电压在0~3.3V的电压范围,否则可能烧坏我们的ADC,甚至是整个主控芯片。
33.5 单通道ADC过采样(16位分辨率)实验
本实验我们来学习单通道ADC过采样(16位分辨率)实验。本实验使用常规转换组单通道的单次转换模式,并且通过软件触发,即通过对ADC_CR寄存器的ADSTART位写1启动转换。下面先带大家来了解本实验要配置的寄存器。
33.5.1 ADC寄存器
本实验很多配置和单通道ADC采集实验是一样的,下面只介绍ADC_CFGR2寄存器。
ADC配置寄存器2(ADC_CFGR2)
ADC配置寄存器2描述如图33.5.1.1所示:
图33.5.1.1 ADC_CFGR2寄存器
OVSR[2:0]位用于设置ADC的过采样率。本实验使用过采样,并且是16位分辨率,所以设置OVSR[2:0] = 111,即256倍过采样。
ROVSM位用于设置常规过采样模式,默认为0即可,即连续模式。
TROVS位用于设置已触发常规过采样,默认为0即可,即会在触发后连续完成某一通道的所有过采样转换。
OVSS[3:0]位用于设置过采样结果右移位数。
ROVSE位是常规过采样使能位,置1使能常规过采样。
33.5.2 硬件设计
1. 例程功能
ADC1过采样(16位分辨率)采集通道19(PA5)上面的电压,在LCD模块上面显示对应的ADC转换值以及换算成电压后的电压值。可以使用杜邦线连接PA5到你想测量的电压源(0~3.3V),然后通过TFTLCD显示的电压值。LED0闪烁,提示程序运行。
2. 硬件资源
1)LED灯
LED0: LED0 – PD14
2)串口1(PB14/PB15连接在板载USB转串口芯片CH340上面)
3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(包括MCU屏和RGB屏,都支持)
4)ADC1 :通道9 – PB0
3. 原理图
ADC属于STM32H7R7内部资源,实际上我们只需要软件设置就可以正常工作,另外还需要将待测量的电压源连接到ADC通道上,以便ADC测量。本实验,我们通过ADC1过采样(16位分辨率)通道9(PB0)来采集外部电压值。
33.5.3 程序设计
33.5.3.1 ADC的HAL库驱动
本实验用到的ADC的HAL库API函数前面都介绍过,具体调用情况请看程序解析部分。
单通道ADC过采样(16位分辨率)配置步骤
1)开启ADCx和ADC通道对应的IO时钟,并配置该IO为模拟功能
首先开启ADCx的时钟,然后配置GPIO为模拟模式。本实验我们默认用到ADC1通道19,对应IO是PB0,它们的时钟开启方法如下:
- __HAL_RCC_ADC12_CLK_ENABLE (); /* 使能ADC1 & ADC2时钟 */
- __HAL_RCC_GPIOB_CLK_ENABLE(); /* 开启GPIOB时钟 */
复制代码 2)初始化ADCx,配置其工作参数
通过HAL_ADC_Init函数来设置ADCx时钟分频系数、分辨率、模式、扫描方式、对齐方式、开启过采样等信息。
注意:该函数会调用:HAL_ADC_MspInit回调函数来存放ADC及GPIO时钟使能、GPIO初始化等代码。我们也可以不存放在这个函数里,本实验就没用到这个MSP回调函数。
3)配置ADC通道并启动AD转换器
在HAL库中,通过HAL_ADC_ConfigChannel函数来设置配置ADC的通道,根据需求设置通道、序列、采样时间和校准配置单端输入模式或差分输入模式等。
配置好ADC通道之后,通过HAL_ADC_Start函数启动AD转换器。
4)读取ADC值
这里选择查询方式读取,在读取ADC值之前需要调用HAL_ADC_PollForConversion等待上一次转换结束。然后就可以通过HAL_ADC_GetValue来读取ADC值。
33.5.3.2 程序解析
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。ADC驱动源码包括两个文件:adc.c和adc.h。adc.h文件与前面几个小实验大致相同,我们看adc.c文件。
- /**
- * @brief 初始化ADC过采样
- * @param ratio: 过采样率
- * @arg ADC_OVERSAMPLING_RATIO_2: 2
- * @arg ADC_OVERSAMPLING_RATIO_4: 4
- * @arg ADC_OVERSAMPLING_RATIO_8: 8
- * @arg ADC_OVERSAMPLING_RATIO_16: 16
- * @arg ADC_OVERSAMPLING_RATIO_32: 23
- * @arg ADC_OVERSAMPLING_RATIO_64: 64
- * @arg ADC_OVERSAMPLING_RATIO_128: 128
- * @arg ADC_OVERSAMPLING_RATIO_256: 256
- * @param right_shift: 过采样右移位数
- * @arg ADC_RIGHTBITSHIFT_NONE: 无右移
- * @arg ADC_RIGHTBITSHIFT_1: 右移1位
- * @arg ADC_RIGHTBITSHIFT_2: 右移2位
- * @arg ADC_RIGHTBITSHIFT_3: 右移3位
- * @arg ADC_RIGHTBITSHIFT_4: 右移4位
- * @arg ADC_RIGHTBITSHIFT_5: 右移5位
- * @arg ADC_RIGHTBITSHIFT_6: 右移6位
- * @arg ADC_RIGHTBITSHIFT_7: 右移7位
- * @arg ADC_RIGHTBITSHIFT_8: 右移8位
- * @retval 无
- */
- void adc_oversample_init(uint32_t ratio, uint32_t right_shift)
- {
- /* 初始化ADC */
- g_adc_handle.Instance = ADC_ADCX;
- g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;
- /* 时钟源和预分频系数 */
- g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B; /* 分辨率 */
- g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; /* 数据对齐 */
- g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; /* 扫描转换模式 */
- g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV; /* EOC */
- g_adc_handle.Init.LowPowerAutoWait = DISABLE; /* 低功耗自动延迟 */
- g_adc_handle.Init.ContinuousConvMode = DISABLE; /* 连续转换模式 */
- g_adc_handle.Init.NbrOfConversion = 1; /* 连续转换模式下的转换数量 */
- g_adc_handle.Init.DiscontinuousConvMode = DISABLE; /* 非连续转换模式 */
- g_adc_handle.Init.NbrOfDiscConversion = 1;
- /* 非连续转换模式下的转换数量 */
- g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
- /* 规则组转换外部触发源 */
- g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
- /* 规则组转换外部触发源的有效沿 */
- g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;
- /* 采样模式 */
- g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
- /* 管理转换数据 */
- g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; /* 溢出行为 */
- g_adc_handle.Init.OversamplingMode = ENABLE; /* 过采样模式 */
- g_adc_handle.Init.Oversampling.Ratio = ratio; /* 过采样率 */
- g_adc_handle.Init.Oversampling.RightBitShift=right_shift;/* 过采样右移位数 */
- g_adc_handle.Init.Oversampling.TriggeredMode =
- ADC_TRIGGEREDMODE_SINGLE_TRIGGER; /* 过采样触发模式 */
- g_adc_handle.Init.Oversampling.OversamplingStopReset =
- ADC_REGOVERSAMPLING_CONTINUED_MODE; /* 规则过采样模式 */
- HAL_ADC_Init(&g_adc_handle);
-
- /* ADC自动自校准 */
- HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);
- }
复制代码 现在来看看main.c的代码:
- int main(void)
- {
- uint32_t adc_result;
- uint32_t voltage;
-
- sys_mpu_config(); /* 配置MPU */
- sys_cache_enable(); /* 使能Cache */
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(300, 6, 2); /* 配置时钟,600MHz */
- delay_init(600); /* 初始化延时 */
- usart_init(115200); /* 初始化串口 */
- led_init(); /* 初始化LED */
- hyperram_init(); /* 初始化HyperRAM */
- lcd_init(); /* 初始化LCD */
- adc_oversample_init(ADC_OVERSAMPLING_RATIO_256, ADC_RIGHTBITSHIFT_4);
- /* 初始化ADC */
-
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
- lcd_show_string(30, 70, 200, 16, 16, "ADC TEST", RED);
- lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
- lcd_show_string(30, 110, 200, 16, 16, "ADC1_CH9_VAL: 0", BLUE);
- lcd_show_string(30, 130, 200, 16, 16, "ADC1_CH9_VOL: 0.000V", BLUE);
-
- while (1)
- {
- adc_result = adc_get_result_average(ADC_ADCX_CHY, 20);
- /* 均值滤波获取ADC结果 */
- lcd_show_xnum(142, 110, adc_result, 5, 16, 0, BLUE);
-
- voltage = (adc_result * VREFINT_CAL_VREF) / 65535;
- /* 计算实际电压值(扩大1000倍) */
- lcd_show_xnum(142, 130, voltage / 1000, 1, 16, 0, BLUE);
- lcd_show_xnum(158, 130, voltage % 1000, 3, 16, 0x80, BLUE);
-
- LED0_TOGGLE();
-
- delay_ms(100);
- }
- }
复制代码 此部分代码,我们在TFTLCD模块上显示一些提示信息后,将每隔100ms读取一次ADC通道9的值,并显示读到的ADC值(数字量),以及其转换成模拟量后的电压值。同时控制LED0闪烁,以提示程序正在运行。
33.5.4 下载验证
下载代码后,LED0闪烁,提示程序运行。可以看到LCD显示如图33.5.4.1所示:
图33.5.4.1 单通道ADC过采样(16位分辨率)实验测试图
大家也可以用杜邦线将ADC排针接到其它待测量的电压点,看看测量到的电压值是否准确?但是要注意:一定要保证测试点的电压在0~3.3V的电压范围,否则可能烧坏我们的ADC,甚至是整个主控芯片。
|
|