欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

程序员文章站 2024-02-25 14:30:09
...

1 ADC (HAL库)

1.1 ADC工作原理

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

1.1.1 ADC架构

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

图1 ADC框图

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 就可以测量。

表1 ADC引脚

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

【注】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 个通道。

表2规则序列寄存器

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

注入序列
注入序列寄存器 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 一样。

表3注入序列寄存器

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

4.触发源
通道选好了,转换的顺序也设置好了,那接下来就该开始转换了。 ADC 转换可以由ADC 控制寄存器 2: 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 2^{12} 212。数值0 对应的就是 0V。 如果转换后的数值为 X , X 对应的模拟电压为 Y,那么会有这么一个等式成立: 2 12 / 3.3 = X / Y , = > Y = ( 3.3 ∗ X ) / 2 12 2^{12} / 3.3 = X / Y, => Y = (3.3 * X ) / 2^{12} 212/3.3=X/Y=>Y=(3.3X)/212

1.1.2 ADC通道选择

表4 ADC对应通道

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

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

通过一个形象的例子可以说明: 假如你在家里的院子内放了 5 个温度探头,室内放了 3 个温度探头; 你需要时刻监视室外温度即可,但偶尔你想看看室内的温度;因此你可以使用规则通道组循环扫描室外的 5 个探头并显示 AD 转换结果,当你想看室内温度时,通过一个按钮启动注入转换组(3个室内探头)并暂时显示室内温度,当你放开这个按钮后,系统又会回到规则通道组继续检测室外温度。从系统设计上,测量并显示室内温度的过程中断了测量并显示室外温度的过程,但程序设计上可以在初始化阶段分别设置好不同的转换组,系统运行中不必再变更循环转换的配置,从而达到两个任务互不干扰和快速切换的结果。可以设想一下,如果没有规则组和注入组的划分,当你按下按钮后,需要从新配置 AD 循环扫描的通道,然后在释放按钮后需再次配置 AD 循环扫描的通道。

上面的例子因为速度较慢,不能完全体现这样区分(规则通道组和注入通道组)的好处,但在工业应用领域中有很多检测和监视探头需要较快地处理,这样对 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 中。

1.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.必须避免同时为规则和注入组设置间断模式。间断模式只能作用于一组转换。

1.2 STM32Cube新建工程

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

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

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

图2 AD转换电路

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

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

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

图3配置ADC

具体配置参数如下。

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

图4 ADC参数配置

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

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

图5 ADC时钟配置

然后生成工程即可。

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

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

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

图6 使能ADC中断

值得注意的是,本例程使用了串口中断,因此还需要设置中断优先级,进入NVIC配置表,简单配置下即可,如下图所示。
《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

图7中断优先级配置

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

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

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

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

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

图8 DMA参数配置

点击DMASettings 点击 Add 添加通道,选择ADC1传输速率设置为中速,DMA传输模式为正常模式,DMA内存地址自增,每次增加一个word(字)。
值得注意的是,STM32cubeMX默认开启了DMA的中断。

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

图9 DMA中断

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

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

图10中断优先级配置

然后生成代码即可。

1.3 ADC具体代码实现与分析

1.3.1 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数据代码实现很简单,在主函数中的循环体中添加以下代码即可。

HAL_ADCEx_Calibration_Start(&hadc1);  //校准
HAL_ADC_Start(&hadc1);   //开启
HAL_ADC_PollForConversion(&hadc1,50);  //等待转换完成
		
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);

代码很好理解,先进行ADC校准,然后开启ADC采样,等待转换完成,最后获取数据即可。不断循环即可。

完整代码请参看附件。

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

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

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

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

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 */
		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 BEGIN 3 */
  }
  /* 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开启一次采集    
}

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

DMA方式实现的代码结构和查询方式差不多,只需要开启DMA转换即可。然后即可获取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_DMA_Init();
  MX_USART1_UART_Init();
  MX_ADC1_Init();
  /* USER CODE BEGIN 2 */
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxBuffer, 1);
	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 */
		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 BEGIN 3 */
  }
  /* USER CODE END 3 */
}

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

当然,也可开启定时器,将主循环中的代码放入定时器的回调函数中。

这里还需要说明一下 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 转换操作了。

1.4实验现象

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

《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)

图11实验现象



代码获取方法

1.长按下面二维码,关注公众号[嵌入式实验楼]
2.在公众号回复关键词[STM32F1]获取资料
《嵌入式-STM32开发指南》第二部分 基础篇 - 第8章 模拟输入输出-ADC(HAL库)



欢迎访问我的网站:

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