0
点赞
收藏
分享

微信扫一扫

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验

第二十九章ADC实验​


本章,我们将介绍STM32MP157的ADC(Analog-to-digital converters,模数转换器)功能。我们通过四个实验来学习ADC,分别是单通道ADC采集实验、单通道ADC采集(DMA读取)实验、多通道ADC采集(DMA读取)实验和单通道ADC过采样(26位分辨率)实验。

本章分为如下几个小节:

29.1 、ADC简介;

29.2 、单通道ADC采集实验;

29.3 、单通道ADC采集(DMA读取)实验;

29.4 、多通道ADC采集(DMA读取)实验;

29.5、单通道ADC过采样(26位分辨率)实验;


29.1 ADC简介

ADC即模拟数字转换器(Analog-to-digital converter),用于将连续变化的模拟信号转换为离散的数字信号。

真实世界中的模拟信号,例如温度、湿度、音量、压力或者图像等等模拟信号,这些信号在时域上是连续的,需要转换成MCU更容易储存、处理和发射的数字形式信号,这个就需要模/数转换器了。这里我们区分一下一些概念:

(1)转换采样率

ADC转换采样率(采样率)是指完成一次从模拟量转换成数字量时ADC所用的时间的倒数,即每秒从连续信号中提取并转换成离散数字量的信号个数。也就是1/ TCONV ,后面我们会介绍TCONV 的计算方法。

(2)分辨率

ADC分辨率是指满量程与2的n次方的比值(n表示ADC的位数),它表示能够采样/分辨的最小值,用于计算精度。分辨率常用二进制位表示,例如我们常常听到ADC的分辨率为8位、10位、12位这样的字眼。例如使用一个16位的ADC去采集一个10V的满量程信号(假设此ADC能测量10V的电压信号,即输入电压为10V),这个16位的ADC满刻度(最大值)时的数字量为2^16=65536,当AD的数字量为65536时表示采集到了10V,当AD的数字量为256时,表示采集到了10V*

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_寄存器

=0.0391V,此ADC的分辨率是

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_寄存器_02

。ADC的位数越高,其分辨率就越高。

(3)基准电压

基准电压也叫参考电压(VREF),其用来当做参照作用。我们测试外部电压时会以基准电压作为参考,先把基准电压分成多少分(根据分辨率来分),然后再和被测电压进行比较,然后得到比较的结果,这样就能测试出输入的电压是多少了。

29.1.1 ADC特性

STM32MP157系列有2个ADC(ADC1和ADC2),每个ADC都可以独立工作,每个ADC都可以单独分配给A7或者M4内核来使用。ADC1和ADC2除了可以工作在独立模式以外,还可以在双重模式下工作(提高采样率,ADC1为主机),STM32MP157的ADC主要特性我们可以总结为以下几条:

(1)可配置16位、14位、12位、10位或8位分辨率,可通过降低分辨率来缩短转换时间,因为转换时间缩短,我们可以做到的采样率就越高。

(2)每个ADC具有多达20个的采集模拟通道,其中有6路快速通道和14路慢速通道,慢速和快速的区别主要是支持的最高采样率不同,慢速通道要比快速通道低。这些通道的A/D转换可以单次、连续、扫描或间断模式执行。

(3)ADC的结果可以左对齐或右对齐方式存储在32位数据寄存器中。

(4)ADC具有6条专用的内部通道,用于内部参考电压 (VREFINT ) 、内部温度传感器 (VSENSE )、VBAT监测通道BAT、连接到DAC内部通道、VDDCORE监视通道。

(5)支持过采样,最高可以到26位采样率。

(6)每个ADC支持3路模拟看门狗。

(7)支持单独输入和差分输入。

(8)ADC输入范围:VREF– ≤ VINVREF+。由VREF- 、VREF+ 、VDDA 和VSSA 这四个外部引脚决定。一般我们把VSSA 和VREF- 接地,把REF+ 和VDDA接到3.3V,所以得到ADC 的输入电压范围是:0~3.3V。注意不要接超出这个范围的电压进来,否则容易烧坏芯片。

上面我们列出的一些特性都是ADC重要的特性和关键知识点。下面来介绍ADC(仅限ADC1或ADC2)的框图:

29.1.2 ADC 框图

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_寄存器_03


图29.1.2. 1 ADC框图

在进行框图分析前,我们先对框图中的部分信号做如下说明:

名称

信号类型

说明

VREF+

正模拟参考电压输入

1.62 V≤ VREF+ ≤VDDA

VDDA

模拟电源输入

VDDA1.62 V ≤ VDDA ≤ 3.6 V

VREF-

负模拟参考电压输入

ADC低/负参考电压,VREF- = VSSA

VSSA

模拟电源地输入

模拟电源地电压等于VSS

VINP[19:0]

每个ADC的正输入模拟通道

连接到外部通道ADC_INPi或内部通道。

VINN[19:0]

每个ADC的负输入模拟通道

连接到VREF-或外部通道ADC_INNi

ADC_INP[19:0]

外部模拟输入信号

多达20个模拟输入通道:

ADC_INP[0:5]快速通道

ADC_INP[6:19]慢速通道

ADC_INN[19:0]

多达20个模拟输入通道:

ADC_INN[0:5]快速通道

ADC_INN[6:19]慢速通道

PCSEL[19:0]

输出,通道预选控制信号

连接到GPIO预先选择通道

29.1.2. 1内部输入/输出信号

内部信号名称

信号类型

说明

adc_ext_trg[20:0]

输入

共有多达21个外部触发输入用于常规转换(可连接至片上

定时器),这些输入由主ADC和从ADC共享。

adc_jext_trg[20:0]

输入

共有多达21个外部触发输入用于注入转换(可连接至片上

定时器),这些输入由主ADC和从ADC共享。

adc_awd1

adc_awd2

adc_awd3

输出

内部模拟看门狗输出信号,连接至片上定时器。(x = 模拟看

门狗编号 1、2、3)

V SENSE

模拟输入

内部温度传感器的输出电压

V REFINT

模拟输入

内部参考电压的输出电压

V BAT

模拟输入

外部电池电压

adc_it

输出

ADC 中断

adc_hclk

输入

AHB时钟

adc_ker_ck

输入

ADC 内核时钟

adc_dma

输出

ADC DMA 请求

29.1.2. 2输入/输出引脚

图中,我们标记了11处位置,说明如下:

1. VREF+电压

VREF+电压

VREF+ 是正模拟参考电压输入,选择范围是1.62V~3.6V,开发板上我们一般给VREF+接入的电压时3.3V,所以得到开发板上的ADC测量范围是0~3.3V。此外,STM32MP157有ADC和DAC共用的内部基准电压VREFBUF,可通过VREFBUF_CSR寄存器进行配置,可选1.5 V1.8 V、2.048 V和2.5 V。

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_04


29.1.2. 2数据手册部分截图

2. ADC的双时钟域架构

② 是ADC的双时钟域架构

双时钟域架构意味着ADC时钟独立于AHB总线时钟, ADC有两种时钟源可以选择,分别是adc_hclk和adc_ker_ck

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_寄存器_05


图29.1.2.1. 1和ADC2时钟

(1)adc_hclk(属于同步时钟)

adc_hclk来自AHB总线的系统时钟,ADC1和ADC2处在209MHZ的 AHB2总线时钟。可以通过ADC_CCR寄存器的CKMODE[1:0]位来选择不同分频的AHB2总线时钟。

有以下的四种情况:

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(同步时钟模式)

比如我们选择1分频的adc_hclk,得到的频率是209MHZ,但是参考手册上限制ADC最高是 133MHz,说明这样配置就超频了,这是不可行的,因为超频误差会比较大。我们可以降低AHB2总线时钟,但是这样可能会影响我们其他外设的性能,所以为了整个芯片的最优性能,我们可以选择其他配置。

(2)adc_ker_ck(属于异步时钟)

adc_ker_ck可以通过RCC_ADCCKSELR寄存器的ADCSRC[1:0]位来选择不同的时钟源,前提是前面提到的CKMODE[1:0]=00。选择的情况如下:

ADCSRC[1:0]=00pll4_r_ck作为ADC时钟源(复位后的默认值)

ADCSRC[1:0]=01per_ck作为ADC时钟源

ADCSRC[1:0]=10pll3_q_ck作为ADC时钟源

例如我们可以选择per_ck作为ADC时钟源,而per_ck 时钟可为 hse_ck、hsi_ker_ck 或 csi_ker_ck,通过RCC_CPERCKSELR寄存器的CKPERSRC[1:0]位选择,默认选择hsi_ker_ck作为per_ck的时钟源,hsi_ker_ck时钟源就是来自频率为64MHz的高速内部RC振荡器(HSI)。也可以选择pll4_r_ck或者pll3_q_ck作为per_ck 时钟,最大可以配置为133MHz。

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_时钟周期_06


图29.1.2.1. 2时钟

如果选择了adc_ker_ck时钟源作为ADC的时钟,则可以通过ADC_CCR寄存器的PRESC[3:0]位进行分频,可以是1、2、4、6、8、10、12、16、32、64、128、256这12种分频系数。上面的分析请结合下面的ADC时钟方案图理解。

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_07


图29.1.2.1. 3 ADC时钟方案

3. ADC的输入通道

③是输入通道

在讲解STM32MP157的ADC输入通道前,我们先了解单端输入和差分输入。

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_08


图29.1.2.1. 4的单端输入和差分输入

(1)单端输入

单端输入只有一个输入引脚ADCin,同时使用公共地GND作为电路的返回端ADC的采样值:VADC=VADCin -VGND。这种输入方式接线简单,ADC的采样值由Vin决定且随着Vin受到的干扰而变化

(2)差分输入

差分输入比单端输入多了一根线,ADC采样值: VADC =VADCin+-VADCin-这种输入方式接线稍微复杂不过两根线受到的干扰差不多,属于输入共模干扰在输入ADC时这些干扰会被互相抵消掉从而降低了干扰,所以测量精度是比较高的。

(3)STM32MP157的通道资源

STM32MP157的每个ADC最多有20个多路复用模拟通道。因为STM32MP157的ADC支持差分通道输入,因此有ADCx_INP[19:0]和ADCx_INN[19:0]两组信号,其中,INP是差分正向输入,INN是差分反向输入,其中的ADC_INP[0:5]和ADC_INN[0:5]是快速模拟输入,ADC_INP[6:19]和 ADC_INN[6:19]是慢速模拟输入。

如果我们使用单端输入,则只有ADCx_INP[19:0]有效,而因为ADCx_INN[19:0]在内部自动接了VSSA,所以ADCx_INN[19:0]不能选择作为单端输入。内部电压信号可以连接到ADC_INP的某个通道上面进行采集,如:内部参考电压REFINT ) 连接到ADC2_INP13、内部温度传感器 (VSENSE连接到ADC2_INP12、VBAT监测通道BAT/4) 连接到ADC2_INP15等。

注意:STM32MP157的ADC支持单端/差分转换,由寄存器ADC_DIFSEL控制,我们一般使用单端转换模式,默认这个寄存器都是0的,刚好就是单端转换模式。

4. ADC的转换序列

④是转换序列

(1)常规通道组和注入通道组

在ADC的20个多路复用模拟通道中,可以分为规则通道组(也可以称为常规通道组)和注入通道规则通道组最多可以安排16个通道,注入通道组最多可以安排4个通道。规则通道组的通道可以称为规则通道(或常规通道,字面上一个意思),注入通道组的通道可以称为注入通道。规则通道可以理解为常规的通道,相当于正常运行的程序,我们一般使用的是规则通道,而注入通道可以以抢占式的方式打断规则通道的采样,也就是说注入通道相当于中断,当规则通道在执行任务时,可以插入注入通道(中断发生),规则通道先暂停处理任务而去处理注入通道的任务(中断响应),当注入通道处理完以后再返回去继续执行规则通道的任务(中断返回),这个过程就相当于中断的过程了。

(2)转换序列

ADC的作用就是将模拟量转换成数字量,可以将转换分为两组:规则转换和注入转换。每个组包含一个转换序列,该序列的作用就是控制通道的转换顺序。比如20个通道中有16个规则通道和4个注入通道,规则通道有1~16个转换序列,注入通道有1~4个转换序列,寄存器ADC_SQR1、ADC_SQR2、ADC_SQR3和ADC_SQR4控制着规则转换顺序,寄存器ADC_JSQR控制着注入转换。我们以规则转换为例,以一个寄存器来说明,例如,ADCx_SQR1寄存器的SQ1[4:0] 控制着规则序列中的第1个转换,SQ4[4:0]控制着规则序列的第4个转换,如果通道8想在第3个转换,则在SQ3[4:0]写入8即可,其它的寄存器也类似。

寄存器ADCx_SQR1

SQ1[4:0]

规则序列中的第1次转换

SQ2[4:0]

规则序列中的第2次转换

SQ3[4:0]

规则序列中的第3次转换

SQ4[4:0]

规则序列中的第4次转换

29.1.2.4. 1转换序列

一个常规转换组最多由16个转换构成。一个注入转换组最多由4个转换构成。常规转换必须在ADC_SQRy(y为1~4)寄存器中选择转换序列的常规通道及其顺序,转换总数必须写入ADC_SQR1寄存器中的L[3:0]位。注入转换必须在ADC_JSQR寄存器中选择转换序列的注入通道及其顺序,转换总数必须写入ADC_JSQR寄存器中的 JL[1:0] 位。

注入通道的转换可以打断常规通道的转换,在注入通道被转换完成之后,常规通道才得以继续转换。

5. ADC的触发源

⑤是触发源

经过前面的步骤以后,选择好了通道以及转换顺序,接下来就开始进行转换操作了,转换需要触发才可以进行转换,ADC的触发转换有两种方法:分别是通过软件或外部事件(也就是硬件)触发转换。

(1)软件触发

我们先来看看通过软件触发转换的方法,方法是:通过写ADC_CR寄存器的ADSTART这个位来控制,写1就开始转换,写0就停止转换,这个控制ADC转换的方式非常简单。

(2)硬件触发

另一种就是通过外部事件触发转换的方法。有定时器和输入引脚触发等等,具体请看《STM32MP157参考手册》的表187和表188如下表列出部分可分为:常规通道的外部触发和注入通道的外部触发两种。

ADC1 和ADC2常规通道的外部触发部分

名称

类型

EXTSEL[4:0]

adc_ext_trg0

tim1_oc1事件

片上定时器的内部信号

00000

adc_ext_trg1

tim1_oc2事件

片上定时器的内部信号

00001

adc_ext_trg2

tim1_oc3事件

片上定时器的内部信号

00010

adc_ext_trg3

tim2_oc2事件

片上定时器的内部信号

00011

adc_ext_trg4

tim3_trgo事件

片上定时器的内部信号

00100

adc_ext_trg5

tim4_oc4事件

片上定时器的内部信号

00101

adc_ext_trg6

exti线11

外部引脚

00110

此处省略部分内容

adc_ext_trg20

lptim3_out事件

片上定时器的内部信号

10100

此处省略部分内容

adc_ext_trg30

保留

-

11110

adc_ext_trg31

保留

-

11111

ADC1 和ADC2 注入通道的外部触发部分

名称

类型

EXTSEL[4:0]

adc_jext_trg0

tim1_trgo事件

片上定时器的内部信号

00000

adc_jext_trg1

tim1_oc4事件

片上定时器的内部信号

00001

adc_jext_trg2

tim2_trgo事件

片上定时器的内部信号

00010

adc_jext_trg3

tim2_oc1事件

片上定时器的内部信号

00011

此处省略部分内容

adc_jext_trg20

lptim3_out事件

片上定时器的内部信号

10100

此处省略部分内容

adc_jext_trg30

保留

-

11110

adc_jext_trg31

保留

-

11111

29.1.2.5. 1的外部事件触发源

adc_ext_trg[20:0],对应的就是常规通道的外部触发,共有21路。

adc_jext_trg[20:0],对应的就是注入通道的外部触发,共有21路。

如果选择硬件触发,则需要选择相应的硬件触发通道和触发边沿等,然后由外部硬件通道来触发ADC的采集(注意:ADSTART位同样要设置为1)。硬件触发通道由ADC_CFGR寄存器的EXTSEL[4:0]和ADC_JSQR寄存器的 JEXTSEL[4:0]位来选择,分别是常规转换组和注入转换组的触发源选择。而触发边沿是通过ADC_CFGR寄存器的EXTEN[1:0]和ADC_JSQR寄存器的JEXTEN[1:0]位来选择。其他的设置我们后面再讲解。

6. ADC的转换时间

⑥是转换时间

(1)计算转换时间

STM32MP157的ADC总转换时间的计算公式如下:

TCONV = 采样时间(TSMPL) + 逐次逼近时间(TSAR

采样时间TSMPL)可通过ADC_SMPR1和ADC_SMPR2寄存器中的SMP[2:0]位编程,ADC_SMPR1控制的是通道0~9,ADC_SMPR2控制的是通道10~19。所有通道都可以通过编程来控制使用不同的采样时间,可选采样时间值如下:

SMP = 0001.5 ADC 时钟周期

SMP = 0012.5 ADC 时钟周期

SMP = 0108.5 ADC 时钟周期

SMP = 01116.5 ADC 时钟周期

SMP = 10032.5 ADC 时钟周期

SMP = 10164.5 ADC 时钟周期

SMP = 110387.5 ADC 时钟周期

SMP = 111810.5 ADC 时钟周期

逐次逼近时间(TSAR)是由分辨率决定的,分辨率通过对ADC_CFGR寄存器的RES[1:0]位进行编程,可将分辨率配置为16位、14位、12位、10位、8位。而逐次逼近时间和分辨率的对应关系如下表所示:

RES

TSAR

ADC时钟周期

TSAR (ns)

Fadc_ker_ck=24MHz

Tadc_ker_ck(ADC时钟周期)

(采样时间=1.5个ADC时钟周期)

Tadc_ker_ck (ns)

Fadc_ker_ck =24 MHz

16

8.5 个 ADC 时钟周期

354.2

10 个 ADC 时钟周期

416.7

14

7.5 个 ADC 时钟周期

312.5

9 个 ADC 时钟周期

375

12

6.5 个 ADC 时钟周期

270.8

8 个 ADC 时钟周期

333.3

10

5.5 个 ADC 时钟周期

229.2

7 个 ADC 时钟周期

291.7

8

4.5 个 ADC 时钟周期

187.5

6 个 ADC 时钟周期

250.0

表29.1.2.6. 1与分辨率的对应关系

举个例子,我们配置SMP = 111,即设置最大采样周期,然后采用16位分辨率,那么得到:

TCONV = 810.5个ADC时钟周期 + 8.5个ADC时钟周期 = 819个ADC时钟周期

表格中,Fadc_ker_ck的频率是24MHZ,我们的例程中ADC的时钟源如果是64MHZhsi_ker_ck经过2分频得到,即32MHZ。我们就以Fadc_ker_ck的频率为32MHZ来举例,可得到:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_09

(2)计算采样率

得到转换时间,我们就可以计算出采样率:

                        采样率=1/采样时间

在数据手册中有给出采样率:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_时钟周期_10


29.1.2. 3ADC的采样率列表

我们以表中第一个参数为例进行计算,ADC的时钟频率是36MHz,采样时间是1.5个ADC时钟周期,所以采样时间TCONV =(1.5+8.5)/36MHz,那么采样率fs,和表中的3.6一致。

7. 其它

⑦是选择参考电压

选择参考电压,我们可以设置参考电压来自外部的VREF+,也可以设置参考电压来自内部的稳压器。

⑧ADC的核心

ADC的核心是一个16位的逐次逼近型ADC转换器,它根据我们设置好的参考电压、输入通道、启动条件等,执行模数转换。

⑨数据寄存器

这是ADC转换完成后的数据输出寄存器。其中RDATA[31:0]用于保存常规通道的转换结果,JDATA1~4[31:0]用于保存注入通道的转换结果,如果是使用双重模式,常规通道的数据则是存放在ADC_CDR寄存器。转换结果CPU可以通过AHB总线读取,同时也可以产生相关中断(adc_it)。

⑩中断

对于每个ADC都可在下列情况下产生中断:

中断事件

事件标志

使能控制位

ADC 就绪

ADRDY

ADRDYIE

结束常规通道组的转换

EOC

EOCIE

常规通道组的转换序列结束

EOS

EOSIE

注入组的转换结束

JEOC

JEOCIE

注入组的转换序列结束

JEOS

JEOSIE

模拟看门狗 1 状态位置 1

AWD1

AWD1IE

模拟看门狗 2 状态位置 1

AWD2

AWD2IE

模拟看门狗 3 状态位置 1

AWD3

AWD3IE

采样阶段结束

EOSMP

EOSMPIE

上溢

OVR

OVRIE

注入上下文队列溢出

JQOVF

JQOVFIE

表29.1.2.7. 1每个ADC的ADC中断

表中前面5个中断都很好理解,我们从模拟看门狗中断介绍。

模拟看门狗中断发生条件:首先通过ADC_LTR和ADC_HTR寄存器设置低阈值和高阈值,然后开启了模拟看门狗中断后,当被ADC转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断。例如我们设置高阈值是3.0V,那么模拟电压超过3.0V的时候,就会产生模拟看门狗中断,低阈值的情况类似。

上溢中断:如果发生传输数据丢失,会置位ADC状态寄存器ADC_ISR的OVR位,如果同时使能了溢出中断ADC_IER寄存器的OVRIE位,就会在转换结束后会产生一个溢出中断。

此外,我们还要知道常规组和注入组的转换结束后,除了产生中断外,还可以产生DMA请求,把转换好的数据存储在内存里面,防止读取不及时数据被覆盖。

通道预选控制信号

通道预选控制信号,用于将ADC某个通道连接到对应的GPIO上。PCSEL[19:0]每个位对应一个通道,总共20个通道。这一点和以前的STM32系列不一样,在使用的时候,需要特别注意。

29.2 单通道ADC采集实验

本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\3、M4裸机驱动例程\库V1.2\实验18-1 ADC实验-单通道ADC采集

STM32MP157的ADC可以进行很多种不同的转换模式,这些模式在《STM32MP157参考手册》的第29章也都有详细介绍。ADC有独立模式和双ADC模式,独立模式一般是指常规通道,独立模式中又会有:单通道、单次转换模式、多通道(扫描)、单次转换模式、单通道连续转换模式、多通道(扫描)连续转换模式和注入转换模式。双ADC模式会稍微复杂,我们这里先介绍独立模式。

29.2.1 单次转换和连续转换

ADC的转换模式中,主要需要了解单次转换模式和连续转换模式。

1. 单次转换模式

将ADC_CFGR 寄存器的位CONT=0后则设置为单次转换模式,启动ADC后,转换一次完成后则停止,然后等待下一次的ADC启动后再继续进行下一次的转换,这里的通道数可以是一个也可以是多个,但是只采集一次。

转换序列的作用就是控制通道的转换顺序,如果设置了几个通道的转换顺序,当转换完设置的所有通道后,我们就说转换序列完成,只有一个通道的话,可将序列长度编程为1。

(1)对于常规通道:

在常规序列中,一旦选择的通道转换完成,转换数据被储存在16位ADC_DR寄存器中,ADC状态寄存器(ADC_ISR) 的EOC(转换结束)标志被置1,如果设置了EOCIE,则产生中断,然后ADC停止。 如果常规序列完成后,EOS(常规序列结束)标志置 1,EOSIE 位置 1 时将产生中断。

(2)对于注入通道:

在注入序列中,每次转换完成后,转换数据被储存在16位的ADC_DRJ1寄存器中,ADC状态寄存器(ADC_ISR) 的JEOC(注入转换结束)标志被置1,如果设置了JEOCIE位,则产生中断,然后ADC停止。如果注入序列完成后,JEOS(注入序列结束)标志置 1,JEOSIE 位置 1 时将产生中断。

2. 连续转换模式

将ADC_CFGR 寄存器的位CONT=1后,则设置为连续转换模式,连续转换模式只适用于常规通道。启动ADC后则开始转换,转换完所设置的所有通道后,返回到第一步再继续重新转换所有的通道,即转换完一次后继续开始下一次转换,转换数据存储在 32位 ADC_DR 寄存器中。

在常规序列中,每次转换完成后,EOC(转换结束)会被置1,如果EOCIE 位被设置为1 时将产生中断;如果转换序列完成后,EOS(序列结束)标志置 1,EOSIE 位置 1 时将产生中断。

3. 独立模式

(1)单通道、单次转换模式

这是最简单的ADC模式,在此模式下,ADC 执行单个通道x的单次转换(单次采样),并在转换完成后停止:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_寄存器_11


图29.2. 1单通道、单次转换模式

(2)多通道(扫描)、单次转换模式

扫描模式一般用于顺序转换多个通道,是针对多通道ADC而言的,单个通道没有扫描模式。多通道(扫描)、单次转换模式用于在独立模式下对一些通道进行依次转换,可以在此模式下以不同的采样时间和采样顺序对任意序列的通道(最多 16 个)依次进行配置。通过这种方式,用户不必在转换过程中停止ADC即可以不同的采样时间重新配置下一个通道。此模式可以避免额外的 CPU 负载以及繁重的软件开发。例如,在机械臂的系统中,必须在上电时读取机械臂系统中每个关节的位置才能确定机械臂顶端的坐标,那么可配置用到的通道,例如下图配置11个通道:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_时钟周期_12


图29.2. 2以不同采样时间转换11个通道

所配置的通道可按设置好的次序进行转换:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_时钟周期_13


图29.2. 3多通道单次转换

(3)单通道连续转换模式

对单个通道进行连续不断的转换,即转换完一次后继续开始下一次转换这种模式叫做单通道连续转换模式。连续转换模式也可以使用DMA从而降低 CPU 负载。

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_寄存器_14


图29.2. 4单通道多次转换

本实验我们来学习使用常规单通道的单次转换模式。

STM32MP157的ADC在单次转换模式下(寄存器ADC_CFGR的CONT位为0),只执行一次转换,该模式可以通过ADC_CR寄存器的ADSTART位(只适用于常规通道)启动,也可以通过外部触发启动(适用于常规通道和注入通道,但是必须先设置ADSTART/JADSTART位)。

以常规通道为例,一旦所选择的通道转换完成,转换结果将被存在ADC_DR寄存器中,EOC(转换结束)标志将被置位,如果设置了EOCIE,则会产生中断。然后ADC将停止,直到下次启动。

29.2.2 ADC寄存器

下面,我们介绍执行常规通道的单次转换,需要用到的一些ADC寄存器。

1. ADC通用控制寄存器(ADC_CCR)

ADC通用控制寄存器描述如下图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_寄存器_15


29.2.1. 1寄存器

该寄存器本章只需要用到PRESC[3:0]这四个位,用于设置ADC时钟的预分频系数(即对adc_ker_ck的分频系数),表示2^PRESC[3:0]分频:

0000:输入 ADC 时钟未分频

0001:输入 ADC 时钟 2 分频

0010:输入 ADC 时钟 4 分频

0011:输入 ADC 时钟 6 分频

0100:输入 ADC 时钟 8 分频

0101:输入 ADC 时钟 10 分频

0110:输入 ADC 时钟 12 分频

0111:输入 ADC 时钟 16 分频

1000:输入 ADC 时钟 32 分频

1001:输入 ADC 时钟 64 分频

1010:输入 ADC 时钟 128 分频

1011:输入 ADC 时钟 256 分频

其它:保留

adc_ker_ck的时钟来自RCC_ADCCKSELR寄存器的ADCSRC[1:0]位的选择,我们一般设置ADCSRC[1:0]=0x01,即选择per_ck作为时钟源,而per_ck又由RCC_CPERCKSELR寄存器的CKPERSRC[1:0]位选择,默认为0,即选择hsi_ker_ck(64Mhz)作为per_ck。因此:

adc_ker_ck=per_ck=hsi_ker_ck=64Mhz。

注意ADC的输入时钟频率不能大于133MHz。可以根据需要设置ADC时钟的预分频系数,例如设置PRESC[3:0]=1,即可得到ADC转换时钟频率为:adc_ker_ck/2^PRESC[3:0]=64/2=32MHz。

2. ADC控制寄存器(ADC_CR)

ADC控制寄存器描述如下图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_16


29.2.1. 2寄存器

该寄存器我们用到多个位,这里就不全部列出来讲解了,而是抽出几个重要的位进行针对性的介绍,详细的介绍,请参考参考手册。

ADEN位,用于使能ADC转换器。需要设置该位为1,ADC才可以正常工作。

ADSTART位,用于启动ADC常规通道的转换序列。当使用硬件触发时(EXTEN[1:0]!=0),设置该位为1,必须在相应的硬件触发事件产生时,才会启动ADC转换。而当不使用硬件触发时(EXTEN[1:0]=0),设置该位为1则可以立即启动ADC转换。

BOOST位,用于设置是否使用BOOST模式。当BOOST=0时,ADC转换时钟必须小于20MHz;当BOOST=1时,ADC转换时钟必须大于20MHz。如果设置的32MHz的ADC转换时钟,因此该位必须设置为1。

ADCALLIN位,用于设置线性ADC校准。设置该位为1,可以设置ADC的校准模式为线性校准。

ADCAL位,用于控制/读取ADC校准状态。设置该位为1时,可以启动ADC校准,等校准完成以后,硬件会自动清零该位。因此在设置改位为1以后,通过判断该位是否变为0,即可判断校准是否完成。

3. ADC配置寄存器(ADC_CFGR)

ADC配置寄存器描述如图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_17


29.2.1. 3寄存器

RES[2:0]位,用于设置ADC转换的分辨率:0,16位;1,14位;2,12位;3,10位;4,8位;其他值:保留。本章我们使用16位分辨率,因此设置这3个位全0即可。

EXTEN[1:0]位,用于设置常规通道的外部触发方式和极性。本章我们使用软件触发,因此设置EXTEN[1:0]=00,即禁止外部触发即可。

OVRMOD位,用于设置是否使能覆写功能。当设置该位为0时,如果上一次转换的数据未及时读取,新的转换结果将被丢弃;当设置该位为1时,如果上一次转换的数据未及时读取,将会被新的结果覆盖。本章,我们设置该位为1。

CONT位,用于设置转换模式。当CONT=0时,表示单次转换模式;当CONT=1时,表示连续转换模式。本章,我们设置该位为0。

4. ADC配置寄存器2(ADC_CFGR2)

ADC配置寄存器2描述如图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_寄存器_18


29.2.1. 4寄存器

OSR[9:0]位,用于设置ADC的过采样率。OSR[9:0]=0~1023,表示1x~1024x过采样。本章,我们不使用过采样,设置OSR[9:0]=0即可。

LSHIFT[3:0]位,用于设置输出结果的左移位数,0~15表示左移0~15位。本章不使用左移(数据右对齐),因此设置LSHIFT[3:0]=0即可。

5. ADC常规序列寄存器1(ADC_SQR1)

ADC常规序列寄存器描述如下图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_时钟周期_19


29.2.1. 5寄存器

L[3:0]:用于存储常规序列的长度,取值范围:0~15,表示常规序列长度为:1~16。我们这里只用了1个通道,所以设置这几个位的值为0即可。

SQ1~SQ4表示常规序列中的第1~4个序列的转换通(0~19),每个常规序列的转换通道,可以由SQx(x=1~16)指定,比如我们设置:SQ1[4:0]=19,就表示常规序列1的转换通道为19(ADC1/2/3_CH19)。SQ5~SQ16由寄存器ADC_SQR2~4控制。

6. ADC采样时间寄存器2(ADC_SMPR2)

ADC采样时间寄存器2描述如下图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_20


29.2.1. 6寄存器

该寄存器用于设置ADC通道10~19的采样时间,而ADC_SMPR1设置ADC通道0~9的采样时间。STM32MP157的ADC总转换时间的计算方法前面已经介绍过了,采样时间周期越长,精度就越高,所以这里我们设置为最大采样时间。采样时间我们建议尽量长一点,以获得较高的准确度,但是这样会降低ADC的转换速率,所以大家在实际应用中自行结合自身情况设置。

7. ADCx通道预选寄存器(ADC_ PCSEL)

ADC通道预选寄存器描述如下图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_时钟周期_21


29.2.1. 7寄存器

该寄存器用于控制ADC具体某个输入通道和对应IO的连接,相当于在ADC输入和IO之间,加了一个开关,想要正常使用某个通道,则必须设置对应的PCSELy位为1(y=0~19),否则无法得到对应IO口的正常电压。注意:在STM32H7之前的的其他STM32芯片上面,是没有的,该寄存器的存在,有利于隔离ADC和IO的隔离。

举个简单的例子,在STM32H7上面,即便是ADC通道对应的IO口,只要不使用ADC功能(PCSEL不设置为1),那么该IO口就可以兼容5V,但是在STM32H7之前的其他STM32芯片上面,ADC所在的IO口,都不能做5V兼容。

8. ADC常规数据寄存器(ADC_ DR)

ADC常规数据寄存器描述如下图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_22


29.2.1. 8寄存器

常规序列中的AD转化结果都将被存在这个寄存器里面,我们通过读取该寄存器,即可得到ADC转换后的结果,而注入通道的转换结果被保存在ADC_JDRy(y=1~4)里面所以读取ADC_JDRy这个寄存器的就可以读取注入通道的ADC的转换值。

9. ADC中断和状态寄存器(ADC_ ISR)

ADC中断和状态寄存器描述如下图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_23


29.2.1. 9寄存器

ADC_ ISR寄存器的位我们只介绍常用的几位

EOS位是常规序列结束标志位,常规通道序列转换结束后,硬件将该位置 1,通过软件写入 1 可将该位清零。

OVR是ADC 溢出位,该位在常规通道上发生溢出事件时由硬件置 1,这意味着在 EOC 标志已置 1 时,新转换已完成,通过软件写入 1 可将该位清零。

EOC位是转换结束标志,当通道的每次常规转换结束,新数据出现在 ADC_DR 寄存器时,会通过硬件将该位置 1通过软件向该位写入1,或读取 ADC_DR 寄存器都可将该位清零。

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

至此,本章要用到的ADC相关寄存器全部介绍完毕了,对于未介绍的部分,请大家参考《STM32MP157参考手册》第29章相关内容。

29.2.3 ADC的HAL库驱动

ADCHAL库中的驱动代码在stm32mp1xx_hal_adc.c和stm32mp1xx_hal_adc_ex.c文件(及其头文件)中。

1. HAL_ADC_Init函数

ADC的初始化函数,其声明如下:

HAL_StatusTypeDef HAL_ADC_Init(ADC_HandleTypeDef *hadc);

  • 函数描述:用于初始化ADC
  • 函数形参:形参1ADC_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注入通道配置结构,用于配置注入通道的转换顺序,数据格式等 */ ​
ADC_InjectionConfigTypeDef InjectionConfig ; ​
}ADC_HandleTypeDef;

  • 该结构体定义和其他外设比较类似,我们着重看第二个成员变量Init含义,它是结构体ADC_InitTypeDef类型,结构体ADC_InitTypeDef定义为:

typedef struct {
uint32_t ClockPrescaler; /* 设置预分频系数,即PRESC[3:0]位 */
uint32_t Resolution; /* 配置ADC的分辨率 */
uint32_t ScanConvMode; /* 扫描模式 */
uint32_t EOCSelection; /* 转换完成标志位 */
FunctionalState LowPowerAutoWait; /* 低功耗自动延时 */
FunctionalState ContinuousConvMode; /* 开启连续转换模式否则就是单次转换模式 */
uint32_t NbrOfConversion; /* 设置转换通道数目 */
FunctionalState DiscontinuousConvMode; /* 单次转换模式选择 */
uint32_t NbrOfDiscConversion; /* 单次转换通道的数目 */
uint32_t ExternalTrigConv; /* ADC外部触发源选择*/
uint32_t ExternalTrigConvEdge; /* ADC外部触发极性*/
uint32_t ConversionDataManagement; /* 数据管理 */
uint32_t Overrun; /* 发生溢出时,进行的操作 */
uint32_t LeftBitShift; /* 数据左移几位 */
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) ScanConvMode:配置是否使用扫描。如果是单通道转换使用ADC_SCAN_DISABLE,如果是多通道转换使用ADC_SCAN_ENABLE
    4) EOCSelection:可选参数为ADC_EOC_SINGLE_CONV和ADC_EOC_SEQ_CONV,指定转换结束时是否产生EOS中断或事件标志。
    5) LowPowerAutoWait:配置是否使用低功耗自动延迟等待模式,可选参数为 ENABLE和 DISABLE,当使能时,仅当一组内所有之前的数据已处理完毕时,才开始新的转换,适用于低频应用。该模式仅用于ADC的轮询模式,不可用于DMA以及中断。
    6) ContinuousConvMode:可选参数为ENABLE和DISABLE,配置自动连续转换还是单次转换。使用ENABLE配置为使能自动连续转换;使用DISABLE配置为单次转换,转换一次后停止需要手动控制才重新启动转换。
    7) NbrOfConversion:设置常规转换通道数目,范围是:1~16。
    8) DiscontinuousConvMode:配置是否使用不连续的采样模式,比如要转换的通道有1、2、5、7、8、9,那么第一次触发会进行通道 1 与通道 2,下次触发就是转换通道 5 与通道 7,这样不连续的转换,依次类推。此参数只有将 ScanConvMode 使能,还有ContinuousConvMode失能的情况下才有效,不可同时使能。
    9) NbrOfDiscConversion:不连续采样通道数。
    10) ExternalTrigConv:外部触发方式的选择,如果使用软件触发,那么外部触发会关闭。
    11) ExternalTrigConvEdge:外部触发极性选择,如果使用外部触发,可以选择触发的极性,可选有禁止触发检测、上升沿触发检测、下降沿触发检测以及上升沿和下降沿均可触发检测。
    12) ConversionDataManagement: 指定ADC转换后的数据处理方式。可以选择 DMA管理传输数据、数据存储在数据寄存器中或者是传输到DFSDM寄存器中。
    13) Overrun:当有新的数据溢出时,可以选择覆盖写入或者是丢弃新的数据。
    14) LeftBitShift:数据左移位数,最多可支持左移15位。
    15) OversamplingMode:是否使用过采样模式。
    16) Oversampling:配置过采样模式的参数。
  • 函数返回值:HAL_StatusTypeDef枚举类型的值。

2. HAL_ADCEx_Calibration_Start函数

ADC的自校准函数,其声明如下:

HAL_StatusTypeDef HAL_ADCEx_Calibration_Start(ADC_HandleTypeDef *hadc,​
uint32_t CalibrationMode, uint32_t SingleDiff);

  • 函数描述:首先调用HAL_ADC_Init函数配置了相关的功能后,再调用此函数进行ADC自校准功能。
  • 函数形参:形参1ADC_HandleTypeDef结构体类型指针变量。
    形参2是校准模式选择,有以下两种:
    1)ADC_CALIB_OFFSET表示只运行偏移校准而不运行线性度校准。
    2)ADC_CALIB_OFFSET_LINEARITY表示同时运行偏移校准和线性度校准。
    形参3是单端或差分模式选择,有以下两种:
    1)ADC_SINGLE_ENDED表示单端输入模式。
    2)ADC_DIFFERENTIAL_ENDED表示差分输入模式。
  • 函数返回值:HAL_StatusTypeDef枚举类型的值。

3. HAL_ADC_ConfigChannel函数

ADC通道配置函数,其声明如下:

HAL_StatusTypeDef HAL_ADC_ConfigChannel(ADC_HandleTypeDef *hadc, ​
ADC_ChannelConfTypeDef *sConfig);

  • 函数描述:调用了HAL_ADC_Init函数配置了相关的功能后,就可以调用此函数配置ADC具体通道。
  • 函数形参:形参1ADC_HandleTypeDef结构体类型指针变量。形参2ADC_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; /* 偏移量 */​
FunctionalState OffsetRightShift; /* 数据右移位数*/​
FunctionalState OffsetSignedSaturation; /* 转换数据格式为有符号位数据 */​
} ADC_ChannelConfTypeDef;


  • 1) Channel:ADC转换通道,范围:0~19。
    2) Rank:在常规转换中的常规组的转换顺序,可以选择1~16。
    3) SamplingTime:ADC的采样周期,最大810.5ADC时钟周期,要求尽量大以减少误差。
    4) SingleDiff:选择通道单端输入还是差分输入。
    5) OffsetNumber:选择使用偏移量的通道。
    6) Offset:定义要从原始数据减去的偏移量。根据ADC的分辨率不同,支持的最大偏移量也不同,例如分辨率是16bit,,最大的偏移量为0xFFFF。
    7) OffsetRightShift:采样值进行右移的位数。
    8) OffsetSignedSaturation:是否使能ADC采样值的最高位为符号位。
  • 函数返回值:HAL_StatusTypeDef枚举类型的值。

4. HAL_ADC_Start函数

  • 函数描述:当配置好ADC的基础的功能后,就调用此函数启动ADC
  • 函数形参:形参1ADC_HandleTypeDef结构体类型指针变量。
  • 函数返回值:HAL_StatusTypeDef枚举类型的值。
    ADC转换启动函数,其声明如下:
    HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef *hadc);

5. HAL_ADC_Stop函数

  • 函数描述: 停止ADC的转换(常规模式下停止常规组的ADC转换,注入模式下停止注入通道),禁用ADC外设。注意的是,ADC外设禁用正在强制停止注入组的电位转换。 如果正在使用注入组,则应使用HAL_ADCEx_InjectedStop函数预先将其停止。
  • 函数形参: 形参1ADC_HandleTypeDef结构体类型指针变量。
  • 函数返回值:HAL_StatusTypeDef枚举类型的值。
    HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef *hadc)

6. HAL_ADC_PollForConversion函数

等待ADC常规组转换完成函数,其声明如下:

HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef *hadc,​
uint32_t Timeout);

  • 函数描述:一般先调用HAL_ADC_Start函数启动转换,再调用该函数等待转换完成,然后再调用HAL_ADC_GetValue函数来获取当前的转换值。
  • 函数形参:形参1ADC_HandleTypeDef结构体类型指针变量。
    形参2是等待转换的等待时间,单位是毫秒(ms)。
  • 函数返回值:HAL_StatusTypeDef枚举类型的值。

7. HAL_ADC_GetValue函数

获取常规组ADC转换值函数,其声明如下:

uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef *hadc);

  • 函数描述:一般先调用HAL_ADC_Start函数启动转换,再调用HAL_ADC_PollForConversion函数等待转换完成,然后再调用HAL_ADC_GetValue函数来获取当前的转换值。
  • 函数形参:形参1ADC_HandleTypeDef结构体类型指针变量。
  • 函数返回值:当前的转换值,uint32_t类型数据。

29.2.4 硬件设计

1. 例程功能

使用ADC1采集通道19(PA5)上面的电压,然后通过串口UART4打印ADC转换值以及换算成电压后的电压值。同时程序中通过LED0闪烁来指示程序在运行状态。

开发板上有引出PA5引脚,先使用跳线帽将JP2排针的ADC1和电位器的RP_AD连接,这样PA5就连接到电位器VR1上了,电位器上接的是3.3V,用户可以通过调节电位器的旋钮改变接入到PA5上的电压值为0~3.3V(实际上就是通过改变电阻来改变电压)。实验前要记住检查跳线帽是否有接好:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_寄存器_24


29.2.3. 1硬件部分

开发板上有1 组 3.3V 电源供应接口JP7,JP7排针有3路输出3.3V,另外3路接地(0V),我们也可以使用这个排针来测试,接入到被测通道中。注意的是,千万不要接错旁边的JP8排针引出的5V引脚,否则烧坏IO口甚至整个主控芯片

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_时钟周期_25


图29.2.3. 2开发板的电源输出接口

2. 硬件资源

1)LED灯:LED0

2)串口4

3)ADC1的通道19引脚(PA5)

LED0

UART4_TX

UART4_RX

ADC1_INP19

PI0

PG11

PB2

PA5

29.2.3. 1硬件资源

3. 原理图

ADC属于STM32MP157的内部资源,实际上我们只需要软件设置就可以正常工作,不过我们需要在外部将ADC1的通道19引脚(PA5)连接到被测电压点上面,本实验的被测电压是来自开发板自带的电位器上的电压,电位器可调节的电压范围是:0~3.3V。当然也可以使用ADC1测试其它外接入的电压,只需要一根杜邦线将要测试的电压引入PA5引脚即可,要注意接入的电压范围不能超过3.3V,否则可能烧坏我们的ADC,甚至是整个主控芯片。

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_时钟周期_26


29.2.3. 3原理图部分

29.2.5 程序设计

本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ ADC1_SINGLE_CH

1. adc.h文件代码

adc.h文件针对ADC及通道引脚定义了一些宏定义,具体如下:

#ifndef __ADC_H​
#define __ADC_H​
#include "./SYSTEM/sys/sys.h"​
/*************************单通道ADC采集实验 ADC及引脚 定义 *************/​
#define ADC_ADCX_CHY_GPIO_PORT GPIOA​
#define ADC_ADCX_CHY_GPIO_PIN GPIO_PIN_5​
/* PA口时钟使能 */​
#define ADC_ADCX_CHY_GPIO_CLK_ENABLE() do{__HAL_RCC_GPIOA_CLK_ENABLE();}while(0) ​
#define ADC_ADCX ADC1 ​
#define ADC_ADCX_CHY ADC_CHANNEL_19 /* 通道Y, 0 <= Y <= 19 */​
/* ADC1 & ADC2 时钟使能 */ ​
#define ADC_ADCX_CHY_CLK_ENABLE() do{ __HAL_RCC_ADC12_CLK_ENABLE(); }while(0) ​
/*******************************函数声明*************************/​
void adc_init(void); /* ADC初始化函数 */​
void adc_gpio_init(void); /* ADC的gpio初始化函数 */​
uint32_t adc_get_result(uint32_t ch); /* 获得某个通道值 */​
/* 得到某个通道给定次数采样的平均值 */​
uint32_t adc_get_result_average(uint32_t ch, uint8_t times); ​
#endif

2. adc.c文件代码

(1)ADC初始化函数

ADC_HandleTypeDef g_adc_handle; /* ADC句柄 */​
/**​
* @brief初始化函数​
* @note本函数支持ADC1/ADC2任意通道,但是不支持ADC3​
我们使用16位精度, ADC采样时钟=32M, 转换时间​
为:采样周期 + 8.5个ADC周期​
设置最大采样周期: 810.5, 则转换时间 = 819个ADC周期 = 25.6us​
* @param无​
* @retval无​
*/​
void adc_init(void)​
{​
RCC_PeriphCLKInitTypeDef adc_periphclk_init_struct; ​
ADC_ADCX_CHY_CLK_ENABLE(); /* 使能ADC1/2时钟 */​
/* 设置ADC时钟源=PER=64MHz,后面分为2分频,即ADC时钟源为32MHz */​
adc_periphclk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;​
adc_periphclk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PER;​
HAL_RCCEx_PeriphCLKConfig(&adc_periphclk_init_struct);​
adc_gpio_init(); /* GPIO初始化 */​
g_adc_handle.Instance = ADC_ADCX;​
/* 输入时钟2分频,即adc_ker_ck= PER/2=32MHz */​
g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;​
/* ADC分辨率为16位模式 */​
g_adc_handle.Init.Resolution = ADC_RESOLUTION_16B; ​
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; /* 使用了1个转换通道 */​
g_adc_handle.Init.DiscontinuousConvMode = DISABLE; /* 禁止不连续采样模式 */​
/* 禁止不连续模式后,此参数忽略,不连续采样通道数为0 */​
g_adc_handle.Init.NbrOfDiscConversion = 0; ​
g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;/* 软件触发 */​
/* 采用软件触发的话,此位忽略 */​
g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; ​
/* 有新的数据的死后直接覆盖掉旧数据 */​
g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; ​
g_adc_handle.Init.OversamplingMode = DISABLE; /* 过采样关闭 */​
/* 规则通道的数据仅仅保存在DR寄存器里面 */​
g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR; ​
HAL_ADC_Init(&g_adc_handle); /* 使用HAL库初始化ADC */​
/* ADC校准 */​
HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED); ​
}

通过HAL_RCCEx_PeriphCLKConfig(&adc_periphclk_init_struct);语句配置ADC时钟源来自PER,其中PER时钟源默认使用HSI,即为64MHz。通过g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;语句配置为2分频,所以最终ADC的时钟频率为32MHz。

最后一行,ADC校准需在ADC开始前或结束后,如果不校验,结果会有偏差该函数调用HAL_ADC_Init函数配置了ADC的基础功能参数,HAL_ADC_Init函数的MSP回调函数是HAL_ADC_MspInit,用来使能时钟和初始化IO口。但是这里我们不使用HAL库提供的这个MSP回调函数,而是自己定义了一个adc_gpio_init函数来完成初始化IO口的功能,这里是为了方便后面其他的ADC实验调用。adc_gpio_init函数定义如下:

(2)adc_gpio_init函数

/**​
* @brief的gpio初始化函数​
* @param无​
* @note此函数会被adc_init()调用​
* @retval无​
*/​
void adc_gpio_init(void)​
{​
GPIO_InitTypeDef gpio_init_struct;​
ADC_ADCX_CHY_GPIO_CLK_ENABLE(); /* 开启ADC通道IO引脚时钟 */​
gpio_init_struct.Pin = ADC_ADCX_CHY_GPIO_PIN; /* ADC通道IO引脚 */​
gpio_init_struct.Mode = GPIO_MODE_ANALOG; /* 模拟 */​
gpio_init_struct.Pull = GPIO_NOPULL; /* 不带上下拉 */​
/* 使用HAL库初始化GPIO */​
HAL_GPIO_Init(ADC_ADCX_CHY_GPIO_PORT, &gpio_init_struct); ​
}

下面是获得ADC转换后的结果函数,其定义如下:

(3)获得ADC转换后的结果函数

adc_get_result函数用于开启ADC通道、进行轮询转换、并返回最近一次ADC1规则组的转换结果:

/**​
* @brief获得ADC转换后的结果 ​
* @param通道值 0~19,取值范围为:ADC_CHANNEL_0~ADC_CHANNEL_19​
* @retval返回值:转换结果​
*/​
uint32_t adc_get_result(uint32_t ch) ​
{​
ADC_ChannelConfTypeDef ADC1_ChanConf;​
ADC1_ChanConf.Channel = ch; /* 通道 */​
ADC1_ChanConf.Rank = ADC_REGULAR_RANK_1; /* 1个序列 */​
ADC1_ChanConf.SamplingTime = ADC_SAMPLETIME_810CYCLES_5; /* 采样时间 */​
ADC1_ChanConf.SingleDiff = ADC_SINGLE_ENDED; /* 单边采集 */​
ADC1_ChanConf.OffsetNumber = ADC_OFFSET_NONE;​
ADC1_ChanConf.Offset = 0; ​
HAL_ADC_ConfigChannel(&g_adc_handle ,&ADC1_ChanConf); /* 通道配置 */​
HAL_ADC_Start(&g_adc_handle); /* 开启ADC */​
HAL_ADC_PollForConversion(&g_adc_handle, 10); /* 轮询转换 */​
/* 返回最近一次ADC1规则组的转换结果 */​
return HAL_ADC_GetValue(&g_adc_handle); ​
}

我们前面配置的是软件触发模式,也就是需要启动ADC以后ADC才可以进行转换操作adc_get_result_average函数调用adc_get_result函数实现获取ADC通道的转换值,该函数内容如下:

/**​
* @brief获取通道ch的转换值,取times次,然后平均​
* @param通道号, 0~19​
* @param获取次数​
* @retval通道ch的times次转换结果平均值​
*/​
uint32_t adc_get_result_average(uint32_t ch, uint8_t times)​
{​
uint32_t temp_val = 0;​
uint8_t t;​
for (t = 0; t < times; t++) /* 获取times次数据 */​
{​
temp_val += adc_get_result(ch);​
delay_ms(5);​
}​
return temp_val / times; /* 返回平均值 */​
}

参数timesADC轮询转换的次数可以手动设置转换次数,然后取平均值

3. main.c文件代码

int main(void)​
{​
uint16_t adcx;​
float temp;​
uint8_t i = 0;​
HAL_Init(); /* 初始化HAL库 */​
/* 初始化M4内核时钟,209M */​
if(IS_ENGINEERING_BOOT_MODE())​
{​
sys_stm32_clock_init(34, 2, 2, 17, 6826);​
}​
usart_init(115200); /* 串口初始化为115200 */​
delay_init(209); /* 延时初始化 */​
led_init(); /* 初始化LED */​
adc_init(); /* 初始化ADC */​
while (1)​
{​
i++;​
/* 获取通道19的转换值,10次取平均 */​
adcx = adc_get_result_average(ADC_ADCX_CHY, 10); ​
/* 获取计算后的带小数的实际电压值,比如3.1111 */​
temp = (float)adcx * (3.3 / 65536); ​
printf("ADC Value = %d, Voltage = %.3fV\r\n", adcx, temp);​
if(i == 5) ​
{​
i = 0;​
LED0_TOGGLE();​
}​
delay_ms(5000);​
}​
}

ADC1通道19的转换值记录在adcx变量中计算ADC转换值对应的电压值:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_27

这里,我们前面配置ADC的分辨率为16位,也就是2^16=65536,参考电压是3.3V,然后将转换的ADC的值以及计算出的参考电压值通过UART4打印出来。

29.2.6 编译和测试

编译无报错,测试结果如下,用螺丝刀拧动电位器,改变接入测试点PA5的阻值,接入点的测试电压将会变化,串口打印数据如下(每隔5s打印一次,LED0每隔5s闪烁一次):

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_寄存器_28


29.2.5. 1运行结果

29.3 单通道ADC采集(DMA读取)实验

本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\3、M4裸机驱动例程\库V1.2\实验18-2 ADC实验-单通道ADC采集(DMA读取)

29.3.1 DMA方式传输数据

本实验我们来学习使用常规单通道的连续转换模式,并且使用DMA将ADC转换后的数据传输到内存中,再从内存中获取数据进行后期的计算。

1. DMA方式传输的优势

在上一章实验中,我们学习了ADC常规单通道的单次转换,我们使用HAL_ADC_PollForConversion函数先等待ADC转换完成后将转换值存在ADC_DR寄存器中,程序中使用HAL_ADC_GetValue获取转换值然后再进行后期运算的,运算完成后就使用UART4发送转换和运算的结果。

我们知道,ADC根据转换组的不同,ADC转换后的数据存放的位置不同,常规组转换后的数据是存在常规数据寄存器(ADC_DR)中,注入组的数据是存在注入数据寄存器(ADC_JDRy)中的常规通道有16个,注入通道有4个,以常规通道为例,常规数据寄存器(ADC_DR)只有一个如果要使用多个通道进行转换转换后的数值都保存在ADC_DR寄存器中了如果不及时读取和处理转换后的数据上一次的转换结果可能会被下一次的转换结果覆盖掉势必造成数据的错误如果使用DMA,ADC会在每次通道转换后生成 DMA 请求,DMA把转换完成后存在ADC_DR寄存器中的数据传输到内存(软件选择的目标位置)后再进行处理,而且使用DMA传输数据,无需CPU干预,可以减轻CPU的负担,使CPU更高效地执行其他任务。

2. 数据管理

  1. 数据溢出使用DMA传输还会存在一个问题,如果因 DMA 无法及时处理 DMA 传输请求而发生溢出 (OVR=1),ADC 会停止生成 DMA 请求,而ADC仍在继续转换数据,新转换对应的数据不会通过 DMA 进行传输,所以我们要进行相应的处理,所以我们可以使用ADC_CFGR寄存器的OVRMOD位来控制,此位用于管理数据溢出的方式:此位写0,当检测到溢出时,ADC_DR寄存器与旧数据一起保留;此位写1,当检测到溢出时,ADC_DR寄存器将被上一次转换结果覆盖,旧的数据被丢弃。在STM32CubeMX中,这个配置就是我们前面配置ADC参数时选择的Overrun behaviour选项:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_29

29.3.1. 1设置溢出数据处理方法

  1. DMA 单次模式和DMA 循环模式

根据DMA传输数据的数目以及DMA设置的模式不同,还需要配置为对应的模式,这里

(1)DMA 单次模式 (ADC_CFGR寄存器的DMNGT=01):

如果将 DMA 设置为传输固定数目的数据就停止的话,可以选择此模式。

在该模式下,在转换固定数量的数据内,每次出现新的转换数据时,ADC 都会生成 DMA 传输请求,DMA 传输完最后一个数据时,(发生 DMA_EOT 中断),此时ADC还在继续转换,即使转换已再次开始,ADC 也会停止生成 DMA 请求。例如使用DMA传输5和数据,ADC转换一次就会生成DMA请求,5个数据就需要ADC转换5次,而ADC也请求DMA传输5次,当DMA将这5次传输完成后,如果不关掉ADC,那么ADC还在继续转换,而ADC不会在对DMA发起请求传输数据了。

(2)DMA 循环模式(ADC_CFGR寄存器的DMNGT 位 = 11)

如果在循环模式下对 DMA 进行编程,应选择此模式。

在该模式下,每次数据寄存器中出现新的转换数据时,ADC 都会生成 DMA 传输请求,即使DMA 已到达最后一次 DMA 传输操作也不例外。这样可将处于循环模式的 DMA 配置为处理连续的模拟输入数据流。

(3)还有一种模式就是使用 DFSDM(调制器的数字滤波器)进行管理,ADC 转换结果可直接传送到DFSDM中,这里就要求数据必须为 16 位有符号格式,任何超出 16 位有符号格式的值将会被截断,即ADCx_DR[31:16]无效,ADCx_DR[15]用于表示符号位,ADCx_DR[14:0]用于存储数据。传输生效后,会立即复位 EOC 标志。关于此模式,本实验没有用到。

以上讲解的3种模式,在STM32CubeMX下可配置,根据DMA的模式来选择,如下图, Conversion Data Management Mode配置用于配置转换后的数据管理模式:

  • Regular Conversion data stored in DR register only表示常规转换数据仅存储在DR寄存器中;
  • DFSDM Mode就是我们前面讲的使用DFSDM(调制器的数字滤波器)进行管理;
  • DMA One Shot Mode表示DMA单次模式,DMA Circular Mode表示DMA循环模式。

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_寄存器_30


29.3.1. 2设置转换数据管理模式

如果是使用STM32CubeMX进行配置生成初始化代码的朋友可以参考《【正点原子】STM32MP1 M4裸机CubeIDE开发指南》的配置步骤进行配置。

22.3.2 ADC & DMA寄存器 ​

本实验我们很多的设置和单通道ADC采集实验是一样的,所以下面介绍寄存器的时候我们不会继续全部都介绍,而是针对性选择与单通道ADC采集实验不同设置的ADC_CFGR寄存器进行介绍,其他的配置基本一样的。另外因为我们用到DMA读取数据,所以还会用到DMA相关的寄存器。DMA相关的寄存器在前面DMA章节有讲解,部分相关的寄存器这里就不再赘述了。

1. ADC配置寄存器(ADC_CFGR)​

ADC配置寄存器如下图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_时钟周期_31


图29.3.2. 1 ADC_CFGR寄存器

ADC_CFGR寄存器中我们主要跟前面设置不同的有两个位,分别如下:

CONT位,用于设置转换模式。单通道ADC采集实验单次转换模式,本实验中我们要设置为连续转换模式,所以该位设置为1

DMNGT[1:0]位,用于数据管理配置。单通道ADC采集实验我们是默认设置为:

00:常规转换数据仅存储在DR中,然后通过软件去DR数据寄存器读取;

01:选择 DMA 单次模式;

10:选择 DFSDM 模式;

11:选择 DMA 循环模式。

本实验我们可以设置为01,即选择 DMA单次模式,这样启动一次DMA传输,DMA就会自动读取一次数据。也可以选择DMA 循环模式,这样就需要配置DMA请求为循环模式。如下是STM32CubeMX中的配置项

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_寄存器_32


图29.3.2. 2设置DMA循环模式

本实验ADC的寄存器就介绍ADC_CFGR寄存器,其他的寄存器参考上一个实验的配置。下面介绍DAM一些比较重要的寄存器配置。

2. DMA数据流x外设地址寄存器(DMA_SxPAR)​

DMA数据流x外设地址寄存器如下图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_寄存器_33


图29.3.2. 3 DMA_SxPAR寄存器

该寄存器存放的是DMA读或者写数据的外设数据寄存器的基址。本实验,我们需要通过DMA读取ADC1转换后存放在ADC1常规数据寄存器 (ADC_DR) 的结果数据。所以我们需要给DMA_SxPAR寄存器写入ADC_DR寄存器的地址。这样配置后,DMA就会从ADC_DR寄存器的地址读取ADC的转换后的数据到某个内存空间。这个内存空间地址需要我们通过DMA_SxM0AR寄存器来设置,比如定义一个变量,把这个变量的地址值写入该寄存器。

注意:DMA_SxPAR寄存器受到写保护,只有DMA_SxCR寄存器中的EN为“0”时才可以写入,即先要禁止数据流传输才可以写入。

3. DMA数据流x存储器0地址寄存器(DMA_SxM0AR)​

DMA数据流x存储器0地址寄存器描述如下图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_时钟周期_34


图29.3.2. 4 DMA_SxM0AR寄存器

该寄存器存放的是DMA读或者写数据的存储器的地址。这些位受到写保护,只有当禁止数据流(DMA_SxCR 寄存器中的位 EN=“0”)或使能数据流(DMA_SxCR 寄存器中的 EN=“1”)并且 DMA_SxCR 寄存器中的位 CT =“1”(在双缓冲区模式下)时才可以写入。如果用到双缓冲区模式我们还需要用到DMA_SxM1AR寄存器,本实验我们是用不到的。

4. DMA数据流x数据项数寄存器(DMA_SxNDTR)​

DMA数据流x数据项数寄存器描述如下图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_35


图29.3.2. 5寄存器

DMA_SxPAR寄存器是传输的源地址,DMA_SxM0AR寄存器是传输的目的地址,DMA_SxNDTR寄存器则是要传输的数据项数目(0到65535)。

其他的DMA寄存器我们就不一一介绍了,请大家看着寄存器源码对照手册理解。

29.3.3 ADC & DMA的HAL库驱动​

单通道ADC采集实验已经介绍本实验要用到的ADC的HAL库API函数,这里我们要介绍启动使用中断的DMA传输函数和启动ADC(DMA传输)方式函数。

1. HAL_ADC_Start_IT​

HAL_ADC_Start_IT开启ADC中断转换函数明如下:

HAL_StatusTypeDef HAL_ADC_Start_IT(ADC_HandleTypeDef *hadc)

  • 函数描述: 启用ADC中断常规组的转换。
  • 函数形参: 形参1是ADC_HandleTypeDef结构体类型指针变量。
  • 函数返回值: HAL_StatusTypeDef枚举类型的值。
  • 注意事项:该功能中启用中断:EOC(转换结束)、EOS(序列结束)、OVR溢出,每个中断都有其专用的回调函数。同时注意使用多重模式功能的情况,当多重模式功能可用时,必须先为ADC从设备调用HAL_ADC_Start_IT,然后才为ADC主设备调用:对于ADC从设备,仅启用ADC(不启动转换);对于ADC主机,启用ADC并启动多模转换。如果要停止转换,请使用HAL_ADC_Stop_IT函数
    用户可以重新定义ADC转换完成回调函数HAL_ADC_ConvCpltCallback以实现想要的功能

2. HAL_DMA_Start_IT函数​

启动使用中断的DMA传输函数,其声明如下:

HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, ​
uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength);

  • 函数描述: 用于启动使用中断的DMA传输。
  • 函数形参: 形参1是DMA_HandleTypeDef结构体类型指针变量。
    形参2DMA传输的源地址。
    形参3DMA传输的目的地址。
    形参4是要传输的数据项数目,实际上配置的是DMA_SxNDTR寄存器的NDT[15:0]值,要传输的数据项数目在0 到 65535之间。
  • 函数返回值:HAL_StatusTypeDef枚举类型的值。

3. HAL_ADC_Start_DMA函数​

启动ADC,开始常规组的转换,并通过DMA传输结果的函数,注意此函数的形参2和形参3是uint32_t类型的,其声明如下:

HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc, ​
uint32_t *pData, uint32_t Length);

  • 函数描述: 用ADC,开始常规组的转换,并通过DMA传输结果。
  • 函数形参: 形参1是ADC_HandleTypeDef结构体类型指针变量。
    形参2是ADC 采样数据传输的目的地址。
    形参3是要传输的数据项数目, 实际上配置的是DMA_SxNDTR寄存器的NDT[15:0]值,要传输的数据项数目在0 到 65535之间。
  • 函数返回值: HAL_StatusTypeDef枚举类型的值。
  • 注意事项:

HAL_ADC_Start_DMAHAL_DMA_Start_IT都是配置并启动DMA函数,区别是:HAL_ADC_Start_DMA比较局限性,用于启动ADC的数据传输,但ADC对DMA的传输请求和我们前面说的DMA单次模式以及DMA的循环模式有关。HAL_DMA_Start_IT则适用性较广泛,任何能使用DMA传输的场景都可以用该函数启动。实际应用中看个人的需求选择用哪个函数。在例程中我们可以使用HAL_DMA_Start_IT函数来启动DMA传输,当然该函数还为我们使能了DMA全部的中断,如果不使用DMA中断,我们还可以使用HAL_DMA_Start函数。也可以使用HAL_ADC_Start_DMA函数来实现本实验我们就使用HAL_ADC_Start_DMA函数

启用多模式的情况(当多模式功能可用时),HAL_ADC_Start_DMA仅为单ADC模式设计,对于多重模式,必须使用HAL_ADCEx_MultiModeStart_DMA。

可以使用HAL_ADC_Stop_DMA函数停止转换和禁用ADC外设,ADC转换完成后,用户可以重新定义HAL_ADC_ConvCpltCallback或者HAL_ADC_ConvHalfCpltCallback(半传输完成)函数实现想要的操作

4. 回调函数

不管是使用HAL_ADC_Start_DMA还是使用HAL_ADC_Start_IT,ADC转换完成以后都会进入回调函数HAL_ADC_ConvCpltCallback,我们可以在此回调函数中做文章,当ADC转换完成后执行某些操作。

几个回调函数如下,用户可以自行定义回调函数以实现对应的功能:

/* 非阻塞模式下的ADC错误回调函数 */​
__weak void HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc) ​
/* 拟看门狗1回调在非阻塞模式函数 */​
__weak void HAL_ADC_LevelOutOfWindowCallback(ADC_HandleTypeDef *hadc)​
/* 在非阻塞模式下完成转换回调函数 */​
__weak void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)​
/*非阻塞模式下的转换DMA半传输回调函数 */​
__weak void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc)

29.3.4 硬件设计

1. 例程功能

使用ADC1采集通道19(PA5)上面的电压,然后通过串口UART4打印ADC转换值以及换算成电压后的电压值。同时程序中通过LED0闪烁来指示程序在运行状态。

开发板上有引出PA5引脚,先使用跳线帽将JP2排针的ADC1和电位器的RP_AD连接,这样PA5就连接到电位器VR1上了,电位器上接的是3.3V,用户可以通过调节电位器的旋钮改变接入到PA5上的电压值为0~3.3V(实际上就是通过改变电阻来改变电压)。实验前要记住检查跳线帽是否有接好:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_36


图29.3.4. 1硬件部分

开发板上有1 组 3.3V 电源供应接口JP7,JP7排针有3路输出3.3V,另外3路接地(0V),我们也可以使用这个排针来测试,接入到被测通道中。注意的是,千万不要接错旁边的JP8排针引出的5V引脚,否则烧坏IO口甚至整个主控芯片。

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_37


图29.3.4. 2开发板的电源输出引脚

2. 硬件资源

1)LED灯:LED0

2)串口4

3)ADC1的通道19引脚(PA5)

4)DMA(DMA2 数据流7 ,也可以配置为其它数据流)

LED0

UART4_TX

UART4_RX

ADC1_INP19

PI0

PG11

PB2

PA5

表29.3.4. 1硬件资源

3. 原理图

DMA属于STM32MP157内部资源,通过软件设置好就可以了。

ADC属于STM32MP157的内部资源,实际上我们只需要软件设置就可以正常工作,不过我们需要在外部将ADC1的通道19引脚(PA5)连接到被测电压点上面,本实验的被测电压是来自开发板自带的电位器上的电压,电位器可调节的电压范围是:0~3.3V。当然也可以使用ADC1测试其它外接入的电压,只需要一根杜邦线将要测试的电压引入PA5引脚即可,要注意接入的电压范围不能超过3.3V,否则可能烧坏我们的ADC,甚至是整个主控芯片。

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_寄存器_38


图29.3.4. 1原理图部分

29.3.5 程序设计

ADC相关程序我们放在adc.cadc.h文件中。

1. adc.h文件代码

adc.h文件针对本实验用到DMA,我们定义了以下一些宏定义:

/* 注意: 这里我们的通道还是使用上面的定义 */​
#define ADC_ADCX_DMASx DMA1_Stream7 ​
#define ADC_ADCX_DMASx_REQ DMA_REQUEST_ADC1/* ADC1_DMA请求源 */​
#define ADC_ADCX_DMASx_IRQn DMA1_Stream7_IRQn ​
#define ADC_ADCX_DMASx_IRQHandler DMA1_Stream7_IRQHandler​
/* 判断DMA1 Stream7传输完成标志, 这是一个假函数形式,不能当函数使用, 只能用在if等语句里面 */​
#define ADC_ADCX_DMASx_IS_TC() ( __HAL_DMA_GET_FLAG(&g_dma_adc_handle, DMA_FLAG_TCIF3_7) )​
/* 清除DMA1 Stream7传输完成标志 */ ​
#define ADC_ADCX_DMASx_CLR_TC() do{ __HAL_DMA_CLEAR_FLAG(&g_dma_adc_handle, DMA_FLAG_TCIF3_7); }while(0) ​
void adc_dma_init(uint32_t par, uint32_t mar); /* ADC采集(DMA读取)初始化函数 */​
void adc_dma_enable( uint16_t ndtr); /* 使能一次ADC DMA采集传输 */

2. adc.c文件代码

下面直接开始介绍adc.c的程序,首先是ADC采集 DMA读取初始化函数。

(1)ADC采集 DMA读取初始化函数

1 ADC_HandleTypeDef g_adc_dma_handle; /* 与DMA关联的ADC句柄 */​
2 DMA_HandleTypeDef g_dma_adc_handle; /* 与ADC关联的DMA句柄 */​
3 uint8_t g_adc_dma_sta = 0; /* DMA传输状态标志, 0,未完成; 1, 已完成 */​
4 /**​
5 * @brief采集DMA读取 初始化函数​
6 * @note本函数还是使用adc_init对ADC进行大部分配置,有差异的地方再单独配置​
7 * @param外设地址​
8 * @param存储器地址​
9 * @retval无​
10 */​
11 void adc_dma_init(uint32_t par, uint32_t mar)​
12 {​
13 RCC_PeriphCLKInitTypeDef adc_periphclk_init_struct;​
14 ADC_ChannelConfTypeDef adc_ch_conf;​
15 ​
16 ADC_ADCX_CHY_CLK_ENABLE(); /* 使能ADC1/2时钟 */​
17 __HAL_RCC_DMAMUX_CLK_ENABLE(); /* 使能DMAMUX时钟 */​
18 ​
19 /* 设置ADC时钟源=PER=64MHz,后面分为2分频,即ADC时钟源为32MHz */​
20 adc_periphclk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;​
21 adc_periphclk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PER;​
22 HAL_RCCEx_PeriphCLKConfig(&adc_periphclk_init_struct);​
23 ​
24 adc_gpio_init(); /* GPIO初始化 */​
25 ​
26 g_adc_dma_handle.Instance = ADC_ADCX; /* ADC1 */​
27 /* 输入时钟2分频,即adc_ker_ck= PER/2=32MHz */​
28 g_adc_dma_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2; ​
29 g_adc_dma_handle.Init.Resolution = ADC_RESOLUTION_16B; /* 16位模式 */​
30 g_adc_dma_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; /* 非扫描模式 */​
31 g_adc_dma_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;/* 关闭EOC中断 */​
32 g_adc_dma_handle.Init.LowPowerAutoWait = DISABLE; /* 自动低功耗关闭 */​
33 g_adc_dma_handle.Init.ContinuousConvMode = DISABLE; /* 关闭连续转换 */​
34 g_adc_dma_handle.Init.NbrOfConversion = 1; /* 使用了1个转换通道 */​
35 g_adc_dma_handle.Init.DiscontinuousConvMode = DISABLE; /* 禁止不连续采样模式 */​
36 /* 禁止不连续模式后,此参数忽略,不连续采样通道数为0 */​
37 g_adc_dma_handle.Init.NbrOfDiscConversion = 0; ​
38 g_adc_dma_handle.Init.ExternalTrigConv= ADC_SOFTWARE_START;/* 采用软件触发 */​
39 /* 采用软件触发的话,此位忽略 */​
40 g_adc_dma_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;​
41 /* 有新的数据的后直接覆盖掉旧数据 */​
42 g_adc_dma_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; ​
43 g_adc_dma_handle.Init.OversamplingMode = DISABLE; /* 过采样关闭 */​
44 /* 规则通道的数据仅仅保存在DR寄存器里面 */​
45 g_adc_dma_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR; ​
46 HAL_ADC_Init(&g_adc_dma_handle); /* 初始化 */​
47 /* ADC校准 */​
48 HAL_ADCEx_Calibration_Start(&g_adc_dma_handle, ADC_CALIB_OFFSET, \ ADC_SINGLE_ENDED); ​
49​
50 /* 配置ADC连续转换, DMA单次传输ADC数据 */​
51 ADC_ADCX->CFGR |= 1 << 0; /* DMNGT[1:0] = 01, DMA单次传输ADC数据 */​
52 ADC_ADCX->CFGR |= 1 << 13; /* CONT = 1, 连续转换模式 */​
53​
54 /* DMA相关配置 */​
55 dma_mux_init(ADC_ADCX_DMASx, ADC_ADCX_DMASx_REQ); /* 初始化DMA 请求复用器 */ ​
56 ADC_ADCX_DMASx->PAR = par; /* DMA外设地址 */​
57 ADC_ADCX_DMASx->M0AR = mar; /* DMA 存储器0地址 */​
58 ADC_ADCX_DMASx->NDTR = 0; /* DMA 存储器0地址 */​
59​
60 g_dma_adc_handle.Instance = ADC_ADCX_DMASx; /* 使用DMA1 Stream7 */​
61 /* 请求类型采用DMA_REQUEST_ADC1 */​
62 g_dma_adc_handle.Init.Request = ADC_ADCX_DMASx_REQ; ​
63 /* 传外设到存储器模式 */​
64 g_dma_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; ​
65 g_dma_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */​
66 g_dma_adc_handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器增量模式 */​
67 /* 外设数据长度:16位 */​
68 g_dma_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; ​
69 /* 存储器数据长度:16位 */​
70 g_dma_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; ​
71 g_dma_adc_handle.Init.Mode = DMA_CIRCULAR; /* 循环模式 */​
72 g_dma_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM; /* 中等优先级 */​
73 g_dma_adc_handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; /* 禁止FIFO*/​
74 HAL_DMA_Init(&g_dma_adc_handle); /* 初始化DMA */​
75​
76 ADC_ADCX_DMASx->CR |= 1 << 4; /* TCIE = 1, DMA传输完成中断使能 */​
77 /* 将DMA与adc联系起来 */​
78 __HAL_LINKDMA(&g_adc_dma_handle, DMA_Handle, g_dma_adc_handle); ​
79 /* 设置DMA中断的优先级,以及使能DMA中断 */​
80 HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 3, 3);​
81 HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn);​
82​
83 adc_ch_conf.Channel = ADC_ADCX_CHY; /* 配置使用的ADC通道 */​
84 adc_ch_conf.Rank = ADC_REGULAR_RANK_1; /* 采样序列里的第1个 */​
85 /* 采样周期为810.5个时钟周期 */​
86 adc_ch_conf.SamplingTime = ADC_SAMPLETIME_810CYCLES_5; ​
87 adc_ch_conf.SingleDiff = ADC_SINGLE_ENDED ; /* 单端输入 */​
88 adc_ch_conf.OffsetNumber = ADC_OFFSET_NONE; /* 无偏移 */​
89 adc_ch_conf.Offset = 0; /* 无偏移的情况下,此参数忽略 */​
90 adc_ch_conf.OffsetRightShift = DISABLE; /* 禁止右移 */​
91 adc_ch_conf.OffsetSignedSaturation = DISABLE; /* 禁止有符号饱和 */​
92 HAL_ADC_ConfigChannel(&g_adc_dma_handle, &adc_ch_conf);/* 配置ADC通道 */​
93​
94 ADC_Enable(&g_adc_dma_handle); /* 使能ADC */​
95 }

该函数比较长,主要就分为两部分,ADC和DMA的配置。第20~52行和83~92行是ADC初始化相关的配置,我们来分析20~52行代码:

第16和17行,使能ADC和DMA的时钟;

第20~22行,设置ADC时钟源来自PER,其中PER时钟源默认使用HSI,即为64MHz

第24行,初始化GPIO,adc_gpio_init函数使用的是上一小节实验的代码

第28行,配置为2分频,所以最终ADC的时钟频率为32MHz;

第30行,配置ADC为非扫描模式,当有多个通道需要采集信号时必须开启扫描模式,此时ADC将会按设定的顺序轮流采集各通道信号,单通道转换不需要使用此功能,这里选择Dsabled;

第32行,配置是否使用低功耗自动延迟等待模式,当使能时,仅当一组内所有之前的数据已处理完毕时,才开始新的转换,适用于低频应用,该模式仅用于ADC的轮询模式,不可用于DMA以及中断,这里我们选择关闭

第33行,配置ADC关闭连续转换,在第52行又配置了连续转换模式。单次转换,转换一次后停止需要手动控制才重新启动转换而连续转换就不需要手动控制转换完成则自动继续进行下一轮转换最终我们使用的是连续转换

第35行,禁止不连续采样模式,所谓不连续,比如要转换的通道有1、2、5、7、8、9,那么第一次触发会进行通道 1 与通道 2,下次触发就是转换通道 5 与通道 7,这样不连续的转换,依次类推。这里我们选择禁用不连续的转换模式Disabled;

第42行,用于配置有新的数据溢出时,是覆盖写入还是丢弃新的数据,我们选择覆盖写入新的数据

第45行,规则通道的数据仅仅保存在DR寄存器里面。用于配置转换数据管理模式,后面的配置中,我们选择DMA单次模式传输ADC数据

第46和48行,初始化ADC和校准ADC;

第55~74行主要是DMA的初始化代码:

第55~58行,外设地址是参数par,存储器地址是参数mar,后面在main函数中我们调用此函数的时候,会设置外设地址是ADC的DR寄存器,存储器地址我们会定义一个数组;

第60行,DMA初始化,我们使用的是DMA1 Stream7,即DMA1的数据流7;

第64~66行,数据方向为外设到内存,所以配置外设非增量模式,存储器增量模式;

第68和70行,我们配置外设数据长度和存储器数据长度都是16位,即半字;

第71~74行,配置DMA请求为循环模式(Circular),中等优先级,不使用FIFO,然后调用HAL库完成DMA的初始化;

第76行,使能DMA传输完成中断;

第78行,将DMA与ADC联系起来;

第80和81行,设置DMA中断的优先级,以及使能DMA中断;

第83~92行,配置ADC通道的重要参数,其中,选择ADC1的通道19,采样周期选择

第94行,使能ADC,使用ADC的话,这行代码不能漏。

(2)使能一次ADC转换(采集)DMA传输函数

接下来我们看看使能ADC转换(采集)DMA传输函数,通过调用此函数来使能ADC转换和DMA传输:

/**​
* @brief使能一次ADC DMA传输 ​
* @param传输的次数​
* @retval无​
*/​
void adc_dma_enable(uint16_t ndtr)​
{​
dma_enable(ADC_ADCX_DMASx, ndtr); /* 重新使能DMA传输 */​
ADC_ADCX->CR |= 1 << 2; /* 启动规则转换通道 */​
}

(3)ADC 采集DMA传输中断服务函数

本节用到DMA传输完成中断,当DMA传输完成后,会执行ADC_ADCX_DMASx_IRQHandler函数

/**​
* @brief采集中断服务函数​
* @param无​
* @retval无​
*/​
void ADC_ADCX_DMASx_IRQHandler(void)​
{​
if (ADC_ADCX_DMASx_IS_TC())​
{​
g_adc_dma_sta = 1; /* 标记DMA传输完成 */​
ADC_ADCX_DMASx_CLR_TC(); /* 清除DMA1 数据流7 传输完成中断 */​
}​
}

以上代码是添加传输完成标志位g_adc_dma_staDMA传输完成后会执行中断服务函数ADC_ADCX_DMASx_IRQHandler。当传输完成时寄存器DMA_LISR的TCIF3被置1,表示流传输完成,我们通过__HAL_DMA_GET_FLAG(&hdma_adc1, DMA_FLAG_TCIF3_7)来判断TCIF是否置1了,如果是1,表示DMA已经传输完成指定数目的数据,将g_adc_dma_sta标志位置1,每次传输完成后可以进行读取转换值用于计算电压值。

这里DMA_FLAG_TCIF3_7表示DMA的通道3和通道7,又如DMA_FLAG_TCIF1_5就是指DMA的通道1和通道5,在stm32mp1xx_hal_dma.h文件中有定义。

dma.c文件的代码这里就不再赘述了,其和前面的DMA章节的实验代码类似。

3. main.c文件代码

1 #include "./SYSTEM/sys/sys.h"​
2 #include "./SYSTEM/delay/delay.h"​
3 #include "./SYSTEM/usart/usart.h"​
4 #include "./USMART/usmart.h"​
5 #include "./BSP/LED/led.h"​
6 #include "./BSP/BEEP/beep.h"​
7 #include "./BSP/KEY/key.h"​
8 #include "./BSP/ADC/adc.h"​
9 #define ADC_DMA_BUF_SIZE 100 /* ADC DMA采集 BUF大小 */​
10 uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]; /* ADC DMA BUF */​
11 /* DMA传输状态标志, 0,未完成; 1, 已完成 */​
12 extern uint8_t g_adc_dma_sta; ​
13 /**​
14 * @brief主函数​
15 * @param无​
16 * @retval无​
17 */​
18 int main(void)​
19 {​
20 uint16_t i;​
21 uint16_t adcx;​
22 uint32_t sum;​
23 float temp;​
24 HAL_Init(); /* 初始化HAL库 */​
25 /* 初始化M4内核时钟,209M */​
26 if(IS_ENGINEERING_BOOT_MODE())​
27 {​
28 sys_stm32_clock_init(34, 2, 2, 17, 6826);​
29 }​
30 usart_init(115200); /* 串口初始化为115200 */​
31 delay_init(209); /* 延时初始化 */​
32 led_init(); /* 初始化LED */​
33 /* 初始化ADC采集DMA转换 */ ​
34 adc_dma_init((uint32_t)&ADC1->DR, (uint32_t)&g_adc_dma_buf); ​
35 adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC采集DMA转换 */​
36 while (1)​
37 {​
38 if (g_adc_dma_sta == 1)​
39 { ​
40 /* 计算DMA 采集到的ADC数据的平均值 */​
41 sum = 0;​
42 for (i=0; i<100; i++) /* 累加 */​
43 {​
44 sum += g_adc_dma_buf[i];​
45 }​
46 adcx= sum / ADC_DMA_BUF_SIZE; /* 取平均值 */​
47 /* 获取计算后的带小数的实际电压值,比如3.1111 */ ​
48 temp = (float)adcx * (3.3 / 65536); ​
49 printf("ADC Value = %d, Voltage = %.3fV\r\n", adcx, temp);​
50 adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */​
51 }​
52 LED0_TOGGLE();​
53 delay_ms(100);​
54 }​
55 }

第34行,外设地址是ADC的DR寄存器,存储器地址是我们定义的数组g_adc_dma_buf[ADC_DMA_BUF_SIZE];

第35行,调用adc_dma_enable函数启动ADC采集DMA转换;

第38~51行,如果配置ADC的转换数据管理模式为DMA单次模式,那么DMA传输完ADC_DMA_BUF_SIZE次以后,DMA就停止了,所以会看到串口只打印一组数据,要想看到串口打印多组数据,则需要再执行第50行的代码;

第38行,通过标志位g_adc_dma_sta=1判断DMA传输已经完成,此时可以使用g_adc_dma_buf[ADC_DMA_BUF_SIZE里的数据进行计算了;

第49,使用g_adc_dma_buf[ADC_DMA_BUF_SIZE里的数据计算出电压值,并将每次转换值以及计算出的电压值打印出来;

第50行,此行非常重要!在DMA单次传输模式下,DMA传输完指定的数据后,DMA停止传输,而ADC还在不断采集数据,但后续采集的数据没有更新到g_adc_dma_buf[ADC_DMA_BUF_SIZE中。如果不加此行,那么串口UART4只打印一组就数据,后续打印的数据也还是前面ADC第一组转换得到的值,此值不再更新,那么必须加此行,目的是再次启动ADC请求DMA传输;

29.3.6 编译和测试

测试结果如下,数据在刷新,调节电位器可以发现数值再变化

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_39


图22.3.6. 1实验结果

如果想使用HAL_ADC_Start_DMA函数来完成此实验的朋友,也可以参考《【正点原子】STM32MP1 M4裸机CubeIDE开发指南》的本节实验,实验代码中,也就是通过HAL库的HAL_ADC_Start_DMA函数代替了本节实验的adc_dma_enable函数,不过,使用HAL_ADC_Start_DMA函数的话,注意此函数的形参2和形参3是uint32_t类型的,所以以上的外设数据长度和存储器数据长度最好选择Word,即32位。

29.4 多通道ADC采集(DMA读取)实验

本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\3、M4裸机驱动例程\库V1.2\实验18-3 ADC实验-多通道ADC采集(DMA读取)

29.4.1 扫描模式和不连续转换

本实验我们来学习使用常规多通道的连续转换模式,并且使用DMA读取ADC的数据。前面我们了解过ADC单通道的单次转换模式和连续转换模式。如果是多通道还需要设置扫描模式实际上如果设置的是多通道,在STM32CubeMX上就默认开启了扫描模式。

1. 扫描模式

扫描模式就是在每个组的每个通道上执行单次转换,扫描的通道从等级1到等级'n'排列。如果设置了CONT位为1(连续转换模式),转换不会在所选择组的最后一个通道上停止,而是再次从选择组的第一个通道开始再继续转换。如果使用STM32CubeMX生成初始化代码,可以在以下配置处开启扫描模式

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_40


图29.4.1. 1使能扫描模式

假设开启了扫描模式,且配置Rank1 Rank2 Rank3分别对应Channel 3 、Channel 2、Channel 1 通道,则转换一次通道顺序为Channel 3 、Channel 2 Channel 1

2. 不连续转换

在多通道扫描模式下,可以选择单次转换、连续转换和不连续转换(间断转换),连续转换和单次转换我们前面都有介绍过。下面我们看看不连续转换。如果使用STM32CubeMX生成初始化代码,如下图,Discontinuous Conversion Mode就是配置不连续转换模式如果开启了不连续转换模式则需要配置不连续转换数Number Of Discontinuous Conversions实际上就是配置寄存器ADC_CFGR的DISCNUM[2:0] 位的值:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_时钟周期_41


图22.4.1. 2开启不连续转换模式

不连续转换(间断转换)是指每一次触发信号可以执行一个短序列的n次转换,当以间断模式转换一个规则组时,转换序列结束后不自动从头开始,需要软件触发或者硬件触发才可以再继续转换。常规组和注入组都可以设置为不连续转换模式,该模式用于转换含有 n (n ≤ 8) 个转换的短序列(子组),可以通过设置ADC_CFGR寄存器的DISCNUM[2:0] 位来指定 n 的值。如果要使用不连续转换模式,这设置ADC_CFGR 寄存器的位DISCEN=1(注入组的是JDISCEN)。

举个例子说明使用不连续转换和不连续转换的情况:

当使用不连续转换(DISCEN=1),且设置DISCNUM[2:0] = 3,要转换的通道 = 1、2、3、6、7、8、9、10、11

  • 第一次触发:转换的通道为 1、2、3(每次转换时都生成 EOC 事件)。
  • 第二次触发:转换的通道为 6、7、8(每次转换时都生成 EOC 事件)。
  • 第三次触发:转换的通道为 9、10、11(每次转换时都生成 EOC 事件),并会在通道 11 转换完成后生成 EOS 事件。
  • 第四次触发:转换的通道为 1、2、3(每次转换时都生成 EOC 事件)。
  • ...

当不使用不连续转换(DISCEN=0),要转换的通道 = 1、2、3、6、7、8、9、10、11

  • 第一次触发:转换整个序列:通道 1、然后是通道 2、3、6、7、8、9、10 和 11。每次转换都会生成 EOC 事件,最后一次转换还会生成 EOS 事件。
  • 所有后续触发事件都将重启整个序列。

注意事项:

(1)STM32CubeMX下,如果设置了多个ADC转换通道(不是勾选的通道数量,是Number Of Conversion里配置的转换通道的数量),扫描模式就会自动自动开启,且无法关闭,如果只有1个转换通道,则扫描模式默认关闭,且无法开启。

(2)不能同时使能不连续模式和连续模式。如果同时使能两种模式(即 DISCEN=1 、 CONT=1 ),ADC 会认定连续模式已禁止并继续执行相关操作。

(3)如果开启间断模式,每次需要先使用HAL_ADC_Start或HAL_ADC_Start_IT或HAL_ADC_Start_DMA启动转换以后才可以启动下次转换如果开启连续模式,只需要使用一次HAL_ADC_Start或HAL_ADC_Start_IT或HAL_ADC_Start_DMA启动转换以后, ADC会马不停蹄的转换。这两种模式下注意的是,如果要使用DMA传输,如果使用HAL_ADC_Start_DMA要注意DMA设置的是DMA单次模式还是DMA循环模式,如果是DMA单次模式,要想DMA继续传输,则得再次开启DMA。

29.4.2 ADC寄存器

本实验我们很多的设置和单通道ADC采集(DMA读取)实验是一样的,所以下面介绍寄存器的时候我们不会继续全部都介绍,而是针对性选择与单通道ADC采集(DMA读取)实验不同设置的ADC_SQRy寄存器进行介绍,其他的配置基本一样的。另外我们用到DMA读取数据,配置上和单通道ADC采集(DMA读取)实验是一样的。

ADC常规序列寄存器有四个(ADC_SQR1~ ADC_SQR4),具体怎么配置,需要看我们用多少个通道,比如本实验我们使用6个通道同时采集ADC数据,具体配置如下:

ADC常规序列寄存器1(ADC_SQR1

ADC常规序列寄存器1描述如下图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_寄存器_42


图29.4.2. 1寄存器

规则通道组的转换顺序要在规则序列寄存器ADC_SQR1~ ADC_SQR4中设置,转换次序SQ1~SQ4在ADC_SQR1中设置,SQ5~SQ9在ADC_SQR2中设置以此类推,常规通道有16个,SQ15和SQ16在ADC_SQR4中设置。以寄存器ADC_SQR1为例

SQ4[4:0] 位:常规序列中的第四次转换;

SQ3[4:0] 位:常规序列中的第三次转换;

SQ2[4:0] 位:常规序列中的第二次转换;

SQ1[4:0] 位:常规序列中的第一次转换;

L[3:0] 位:常规通道序列长度。

多通道转换顺序的设置方法:

(1)首先确认使用的通道数,由L[3:0]位设置,该位用于存储常规序列的长度,取值范围:0~15,表示常规序列长度为:1~16。如果使用了6个通道,所以设置这几个位的值为5即可。

(2)其次是设置常规序列的转换顺序,SQ1~SQ4表示常规序列中的第1~4个序列的转换通(0~19),每个常规序列的转换通道,可以由SQx(x=1~16)指定,比如我们设置:SQ1[4:0]=14,就表示常规序列1的转换通道为14(ADC1/2/_CH14)。SQ5~SQ16由寄存器ADC_SQR2~4控制。

SQ1~SQ16的用法,我们举个例子看看是怎么设置的:SQ1为赋值为14SQ2为赋值为15SQ3为赋值为16SQ4为赋值为17SQ5为赋值为18SQ6为赋值为19,即常规序列1到6分别对应的通道是14到19。其中SQ5 SQ6位是在ADC_SQR2寄存器中配置。

29.4.3 硬件设计

1. 例程功能

ADC和DMA属于STM32MP157内部资源,实际上我们只需要软件设置就可以正常工作,不过我们需要使用杜邦线将要测试的外部电压到ADC1通道上,使用ADC1采集(DMA读取)通道2\10\16\18\19的输入电压,并通过UART4打印出来对应的ADC转换值以及换算成电压后的电压值,可以使用杜邦线连接PF11/PC0/PA0/PA4/PA5到你想测量的输入电压源(0~3.3V)。程序通过LED0闪烁来提示程序运行。

本实验要用到比较多的输入电压,共5路,刚好开发板上有1 组 3.3V 电源供应接口JP7,JP7排针有3路输出3.3V,另外3路接地(0V),我们可以使用这个排针来测试,接入到被测通道中,也可以使用数字信号发生器来接入一个电压。注意的是,不要接错开发板上的5V 电源供应接口,否则烧坏IO口甚至整个主控芯片

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_43


图29.4.3. 1开发板的电源输出引脚

2. 硬件资源

1)LED0灯

2)串口4

3)ADC1输入通道:

通道2- PF11、通道10-PC0、通道16-PA0、通道18-PA4、通道19-PA5

LED0

UART4_TX

UART4_RX

UART4_RX

通道2

通道10

通道16

通道18

通道19

PI0

PG11

PB2

PB2

PF11

PC0

PA0

PA4

PA5

表29.4.3. 1硬件资源

5)DMA(DMA2 数据流7,也可以配置为其它数据流)

3. 原理图

以上使用的ADC1的5个GPIO的复用关系可以通过参考手册上看出来,也可以通过原理图来查询,由于引脚比较多,这里就不贴出原理图了。

29.4.4 程序设计

1. adc.h文件代码

#define ADC_ADCX_DMASx DMA1_Stream7 ​
#define ADC_ADCX_DMASx_REQ DMA_REQUEST_ADC1 /* ADC1_DMA请求源 */​
#define ADC_ADCX_DMASx_IRQn DMA1_Stream7_IRQn ​
#define ADC_ADCX_DMASx_IRQHandler DMA1_Stream7_IRQHandler ​
/* 判断DMA1 Stream7传输完成标志, 这是一个假函数形式,不能当函数使用, 只能用在if等语句里面 */​
#define ADC_ADCX_DMASx_IS_TC() ( __HAL_DMA_GET_FLAG(&g_dma_nch_adc_handle, \ DMA_FLAG_TCIF3_7) )​
/* 清除DMA1 Stream7传输完成标志 */ ​
#define ADC_ADCX_DMASx_CLR_TC() do{ __HAL_DMA_CLEAR_FLAG(&g_dma_nch_adc_handle,\ DMA_FLAG_TCIF3_7); }while(0) ​
void adc_nch_dma_init(uint32_t par, uint32_t mar); /* ADC多通道 DMA采集初始化 */​
void adc_nch_dma_gpio_init(void);

以上代码中,在adc.c文件中,也同样会用到__HAL_DMA_GET_FLAG和__HAL_DMA_CLEAR_FLAG这两个函数

2. adc.c文件代码

(1)多通道ADC采集(DMA读取)初始化代码

1 ADC_HandleTypeDef g_adc_nch_dma_handle; /* 与DMA关联的ADC句柄 */​
2 DMA_HandleTypeDef g_dma_nch_adc_handle; /* 与ADC关联的DMA句柄 */​
3 /**​
4 * @brief通道(6通道) DMA读取 初始化函数​
5 * @note本函数还是使用adc_init对ADC进行大部分配置,有差异的地方再单独配置​
6另外,由于本函数用到了6个通道, 宏定义会比较多内容, 因此,本函数就​
7 不采用宏定义的方式来修改通道了,直接在本函数里面修改, ​
8 这里我们默认使用PA0~PA5这6个通道.​
9 *​
10 注意: 本函数还是使用 ADC_ADCX(默认=ADC1) 和 ​
11 默认=DMA1_Stream7) 及其相关定义​
12 不要乱修改adc.h里面的这两部分内容, 必须在理解原理​
13 的基础上进行修改, 否则可能导致无法正常使用.​
14 *​
15 * @param数据流,DMA1_Stream0~7/DMA2_Stream0~7​
16 * @param通道选择​
17 * @param外设地址​
18 * @param存储器地址 ​
19 * @retval无​
20 */​
21 void adc_nch_dma_init(uint32_t par, uint32_t mar)​
22 {​
23 RCC_PeriphCLKInitTypeDef adc_periphclk_init_struct;​
24 ADC_ChannelConfTypeDef adc_ch_conf;​
25 ​
26 ADC_ADCX_CHY_CLK_ENABLE(); /* 使能ADC1/2时钟 */​
27 __HAL_RCC_DMAMUX_CLK_ENABLE(); /* 使能DMAMUX时钟 */​
28 ​
29 /* 设置ADC时钟源=PER=64MHz,后面分为2分频,即ADC时钟源为32MHz */​
30 adc_periphclk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;​
31 adc_periphclk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PER;​
32 HAL_RCCEx_PeriphCLKConfig(&adc_periphclk_init_struct);​
33 ​
34 adc_nch_dma_gpio_init(); /* GPIO初始化 */ ​
35 ​
36 g_adc_nch_dma_handle.Instance = ADC_ADCX; /* ADC1 */​
37 /* 输入时钟2分频,即adc_ker_ck= PER/2=32MHz */​
38 g_adc_nch_dma_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2; ​
39 g_adc_nch_dma_handle.Init.Resolution = ADC_RESOLUTION_16B; /* 16位模式 */​
40 g_adc_nch_dma_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; /* 非扫描模式 */​
41 g_adc_nch_dma_handle.Init.EOCSelection=ADC_EOC_SINGLE_CONV; /*关闭EOC中断*/​
42 g_adc_nch_dma_handle.Init.LowPowerAutoWait = DISABLE; /* 自动低功耗关闭 */​
43 g_adc_nch_dma_handle.Init.ContinuousConvMode = DISABLE; /* 关闭连续转换 */​
44 g_adc_nch_dma_handle.Init.NbrOfConversion = 5; /* 使用了5个转换通道​
45 g_adc_nch_dma_handle.Init.DiscontinuousConvMode=DISABLE; /*禁止不连续采样模*/​
46 /* 禁止不连续模式后,此参数忽略,不连续采样通道数为0 */​
47 g_adc_nch_dma_handle.Init.NbrOfDiscConversion = 0; ​
48 g_adc_nch_dma_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;/* 采用软件触发 */​
49 /* 采用软件触发的话,此位忽略 */​
50 g_adc_nch_dma_handle.Init.ExternalTrigConvEdge = \ ADC_EXTERNALTRIGCONVEDGE_NONE;​
51 /* 有新的数据就直接覆盖掉旧数据 */ ​
52 g_adc_nch_dma_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN; ​
53 g_adc_nch_dma_handle.Init.OversamplingMode = DISABLE; /* 过采样关闭 */​
54 /* 规则通道的数据仅仅保存在DR寄存器里面 */​
55 g_adc_nch_dma_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR; ​
56 HAL_ADC_Init(&g_adc_nch_dma_handle); /* 初始化 */ ​
57 /* ADC校准 */​
58 HAL_ADCEx_Calibration_Start(&g_adc_nch_dma_handle, ADC_CALIB_OFFSET, \ ADC_SINGLE_ENDED); ​
59 ​
60 /* 配置ADC连续转换, DMA单次传输ADC数据 */​
61 ADC_ADCX->CFGR |= 1 << 0; /* DMNGT[1:0] = 01, DMA单次传输ADC数据 */​
62 ADC_ADCX->CFGR |= 1 << 13; /* CONT = 1, 连续转换模式 */​
63 ​
64 ADC_ADCX->SQR1 = 0; /* SQR1清零 */​
65 ADC_ADCX->SQR1 |= 5 << 0;/* L[3:0]=5,6个转换在规则序列中 也就是转换规则序列1~6 */​
66 ​
67 /* DMA相关配置 */​
68 dma_mux_init(ADC_ADCX_DMASx, ADC_ADCX_DMASx_REQ);/* 初始化DMA 请求复用器 */ ​
69 ADC_ADCX_DMASx->PAR = par; /* DMA外设地址 */​
70 ADC_ADCX_DMASx->M0AR = mar; /* DMA 存储器0地址 */​
71 ADC_ADCX_DMASx->NDTR = 0; /* DMA 存储器0地址 */​
72 ​
73 g_dma_nch_adc_handle.Instance = ADC_ADCX_DMASx;/* 使用DMA1 Stream7 */​
74 /* 请求类型采用DMA_REQUEST_ADC1 */​
75 g_dma_nch_adc_handle.Init.Request = ADC_ADCX_DMASx_REQ;​
76 /* 传外设到存储器模式 */ ​
77 g_dma_nch_adc_handle.Init.Direction = DMA_PERIPH_TO_MEMORY; ​
78 g_dma_nch_adc_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */​
79 g_dma_nch_adc_handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器增量模式 */​
80 /* 外设数据长度:16位 */​
81 g_dma_nch_adc_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; ​
82 /* 存储器数据长度:16位 */ ​
83 g_dma_nch_adc_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; ​
84 g_dma_nch_adc_handle.Init.Mode = DMA_CIRCULAR; /* 循环模式 */​
85 g_dma_nch_adc_handle.Init.Priority = DMA_PRIORITY_MEDIUM; /* 中等优先级 */​
86 g_dma_nch_adc_handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; /* 禁止FIFO */​
87 HAL_DMA_Init(&g_dma_nch_adc_handle); /* 初始化DMA */​
88 ​
89 ADC_ADCX_DMASx->CR |= 1 << 4; /* TCIE = 1, DMA传输完成中断使能 */​
90 /* 将DMA与adc联系起来 */ ​
91 __HAL_LINKDMA(&g_adc_nch_dma_handle, DMA_Handle, g_dma_nch_adc_handle); ​
92 /* 设置DMA中断的优先级,以及使能DMA中断 */​
93 HAL_NVIC_SetPriority(ADC_ADCX_DMASx_IRQn, 3, 3);​
94 HAL_NVIC_EnableIRQ(ADC_ADCX_DMASx_IRQn);​
95 ​
96 adc_ch_conf.Channel = ADC_CHANNEL_2; /* 配置使用的ADC通道 2 */​
97 adc_ch_conf.Rank = ADC_REGULAR_RANK_1; /* 采样序列里的第1个 */​
98 /* 采样周期为810.5个时钟周期 */​
99 adc_ch_conf.SamplingTime = ADC_SAMPLETIME_810CYCLES_5; ​
100 adc_ch_conf.SingleDiff = ADC_SINGLE_ENDED ; /* 单端输入 */​
101 adc_ch_conf.OffsetNumber = ADC_OFFSET_NONE; /* 选择无偏移通道 */​
102 adc_ch_conf.Offset = 0; /* 无偏移的情况下,此参数忽略 */​
103 adc_ch_conf.OffsetRightShift = DISABLE; /* 禁止右移 */​
104 adc_ch_conf.OffsetSignedSaturation = DISABLE; /* 禁止有符号饱和 */​
105 HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle,&adc_ch_conf);/*配置ADC通道2 */​
106 ​
107 adc_ch_conf.Channel = ADC_CHANNEL_10; /* 配置使用的ADC通道 10 */​
108 adc_ch_conf.Rank = ADC_REGULAR_RANK_2; /* 采样序列里的第2个 */​
109 HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);/* 配置ADC通道10 */​
110​
111 adc_ch_conf.Channel = ADC_CHANNEL_16;/* 配置使用的ADC通道 16 */​
112 adc_ch_conf.Rank = ADC_REGULAR_RANK_3;/* 采样序列里的第3个 */​
113 HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);/* 配置ADC通道16 */​
114​
115 adc_ch_conf.Channel = ADC_CHANNEL_18;/* 配置使用的ADC通道 18 */​
116 adc_ch_conf.Rank = ADC_REGULAR_RANK_4;/* 采样序列里的第4个 */​
117 HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);/* 配置ADC通道18 */​
118​
119 adc_ch_conf.Channel = ADC_CHANNEL_19;/* 配置使用的ADC通道 19 */​
120 adc_ch_conf.Rank = ADC_REGULAR_RANK_5;/* 采样序列里的第5个 */​
121 HAL_ADC_ConfigChannel(&g_adc_nch_dma_handle, &adc_ch_conf);/* 配置ADC通道19 */​
122​
123 ADC_Enable(&g_adc_nch_dma_handle); /* 使能ADC */​
124​
125 }

以上代码的大部分我们在上一小节的实验已经分析过,这里重点讲解以下部分:

第44行,本实验使用了5个通道,所以此处配置为5;

第48行,我们使用的还是软件触发,ADC在接收到到触发信号后才开始进行模数转换,触发源可以是定时器触发、外部中断触发等硬件触发、也可以是软件控制触发,这里选择软件触发,工程中需要我们添加启动ADC转换相关的代码

第64和65行,因为本实验使用的是5个通道,所以配置常规通道序列长度为5;

第96~121行,配置ADC的通道2、通道10、通道16、通道18和通道19的重要参数,其中采样周期选择采样序列依次从1到5。Rank配置的是模拟信号采集及转换的次序,总共有5组,我们设置通道2\10\16\18\19按照先后顺序转换,最先转换的是通道2,所以设置序列Rank1处是Channel2,其它通道先后顺序依次类推。

第34行,调用adc_nch_dma_gpio_init函数完成多通道ADC的gpio引脚初始化,该函数如下:

/**​
* @brief多通道ADC的gpio初始化函数​
* @param无​
* @note此函数会被adc_nch_dma_init()调用​
* @note、PA4-ADC_CHANNEL_18、PA5-ADC_CHANNEL_19​
、PC0-ADC_CHANNEL_10​
* @retval无​
*/​
void adc_nch_dma_gpio_init(void)​
{​
GPIO_InitTypeDef gpio_init_struct;​
/* 开启GPIOA、GPIOF和GPIOC时钟 */​
__HAL_RCC_GPIOA_CLK_ENABLE(); ​
__HAL_RCC_GPIOF_CLK_ENABLE(); ​
__HAL_RCC_GPIOC_CLK_ENABLE(); ​
/* GPIOA0、4和5 */​
gpio_init_struct.Pin = GPIO_PIN_0 | GPIO_PIN_4 | GPIO_PIN_5; ​
gpio_init_struct.Mode = GPIO_MODE_ANALOG; /* 模拟模式 */​
gpio_init_struct.Pull = GPIO_NOPULL; /* 不带上下拉 */​
HAL_GPIO_Init(GPIOA, &gpio_init_struct); /* 初始化GPIOA */​

gpio_init_struct.Pin = GPIO_PIN_11; /* GPIOF11 */​
HAL_GPIO_Init(GPIOF, &gpio_init_struct); /* 初始化GPIOF */​

gpio_init_struct.Pin = GPIO_PIN_0; /* GPIOF11 */​
HAL_GPIO_Init(GPIOC, &gpio_init_struct); /* 初始化GPIOC */​
}

该函数主要完成了5个通道的引脚初始化。

(2)ADC转换 DMA传输中断服务函数

下面是ADC转换 DMA传输中断服务函数,和上一小节的实验一样,本节实验使用到中断。DMA传输完成后会执行中断服务函数ADC_ADCX_DMASx_IRQHandler。当传输完成时寄存器DMA_LISR的TCIF3被置1,表示流传输完成,我们通过__HAL_DMA_GET_FLAG(&g_dma_nch_adc_handle, DMA_FLAG_TCIF3_7)来判断TCIF是否置1了,如果是1,表示DMA已经传输完成指定数目的数据,将g_adc_dma_sta标志位置1,中断服务函数中记得调用__HAL_DMA_CLEAR_FLAG(&g_dma_nch_adc_handle, DMA_FLAG_TCIF3_7)清除中断标志。每次传输完成后可以进行读取转换值用于计算电压值。

/**​
* @brief转换DMA传输中断服务函数​
* @param无​
* @retval无​
*/​
void ADC_ADCX_DMASx_IRQHandler(void)​
{​
if (ADC_ADCX_DMASx_IS_TC())​
{​
g_adc_dma_sta = 1; /* 标记DMA传输完成 */​
ADC_ADCX_DMASx_CLR_TC(); /* 清除DMA1 数据流7 传输完成中断 */​
}​
}

3. main.c文件代码

1 #include "./SYSTEM/sys/sys.h"​
2 #include "./SYSTEM/delay/delay.h"​
3 #include "./SYSTEM/usart/usart.h"​
4 #include "./USMART/usmart.h"​
5 #include "./BSP/LED/led.h"​
6 #include "./BSP/BEEP/beep.h"​
7 #include "./BSP/KEY/key.h"​
8 #include "./BSP/ADC/adc.h"​
9 ​
10 #define ADC_DMA_BUF_SIZE 5 /* ADC DMA采集 BUF大小, 应等于ADC通道数的整数倍 */ ​
11 uint16_t g_adc_dma_buf[ADC_DMA_BUF_SIZE];/* ADC DMA BUF */​
12 extern uint8_t g_adc_dma_sta; /* DMA传输状态标志, 0,未完成; 1, 已完成 */​
13 /**​
14 * @brief主函数​
15 * @param无​
16 * @retval无​
17 */​
18 int main(void)​
19 {​
20 HAL_Init(); /* 初始化HAL库 */​
21 /* 初始化M4内核时钟,209M */​
22 if(IS_ENGINEERING_BOOT_MODE())​
23 {​
24 sys_stm32_clock_init(34, 2, 2, 17, 6826);​
25 }​
26 usart_init(115200); /* 串口初始化为115200 */​
27 delay_init(209); /* 延时初始化 */​
28 led_init(); /* 初始化LED */​
29 /* 初始化ADC DMA采集 */ ​
30 adc_nch_dma_init((uint32_t)&ADC1->DR, (uint32_t)&g_adc_dma_buf); ​
31 adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动ADC DMA采集 */​
32 printf("ADC Start\r\n");​
33 while (1)​
34 { ​
35 if (g_adc_dma_sta == 1)​
36 {​
37 /* 循环显示通道14~通道19的结果 */​
38 printf("ADC Value_ch2 = %d Voltage =%.3f \r\n", \ g_adc_dma_buf[0],(float)g_adc_dma_buf[0] * (3.3 / 65536));​
39 printf("ADC Value_ch10 = %d Voltage=%.3f \r\n", \ g_adc_dma_buf[1],(float)g_adc_dma_buf[1] * (3.3 / 65536));​
40 printf("ADC Value_ch16 = %d Voltage =%.3f \r\n", \ g_adc_dma_buf[2],(float)g_adc_dma_buf[2] * (3.3 / 65536));​
41 printf("ADC Value_ch18 = %d Voltage =%.3f \r\n", \ g_adc_dma_buf[3],(float)g_adc_dma_buf[3] * (3.3 / 65536));​
42 printf("ADC Value_ch19 = %d Voltage =%.3f \r\n", \ g_adc_dma_buf[4],(float)g_adc_dma_buf[4] * (3.3 / 65536));​
43 g_adc_dma_sta = 0; /* 清除DMA采集完成状态标志 */​
44 adc_dma_enable(ADC_DMA_BUF_SIZE); /* 启动下一次ADC DMA采集 */​
45 }​
46 LED0_TOGGLE(); /* LED0翻转 */​
47 delay_ms(2000); /* 延时2s */​
48 }​
49 }

以上代码和上一章节的实验一样,我们使用adc_dma_enable函数来启动ADC转换,同时ADC会请求DMA进行传输。

如果标志位g_adc_dma_sta为1,说明DMA传输完了1组数据(这组数据有5个),我们定义一个Buffer当做DMA传输的目标地址g_adc_dma_buf[ADC_DMA_BUF_SIZE]。我们采集5个通道的转换值,通道2、10、16、18和19的转换值依次保存在g_adc_dma_buf[0]g_adc_dma_buf[1]g_adc_dma_buf[2]g_adc_dma_buf[3]和g_adc_dma_buf[4]中,我们分别读取每个的值,然后再计算对应的电压值。

注意第44行的代码,如果要连续采集新的数据,这行代码就必须添加上。除了可以使用adc_dma_enable函数来启动ADC转换和DMA传输以外,还可以使用HAL_ADC_Start_DMA函数来实现

29.4.5 编译和测试

本实验通道2、通道10用杜邦线接的开发板JP7上的3.3V引脚,通道16和通道18接的JP7的GND,通道19接的电位器,通过调节电位器来输出0~3.3V的电压。

运行后,LED0每隔2s的时间闪烁,同时串口打印ADC的5个通道的转换结果,测试结果如下,每组数据都会不一样,数据在刷新:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_44


图29.4.5. 1实验结果

29.5 单通道ADC过采样(26位分辨率)实验

本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\3、M4裸机驱动例程\库V1.2\实验18-4 ADC实验-单通道ADC过采样(26位分辨率)

29.5.1 STM32MP157过采样

​​信号处理​​中,过采样(Oversampling)是指采样频率以高于信号最高频率的两倍来采样,这样就可以从采样信号中尽量恢复出原始信号。

本实验我们来学习使用常规单通道的连续模式的ADC过采样(26位分辨率),在过采样模式下,大部分 ADC 工作模式都会保留。过采样是在硬件资源不够的情况下的一种资源换精度的方法,使用过采样技术,保留了输入信号的较完整信息,降低噪声,并提高了采样子系统的精度。如果频率足够高,那么可以获得无限位精度。

STM32MP157的ADC可达16位的转换精度,使用过采样技术可使得ADC达到26位转换精度。其过采样单元会进行数据预处理,可以减轻 CPU 的负担,过采样单元还能处理多个转换,并计算多个转换结果的平均值,过采样率N和除法系数M可以调整:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_时钟周期_45


29.5.1. 1过采样结算公式

以上参数中描述如下:

  • 过采样率 N 通过ADC_CFGR2寄存器中的OSR[9:0]位进行定义,范围为2x到1024x;
  • 分频系数M 通过向右移位来实现(最多可移10 位),并且通过 ADC_CFGR2 寄存器中的OVSS[3:0] 位定义;
  • 求和单元可得出多达 26 位(1024 x 16 位结果)的结果,结果可左移或右移。如果选择右移,则会使用移位后剩下的有效位四舍五入为最接近的数值,然后将得到的这移位后的结果传输到ADC_DR 数据寄存器中,此值就是得到的转换值。如下图是移位10位后的结果图,最后的第0~15位就是我们要读取转换值的有效位:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_时钟周期_46


29.5.1. 2采用10位右移和四舍五入进行过采样得到的16位结果

STM32CubeMX上最大可以设置右移11位。设置移位的时候,要注意计算电压时的公式,例如,如果设置右移10位,最后的计算结果中需要3.3/216,如果不移位,最后的计算结果需要3.3/226,如果右移4位,最后的计算结果需要3.3/222。本节实验我们设置不移位,所以后续的计算中要除以226

29.5.2 ADC寄存器

本实验我们很多的设置和单通道ADC采集实验是一样的,所以下面介绍寄存器的时候我们不会继续全部都介绍,而是针对性选择与单通道ADC采集实验不同设置的ADC_CFGR2寄存器进行介绍,其他的配置基本一样的

  • ADx配置寄存器2(ADC_CFGR2)

ADC配置寄存器2描述如下图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_47


29.5.2. 1寄存器

OSR[9:0]位,用于设置ADC的过采样率。OSR[9:0]=0~1023,表示1x~1024x过采样(1x表示不进行过采样)。本实验,我们使用过采样,并且分辨率调到最大,所以设置OSR[9:0] = 1023。

ROVSM位,用于设置常规过采样模式,默认为0即可,即连续模式。

TROVS位,用于设置已触发常规过采样,默认为0即可,即会在触发后连续完成某一通道的所有过采样转换。

OVSS[3:0]位,用于设置过采样右移。

ROVSE位,常规过采样使能位,置1使能常规过采样。

29.5.3 硬件设计

1. 例程功能

使用ADC1过采样(26位分辨率)采集通道19(PA5)上面的电压,并通过UART4打印出来。实验中LED0闪烁,提示程序运行。

开发板上有引出PA5引脚,先使用跳线帽将JP2排针的ADC1和电位器的RP_AD连接,这样PA5就连接到电位器VR1上了,电位器上接的是3.3V,用户可以通过调节电位器的旋钮改变接入到PA5上的电压值为0~3.3V(实际上就是通过改变电阻来改变电压)。实验前要记住检查跳线帽是否有接好:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_48


29.5.3. 1硬件部分

开发板上有1 组 3.3V 电源供应接口JP7,JP7排针有3路输出3.3V,另外3路接地(0V),我们也可以使用这个排针来测试,接入到被测通道中。注意的是,千万不要接错旁边的JP8排针引出的5V引脚,否则烧坏IO口甚至整个主控芯片。

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_时钟周期_49


29.5.3. 2开发板的电源输出引脚

2. 硬件资源

1)LED灯:LED0

2)串口4

3)ADC1的通道19引脚(PA5)

4)DMA(DMA2 数据流7 ,也可以配置为其它数据流)

LED0

UART4_TX

UART4_RX

ADC1_INP19

PI0

PG11

PB2

PA5

29.5.3. 1硬件资源

3. 原理图

DMA属于STM32MP157内部资源,通过软件设置好就可以了。ADC属于STM32MP157的内部资源,实际上我们只需要软件设置就可以正常工作。如果使用ADC1测试其它外接入的电压,只需要一根杜邦线将要测试的电压引入PA5引脚即可,要注意接入的电压范围不能超过3.3V,否则可能烧坏我们的ADC,甚至是整个主控芯片。

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_50


29.5.3. 3原理图部分

29.5.4 程序设计

1. adc.c文件代码

adc.h文件代码和上一章节的一样基本无变化下面我们来看看adc.c文件的代码:

1 /**​
2 * @brief过采样 初始化函数​
3 * @note本函数还是使用adc_init对ADC进行大部分配置,有差异的地方再单独配置​
4 本函数可以控制ADC过采样范围从1x ~ 1024x, 得到最高26位分辨率的AD转换结果​
5 * @param过采样倍率, 0 ~ 1023, 表示:1x ~ 1024x过采样倍率​
6 * @param过采样右移位数, 0~11, 表示右移0位~11位.​
7 * @note过采样后, ADC的转换时间相应的会慢 osr倍.​
8 * @retval无​
9 */​
10 void adc_oversample_init(uint32_t osr, uint32_t ovss)​
11 {​
12 RCC_PeriphCLKInitTypeDef adc_periphclk_init_struct;​
13 ADC_ADCX_CHY_CLK_ENABLE(); /* 使能ADC1/2时钟 */​
14 ​
15 /* 设置ADC时钟源=PER=64MHz,后面分为2分频,即ADC时钟源为32MHz */​
16 adc_periphclk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;​
17 adc_periphclk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PER;​
18 HAL_RCCEx_PeriphCLKConfig(&adc_periphclk_init_struct);​
19​
20 adc_gpio_init(); /* GPIO初始化 */​
21​
22 g_adc_handle.Instance = ADC_ADCX; /* ADC1 */​
23 /* 输入时钟2分频,即adc_ker_ck= PER/2=32MHz */​
24 g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2; ​
25 g_adc_handle.Init.Resolution = ADC_RESOLUTION_16B; /* 16位模式 */​
26 g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; /* 非扫描模式 */​
27 g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;/* 关闭EOC中断 */​
28 g_adc_handle.Init.LowPowerAutoWait = DISABLE; /* 自动低功耗关闭 */​
29 g_adc_handle.Init.ContinuousConvMode = DISABLE; /* 关闭连续转换 */​
30 g_adc_handle.Init.NbrOfConversion = 1; /* 使用了1个转换通道 */​
31 g_adc_handle.Init.DiscontinuousConvMode = DISABLE; /* 禁止不连续采样模式 */​
32 /* 禁止不连续模式后,此参数忽略,不连续采样通道数为0 */​
33 g_adc_handle.Init.NbrOfDiscConversion = 0; ​
34 g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;/* 软件触发 */​
35 /* 采用软件触发的话,此位忽略 */​
36 g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;​
37 /* 有新的数据的死后直接覆盖掉旧数据 */​
38 g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;​
39 /* 规则通道的数据仅仅保存在DR寄存器里面 */​
40 g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR; ​
41 g_adc_handle.Init.OversamplingMode = ENABLE; /* 开启过采样 */​
42 g_adc_handle.Init.Oversampling.Ratio = osr+1; /* osr+1倍过采样 */​
43 g_adc_handle.Init.Oversampling.RightBitShift = ovss;/* 数据右移ovss bit */​
44 /* 触发后连续完成每个通道的所有过采样转换 */​
45 g_adc_handle.Init.Oversampling.TriggeredMode = \ ADC_TRIGGEREDMODE_SINGLE_TRIGGER;​
46 /* 若触发注入转换,过采样会暂时停止,并会在注入序列完成后继续,注入序列过程中会保留过采样缓冲区*/​
47 g_adc_handle.Init.Oversampling.OversamplingStopReset = \ ADC_REGOVERSAMPLING_CONTINUED_MODE; ​
48 HAL_ADC_Init(&g_adc_handle); /* 初始化ADC */​
49 /* ADC校准 */​
50 HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_CALIB_OFFSET, \ ADC_SINGLE_ENDED); ​
51 }

以上的代码大部分我们前面已经分析过,下面我们来分析部分代码:

第30行,本节实验只使用1个通道,所以配置为1;

第41行,本节实验使用过采样,所以要开启过采样;

第42行,配置为osr+1倍过采样,也就是配置ADC的过采样率,即ADC_CFGR2寄存器的OSR[9:0]位,我们在main.c文件中调用此函数时会设置参数osr为最大值,为1024-1

第43行,配置数据右移ovss位,也就是设置寄存器ADC_CFGR2的OVSS[3:0]位,在main.c文件中调用此函数时,我们设置不移位,也就是26位分辨率,最后计算电压的时候是3.3/226

第45行,用于配置触发常规过采样的转换模式如果选择了连续过采样,则只能配置为一次触发所有过采样的转换这里我们配置为一次触发所有过采样的转换,即每次触发时,所有的过采样都进行转换;

第47行,该模式用于实现当触发注入转换时,过采样会暂时停止,当注入序列完成后继续执行原来的过采样;

第48行,初始化ADC;

第50行,ADC校准,如果不校准,数据会不准确。

2. main.c文件代码

main.c文件的代码如下:

#include "./SYSTEM/sys/sys.h"​
#include "./SYSTEM/delay/delay.h"​
#include "./SYSTEM/usart/usart.h"​
#include "./USMART/usmart.h"​
#include "./BSP/LED/led.h"​
#include "./BSP/BEEP/beep.h"​
#include "./BSP/KEY/key.h"​
#include "./BSP/ADC/adc.h"​
/**​
* @brief主函数​
* @param无​
* @retval无​
*/​
int main(void)​
{​
uint32_t adcx;​
float temp;​

HAL_Init(); /* 初始化HAL库*/​
/* 初始化M4内核时钟,209M */​
if(IS_ENGINEERING_BOOT_MODE())​
{​
sys_stm32_clock_init(34, 2, 2, 17, 6826);​
}​
usart_init(115200); /* 串口初始化为115200 */​
delay_init(209); /* 延时初始化 */​
led_init(); /* 初始化LED */​
key_init(); /* 初始化按键 */​
/* ​
初始化ADC, 1024倍过采样, 不移位; ​
位ADC分辨率最大值为:67108864, 实际上由于分辨率太高 ,低位值已经不准确;​
一般我们可以设置 ovss=4, 缩小16倍, 即22位分辨率, 低位值会相对稳定一些;​
这里我们为了演示26位过采样ADC转换效果, 把分辨率调到最大, 24位,并且不移位。 ​
*/​
adc_oversample_init(1024 - 1, ADC_RIGHTBITSHIFT_NONE);​
while (1)​
{ ​
adcx = adc_get_result_average(ADC_ADCX_CHY, 10); /* 获取转换值,10次取平均 */​
temp = (float)adcx * (3.3 / 67108864); /* 转换电压值 */​
printf("ADC Value = %d, Voltage = %.3fV\r\n", adcx, temp);​

LED0_TOGGLE(); /* LED0翻转 */​
delay_ms(2000); /* 延时2秒 */​
}​
}

以上代码中,调用adc_oversample_init(1024 - 1, ADC_RIGHTBITSHIFT_NONE);函数设置了ADC为1024倍过采样,数据不移位也就是26位分辨率的ADC,最后计算电压的时候是3.3/67108864

29.5.5 编译和测试

我们在PA5上接3.3V的电压,测试结果如下:

《STM32MP1 M4裸机HAL库开发指南》第二十九章 ADC实验_采样率_51


29.5.5. 1运行结果


举报

相关推荐

0 条评论