【ARM Cortex-M 开发实战指南(基础篇)】第12章 ADC

开发环境:
MDK:Keil 5.30
STM32CubeMX:V6.4.0
MCU:STM32F103ZET6

12.1 ADC工作原理

STM32F1系列有 3 个 ADC,精度为 12 位,每个 ADC 最多有 16 个外部通道。其中ADC1 和 ADC2 都有 16 个外部通道, ADC3 根据 CPU 引脚的不同通道数也不同,一般都有8 个外部通道。各通道的A/D转换可以单次、连续、扫描或间断模式执行。ADC的结果可以左对齐或右对齐方式存储在16位数据寄存器中。模拟看门狗特性允许应用程序检测输入电压是否超出用户定义的高/低阀值。ADC 的输入时钟不得超过14MHz,它是由PCLK2经分频产生。

12.1.1 ADC架构

ADC架构如下图所示。

xpEay8.md.png

1.电压输入范围
ADC 输入范围为:VREF- ≤ VIN ≤ VREF+。由 VREF-、VREF+、VDDA、VSSA这四个外部引脚决定。

我们在设计原理图的时候一般把 VSSA 和 VREF-接地,把 VREF+和 VDDA 接 3V3,得到ADC 的输入电压范围为: 0 ~ 3.3V。在 64 脚以下的 CPU 中,没有 VREF-和 VREF+这两个引脚,
ADC 电压输入范围直接由 VDDA和 VSSA决定。如果我们想让输入的电压范围变宽,去到可以测试负电压或者更高的正电压,我们可以在外部加一个电压调理电路,把需要转换的电压抬升或者降压到 0~3.3V,这样 ADC 就可以测量。

xpVgNd.md.png

【注】1.VDDA和VSSA应该分别连接到VDD和VSS。

2.输入通道
我们确定好ADC输入电压之后,那么电压怎么输入到 ADC?这里我们引入通道的概念,STM32 的ADC多达18个通道,其中外部的16个通道就是框图中的 ADCx_IN0、ADCx_IN1…ADCx_IN5。这16个通道对应着不同的 IO 口,具体是哪一个 IO 口可以从手册查询到。其中 ADC1/2/3 还有内部通道: ADC1 的通道 16 连接到了芯片内部的温度传感器, Vrefint 连接到了通道 17。 ADC2 的模拟通道 16 和 17 连接到了内部的 VSS。ADC3 的模拟通道 9、 14、 15、 16 和 17 连接到了内部的 VSS。

外部的 16 个通道在转换的时候又分为规则通道和注入通道,其中规则通道最多有 16路,注入通道最多有 4 路。那这两个通道有什么区别?在什么时候使用?

规则通道
规则通道:规则通道就是很规矩的意思,我们平时一般使用的就是这个通道,或者应该说我们用到的都是这个通道,没有什么特别要注意的可讲。

注入通道
注入,可以理解为插入,插队的意思,是一种不安分的通道。它是一种在规则通道转换的时候强行插入要转换的一种。如果在规则通道转换过程中,有注入通道插队,那么就要先转换完注入通道,等注入通道转换完成后,再回到规则通道的转换流程。这点跟中断程序很像,都是不安分的主。所以,注入通道只有在规则通道存在时才会出现。

3.转换顺序

规则序列
规则序列寄存器有 3 个,分别为 SQR3、 SQR2、 SQR1。 SQR3 控制着规则序列中的第一个到第六个转换,对应的位为: SQ1[4:0] ~ SQ6[4:0],第一次转换的是位 4:0 SQ1[4:0],如果通道 16 想第一次转换,那么在 SQ1[4:0]写 16 即可。SQR2 控制着规则序列中的第 7 到第12 个转换,对应的位为: SQ7[4:0] ~ SQ12[4:0],如果通道 1 想第 8 个转换,则 SQ8[4:0]写 1即可。 SQR1 控制着规则序列中的第 13 到第 16 个转换,对应位为: SQ13[4:0] ~ SQ16[4:0],如果通道 6 想第 10 个转换,则 SQ10[4:0]写 6 即可。具体使用多少个通道,由 SQR1 的位L[3:0]决定,最多 16 个通道。

xpVOCn.md.png

注入序列
注入序列寄存器 JSQR 只有一个,最多支持 4 个通道,具体多少个由 JSQR 的 JL[2:0]决定。如果 JL 的 值小于 4 的话,则 JSQR 跟 SQR 决定转换顺序的设置不一样,第一次转换的不是 JSQR1[4:0],而是 JCQRx[4:0] , x = (4-JL),跟 SQR 刚好相反。如果 JL=00(1个转换),那么转换的顺序是从 JSQR4[4:0]开始,而不是从 JSQR1[4:0]开始,这个要注意,编程的时候不要搞错。当 JL 等于 4 时,跟 SQR 一样。

xpVX3q.md.png

4.触发源
通道选好了,转换的顺序也设置好了,那接下来就该开始转换了。 ADC 转换可以由ADC 控制寄存器 : ADC_CR2 的 ADON 这个位来控制,写 1 的时候开始转换,写 0 的时候停止转换,这个是最简单也是最好理解的开启 ADC 转换的控制方式,理解起来没啥技术含量。

除了这种庶民式的控制方法, ADC 还支持触发转换,这个触发包括内部定时器触发和外部 IO 触发。触发源有很多,具体选择哪一种触发源,由 ADC 控制寄存器 2:ADC_CR2 的EXTSEL[2:0]和 JEXTSEL[2:0]位来控制。 EXTSEL[2:0]用于选择规则通道的触发源,JEXTSEL[2:0]用于选择注入通道的触发源。选定好触发源之后,触发源是否要激活,则由ADC 控制寄存器 2:ADC_CR2 的 EXTTRIG 和 JEXTTRIG 这两位来激活。其中 ADC3 的规则转换和注入转换的触发源与 ADC1/2 的有所不同,在框图上已经表示出来。

5.转换时间
ADC 时钟
ADC 输入时钟 ADC_CLK 由 PCLK2 经过分频产生,最大是 14M,分频因子由 RCC 时钟配置寄存器 RCC_CFGR 的位 15:14 ADCPRE[1:0]设置,可以是 2/4/6/8 分频,注意这里没有 1 分频。一般我们设置 PCLK2=HCLK=72M。

采样时间
ADC 使用若干个 ADC_CLK 周期对输入的电压进行采样,采样的周期数可通过 ADC采样时间寄存器 ADC_SMPR1 和 ADC_SMPR2 中的 SMP[2:0]位设置, ADC_SMPR2 控制的是通道 0~9, ADC_SMPR1 控制的是通道 10~17。每个通道可以分别用不同的时间采样。其中采样周期最小是 1.5 个,即如果我们要达到最快的采样,那么应该设置采样周期为 1.5个周期,这里说的周期就是 1/ADC_CLK。

ADC 的转换时间跟 ADC 的输入时钟和采样时间有关,公式为: Tconv = 采样时间 +12.5 个周期。 当 ADCLK = 14MHZ (最高),采样时间设置为 1.5 周期(最快),那么总的转换时间(最短) Tconv = 1.5 周期 + 12.5 周期 = 14 周期 = 1us。一般我们设置 PCLK2=72M,经过 ADC 预分频器能分频到最大的时钟只能是 12M,采样周期设置为 1.5 个周期,算出最短的转换时间为 1.17us,这个才是最常用的。

6.数据寄存器
一切准备就绪后, ADC 转换后的数据根据转换组的不同,规则组的数据放在 ADC_DR寄存器,注入组的数据放在 JDRx。

规则数据寄存器
ADC 规则组数据寄存器 ADC_DR 只有一个,是一个 32 位的寄存器,低 16 位在单 ADC时使用,高 16 位是在 ADC1 中双模式下保存 ADC2 转换的规则数据,双模式就是 ADC1 和ADC2 同时使用。在单模式下, ADC1/2/3 都不使用高 16 位。因为 ADC 的精度是 12 位,无论 ADC_DR 的高 16 或者低 16 位都放不满,只能左对齐或者右对齐,具体是以哪一种方式存放,由 ADC_CR2 的 11 位 ALIGN 设置。

规则通道可以有 16 个这么多,可规则数据寄存器只有一个, 如果使用多通道转换, 那转换的数据就全部都挤在了 DR 里面,前一个时间点转换的通道数据,就会被下一个时间点的另外一个通道转换的数据覆盖掉,所以当通道转换完成后就应该把数据取走,或者开启 DMA 模式,把数据传输到内存里面,不然就会造成数据的覆盖。 最常用的做法就是开启 DMA 传输。

注入数据寄存器
ADC 注入组最多有 4 个通道,刚好注入数据寄存器也有 4 个,每个通道对应着自己的寄存器,不会跟规则寄存器那样产生数据覆盖的问题。 ADC_JDRx 是 32 位的,低 16 位有效,高 16 位保留,数据同样分为左对齐和右对齐,具体是以哪一种方式存放,由ADC_CR2 的 11 位 ALIGN 设置。

7.中断
转换结束中断
数据转换结束后,可以产生中断,中断分为三种:规则通道转换结束中断,注入转换通道转换结束中断,模拟看门狗中断。其中转换结束中断很好理解,跟我们平时接触的中断一样,有相应的中断标志位和中断使能位,我们还可以根据中断类型写相应配套的中断服务程序。

模拟看门狗中断
当被 ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断,前提是我们开启了模拟看门狗中断,其中低阈值和高阈值由 ADC_LTR 和 ADC_HTR 设置。例如我们设置高阈值是 2.5V,那么模拟电压超过 2.5V 的时候,就会产生模拟看门狗中断,反之低阈值也一样。

DMA 请求
规则和注入通道转换结束后,除了产生中断外,还可以产生 DMA 请求,把转换好的数据直接存储在内存里面。要注意的是只有 ADC1 和 ADC3 可以产生 DMA 请求。有关DMA 请求需要配合《STM32F10X参考手册》 DMA 控制器这一章节来学习。一般我们在使用 ADC 的时候都会开启 DMA 传输。

8.电压转换
模拟电压经过 ADC 转换后,是一个 12 位的数字值,如果通过串口以 16 进制打印出来的话,可读性比较差,那么有时候我们就需要把数字电压转换成模拟电压,也可以跟实际的模拟电压(用万用表测)对比,看看转换是否准确。

我们一般在设计原理图的时候会把 ADC 的输入电压范围设定在: 0~3.3v,因为 ADC是 12 位的,那么 12 位满量程对应的就是 3.3V, 12 位满量程对应的数字值是: 2^12。数值0 对应的就是 0V。 如果转换后的数值为 X , X 对应的模拟电压为 Y,那么会有这么一个等式成立: 2^12 / 3.3 = X / Y, => Y = (3.3 * X ) / 2^12。

12.1.2 ADC通道选择

STM32 将 ADC 的转换分为 2 个通道组:规则通道组和注入通道组。规则通道相当于你正常运行的程序,而注入通道呢,就相当于中断。在你程序正常执行的时候,中断是可以打断你的执行的。同这个类似,注入通道的转换可以打断规则通道的转换, 在注入通道被转换完成之后,规则通道才得以继续转换。

xpVzuT.md.jpg

上面的例子因为速度较慢,不能完全体现这样区分(规则通道组和注入通道组)的好处,但在工业应用领域中有很多检测和监视探头需要较快地处理,这样对 AD 转换的分组将简化事件处理的程序并提高事件处理的速度。

STM32 其 ADC 的规则通道组最多包含 16 个转换,而注入通道组最多包含 4 个通道。关于这两个通道组的详细介绍,请参考《STM32 参考手册》,我们这里就不在一一列举了。
● 规则组由多达16个转换组成。规则通道和它们的转换顺序在ADC_SQRx寄存器中选择。规则组中转换的总数应写入ADC_SQR1寄存器的L[3:0]位中。
● 注入组由多达4个转换组成。注入通道和它们的转换顺序在ADC_JSQR寄存器中选择。注入组里的转换总数目应写入ADC_JSQR寄存器的L[1:0]位中。

如果ADC_SQRx或ADC_JSQR寄存器在转换期间被更改,当前的转换被清除,一个新的启动脉冲将发送到ADC 以转换新选择的组。

温度传感器和通道ADC1_IN16相连接,内部参照电压VREFINT和ADC1_IN17相连接。可以按注入或规则通道对这两个内部通道进行转换。

【注意】温度传感器和VREFINT只能出现在主ADC1 中。

12.1.3 ADC转换模式

 单次转换模式
单次转换模式下,ADC只执行一次转换。该模式既可通过设置ADC_CR2 寄存器的ADON位(只适用于规则通道)启动也可通过外部触发启动(适用于规则通道或注入通道),这时CONT位为0 。一旦选择通道的转换完成:

● 如果一个规则通道被转换:
─ 转换数据被储存在16位ADC_DR寄存器中
─ EOC(转换结束)标志被设置
─ 如果设置了EOCIE,则产生中断。

● 如果一个注入通道被转换:
─ 转换数据被储存在16位的ADC_DRJ1寄存器中
─ JEOC(注入转换结束)标志被设置
─ 如果设置了JEOCIE位,则产生中断。然后ADC停止。

 连续转换模式
在连续转换模式中,当前面ADC转换一结束马上就启动另一次转换。此模式可通过外部触发启动或通过设置ADC_CR2寄存器上的ADON位启动,此时CONT位是1。 每个转换后:
● 如果一个规则通道被转换:
─ 转换数据被储存在16位的ADC_DR寄存器中
─ EOC(转换结束)标志被设置
─ 如果设置了EOCIE,则产生中断。
● 如果一个注入通道被转换:
─ 转换数据被储存在16位的ADC_DRJ1寄存器中
─ JEOC(注入转换结束)标志被设置
─ 如果设置了JEOCIE位,则产生中断。

 扫描模式
此模式用来扫描一组模拟通道。

扫描模式可通过设置ADC_CR1寄存器的SCAN位来选择。一旦这个位被设置,ADC扫描所有被ADC_SQRX 寄存器(对规则通道)或ADC_JSQR(对注入通道)选中的所有通道。在每个组的每个通道上执行单次转换。在每个转换结束时,同一组的下一个通道被自动转换。如果设置了CONT位,转换不会在选择组的最后一个通道上停止,而是再次从选择组的第一个通道继续转换。

如果设置了DMA位,在每次EOC后,DMA控制器把规则组通道的转换数据传输到SRAM 中。而注入通道转换的数据总是存储在ADC_JDRx寄存器中。

 间断模式
规则组
此模式通过设置ADC_CR1 寄存器上的DISCEN位激活。它可以用来执行一个短序列的n次转换(n<=8),此转换是ADC_SQRx寄存器所选择的转换序列的一部分。数值n由ADC_CR1寄存器的DISCNUM[2:0]位给出。

一个外部触发信号可以启动ADC_SQRx 寄存器中描述的下一轮n次转换,直到此序列所有的转换完成为止。总的序列长度由ADC_SQR1寄存器的L[3:0]定义。

举例: n=3,被转换的通道 = 0 、1、2、3、6、7、9、10
第一次触发:转换的序列为 0 、1、2
第二次触发:转换的序列为 3 、6、7
第三次触发:转换的序列为 9 、10,并产生EOC事件
第四次触发:转换的序列 0 、1、2

注意:
1.当以间断模式转换一个规则组时,转换序列结束后不自动从头开始。
2.当所有子组被转换完成,下一次触发启动第一个子组的转换。在上面的例子中,第四次触发重新转换第一子组的通道 0 、1和2。

注入组

此模式通过设置ADC_CR1 寄存器的JDISCEN位激活。在一个外部触发事件后,该模式按通道顺序逐个转换ADC_JSQR寄存器中选择的序列。

一个外部触发信号可以启动ADC_JSQR寄存器选择的下一个通道序列的转换,直到序列中所有的转换完成为止。总的序列长度由ADC_JSQR寄存器的JL[1:0]位定义。

例子: n=1,被转换的通道 = 1 、2、3
第一次触发:通道1被转换
第二次触发:通道2被转换
第三次触发:通道3被转换,并且产生EOC和JEOC事件
第四次触发:通道1被转换

【注意】
1.当完成所有注入通道转换,下个触发启动第1个注入通道的转换。在上述例子中,第四个触发重新转换第1个注入通道1。
2.不能同时使用自动注入和间断模式。
3.必须避免同时为规则和注入组设置间断模式。间断模式只能作用于一组转换。

12.2 ADC寄存器描述

我们介绍一下我们执行规则通道的单次转换,需要用到的 ADC 寄存器。第一个要介绍的是 ADC 控制寄存器(ADC_CR1 和 ADC_CR2)。 ADC_CR1 的各位描述如下图所示。

xpZCE4.md.png

这里我们不再详细介绍每个位,而是抽出几个我们本章要用到的位进行针对性的介绍,详细的说明及介绍,请参考《STM32 参考手册》。

ADC_CR1 的 SCAN 位,该位用于设置扫描模式,由软件设置和清除,如果设置为 1,则使用扫描模式,如果为 0,则关闭扫描模式。在扫描模式下,由 ADC_SQRx 或 ADC_JSQRx 寄存器选中的通道被转换。如果设置了 EOCIE 或 JEOCIE,只在最后一个通道转换完毕后才会产生 EOC 或 JEOC 中断。

ADC_CR1[19: 16]用于设置 ADC 的操作模式,详细的对应关系如下图所示。

xpZPUJ.png

本章我们要使用的是独立模式,所以设置这几位为 0 就可以了。接着我们介绍 ADC_CR2,该寄存器的各位描述如下图所示。

xpZi59.md.png

该寄存器我们也只针对性的介绍一些位: ADON 位用于开关 AD 转换器。而 CONT 位用于设置是否进行连续转换,我们使用单次转换,所以 CONT 位必须为 0。 CAL 和 RSTCAL 用于ADC 校准。
ADC_CR2寄存器中的ALIGN位选择转换后数据储存的对齐方式。数据可以左对齐或右对齐,如下图所示。

xpZkCR.md.png

xpZA81.md.png

注入组通道转换的数据值已经减去了在ADC_JOFRx寄存器中定义的偏移量,因此结果可以是一个负值。SEXT位是扩展的符号值。对于规则组通道,不需减去偏移值,因此只有12个位有效。
EXTSEL[2: 0]用于选择启动规则转换组转换的外部事件,详细的设置关系如下图所示。

xpZEgx.png

我们这里使用的是软件触发(SWSTART),所以设置这 3 个位为 111。 ADC_CR2 的SWSTART 位用于开始规则通道的转换,我们每次转换(单次转换模式下)都需要向该位写 1。TSVREFE为用于使能温度传感器和 Vrefint。 STM32 内部的温度传感器我们将在后文介绍。

第二个要介绍的是 ADC 采样事件寄存器(ADC_SMPR1 和 ADC_SMPR2),这两个寄存器用于设置通道 0~17 的采样时间,每个通道占用 3 个位。 ADC_SMPR1 的各位描述如下图。

xpZnbD.md.png

ADC_SMPR2 的各位描述如下图所示。

xpZKVe.md.png

对于每个要转换的通道,采样时间建议尽量长一点,以获得较高的准确度,但是这样会降低 ADC 的转换速率。ADC的转换时间可以由以下公式计算:

Tcovn=采样时间+12.5 个周期

其中: Tcovn 为总转换时间,采样时间是根据每个通道的 SMP 位的设置来决定的。例如,当 ADCCLK=14Mhz 的时候,并设置 1.5 个周期的采样时间,则得到: Tcovn=1.5+12.5=14 个周期=1us。

常见的周期有:1.5周期、7.5周期、13.5周期、28.5周期、41.5周期、55.5周期、71.5周期、239.5周期。

第三个要介绍的是 ADC 规则序列寄存器(ADC_SQR1~3) ,该寄存器总共有 3 个,这几个寄存器的功能都差不多,这里我们仅介绍一下 ADC_SQR1,该寄存器的各位描述如下图所示。

xpZ38I.md.png

L[3: 0]用于存储规则序列的长度,我们这里只用了 1 个,所以设置这几个位的值为 0。其他的 SQ13~16 则存储了规则序列中第 13~16 个通道的编号(0~17)。另外两个规则序列寄存器同 ADC_SQR1 大同小异,我们这里就不再介绍了,要说明一点的是:我们选择的是单次转换,所以只有一个通道在规则序列里面,这个序列就是 SQ1,通过 ADC_SQR3 的最低 5 位(也就是 SQ1) 设置。

第四个要介绍的是 ADC 规则数据寄存器(ADC_DR)。规则序列中的 AD 转化结果都将被存在这个寄存器里面,而注入通道的转换结果被保存在 ADC_JDRx 里面。 ADC_DR 的各位描述如下图。

xpZGxP.md.png

xpZYKf.md.png

这里要提醒一点的是,该寄存器的数据可以通过 ADC_CR2 的 ALIGN 位设置左对齐还是右对齐。在读取数据的时候要注意。

最后一个要介绍的 ADC 寄存器为 ADC 状态寄存器(ADC_SR),该寄存器保存了 ADC 转换时的各种状态。该寄存器的各位描述如上图。

这里我们要用到的是 EOC 位,我们通过判断该位来决定是否此次规则通道的 AD 转换已经完成,如果完成我们就从 ADC_DR 中读取转换结果,否则等待转换完成。

12.3 ADC具体代码实现-标准库

接下来笔者将通过三种方式实现ADC单通道电压数据采集,先看看笔者使用的开发板的硬件电路,其中PC0外接了一个滑动电阻。

xpZaVg.png

12.3.1 ADC单通道电压采集查询方式实现

ADC参数设置的详细步骤:

1)开启 PC 口时钟和 ADC1 时钟,设置 PC0 为模拟输入。
STM32F103ZET6 的ADC 通道 10 在 PC0 上,所以,我们先要使能 PC 的时钟和 ADC1时钟,然后设置PC0为模拟输入。 使能 GPIOC 和 ADC 时钟用 RCC_APB2PeriphClockCmd 函数,设置 PC0的输入方式,使用 GPIO_Init 函数即可。

2)复位 ADC1,同时设置 ADC1 分频因子。
开启 ADC1 时钟之后,我们要复位 ADC1, 将 ADC1 的全部寄存器重设为缺省值之后我们就可以通过 RCC_CFGR 设置 ADC1 的分频因子。分频因子要确保 ADC1 的时钟(ADCCLK)不要超过 14Mhz。 这个我们设置分频因子位 8, 时钟为 72/8=9MHz,库函数的实现方法是:

void RCC_ADCCLKConfig(uint32_t RCC_PCLK2);

输入参数范围:

#define RCC_PCLK2_Div2    ((uint32_t)0x00000000)
#define RCC_PCLK2_Div4    ((uint32_t)0x00004000)
#define RCC_PCLK2_Div6    ((uint32_t)0x00008000)
#define RCC_PCLK2_Div8    ((uint32_t)0x0000C000)

xpZw5j.png

STM32的ADC最大的转换速率为1Mhz,也就是转换时间为1us(在ADCCLK=14M,采样周期为1.5个ADC时钟下得到),不要让ADC的时钟超过14M,否则将导致结果准确度下降。

3) 初始化 ADC1 参数,设置 ADC1 的工作模式以及规则序列的相关信息。
在设置完分频因子之后,我们就可以开始 ADC1 的模式配置了,设置单次转换模式、触发方式选择、数据对齐方式等都在这一步实现。 同时,我们还要设置 ADC1 规则序列的相关信息,我们这里只有一个通道,并且是单次转换的,所以设置规则序列中通道数为 1。 这些在库函数中是通过函数 ADC_Init 实现的,下面我们看看其定义:

void ADC_Init(ADC_TypeDef* ADCx, ADC_InitTypeDef* ADC_InitStruct);

从函数定义可以看出,第一个参数是指定 ADC 号。这里我们来看看第二个参数,跟其他外设初始化一样,同样是通过设置结构体成员变量的值来设定参数。

typedef struct
{
uint32_t ADC_Mode;
FunctionalState ADC_ScanConvMode;
FunctionalState ADC_ContinuousConvMode;
uint32_t ADC_ExternalTrigConv;
uint32_t ADC_DataAlign;
uint8_t ADC_NbrOfChannel;
}ADC_InitTypeDef;

ADC_Mode 故名是以是用来设置 ADC 的模式。前面讲解过,ADC的模式非常多,包括独立模式,注入同步模式等等,这里我们选择独立模式,所以参数为 ADC_Mode_Independent。

ADC_ScanConvMode 用来设置是否开启扫描模式,因为是单通道转换,这里我们选择不开启值 DISABLE 即可。

ADC_ContinuousConvMode 用来设置是否开启连续转换模式,因为是单次转换模式,所以我们选择不开启连续转换模式,DISABLE 即可。

ADC_ExternalTrigConv 是用来设置启动规则转换组转换的外部事件,这里我们选择软件触发,选择值为 ADC_ExternalTrigConv_None 即可。

DataAlign 用来设置 ADC 数据对齐方式是左对齐还是右对齐,这里我们选择右对齐方式ADC_DataAlign_Right。

ADC_NbrOfChannel 用来设置规则序列的长度,这里我们是单次转换,所以值为 1 即可。通过上面对每个参数的讲解, 下面来看看我们的初始化范例:

ADC_InitTypeDef ADC_InitStructure;

ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;/*独立工作模式*/
ADC_InitStructure.ADC_ScanConvMode=DISABLE;/*单通道模式*/
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;/*单次模式*/
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;/*触发方式*/
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;/*右对齐*/
ADC_InitStructure.ADC_NbrOfChannel=1;/*转换数目*/

ADC_Init(ADC1,&ADC_InitStructure);

4)使能 ADC 并校准。
在设置完了以上信息后, 我们就使能 AD 转换器,执行复位校准和 AD 校准,注意这两步是必须的!不校准将导致结果很不准确。

使能指定的 ADC 的方法是:

ADC_Cmd(ADC1, ENABLE); //使能指定的 ADC1

执行复位校准的方法是:

ADC_ResetCalibration(ADC1);

执行 ADC 校准的方法是:

ADC_StartCalibration(ADC1); //开始指定 ADC1 的校准状态

记住,每次进行校准之后要等待校准结束。 这里是通过获取校准状态来判断是否校准是否结束。

下面我们一一列出复位校准和 AD 校准的等待结束方法:

while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
while(ADC_GetCalibrationStatus(ADC1)); //等待ADC校准结束

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

通过设置ADC_CR2寄存器的CAL位启动校准。一旦校准结束,CAL位被硬件复位,可以开始正常转换。建议在上电时执行一次ADC校准。校准阶段结束后,校准码储存在ADC_DR 中。

【注意】
1.建议在每次上电后执行一次校准。
2.启动校准前,ADC必须处于关电状态(ADON=’0’)超过至少两个ADC时钟周期。

5)读取 ADC 值。

在上面的校准完成之后, ADC 就算准备好了。接下来我们要做的就是设置规则序列 1 里面的通道,采样顺序, 以及通道的采样周期, 然后启动 ADC 转换。在转换结束后,读取 ADC 转换结果值就是了。 这里设置规则序列通道以及采样周期的函数是:

void ADC_RegularChannelConfig(ADC_TypeDef* ADCx, uint8_t ADC_Channel,
uint8_t Rank, uint8_t ADC_SampleTime);

我们这里是规则序列中的第 1 个转换,同时采样周期为 239.5,所以设置为:

ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5 );

软件开启 ADC 转换的方法是:

ADC_SoftwareStartConvCmd(ADC1, ENABLE);//使能指定的 ADC1 的软件转换启动功能

开启转换之后,就可以获取转换 ADC 转换结果数据, 方法是:

ADC_GetConversionValue(ADC1);

同时在 AD 转换中,我们还要根据状态寄存器的标志位来获取 AD 转换的各个状态信息。 库函数获取 AD 转换的状态信息的函数是:

FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);

比如我们要判断 ADC1d 的转换是否结束,方法是:

while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束

接下来看看ADC完成的配置。

/**
  * @brief     ADC初始化
  * @param     None
  * @retval    None
  */
void ADC_Configuration(void)
{
    ADC_InitTypeDef ADC_InitStructure;

    ADC_GPIO_Configuration();

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
    RCC_ADCCLKConfig(RCC_PCLK2_Div8);//9M  最大14M

    ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;/*独立工作模式*/
    ADC_InitStructure.ADC_ScanConvMode=DISABLE;/*单通道模式*/
    ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;/*单次模式*/
    ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;/*触发方式*/
    ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;/*右对齐*/
    ADC_InitStructure.ADC_NbrOfChannel=1;/*转换数目*/

    ADC_Init(ADC1,&ADC_InitStructure);

    /*指定ADC的规则组通道,设定他们的转换顺序和采样时间*/
    ADC_RegularChannelConfig(ADC1,ADC_Channel_10,1,ADC_SampleTime_239Cycles5);

    ADC_Cmd(ADC1,ENABLE);//使能ADC

    ADC_ResetCalibration(ADC1);//获取ADC重置校准寄存器状态
    while(ADC_GetResetCalibrationStatus(ADC1));  //等待ADC重置完成

    ADC_StartCalibration(ADC1);//开始指定ADC校准状态
    while(ADC_GetCalibrationStatus(ADC1));//等待ADC校准完成
}

主函数如下所示。

/**
  * @brief     主函数
  * @param     None
  * @retval    int
  */
int main(void)
{
    float ADC_ConvertedValueLocal;   
    uint32_t ADC_ConvertedValue;

    /*SysTick Init*/
    SysTick_Init();

    /* USART1 config 115200 8-N-1 */
    USART_Config();

    /*ADC初始化*/
    ADC_Configuration();

    for(;;)
    {
        ADC_SoftwareStartConvCmd(ADC1,ENABLE);//启动ADC转换

        while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC));//检查转换标志
        ADC_ConvertedValue=ADC_GetConversionValue(ADC1);//ADC转换结果

        ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*3.3; // 读取转换的AD值
        printf("The current AD value = 0x%04X \r\n", ADC_ConvertedValue); 
        printf("The current AD value = %f V \r\n\r\n",ADC_ConvertedValueLocal); //实际电压值

        Delay_ms(1000);
    }
}

如果想开启连续转换,只需将ADC_ContinuousConvMode 配置为ENABLE即可。

ADC_InitStructure.ADC_ContinuousConvMode= ENABLE;/*连续模式*/

然后只需打开启动一次ADC转换。

ADC_SoftwareStartConvCmd(ADC1,ENABLE);//启动ADC转换

12.3.2 ADC单通道电压采集中断方式实现

中断方式和查询方式不同的地方在于需要开启ADC中断服务,配置中断优先级和中断服务函数。笔者接下来之讲与查询方式不同的地方。

1.需要在ADC配置函数中开启ADC中断

ADC_ITConfig(ADC1, ADC_IT_EOC, ENABLE);
  1. NVIC配置

因为我们是在转换完成后利用中断,在中断函数中读取数据,所以要首先配置中断函数的优先级。

/**
  * @brief     ADC中断初始化
  * @param     None
  * @retval    None
  */
static void ADC_NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;

    NVIC_InitStructure.NVIC_IRQChannel = ADC1_2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&NVIC_InitStructure);
}

3.中断服务函数

在中断函数中进行读取数据,将数据存放在变量ADC_ConvertedValue中。需要注意的是,此处使用关键字extern声明,代表变量ADC_ConvertedValue已经在其他文件中定义。

/**
  * @brief  This function handles ADC1 Handler.
  * @param  None
  * @retval None
  */
void ADC1_2_IRQHandler(void)
{
    if (ADC_GetITStatus(ADC1,ADC_IT_EOC)==SET) 
    {
        // 读取 ADC 的转换值
        ADC_ConvertedValue = ADC_GetConversionValue(ADC1);

    }
    ADC_ClearITPendingBit(ADC1,ADC_IT_EOC);
}

4.主函数
主函数负责接收转换的值,并将其转换为电压值,然后通过串口打印出来,便于查看ADC转换值。

/**
  * @brief     主函数
  * @param     None
  * @retval    int
  */
int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

    /*SysTick Init*/
    SysTick_Init();

    /* USART1 config 115200 8-N-1 */
    USART_Config();

    /*ADC初始化*/
    ADC_Configuration();

    for(;;)
    {
        ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*3.3; // 读取转换的AD值
        printf("The current AD value = 0x%04X \r\n", ADC_ConvertedValue); 
        printf("The current AD value = %f V \r\n\r\n",ADC_ConvertedValueLocal); //实际电压值

        Delay_ms(1000);
    }
}

12.3.3 ADC单通道电压采集DMA方式实现

DMA方式实现的代码结构和查询方式差不多,主要是ADC配置不同。

/**
  * @brief  配置ADC1的工作模式为MDA模式
  * @param  无
  * @retval 无
  */
static void ADC_Mode_Config(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;

    /* Enable DMA clock */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    /* Enable ADC1 clock */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

    /* DMA channel1 configuration */
    DMA_DeInit(DMA1_Channel1);

    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;  //ADC地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_ConvertedValue; //内存地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = 1;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址固定
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;  //内存地址固定
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //半字
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;  //循环传输
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);

    /* Enable DMA channel1 */
    DMA_Cmd(DMA1_Channel1, ENABLE);

    /* ADC1 configuration */
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  //独立ADC模式
    ADC_InitStructure.ADC_ScanConvMode = DISABLE ;  //禁止扫描模式,扫描模式用于多通道采集
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  //开启连续转换模式,即不停地进行ADC转换
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //不使用外部触发转换
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;  //采集数据右对齐
    ADC_InitStructure.ADC_NbrOfChannel = 1;  //要转换的通道数目1
    ADC_Init(ADC1, &ADC_InitStructure);

    /*配置ADC时钟,为PCLK2的8分频,即9MHz*/
    RCC_ADCCLKConfig(RCC_PCLK2_Div8); 
    /*配置ADC1的通道11为55.   5个采样周期,序列为1 */ 
    ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_55Cycles5);

    /* Enable ADC1 DMA */
    ADC_DMACmd(ADC1, ENABLE);

    /* Enable ADC1 */
    ADC_Cmd(ADC1, ENABLE);

    /*复位校准寄存器 */   
    ADC_ResetCalibration(ADC1);
    /*等待校准寄存器复位完成 */
    while(ADC_GetResetCalibrationStatus(ADC1));

    /* ADC校准 */
    ADC_StartCalibration(ADC1);
    /* 等待校准完成*/
    while(ADC_GetCalibrationStatus(ADC1));

    /* 启动ADC转换 */ 
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

代码的注释已经很详细了,我不再赘述了。完整代码请参看附件。

这里还需要说明一下 ADC 的参考电压,我的开发板使用的是 STM32F103ZET6,

该芯片有外部参考电压: Vref-和 Vref+,其中 Vref-必须和 VSSA 连接在一起, 而 Vref+的输入范围为: 2.4~VDDA。需要设置 Vref-和 Vref+设置参考电压,默认的我们是通过跳线帽将 Vref-接到 GND, Vref+接到 VDDA,参考电压就是 3.3V。如果大家想自己设置其他参考电压,将你的参考电压接在 Vref-和 Vref+上就 OK 了。本章我们的参考电压设置的是 3.3V。一般的开发板已经设置好了,不在需要单独去设置。

通过以上几个步骤的设置,我们就能正常的使用 STM32 的 ADC1 来执行 AD 转换操作了。

12.4 ADC具体代码实现-HAL库

本文使用串口通信的工程进行修改。打开工程。笔者只讲解ADC的配置,关于工程搭建和串口配置请看笔者博客。

接下来笔者将通过三种方式实现ADC单通道电压数据采集,先看看笔者使用的开发板的硬件电路,其中PC0外接了一个滑动电阻。前面已经介绍过了。

12.4.1 ADC单通道电压采集查询方式实现

12.4.1.1 ADC单通道电压采集查询方式工程配置

打开工程,打开Analog选项,配置ADC参数。

xpZgqU.md.png

具体配置参数如下。

xpZRZF.md.png

使能连续转换模式(Continuous Conversion Mode)。设置转换周期。其他为默认设置。
值得注意的是,ADC 的输入时钟不得超过14MHz,它是由PCLK2经分频产生,需要调整ADC输入的分频系数。

xpZ4i9.md.png

然后生成工程即可。

12.4.1.2 ADC单通道电压采集查询方式实现

在讲解代码之前,先看ADC采集数据的编程流程:

1.硬件等初始化;
2.串口、ADC等参数配置;
3.校准ADC,处理ADC数据;

关于串口的部分,笔者就不讲了,这里只讲解ADC的部分。在讲解之前,先看看ADC的结构体,其原型如下。

typedef struct __ADC_HandleTypeDef
{
  ADC_TypeDef                   *Instance;              /*!< Register base address */

  ADC_InitTypeDef               Init;                   /*!< ADC required parameters */

  DMA_HandleTypeDef             *DMA_Handle;            /*!< Pointer DMA Handler */

  HAL_LockTypeDef               Lock;                   /*!< ADC locking object */

  __IO uint32_t                 State;                  /*!< ADC communication state (bitmap of ADC states) */

  __IO uint32_t                 ErrorCode;              /*!< ADC Error code */
}ADC_HandleTypeDef;

以上六个参数分别是寄存器基地址、ADC初始化参数的结构体、指向DMA机构体的指针、互斥锁、状态描述符、保存错误代码的变量,其中Init是需要我们重点关注的,其原型如下所示。

typedef struct
{
  uint32_t DataAlign;                        /*!< Specifies ADC data alignment to right (MSB on register bit 11 and LSB on register bit 0) (default setting)
                                                  or to left (if regular group: MSB on register bit 15 and LSB on register bit 4, if injected group (MSB kept as signed value due to potential negative value after offset application): MSB on register bit 14 and LSB on register bit 3).
                                                  This parameter can be a value of @ref ADC_Data_align */
  uint32_t ScanConvMode;                     /*!< Configures the sequencer of regular and injected groups.
                                                  This parameter can be associated to parameter 'DiscontinuousConvMode' to have main sequence subdivided in successive parts.
                                                  If disabled: Conversion is performed in single mode (one channel converted, the one defined in rank 1).
                                                               Parameters 'NbrOfConversion' and 'InjectedNbrOfConversion' are discarded (equivalent to set to 1).
                                                  If enabled:  Conversions are performed in sequence mode (multiple ranks defined by 'NbrOfConversion'/'InjectedNbrOfConversion' and each channel rank).
                                                               Scan direction is upward: from rank1 to rank 'n'.
                                                  This parameter can be a value of @ref ADC_Scan_mode
                                                  Note: For regular group, this parameter should be enabled in conversion either by polling (HAL_ADC_Start with Discontinuous mode and NbrOfDiscConversion=1)
                                                        or by DMA (HAL_ADC_Start_DMA), but not by interruption (HAL_ADC_Start_IT): in scan mode, interruption is triggered only on the
                                                        the last conversion of the sequence. All previous conversions would be overwritten by the last one.
                                                        Injected group used with scan mode has not this constraint: each rank has its own result register, no data is overwritten. */
  FunctionalState ContinuousConvMode;         /*!< Specifies whether the conversion is performed in single mode (one conversion) or continuous mode for regular group,
                                                  after the selected trigger occurred (software start or external trigger).
                                                  This parameter can be set to ENABLE or DISABLE. */
  uint32_t NbrOfConversion;                  /*!< Specifies the number of ranks that will be converted within the regular group sequencer.
                                                  To use regular group sequencer and convert several ranks, parameter 'ScanConvMode' must be enabled.
                                                  This parameter must be a number between Min_Data = 1 and Max_Data = 16. */
  FunctionalState  DiscontinuousConvMode;    /*!< Specifies whether the conversions sequence of regular group is performed in Complete-sequence/Discontinuous-sequence (main sequence subdivided in successive parts).
                                                  Discontinuous mode is used only if sequencer is enabled (parameter 'ScanConvMode'). If sequencer is disabled, this parameter is discarded.
                                                  Discontinuous mode can be enabled only if continuous mode is disabled. If continuous mode is enabled, this parameter setting is discarded.
                                                  This parameter can be set to ENABLE or DISABLE. */
  uint32_t NbrOfDiscConversion;              /*!< Specifies the number of discontinuous conversions in which the  main sequence of regular group (parameter NbrOfConversion) will be subdivided.
                                                  If parameter 'DiscontinuousConvMode' is disabled, this parameter is discarded.
                                                  This parameter must be a number between Min_Data = 1 and Max_Data = 8. */
  uint32_t ExternalTrigConv;                 /*!< Selects the external event used to trigger the conversion start of regular group.
                                                  If set to ADC_SOFTWARE_START, external triggers are disabled.
                                                  If set to external trigger source, triggering is on event rising edge.
                                                  This parameter can be a value of @ref ADC_External_trigger_source_Regular */
}ADC_InitTypeDef;

DataAlign:数据对齐方式,可选左对齐与右对齐;

ScanConvMode:是否使用扫描模式,只有一个转换通道关闭即可,多可通道需要开启;

ContinuousConvMode:单一/连续转换模式,一般采用来连续转换模式;

NbrOfConversion:ADC转换通道数目,有几个写就行;

DiscontinuousConvMode:是否使用间断模式,这个在工程应用很有必要,也就是在某个事件触发下,开启转换,实际的产品是需要考虑功耗问题的,不仅仅是实现;

NbrOfDiscConversion:间断模式的转换数据,如果没有开启间断模式,就不用设置。

ExternalTrigConv:外部触发选择;这个有多个选择,一般采用软件触发方式;

还有一个结构体ADC_ChannelConfTypeDef,原型如下。

typedef struct 
{
  uint32_t Channel;                /*!< Specifies the channel to configure into ADC regular group.
                                        This parameter can be a value of @ref ADC_channels
                                        Note: Depending on devices, some channels may not be available on package pins. Refer to device datasheet for channels availability.
                                        Note: On STM32F1 devices with several ADC: Only ADC1 can access internal measurement channels (VrefInt/TempSensor) 
                                        Note: On STM32F10xx8 and STM32F10xxB devices: A low-amplitude voltage glitch may be generated (on ADC input 0) on the PA0 pin, when the ADC is converting with injection trigger.
                                              It is advised to distribute the analog channels so that Channel 0 is configured as an injected channel.
                                              Refer to errata sheet of these devices for more details. */
  uint32_t Rank;                   /*!< Specifies the rank in the regular group sequencer 
                                        This parameter can be a value of @ref ADC_regular_rank
                                        Note: In case of need to disable a channel or change order of conversion sequencer, rank containing a previous channel setting can be overwritten by the new channel setting (or parameter number of conversions can be adjusted) */
  uint32_t SamplingTime;           /*!< Sampling time value to be set for the selected channel.
                                        Unit: ADC clock cycles
                                        Conversion time is the addition of sampling time and processing time (12.5 ADC clock cycles at ADC resolution 12 bits).
                                        This parameter can be a value of @ref ADC_sampling_times
                                        Caution: This parameter updates the parameter property of the channel, that can be used into regular and/or injected groups.
                                                 If this same channel has been previously configured in the other group (regular/injected), it will be updated to last setting.
                                        Note: In case of usage of internal measurement channels (VrefInt/TempSensor),
                                              sampling time constraints must be respected (sampling time can be adjusted in function of ADC clock frequency and sampling time setting)
                                              Refer to device datasheet for timings values, parameters TS_vrefint, TS_temp (values rough order: 5us to 17.1us min). */
}ADC_ChannelConfTypeDef;

这个结构体用于配置通道的采样时间以及通道排序。查询方式采集ADC数据代码实现很简单,在主函数中进行 ADC校准并开启ADC。

HAL_ADCEx_Calibration_Start(&hadc1);  //校准
HAL_ADC_Start(&hadc1);   //开启

然后再循环体中插入如下代码:

if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1), HAL_ADC_STATE_REG_EOC))
{ 
    ADC_ConvertedValue = HAL_ADC_GetValue(&hadc1);

    ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*3.3; // 读取转换的AD值
    printf("The current AD value = 0x%04X \r\n", ADC_ConvertedValue); 
    printf("The current AD value = %f V \r\n\r\n",ADC_ConvertedValueLocal); //实际电压值
}
HAL_Delay(1000);

代码很好理解,等待转换完成,最后获取数据即可。不断循环即可。

12.4.2 ADC单通道电压采集中断方式实现

12.4.2.1 ADC单通道电压采集中断方式工程配置

ADC单通道电压采集中断方式工程配置只需要在上个工程的基础上进行配置即可,打开工程,找到Analog选项,使能ADC中断即可。

xpZoxx.md.png

值得注意的是,本例程使用了串口中断,因此还需要设置中断优先级,进入NVIC配置表,简单配置
下即可,如下图所示。

xpZbqO.md.png

好了,直接就生成工程即可。

12.4.2.2 ADC单通道电压采集中断方式实现

中断方式和查询方式不同的地方在于需要开启ADC中断服务,配置中断优先级和中断服务函数。

笔者接下来之讲与查询方式不同的地方。

1.需要在ADC配置函数中开启ADC校准和ADC中断

HAL_ADCEx_Calibration_Start(&hadc1);    //ADC校准
HAL_ADC_Start_IT(&hadc1); //开启ADC中断转换

2.中断回调函数
在中断回调函数中进行读取数据,将数据存放在变量ADC_ConvertedValue中。

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)    //ADC转换完成回调
{
    HAL_ADC_Stop_IT(hadc);  //关闭ADC
    ADC_ConvertedValue = HAL_ADC_GetValue(hadc);
}

有朋友会问为何要关闭ADC的中断,因为ADC采样频率很高,我们每隔1S获取数据,因此我们在获取数据后就关闭ADC,这样可以提高CPU利用率。

4.主函数
主函数负责接收转换的值,并将其转换为电压值,然后通过串口打印出来,便于查看ADC转换值。

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxBuffer, 1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
    ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*3.3; // 读取转换的AD值
    printf("The current AD value = 0x%04X \r\n", ADC_ConvertedValue); 
    printf("The current AD value = %f V \r\n\r\n",ADC_ConvertedValueLocal); //实际电压值
HAL_Delay(1000);
    HAL_ADC_Start_IT(&hadc1); //开启ADC中断转换
  }
  /* USER CODE END 3 */
}

我们还可以通过定时器方式来实现,关于定时器参看前面的章节。如果开启定时器1,定时时间为1s,则可将以下函数的内容替换main()函数的循环体的内容。这样可空出主循环干其他事情了。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)    //定时器中断回调
{
  ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*3.3; // 读取转换的AD值
  printf("The current AD value = 0x%04X \r\n", ADC_ConvertedValue); 
  printf("The current AD value = %f V \r\n\r\n",ADC_ConvertedValueLocal); //实际电压值

  HAL_ADC_Start_IT(&hadc1); //定时器中断里面开启ADC中断转换,1s开启一次采集    
}

12.4.3 ADC单通道电压采集DMA方式实现

12.4.3.1 ADC单通道电压采集DMA方式工程配置

前面的章节也讲过DMA,如果不懂可先去看看,这是简单说下用DMA的好处:我们在前面使用了查询方式和中断方式进行采样,都需要在主程序中占用好多时间出来,不管是那种采样都需要调用HAL_ADC_GetValue()这个函数获取转换后的值,中断还好点,要是查询的话,还有可能会丢失数据,使用DMA就可以避免丢失数据,而且不需要消耗CPU资源,下面看看如何使用使用STM32cubeMX配置DMA。

我们还是在第二个工程的基础上进行修改,打开工程。根据DMA通道预览可以知道,我们用的ADC1对应DMA1 的通道1。

xpZvid.md.png

点击DMASettings 点击 Add 添加通道,选择ADC1传输速率设置为中速,DMA传输模式为正常模式。

值得注意的是,STM32cubeMX默认开启了DMA的中断。

xpZxJA.md.png

还需要设置中断优先级,进入NVIC配置表,简单配置下即可,如下图所示。

xpZzRI.md.png

然后生成代码即可。

12.4.3.2 ADC单通道电压采集DMA方式实现

DMA方式实现的代码结构和查询方式差不多,只需要开启DMA转换即可。然后即可获取ADC的数据。

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */

  HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&ADC_ConvertedValue, 1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    ADC_ConvertedValueLocal =(float) ADC_ConvertedValue/4096*3.3; // 读取转换的AD值
    printf("The current AD value = 0x%04X \r\n", ADC_ConvertedValue); 
    printf("The current AD value = %f V \r\n\r\n",ADC_ConvertedValueLocal); //实际电压值
    HAL_Delay(1000);
  }
  /* USER CODE END 3 */
}

代码的注释已经很详细了,我不再赘述了。完整代码请参看附件。
当然,也可开启定时器,将主循环中的代码放入定时器的回调函数中。
通过以上几个步骤的设置,我们就能正常的使用 STM32 的 ADC1 来执行 AD 转换操作了。

12.5实验现象

将程序编译好后下载到板子中,打开串口助手可以看到如下现象,当然了,普通方式、中断方式和DMA方式都是一样的现象。

xpe9QP.md.png


欢迎访问我的网站

BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的简书
BruceOu的知乎


资源获取方式

1.关注公众号[嵌入式实验楼]
2.在公众号回复关键词[Cortex-M]获取资料提取码

Related posts

Leave a Comment