你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32MP1 M4裸机CubeIDE开发指南——ADC实验

[复制链接]
STMCU小助手 发布时间:2022-9-26 22:21
ADC实验
! G" I% x" B3 k2 S2 C, P1 {本章,我们将介绍STM32MP157的ADC(Analog-to-digital converters,模数转换器)功能。我们通过四个实验来学习ADC,分别是单通道ADC采集实验、单通道ADC采集(DMA读取)实验、多通道ADC采集(DMA读取)实验和单通道ADC过采样(26位分辨率)实验。
( ^: X- H0 e0 O8 C. {* R  Y+ ?& Z8 C1 ^4 |
22.1 ADC简介/ X# f. R2 f6 g
ADC即模拟数字转换器(Analog-to-digital converter),用于将连续变化的模拟信号转换为离散的数字信号。
( j1 n$ c& D* f, {" ~2 u8 t. T真实世界中的模拟信号,例如温度、湿度、音量、压力或者图像等等模拟信号,这些信号在时域上是连续的,需要转换成MCU更容易储存、处理和发射的数字形式信号,这个就需要模/数转换器了。这里我们区分一下一些概念:( s# `$ E, }4 b  _
(1)转换采样率
. K2 b: ^6 B6 c4 kADC转换采样率(采样率)是指完成一次从模拟量转换成数字量时ADC所用的时间的倒数,即每秒从连续信号中提取并转换成离散数字量的信号个数。也就是1/ TCONV ,后面我们会介绍TCONV 的计算方法。) z; S# B& Y7 q6 \% D9 B( s3 ?
(2)分辨率, G7 `/ h7 f* v0 Y. Y. @
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*=0.0391V,此ADC的分辨率是 。ADC的位数越高,其分辨率就越高。
3 J7 I) O, U+ E7 ]* A7 @(3)基准电压8 J! f5 V+ Y( p0 l2 E' K
基准电压也叫参考电压(VREF),其用来当做参照作用。我们测试外部电压时会以基准电压作为参考,先把基准电压分成多少分(根据分辨率来分),然后再和被测电压进行比较,然后得到比较的结果,这样就能测试出输入的电压是多少了。
& ?) }( C8 P. `: n1 ~22.1.1 ADC特性
- c' M3 W! I% X( @. L/ ZSTM32MP157系列有2个ADC(ADC1和ADC2),每个ADC都可以独立工作,每个ADC都可以单独分配给A7或者M4内核来使用。ADC1和ADC2除了可以工作在独立模式以外,还可以在双重模式下工作(提高采样率,ADC1为主机),STM32MP157的ADC主要特性我们可以总结为以下几条:7 K$ Z% Y  M, p0 a4 o+ r; m) A
(1)可配置16位、14位、12位、10位或8位分辨率,可通过降低分辨率来缩短转换时间,因为转换时间缩短,我们可以做到的采样率就越高。
* v7 W5 q( I9 N8 }# E7 L* M# q(2)每个ADC具有多达20个的采集模拟通道,其中有6路快速通道和14路慢速通道,慢速和快速的区别主要是支持的最高采样率不同,慢速通道要比快速通道低。这些通道的A/D转换可以单次、连续、扫描或间断模式执行。
# B4 t6 B/ D  R& m(3)ADC的结果可以左对齐或右对齐方式存储在32位数据寄存器中。
/ q4 {6 _6 S) z# ~4 X) [6 b(4)ADC具有6条专用的内部通道,用于内部参考电压 (VREFINT ) 、内部温度传感器 (VSENSE )、VBAT 监测通道 (VBAT /4)、连接到DAC内部通道、VDDCORE监视通道。
: D3 G; {, F9 m. |(5)支持过采样,最高可以到26位采样率。3 g" P1 x3 H& K+ R: d( ?" [
(6)每个ADC支持3路模拟看门狗。
5 B8 Q2 b( m+ c+ K7 r9 d(7)支持单独输入和差分输入。
' m% w  }; p% |% A6 Z(8)ADC输入范围:VREF– ≤ VIN ≤ VREF+。由VREF- 、VREF+ 、VDDA 和VSSA 这四个外部引脚决定。一般我们把VSSA 和VREF- 接地,把 VREF+ 和VDDA接到3.3V,所以得到ADC 的输入电压范围是:0~3.3V。注意不要接超出这个范围的电压进来,否则容易烧坏芯片。7 U$ A6 _2 @8 E
上面我们列出的一些特性都是ADC重要的特性和关键知识点。下面来介绍ADC(仅限ADC1或ADC2)的框图:, t; L9 o5 U9 y7 K. Z- i
22.1.2 ADC 框图8 t# i( A" j, k' h: L

9 b- W4 |) [! }- F 01ac96d2f9234ffd85ba0398a9463993.png ' ~  l, W* e. B7 X

3 c. B! J' z3 G0 t图22.1.2. 1 ADC框图+ l( `. U' c- Z/ w  x5 M
在进行框图分析前,我们先对框图中的部分信号做如下说明:4 G( O& S0 g. E* ^# j2 w

- F% l9 m3 R6 ^" R* m. D! ^ d3820b4c58014c489ab6852f175d3d30.png
  J. u; G9 J& g
' U) @6 P" j( m9 q表22.1.2. 1 ADC内部输入/输出信号
# N( _2 @# o" \+ ?
* {' A- x# T$ B1 c  g 8be437e4fd554b21b6cc94a354e5ec34.png
1 o8 O" @) t' D1 U+ l
: {) b- J: K2 W" I图中,我们标记了11处位置,说明如下:
* T, ~: n! }1 ]8 a
6 v6 N8 f4 M; Y. c( N% i$ }4 _1.VREF+电压
8 H2 Y" `6 g. B① 是VREF+电压- K/ Q5 V( ]/ {0 S0 Q" \( y
VREF+ 是正模拟参考电压输入,选择范围是1.62V3.6V,开发板上我们一般给VREF+接入的电压时3.3V,所以得到开发板上的ADC测量范围是03.3V。此外,STM32MP157有ADC和DAC共用的内部基准电压VREFBUF,可通过VREFBUF_CSR寄存器进行配置,可选1.5 V、1.8 V、2.048 V和2.5 V。2 ^" C% `3 F6 p2 B
  @; _* m6 O, K3 n0 s8 f$ k
b11d5d4c62e848d4af6c6be7244b3f4e.png
& K# v5 O8 E$ R3 @" ~1 ?: b
6 ~) Q; v1 ?) e8 s! ~8 R8 n& f( \图22.1.2. 2数据手册部分截图( W3 Z1 d. @6 X6 ^! w
2. ADC的双时钟域架构8 X0 X- p+ [6 `; q2 r, S
② 是ADC的双时钟域架构
" ~- l1 N) c7 y# S双时钟域架构意味着ADC时钟独立于AHB总线时钟, ADC有两种时钟源可以选择,分别是adc_hclk和adc_ker_ck。+ J# z' m1 h) Q! n: U
0 ~7 F  |8 }" t; Y- a$ P1 X
5210d18b97c24a068981c70eaa2a892d.png
# o/ M( V" C! y7 _8 G) s; U1 }. s( c; [- ~0 L. K* o7 `, }# X8 \0 F
图22.1.2.1. 1 ADC1和ADC2时钟" }5 X. w4 u* v) p6 T$ N
(1)adc_hclk(属于同步时钟)
  x/ N0 y/ w; Q2 }* }! gadc_hclk来自AHB总线的系统时钟,ADC1和ADC2处在209MHZ的 AHB2总线时钟。可以通过ADC_CCR寄存器的CKMODE[1:0]位来选择不同分频的AHB2总线时钟。/ c  F4 i4 [' Y* ^- y6 c; c
有以下的四种情况:( }% y" b  ?9 H4 F1 w
CKMODE[1:0]=00,这是异步时钟模式选择的配置,适用于下面要讲的adc_ker_ck时钟。
$ ]8 r) G7 j3 U- ICKMODE[1:0]=01,adc_hclk/1(同步时钟模式)
# J+ a, q( D- V: V8 j  t. O6 e/ NCKMODE[1:0]=10,adc_hclk/2(同步时钟模式)
$ g6 m  Y3 N! P" JCKMODE[1:0]=11,adc_hclk/4(同步时钟模式)
! q+ f  H  D' e* s/ F1 }4 ?比如我们选择1分频的adc_hclk,得到的频率是209MHZ,但是参考手册上限制ADC最高是 133MHz,说明这样配置就超频了,这是不可行的,因为超频误差会比较大。我们可以降低AHB2总线时钟,但是这样可能会影响我们其他外设的性能,所以为了整个芯片的最优性能,我们可以选择其他配置。
/ w3 ~5 s3 o' D(2)adc_ker_ck(属于异步时钟)
! R8 M1 ?6 V) D# |8 ~  Sadc_ker_ck可以通过RCC_ADCCKSELR寄存器的ADCSRC[1:0]位来选择不同的时钟源,前提是前面提到的CKMODE[1:0]=00。选择的情况如下:
# X4 c5 ?8 `5 T4 w8 y; y  ?- A0 Y' tADCSRC[1:0]=00,pll4_r_ck作为ADC时钟源(复位后的默认值)
; @3 _5 V4 }5 o! Y% w0 [1 q. ~ADCSRC[1:0]=01,per_ck作为ADC时钟源
+ Q3 @0 x  e, S  i. d1 \3 q1 EADCSRC[1:0]=10,pll3_q_ck作为ADC时钟源3 ?1 Y5 B$ b! L8 k- \0 \8 u  p& V
例如我们可以选择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。9 ]+ o+ l2 u3 ?9 A4 n' i$ \
3 C. G0 r, O) |, m. E/ z* ]2 A' w
4856ac87dbbf4905a6a8c9b92dc18866.png 4 o  _+ Y, L0 }3 F

3 C2 v! s) J, O图22.1.2.1. 2 PER时钟
: I* e& J" B3 n5 i$ x4 N* V如果选择了adc_ker_ck时钟源作为ADC的时钟,则可以通过ADC_CCR寄存器的PRESC[3:0]位进行分频,可以是1、2、4、6、8、10、12、16、32、64、128、256这12种分频系数。上面的分析请结合下面的ADC时钟方案图理解。9 a9 Y' `1 `7 w, @7 z* `( F0 b# ]

$ U0 J' h. Z" o* w 1109fcbc5ba3492fbaac3b3eac6d8b29.png 0 f1 e' g: S, g, f
$ |1 k" u6 e* l" x, C2 s
图22.1.2.1. 3 ADC时钟方案
7 L$ P6 l7 g& K$ Z: k3. ADC的输入通道. V( O' J# ~8 _" i1 S0 ?
③是输入通道# A  R* B# S  |; \# p
在讲解STM32MP157的ADC输入通道前,我们先了解单端输入和差分输入。$ M" k$ I- [1 \! {1 ]3 E
% O+ F1 }7 a! g2 [5 E
f820db4b52f749489857a035bab3c52d.png
% {+ r9 G# T6 n$ K  f5 ?- f  f5 I' I( ?5 u
图22.1.2.1. 4 ADC的单端输入和差分输入% Y; \; B. \! J- J
(1)单端输入
4 J! R1 l+ Y. ^2 \0 f1 Q  O) Y+ ~# |; z单端输入只有一个输入引脚ADCin,同时使用公共地GND作为电路的返回端,ADC的采样值:VADC=VADCin -VGND。这种输入方式接线简单,ADC的采样值由Vin决定,且随着Vin受到的干扰而变化。
8 e8 C  I' ?2 H  @  B" H8 d- q(2)差分输入
; I& G# G7 \/ g* {9 ~+ p. P差分输入比单端输入多了一根线,ADC采样值: VADC =VADCin±VADCin-。这种输入方式接线稍微复杂,不过两根线受到的干扰差不多,属于输入共模干扰,在输入ADC时,这些干扰会被互相抵消掉,从而降低了干扰,所以测量精度是比较高的。
( T% M, Z4 A$ p4 P0 t2 |: i(3)STM32MP157的通道资源
: w3 L9 n( p/ u& E3 N# E; P4 ?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]是慢速模拟输入。
" U7 ^% [4 \/ C  U. k# }$ L: O如果我们使用单端输入,则只有ADCx_INP[19:0]有效,而因为ADCx_INN[19:0]在内部自动接了VSSA ,所以ADCx_INN[19:0]不能选择作为单端输入。内部电压信号可以连接到ADC_INP的某个通道上面进行采集,如:内部参考电压 (VREFINT ) 连接到ADC2_INP13、内部温度传感器 (VSENSE ) 连接到ADC2_INP12、VBAT 监测通道 (VBAT/4) 连接到ADC2_INP15等。3 K4 n% p" l) U, t+ a  v
注意:STM32MP157的ADC支持单端/差分转换,由寄存器ADC_DIFSEL控制,我们一般使用单端转换模式,默认这个寄存器都是0的,刚好就是单端转换模式。/ v* K+ _1 S- c+ y1 K1 G" m
4. ADC的转换序列
% W/ y: r1 H! Q- o2 k④是转换序列* _5 ]2 c+ S5 j0 a- ~
(1)常规通道组和注入通道组
" O- N1 [) G7 _+ D7 C7 d: d" B; e在ADC的20个多路复用模拟通道中,可以分为规则通道组(也可以称为常规通道组)和注入通道组。规则通道组最多可以安排16个通道,注入通道组最多可以安排4个通道。规则通道组的通道可以称为规则通道(或常规通道,字面上一个意思),注入通道组的通道可以称为注入通道。规则通道可以理解为常规的通道,相当于正常运行的程序,我们一般使用的是规则通道,而注入通道可以以抢占式的方式打断规则通道的采样,也就是说注入通道相当于中断,当规则通道在执行任务时,可以插入注入通道(中断发生),规则通道先暂停处理任务而去处理注入通道的任务(中断响应),当注入通道处理完以后再返回去继续执行规则通道的任务(中断返回),这个过程就相当于中断的过程了。
+ K8 P# P' y' _- [9 k(2)转换序列
: i  w7 u6 X3 r5 v6 }1 A( G6 t9 k1 JADC的作用就是将模拟量转换成数字量,可以将转换分为两组:规则转换和注入转换。每个组包含一个转换序列,该序列的作用就是控制通道的转换顺序。比如20个通道中有16个规则通道和4个注入通道,规则通道有116个转换序列,注入通道有14个转换序列,寄存器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即可,其它的寄存器也类似。
* z; C% n0 O. y( H
: c3 Z; p% g, S. C- j5 X; A) \# e 9386a2ebdc2141b698f970101527d1ab.png 2 \3 p/ s6 G% I9 `

* X5 H9 l( n- [8 q( ?* g0 \7 E表22.1.2.4. 1 转换序列
# I' X$ Z" G1 V7 Q一个常规转换组最多由16个转换构成。一个注入转换组最多由4个转换构成。常规转换必须在ADC_SQRy(y为1~4)寄存器中选择转换序列的常规通道及其顺序,转换总数必须写入ADC_SQR1寄存器中的L[3:0]位。注入转换必须在ADC_JSQR寄存器中选择转换序列的注入通道及其顺序,转换总数必须写入ADC_JSQR寄存器中的 JL[1:0] 位。* _' w: X' F; ^( f
注入通道的转换可以打断常规通道的转换, 在注入通道被转换完成之后,常规通道才得以继续转换。
. |$ W8 L& u6 O( t1 P6 Y5. ADC的触发源  G  \) G. U1 M
⑤是触发源
7 U% m2 p! t; K经过前面的③和④步骤以后,选择好了通道以及转换顺序,接下来就开始进行转换操作了,转换需要触发才可以进行转换,ADC的触发转换有两种方法:分别是通过软件或外部事件(也就是硬件)触发转换。
0 L/ b2 q$ i' t/ M3 d5 {+ {  e7 U( j. S* p(1)软件触发
* B3 t7 s* n8 h- m我们先来看看通过软件触发转换的方法,方法是:通过写ADC_CR寄存器的ADSTART这个位来控制,写1就开始转换,写0就停止转换,这个控制ADC转换的方式非常简单。5 _/ U: P2 I4 A( t
(2)硬件触发
  i" y: Z) r5 c; M0 e4 U5 ^另一种就是通过外部事件触发转换的方法。有定时器和输入引脚触发等等,具体请看《STM32MP157参考手册》的表187和表188,如下表列出部分,可分为:常规通道的外部触发和注入通道的外部触发两种。. t1 l: |  X9 l
ADC1 和ADC2常规通道的外部触发部分
& D) c3 }0 s+ n5 e$ G7 {! _* {( `$ s$ {! f& A0 Z
2beb127609b64715818386f955690251.png + j7 s/ C8 H6 E0 g
4 N- ]# ~& w  y& c' U; ]$ u
adc_ext_trg[20:0],对应的就是常规通道的外部触发,共有21路。
  \1 F3 Y) Z5 Y; t9 _) `1 d+ Uadc_jext_trg[20:0],对应的就是注入通道的外部触发,共有21路。9 D4 L2 [2 Y5 z. M$ }6 n* @' N- g
如果选择硬件触发,则需要选择相应的硬件触发通道和触发边沿等,然后由外部硬件通道来触发ADC的采集(注意:ADSTART位同样要设置为1)。硬件触发通道由ADC_CFGR寄存器的EXTSEL[4:0]和ADC_JSQR寄存器的 JEXTSEL[4:0]位来选择,分别是常规转换组和注入转换组的触发源选择。而触发边沿是通过ADC_CFGR寄存器的EXTEN[1:0]和ADC_JSQR寄存器的JEXTEN[1:0]位来选择。其他的设置我们后面再讲解。3 E* H# ?$ F$ b  f1 K
6. ADC的转换时间* x; X  h+ [7 \
⑥是转换时间
0 B: L7 Q7 B5 f; T" {' u  O(1)计算转换时间
; s6 Y* [2 D8 A% s3 B% ~3 USTM32MP157的ADC总转换时间的计算公式如下:
% D- D0 J5 \" W4 n7 @TCONV = 采样时间(TSMPL) + 逐次逼近时间(TSAR)# d5 z5 b9 o& d7 I8 h* P1 V! c2 J
采样时间(TSMPL)可通过ADC_SMPR1和ADC_SMPR2寄存器中的SMP[2:0]位编程,ADC_SMPR1控制的是通道09,ADC_SMPR2控制的是通道1019。所有通道都可以通过编程来控制使用不同的采样时间,可选采样时间值如下:
0 O* n' X3 r. J1 s: O" ]) dSMP = 000:1.5 个 ADC 时钟周期
7 [5 t' O1 S* |& W0 j& xSMP = 001:2.5 个 ADC 时钟周期
  `( v; l- L5 I2 |SMP = 010:8.5 个 ADC 时钟周期, F, P4 D* \; o" M3 U
SMP = 011:16.5 个 ADC 时钟周期2 e1 L+ u/ A: t% y) Y
SMP = 100:32.5 个 ADC 时钟周期; I$ `7 [: y- m% i- O; B# H5 p
SMP = 101:64.5 个 ADC 时钟周期* u( s' d$ Q5 G$ F
SMP = 110:387.5 个 ADC 时钟周期/ S6 \: k# d4 j( L* X
SMP = 111:810.5 个 ADC 时钟周期
: b! V6 ^: E) v- s9 U4 g逐次逼近时间(TSAR)是由分辨率决定的,分辨率通过对ADC_CFGR寄存器的RES[1:0]位进行编程,可将分辨率配置为16位、14位、12位、10位、8位。而逐次逼近时间和分辨率的对应关系如下表所示:" x9 U; |* M7 z/ K0 o7 l& m8 E
8 K7 e$ e6 L- V- O
ac6d33d0474a41b4913be03435e24ea6.png $ Z$ f) f% m( y% p; x8 t; x

6 }7 [/ t* ]! _' N# H表22.1.2.6. 1 TSAR与分辨率的对应关系# n1 [/ z$ r0 b' w6 m4 z
举个例子,我们配置SMP = 111,即设置最大采样周期,然后采用16位分辨率,那么得到:
( ^1 v. m3 v" T# {; J1 i/ pTCONV = 810.5个ADC时钟周期 + 8.5个ADC时钟周期 = 819个ADC时钟周期
9 w7 {. }8 K+ c3 ^+ G表格中,Fadc_ker_ck的频率是24MHZ,我们的例程中ADC的时钟源如果是64MHZ的hsi_ker_ck经过2分频得到,即32MHZ。我们就以Fadc_ker_ck的频率为32MHZ来举例,可得到:
8 y# K9 E. x% b, R. V2 CTCONV = 819个ADC时钟周期 = = 25.6us
* H, Z) q* K+ o+ X. o1 v# H1 ^" S(2)计算采样率4 g5 Q2 A  V% l  c  ~9 R, W
得到转换时间,我们就可以计算出采样率:) j6 D6 |0 W) P1 F( u
采样率=1/采样时间' N! R" y* E" @$ x* b0 f% [
在数据手册中有给出采样率:# z; e7 L: P! n5 O) y+ h
5 T) j7 e+ ^* b
1c4e05d8838a41cf845c76b212a4e537.png
$ M3 ?+ m/ j- m. q2 V  ^/ S8 F8 u, l  u# l% ]# R2 f
图22.1.2. 3ADC的采样率列表
( D5 g/ O: ~2 r5 J我们以表中第一个参数为例进行计算,ADC的时钟频率是36MHz,采样时间是1.5个ADC时钟周期,所以采样时间TCONV =(1.5+8.5)/36MHz,那么采样率fs =36MHz/10=3.60MSPS,和表中的3.6一致。) o( A+ g9 m8 q/ u! V3 n4 ?
7. 其它1 C$ G8 u" J$ n: y# j! M
⑦是选择参考电压/ U! a' {" u/ D! O5 E
选择参考电压,我们可以设置参考电压来自外部的VREF+,也可以设置参考电压来自内部的稳压器。
0 u+ }$ a5 b7 J7 o6 l: _⑧ADC的核心
7 ]4 P4 ]% P4 @8 KADC的核心是一个16位的逐次逼近型ADC转换器,它根据我们设置好的参考电压、输入通道、启动条件等,执行模数转换。
- ?9 E* j! k7 W) b" L⑨数据寄存器0 }7 I2 T7 Q/ C2 u4 A& T: X
这是ADC转换完成后的数据输出寄存器。其中RDATA[31:0]用于保存常规通道的转换结果,JDATA1~4[31:0]用于保存注入通道的转换结果,如果是使用双重模式,常规通道的数据则是存放在ADC_CDR寄存器。转换结果CPU可以通过AHB总线读取,同时也可以产生相关中断(adc_it)。& [% H5 t" }  g4 S! d6 M8 f- h
⑩中断4 P4 @( u0 U# Z- A$ l& z* A
对于每个ADC都可在下列情况下产生中断:
6 Z3 s' e! b7 f: @& M- o
" o8 j# J" X: e  g  B# u 935b0e1e7e5b4a5981ce9fc865cbfd41.png & X8 |: P0 U$ j. e# p6 [+ z5 Y
) u2 u- u7 B$ L
表22.1.2.7. 1每个ADC的ADC中断
6 {* l: p# L* w0 j- [表中前面5个中断都很好理解,我们从模拟看门狗中断介绍。
# o( @! K! V: y7 S* x, G) B模拟看门狗中断发生条件:首先通过ADC_LTR和ADC_HTR寄存器设置低阈值和高阈值,然后开启了模拟看门狗中断后,当被ADC转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断。例如我们设置高阈值是3.0V,那么模拟电压超过3.0V的时候,就会产生模拟看门狗中断,低阈值的情况类似。
0 }" a; N7 b: R; s, I/ ^上溢中断:如果发生传输数据丢失,会置位ADC状态寄存器ADC_ISR的OVR位,如果同时使能了溢出中断ADC_IER寄存器的OVRIE位,就会在转换结束后会产生一个溢出中断。, k9 J! `2 X  t! B
此外,我们还要知道常规组和注入组的转换结束后,除了产生中断外,还可以产生DMA请求,把转换好的数据存储在内存里面,防止读取不及时数据被覆盖。
3 M7 b7 ]( }0 ]+ G6 e& g: |⑪通道预选控制信号
% r: r" N/ o- S" T7 Z通道预选控制信号,用于将ADC某个通道连接到对应的GPIO上。PCSEL[19:0]每个位对应一个通道,总共20个通道。这一点和以前的STM32系列不一样,在使用的时候,需要特别注意。: \2 D; O  ^: I
22.2 单通道ADC采集实验
% S7 G  C* A7 M" p9 T: O本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 15-1 ADC1_SINGLE_CH。% C% t& Q  n  r$ \* d$ i
STM32MP157的ADC可以进行很多种不同的转换模式,这些模式在《STM32MP157参考手册》的第29章也都有详细介绍。ADC有独立模式和双ADC模式,独立模式一般是指常规通道,独立模式中又会有:单通道、单次转换模式、多通道(扫描)、单次转换模式、单通道连续转换模式、多通道(扫描)连续转换模式和注入转换模式。双ADC模式会稍微复杂,我们这里先介绍独立模式。
1 O; n% K7 }- ?; v1 J22.2.1 单次转换和连续转换* f" Z) G* f7 P) o" Y- \( b
ADC的转换模式中,主要需要了解单次转换模式和连续转换模式。- s# ^( S! r  y5 u. k5 t$ w
5 o5 a- H6 X6 I  i7 H4 p
1.单次转换模式) _- T8 a' q+ {4 n
将ADC_CFGR 寄存器的位CONT=0后则设置为单次转换模式,启动ADC后,转换一次完成后则停止,然后等待下一次的ADC启动后再继续进行下一次的转换,这里的通道数可以是一个也可以是多个,但是只采集一次。# e7 n$ r; }" ]( h2 d* u* W6 F1 v
转换序列的作用就是控制通道的转换顺序,如果设置了几个通道的转换顺序,当转换完设置的所有通道后,我们就说转换序列完成,只有一个通道的话,可将序列长度编程为1。
- D+ W% }- U& B* E0 C: \6 c(1)对于常规通道:
+ Q; C+ a# @8 \- k在常规序列中,一旦选择的通道转换完成,转换数据被储存在16位ADC_DR寄存器中,ADC状态寄存器(ADC_ISR) 的EOC(转换结束)标志被置1,如果设置了EOCIE,则产生中断,然后ADC停止。 如果常规序列完成后,EOS(常规序列结束)标志置 1,EOSIE 位置 1 时将产生中断。3 B- U' C1 W, `/ i
(2)对于注入通道:
9 d+ l2 J! E1 i' T! ^& w3 u- |在注入序列中,每次转换完成后,转换数据被储存在16位的ADC_DRJ1寄存器中,ADC状态寄存器(ADC_ISR) 的JEOC(注入转换结束)标志被置1,如果设置了JEOCIE位,则产生中断,然后ADC停止。如果注入序列完成后,JEOS(注入序列结束)标志置 1,JEOSIE 位置 1 时将产生中断。) g4 b9 m$ o* F
2.连续转换模式1 J. {8 {, F: p  C" {- v8 f
将ADC_CFGR 寄存器的位CONT=1后,则设置为连续转换模式,连续转换模式只适用于常规通道。启动ADC后则开始转换,转换完所设置的所有通道后,返回到第一步再继续重新转换所有的通道,即转换完一次后继续开始下一次转换,转换数据存储在 32位 ADC_DR 寄存器中。
: S" D  s7 O  D' F9 Q4 m7 U在常规序列中,每次转换完成后,EOC(转换结束)会被置1,如果EOCIE 位被设置为1 时将产生中断;如果转换序列完成后,EOS(序列结束)标志置 1,EOSIE 位置 1 时将产生中断。
! ~# `# e4 w1 R/ q( V3.独立模式
: G0 m2 a! x: e(1)单通道、单次转换模式
2 d5 M! F. w# C0 [3 ?2 q这是最简单的ADC模式,在此模式下,ADC 执行单个通道x的单次转换(单次采样),并在转换完成后停止:" q/ h0 K: N, P; s5 t2 }
( @/ s7 ?5 G' }0 G7 C3 L
1f4401e429764a7cbf0132dba0df87d8.png
* C/ L1 k$ o' \8 g: L
$ l( ]! o  h9 f& r) N图22.2. 1单通道、单次转换模式
2 C  t# }( {9 c% `- p$ j(2)多通道(扫描)、单次转换模式! R8 x5 F# Q* `- Q& W4 V
扫描模式一般用于顺序转换多个通道,是针对多通道ADC而言的,单个通道没有扫描模式。多通道(扫描)、单次转换模式用于在独立模式下对一些通道进行依次转换,可以在此模式下以不同的采样时间和采样顺序对任意序列的通道(最多 16 个)依次进行配置。通过这种方式,用户不必在转换过程中停止ADC即可以不同的采样时间重新配置下一个通道。此模式可以避免额外的 CPU 负载以及繁重的软件开发。例如,在机械臂的系统中,必须在上电时读取机械臂系统中每个关节的位置才能确定机械臂顶端的坐标,那么可配置用到的通道,例如下图配置11个通道:( J4 C+ t2 L+ t! m9 p
. D& M( m& ]6 j2 h3 }
866305096c574a988dd62ea666aaaa1c.png
% g; l! S, W0 _6 V
  x* s/ F- g# T" R5 x4 T图22.2. 2以不同采样时间转换11个通道1 j) K+ K$ s  Y, G
所配置的通道可按设置好的次序进行转换:9 a, B/ f( o, W

, w$ j! W/ P# x/ q3 @% v& k% k df59179f1e344cfc81541d4ac0727f0e.png 5 ?+ }9 d; S+ T: `1 u! ~+ f  z3 y8 b
7 Z: \* _9 v2 b$ L- v4 U" j8 r; d8 @7 V
图22.2. 3多通道、单次转换  a; D5 q4 U) w% v
(3)单通道连续转换模式
' x* q" r2 B! D$ x对单个通道进行连续不断的转换,即转换完一次后继续开始下一次转换,这种模式叫做单通道连续转换模式。连续转换模式也可以使用DMA,从而降低 CPU 负载。# `% H+ t3 e2 h. d3 T3 k, W
( E# p1 F7 S4 m; Q4 `$ B9 ~
df7a7d208d7146f391ec5859049ed6bc.png
; ~9 W5 L: |$ E
/ [. M" \. Z  Z4 ^* S图22.2. 4单通道多次转换9 v  h2 Z7 W& x$ }8 y& w* k
本实验我们来学习使用常规单通道的单次转换模式。
% B7 F# U& B( o- e4 _STM32MP157的ADC在单次转换模式下(寄存器ADC_CFGR的CONT位为0),只执行一次转换,该模式可以通过ADC_CR寄存器的ADSTART位(只适用于常规通道)启动,也可以通过外部触发启动(适用于常规通道和注入通道,但是必须先设置ADSTART/JADSTART位)。
! E/ C) I& `. F$ a2 c) T3 O$ X以常规通道为例,一旦所选择的通道转换完成,转换结果将被存在ADC_DR寄存器中,EOC(转换结束)标志将被置位,如果设置了EOCIE,则会产生中断。然后ADC将停止,直到下次启动。! Z5 X5 I2 n8 A0 n  P3 Q; W
22.2.2 ADC寄存器: T4 O5 J0 R# c6 g. I
下面,我们介绍执行常规通道的单次转换,需要用到的一些ADC寄存器。
8 W! s/ Y( A2 x  d2 G7 x
% V4 C- `. |- d4 H, w1.ADC通用控制寄存器(ADC_CCR)
5 ?: \( _/ ]6 ]) l* b+ qADC通用控制寄存器描述如下图所示:
. _) Y- l0 k# F( W) \8 R5 i( s
, t3 o" d: N/ w& H  b f2c0c48aaae242afaf6d69751d74bd77.png
0 t" m5 G$ k! N7 \6 N: w/ s" L9 A; W$ T* e, i
图22.2.1. 1 ADC_CCR寄存器
$ O3 ]; o5 r3 y  n9 c该寄存器本章只需要用到PRESC[3:0]这四个位,用于设置ADC时钟的预分频系数(即对adc_ker_ck的分频系数),表示2^PRESC[3:0]分频:7 y) v' a% z' ^# u- g
0000:输入 ADC 时钟未分频
' }. _, P5 b) O- k) N0001:输入 ADC 时钟 2 分频2 q: j' D9 B, P  Q; M' g
0010:输入 ADC 时钟 4 分频3 L) R( Q' y3 O% @. O
0011:输入 ADC 时钟 6 分频
; |9 {% V: \; x5 n. R. \0100:输入 ADC 时钟 8 分频
1 o5 z3 Z* j. Z, j# t6 i; t0101:输入 ADC 时钟 10 分频
5 u; A& u- O+ t2 T) F1 a0110:输入 ADC 时钟 12 分频
8 B3 N8 K1 D5 S4 l! o; `; W5 C0111:输入 ADC 时钟 16 分频/ @5 p0 `' Z* y4 n- t& B  B8 U
1000:输入 ADC 时钟 32 分频
" q* e. S/ l: R3 c1001:输入 ADC 时钟 64 分频
" ^* Q$ Z! h4 I; Z: Y/ M1010:输入 ADC 时钟 128 分频! I) ^1 P# _# e) m  y2 M/ B) o
1011:输入 ADC 时钟 256 分频
- u+ p% \; {7 t- M. j8 c& W5 g8 a* f/ {其它:保留
2 Z/ e+ }6 J" s2 z. u: Ladc_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。因此:6 H) j4 m8 }) O0 w5 b# k
adc_ker_ck=per_ck=hsi_ker_ck=64Mhz。# x' b1 W, ^7 a6 p6 m
注意ADC的输入时钟频率不能大于133MHz。可以根据需要设置ADC时钟的预分频系数,例如设置PRESC[3:0]=1,即可得到ADC转换时钟频率为:adc_ker_ck/2^PRESC[3:0]=64/2=32MHz。
# D" \- w% k/ h1 w2. ADC控制寄存器(ADC_CR)
0 S# |8 X) Z& V7 L4 [- DADC控制寄存器描述如下图所示:/ H. \$ |4 m& @! M, Z. A" N0 g

% x4 E+ ^' l8 J8 s  o$ \7 H6 P 8ba719705677434386a606045a5db96e.png ; c3 w% h/ v- D+ G
( ~' \3 d8 ^$ B" q  z
图22.2.1. 2 ADC_CR寄存器
. w) @9 Z+ N* d. W8 n& Y7 d该寄存器我们用到多个位,这里就不全部列出来讲解了,而是抽出几个重要的位进行针对性的介绍,详细的介绍,请参考参考手册。
' @# ~2 s/ r, b- BADEN位,用于使能ADC转换器。需要设置该位为1,ADC才可以正常工作。; Y# ~" ?$ f$ g
ADSTART位,用于启动ADC常规通道的转换序列。当使用硬件触发时(EXTEN[1:0]!=0),设置该位为1,必须在相应的硬件触发事件产生时,才会启动ADC转换。而当不使用硬件触发时(EXTEN[1:0]=0),设置该位为1则可以立即启动ADC转换。2 g0 K0 f0 I% f; u3 u+ \" c' W
BOOST位,用于设置是否使用BOOST模式。当BOOST=0时,ADC转换时钟必须小于20MHz;当BOOST=1时,ADC转换时钟必须大于20MHz。如果设置的32MHz的ADC转换时钟,因此该位必须设置为1。
& R3 q5 ]9 x- O* l- qADCALLIN位,用于设置线性ADC校准。设置该位为1,可以设置ADC的校准模式为线性校准。
# F3 K" W2 h- ~, {ADCAL位,用于控制/读取ADC校准状态。设置该位为1时,可以启动ADC校准,等校准完成以后,硬件会自动清零该位。因此在设置改位为1以后,通过判断该位是否变为0,即可判断校准是否完成。
4 J/ R! @! K: e8 j3. ADC配置寄存器(ADC_CFGR)
% i9 X8 S7 e% [& N: F4 aADC配置寄存器描述如图所示:1 n' K( n! j  r" u$ f  o( ~

. H/ k. s4 ]" C e09f95e47e8041a6a758bfde08e24c68.png ) ?9 ^$ C7 e( e3 d* z' ?

# J! N' x) [9 U. V$ `8 \图22.2.1. 3 ADCx_CFGR寄存器: U& Z- L' t& ~/ ]4 o4 x9 O
RES[2:0]位,用于设置ADC转换的分辨率:0,16位;1,14位;2,12位;3,10位;4,8位;其他值:保留。本章我们使用16位分辨率,因此设置这3个位全0即可。, V$ L1 Y: O9 W$ L
EXTEN[1:0]位,用于设置常规通道的外部触发方式和极性。本章我们使用软件触发,因此设置EXTEN[1:0]=00,即禁止外部触发即可。
& G6 P/ A+ [9 N; y. E. GOVRMOD位,用于设置是否使能覆写功能。当设置该位为0时,如果上一次转换的数据未及时读取,新的转换结果将被丢弃;当设置该位为1时,如果上一次转换的数据未及时读取,将会被新的结果覆盖。本章,我们设置该位为1。0 j/ O9 H, e5 V. i5 F0 @. @/ s' y
CONT位,用于设置转换模式。当CONT=0时,表示单次转换模式;当CONT=1时,表示连续转换模式。本章,我们设置该位为0。
# b. T9 y% e0 M3 S! [  {4. ADC配置寄存器2(ADC_CFGR2): ?, M* ~+ K+ W; X9 i( M4 Y
ADC配置寄存器2描述如图所示:
! z7 z, _5 @, o2 m  H- q* P2 |- y8 {' H& n5 P
1f74ded1e3054500bbf672ab67f15817.png ; l5 J' K( S- G  M) b, L5 z
8 P1 [% e4 s8 P' B; M* p# k
图22.2.1. 4 ADCx_CFGR2寄存器( ~+ k2 p/ V+ D2 n
OSR[9:0]位,用于设置ADC的过采样率。OSR[9:0]=01023,表示1x1024x过采样。本章,我们不使用过采样,设置OSR[9:0]=0即可。
0 j  V: W: n5 `0 [LSHIFT[3:0]位,用于设置输出结果的左移位数,015表示左移015位。本章不使用左移(数据右对齐),因此设置LSHIFT[3:0]=0即可。* o1 e5 E5 J) N$ q$ y7 P
5. ADC常规序列寄存器1(ADC_SQR1)
& O& U' Z* w) U4 \5 vADC常规序列寄存器描述如下图所示:
- F" ^  T9 W2 ?0 G. K( v# z5 `  a1 w+ ~8 ^, p# S, O* f
10c2ecde87574b4696ccf08b4f8ad1db.png
) r7 D' L% Y3 r  _. g) i
5 E* U$ d( {+ ]0 h* s5 \; r图22.2.1. 5 ADC_SQR1寄存器% G9 L* K$ H2 s- @$ p
L[3:0]:用于存储常规序列的长度,取值范围:015,表示常规序列长度为:116。我们这里只用了1个通道,所以设置这几个位的值为0即可。) {$ E7 P* w: |. W( S0 m
SQ1SQ4表示常规序列中的第14个序列的转换通(019),每个常规序列的转换通道,可以由SQx(x=116)指定,比如我们设置:SQ1[4:0]=19,就表示常规序列1的转换通道为19(ADC1/2/3_CH19)。SQ5SQ16由寄存器ADC_SQR24控制。  a; ~) I" @: ^
6. ADC采样时间寄存器2(ADC_SMPR2)- C5 E1 K4 q+ R
ADC采样时间寄存器2描述如下图所示:
- Y$ I0 Q1 a; d8 j" O1 B9 u
) R: L& L8 F; q* @3 Q; G# C2 [4 m0 h c043661a4cf4491483c2cb9e8d91021b.png $ C7 N( d9 k. R, ?) r
8 h, c$ [2 V) d3 f% K
图22.2.1. 6 ADC_SMPR2寄存器: {) _" a) P" ^) }. h$ p
该寄存器用于设置ADC通道1019的采样时间,而ADC_SMPR1设置ADC通道09的采样时间。STM32MP157的ADC总转换时间的计算方法前面已经介绍过了,采样时间周期越长,精度就越高,所以这里我们设置为最大采样时间。采样时间我们建议尽量长一点,以获得较高的准确度,但是这样会降低ADC的转换速率,所以大家在实际应用中自行结合自身情况设置。
& E9 L% Y, J8 p1 ^7 i7. ADCx通道预选寄存器(ADC_ PCSEL)5 p6 E/ s, d, z* O. t$ S
ADC通道预选寄存器描述如下图所示:, X, v/ u( E& {& n4 ~- n, r
# t: x, L. Q. m' s
ca29e0a83c90460ba401447e34a75b34.png   o$ c, d( O$ g" _* ?* Y7 w

+ H; _0 L% D! t- Q1 V图22.2.1. 7 ADCx_ PCSEL寄存器
: S8 `, s; G6 G( L: b. X8 t$ k该寄存器用于控制ADC具体某个输入通道和对应IO的连接,相当于在ADC输入和IO之间,加了一个开关,想要正常使用某个通道,则必须设置对应的PCSELy位为1(y=0~19),否则无法得到对应IO口的正常电压。注意:在STM32H7之前的的其他STM32芯片上面,是没有的,该寄存器的存在,有利于隔离ADC和IO的隔离。
) p/ Q2 c, ?, Q- A! y( Z* b  }举个简单的例子,在STM32H7上面,即便是ADC通道对应的IO口,只要不使用ADC功能(PCSEL不设置为1),那么该IO口就可以兼容5V,但是在STM32H7之前的其他STM32芯片上面,ADC所在的IO口,都不能做5V兼容。- O! C+ ?4 j9 w( v
8. ADC常规数据寄存器(ADC_ DR)7 s, g% U% Y  r" E1 L  E; O
ADC常规数据寄存器描述如下图所示:: i) D, b$ h4 h( s: j: Z

1 y% w# R3 t$ n  C* L' U 9983eb6ea4d0499f9c223fda6e275fc0.png 1 q, M9 g/ N/ O* a. ?5 r

+ J& E) z2 s: O图22.2.1. 8 ADC_ DR寄存器
6 L2 X9 B7 c! p2 O8 ^: `# g- N常规序列中的AD转化结果都将被存在这个寄存器里面,我们通过读取该寄存器,即可得到ADC转换后的结果,而注入通道的转换结果被保存在ADC_JDRy(y=1~4)里面,所以读取ADC_JDRy这个寄存器的就可以读取注入通道的ADC的转换值。2 m/ V- w/ h4 H  u2 \
9. ADC中断和状态寄存器(ADC_ ISR)
! B2 {6 i. a! y/ t- p  YADC中断和状态寄存器描述如下图所示:  ]5 N( n0 X" t( m% c  v6 o

1 C, B1 b% C/ s7 E$ U e3c964ea822946de911150234b14b65a.png
7 X4 o$ _5 m9 G: D  W
8 v+ B0 y% o3 h6 ?图22.2.1. 9 ADC_ ISR寄存器- Q2 B2 w' u' N. t+ B6 G4 Q; B
ADC_ ISR寄存器的位我们只介绍常用的几位:
' j' ]$ c- w" G4 |  U& dEOS位是常规序列结束标志位,常规通道序列转换结束后,硬件将该位置 1,通过软件写入 1 可将该位清零。
/ H$ H7 g+ R: b5 j4 a& Q+ o8 FOVR是ADC 溢出位,该位在常规通道上发生溢出事件时由硬件置 1,这意味着在 EOC 标志已置 1 时,新转换已完成,通过软件写入 1 可将该位清零。
; Z, v2 a8 \  \+ ^) U; q5 A, XEOC位是转换结束标志,当通道的每次常规转换结束,新数据出现在 ADC_DR 寄存器时,会通过硬件将该位置 1通过软件向该位写入1,或读取 ADC_DR 寄存器都可将该位清零。+ J% Q- a- f/ A4 d6 ]
这里我们仅介绍将要用到的是EOC位,我们可以通过判断该位来决定是否此次规则通道的AD转换已经完成,如果该位位1,则表示转换完成了,就可以从ADC_DR中读取转换结果,否则等待转换完成。  N) G+ W& o$ I) B( G
至此,本章要用到的ADC相关寄存器全部介绍完毕了,对于未介绍的部分,请大家参考《STM32MP157参考手册》第29章相关内容。
/ d* B/ W+ h' |* O" C1 m$ A22.2.3 ADC的HAL库驱动3 N. g  [* K9 S
ADC在HAL库中的驱动代码在stm32mp1xx_hal_adc.c和stm32mp1xx_hal_adc_ex.c文件(及其头文件)中。$ `8 g+ p) c' h6 d2 x. N
1.HAL_ADC_Init函数
  p# A6 f$ [5 oADC的初始化函数,其声明如下:+ j4 T5 r6 ]* P( C! [/ H
  1. HAL_StatusTypeDef HAL_ADC_Init(ADC_HandleTypeDef *hadc);
    6 W$ _1 W- H0 |: C
  2. 函数描述:$ h) J4 B& S( P  o
  3. 用于初始化ADC。
    $ m- J8 k8 B9 o8 u+ l9 u
  4. 函数形参:
    # b6 K( Z* e+ a# z" P" f& B
  5. 形参1是ADC_HandleTypeDef结构体类型指针变量,其定义如下:. i4 r9 J$ u1 h$ t+ R+ f
  6. typedef struct, I8 t3 W* }2 [& W) N! w
  7. {
      A9 m# Z# C6 ]0 q& ?- p
  8.   ADC_TypeDef                    *Instance;          /* ADC寄存器基地址 */ 1 I- w4 m# `) E8 d
  9.   ADC_InitTypeDef               Init;                /* ADC参数初始化结构体变量 */   - q: u  q7 e2 f+ s4 I. |
  10.   DMA_HandleTypeDef             *DMA_Handle;       /* DMA配置结构体 */        
    6 t4 N1 ?" l# a2 H1 j9 X9 q2 o; `
  11.   HAL_LockTypeDef               Lock;                /* ADC锁定对象 */   
    . S" G; ^1 T+ P7 F7 J4 y
  12.   __IO uint32_t                 State;               /* ADC工作状态 */     
    / Y: b* o: n+ N6 z1 ?! E
  13.   __IO uint32_t                 ErrorCode;          /* ADC错误代码 */  
    6 E7 ^# B7 K3 i6 Z- R8 l
  14. /* ADC注入通道配置结构,用于配置注入通道的转换顺序,数据格式等 */    ) H% v8 N7 ]# Q% I
  15.   ADC_InjectionConfigTypeDef InjectionConfig ;  - m* h2 y: f/ a/ o$ t4 B6 C
  16. }ADC_HandleTypeDef;
    1 r  B: D$ q( |# \7 x  y
  17. 该结构体定义和其他外设比较类似,我们着重看第二个成员变量Init含义,它是结构体: R9 |3 H' \7 G/ b3 i  l/ \' F. w
  18. ADC_InitTypeDef类型,结构体ADC_InitTypeDef定义为:
      t* s1 w1 b  I, ~
  19. typedef struct {
    5 p6 w& Q* N! F
  20. uint32_t ClockPrescaler;                /* 设置预分频系数,即PRESC[3:0]位 */- U: N+ L, j+ l) U, J8 w" Z; H3 U
  21. uint32_t Resolution;                     /* 配置ADC的分辨率 */
    . h5 ]$ a4 |6 f3 |/ R8 j7 @/ ^. n
  22. uint32_t ScanConvMode;                   /* 扫描模式 */
      f; ]- f, S/ o' E, `# K
  23. uint32_t EOCSelection;                   /* 转换完成标志位 */+ B! |+ }; n* p6 d' z* b/ S9 J
  24. FunctionalState LowPowerAutoWait;      /* 低功耗自动延时 */
    $ d# y6 t; u/ M* s7 E/ p! B
  25. FunctionalState ContinuousConvMode; /* 开启连续转换模式否则就是单次转换模式 */
    / Y6 E/ V* l4 ^! e8 M4 T- P
  26. uint32_t NbrOfConversion;                /* 设置转换通道数目 */& U, q. Y- J( I8 ?; e8 N3 h. M
  27. FunctionalState DiscontinuousConvMode; /* 单次转换模式选择 */+ ~2 B7 Z1 z- _; X
  28. uint32_t NbrOfDiscConversion;            /* 单次转换通道的数目 */
    + c, {* N( K7 d. b8 I
  29. uint32_t ExternalTrigConv;                /* ADC外部触发源选择*/
    , E7 B' J4 Y8 Z4 D- q" k0 ^
  30. uint32_t ExternalTrigConvEdge;           /* ADC外部触发极性*/+ w; m) Z* [/ Q
  31. uint32_t ConversionDataManagement;       /* 数据管理 */7 H3 N- _, Y% Z" j' e
  32. uint32_t Overrun;                           /* 发生溢出时,进行的操作 */
    ' A% ^1 ^9 e2 E" R5 R' v
  33. uint32_t LeftBitShift;                     /* 数据左移几位 */
    ! b% v% G. u, }0 y1 a
  34. FunctionalState OversamplingMode;        /* 过采样模式 */# A7 i4 p8 |; L. E8 d3 S; I# `. C
  35. ADC_OversamplingTypeDef Oversampling;   /* 过采样的参数配置 *// Y2 j5 b3 O( ~3 T- l9 x/ I$ C
  36. } ADC_InitTypeDef;
复制代码

1 _& `% {. x, R) ^" h; G/ lClockPrescaler:ADC预分频系数选择,可选的分频系数为 1、2、4、6、8、10、12、16、32、64、128、256。ADC最大时钟配置为36MHZ。
3 y: h. U# M" R) z" d+ EResolution:配置ADC的分辨率,可选的分辨率有16 位、12 位、10 位和 8 位。分辨率越高,转换数据精度越高,转换时间也越长;反之分辨率越低,转换数据精度越低,转换时间也越短。8 A0 e0 w5 Z3 H% L- @) J
ScanConvMode:配置是否使用扫描。如果是单通道转换使用ADC_SCAN_DISABLE,如果是多通道转换使用ADC_SCAN_ENABLE。
. R4 m' o# h+ v. {1 u; }+ v6 @EOCSelection:可选参数为ADC_EOC_SINGLE_CONV和ADC_EOC_SEQ_CONV,指定转换结束时是否产生EOS中断或事件标志。7 W: U; f! l* h% T2 S4 n8 @+ |
LowPowerAutoWait:配置是否使用低功耗自动延迟等待模式,可选参数为 ENABLE和 DISABLE,当使能时,仅当一组内所有之前的数据已处理完毕时,才开始新的转换,适用于低频应用。该模式仅用于ADC的轮询模式,不可用于DMA以及中断。
8 T) M3 s  m# {: Y  ]ContinuousConvMode:可选参数为ENABLE和DISABLE,配置自动连续转换还是单次转换。使用ENABLE配置为使能自动连续转换;使用DISABLE配置为单次转换,转换一次后停止需要手动控制才重新启动转换。
" \! ^  ?7 E: {. b( m2 QNbrOfConversion:设置常规转换通道数目,范围是:1~16。+ f7 d. Y- z5 [2 ^  u5 @7 a
DiscontinuousConvMode:配置是否使用不连续的采样模式,比如要转换的通道有1、2、5、7、8、9,那么第一次触发会进行通道 1 与通道 2,下次触发就是转换通道 5 与通道 7,这样不连续的转换,依次类推。此参数只有将 ScanConvMode 使能,还有ContinuousConvMode失能的情况下才有效,不可同时使能。4 q% m0 v0 u7 N1 W4 o# T8 p
NbrOfDiscConversion:不连续采样通道数。
, h% ~+ s/ z! c- y- X5 f. zExternalTrigConv:外部触发方式的选择,如果使用软件触发,那么外部触发会关闭。! \% @9 T4 r: \( k
ExternalTrigConvEdge:外部触发极性选择,如果使用外部触发,可以选择触发的极性,可选有禁止触发检测、上升沿触发检测、下降沿触发检测以及上升沿和下降沿均可触发检测。% |! ]* \- ]2 E* ?
ConversionDataManagement: 指定ADC转换后的数据处理方式。可以选择 DMA管理传输数据、数据存储在数据寄存器中或者是传输到DFSDM寄存器中。7 p* z6 {1 r0 d8 V: X- r
Overrun:当有新的数据溢出时,可以选择覆盖写入或者是丢弃新的数据。
0 [% h: O& P9 o; U3 z8 i0 QLeftBitShift:数据左移位数,最多可支持左移15位。2 P- B; ~0 G( k
OversamplingMode:是否使用过采样模式。/ z; X; R' g4 M9 d) b; b
Oversampling:配置过采样模式的参数。5 {* Z2 w0 u1 D  M9 n
函数返回值:8 w* X3 _' P: Q
HAL_StatusTypeDef枚举类型的值。
; D' g% A5 c5 A  f4 i1 ~9 t* THAL_ADCEx_Calibration_Start函数
6 b& z) G* M& t8 }! iADC的自校准函数,其声明如下:
$ |  B2 [1 b; @8 a4 i! iHAL_StatusTypeDef HAL_ADCEx_Calibration_Start(ADC_HandleTypeDef *hadc,
5 J: o- O3 g- R, x0 _' V/ J. ~uint32_t CalibrationMode, uint32_t SingleDiff);
! ?) H+ g1 ?# @函数描述:
& Z! C( s+ j: S% K. z首先调用HAL_ADC_Init函数配置了相关的功能后,再调用此函数进行ADC自校准功能。
( _1 v( ^; d( O  p' [0 A函数形参:
! C1 x# L. Q: L( `( d7 K形参1是ADC_HandleTypeDef结构体类型指针变量。
/ P5 p  K9 e% q形参2是校准模式选择,有以下两种:, L8 H  p4 e# i5 T# V
1)ADC_CALIB_OFFSET表示只运行偏移校准而不运行线性度校准。4 ^3 D+ u9 A4 w1 L
2)ADC_CALIB_OFFSET_LINEARITY表示同时运行偏移校准和线性度校准。, y6 t: Y+ p; j) P, \) V7 c
形参3是单端或差分模式选择,有以下两种:/ y1 V; m/ k& N' ?8 |! D! T" z
1)ADC_SINGLE_ENDED表示单端输入模式。+ X3 l9 z: k7 x( n$ k
2)ADC_DIFFERENTIAL_ENDED表示差分输入模式。# h3 g- y  y0 ]; J  g
函数返回值:
8 H8 D$ I! p* G8 d- F% @HAL_StatusTypeDef枚举类型的值。% C$ _6 Z0 G* ?3 I1 p' F9 \
HAL_ADC_ConfigChannel函数
+ G- D7 Y' z4 g( d& i  Z7 DADC通道配置函数,其声明如下:
. Y3 z$ @0 q9 E* j% u; ~/ _HAL_StatusTypeDef HAL_ADC_ConfigChannel(ADC_HandleTypeDef *hadc,% x, h3 U  q( X0 X1 L8 f2 Q  p
ADC_ChannelConfTypeDef *sConfig);
9 U) J4 N; C- [5 h! J' j+ K- ~函数描述:
* }# U/ Z3 |; c+ P* h$ a调用了HAL_ADC_Init函数配置了相关的功能后,就可以调用此函数配置ADC具体通道。
9 J: J; ~( S( l. Y" f函数形参:
: C* d7 E9 ~+ y. i7 r" ~) g形参1是ADC_HandleTypeDef结构体类型指针变量。4 D4 \- ?0 }8 E
形参2是ADC_ChannelConfTypeDef结构体类型指针变量,用于配置ADC采样时间,使用的通道号,单端或者差分方式的配置等。该结构体定义如下:
% A* w( Z% a4 Q- s+ ~' i
  1. typedef struct {+ d6 \; _' y1 h& T) e/ z9 s
  2.   uint32_t Channel;                          /* ADC转换通道*/' O/ @& Q/ U4 H4 ]
  3.   uint32_t Rank;                              /* ADC转换顺序 */
      q) v9 C  e- N; E- F3 @" R, x
  4.   uint32_t SamplingTime;                     /* ADC采样周期 */
    4 x' ~: Q" I! B( ]$ Y- E
  5.   uint32_t SingleDiff;                       /* 输入信号线的类型*/
    6 p* t7 B- L  g* E! N. D' U* J
  6.   uint32_t OffsetNumber;                     /* 采用偏移量的通道 */
    & R% _' Y- E) f- k
  7.   uint32_t Offset;                             /* 偏移量 */5 a& o: W# D8 p% a
  8.   FunctionalState OffsetRightShift;        /* 数据右移位数*/
    5 }8 M' J+ Y7 J* l: T
  9.   FunctionalState OffsetSignedSaturation; /* 转换数据格式为有符号位数据 */
    : P* U$ `, C* g9 l, \
  10. } ADC_ChannelConfTypeDef;
复制代码
  E# m" J4 W: o( J
Channel:ADC转换通道,范围:0~19。
6 V" P$ j9 V. ]" Q, Q8 L0 ~' ~1 NRank:在常规转换中的常规组的转换顺序,可以选择1~16。
0 V- ~) [! t! }0 t4 _SamplingTime:ADC的采样周期,最大810.5个ADC时钟周期,要求尽量大以减少误差。  Y: O$ a' e5 P6 h5 f2 ^4 T
SingleDiff:选择通道单端输入还是差分输入。8 D! m  ^. m6 F# B
OffsetNumber:选择使用偏移量的通道。0 p1 c& I$ t2 g3 i  ^4 m5 a* U
Offset:定义要从原始数据减去的偏移量。根据ADC的分辨率不同,支持的最大偏移量也不同,例如分辨率是16bit,,最大的偏移量为0xFFFF。
, M. A9 {4 l- i+ |3 n5 pOffsetRightShift:采样值进行右移的位数。
1 Z- z. J. t4 w4 z0 K9 [OffsetSignedSaturation:是否使能ADC采样值的最高位为符号位。
5 E# u1 X7 r2 t; x  {  h) z' _函数返回值:0 B9 O' w& k, p& h& v9 N9 E
HAL_StatusTypeDef枚举类型的值。
! H! C) V0 K) H8 x
  h( S( d: t0 x- {% i" C: b4.HAL_ADC_Start函数
, h: U7 c# w- m( [/ t函数描述:
0 Z% t" q' R* x; }% r# l当配置好ADC的基础的功能后,就调用此函数启动ADC。
% n. F1 {* o$ A3 ?2 A# C# L函数形参:5 n; K; q2 ~; j/ Y& u) ]
形参1是ADC_HandleTypeDef结构体类型指针变量。3 Y2 ~, B$ S8 D; W: X) B
函数返回值:/ n/ {. F+ P- S# h0 m+ v$ D! Z
HAL_StatusTypeDef枚举类型的值。
% p" g3 ]6 m9 C9 M# b! }  HADC转换启动函数,其声明如下:* f6 A3 q5 d8 s9 c4 I
HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef *hadc);
, w: g" H# P0 A8 Y
$ V. s* F. K5 E, O$ L" B2 K5.HAL_ADC_Stop函数* `1 ~' _+ D: Z/ e' w5 y
函数描述:
8 S" ~: K3 T2 H& O' l, ~停止ADC的转换(常规模式下停止常规组的ADC转换,注入模式下停止注入通道),禁用ADC外设。注意的是,ADC外设禁用正在强制停止注入组的电位转换。 如果正在使用注入组,则应使用HAL_ADCEx_InjectedStop函数预先将其停止。! A) u1 ^- s  n: M' W+ o$ n) y. }2 C
函数形参:
) |- E# @# ]' R7 |# f" R形参1是ADC_HandleTypeDef结构体类型指针变量。* l' K; y3 @! I% Y+ C( P6 E
函数返回值:
3 a& w: S  k# Z1 h, r4 P7 F5 cHAL_StatusTypeDef枚举类型的值。5 F$ r1 D; |% q9 X
HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef *hadc)
; L! S; M: N9 L* R6 V+ [. X4 Q1 k3 a' V. G; s7 t, e% h/ u
6.HAL_ADC_PollForConversion函数
! `- j5 q" h- X! b等待ADC常规组转换完成函数,其声明如下:0 E% v) J  h& ]* v9 y) T: {
HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef *hadc," F5 I$ a+ s  |% y9 a
uint32_t Timeout);2 q1 [4 L! j) ^
函数描述:  o8 B3 S4 I* A% _
一般先调用HAL_ADC_Start函数启动转换,再调用该函数等待转换完成,然后再调用HAL_ADC_GetValue函数来获取当前的转换值。
7 z' T9 w5 R5 @6 K, A函数形参:
* v4 b" n% o4 Q" K形参1是ADC_HandleTypeDef结构体类型指针变量。
% s9 C8 f, W; Q7 b3 l) U. e7 y形参2是等待转换的等待时间,单位是毫秒(ms)。
, H5 Z7 H; S2 n( z/ T函数返回值:: y% z% R+ B9 t' W6 i- t5 }8 y8 ]
HAL_StatusTypeDef枚举类型的值。8 c: Y. T4 T& n/ R) [8 Z: d

0 L  \' a& x& G+ @4 r3 R7.HAL_ADC_GetValue函数
" H4 ~0 h- A" E- I! A2 h获取常规组ADC转换值函数,其声明如下:: L9 P' d+ E+ u, o$ Q: \
uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef *hadc);6 p& k! N# W2 _7 V! M: M- {4 C3 V
函数描述:- L$ g0 @6 k- K/ W* A. q
一般先调用HAL_ADC_Start函数启动转换,再调用HAL_ADC_PollForConversion函数等待转换完成,然后再调用HAL_ADC_GetValue函数来获取当前的转换值。/ z( g) }( r" e. r6 ?' m& F
函数形参:5 c% Z) n: a3 F- P" O
形参1是ADC_HandleTypeDef结构体类型指针变量。
; g, ]8 q& ?6 K' ~, ?6 K8 S) C( a/ t函数返回值:
1 N: V/ n* z7 U/ R4 m! {7 C当前的转换值,uint32_t类型数据。- x3 J4 G/ q/ g# S  ^% L( u! u/ C3 s
4 q5 h: e# ]- T5 V
22.2.4 硬件设计
+ t/ ^3 Q4 |6 c! |) L: f8.例程功能6 C. z! W* ^9 r
使用ADC1采集通道19(PA5)上面的电压,然后通过串口UART4打印ADC转换值以及换算成电压后的电压值。同时程序中通过LED0闪烁来指示程序在运行状态。( k  l8 U5 W0 B% s9 Y1 C7 i
开发板上有引出PA5引脚,先使用跳线帽将JP2排针的ADC1和电位器的RP_AD连接,这样PA5就连接到电位器VR1上了,电位器上接的是3.3V,用户可以通过调节电位器的旋钮改变接入到PA5上的电压值为0~3.3V(实际上就是通过改变电阻来改变电压)。实验前要记住检查跳线帽是否有接好:; i+ ]/ i; ?4 f" T. b# Y2 F

2 f6 [6 u! Q9 H! e+ ^( b5 ~ b0ee340ceeae4e7fb583b3e228b07b1b.png 1 ]4 s- C+ w5 L  p' r0 J1 s9 A6 @
3 c( Z  R6 o/ y4 A+ C( B
图22.2.3. 1硬件部分# M! x/ \* ?; v" `' Y
开发板上有1 组 3.3V 电源供应接口JP7,JP7排针有3路输出3.3V,另外3路接地(0V),我们也可以使用这个排针来测试,接入到被测通道中。注意的是,千万不要接错旁边的JP8排针引出的5V引脚,否则烧坏IO口甚至整个主控芯片。
, d2 l1 C) b8 n( e4 |' X" `2 n5 t. V2 n$ i" r0 k$ l
8242de4f66854b7a987c0ad21403f808.png % J5 P1 c; f5 F2 {

" \+ ?! ^, t% Q; `图22.2.3. 2开发板的电源输出接口
% S" j) k& |4 H, n9 S2. 硬件资源0 n( M( X+ ~- O, `$ _% \- o
1)LED灯:LED06 J8 M/ X1 e" i( E7 s' {! u0 R
2)串口40 i2 E9 V8 u( Z- g5 H7 G( C
3)ADC1的通道19引脚(PA5)$ u: I6 t* ^; v, v
LED0 UART4_TX UART4_RX ADC1_INP193 B! g, @6 W' W4 K% q3 K  F/ o
PI0 PG11 PB2 PA52 m$ R8 B" Q5 \' S( `
表22.2.3. 1硬件资源, b" Y7 W7 G1 }. ~* L0 Y1 U1 \
3. 原理图
# w" ~1 y- V" `6 o( j& {ADC属于STM32MP157的内部资源,实际上我们只需要软件设置就可以正常工作,不过我们需要在外部将ADC1的通道19引脚(PA5)连接到被测电压点上面,本实验的被测电压是来自开发板自带的电位器上的电压,电位器可调节的电压范围是:0~3.3V。当然也可以使用ADC1测试其它外接入的电压,只需要一根杜邦线将要测试的电压引入PA5引脚即可,要注意接入的电压范围不能超过3.3V,否则可能烧坏我们的ADC,甚至是整个主控芯片。7 G! r$ h8 M( `* D* J

/ C1 i* q/ o. M0 L0 \ c7f56758b1ea4944b78d6b7ea8db9369.png
6 t: i1 J. j& Y2 K" z* Z( G  j  O9 g9 b6 L; J5 F
图22.2.3. 3原理图部分
  ^0 i7 ]- Q' X+ g. m9 P7 i
) b- R, \, [6 N7 c, p+ P0 T22.2.5 软件设计
' F% c$ N- s1 @1 b7 `7 ~. [本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ ADC1_SINGLE_CH。. T) y) U. G) Y/ o: k' h0 v8 P, N

/ D' l3 j9 r  Y3 q8 |. r1.新建和配置工程
: r: x9 L; Z# ?3 c& S* ~5 p(1)引脚配置
6 Y+ A+ ^$ u/ q4 D1 F新建工程ADC1_SINGLE_CH,然后配置LED0、UART4,这两个配置方法我们前面实验多次介绍了,这里就不再赘述,这里UART4使用中断,UART4的配置请参考前面串口通信实验章节。
- e8 U5 D: \$ X9 F/ @8 n1 a; X6 r接下来配置ADC1引脚,本实验我们使用的是ADC1的单端输入功能,在Pinout view处选择PA5引脚并配置为ADC1_INP19,我们前面说过,ADC_INP[19:0]可以做单端输入,这里选择通道19。配置ADC的通道的话,也可以直接在AnalogADC1处配置ADC,选择对应的通道以后,Pinout view处也会自动配置好,可以参考后面的(2)配置ADC参数。+ T2 x8 ?4 }) j1 y' E

4 ^, ~: w  B  Z( s 6e84b0e1c78f48d3901728ed2c574678.png   J8 D0 x# j0 u/ @3 `1 L9 T

1 t& f- k' Q! p1 _$ i图22.2.4. 1配置PA5引脚7 z! p9 \3 n- i1 h7 |
(2)配置ADC参数/ b1 H& m3 p) t3 q9 B# n9 X( ?
在AnalogADC1处配置ADC。先选择ADC1给M4内核使用,如下所示,有IN0~IN19,表示19个通道,ADC1的通道0、6、7、8、9、13、14、15、17、19只能配置为单端模式,而其余通道可以配置为单端或者差分模式,本实验用到ADC1的通道19的单端输入,所以勾选IN19 Single-ended,我们没有使用硬件触发,使用的是软件触发,所以EXTI Conversion Trigger选项要Disable:
% d' M8 A0 D- l
% B' y8 `, Q3 k8 a4 V0 l4 F+ o2 } d46c621656324fedaddc02028c995614.png
- C  t! v+ `- a4 K+ J. a
! ]# z$ c/ w# `) B5 D图22.2.4. 2 ADC1模式配置! ^7 ~6 Q) k5 \) [9 p2 b6 Y( c
接下来配置ADC1的初始化参数,如下:2 o: j3 Q: [6 @: s1 U% {

& ]2 p* m1 e5 q6 p* @$ M 658424d8ffd94cb6bf93d9bbbeea52fb.png
/ r7 [' B, K7 _3 q: o$ Z5 ~6 g5 w" C( B& r
图22.2.4. 3 ADC初始化参数配置6 k9 F2 n- H# @: o! ]
对以上的ADC参数配置选项进行介绍:
* y2 |7 s* h7 g  v2 G' ~! b% L●ADCs_Common_Settings(ADC工作模式配置):4 Z6 ~/ Z* j8 D9 S+ P
这里配置为独立模式,独立模式是指在同一个管脚上只有一个ADC采集该管脚的电压信号。如果只是用了一个ADC的时候就配置为独立模式。
) m7 v' U' I% ]# N/ x2 s除了独立模式,还有双重模式以及三重模式等多重模式,多重模式是指双ADC共同工作,如果需要两个ADC同步的话,则使用此模式。
- x2 x, h+ s5 Q' C7 J0 N; v+ X6 ?●ADC_Settings(ADC参数设置)
7 i) Q0 V* W: O3 @2 ^* GClock Prescaler用于配置时钟分频,这里选择2分频;, [1 X4 n  I! I
Resolution用于配置ADC的分辨率,这里选择16位;
$ M6 A7 x# @2 y0 O4 iScan Conversion Mode用于配置扫描模式,当有多个通道需要采集信号时必须开启扫描模式,此时ADC将会按设定的顺序轮流采集各通道信号,单通道转换不需要使用此功能,这里选择Dsabled;/ T! S' o5 ?8 D
Continuous Conversion Mode用于配置自动连续转换还是单次转换。使用Enable配置为使能自动连续转换;使用Disabled配置为单次转换,转换一次后停止需要手动控制才重新启动转换,我们选择Disabled;' d) e, I) ]3 Z. h0 x4 y$ c* J: W
Disabled Discontinuous Conversion Mode用于配置是否使用不连续的转换模式,所谓不连续,比如要转换的通道有1、2、5、7、8、9,那么第一次触发会进行通道 1 与通道 2,下次触发就是转换通道 5 与通道 7,这样不连续的转换,依次类推。这里我们选择禁用不连续的转换模式Disabled;
, e. q5 y+ P" H. \End Of Conversion Selection用于配置转换方式结束选择,可选择单通道转换完成后EOC标志位置位或者所有通道转换成后EOC置位,也可以选择转换序列结束后EOS置位(配置为End of sequence of conversion),这里配置End of single conversion,即单通道转换完成后EOC置位;& W5 W2 ~3 e" ]5 b0 e
Overrun behaviour用于配置有新的数据溢出时,是覆盖写入还是丢弃新的数据,我们选择覆盖写入新的数据;2 Z9 R3 P  s2 j% S* C
Conversion Data Management Mode用于配置转换数据管理模式,我们选择Regular Conversion data stored in DR register only,即将常规转换的数据存储在DR寄存器中,另外还有DMA相关以及DFSDM,这两个本实验我们不使用;0 E$ @% C) g7 G* e) v4 I
Low Power Auto Wait配置是否使用低功耗自动延迟等待模式,当使能时,仅当一组内所有之前的数据已处理完毕时,才开始新的转换,适用于低频应用,该模式仅用于ADC的轮询模式,不可用于DMA以及中断,这里我们选择关闭。0 a* `3 @. e/ r7 Z
●ADC Regular_ConversionMode(ADC常规通道转换模式)
+ x( y2 B' ^* K6 ?- a2 nEnable Regular Conversions选择Enable,启用常规转换;& y, S8 E- R9 \+ r) C- q
Left Bit Shit 数据左移位数,最多可支持左移15位,这里选择没有位转移No bit shit;
$ X9 ]5 R7 x/ N$ XEnable Regular Oversampling用于配置是否使用常规通道过采样,这里不使用此功能;
/ V* k2 H* a! LNumber Of Conversion转换通道数量,此参数会影响可供设置的通道数,按实际使用的通道数来选择即可,这里是1;  }) ]! o/ j: T+ f- c1 u& r: m
External Trigger Conversion Source外部触发转换模式配置,ADC在接收到到触发信号后才开始进行模数转换,触发源可以是定时器触发、外部中断触发等硬件触发、也可以是软件控制触发,这里选择软件触发,工程中需要我们添加启动ADC的代码;0 r" L; S: G3 O0 u7 ?( d
External Trigger Conversion Edge配置触发边沿,这里选择无触发边沿;1 h7 V4 q5 Y9 [; F
Rank配置模拟信号采集及转换的次序,默认是1,其中:
$ [! a5 R9 Q6 ~2 b# b" D, BChannel用于选择转换的通道,这里选择通道19;
+ |, i, X9 G1 t! c3 |Sampling Time采样周期选择 810.5Cycles;: V# X  ]1 J: M* B$ b2 L9 B
Offset Number配置偏移量的通道选择,这里选择无偏移通道;8 w8 R. C! W* p' D
●ADC_Injected_ConversionMode(ADC注入通道转换模式)' E/ q6 ~/ j& T6 D- u: l
Enable Injected Conversions用于配置注入通道转换模式,实验中我们不需要使用注入通道,所以此项配为Disable。
8 J8 P4 O6 @( e3 [6 @) |, c后面的是模拟量看门狗的设置,本实验我们不需要,不配置即可。
9 I0 v& b1 Z. o  y5 d(3)UART相关的参数配置4 L" X1 ~" `" R1 D) J5 [3 b5 t& T( D3 y
UART4的参数配置以及中断配置请参考前面串口通信实验5 X( j" i, b0 y# y2 V# Z! p
(4)时钟配置
* K% h- c, [4 c  _$ j; M4 G9 l# K这里配置MCU的时钟为209MHz,使用外部时钟HSE,这部分配置可以参考前面实验章节:& Q4 Q3 C. i4 f0 F, o

2 }2 P& G; l/ f; u9 r7 \3 p2 _! i f9e02b3cd98b40e2af1d2d1d6eec1cb3.png 0 [( {+ Q$ m3 H4 A

& D. B) F' M6 v% }3 P4 a# `$ B图22.2.4. 4时钟树配置
+ k: ~3 H, C; o  ^2 W( o, bADC1的时钟使用PER,其中PER时钟源默认使用HSI,即为64MHz。上面ADC参数配置中,我们配置分频系数为2,所以实际ADC的时钟是32MHz。0 L9 ~3 [: }% R- |* K' ~3 K  c
2 n0 o1 T$ C: B6 y8 }6 U; d
ba47dc95d3c7450ca00db0aebb36b3c5.png 9 B! d5 |; ]+ R! j% M# H" k) l

) m3 q8 P  `7 l图22.2.4. 5ADC时钟源选择
5 m, u- G) S9 X0 p. ]1 F& P) Z(5)配置生成独立的.c和.h文件8 C6 F% \" m+ e" X

# T7 _/ r8 [7 ^! Y: `0 V7 s 91c852fa07134e9d902bf57265a7cb4f.png   l3 v5 Q6 Z7 D7 [( X+ Q: G  l  s
+ B& |# o- K1 T, t/ g0 \: }
图22.2.4. 6配置生成独立的文件
' b2 Y5 K) u" X4 ~; s2. 生成工程
# y9 r) r9 P/ Y! b如下生成工程,本实验中使用到LED0,所以将前面实验使用的BSP文件夹拷贝到Src目录下:
7 T# k- g# X, F2 D, o* Q0 q
1 q0 m' R' V7 L- l" y* S% S1 r ce4c1c0cbb3f4e7dafacf98ec42151d2.png + d' ^7 C  J. \) L5 W$ n8 [( f
7 j3 R% w" h7 S
图22.2.4. 7生成工程  H  Z/ q, R; W; b: B
4. 初始化代码分析
* k: D, \( F8 p9 MADC的初始化代码均在adc.h和adc.c文件中,其代码如下,代码中已经附上了详细的注释:2 ~0 z8 E! [  f! `

8 G6 Y% z. G! M2 ?/ `1 `+ L
  1. 1   #include "adc.h"5 e( e) ~" O5 ^6 z
  2. 2  
    & q' g6 U3 N6 X0 q0 Q
  3. 3   ADC_HandleTypeDef hadc1;                /* ADC句柄 */
    / k+ P# X! a- N" L# L, ?
  4. 4  6 M, }% _' n# }# b& ^
  5. 5   /**
    " l- V" h  a7 j6 c" l& F; b! k" I
  6. 6    * @brief     ADC初始化函数
    3 K5 ?- F1 R4 ^! p
  7. 7    * @note      本函数支持ADC1/ADC2任意通道
    7 H4 g6 ?0 ?. [( q5 F- i
  8. 8    *             我们使用16位精度, ADC采样时钟=32M, 转换时间  _  |. N0 @0 o/ J5 v" j
  9. 9    *             为:采样周期 + 8.5个ADC周期' c6 H8 Y7 q" R
  10. 10   *             设置最大采样周期: 810.5, 则转换时间 = 819个ADC周期 = 25.6us
    $ Z* e7 ~) Y: i' o& r/ U. P
  11. 11   * @param       无
    : d; h  U9 S- ~' x
  12. 12   * @retval      无
    : c/ w' W  P! u  Z
  13. 13   */
    4 x& e$ n0 I& k! ]
  14. 14  void MX_ADC1_Init(void)0 H# L" _" E+ K- n  s+ }0 f8 S$ ?
  15. 15  {
    . Z& k" B& \% t4 M4 O0 l1 k5 Q
  16. 16    ADC_MultiModeTypeDef multimode = {0};3 ?7 ]+ J8 f) H  Q. r) @
  17. 17    ADC_ChannelConfTypeDef sConfig = {0};
    6 y" T4 E; P! Q& X8 U) {2 ?
  18. 18
    & g! \/ s, O4 e
  19. 19    hadc1.Instance = ADC1;                 /* ADC1 */7 R. h; G1 \, }
  20. 20    /* 输入时钟2分频,即adc_ker_ck= PER/2=32MHz */2 _$ v5 r7 p3 p1 u( _

  21. , X' Z0 |: t- c" z
  22. 21    hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2;- f9 A+ X; p% w- R
  23. 22    hadc1.Init.Resolution = ADC_RESOLUTION_16B; /* ADC分辨率为16位模式  */  z2 ^# ^$ s) k
  24. 23    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;/* 非扫描模式 */  t7 c% ]2 n8 \. X
  25. 24    hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;         /* 关闭EOC中断 */
    # p6 [) f* Q4 K9 x8 s6 l1 C) o$ I
  26. 25    hadc1.Init.LowPowerAutoWait = DISABLE;                        /* 自动低功耗关闭 */0 z) x" T2 {# z) i
  27. 26    hadc1.Init.ContinuousConvMode = DISABLE;                        /* 关闭连续转换 */) L; s6 l9 x9 G
  28. 27    hadc1.Init.NbrOfConversion = 1;                                /* 使用了1个转换通道 */2 X; I9 c& x+ l
  29. 28    hadc1.Init.DiscontinuousConvMode = DISABLE;/* 禁止不连续采样模式 */2 T; Q# O- t' E9 D) s& R5 Z; @" U
  30. 29    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;/* 软件触发 */& d1 l+ @4 `  E0 D; Y$ t( K7 J$ h
  31. 30    /* 采用软件触发的话,此位忽略 */
    , S; ~8 m+ h0 ?8 v
  32. 31    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;, `, X& G7 g% m+ u$ X/ V
  33. 32    /* 规则通道的数据仅仅保存在DR寄存器里面 */' M! `' t. W1 y- M+ @
  34. 33    hadc1.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
    3 m6 O) V* H# I6 L0 H. n
  35. 34    /* 有新的数据后直接覆盖掉旧数据 */
    8 t, g+ R  b9 t. x4 X4 e! y- U
  36. 35    hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;, Y8 }2 o* r( y- u% V& K; m! k
  37. 36    hadc1.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;/* ADC无位移位 */
    / x8 A6 _% V/ J/ g- v! u+ r
  38. 37    hadc1.Init.OversamplingMode = DISABLE;                        /* 过采样关闭 */
    ! U& i% n% S% D' ]) [4 ]. O$ `
  39. 38    if (HAL_ADC_Init(&hadc1) != HAL_OK)                         /* 使用HAL库初始化ADC */
    % d. j- m& V" i0 V' ?' T  V
  40. 39    {: h4 s/ p; {$ p  v* t0 c
  41. 40      Error_Handler();
    2 ~/ ]" Y+ ?+ |4 k
  42. 41    }
    ! u) g: ]6 Z# M! j
  43. 42    multimode.Mode = ADC_MODE_INDEPENDENT;                /* 独立模式 */- z5 @3 c& }, m% e* X4 E
  44. 43    /* 独立模式通道配置 */
    % u' P: B) N  d- o( D
  45. 44    if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
    ) ]0 `7 R8 w0 m  W
  46. 45    {
    1 A7 {. l" ^( [0 ?5 C# F
  47. 46      Error_Handler();4 b* x: }; c0 g$ y3 i4 N- h
  48. 47    }
    + Y. v& V/ B% [! D
  49. 48    sConfig.Channel = ADC_CHANNEL_19;         /* ADC1的通道19 */
    2 U/ o1 u. [0 t$ |& C3 v* Z5 ?
  50. 49    sConfig.Rank = ADC_REGULAR_RANK_1;        /* 1个序列 */
    , O/ y7 ?3 O$ Z/ B4 _+ ]
  51. 50    /* 采样时间,最大采样周期: 810.5个ADC周期 */0 J: g8 h0 H9 n' i
  52. 51    sConfig.SamplingTime = ADC_SAMPLETIME_810CYCLES_5;& j' Z8 R3 D9 ]" o6 G3 a+ p9 E
  53. 52    sConfig.SingleDiff = ADC_SINGLE_ENDED;        /* 单边采集 */
    7 C" R8 Z4 w5 y8 M! j- P: d& Z; g3 s
  54. 53    sConfig.OffsetNumber = ADC_OFFSET_NONE;        /* 不使用偏移量的通道 */: S4 ]/ \1 b: W( d. L. N
  55. 54    sConfig.Offset = 0;                                                 /* 偏移量为0 */! o. D6 F& S1 f+ l' t) C4 g' y0 D
  56. 55    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)/* 通道配置 */
    0 ^0 O$ U" y$ T% C) ?
  57. 56    {
    : y( a5 B. G% m6 P& S" B
  58. 57      Error_Handler();
    : I7 H$ y4 q* p  m3 {
  59. 58    }
    , Z6 B$ [% P9 R/ h
  60. 59  }
    3 {% G: a* h: f- r1 R
  61. 60  /**
    " E/ X3 @$ z! x$ z' m. f1 H8 f8 G0 A
  62. 61   * @brief       ADC的GPIO初始化函数# b- H" ?& h7 g( W$ H! l, T9 `
  63. 62   * @param       无# a0 X4 W( D) E9 g* C  O
  64. 63   * @retval      无. x* v0 X  W8 B; u
  65. 64   */
    - P# @# o, c' o5 z' \
  66. 65  void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
    ; ~8 |) c2 ]# c2 a  N
  67. 66  {/ ^# a% [3 A* ^
  68. 67    GPIO_InitTypeDef GPIO_InitStruct = {0};, I/ h1 m7 \+ T. T" r
  69. 68    RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
    4 \# ~. @0 I: ?9 }3 l
  70. 69    if(adcHandle->Instance==ADC1)& p9 I/ ~2 s: ]
  71. 70    {  b& K+ ]$ O3 L4 U# y" I% D
  72. 71    if(IS_ENGINEERING_BOOT_MODE()): y: U0 y; e7 _5 X. K; U
  73. 72    {
    9 `' `6 I, a$ `: [) B4 x3 D
  74. 73        /* ADC外设时钟配置 */
    ; C4 \- [" _2 K2 l8 X
  75. 74      PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;3 ?1 M3 O% S; q# z* j7 }# f
  76. 75      /* ADC外设时钟选择的是PER */
    $ v* r: x- Z/ |" x0 M4 V
  77. 76      PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_PER;  x" k& r- W1 c0 `9 ]
  78. 77      /* 配置ADC的时钟 */: Q4 i. Z) Q  j% ?& U4 s7 ~
  79. 78      if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)2 t# e% f& ~3 j
  80. 79      {6 @$ \! w, u; \" l
  81. 80        Error_Handler();: [! N& c6 }1 s) Q
  82. 81      }
    / D0 D# L# {& S2 l
  83. 82    }
    2 A! C1 f# c, M" J
  84. 83      __HAL_RCC_ADC12_CLK_ENABLE();                /* ADC时钟使能 */" r& l6 i* s$ R) K
  85. 84      __HAL_RCC_GPIOA_CLK_ENABLE();                /* GPIOA时钟使能 */5 c, b" k6 c! P2 l  L4 Y) U
  86. 85      /**ADC1 GPIO Configuration
    * G' C; j4 \9 P) N
  87. 86      PA5     ------> ADC1_INP196 G* V( S0 W. N8 ~# p9 Z
  88. 87      */0 X: a. H5 o# g+ W* Y1 c  Y, h, r3 w6 e
  89. 88      GPIO_InitStruct.Pin = GPIO_PIN_5;                        /* PA5引脚 */: U: S8 ~- c# D& v! R! H
  90. 89      GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;        /* 配置引脚为模拟模式 */4 {$ b. a  d/ c- D; v+ j% q
  91. 90      HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);/* 使用HAL库初始化GPIOA */
    : k5 E1 \$ M6 m2 L
  92. 91    }4 A) {! _" F5 Q! J  W0 ~2 o
  93. 92  }. v/ L: o9 s9 o" Y
  94. 93  /**
    2 H  O3 Z' @# Z7 d& h  {* [
  95. 94   * @brief       ADC的GPIO反初始化函数
    4 S" b- V+ C# _( q: E
  96. 95   * @param       无
    4 \: N% a2 ]4 O3 {, j
  97. 96   * @retval      无$ ?2 B- z9 {5 ^6 D7 D. f1 b" H
  98. 97   */
    6 J; p# x* ~# [7 S4 J5 H1 ]* l
  99. 98  void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
    $ e0 p& S; ^+ _0 G1 w& D* |9 O
  100. 99  {- H& S9 n4 s% g3 C0 E8 `% o
  101. 100   if(adcHandle->Instance==ADC1)
    . s! z% c0 e0 ^( b: f& `
  102. 101   {
    ' z+ s" y# X$ v6 c: H0 g
  103. 102     __HAL_RCC_ADC12_CLK_DISABLE();                        /* 失能ADC的时钟 */
    ' _* Z8 Y) o, G  Q' ]5 `6 @
  104. 103     /**ADC1 GPIO Configuration
    $ u0 d$ d) K2 t3 ~9 l
  105. 104     PA5     ------> ADC1_INP19) ]* ?: o* c( E8 g( \+ ]
  106. 105     */
    , e+ s4 F3 g% o! L3 O6 p1 Y; Y6 K
  107. 106     HAL_GPIO_DeInit(GPIOA, GPIO_PIN_5);        /* 失能GPIOA的配置 */
    ( |* B+ k) Q2 a2 T
  108. 107   }" \- ]. t- f  r1 t' o
  109. 108 }
复制代码
5.添加用户驱动代码7 O: z8 M5 j. x) _- r
(1)LED0驱动代码5 ~8 c& E* H1 F
本实验使用LED0来提示程序在正常运行,LED0的代码已经在上面的BSP文件夹里了。注意前面STM32CubeMX中要配置PI0,同时User Lable要配置为LED0。9 }' `" c) P' W+ }/ G. G2 l5 r
(2)UART4驱动代码
! \2 n- ?% x: G$ m1 H" I本实验需要使用UART4打印ADC采样转换后的数值,UART4的配置以及代码添加我们在前面的实验中多次讲解过,这里就不再赘述了,相关部分可以参考前面串口通信实验章节部分。. h% f3 x6 F% {/ G  I6 j
(3)获取ADC1通道的转换结果函数
4 n0 O+ f( F. e* Aadc_get_result_average函数用于校准ADC1、启动ADC1、将ADC采样值进行轮询转换、获取转换结果。该函数我们手动实现,直接在adc.c文件中添加,该函数内容如下:1 _2 G0 X$ c: O$ a( p" g3 N' ^! O
先在adc.h文件中添加如下代码,进行函数的声明:
& g! O$ v! B! y& @: M2 o/* USER CODE BEGIN Private defines /
) a& r7 Q/ a( u# f. i: K4 juint32_t adc_get_result_average(uint32_t ch, uint8_t times);  C! M) x# ?" Z7 N* n8 r' M
/ USER CODE END Private defines *// a( g# W2 I4 w  e/ ]/ A, e/ v
adc.c文件代码如下:
; I+ \) k8 m6 t% X5 y* J# P+ m
  1. 1   /* USER CODE BEGIN 1 */7 _. h% f  ~! B3 v) ]4 u8 ~
  2. 2   /**
    1 V& D2 g0 E2 g5 b. H. i7 E7 A
  3. 3    * @brief       获取ADC1通道ch的转换结果,先取times次,然后取平均
    ' E2 W5 s; @# l4 |8 K
  4. 4    * @param       ch      : 通道 ,为0~196 W0 W6 y! H$ U
  5. 5    * @param       times   : 获取次数1 P' n; }; }& F  d2 t$ i+ ]! g
  6. 6    * @retval      通道ch的times次转换结果的平均值
    - X8 S$ R: j' K0 }% \, z. J  o( P
  7. 7    */4 [: @  ?; C; m. H
  8. 8   uint32_t adc_get_result_average(uint32_t ch, uint8_t times)8 q1 O6 F/ G6 n9 r3 ]8 S
  9. 9   {' b  @2 t& f+ ?" k" g
  10. 10      uint32_t temp_val = 0;
    0 X8 i. s. C6 a1 A3 x  F' B. p0 a
  11. 11      uint8_t t;
    : V$ ]1 U6 \  \
  12. 12      /* ADC校准 */
    ; W) U; U3 M1 x9 g1 G; L
  13. 13      HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET,                                                                                                                                         ADC_SINGLE_ENDED);
    8 e" N: F. X4 r: }3 g3 k
  14. 14      HAL_ADC_Start(&hadc1);                        /* 启动ADC */( p7 d4 V, O! n3 u
  15. 15      HAL_ADC_PollForConversion(&hadc1, 10);     /* 轮询转换 */;, P' x* }' k1 Z8 M1 Q
  16. 16      for (t = 0; t < times; t++)                                         /* 获取times次数 */! C: s" W* c  F, P
  17. 17      {
    4 V* _: Q# L" f" m% I7 b5 [
  18. 18          /* 获取转换结果,并将每次转换结果进行相加 */$ Q# l$ B0 d2 F( w( q* w7 K
  19. 19          temp_val +=  HAL_ADC_GetValue(&hadc1);: i( h8 \. P' Y* `' |
  20. 20          HAL_Delay(5);
    / |; X- \$ T5 Z& f/ M
  21. 21      }
    ! H$ Z" w' M6 K3 }; W3 |
  22. 22      return temp_val / times;                                            /* 返回转换后的平均值*/3 a  R" t3 @( h3 f" L
  23. 23  }
    , k+ j2 W; R1 a# v, R9 W
  24. 24  /* USER CODE END 1 */
复制代码

% b9 ~9 r. s& }/ N9 S0 [第13行,进行ADC1校准,ADC校准需在ADC开始前或结束后,如果不校验,结果会有偏差;
) C5 l7 A# [' D3 n0 x- P 第14行,启动ADC1,我们前面配置的是软件触发模式,也就是需要启动ADC以后ADC才可以进行转换操作;+ `* P% C- O; r; h
第15行,ADC轮询转换,这里设置的是转换10次;1 M0 r. l: F6 J
第19行,将ADC 10次的转换的结果进行相加,ADC转换的值是保存在ADC_DR寄存器中的;. v" G& Q6 b* b2 A7 a2 G
第22行,返回这10次转换结果的平均值。
7 s3 B) o- {. {* `" I/ D( `- l& k0 u% Q0 \
(4)修改main.c文件
3 R) |. W- T. H& l* K# N  J我们直接在main.c文件中实现串口UART4打印ADC转换后的数据以及对应的电压值。main.c文件如下:5 x! n7 ^8 {! N( |$ Q" l1 g# F4 K
) u7 T2 M! U- v9 E) J
  1. 1   #include "main.h"4 z9 g4 K4 C  f9 ~* v* u
  2. 2   #include "adc.h"
    " P: F/ ~- U3 x7 E* |& B
  3. 3   #include "usart.h"
    : }- r) t; ~  X+ {$ F; W
  4. 4   #include "gpio.h"
    ' v! ~# C8 }1 A0 D0 S
  5. 5 & c8 v. l, L% C9 r
  6. 6   /* USER CODE BEGIN Includes */
    3 L3 g) C# P9 A
  7. 7   #include "./BSP/Include/led.h"' _3 X8 T& u7 N' ?3 P2 T
  8. 8   /* USER CODE END Includes */
    8 w' `0 }  o4 D
  9. 9 7 V5 V( m# h7 G1 j) a
  10. 10  void SystemClock_Config(void);. l& Q2 Z' a- y7 A' h! A: M5 X( @
  11. 11  void PeriphCommonClock_Config(void);7 n7 }' \& S& M' J* s/ }9 F! s& X
  12. 123 r& k) o+ [3 b4 ^1 R# ~! U) ]
  13. 13  int main(void)( G/ P' b2 `$ H+ F4 h
  14. 14  {
    , `( U, H/ I3 j: F
  15. 15    /* USER CODE BEGIN 1 */
    ' ^8 J. R, q( z, g, s! y) O1 e  R
  16. 16      uint16_t adcx;
    $ k) S; \* O/ X; v% e/ N. m, z
  17. 17      float temp;/ x% u0 G- ]; M. j: l6 [7 w
  18. 18      uint8_t i = 0;' K, u3 g4 O) T; c' M8 Z4 Q
  19. 19    /* USER CODE END 1 */
    0 [; L, A2 b# N5 [/ ]& c
  20. 20
    $ R) J9 y, d& A, G) ]6 U0 V
  21. 21    HAL_Init();                                                                         /* HAL库初始化 */
      @, K. O) a6 R7 K0 [7 @+ q: i
  22. 22# T  l  x# m: _1 A8 \/ N' d% ^
  23. 23    if(IS_ENGINEERING_BOOT_MODE())% g7 z$ `# m; Z% k2 z5 ?& _: N
  24. 24    {% p" I6 F3 x- f/ q$ \
  25. 25      SystemClock_Config();                                        /* 系统时钟配置 */5 L. K% u! ?( y- n: {
  26. 26    }& L. o# p7 {- ?3 t9 G$ w7 V
  27. 27    if(IS_ENGINEERING_BOOT_MODE())) L% v, @, z  @
  28. 28    {
    8 s2 e! w# n) o
  29. 29      PeriphCommonClock_Config();                                /* 外设时钟配置 */
    " S& R; i/ D' _' f! D
  30. 30    }* L# M; _" k2 Y0 \6 x# r- n
  31. 31    MX_GPIO_Init();                                                                /* GPIO初始化 */
    $ c' F% B8 G5 A7 W4 e  ^
  32. 32    MX_ADC1_Init();                                                                /* ADC初始化 */
    * e8 ?9 t2 F; ?( |- Z! C* ~. P
  33. 33    MX_UART4_Init();                                                        /* UART4初始化 */
    1 S2 f& e& c3 x* Y8 z6 Z8 l  i7 I5 E
  34. 34    /* USER CODE BEGIN 2 */8 y  K: D/ M7 ^# v4 b' e& V( b
  35. 35    HAL_UART_Receive_IT(&huart4,&RxBuffer,1);/* 以中断方式接收函数 */2 b8 K5 Q/ h9 o8 Z$ H8 T! _
  36. 36    /* USER CODE END 2 */3 `' {0 d$ ]8 q, c' \" j7 Q
  37. 37, W. q5 Y$ v9 P) e9 \! G5 I
  38. 38    while (1)
    7 P0 H1 O/ x1 l8 C2 `
  39. 39    {
    . L% T- G: I- V# \1 n: F# E
  40. 40      /* USER CODE BEGIN 3 *// ]. ^' a* g" g& R
  41. 41      i++;
    5 E. G' K: C& y$ t" u5 _. z
  42. 42      /* 获取通道19的转换值,10次值取平均 */9 N* w. [& Y5 z* X1 K
  43. 43      adcx = adc_get_result_average(ADC_CHANNEL_19, 10); 5 d# k) p- |9 ^# T1 U) Z
  44. 44      /* 获取计算后的带小数的实际电压值 */
    $ E- t6 ?% x' N4 h/ h3 ~# T  s
  45. 45      temp = (float)adcx * (3.3 / 65536);             4 y6 s2 t' Y5 w, B
  46. 46      /* UART4打印值 */: @  M1 Y, ^  v6 X7 V
  47. 47      printf("ADC Value = %d, Voltage = %.3fV\r\n", adcx, temp);" N6 d% k5 @) E1 i
  48. 48      if(i == 5)% n  S; l8 Z3 \) i, u: L- |
  49. 49      {, O) k  N2 X( f- v
  50. 50          i = 0;$ A- {, }9 q& e1 ?& E+ P# ]& P
  51. 51          LED0_TOGGLE();                                        /* LED0翻转 */
    8 D) `/ L1 J! H
  52. 52      }
    % i8 ?1 C& A% `) c) V
  53. 53      HAL_Delay(5000);                                        /* 延时5s */4 ~4 u# g& F; N* H1 o0 T: ?+ S& G
  54. 54    }2 n1 t/ q: E" g4 A. j
  55. 55    /* USER CODE END 3 */
    1 q% _. X3 c! H& [  S) Q  |
  56. 56  }
复制代码

( |* a. x7 H& n. b红色字体之间的代码是我们手动添加的。
. E4 {2 H( X8 ^" H第43行,ADC1通道19的转换值记录在adcx变量中;8 g" j' \" C  F/ f
第45行,计算ADC转换值对应的电压值:
2 L$ S# Q( P/ N采集到的电压值=*参考电压
. C5 o: m2 t+ H- d" f9 b这里,我们前面配置ADC的分辨率为16位,也就是2^16=65536,参考电压是3.3V。' @' f& J2 J' j' c! A9 {8 m
第47行,将转换的ADC的值以及计算出的参考电压值通过UART4打印出来。4 l& d1 h" d# W7 b/ j
5 R" t, m/ K3 p6 X2 \/ g+ W7 }
22.2.6 编译和测试7 T( x# I" v8 D( S
编译前需要设置M4支持浮点数据打印,设置如下:8 ^" W& W3 f+ l. \, B
' N8 j4 S7 ?1 A% n6 |/ }0 G7 N3 {8 K
e7f8d70499134a03972353cc76d04b25.png   h8 \+ }' P$ W9 I
, g! \( k1 r. _4 Q0 }
图22.2.5. 1配置M4支持浮点打印+ e% N! j- q/ w$ k7 N
设置好以后编译无报错,测试结果如下,用螺丝刀拧动电位器,改变接入测试点PA5的阻值,接入点的测试电压将会变化,串口打印数据如下(每隔5s打印一次):2 }1 ?4 N; H6 N) B4 S* N7 C7 h
" ?0 \' x# E* l7 C( u) K3 T
29c02a6d9fae4f5087cf9d3a0f79894f.png ! F  z7 q4 q5 Y$ r, }9 ^: V

" j2 v( I+ s/ J图22.2.5. 2运行结果
+ n0 o+ U6 X0 M1 I) z' F
& m! p" C( p/ _6 {; H5 O4 M7 h22.3 单通道ADC采集(DMA读取)实验" k, Z( ?5 x1 K
本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 15-2 ADC1_SINGLE_DMA。1 l4 b4 `) N$ y9 |1 S
22.3.1 DMA方式传输数据
. Y: \% o5 a4 x3 u1 a0 p" L本实验我们来学习使用常规单通道的连续转换模式,并且使用DMA将ADC转换后的数据传输到内存中,再从内存中获取数据进行后期的计算。
' Q2 c* ?( b! W8 Y9 P* k7 h! b' `, ^8 T
1.DMA方式传输的优势
$ Y2 I" t" p% A" _$ o- X# r! F在上一章实验中,我们学习了ADC常规单通道的单次转换,我们使用HAL_ADC_PollForConversion函数先等待ADC转换完成后将转换值存在ADC_DR寄存器中,程序中使用HAL_ADC_GetValue获取转换值,然后再进行后期运算的,运算完成后就使用UART4发送转换和运算的结果。, W  J) h- ^; z8 B
我们知道,ADC根据转换组的不同,ADC转换后的数据存放的位置不同,常规组转换后的数据是存在常规数据寄存器(ADC_DR)中,注入组的数据是存在注入数据寄存器 (ADC_JDRy)中的。常规通道有16个,注入通道有4个,以常规通道为例,常规数据寄存器(ADC_DR)只有一个,如果要使用多个通道进行转换,转换后的数值都保存在ADC_DR寄存器中了,如果不及时读取和处理转换后的数据,上一次的转换结果可能会被下一次的转换结果覆盖掉,势必造成数据的错误。如果使用DMA,ADC会在每次通道转换后生成 DMA 请求,DMA把转换完成后存在ADC_DR寄存器中的数据传输到内存(软件选择的目标位置)后再进行处理,而且使用DMA传输数据,无需CPU干预,可以减轻CPU的负担,使CPU更高效地执行其他任务。
6 }/ v. W  ~0 X! U5 b# ~. H2.数据管理" P3 S0 f; o' Q; P
●数据溢出4 _* Y4 h5 \4 v9 j6 ^$ k7 U3 ~
使用DMA传输还会存在一个问题,如果因 DMA 无法及时处理 DMA 传输请求而发生溢出 (OVR=1),ADC 会停止生成 DMA 请求,而ADC仍在继续转换数据,新转换对应的数据不会通过 DMA 进行传输,所以我们要进行相应的处理,所以我们可以使用ADC_CFGR寄存器的OVRMOD位来控制,此位用于管理数据溢出的方式:
4 I) v  b% t" r2 h( P3 Z此位写0,当检测到溢出时,ADC_DR寄存器与旧数据一起保留;
# z1 v  \* F* `: F" o; L; D% G5 u$ M此位写1,当检测到溢出时,ADC_DR寄存器将被上一次转换结果覆盖,旧的数据被丢弃。
3 V, q9 K. v0 P( Q: g这个配置就是我们前面配置ADC参数时选择的Overrun behaviour选项:+ o3 T  D$ L, r4 q& L+ ?2 n9 H
* T- Q+ r6 N$ {1 H
450b8c6341844565abd159de8fd75e3e.png
3 i7 I3 H/ G0 f- O5 v# s; T) V' S5 P
图22.3.1. 1设置溢出数据处理方法
; H  u2 S( ~* X. a●DMA 单次模式和DMA 循环模式
& t1 O- Y7 M  M8 w! k根据DMA传输数据的数目以及DMA设置的模式不同,还需要配置为对应的模式,这里
: S4 v- {5 d, C9 x(1)DMA 单次模式 (ADC_CFGR寄存器的DMNGT=01):2 H2 O0 J* ]9 R, e+ Z) Z
如果将 DMA 设置为传输固定数目的数据就停止的话,可以选择此模式。
6 F+ n* [' s* P& z* ?, Z在该模式下,在转换固定数量的数据内,每次出现新的转换数据时,ADC 都会生成 DMA 传输请求,DMA 传输完最后一个数据时,(发生 DMA_EOT 中断),此时ADC还在继续转换,即使转换已再次开始,ADC 也会停止生成 DMA 请求。例如使用DMA传输5和数据,ADC转换一次就会生成DMA请求,5个数据就需要ADC转换5次,而ADC也请求DMA传输5次,当DMA将这5次传输完成后,如果不关掉ADC,那么ADC还在继续转换,而ADC不会在对DMA发起请求传输数据了。' T; }" G2 v7 X% h4 c
(2)DMA 循环模式(ADC_CFGR寄存器的DMNGT 位 = 11), z& x# j- _& x. R
如果在循环模式下对 DMA 进行编程,应选择此模式。
: ?& T  T6 X' r2 P6 s在该模式下,每次数据寄存器中出现新的转换数据时,ADC 都会生成 DMA 传输请求,即使DMA 已到达最后一次 DMA 传输操作也不例外。这样可将处于循环模式的 DMA 配置为处理连续的模拟输入数据流。8 C4 j. s& _* n+ l! S. Y" L3 m8 f
(3)还有一种模式就是使用 DFSDM(调制器的数字滤波器)进行管理,ADC 转换结果可直接传送到DFSDM中,这里就要求数据必须为 16 位有符号格式,任何超出 16 位有符号格式的值将会被截断,即ADCx_DR[31:16]无效,ADCx_DR[15]用于表示符号位,ADCx_DR[14:0]用于存储数据。传输生效后,会立即复位 EOC 标志。关于此模式,本实验没有用到。; r6 {. G5 c9 H& a$ X! K. {- U
以上讲解的3种模式,在STM32CubeMX下可配置,根据DMA的模式来选择,如下图, Conversion Data Management Mode配置用于配置转换后的数据管理模式:
1 t8 k/ _0 g8 g* [: PRegular Conversion data stored in DR register only表示常规转换数据仅存储在DR寄存器中;
' c% b, }3 S# y5 R2 t1 Q* eDFSDM Mode就是我们前面讲的使用DFSDM(调制器的数字滤波器)进行管理;
7 M0 U" t1 f& R- }$ u9 Y4 ^DMA One Shot Mode表示DMA单次模式,DMA Circular Mode表示DMA循环模式。
/ f; Y) p% g! g& @% _# X! n& ]  K* O6 r* `; H
157086a2c87f4f1188add48f012e1c09.png
6 h7 d8 K6 p9 Y8 }' ~2 V
% c2 d% w6 [+ o: u* K图22.3.1. 2设置转换数据管理模式0 V: F6 w0 ~5 b/ h# s, G4 }
22.3.2 ADC & DMA寄存器
2 `; f+ K- b1 X% ^, V* U3 r本实验我们很多的设置和单通道ADC采集实验是一样的,所以下面介绍寄存器的时候我们不会继续全部都介绍,而是针对性选择与单通道ADC采集实验不同设置的ADC_CFGR寄存器进行介绍,其他的配置基本一样的。另外因为我们用到DMA读取数据,所以还会用到DMA相关的寄存器。DMA相关的寄存器在前面DMA章节有讲解,部分相关的寄存器这里就不再赘述了。; e! j) H" `8 K5 }

6 s4 a7 W* W2 M9 d/ w: r1.ADC配置寄存器(ADC_CFGR)
6 P& ~. A* m: i! C. ?ADC配置寄存器如下图所示:
( C8 Q4 m$ e3 G7 s! r
% G# z8 `4 {$ M2 ^ a8673190b797433fbc66c49830b84684.png ' f0 v3 L1 ]! E4 Z% F

) G/ r" \5 ~& Q图22.3.2. 1 ADC_CFGR寄存器0 x; Q! j' L& h& G4 q5 C; @
ADC_CFGR寄存器中我们主要跟前面设置不同的有两个位,分别如下:' Q6 o! y$ p7 i- N7 j
CONT位,用于设置转换模式。单通道ADC采集实验单次转换模式,本实验中我们要设置为连续转换模式,所以该位设置为1。
+ }7 b7 n; @; b% k. r3 `DMNGT[1:0]位,用于数据管理配置。单通道ADC采集实验我们是默认设置为:
/ ^  ^2 D( r5 k# n00:常规转换数据仅存储在DR中,然后通过软件去DR数据寄存器读取;9 l$ o! Z. i! k5 R* h$ ?
01:选择 DMA 单次模式;  Y& _9 _1 V8 A- h3 K: z
10:选择 DFSDM 模式;
8 G( [7 H( a4 R2 E0 r0 L% U. Z11:选择 DMA 循环模式。
& F# h$ t  v0 P/ x8 j本实验我们可以设置为01,即选择 DMA单次模式,这样启动一次DMA传输,DMA就会自动读取一次数据。也可以选择DMA 循环模式,这样就需要配置DMA请求为循环模式。- e. k4 I" a) N1 v( a
/ o4 S# L5 d, |$ |$ X$ a5 f6 L( g
22fb5f5b7ca04e23b92926576e3d5a9f.png ; k9 [2 p7 i& O# B% ~7 B

; v8 \1 o, F+ m3 |7 Z2 T图22.3.2. 2设置DMA循环模式8 [9 `2 o* _7 L
本实验ADC的寄存器就介绍ADC_CFGR寄存器,其他的寄存器参考上一个实验的配置。下面介绍DAM一些比较重要的寄存器配置。
8 T% P; ]' L1 S2 N2. DMA数据流x外设地址寄存器(DMA_SxPAR)# U+ x* q6 C8 \, B2 @) i
DMA数据流x外设地址寄存器如下图所示:
/ H3 q9 m! b# V2 s, E. C1 G. a" z$ m+ a$ i# n1 N& R
70da0bdf4ce2494e909641c521e6ecec.png
% F( |3 Q: P# T. b+ o0 y0 x# G0 \6 ~( L' P3 {
图22.3.2. 3 DMA_SxPAR寄存器( ]) H. a8 i- g8 [3 w
该寄存器存放的是DMA读或者写数据的外设数据寄存器的基址。本实验,我们需要通过DMA读取ADC1转换后存放在ADC1常规数据寄存器 (ADC_DR) 的结果数据。所以我们需要给DMA_SxPAR寄存器写入ADC_DR寄存器的地址。这样配置后,DMA就会从ADC_DR寄存器的地址读取ADC的转换后的数据到某个内存空间。这个内存空间地址需要我们通过DMA_SxM0AR寄存器来设置,比如定义一个变量,把这个变量的地址值写入该寄存器。
! i9 _5 v# @5 O  [, x注意:DMA_SxPAR寄存器受到写保护,只有DMA_SxCR寄存器中的EN为“0”时才可以写入,即先要禁止数据流传输才可以写入。/ t2 S& X1 `" B+ u
3. DMA数据流x存储器0地址寄存器(DMA_SxM0AR)
; `# L) }2 d1 Q/ |) {DMA数据流x存储器0地址寄存器描述如下图所示:
  |: @1 d9 U+ `1 L1 K, V
2 m1 c% ^! d7 u9 K5 X9 J* p a6a585afd776487b80fbd2d798fa7601.png 5 H0 q  H9 m1 J8 r" n% u' q

* p. C7 }' L( l2 l; d% q图22.3.2. 4 DMA_SxM0AR寄存器$ w$ {2 k6 J" d& `4 R& n  Z+ d
该寄存器存放的是DMA读或者写数据的存储器的地址。这些位受到写保护,只有当禁止数据流(DMA_SxCR 寄存器中的位 EN=“0”)或使能数据流(DMA_SxCR 寄存器中的 EN=“1”)并且 DMA_SxCR 寄存器中的位 CT =“1”(在双缓冲区模式下)时才可以写入。如果用到双缓冲区模式我们还需要用到DMA_SxM1AR寄存器,本实验我们是用不到的。
* z4 P! }; ]! W. H( G8 \" q4. DMA数据流x数据项数寄存器(DMA_SxNDTR)+ d! Q& M" j$ w  b" R
DMA数据流x数据项数寄存器描述如下图所示:
: s+ l2 X$ b+ F. b/ i
! G$ C( b( i$ N: k$ x& n 16c85652040949609c50b7727ff53840.png
9 I6 }, E% l  ]0 R1 Q8 z# Y+ O5 r+ }, F: Z. ~2 P9 z, O; q
图22.3.2. 5 DMA_SxNDTR寄存器
" _% s. n$ n  U: n! C; M' \  iDMA_SxPAR寄存器是传输的源地址,DMA_SxM0AR寄存器是传输的目的地址,DMA_SxNDTR寄存器则是要传输的数据项数目(0到65535)。
/ d2 s7 Z; W& J3 C其他的DMA寄存器我们就不一一介绍了,请大家看着寄存器源码对照手册理解。
$ O9 ~( Q, Y4 x% |! C+ G22.3.3 ADC & DMA的HAL库驱动+ Y9 C# Q( z& y- i8 o. Y
单通道ADC采集实验已经介绍本实验要用到的ADC的HAL库API函数,这里我们要介绍启动使用中断的DMA传输函数和启动ADC(DMA传输)方式函数。
9 r6 U7 O* x) \% V4 g! D/ b% S8 ?8 ~0 `
1.HAL_ADC_Start_IT+ k. n# L5 w" `9 h+ k! X
HAL_ADC_Start_IT开启ADC中断转换函数明如下:
& u0 s0 e& n$ D; \2 e3 i5 W% l5 L$ j9 CHAL_StatusTypeDef HAL_ADC_Start_IT(ADC_HandleTypeDef *hadc)7 G' ?" w+ w( H5 a& ^+ q
函数描述:
9 \% J* j) J; Y4 R5 t  `5 c启用ADC中断常规组的转换。+ L+ j+ ^$ O% o/ {3 c
函数形参:* ^" }7 S+ d% Z- `: L
形参1是ADC_HandleTypeDef结构体类型指针变量。4 @3 n( B: k3 D9 @* m
函数返回值:
7 L& P. z7 x+ L4 {/ z) dHAL_StatusTypeDef枚举类型的值。0 q# c6 s% R3 I/ q/ a- _
注意事项:
! F# Q- o  m* X* A7 }8 y3 l, A* K该功能中启用中断:EOC(转换结束)、EOS(序列结束)、OVR溢出,每个中断都有其专用的回调函数。同时注意使用多重模式功能的情况,当多重模式功能可用时,必须先为ADC从设备调用HAL_ADC_Start_IT,然后才为ADC主设备调用:对于ADC从设备,仅启用ADC(不启动转换);对于ADC主机,启用ADC并启动多模转换。如果要停止转换,请使用HAL_ADC_Stop_IT函数。
7 Q7 n+ Q  k/ H9 k0 X用户可以重新定义ADC转换完成回调函数HAL_ADC_ConvCpltCallback以实现想要的功能。
! l( v, @, j, }8 _& s6 D$ {) A" Y2.HAL_DMA_Start_IT函数' d" d. l4 Y5 o! A, ~; R8 K: n
启动使用中断的DMA传输函数,其声明如下:8 }) P0 f6 x* \
HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma,: E, _! M' |7 a9 ~- Q
uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength);" E# [! u9 R2 X( `9 v4 H: K# f6 o/ A) @
函数描述:
2 e7 o% e. M" N! z用于启动使用中断的DMA传输。
% [/ [1 B; ]3 i" ]8 b2 U函数形参:
! e' s2 _# [* E7 a$ a) ^形参1是DMA_HandleTypeDef结构体类型指针变量。0 w" b- I' ], \* c$ m
形参2是DMA传输的源地址。% v2 U. k6 Y. C6 y+ S* |0 h
形参3是DMA传输的目的地址。# A0 G! g! u# b7 c5 e
形参4是要传输的数据项数目,实际上配置的是DMA_SxNDTR寄存器的NDT[15:0]值,要传输的数据项数目在0 到 65535之间。3 G* N/ c/ Q% a
函数返回值:
& B+ l8 d! x! ]& }, \/ ^* `/ rHAL_StatusTypeDef枚举类型的值。2 x8 @: w& ]. C: \
3.HAL_ADC_Start_DMA函数( u* `- {* O8 N8 j" v' E
启动ADC,开始常规组的转换,并通过DMA传输结果的函数,注意此函数的形参2和形参3是uint32_t类型的,其声明如下:5 Q4 U! M% `$ R5 H, H+ n: ~& k
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc,0 a. c1 l6 B6 k( W5 _
uint32_t *pData, uint32_t Length);
  y( n0 N: C. |( g函数描述:
( A) |/ o2 V; t* |2 }用ADC,开始常规组的转换,并通过DMA传输结果。
* P2 ~1 |) B! U6 h7 }函数形参:+ U4 V- n, x/ v
形参1是ADC_HandleTypeDef结构体类型指针变量。7 a; k1 G, S0 c8 i
形参2是ADC 采样数据传输的目的地址。+ }' Y2 p0 ~/ J* z
形参3是要传输的数据项数目, 实际上配置的是DMA_SxNDTR寄存器的NDT[15:0]值,要传输的数据项数目在0 到 65535之间。- }3 n4 R, @. B# v& j
函数返回值:4 Z) M8 v  A7 i+ f" @- _
HAL_StatusTypeDef枚举类型的值。
6 `. o9 Q6 I& O8 H注意事项:
* o) D* b1 m) E. ]' H. {HAL_ADC_Start_DMA和HAL_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函数。" m( z4 y$ Q7 i1 _& y5 E
启用多模式的情况(当多模式功能可用时),HAL_ADC_Start_DMA仅为单ADC模式设计,对于多重模式,必须使用HAL_ADCEx_MultiModeStart_DMA。# C7 U5 M1 u( R! h! b; N4 j
可以使用HAL_ADC_Stop_DMA函数停止转换和禁用ADC外设,ADC转换完成后,用户可以重新定义HAL_ADC_ConvCpltCallback或者HAL_ADC_ConvHalfCpltCallback(半传输完成)函数实现想要的操作。
& {2 I" y0 C' G' w+ N4.回调函数
; U" m* c2 v6 X: N不管是使用HAL_ADC_Start_DMA还是使用HAL_ADC_Start_IT,ADC转换完成以后都会进入回调函数HAL_ADC_ConvCpltCallback,我们可以在此回调函数中做文章,当ADC转换完成后执行某些操作。# U4 H6 H  G; Q, Q( Z% j; @
几个回调函数如下,用户可以自行定义回调函数以实现对应的功能:
3 B, Y( j0 N' [$ z/* 非阻塞模式下的ADC错误回调函数 */3 t3 i8 e3 r) }4 m8 s
__weak void HAL_ADC_ErrorCallback(ADC_HandleTypeDef hadc)
" C& a3 E& E; w3 f4 q! S3 \/ 拟看门狗1回调在非阻塞模式函数 */$ w$ J7 H5 r- ]& a* E
__weak void HAL_ADC_LevelOutOfWindowCallback(ADC_HandleTypeDef hadc)  E4 D% K* J& V4 L
/ 在非阻塞模式下完成转换回调函数 */1 c& [: r2 b/ _
__weak void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef hadc)
- Y/ R! m1 }! h. U: o/ 非阻塞模式下的转换DMA半传输回调函数 */, h  Q8 v  ?% ?" I, i5 j+ [
__weak void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc)
1 k& G& Z0 J9 b7 @0 z/ n% {5 J' |- ?/ [# W% ~+ P3 c" s
22.3.4 硬件设计9 ]5 ~2 ~/ a2 ^- T
5.例程功能6 w' l( @* \1 Z
使用ADC1采集通道19(PA5)上面的电压,然后通过串口UART4打印ADC转换值以及换算成电压后的电压值。同时程序中通过LED0闪烁来指示程序在运行状态。
' z' r7 E, r5 w, m4 h; T* z开发板上有引出PA5引脚,先使用跳线帽将JP2排针的ADC1和电位器的RP_AD连接,这样PA5就连接到电位器VR1上了,电位器上接的是3.3V,用户可以通过调节电位器的旋钮改变接入到PA5上的电压值为0~3.3V(实际上就是通过改变电阻来改变电压)。实验前要记住检查跳线帽是否有接好:
% ?8 i  _, N) I2 c
6 @- U4 w$ b' _! q6 {7 M 145973045fce4be5a74e59beb8c068fc.png 0 O( n7 d( p3 E8 w  z  B2 F

" u% `, S) D$ P" J6 f图22.3.4. 1硬件部分
2 W$ P+ X/ w6 {, f开发板上有1 组 3.3V 电源供应接口JP7,JP7排针有3路输出3.3V,另外3路接地(0V),我们也可以使用这个排针来测试,接入到被测通道中。注意的是,千万不要接错旁边的JP8排针引出的5V引脚,否则烧坏IO口甚至整个主控芯片。
" h3 L* ?1 |6 Y5 Y
' s) n4 e6 |" ^) S! I, _; G9 E 0f4126257cf24977a55eea326ab9ce40.png
) n4 z/ z5 P# t* ]3 G, k7 N. D# B: b2 M9 z- i! c
图22.3.4. 2开发板的电源输出引脚
+ ]' D1 I4 i' V, {* P' Z2. 硬件资源4 u/ f6 S# L! Q% r, C7 B. o
1)LED灯:LED0
/ \  `/ K* ?- M: ^. w2)串口4, q' O4 J' R6 V9 k% D( O, ^! v% U
3)ADC1的通道19引脚(PA5)
# T) q- J- v, F1 B9 P* U$ e4)DMA(DMA2 数据流7 ,也可以配置为其它数据流): {, V$ f4 N$ B% {& C
LED0 UART4_TX UART4_RX ADC1_INP19
& |. q& N  _6 h2 W  uPI0 PG11 PB2 PA59 f, S* E5 ]" }* d; ]
表22.3.4. 1硬件资源
- a3 P9 I& q/ c# x1 o% Q( n3. 原理图# C' {2 L  s* {
DMA属于STM32MP157内部资源,通过软件设置好就可以了。) H% U, p( |  M2 D. X
ADC属于STM32MP157的内部资源,实际上我们只需要软件设置就可以正常工作,不过我们需要在外部将ADC1的通道19引脚(PA5)连接到被测电压点上面,本实验的被测电压是来自开发板自带的电位器上的电压,电位器可调节的电压范围是:0~3.3V。当然也可以使用ADC1测试其它外接入的电压,只需要一根杜邦线将要测试的电压引入PA5引脚即可,要注意接入的电压范围不能超过3.3V,否则可能烧坏我们的ADC,甚至是整个主控芯片。
- |0 D2 T1 g& x# e
9 b; M2 K8 j. ]! ~/ a- M, U f819acead8674e82bfed02038ac8b364.png * G9 G  D9 }$ B' `/ j2 X
: s' L5 q. t3 }! I- j8 C
图22.3.4. 1原理图部分! m" e# `. E# K! m! e
22.3.5 软件设计
! K: e- I, n* Z1.新建和配置工程
# _, w: G3 ]* M. C1 E新建工程ADC1_SINGLE_DMA,配置LED0和UART,这里就不再对LED0和UART的配置进行赘述了,可以看看我们前面串口通信实验的串口中断接收回显实验。下面我们来讲解怎么配置ADC和DMA:
/ O# H" j- a, b5 Y7 a9 Y; J: l(1)配置ADC+ \' t* x+ t' T9 \- m( E8 J" o
在Pinout view处配置ADC引脚如下,也可以直接在AnalogADC1处配置ADC,选择对应的通道以后,Pinout view处也会自动配置好引脚。- v9 D. {3 T" p7 v+ d# s/ [

2 e) T5 z4 X0 d3 ?* i1 \ 6378df69cc4c4b238d23d83444a1c6b0.png
. s# J+ A: P1 c3 w- p0 L- `
9 M+ ?8 R0 d9 X图22.3.5. 1配置ADC引脚, P  ?/ e8 ~* g* C
选择ADC1的通道19:; I! A! N/ i# V) y" P( A
2 E% |1 N; r; T% Q/ ~3 e
239a5b786e83493792ecb33197edc917.png . ^; O& M$ E' J/ o8 W
3 G4 V, o. ~3 ]! M+ Y( L6 e$ S
图22.3.5. 2选择通道193 u/ P  K' U  C+ x: [/ U
ADC参数配置如下:7 h6 {" ]6 l% _: U. b( U
其中注意红方框中的两个选项,Conversion Data Management Mode配置用于配置转换后的数据管理模式,我们先暂时只能选择Regular Conversion data stored in DR register only(表示常规转换数据仅存储在DR寄存器中),等后期配置DMA后才可以选择DMA单次模式或者DMA循环模式。6 `$ k5 Z- |! k/ u6 n* R6 f

+ Q5 w0 A& l9 a; Q a994a5e8355d4c48a8ed6612fb921332.png . M) D4 E! O! @; z

; K: ~" M. [. C, Z! G图22.3.5. 3配置ADC参数2 U$ p: e; g. k& l% p8 j3 b
对以上的ADC参数配置选项进行介绍:- k3 s1 [, J. x* h
●ADCs_Common_Settings(ADC工作模式配置):5 q8 E7 I. `$ \4 L: {1 B/ q$ j, M
这里配置为独立模式,独立模式是指在同一个管脚上只有一个ADC采集该管脚的电压信号。如果只是用了一个ADC的时候就配置为独立模式。
, [; f4 g0 g* a& ~除了独立模式,还有双重模式以及三重模式等多重模式,多重模式是指双ADC共同工作,如果需要两个ADC同步的话,则使用此模式。
' j& L. K: ]$ z/ V: {2 j8 J●ADC_Settings(ADC参数设置)
9 K& o- E6 p4 d- j/ j* DClock Prescaler用于配置时钟分频,这里选择2分频;: X0 g4 S+ [" M. ?
Resolution用于配置ADC的分辨率,这里选择16位;
* }3 F9 D( G# A( {" B* d4 \+ WScan Conversion Mode用于配置扫描模式,当有多个通道需要采集信号时必须开启扫描模式,此时ADC将会按设定的顺序轮流采集各通道信号,单通道转换不需要使用此功能,这里选择Dsabled;5 _" L0 K) i4 |- a& D* p: F; J
Continuous Conversion Mode用于配置自动连续转换还是单次转换。使用Disabled配置为单次转换,转换一次后停止需要手动控制才重新启动转换,使用Enable配置为使能自动连续转换。我们选择Enable;# n( N  Q/ c1 j% ~8 n7 a# [. [
Disabled Discontinuous Conversion Mode用于配置是否使用不连续的转换模式,所谓不连续,比如要转换的通道有1、2、5、7、8、9,那么第一次触发会进行通道 1 与通道 2,下次触发就是转换通道 5 与通道 7,这样不连续的转换,依次类推。这里我们选择禁用不连续的转换模式Disabled;
& a6 q+ K" T; P0 ^; BEnd Of Conversion Selection用于配置转换方式结束选择,可选择单通道转换完成后EOC标志位置位或者所有通道转换成后EOC置位,也可以选择转换序列结束后EOS置位(配置为End of sequence of conversion),这里配置End of single conversion,即单通道转换完成后EOC置位;
/ r4 I# a! Q" ?) XOverrun behaviour用于配置有新的数据溢出时,是覆盖写入还是丢弃新的数据,我们选择覆盖写入新的数据;2 _" w. z# s4 b8 k  i3 x
Conversion Data Management Mode用于配置转换数据管理模式,我们暂时只能选择Regular Conversion data stored in DR register only,即将常规转换的数据存储在DR寄存器中,等下一步配置DMA后,再选择DMA单次模式或者DMA循环模式;9 J: y, s: u/ m$ p2 P; S, p' u, D2 U
Low Power Auto Wait配置是否使用低功耗自动延迟等待模式,当使能时,仅当一组内所有之前的数据已处理完毕时,才开始新的转换,适用于低频应用,该模式仅用于ADC的轮询模式,不可用于DMA以及中断,这里我们选择关闭。
, Q, `/ g5 V9 q4 E0 S●ADC Regular_ConversionMode(ADC常规通道转换模式)
& s$ o4 i% T9 W' Z0 |Enable Regular Conversions选择Enable,启用常规转换;
$ \: k, `1 D3 b2 s# g$ y  w: kLeft Bit Shit 数据左移位数,最多可支持左移15位,这里选择没有位转移No bit shit;1 f/ J- ~0 ^1 t
Enable Regular Oversampling用于配置是否使用常规通道过采样,这里不使用此功能;
  R5 U+ X! `" ]" w  ?- c. Z( ~Number Of Conversion转换通道数量,此参数会影响可供设置的通道数,按实际使用的通道数来选择即可,这里是1;
' S" a6 D: k) C; ^8 lExternal Trigger Conversion Source外部触发转换模式配置,ADC在接收到到触发信号后才开始进行模数转换,触发源可以是定时器触发、外部中断触发等硬件触发、也可以是软件控制触发,这里选择软件触发,工程中需要我们添加启动ADC的代码,也就是我们前面介绍的HAL_ADC_Start_DMA函数;
0 a  k1 l/ A+ G$ c* uExternal Trigger Conversion Edge配置触发边沿,这里选择无触发边沿;2 E# e5 u+ o  ?# F( j$ [
Rank配置模拟信号采集及转换的次序,默认是1,其中:% H" J6 D; [" @8 Y
Channel用于选择转换的通道,这里选择通道19;! D9 x$ D" E: q
Sampling Time采样周期选择 810.5Cycles;
, F) s2 Q- O/ s+ cOffset Number配置偏移量的通道选择,这里选择无偏移通道;
" L+ X0 o! P- t- r& C" m$ k●ADC_Injected_ConversionMode(ADC注入通道转换模式)
+ r; Q; b& c. N8 Q* t* REnable Injected Conversions用于配置注入通道转换模式,实验中我们不需要使用注入通道,所以此项配为Disable。2 K9 H$ L# o7 c/ w1 D5 R+ y8 ]
后面的是模拟量看门狗的设置,本实验我们不需要,不配置即可。% p( {* Z0 r1 A% A8 W5 s1 U
(2)配置DMA- Q( O) V: _# r7 Z; R
本实验我们要使用DMA将ADC转换后存在ADC_DR寄存器的数据传输到自定义的一段内存中,所以需要配置DMA。
) ?( s6 Y$ i7 i3 s! K注意下图配置,我们选择数据流为DMA2_Stream7,数据方向为外设到内存,速度等级为中等,DMA请求为循环模式(Circular),勾选内存递增,要注意,这里选择字宽为1个字(因为HAL_ADC_Start_DMA函数的此函数的形参2和形参3是uint32_t类型的,所以最好选择Word。这里注意,如果配置DMA请求为正常模式(Normal),那么DMA传输完指定的数目的数据后就停止传输了,而且在此模式下,转换数据管理模式不能选择DMA循环模式。
4 F5 D& @' \) q- Y8 Q- A3 q2 H- o, z" T, n) F: r: v1 C
6e7eda49bf2e401eb7f88a78c6f7e2e0.png
, M9 m0 t9 w: d9 \3 u1 z! J" [# T6 p2 d7 k7 Z6 B' ~2 R4 t6 T$ P* I
图22.3.5. 4配置DMA模式0 C6 ?) A! f+ Z: m1 J1 b
配置好了DMA,再回来配置ADC转换后的数据管理模式为DMA循环模式(DMA Circular Mode):
# f0 u( u( ]) n5 D2 e: I, K6 J( ]1 Q( w
293a8303cf80494cba9a6fa742f14034.png
! b/ F' G" V3 s, o  E5 }: d% L9 _- f( I2 K" o! T8 N; I& T4 t  h
图22.3.5. 5配置转换数据管理模式为DMA循环模式# ~" F+ u2 U* X; O
(3)DMA配置
, ?$ @( T, W: t# [. h下面我们设置DMA的中断优先级:
& k* i$ Q; ]) w- p
' n' u& g" z+ t e7533ae03d9e4766a104c17bc3b48cb2.png
8 ^, ~% T- U3 Q2 _5 U: `
( a/ M8 O: C  _/ x; |图22.3.5. 6配置ADC优先级0 s0 Q6 N. t* e2 t2 K, |' S
ADC这边配置好DMA以后,也可以在DMA处看到配置项选的是什么:; o& [; ~3 U3 [  h, j! m: W4 i7 u

" D" y% F1 i0 ~/ b( {$ ` dce1288e81144d1b9fa68f4870398394.png # e  `- o6 Q+ {8 q
4 ~. J& o5 h/ V/ O. ?# a" }
图22.3.5. 7DMA的配置选项
% \8 B' V6 k5 u7 E(4)UART相关的参数配置; q' ?6 k, x8 f9 V
UART4的参数配置以及中断配置请参考前面串口通信实验。
) h3 n2 p; v  x( M( i2 V4 A(5)时钟配置
. u5 e2 E' m5 d' z这里配置MCU的时钟为209MHz,使用外部时钟HSE,这部分配置可以参考前面实验章节:
8 |4 S3 K+ H8 ~# p6 J. _# V& P7 W' }7 [
c15da559343d48d4890522b64afb2ea0.png
3 l/ q2 Q, G( Y2 l( |5 `
% k0 {& ^) O7 Z0 c图22.3.5. 8时钟树配置. J; o4 ?% S. N  }% e7 }4 a
ADC1的时钟使用PER,其中PER时钟源默认使用HSI,即为64MHz。上面ADC参数配置中,我们配置分频系数为2,所以实际ADC的时钟是32MHz。4 W7 B+ }7 k8 k* B& S% v
0 K) t* {2 I$ P/ T' {7 v) d
2f215a70219649eeb3315876601911a2.png ' I. [4 n1 {* x" E0 Q
" H$ b' n6 L" c/ G3 Z! T: V- }
图22.3.5. 9 ADC时钟源选择% \5 ^6 O3 r- o. i; v" F& v! s0 ^: Y( F
(6)配置生成独立的.c和.h文件3 @! b* d  p: p3 t! @

$ y' T- j8 X1 H( d9 A bb747e0729d748ec90352c15c756d3a0.png 4 ^0 I. |$ B! c! K
2 u& B5 ^" h3 e6 A2 r0 F
图22.3.5. 10配置生成独立的文件
, |' ~7 O9 Y- k# q. y2. 生成工程" T8 A( h% X( o2 [. V. e" m
保存配置,生成工程后,将前面章节使用的BSP文件夹拷贝到工程的Src文件夹下,本节我们要使用LED0的驱动:
0 V$ |8 B: i: ?& [; d2 y' L; Z. P% `6 j$ [, b' {' C- B$ @9 R
ac3d8f38b83c406899540f6d5eec9db0.png
8 e) l! }; n, d# S# j0 g+ G' j' \: K  X! x4 u, X5 J& k, G6 r7 H5 v
图22.3.5. 11生成工程: `& v/ ^& K+ @( W& t
3. 初始化代码分析% z# k; S8 \/ D$ h. t, Z
(1)dma.c文件
1 H; F0 O! w6 d( k2 B( t. T  K4 Z" Q# c. D1 a. L. O
        dma.c文件生成的是DMAMUX和DMA2时钟,并开DMA2中断:
5 ~5 ?3 \6 N+ n! U5 M
  1. #include "dma.h") y/ F5 C: }+ z
  2. /**
    5 a" g% L. _& g6 w9 q# v9 Z
  3.      * @brief      DMA外设初始化函数! V- c6 V7 u! ?2 E' Q! D
  4.      * @param      无& V: [- @) p# O# t& z
  5.      * @retval     无- A! X8 g& E  V, t2 Q
  6.      */
    + e8 f# T' `; Q5 J9 s
  7. void MX_DMA_Init(void)
    8 I* g) k5 D2 ^- z& d$ s4 x
  8. {
    4 [) `3 C; J1 g6 W6 h# \: ~! K
  9.   __HAL_RCC_DMAMUX_CLK_ENABLE();/* DMAMUX时钟使能 */! }" v/ a) H0 d' J
  10.   __HAL_RCC_DMA2_CLK_ENABLE();/* DMA2时钟使能 */
      g$ k$ y& h: p, u
  11.   /* 设置DMA抢占优先级为2,子优先级为1,中断优先级分组在HAL_MspInit()中 */5 M6 P1 k! s$ z3 }, q" r; h
  12.   HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 2, 1);2 B5 [( L1 N5 s4 q4 {' T
  13.   HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn);/* 使能DMA中断 */9 [0 B" r4 J+ |. g* X' Q
  14. }
复制代码

, A' ?2 f- r1 k2 r* {(2)adc.c文件
- }8 O2 \5 [( k9 M        adc.c文件的初始化代码就是根据我们前面在STM32CubeMX杀个配置生成的,adc文件代码如下,代码中附上了详细的注释:( x. @$ ?, l4 `. z$ ?" n) i
  1. #include "adc.h"6 k  _8 m2 Z  W- A: [6 W
  2. & A$ i& {' M$ U, e) q
  3. ADC_HandleTypeDef hadc1;/* ADC句柄 */
    / \& N! V* N0 ~/ e' C' I; O
  4. DMA_HandleTypeDef hdma_adc1;/* DMA句柄 */
    1 ]; Q4 b+ [1 Q8 r  D

  5. 8 r) Q! S4 |& O
  6. /**+ i/ ^+ a1 ]" I  a2 O
  7. * @brief       ADC1初始化函数,ADC1转换并请求DMA传输
    - m" q! w2 j5 J
  8. * @param       无
    1 r$ z) x/ h( n  ?) Q* n" N
  9. * @retval      无
    / R8 S5 x0 K' F, \' z+ G4 C
  10. */: G# C8 q+ R) O; i8 _
  11. void MX_ADC1_Init(void)
    1 I2 C2 s# J% t3 V- U
  12. {3 a# \' f% P* X5 t( H
  13.   ADC_MultiModeTypeDef multimode = {0};
    ; \$ c5 M9 p! G+ n4 w6 X& L
  14.   ADC_ChannelConfTypeDef sConfig = {0};
    . C1 }  d! {$ }" Z* R

  15. + p2 J( a: ?' d) c
  16.   hadc1.Instance = ADC1;/* ADC1 */8 U+ K; i  X$ z% q  `
  17.   hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2;/* 时钟2分频 */9 y% P5 r* R; w
  18.   hadc1.Init.Resolution = ADC_RESOLUTION_16B;/* 分辨率为16位 */
    " {  F& F: Y# u5 ?
  19.   hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;/* 关闭扫描模式 */
    , a3 W. |* `3 W# P9 E/ K& d
  20.   hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;/* 单通道转换完成后EOC置位 */' ]# D: ]9 W5 b* s7 C) o
  21.   hadc1.Init.LowPowerAutoWait = DISABLE;/* 关闭低功耗 */' p! z' B* t0 p8 v9 M- D9 j: O
  22.   hadc1.Init.ContinuousConvMode = ENABLE;/* 使用连续转换 */4 _+ Y$ f% z0 S+ n5 b6 q
  23.   hadc1.Init.NbrOfConversion = 1;/* 通道数为1 */9 u" j$ b* r& @) W' F& S
  24.   hadc1.Init.DiscontinuousConvMode = DISABLE;/* 不使用不连续的转换模式 */; K* B# N7 D. D2 ?; }5 C; m" ?
  25.   hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;/* 软件触发 */
    8 s; ^# r0 y; t2 G# W! n
  26.   hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;/* 无触发边沿 */2 ]# B; I1 _9 L5 c! T$ Q
  27.   /* DMA循环传输 */& Z6 u! p4 \' ]. j4 r
  28.   hadc1.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR;
    ) B% X5 K1 D5 E
  29.   hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;/* 有新的数据溢出时,选择覆盖写入 */
    1 [/ l% V. q2 u+ s; B
  30.   hadc1.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;/* 数据转移位数,选择没有位转移 */
    5 ~0 d" x/ K0 I! Y' i
  31.   hadc1.Init.OversamplingMode = DISABLE;/* 不使用过采样 */
    ' x. r* B5 J6 b# O
  32.   if (HAL_ADC_Init(&hadc1) != HAL_OK)/* 使用HAL库初始化ADC */
    5 v+ ]2 o! {+ F0 p7 f* b
  33.   {) }( R* }# l: `/ L* S
  34.     Error_Handler();
    % X+ _3 ^1 c/ L5 R: t
  35.   }
    # S. |5 R7 u6 J- y* N1 [
  36.   /* 我们不使用ADC多重模式,这里的配置没有用到 */
    7 C* ]$ v7 v  i# O, z7 G0 `# N
  37.   multimode.Mode = ADC_MODE_INDEPENDENT;9 q! O: _9 \  X7 p% r( I
  38.   if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
    1 r& w1 o; }, I) [( P3 j* W; S
  39.   {
      a0 ~4 `# J4 r! B6 M
  40.     Error_Handler();
    9 X2 J' w* ?- {: |
  41.   }
    3 x/ R; a% m- X* N
  42.   sConfig.Channel = ADC_CHANNEL_19;/* 选择ADC通道19 */
    0 S+ A3 I& D' x2 W# R4 M% x! `' i
  43.   sConfig.Rank = ADC_REGULAR_RANK_1;/* 转换的次序为1 */& u1 G1 J; }" @( v
  44.   sConfig.SamplingTime = ADC_SAMPLETIME_810CYCLES_5;/* 采样周期选择 810.5Cycles */
    7 ~1 o, W2 r) x! r
  45.   sConfig.SingleDiff = ADC_SINGLE_ENDED;/* ADC通道结束设置为单端 */8 t! r$ n+ K; y( j- D" k
  46.   sConfig.OffsetNumber = ADC_OFFSET_NONE;/* ADC偏移禁用:所选ADC通道无偏移校正 */' n# r8 z8 _5 X4 Q8 |) t
  47.   sConfig.Offset = 0;/* 偏移量为0 */' U) J- V5 P+ i0 H
  48.   if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)/* 使用HAL库初始化ADC通道 */, q# `$ H9 h, I) ^' f
  49.   {/ i1 S3 {/ n2 u
  50.     Error_Handler();* m4 r5 T, n$ z% ^( G# {* Y) X
  51.   }- \! a+ l: s" T6 B0 g( Q: v
  52. }
    . s% S* A5 @# y% G
  53. /**
    3 R! `# C: n/ B. I
  54. * @brief      ADCGPIO始化函数
    5 d8 s! E: s. V8 f% V. P3 W
  55. * @param      adcHandle ADC结构体指针变量
    $ _: H4 H/ l8 e1 L
  56. * @retval      无
      Q3 {/ D& x& I& Y; S/ @
  57. */
    ( f, [2 N# C2 a5 _- V
  58. void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
    % n; A# J$ M- p, l0 G
  59. {# Z% v' j3 |- [! U/ x$ h& z+ W$ \
  60.   GPIO_InitTypeDef GPIO_InitStruct = {0};
    ) v2 E3 g7 e3 Z7 P
  61.   RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};# ~7 z/ c! m; k: _
  62.   if(adcHandle->Instance==ADC1)
    ! g# Q1 L  Z% P' x. Z+ B
  63.   {9 L: A: t1 B. `4 P/ p$ D( q$ [: g
  64.   if(IS_ENGINEERING_BOOT_MODE())
    ' F0 _+ r5 y+ B7 t6 M5 w
  65.   {
    2 k" T+ i' m# S, u0 D1 W3 H
  66.       /* 配置ADC的时钟来自PER */
    ' i( y: @) I- G
  67.     PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
    3 z! k# q8 x0 ?4 C4 O6 g+ }3 }
  68.     PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_PER;
    - n  J! E8 Z1 o" T+ _4 E1 s
  69.     /* 使用HAL库初始化ADC时钟 */
    0 N) z8 C7 D" b3 N, ~; I! L5 Y
  70.     if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)( V# D1 R: ]7 Y' _* c
  71.     {/ z$ a2 r* l  w/ f% ~0 J. U' ?
  72.       Error_Handler();
    # Q$ K  `* x6 O! L# V8 y( k1 Y
  73.     }/ K4 A2 a! ?5 f% J. I& R( u
  74.   }
    . Z' ^6 w( ~- |' p( g9 ^5 l; q
  75.     __HAL_RCC_ADC12_CLK_ENABLE();/* ADC时钟使能 */
    1 x  ]3 ~8 e) a4 U! I- \! A
  76.     __HAL_RCC_GPIOA_CLK_ENABLE();/* GPIOA时钟使能 */
    # K8 |+ c" v$ s$ ]
  77.     /**ADC1 GPIO Configuration% K: S4 k3 |+ U: p4 H: R
  78.     PA5     ------> ADC1_INP19
    ' s* ?5 [+ W/ C& p
  79.     */+ o! |7 L( B0 H2 {
  80.     GPIO_InitStruct.Pin = GPIO_PIN_5;/* 引脚5 */* G  l& M/ g  a; w, w# j% I+ A
  81.     GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;/* 引脚配置为模拟模式 */, }8 z7 X5 O* F- ], H
  82.     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);/* 使用HAL库来初始化GPIOA */5 s+ |5 j2 o4 a5 h
  83.     hdma_adc1.Instance = DMA2_Stream7;/* 数据流 DMA2_Stream7 */7 m- U4 m% [3 d% \0 Q
  84.     /* 指定为指定流选择的请求:DMAMUX1 ADC1请求 */4 A: @$ _  Y! S' D: Y' r' D1 n. Q- p
  85.     hdma_adc1.Init.Request = DMA_REQUEST_ADC1;/ H/ q) T5 R  l. r, k  T5 G6 ~: a
  86.     hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;/* 数据流是:外设--->内存 */
    9 W8 E! ?; ]# G" ~* X+ z: a1 _' ~$ m
  87.     hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;/* 外设增量模式禁用 */
    ) U$ j7 @" }+ {" [. w" T6 h; L8 a
  88.     hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;/* 内存增量模式使能 */
    ) S# ]& ~+ t# q9 \$ ^% {
  89.     hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;/* 外设数据对齐:Word */* d3 W2 e' Q0 N, A
  90.     hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;/* 内存数据对齐:Word */4 y4 m4 A6 L4 ~& ~" O- _3 n
  91.     hdma_adc1.Init.Mode = DMA_CIRCULAR;/* DMA传输模式:循环模式 */; F: m3 _: h( U! ?% l* x
  92.     hdma_adc1.Init.Priority = DMA_PRIORITY_MEDIUM;/* DMA传输速度等级:中等速度 */* D4 ?' @, U8 K2 V0 C6 e: r; T
  93.     hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;/* 不使用FIFO模式 */
    7 v7 E5 L. {* d2 K- s$ d
  94.     if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)/* 使用HAL库初始化DMA */& W! z. h" f! @
  95.     {# G9 P. R: }/ @# S* Z1 L& V" h
  96.       Error_Handler();
    + P8 T, V+ T$ @: v7 f
  97.     }) H% |5 U, i% x6 k
  98.     /* 将DMA句柄关联到ADC1 */; n+ M  s; u7 F
  99.     __HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1);
    : b) A) U2 Y. P
  100.   }
    1 U7 X4 z5 A* N' i3 _
  101. }
    : _. m, g2 ^% e0 |( q
  102. /**
    % U# Q: b6 a; R. v$ ?
  103. * @brief      ADC外设取消初始化函数
    & u& M% a' Y; e; g- x
  104. *@note        如果有需要,可以调用此函数取消ADC的初始化; W8 |7 ]7 h! \
  105. * @param      adcHandle ADC结构体指针变量4 ?5 @7 H2 [: b9 ~( e
  106. * @retval      无
    % i7 U& ]1 D' @# b% b
  107. */0 C0 J* P8 P" p
  108. void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
    7 V& k5 F- ?2 D. b3 f0 [5 O
  109. {
    : D0 h8 f, B. Z7 ]& E7 @8 K
  110.   if(adcHandle->Instance==ADC1); C( j* H5 [( `: O
  111.   {4 g# ]% a' s0 ]
  112.     __HAL_RCC_ADC12_CLK_DISABLE();/* 关闭ADC时钟 */7 L4 d& E( [9 P/ G

  113. . q- v  I! S9 a0 f! |( O' f
  114.     /**ADC1 GPIO Configuration
    + _. O, W4 o  R1 t
  115.     PA5     ------> ADC1_INP19
    , l4 `9 }6 }! D
  116.     */
    , C& ^' n, e6 I( X! |; B: u: k
  117.     HAL_GPIO_DeInit(GPIOA, GPIO_PIN_5);/* 取消初始化PA5 */
    # O8 j, b3 `$ p9 m: _! x( l
  118.     HAL_DMA_DeInit(adcHandle->DMA_Handle);/* 取消初始化DMA */9 A! }# _# Y( m
  119.   }
    ) d  m# q- ~9 a
  120. }
复制代码

9 m' B& L+ m, W& v0 k; e7 X4.添加用户代码
0 d+ Q" W! O5 ^2 T& s. R8 T(1)修改stm32mp1xx_it.c文件
( k% h) \: Q1 N) B. Istm32mp1xx_it.c文件下生成中断服务函数,当DMA传输完成后,会执行DMA2_Stream7_IRQHandler函数,我们做如下修改:) N$ u& r/ u2 g  I
  1. uint8_t over_flag=0; /* 传输完成标志位,0表示未传输完成 */, @% I& @9 L" }) k+ t
  2. void DMA2_Stream7_IRQHandler(void)/ ^$ n  p. I7 j9 E6 E; ~6 |8 R
  3. {
    . ^; i" g- G1 J1 Y5 Z% V
  4.   /* USER CODE BEGIN DMA2_Stream7_IRQn 0 */
    ! C" i. [. `; j/ H
  5. if (__HAL_DMA_GET_FLAG(&hdma_adc1, DMA_FLAG_TCIF3_7))
    7 E' P; F' @" m/ W" D5 s8 c( c2 ~
  6.     {7 q9 g, e5 ~, E/ s3 H' Y, M1 C/ D
  7.      over_flag = 1;          /* 标记DMA传输完成 */
    " i# g: l  p% {6 i, G
  8.      /* 清除标志位 */; L9 ]  E/ }. A3 V) S# B0 X# j
  9.      do{ __HAL_DMA_CLEAR_FLAG(&hdma_adc1, DMA_FLAG_TCIF3_7); }while(0);7 h: _% U) {0 R5 {8 B' l4 p
  10.     }
    7 a; t& a  _9 F" H) W& X
  11.   /* USER CODE END DMA2_Stream7_IRQn 0 */6 R' e) ]! h( {
  12.   HAL_DMA_IRQHandler(&hdma_adc1);
    5 h( V! n! {: Z9 x* H0 A3 b) }
  13.   /* USER CODE BEGIN DMA2_Stream7_IRQn 1 */
    % I8 Z, Q8 i, o% p
  14.   /* USER CODE END DMA2_Stream7_IRQn 1 */
    $ V$ o) f, S2 V8 H6 q. O
  15. }
复制代码
4 i' g$ W9 }, m& c+ z7 k
以上代码是添加传输完成标志位over_flag,当DMA传输完成后会执行中断服务函数DMA2_Stream7_IRQHandler。当传输完成时寄存器DMA_LISR的TCIF3被置1,表示流传输完成,我们通过__HAL_DMA_GET_FLAG(&hdma_adc1, DMA_FLAG_TCIF3_7)来判断TCIF是否置1了,如果是1,表示DMA已经传输完成指定数目的数据,将over_flag标志位置1,每次传输完成后可以进行读取转换值用于计算电压值。
* W( Q! R1 ]; L2 r% Q1 }0 k这里DMA_FLAG_TCIF3_7表示DMA的通道3和通道7,又如DMA_FLAG_TCIF1_5就是指DMA的通道1和通道5,在stm32mp1xx_hal_dma.h文件中有定义。5 \* a  B! k3 S0 U3 A
  H* {9 Q3 w7 {! O# `5 D1 T
(2)修改main.c文件
) `$ a9 Y, H$ y  Q8 @main.c文件代码如下,我们在标红的字体之间添加代码:- j. \4 ?7 C: j6 Y/ Z

, }- r# O, Q1 {6 p# A% X
  1. 1   #include "main.h"
    " E: Z! B( ~" D$ a2 t  U1 j
  2. 2   #include "adc.h"; h( T( P. d+ g* K! C$ l) E
  3. 3   #include "dma.h"
    . F0 ~" a" X6 F" L7 h5 {
  4. 4   #include "usart.h"5 y+ T. k0 p9 o( v9 r
  5. 5   #include "gpio.h"5 \' D2 m$ D, P4 S9 F2 [: c
  6. 6  8 m) U8 k1 X9 i6 y/ {
  7. 7   /* USER CODE BEGIN Includes */
    ; Z( }1 Y- t; U. V
  8. 8   #include "./BSP/Include/led.h"% F( w* Z' j* d& k# u3 P( I
  9. 9   /* USER CODE END Includes */) s& R9 g0 i6 U; p, K
  10. 10
    , V  L: x) |& Z
  11. 11  void SystemClock_Config(void);9 B0 v4 Y  j6 E% y
  12. 12  void PeriphCommonClock_Config(void);5 t! c. f# V6 }
  13. 13 * H1 f/ C& N, G1 n. L
  14. 14  int main(void)
    ! [) W; ^2 p4 x* a9 P4 J; U
  15. 15  {
    ) k2 x/ l# p2 B; P. }- R) x
  16. 16    HAL_Init();                                                /* 初始化HAL库 */
    9 @1 ~4 ~2 D# ^) X% N0 w
  17. 17    if(IS_ENGINEERING_BOOT_MODE())
    $ W5 M) z: p+ d3 s/ B  j# K( P
  18. 18    {4 u  k- {3 S5 i. ~' E
  19. 19      SystemClock_Config();                /* 系统时钟配置 */% B: \; D$ u& N
  20. 20    }
    ( m7 x3 N% D0 ]( U
  21. 21    if(IS_ENGINEERING_BOOT_MODE())2 ~" T9 u& ~6 r; X7 w
  22. 22    {
    $ ~$ z5 B3 X8 O9 v" E9 [& C$ z
  23. 23      PeriphCommonClock_Config();        /* 外设时钟配置 */9 L' Y) i, d5 N! X; B! X" W
  24. 24    }
    0 C; J9 {5 o' t. u
  25. 25    MX_GPIO_Init();                                        /* GPIO初始化 */! O8 P! h( b/ l5 M2 m$ f  N% w6 W9 h
  26. 26    MX_DMA_Init();                                        /* DMA初始化 */
    # z- B" {* v0 _# I9 j: U. N3 i  H
  27. 27    MX_ADC1_Init();                                        /* ADC1初始化 */
    7 o( D5 e1 b% h* o) D
  28. 28    MX_UART4_Init();                                /* UART4初始化 */
    3 Z6 U2 @4 B: j  t# O3 n' S4 E
  29. 29    /* USER CODE BEGIN 2 */
    % z3 a7 Q( l$ M$ [0 n2 b- l6 g7 ^
  30. 30      uint16_t i;
    # A2 H3 S# U# G+ Q
  31. 31      uint32_t adcx;
    * F. T. E: h) L) r$ D3 q# C
  32. 32      uint32_t sum;' |" x# a% C5 |! z: {7 U! X. }, E
  33. 33      float temp;1 \9 ~" l' c/ ~) y
  34. 34      extern uint8_t over_flag;' S2 H, ^  Z5 d  B. P
  35. 35      #define ADC_DMA_BUF_SIZE 5                                                /* DMA传输的次数 */$ G9 \% h+ i- n6 j
  36. 36      /* DMA传输的目的地址BUF,注意是uint32_t 的 */
    + V9 c0 l& s) P1 U+ V# {
  37. 37      uint32_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]={0};
    ) c/ ^/ N$ g" m' V& o$ \9 n7 m5 }
  38. 38      HAL_UART_Receive_IT(&huart4,&RxBuffer,1);                /* 以中断方式接收函 *// M' L2 S9 F4 U7 V/ H( \. _
  39. 39      /* ADC校准,如果不校准,数据就会有偏差 */
    7 x; Z) e" A7 ^
  40. 40      HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, \                                                                                                                                                ADC_SINGLE_ENDED);
    , {- ~3 c7 P' r5 z8 l
  41. 41      /* 启动ADC,ADC转换会请求DMA传输 */0 k: U3 }" ?+ n6 K
  42. 42      HAL_ADC_Start_DMA(&hadc1, (uint32_t*)g_adc_dma_buf,         \                                                                                                                                                ADC_DMA_BUF_SIZE);
    , R" X6 j9 b5 g
  43. 43      printf("ADC Start\r\n");
    9 L' v2 u9 o3 r+ J$ E  y3 C9 q$ p
  44. 44    /* USER CODE END 2 */
    5 {" z) R, U% [) a
  45. 45    while (1)! a; l- ^8 j3 n- ]. Y2 R. e& g' `
  46. 46    {
    ) v* L& v: A8 W* y) F$ Y
  47. 47      /* USER CODE BEGIN 3 */' J: a% K) s' e
  48. 48  /*
    $ d8 I" c( S; i+ E" o
  49. 49   // DMA One Shot Mode
    1 I3 _; X( N7 W: o$ _8 h9 r
  50. 50      if(over_flag)  //DMA传输完成(传输5次)
    " E4 D. d" E" b: k: j" v
  51. 51      {
      Y! p; Q# U6 m5 `% C' e' F! n
  52. 52          sum =0;
    0 B+ O* U( J! u! L% T: Z+ E  u
  53. 53          for (i = 0; i < ADC_DMA_BUF_SIZE; i++)& T  H* i5 Z& X; y5 C# w
  54. 54          {
    3 q- n' M, U) q) b4 R% i
  55. 55              sum += g_adc_dma_buf<i>;5 E. o! _5 \' I1 W
  56. 56              printf("ADC = %ld\r\n",g_adc_dma_buf<i>);
    + D5 u9 k- x" A0 Y' i( J) y3 y
  57. 57          }
    ( T5 G  \% _4 d/ a! N
  58. 58          adcx = sum / ADC_DMA_BUF_SIZE;+ z; O+ |; c! V. _6 @9 E% y) e
  59. 59          temp = (float)adcx * (3.3 / 65536);% s. E2 W5 I; I
  60. 60          printf("Voltage value  ");! P% N) O. {3 c0 n( L$ ~. G
  61. 61          printf("adcx = %1.3f\r\n",temp);
    : {& e- u$ J9 q& d5 q' D) v
  62. 62      }
    % T8 f8 U& s$ v% M! n( Z
  63. 63      //HAL_ADC_Stop(&hadc1);1 ^/ B; }. x: Q& m
  64. 64      HAL_ADC_Start_DMA(&hadc1,  g_adc_dma_buf, ADC_DMA_BUF_SIZE);( \- j' y  k' o: w" V; h
  65. 65  */
    4 t+ q3 R* y5 o$ M4 u: V
  66. 66
    : L/ D6 Z  W/ ^! S! J
  67. 67   // DMA Circular Mode5 W9 w# p7 W, I) @( t
  68. 68        if(over_flag)                                                 //DMA传输完成(传输5次)
    6 k$ w1 L9 x5 ~$ Y8 l5 j+ o
  69. 69          {
      m# s% A' p" x' t! F/ p
  70. 70            over_flag=0;                                         //将标志位清零,方便下次判断' r9 H: z! q0 E
  71. 71              sum =0;- g3 u+ m6 ^0 u9 c; @% j3 Y
  72. 72              for (i = 0; i < ADC_DMA_BUF_SIZE; i++)
    & Q8 U6 \) y/ f& s/ d
  73. 73              {- j& _9 q( u: x+ M2 v0 F) u
  74. 74                  sum += g_adc_dma_buf<i>; //计算BUF的值4 h( f' g: Y. Y6 s- C. r) X
  75. 75                  printf("ADC = %ld\r\n",g_adc_dma_buf<i>);7 k" h" D" ~3 l8 I4 ^& z9 g# W. N
  76. 76              }+ q) o; L3 h2 q: m
  77. 77              adcx = sum / ADC_DMA_BUF_SIZE; // 计算转换的平均值
    # k6 P# G) A/ J7 A" p1 \7 K$ T
  78. 78              temp = (float)adcx * (3.3 / 65536); // 计算电压值% X% t9 g" v& ]
  79. 79              printf("Voltage value  ");) p8 Y0 m; O# X2 L% l* ]; W# k
  80. 80              printf("adcx = %1.3f\r\n",temp);/ F: _/ o* X' H/ g
  81. 81          }4 Q, U1 f& J. M% H
  82. 82 5 u4 ?) G! T9 l) ?+ O' @& I  A
  83. 83          HAL_Delay(2000);1 y$ k: ]& ~( `, i1 k
  84. 84          LED0_TOGGLE();# G; R" }0 Q: a! z
  85. 85    }& {5 p: ]% M% ^$ b
  86. 86    /* USER CODE END 3 */, k. d6 \8 E2 b6 q7 W
  87. 87  }7 y4 {/ {8 p5 I7 _0 g9 ]" r: G
  88. </i></i></i></i>
复制代码

5 u2 B+ |. o: ?# Q" b6 N+ A我们手动在标红字体之间添加代码,注意第49~64以及第68~81行之间的代码:, I* C: J2 i- ~: W& _( J
如果配置ADC的转换数据管理模式为DMA单次模式,那么DMA传输完5次以后,DMA就停止了,所以会看到串口只打印一组数据,要想看到串口打印多组数据,则使用第49~64行的代码,我们来分析这段代码:0 X: ^2 ]2 S- R7 ]7 F" v6 ^: C. N- T
第50行,通过标志位over_flag=1判断DMA传输已经完成,此时可以使用Buffer里的数据进行计算了;
. H& T: `2 N/ f; b8 i第52~61,使用Buffer里的数据计算出电压值,并将每次转换值以及计算出的电压值打印出来;
" h7 W7 ~+ ]) L0 X7 V1 J8 g2 q( d: i第64行,此行非常重要!在DMA单次传输模式下,DMA传输完指定的数据后,DMA停止传输,而ADC还在不断采集数据,但后续采集的数据没有更新到Buffer中。如果不加此行,那么串口UART4只打印一组就数据,后续打印的数据也还是前面ADC第一组转换得到的值,此值不再更新,那么必须加此行,目的是再次启动ADC请求DMA传输;& m' n9 V( ]7 ~5 j' W
第68~80行的代码是适用于DMA循环模式下的情况,DMA循环传输下只要ADC有转换就会请求DMA传输,DMA会继续传输数据。
3 M% w2 a+ Z% y4 H0 G  K7 Y1 e" _, _' R% r. A
22.3.6 编译测试
6 T! D. U- g! h程序中使用浮点运算,所以要设置工程属性,支持浮点打印:6 n( d  D3 ]' }$ {' D) N

" r( B$ R/ ^; a- { 40724c8791914d0ba9fe88d972275ef7.png 9 F6 ^5 e- E6 D# |

% g% g. d) Z) U9 n图22.3.6. 1设置工程属性
$ V( g" [  A) {  u  s& r; V测试结果如下,每组数据都会不一样,数据在刷新:9 n+ D: O9 E2 A+ ?9 d+ P4 d

$ m9 T. x# k, o 24c8eaca9c0e45fb995dbf832d469791.png ! J& j# `, r6 M6 `
+ J4 x( }9 U7 r1 M; A6 Z$ h
图22.3.6. 2实验结果. a8 d$ J5 C) }7 L4 f/ U- U
22.4 多通道ADC采集(DMA读取)实验
: h& F3 V$ Y) Q) q! K! J本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 15-3 ADC1_MULTI_CHANNEL。: W8 D$ i  P3 {1 c! {9 b4 o* P
22.4.1 扫描模式和不连续转换* w( l7 `$ h9 }. J/ F+ I
本实验我们来学习使用常规多通道的连续转换模式,并且使用DMA读取ADC的数据。前面我们了解过ADC单通道的单次转换模式和连续转换模式。如果是多通道,还需要设置扫描模式。实际上,如果设置的是多通道,STM32CubeMX上就默认开启了扫描模式。
6 }0 R; C5 B$ U( R; Y; G9 |
, p/ t" }1 l: N1.扫描模式
" ], H2 _7 x/ X" P扫描模式就是在每个组的每个通道上执行单次转换,扫描的通道从等级1到等级’n’排列。如果设置了CONT位为1(连续转换模式),转换不会在所选择组的最后一个通道上停止,而是再次从选择组的第一个通道开始再继续转换。
" N0 z$ U5 h$ D3 X3 Y" n4 P0 r& T9 H9 F; [0 @, H& F$ ^4 U
f8bf94888c634c7e8eb092911e46aebf.png 7 o& E) G1 h, s5 b8 S

4 x) f4 [4 l( R/ G- h) i图22.4.1. 1使能扫描模式6 t$ \6 n5 A5 d. B" \) G
假设开启了扫描模式,且配置Rank1、 Rank2、 Rank3分别对应Channel 3 、Channel 2、Channel 1 通道,则转换一次通道顺序为Channel 3 、Channel 2、 Channel 1。  F2 F; F0 @6 {1 S, d; l2 g& e! c
2. 不连续转换: z* q% O- n4 u8 a& z( L
在多通道扫描模式下,可以选择单次转换、连续转换和不连续转换(间断转换),连续转换和单次转换我们前面都有介绍过。下面我们看看不连续转换。如下图,Discontinuous Conversion Mode就是配置不连续转换模式,如果开启了不连续转换模式,则需要配置不连续转换数Number Of Discontinuous Conversions,实际上就是配置寄存器ADC_CFGR的DISCNUM[2:0] 位的值:4 G* e  D, i6 v( i9 b% {
. P% @0 A) O8 Z
fb98c93bcc594c288500ec64adc6dade.png
% R$ k& u/ }9 v8 |5 g- R! s1 b1 ]9 j0 p2 P5 C/ S' @# u
图22.4.1. 2开启不连续转换模式
6 W' c  o- W, p  t不连续转换(间断转换)是指每一次触发信号可以执行一个短序列的n次转换,当以间断模式转换一个规则组时,转换序列结束后不自动从头开始,需要软件触发或者硬件触发才可以再继续转换。常规组和注入组都可以设置为不连续转换模式,该模式用于转换含有 n (n ≤ 8) 个转换的短序列(子组),可以通过设置ADC_CFGR寄存器的DISCNUM[2:0] 位来指定 n 的值。如果要使用不连续转换模式,这设置ADC_CFGR 寄存器的位DISCEN=1(注入组的是JDISCEN)。3 [3 a8 l/ c' W* M6 v7 D
举个例子说明使用不连续转换和不连续转换的情况:
; \' i7 q* m, B! T6 J( m当使用不连续转换(DISCEN=1),且设置DISCNUM[2:0] = 3,要转换的通道 = 1、2、3、6、7、8、9、10、11( A! J! M& I- f, y1 k
第一次触发:转换的通道为 1、2、3(每次转换时都生成 EOC 事件)。
+ O$ p2 s" x: m2 }. C. g" e第二次触发:转换的通道为 6、7、8(每次转换时都生成 EOC 事件)。+ ~1 N# @) D+ Z+ _. B1 X, o; e
第三次触发:转换的通道为 9、10、11(每次转换时都生成 EOC 事件),并会在通道 11 转换完成后生成 EOS 事件。
9 a# W+ A) J% e( e第四次触发:转换的通道为 1、2、3(每次转换时都生成 EOC 事件)。
9 [, z2 m5 z" }- U$ M6 L! l…4 e9 u2 O$ S$ t3 |+ S
当不使用不连续转换(DISCEN=0),要转换的通道 = 1、2、3、6、7、8、9、10、11
5 V3 Z' b3 r; _0 V+ G第一次触发:转换整个序列:通道 1、然后是通道 2、3、6、7、8、9、10 和 11。每次转换都会生成 EOC 事件,最后一次转换还会生成 EOS 事件。9 |( S, ]/ e% z& @" I# M
所有后续触发事件都将重启整个序列。4 E2 H8 l! ?5 V4 [( J3 L* D
注意事项:1 t" K4 p" K3 }% ^
(1)在STM32CubeMX下,如果设置了多个ADC转换通道(不是勾选的通道数量,是Number Of Conversion里配置的转换通道的数量),扫描模式就会自动自动开启,且无法关闭,如果只有1个转换通道,则扫描模式默认关闭,且无法开启。# `4 y; y! B. G8 Z3 y5 ~
(2)不能同时使能不连续模式和连续模式。如果同时使能两种模式(即 DISCEN=1 、 CONT=1 ),ADC 会认定连续模式已禁止并继续执行相关操作。
' O" B$ Z9 J  ~5 n( D& x(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。
" _5 L4 D# ^& q5 c. K9 z" m22.4.2 ADC寄存器
# k- d, C& ?# j( b: P本实验我们很多的设置和单通道ADC采集(DMA读取)实验是一样的,所以下面介绍寄存器的时候我们不会继续全部都介绍,而是针对性选择与单通道ADC采集(DMA读取)实验不同设置的ADC_SQRy寄存器进行介绍,其他的配置基本一样的。另外我们用到DMA读取数据,配置上和单通道ADC采集(DMA读取)实验是一样的。
8 K" x9 C6 Z6 L. c/ d. d- q# r  n. fADC常规序列寄存器有四个(ADC_SQR1~ ADC_SQR4),具体怎么配置,需要看我们用多少个通道,比如本实验我们使用6个通道同时采集ADC数据,具体配置如下:
9 k& e8 y- o' X1 UADC常规序列寄存器1(ADC_SQR1)
8 L& E" ~! S) A+ r/ h* GADC常规序列寄存器1描述如下图所示:' q: n+ \' V% [

- S( T! m" a6 L8 s9 _, z/ n- m! ]1 ~ 232ca8d69a0644fdbd505325435acd4f.png
% e8 [" e/ m3 i- o
% F2 K$ {+ z2 s  J5 z图22.4.2. 1 ADC_SQR1寄存器
2 t! u6 ^6 V- L/ p+ \规则通道组的转换顺序要在规则序列寄存器ADC_SQR1~ ADC_SQR4中设置,转换次序SQ1SQ4在ADC_SQR1中设置,SQ5SQ9在ADC_SQR2中设置,以此类推,常规通道有16个,SQ15和SQ16在ADC_SQR4中设置。以寄存器ADC_SQR1为例:
1 K" g: a( Z' x4 {SQ4[4:0] 位:常规序列中的第四次转换;
$ o2 c% \4 B1 k' f& aSQ3[4:0] 位:常规序列中的第三次转换;, x# P+ k. J& C( X2 X+ T2 G
SQ2[4:0] 位:常规序列中的第二次转换;3 p4 x& K2 j- K; L. J
SQ1[4:0] 位:常规序列中的第一次转换;6 }) W* ]; ?3 E  O4 B
L[3:0] 位:常规通道序列长度。
: [( w' G: b  V6 B( @, k7 `多通道转换顺序的设置方法:" M- ]; i+ i( s- C1 i
(1)首先确认使用的通道数,由L[3:0]位设置,该位用于存储常规序列的长度,取值范围:015,表示常规序列长度为:116。如果使用了6个通道,所以设置这几个位的值为5即可。
1 X7 i; l1 w. K9 y  F4 p# L0 }2 \(2)其次是设置常规序列的转换顺序,SQ1SQ4表示常规序列中的第14个序列的转换通(019),每个常规序列的转换通道,可以由SQx(x=116)指定,比如我们设置:SQ1[4:0]=14,就表示常规序列1的转换通道为14(ADC1/2/_CH14)。SQ5SQ16由寄存器ADC_SQR24控制。9 |( k& J% J/ ?# Y# b4 O
SQ1~SQ16的用法,我们举个例子看看是怎么设置的:SQ1为赋值为14、SQ2为赋值为15、SQ3为赋值为16、SQ4为赋值为17、SQ5为赋值为18、SQ6为赋值为19,即常规序列1到6分别对应的通道是14到19。其中SQ5 和SQ6位是在ADC_SQR2寄存器中配置。
3 a5 v6 s6 P, G9 C# q' p0 a6 w* l8 j" O

4 V0 ?4 e$ B8 E5 v+ [1 K22.4.3 硬件设计. W- o# F. W( ]. n
1.例程功能$ t4 b$ ]* u; L6 V" u. e
ADC和DMA属于STM32MP157内部资源,实际上我们只需要软件设置就可以正常工作,不过我们需要使用杜邦线将要测试的外部电压到ADC1通道上,使用ADC1采集(DMA读取)通道2\10\16\18\19的输入电压,并通过UART4打印出来对应的ADC转换值以及换算成电压后的电压值,可以使用杜邦线连接PF11/PC0/PA0/PA4/PA5到你想测量的输入电压源(0~3.3V)。程序通过LED0闪烁来提示程序运行。
. ^) \& n- Y, ~- l; j+ v& E本实验要用到比较多的输入电压,共5路,刚好开发板上有1 组 3.3V 电源供应接口JP7,JP7排针有3路输出3.3V,另外3路接地(0V),我们可以使用这个排针来测试,接入到被测通道中,也可以使用数字信号发生器来接入一个电压。注意的是,不要接错开发板上的5V 电源供应接口,否则烧坏IO口甚至整个主控芯片。( U8 I% G$ F: e. a$ S

/ a) \  c- G* j! B; ` 420d160206b34ba49f77b93383c6cb58.png
& F8 ^' y- ?" r! l0 A. l5 P9 }' s6 |8 F+ E) s4 M7 E; `
图22.4.3. 1开发板的电源输出引脚! v$ E9 N0 s: v) m; g
2. 硬件资源" [1 I" {/ j" y
1)LED0灯( q+ x' l& [5 w
2)串口4
2 {- `. @$ W) e! [5 z: a, P3)ADC1输入通道:
7 m2 `$ L" C# r1 l: e3 R. s通道2- PF11、通道10-PC0、通道16-PA0、通道18-PA4、通道19-PA5
3 E( K+ e+ w' F6 }LED0 UART4_TX UART4_RX UART4_RX 通道2 通道10 通道16 通道18 通道19
. |% z3 |& J/ J0 S7 uPI0 PG11 PB2 PB2 PF11 PC0 PA0 PA4 PA5
) e7 R" q  U9 y6 a表22.4.3. 1硬件资源
8 e7 ~! N3 [4 l. [5)DMA(DMA2 数据流7,也可以配置为其它数据流)
2 M7 t* O8 E5 h$ t7 H; Y3. 原理图
, Q/ U/ F# Y: Y0 a3 w' o3 c. O以上使用的ADC1的5个GPIO的复用关系可以通过参考手册上看出来,也可以通过原理图来查询,由于引脚比较多,这里就不贴出原理图了。
1 Y* p9 h- `4 y, f) k8 C- u0 Z6 q* `4 x0 e7 T3 R4 ~

6 t" O% \  j9 H! D22.4.4 软件设计0 M5 K- |8 B* P0 f5 f; s/ l5 y
1.新建和配置工程! k& S' w  \, U( i% T, j  u
新建工程ADC1_MULTI_CHANNEL,然后按照前面的步骤配置LED0、UART4,这两个外设的配置方法我们前面实验多次介绍了,这里就不再赘述,这里UART4使用中断,UART4的配置请参考前面串口通信实验章节。: H2 a* s# q* s5 L
(1)配置ADC1的通道
3 x# O, d/ C, g6 ]$ [接下来先配置ADC1通道的引脚,本实验我们使用ADC1的输入通道2\10\16\18\19,根据上面的硬件资源表格来配置ADC1各个通道为单端输入模式模式,配置好通道后:; V2 _) t* b9 R
5 P& R4 H6 n# j$ [" d  o
2dcacdcc5987404a869760bf15971eb5.png 1 `1 X& ~. G3 [  ]5 Y3 [

" y/ O6 [) U" a  U图22.4.4. 1配置ADC1使用的通道! b9 T. T! e9 N5 E1 O
然后配置以上几个通道(通道2\10\16\18\19)为单端输入模式:7 b3 W* v) |& u% K' h5 g

! X: R/ x' z3 P$ W ce6fb83459a84bb5b31d334f188cae47.png
2 s; l3 g5 o0 X& [4 B
3 Q8 p% N7 Q+ |: }% o: V9 u% V图22.4.4. 2配置通道为单端输入, R; W& b6 W1 ~0 g1 z9 J
配置好通道的单端输入模式后,接下来配置ADC1的参数,如下:+ _- W0 l; q* |: D" `

6 i' P! L" `) H0 m, p4 D0 V 5a2f05c13f19477a9dcb53345f319619.png 1 j% t! p5 r5 a6 b6 h# Q9 |

& |2 X, |( u3 K6 ]; [8 J图22.4.4. 3配置ADC1的参数5 J1 u; b5 @8 ^+ {' O4 N+ v
以上部分配置参数我们已经讲过,下面介绍前面我们没有介绍过的参数:
7 S) Y) t  ?- E●ADC_Settings(ADC参数设置)3 u( T$ `8 q! f. y
Scan Conversion Mode用于配置扫描模式,当有多个通道需要采集信号时此项会自动开启;3 T, Y! e& m. g3 s
Continuous Conversion Mode用于配置自动连续转换还是单次转换,我们就选择单次转换;
6 D+ |/ c- q& W: Z8 sDisabled Discontinuous Conversion Mode用于配置是否使用不连续的转换模式,这里我们选择禁用不连续的转换模式Disabled;
% `' l9 j0 c) X, o" g! m$ w% sEnd Of Conversion Selection用于配置转换方式结束选择,可选择单通道转换完成后EOC标志位置位或者所有通道转换成后EOC置位(End of single conversion),也可以选择转换序列结束后EOS置位(End of sequence of conversion),这里配置End of single conversion或者End of sequence of conversion都可以;
7 t6 X) E& {" P! H* f0 Z  \/ ]Overrun behaviour用于配置有新的数据溢出时,是覆盖写入还是丢弃新的数据,我们选择覆盖写入新的数据;+ N3 S! v/ F* k6 U% H! B) V/ y% A
Conversion Data Management Mode用于配置转换数据管理模式,我们暂时只能选择Regular Conversion data stored in DR register only,即将常规转换的数据存储在DR寄存器中,等下一步配置DMA后,我们再回来选择DMA循环模式;; X) E% [9 v1 E9 o( s' z
Low Power Auto Wait配置是否使用低功耗自动延迟等待模式,当使能时,仅当一组内所有之前的数据已处理完毕时,才开始新的转换,适用于低频应用,该模式仅用于ADC的轮询模式,不可用于DMA以及中断,这里我们选择关闭。' M7 k. M# d* m3 Z8 ?, e3 a0 ?# Q
●ADC Regular_ConversionMode(ADC常规通道转换模式)
2 ]( j0 G; ^5 eEnable Regular Conversions选择Enable,启用常规转换;
7 |% w2 a# p0 L. uNumber Of Conversion转换通道数量,此参数会影响可供设置的通道数,按实际使用的通道数来选择即可,这里我们使用5个通道,所以配置为5;
4 Y: I0 t+ D# l4 E& H  CExternal Trigger Conversion Source外部触发转换模式配置,ADC在接收到到触发信号后才开始进行模数转换,触发源可以是定时器触发、外部中断触发等硬件触发、也可以是软件控制触发,这里选择软件触发,工程中需要我们添加启动ADC相关的代码,也就是我们前面介绍的HAL_ADC_Start_DMA函数;: s; H" j; {& J' I
Rank配置模拟信号采集及转换的次序,总共有5组,我们设置通道2\10\16\18\19按照先后顺序转换,最先转换的是通道2,所以设置序列Rank1处是Channel2,其它通道先后顺序依次类推。- P' P: ]: E( m2 b; s
Channel用于选择转换的通道;, s7 O4 `/ o. o/ r2 _( \
Sampling Time采样周期选择 810.5Cycles;
- f+ x7 O- _! I, l" T. [& [Offset Number配置偏移量的通道选择,这里选择无偏移通道;
8 Y  \. g/ Z3 y2 H" g8 J●ADC_Injected_ConversionMode(ADC注入通道转换模式)) ^6 t4 g0 Y" j; v$ Z, k3 r' |2 ?
Enable Injected Conversions用于配置注入通道转换模式,实验中我们不需要使用注入通道,所以此项配为Disable。0 U/ x6 t4 y; g7 J7 p
后面的是模拟量看门狗的设置,本实验我们不需要,不配置即可。其它配置就按照截图上的来。' P( C+ K. ^4 D' i- L
(2)配置DMA
) T2 _. Q( n! s1 b1 y" }, e本实验我们要使用DMA将ADC转换后存在ADC_DR寄存器的数据传输到自定义的一段内存中,所以需要配置DMA。
: m; r9 [" {& {: T9 h注意下图配置,我们选择数据流为DMA2_Stream7,数据方向为外设到内存,速度等级为中等,DMA请求为循环模式(Circular),勾选内存递增,要注意,这里选择字宽为1个字(因为HAL_ADC_Start_DMA函数的此函数的形参2和形参3是uint32_t类型的,所以最好选择Word。这里注意,如果配置DMA请求为正常模式(Normal),那么DMA传输完指定的数目的数据后就停止传输了,而且在此模式下,转换数据管理模式不能选择DMA循环模式。( n" B4 m" p$ U2 @4 d. N; `
9 H6 P5 w8 X/ B, R
6bf9533a40fc40a6a4209da9b4694537.png # N# C1 q5 g2 U' u

7 Z3 Y: }: |$ Q图22.4.4. 4配置DMA模式
7 B2 D4 W' Y! A* F6 o9 P0 h; G( T配置好了DMA,再回来配置ADC转换后的数据管理模式为DMA循环模式(DMA Circular Mode):3 J, L! ]$ u+ |2 a8 p+ Q
& `) z, q$ k" m
d5628414c5444085a886d3b98398293c.png
( R) N$ v3 Z- S% ?
+ ]; Q/ Y6 k; q( q图22.4.4. 5配置转换数据管理模式为DMA循环模式
4 H7 n! e& P. Q0 R9 p; T5 w+ n' K" J我们可以设置DMA的中断优先级,也可以采用默认设置为0。
1 ^' q! r0 q* P3 s. `& J(3)UART相关的参数配置
/ M( `) }& T( c  f& E' j# m; rUART4的参数配置以及中断配置请参考前面串口通信实验(本实验配置中断优先级分组为2,UART4的抢占优先级为0,子优先级为0)。5 j. b- Q+ v6 C( r
(4)时钟配置7 W" |3 t. p* \9 V- ~7 z' i
这里配置MCU的时钟为209MHz,使用外部时钟HSE,这部分配置可以参考前面实验章节:
( J" |) }# G( B7 F4 P! t2 X7 q: x0 Y8 A

  {1 y' C( X/ W5 Y: \7 e" f) M
6 ?/ s0 L( q0 P图22.4.4. 6时钟树配置
8 x3 a2 h. R+ j# S+ j! @ADC1的时钟使用PER,其中PER时钟源默认使用HSI,即为64MHz。上面ADC参数配置中,我们配置分频系数为2,所以实际ADC的时钟是32MHz。2 p$ R2 q) w. T

- b" r( E4 A( M$ [8 C/ y. T1 _; U# M5 U
5 c7 @; X! e  @1 R! r3 n
图22.4.4. 7 ADC时钟源选择  U4 d& e% v: ]1 Y
(5)配置生成独立的.c和.h文件) F  _/ M  L8 _+ N) r0 R+ k3 E' _
* q6 J/ [" a0 N

5 G6 C# O0 r2 K2 e/ h6 Z$ A: K4 F4 p6 A; f
图22.4.4. 8配置生成独立的文件' g/ h8 f! E7 T% v( e
2. 生成工程
' w: H4 {- S" G- [# M, d保存配置,生成工程后,将前面章节使用的BSP文件夹拷贝到工程的Src文件夹下,本节我们要使用LED0的驱动:- C- b: f# p2 k
8 I* P. ]) J( B' m( l; ^

2 q+ E2 {& _) H/ N/ R3 ?& ]. k( O. i/ Y% q' ]/ {; E* k6 U* W+ s
图22.4.4. 9生成工程1 _( b" S8 `* I
4. 添加用户代码/ U9 h) }& p" c: T3 r  r$ Q) C$ k  Z
ADC1和DMA的初始化代码在adc.c和dma.c文件及其头文件中,初始化代码我们这里就不再分析了,前面的实验我们已经分析过,下面我们直接添加测试代码。- t, o' U( l8 N2 F: h0 S% Q) _7 i
(1)修改stm32mp1xx_it.c文件# n2 v+ v5 \4 ]. \2 V
和上一章节的实验一样,我们通过判断TCIF位是否是1来判断DMA是否传输完成,如果传输完成,将我们自定义的标志位over_flag置1:
! E, G  s/ Y0 |( v. [
2 h/ i" \; k: i9 |+ P4 \$ d
  1. /* USER CODE BEGIN Includes */
    5 K. @$ w$ p8 {. e  U, A5 ?! D9 ^& q
  2. uint8_t over_flag=0; /* 定义一个标志位 */
    $ D% b- x" G9 w; p0 R
  3. /* USER CODE END Includes */
    ! N; L4 C6 P$ j
  4. void DMA2_Stream7_IRQHandler(void)4 }1 V' ?4 D* s/ L" [
  5. {# Q, a: V& a. b, H( k' P1 ~/ l* A
  6.   /* USER CODE BEGIN DMA2_Stream7_IRQn 0 */- t; Q# l% E! c- Z0 p) G) m6 d
  7. if (__HAL_DMA_GET_FLAG(&hdma_adc1, DMA_FLAG_TCIF3_7))
    + b: w- ]5 ]; j5 i! g9 o
  8.     {
    ' b/ X: \# _  Z% H
  9.      over_flag = 1;          /* 标记DMA传输完成 */
    $ C' C0 _5 d& P8 R, j/ h7 s
  10.      /* 清除标志位 */& [/ T0 J1 ~/ X0 A6 M
  11.      do{ __HAL_DMA_CLEAR_FLAG(&hdma_adc1, DMA_FLAG_TCIF3_7); }while(0);
    5 A) A; K: C! C* ?0 @% P' N! B- J
  12.     }& V. n6 E' l5 V$ Z7 o, @9 }
  13.   /* USER CODE END DMA2_Stream7_IRQn 0 */. @; r4 @! k( @4 N
  14.   HAL_DMA_IRQHandler(&hdma_adc1);2 ^" c$ \! g0 Q1 Q8 E4 z
  15.   /* USER CODE BEGIN DMA2_Stream7_IRQn 1 */
    , b8 X* f9 b4 X7 F! b- b* p4 b
  16.   /* USER CODE END DMA2_Stream7_IRQn 1 */
    $ f$ ]1 M* X) ]
  17. }
复制代码

! p6 Q1 V! K5 G1 k2 p) o(2)修改main.c文件  f! J& A$ B' o# D' V
+ i' \* c/ d0 N6 y3 H- A. l
  1. #include "main.h") z/ |8 L7 M( ^) J; W+ J( X
  2. #include "adc.h"
    ' W# C# U! }2 y% F5 k& U! \9 I; w
  3. #include "dma.h"2 @6 M6 y! K: U2 L# i* O2 E. Z
  4. #include "usart.h"" B$ k: {$ p( w1 o) [  T
  5. #include "gpio.h"* U$ t2 C3 R3 x* H9 w5 p
  6. 5 \0 b9 @9 I; j. j3 t7 E5 I& t
  7. /* USER CODE BEGIN Includes */) W$ U% X; D& |. T  l; s
  8. #include "./BSP/Include/led.h"
    3 _" ]% _+ X/ S  O5 m; @
  9. /* USER CODE END Includes */
    : H% J2 i$ C6 a( e6 V) q

  10. / V  D0 M# [6 A. H* R7 T
  11. void SystemClock_Config(void);
    0 R# H- r- H- ?7 u* X
  12. void PeriphCommonClock_Config(void);1 M' c$ f2 b" I4 `
  13. 9 l) U% h6 Q$ ~( j/ \& C: |1 j
  14. int main(void); j. U8 J, E+ r" w* B& L  t) x
  15. {
    - K  I+ e6 X3 I0 X" Y
  16.   HAL_Init();& h2 N* ]9 L- Y% Y
  17.   if(IS_ENGINEERING_BOOT_MODE())9 \2 T$ t9 z, ~0 B  H
  18.   {& e3 P* v' m. _5 p0 N, j
  19.     SystemClock_Config();
    ) ?% O( z- k! E7 l' j
  20.   }
    0 _5 G  b8 A/ D0 a  ^# {" f8 e
  21.   if(IS_ENGINEERING_BOOT_MODE())7 Y; B1 n2 u% y2 @
  22.   {5 D) i' n! x3 v; F; Z
  23.     PeriphCommonClock_Config();7 v% P  X4 q5 [6 W( p
  24.   }" B7 C6 ~7 t* V, V! I5 b$ L5 r
  25.   MX_GPIO_Init();* i1 x) r6 @/ U7 Z: P" F* e8 M
  26.   MX_DMA_Init();  d" {7 r) F- A( L: g  L
  27.   MX_ADC1_Init();0 Y; U) b! i, M& t% ]( Y
  28.   MX_UART4_Init();( A8 y+ G: Z/ Y4 Y
  29.   /* USER CODE BEGIN 2 */
    # L! t: M; S* [; e8 W. X$ Z1 {
  30.     HAL_UART_Receive_IT(&huart4,&RxBuffer,1);/* 以中断方式接收函 */3 C0 E4 F& m$ P, O# q% B0 g
  31.     extern uint8_t over_flag;
    2 ~: y* s; ~# e/ |& G9 [
  32.     #define ADC_DMA_BUF_SIZE 5     /* ADC DMA采集 Buffer大小 */; `2 O  s( M' w4 F8 r0 {' P! X
  33.     /* DMA传输的目的地址,这里添加了一个关键字volatile */. p  ~' m' i) Z0 O
  34.     __IO uint32_t g_adc_dma_buf[ADC_DMA_BUF_SIZE]={0};
    * P( o5 j$ h8 z& R& H1 K
  35.     /* ADC校准,如果不校准,转换的数据会有偏差 */( I; E# _  i) i: @, u( A6 P
  36.     HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED);1 C" X/ U& H+ Q/ Z
  37.     /* 启动ADC转换,同时ADC会请求DMA传输 */
    4 ^" V5 C1 d+ V" [/ g
  38.     HAL_ADC_Start_DMA(&hadc1, (uint32_t*)g_adc_dma_buf, ADC_DMA_BUF_SIZE);( ?) s) l4 o/ R1 m, a- x, t6 b# K
  39.     printf("ADC Start\r\n");
    / W4 d, N  n  {- e# a. ]% u9 i9 j  K
  40.   /* USER CODE END 2 */* P; Y+ q  `0 r; k0 Q( J8 g: I. ~! ?3 h

  41. % _! `; Z( [. B  w' F' `
  42.   while (1)
    4 x* V; t+ J9 P$ H  S
  43.   {8 P  L7 a6 r$ m! F3 Y# i6 o
  44.     /* USER CODE BEGIN 3 */
    8 U$ F- B* m( H& l' [
  45.     /* 启动ADC转换,同时DMA进行传输 *// ?6 F$ _$ w$ A: i8 Q6 g2 N
  46.     HAL_ADC_Start_DMA(&hadc1, (uint32_t*)g_adc_dma_buf, ADC_DMA_BUF_SIZE);
    % a# G' M% J$ _" R' {
  47.     if(over_flag): E' ]8 Y5 w: ~# p  o
  48.     {. s7 e, h  b' N( j7 C2 f
  49.             printf("ADC Value_ch2 = %ld   Voltage =%.3f \r\n", \                                                                 g_adc_dma_buf[0],(float)g_adc_dma_buf[0] * (3.3 / 65536));
    8 P8 q4 E+ A4 Y) }" T
  50.             printf("ADC Value_ch10 = %ld  Voltage=%.3f \r\n", \                                                                        g_adc_dma_buf[1],(float)g_adc_dma_buf[1] * (3.3 / 65536));
    ! c* g8 J0 |; ]1 }& H, _3 P
  51.             printf("ADC Value_ch16 = %ld  Voltage =%.3f \r\n", \                                                                g_adc_dma_buf[2],(float)g_adc_dma_buf[2] * (3.3 / 65536));
      f5 ~* c% ^, ?7 X3 ^! g8 \% ~  {" F
  52.             printf("ADC Value_ch18 = %ld  Voltage =%.3f \r\n", \                                                                g_adc_dma_buf[3],(float)g_adc_dma_buf[3] * (3.3 / 65536));# f1 u8 r, p) [( B9 F5 Z1 {
  53.             printf("ADC Value_ch19 = %ld  Voltage =%.3f \r\n", \                                                                g_adc_dma_buf[4],(float)g_adc_dma_buf[4] * (3.3 / 65536));
    ) @) w: f9 W. ?
  54.             over_flag=0;! r1 K% ^) j  V' V5 h" ?" v
  55.             printf("End once\r\n");- j+ p( t2 l: D  m
  56.     }  {* N8 y( p* l7 w6 k5 z5 |- l
  57.     HAL_Delay(2000);/* 延时2s */6 L5 T! M7 N+ F7 |$ o
  58.     LED0_TOGGLE();  /* LED0翻转 */3 w5 X& H6 N8 I: X! H
  59.   }) K  v$ A% p& s0 o. [' U7 s. E
  60.   /* USER CODE END 3 */+ f* Y" A* R- m+ c
  61. }
复制代码
+ a/ j- R: A  t: h$ T9 ?- L& O
以上代码和上一章节的实验一样,我们使用HAL_ADC_Start_DMA函数来启动ADC转换,同时ADC会请求DMA进行传输。
+ c6 U6 ^1 ?' Y6 X1 x如果标志位over_flag为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]中,我们分别读取每个的值,然后再计算对应的电压值。
% F  l8 a( u1 E4 q7 ^$ }
) U3 f) ^: a* F0 V: S. z0 ]22.4.5 编译和测试
8 m2 l) M6 g& O4 m- e程序中使用浮点运算,所以要设置工程属性,支持浮点打印:5 A) L5 e4 U2 s& c
0 g( ?; r: ]8 q/ \; Q0 [

9 a! a. j* l" I& a8 x5 o+ d* X5 U" s
图22.4.4. 10设置工程属性+ u; {1 @% [) P8 Q) g, [& A
本实验通道2、通道16和通道18用杜邦线接的开发板JP7上的3.3V引脚,通道19接的JP7的GND,通道10接的一个数字电源(数字电源调试输出1.8V),当然也可以将通道10接JP7的GND或者接电位器,通过调节电位器来输出0~3.3V的电压。
' V1 N& y) ]: ?' [+ F- I+ |测试结果如下,每组数据都会不一样,数据在刷新:
/ V8 U" e/ X% h/ D3 _7 ^  x
5 Q/ k8 F" ?: E6 D# s c5ce7ada4d7c4c9b8ed39147d5d8c50a.png
3 J" ]3 d+ u- I
/ M( ~! u9 g) ]0 F: \7 V& N- m, H2 H0 M: }图22.4.4. 11实验结果
; J# t8 L7 x/ ]  c0 P2 X: Z/ K22.5 单通道ADC过采样(26位分辨率)实验& }- T. {2 `, q: f
本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 15-4 ADC1_Over_Sampling。/ v5 V. S  z6 U6 a
22.5.1 STM32MP157过采样
- z7 T# O( \" z; z* L+ v4 |2 n在信号处理中,过采样(Oversampling)是指采样频率以高于信号最高频率的两倍来采样,这样就可以从采样信号中尽量恢复出原始信号。
% h, T" M7 j% ~6 b! V! N1 |本实验我们来学习使用常规单通道的连续模式的ADC过采样(26位分辨率),在过采样模式下,大部分 ADC 工作模式都会保留。过采样是在硬件资源不够的情况下的一种资源换精度的方法,使用过采样技术,保留了输入信号的较完整信息,降低噪声,并提高了采样子系统的精度。如果频率足够高,那么可以获得无限位精度。
7 [0 p  h1 V* s7 eSTM32MP157的ADC可达16位的转换精度,使用过采样技术可使得ADC达到26位转换精度。其过采样单元会进行数据预处理,可以减轻 CPU 的负担,过采样单元还能处理多个转换,并计算多个转换结果的平均值,过采样率N和除法系数M可以调整:$ v6 l# G, e4 E- ^( G
8 T& F' o/ o- N! I' g. o
0ec885db2f4245bb8af58dd646020b30.png + i# F; w+ Y( `" q4 M& C# T# o, J
1 [2 Q; X! |2 f/ t% o" h* f6 a7 q
图22.5.1. 1 过采样结算公式& X& G6 H$ I5 |
以上参数中描述如下:
! i5 ]9 P; h6 `* f: c$ E( R. `过采样率 N 通过ADC_CFGR2寄存器中的OSR[9:0]位进行定义,范围为2x到1024x;
1 t% ~: T. B. Q2 ~. Z) @分频系数M 通过向右移位来实现(最多可移10 位),并且通过 ADC_CFGR2 寄存器中的OVSS[3:0] 位定义;
# _# C' ?4 n$ c求和单元可得出多达 26 位(1024 x 16 位结果)的结果,结果可左移或右移。如果选择右移,则会使用移位后剩下的有效位四舍五入为最接近的数值,然后将得到的这移位后的结果传输到ADC_DR 数据寄存器中,此值就是得到的转换值。如下图是移位10位后的结果图,最后的第0~15位就是我们要读取转换值的有效位:
/ {4 U0 o% e! O. p$ r1 c+ f+ M7 ^+ C9 y, a: H
9ffc526e784043eeb886b3c3285e10cc.png
# \4 G' U6 b* S, ?6 C/ J: R9 _; O" Q
图22.5.1. 2采用10位右移和四舍五入进行过采样得到的16位结果
: u7 E1 J( ?9 @STM32CubeMX上最大可以设置右移11位。设置移位的时候,要注意计算电压时的公式,例如,如果设置右移10位,最后的计算结果中需要3.3/216,如果不移位,最后的计算结果需要3.3/226,如果右移4位,最后的计算结果需要3.3/222。本节实验我们设置不移位,所以后续的计算中要除以226。* Q3 `# @3 x( ^
22.5.2 ADC寄存器" e' t4 g% Z/ h, S) Y* l* Z
本实验我们很多的设置和单通道ADC采集实验是一样的,所以下面介绍寄存器的时候我们不会继续全部都介绍,而是针对性选择与单通道ADC采集实验不同设置的ADC_CFGR2寄存器进行介绍,其他的配置基本一样的。5 _1 L: P. l0 }, p
ADx配置寄存器2(ADC_CFGR2)
( D' k) M' B& ?- K( p0 w3 zADC配置寄存器2描述如下图所示:0 [( y1 @$ E4 p: P5 a+ W

. S: u* R1 ]% t: C0 p! }' ? 019fedc0134d42c9aebd8f6fdf430d4c.png
1 t& w6 C1 X6 |. |9 `, F& s* y* l8 O
图22.5.2. 1 ADC_CFGR2寄存器+ d3 U! D( {0 p" \9 G& y% T
OSR[9:0]位,用于设置ADC的过采样率。OSR[9:0]=01023,表示1x1024x过采样(1x表示不进行过采样)。本实验,我们使用过采样,并且分辨率调到最大,所以设置OSR[9:0] = 1023。: z8 ~; `& a; g4 V6 Z
ROVSM位,用于设置常规过采样模式,默认为0即可,即连续模式。$ j  C  [& T; ]
TROVS位,用于设置已触发常规过采样,默认为0即可,即会在触发后连续完成某一通道的所有过采样转换。' ]' h. }4 t5 d; ]  A
OVSS[3:0]位,用于设置过采样右移。
, E; I- J/ l& \3 RROVSE位,常规过采样使能位,置1使能常规过采样。' ~, o3 q* H# Z& I! S) d6 G
+ a7 l( q& h2 B" {$ e8 x
+ K6 t& x" ~2 ]! Q; K! C# r
22.5.3 硬件设计
% H  {6 c" u5 ^6 E$ `1.例程功能
, {0 R7 Y1 F! [4 ~# T使用ADC1过采样(26位分辨率)采集通道19(PA5)上面的电压,并通过UART4打印出来。实验中LED0闪烁,提示程序运行。( v1 o" s& w0 U2 y* g' t
开发板上有引出PA5引脚,先使用跳线帽将JP2排针的ADC1和电位器的RP_AD连接,这样PA5就连接到电位器VR1上了,电位器上接的是3.3V,用户可以通过调节电位器的旋钮改变接入到PA5上的电压值为0~3.3V(实际上就是通过改变电阻来改变电压)。实验前要记住检查跳线帽是否有接好:
& e2 Q; r7 c! R  w8 M" ]0 d( Q8 ]9 e0 G& K7 b
856e0a211f22456db469a682fd05d487.png
) T! T( O& ?/ f  }) d4 E& [0 |* P
图22.5.3. 1硬件部分6 J  [/ j* v' d" F3 E
开发板上有1 组 3.3V 电源供应接口JP7,JP7排针有3路输出3.3V,另外3路接地(0V),我们也可以使用这个排针来测试,接入到被测通道中。注意的是,千万不要接错旁边的JP8排针引出的5V引脚,否则烧坏IO口甚至整个主控芯片。7 l( C. M$ x8 {1 M3 b- ?7 l1 z5 t) m

+ c5 \  O+ {4 m6 }) \ b90fc67425864600ad908c0790776755.png
; H* S* X' b# J/ v; H
' W: r& b4 F* m' a图22.5.3. 2开发板的电源输出引脚# `3 K: U$ \/ P" Q9 W) n; U5 t/ M
2. 硬件资源% b/ w+ j# d7 t( D3 o
1)LED灯:LED0
9 ~" ^7 m  e6 F& H, o& z2)串口4
# [+ g, `  u4 q3 C3)ADC1的通道19引脚(PA5)
* @. _+ }6 ]) R$ n' A. Z- A4)DMA(DMA2 数据流7 ,也可以配置为其它数据流)
. h7 ]5 V2 n8 z4 Z" }% C8 F* M0 cLED0 UART4_TX UART4_RX ADC1_INP19  T8 W3 z1 U, D$ s$ Y
PI0 PG11 PB2 PA5
' a* g; d4 W* t' K9 U表22.5.3. 1硬件资源- \0 s# r9 J  F
3. 原理图. ?/ H  S; T" Z- P2 h" l
DMA属于STM32MP157内部资源,通过软件设置好就可以了。ADC属于STM32MP157的内部资源,实际上我们只需要软件设置就可以正常工作。如果使用ADC1测试其它外接入的电压,只需要一根杜邦线将要测试的电压引入PA5引脚即可,要注意接入的电压范围不能超过3.3V,否则可能烧坏我们的ADC,甚至是整个主控芯片。
; q; K4 k0 z* V
! o7 f' Y! I1 u$ u# S f5f25ab2abf14189a620707419dc5500.png 4 S! Y  y5 [  S, j

4 U" A$ |$ P7 R/ H6 O6 x图22.5.3. 3原理图部分
2 L% w( P* K, r% U- A
8 N8 ^& s# R. m9 J, O5 e" s22.5.4 软件设计
$ ]6 `* S/ V+ ]3 |2 h1.新建和配置工程
5 b: }, K, b: {新建工程ADC1_Over_Sampling,配置LED0和UART,这里就不再对LED0和UART的配置进行赘述了,可以看看我们前面串口通信实验的串口中断接收回显实验。ADC和 DMA的配置请参考前面 单通道ADC采集(DMA读取)实验部分。不同的是,本次ADC1我们使用到了过采样(26位分辨率),ADC的配置参数如下:: @; v) C8 M0 W# H; a7 a' a5 @

' I' W! P* c& k1 V) n1 \ d8737d7ec4b544d2a660d16b4d302cbc.png - e8 T* i: |$ V; L

  z2 O; M6 n1 _. s! ]3 u  n# W$ _图22.5.4. 1ADC1参数配置" \/ @( w  K, d5 N% J$ P1 Z5 `+ R
以上大部分配置我们前面都有进行详细介绍。下面介绍不同部分:0 I2 V6 M7 e* l+ g/ m
Scan Conversion Mode用于配置扫描模式,当有多个通道需要采集信号时此项会自动开启;
# A' U& u: d" k( T; LContinuous Conversion Mode用于配置自动连续转换还是单次转换,我们就选择单次转换;9 H9 j4 }% }7 J/ X" L
Disabled Discontinuous Conversion Mode用于配置是否使用不连续的转换模式,这里我们选择禁用不连续的转换模式Disabled;
4 u$ P, D& m9 ^6 K3 E/ FEnd Of Conversion Selection用于配置转换方式结束选择,可选择单通道转换完成后EOC标志位置位或者所有通道转换成后EOC置位(End of single conversion),也可以选择转换序列结束后EOS置位(End of sequence of conversion),这里配置End of single conversion或者End of sequence of conversion都可以;( W  _1 `. B, n- r
Overrun behaviour用于配置有新的数据溢出时,是覆盖写入还是丢弃新的数据,我们选择覆盖写入新的数据;
8 W' d' k1 f. ]* c$ HEnable Regular Oversampling用于配置是否使用过采样,本节实验要用到,配置为Enable;
/ W! [9 r$ K  ]! ^& HOversampling Right Shift用于配置过采样是否右移,也就是设置寄存器ADC_CFGR2的OVSS[3:0]位。我们不移位,也就是26位分辨率,最后计算电压的时候是3.3/226;1 W" C  W+ A3 I. y# g4 w' V
Oversampling Ratio用于配置ADC的过采样率,也就是ADC_CFGR2寄存器的OSR[9:0]位,这里就配置最大值,为1024-1;
$ f; Y: c" M# ~+ Z% SRegular Oversampling Mode用于配置过采样的模式,也就是配置ADC_CFGR2寄存器的ROVSM位,有两种可选,一种是连续过采样,一种是过采样恢复模式,本节我们使用连续模式;. R' @1 w$ j4 E8 H( n& g/ G. s
Triggered Regular Oversampling 用于配置触发常规过采样的转换模式。如果选择了连续过采样,则只能配置为一次触发所有过采样的转换Single trigger for all oversampled conversions。
) V, M0 g: A; K1 @8 F2. 生成工程
" M. u1 ]' i( S2 r/ ~) a% H: O, H) V保存配置,生成工程后,将前面章节使用的BSP文件夹拷贝到工程的Src文件夹下,本节我们要使用LED0的驱动:9 G( O+ b8 @! Y7 d8 v0 A

  k* B, b5 ]/ A$ X 35faacb4b04d47c3a542d22aaa7ce327.png 2 c. J% P# H6 `8 [) V
% F* {# P8 J! k/ I+ p: E# t' }! b
图22.3.5. 12生成工程" ~8 M" m6 E2 i# r$ w" S& M, ~& ^& r
3. 添加用户代码0 M( {" k' @  f: S4 F: f8 T
用户代码直接参考前面单通道ADC采集(DMA读取)实验部分,这里不同的是,计算电压值的时候,是3.3/67108864:
1 t. R# ~8 s* ~6 U6 u: c
9 i' N6 c  d3 Z. a' C 88bbf06894e14a87ba26c4fb600510e5.png . A" D  K: u3 V% u) O$ E
  h1 z( z$ I, e* D, S
图22.5.4. 2添加代码
$ k" O9 u  S- |( P! P5 J在这里插入图片描述$ K) L. Z; F4 m5 V8 \, j

) u. S/ N- A2 F, D& W; z22.5.5 编译测试; D+ h) Y0 u8 m; G& Z- N0 e
程序中使用浮点运算,所以要设置工程属性,支持浮点打印。
' F7 @1 C) Z3 h4 Z& B2 e  U26位ADC分辨率最大值为:67108864,实际上由于分辨率太高,低位值已经不准确,一般我们可以设置 ovss=4,缩小16倍, 即22位分辨率,低位值会相对稳定一些,这里我们为了演示26位过采样ADC转换效果,把分辨率调到最大,24位,并且不移位。我们在PA5上接3.3V的电压,测试结果如下:# p4 o0 ^& l: r1 a* t1 |1 V
8 R& J1 ^- q% P7 @7 c2 ~/ D1 N
1dfe26892449484a9f8cbe35c15f88af.png : j1 {3 A3 U* x, l# e$ H* }

3 m0 [4 Z& x' `1 P5 s) C9 |' b1 e图22.5.5. 1运行结果
: x- X# }# z4 u9 C/ w————————————————
4 T  l! H7 h8 C) h7 [7 f版权声明:正点原子9 l1 X; D1 y. }) g

  l6 M' G) r2 z7 G6 L. G' d) S5 X" m" Q2 N0 F0 s/ c$ J
9 ^4 V* c3 `" a$ x
60f1bfb6ee3b40d0bb5763954ba5e320.png
2e9ce3873655417c8f1f36166e215081.png
7c376d52328f4246b25ec079e4da54c0.png
f9c69aa5bb1047398e79b1dbcbcc5905.png
b19eb60917b745cbb5282320933d4189.png
赞 收藏 评论0 发布时间:2022-9-26 22:21

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版