6 Q7 @1 L) u2 E9 @. L+ h- Y
! [0 f& z. ~/ F1 H- `) b
" g& a( b& V0 d* ^9 {很多STM32芯片里往往内置了专用的ADC通道,比方用来测量Vrefint,VBAT的分压或温度传感器的输出电压信号。不同系列所内置的模拟信号通道可能有差异。这里以STM32G4系列为例,它内置了对应于Vrefint,VBAT的三分之一分压和温度传感器的输出电压的专用模拟通道。
9 [5 C/ I" B- W4 z5 S, p* l6 ~7 g$ {: D& X8 |) g6 \
, N6 [) ]* Q2 p5 e; r; X
9 x: d; F9 E( ]& S5 z% J" q
$ ?1 x. b! [$ ~
& K- v8 ~/ [* o" A5 e# n# _下面的示例就是针对上述3个通道进行ADC,并测量相关电压和片内温度,最终得到3个结果,分别是VRefint电压,VBAT的电压,片内温度。3 H' U; E8 x6 U
! |7 G# w1 u/ n1 T; G9 Q8 o2 S5 v7 R2 T
$ Y; B! d; r& g7 T$ T" F0 x
" C! K1 N. K7 t: X
实现过程是这样的,大体分四步:【有点点麻雀虽小五脏俱全的味道图片】
1 b1 _1 N$ N7 o) H; M: i; D4 N. Z% o' H8 }
1、TIMER1 更新事件触发ADC的转换;
E ~$ {3 W; Z' z% U0 n3 `$ n4 j9 f! P! |. h
2、CPU基于EOC中断获取ADC结果;4 R% q" o' [8 `- j
. |6 v1 L/ ?, z: `# z
3、对ADC结果进行换算,得到电压值和温度值存放在特定内存位置;
2 ?5 `3 X+ c2 \
5 v5 I+ @( {% Y- z/ K- C4、基于DMA传输通过UART将最终结果在串口终端显示;
0 l* M* L+ A: b, A; J4 N/ c/ I- l& V% W
其中,TIMER1的CH1输出PWM波形,其更新事件做ADC的转换启动信号。每次的TIMER更新事件触发ADC,3个通道扫描方式转换。这里的UART使用片内LPUART,使用它主要是考虑它跟板载虚拟串口直接相连,没有其它特别用意。
9 X4 e1 H8 _5 e% z" a; }3 Q9 t
+ \ j. h( S) n: C) [: K2 Z7 B& d# V l/ G
2 Z! Y" Q+ J* Y& P我使用STM32G474Nucleo板来进行下面实验。其中VDD=3.3v,VBAT与VDD相连。另外,ADC模块的参考电压也是3.3v.
* S1 W0 Y4 `5 Y
, O5 n/ X7 W# K) o+ H( v使用CubeMx图形化工具进行配置,先看TIMER配置:
; u# | }& f0 t' b; X8 {; g( S% A4 w. `- i# l. I
, u k) ?0 B+ Y% Q; U
4 |$ f/ p* |7 q! T/ B
再看看ADC的基本配置:2 ?5 a4 {# V; q1 u9 h/ l
/ ^9 Z+ s4 T& Z2 d. m- E* X
8 G+ Z) z, ^5 q1 V) d# m/ C" W- l7 ]/ h0 M! N6 \$ |, N: T
LPUART的基本配置:
8 X0 K7 i/ o7 F) F( a- t3 T2 j$ w. c3 F7 Z; K1 P k+ D
" Y: L3 V0 E% s; m1 X2 J
1 i' b! Z9 U, V% q: C因为要使用ADC中断和UART的DMA传输,记得做ADC的中断响应使能配置和LPUART的DMA配置,这里只使用UART的TX DMA功能。
4 c, {3 i Q3 @) K% j6 y% Z
* x7 B* L0 Z' S2 `3 \% N: k, q
" w/ f; @1 E4 m z& ` Z6 k, }( \0 F9 O7 _
使用CubeMx主要配置主要是上面这些。
$ Z' @$ [* V3 a: P; ~8 v! {2 u% |) W' |/ y3 G; w9 w
在组织用户代码前,先简单介绍下片内温度传感器的内容。该温度传感器针对不同温度有不同电压输出,其输出电压跟温度呈线性关系。ST公司针对片内温度传感器在两个特定温度【30℃和110℃或30℃和130℃】、基于特定参考电压【3v或3.3v,不同系列以数据手册为准】生成了1组校准值并存放于片内特定FLASH位置。
. I$ o! Y8 x# w e
: B6 a1 t/ Z8 w Y5 USTM32G4系列的校准值是在参考电压为3v,30℃和110℃条件下的两个值,在数据手册里还给出了校准值的片内存放地址。& z X$ t$ K& i: P K5 }
( m0 k1 D4 m5 v' p Z( n' f. y8 t5 k& q) P# X8 G
: v+ t9 d3 m" n9 @6 [( j( }针对这个温度传感器的使用,ST公司在参考手册里还给出了计算公式。其实,有无这个公式无所谓,我们不难自行推理出来。【TS_DATA代表某时刻测得的传感器输出电压对应的转换值,TS_CAL1/TS_CAL2分别表示在30℃和110℃条件下基于传感器输出电压的转换值。】
: ]7 ]3 M. E D+ A4 J8 y5 u! C( G J" ], v, e, Z7 y. f9 c5 K8 n
2 W! k3 R; `# }1 X7 n# Y" x$ k5 Q" x) ^) ?* Y+ h9 X1 j+ `, a% h
另外,前面提过,ST公司在手册里给出了温度传感器的两个温度下的校准值,但要注意生成校准值的ADC模块所用参考电压跟我们实际应用时AD模块所用的参考基准电压可能不一致。如果不一致,就必须将ADC值换算成同一基准参考电压条件下的数据。目前在ST手册里也特别强调这点了。我把上面一副图再贴一遍于此【见黄色语句提醒】。; @7 O2 @; O; j, S0 E" v
) v7 e- W! Z: p- F; o0 Z& G$ p6 `$ w; ~0 J1 x7 w- C
5 C! |! |$ K& I/ [
关于这点,我们也不难理解。同一待测信号、同一ADC模块在不同基准参考电压下转换值往往是不一样的。见下面示意图加以理解。
2 Q( J' _1 V" l& T/ k6 j& [% {* f# N5 C2 {7 I( B6 |
# C, W2 {( ^1 f0 `: D, O
& ?& o7 f, g) I) Y x# [完成各项配置后,创建软件工程。添加必需的用户代码: U& D( ]" q2 t$ N( R: E3 B* H7 Q
4 |+ W% x# P( Z- #define TX_Timeout (9999)& d! v8 T2 u R# T) G! F1 X
/ T& W8 O% r3 X9 }3 A4 y- #define TS_CAL1_ADDR (0x1FFF75A8) //用于计算温度传感器数据; I6 \. T0 ~& \: |* d8 v3 u
- #define TS_CAL2_ADDR (0x1FFF75CA) //用于计算温度传感器数据7 c: N+ V5 y' _% D
$ I+ O: i! e* i- m7 ^1 X- #define size1 (40)" n1 r+ z- c4 b6 ~# `
7 b D. `! ` J4 }6 s! r- char WDVol[size1],BatVol[size1],InVol[size1];
* O' ^3 ?" z( Y, l4 _8 u1 f
7 k P7 |. }, g3 k% a- uint16_t ts_c30,ts_c110;
, A2 a: u3 w9 Q M; ], Q
1 ]7 a) F+ ~ G# f% b& Q- uint16_t ADCResult[3],convCNT;8 V0 u8 U3 A3 @7 [* y5 Y0 S- y+ X
- # @* G8 h& I' z+ x- S' p/ o
- volatile uint32_t Completed,EndofCon_Flag;
" \* o# x1 F3 ]: E: P. c - - Z3 _! N% R/ D: w
- float VBATVolt;//存放BBAT电压最终结果
9 \( ^+ w4 u+ ~( I; u; b - float VRefint; //存放Vrefint电压最终结果
& B- S1 P, ]. z# P5 z) w - float Temperature;//存放片内温度℃最终结果
7 n* E2 ~; I- J/ P% I
" v9 M0 w: I5 Y' }% j- int main(void)4 }" e( m4 f9 |9 b% S0 x
- {
! R5 m0 R2 n2 W0 ^5 P0 P" F, c - /* USER CODE BEGIN 1 */, Q* E, w' ?. P3 ?1 R( P7 L/ Y
: y6 v1 _/ m$ h, M! K. k- l f) U- /* USER CODE END 1 */
$ M5 \& m/ i0 ~. ~4 A - 9 {- H) s4 t$ R# h1 _( a8 w# E) h
- /* MCU Configuration--------------------------------------------------------*/2 s: I9 t+ A( |
, J2 O8 r" _% p; E E- E- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
4 k& t6 A( \. L7 z9 y4 y: N3 Z% l6 I! x - HAL_Init();/ z, Q; w) P- G: I7 Y
- - k4 y% g4 K. ~$ |. F
- /* USER CODE BEGIN Init */
$ P. E9 @ n4 E* a; f - 3 V) Z! p) S: g; t# G4 N9 Z
- /* USER CODE END Init */
! q# h9 Y7 s; `/ [& G( ~! t - 3 U' w: V. i. T/ |$ `$ |% V* I
- /* Configure the system clock */
7 i6 G, b0 I! _) @ - SystemClock_Config();% l# j/ k9 U; O$ C2 A9 j$ s/ Q
- ! A8 D5 H+ v9 z: S# p/ @
- /* USER CODE BEGIN SysInit */. N3 X$ s8 ^/ H- c- P0 o w
- " F' y& e, T6 y# U
- /* USER CODE END SysInit */ l2 c# W( s0 x6 O0 V$ d+ K5 @
- - ]9 d/ ~; A/ r
- /* Initialize all configured peripherals */, B# i% b* c# a! d( r- s4 w
- MX_GPIO_Init();& c2 j" n1 b' [* u1 N
- MX_DMA_Init();
: U1 _4 V! c* p4 w+ P. w5 x - MX_ADC1_Init();8 ]* y& k- ^5 m% n! [) f
- MX_LPUART1_UART_Init();% T8 N! Q+ P7 v; I7 y
- MX_TIM1_Init();# \4 h% D% R& ]9 e2 s% j
- /* USER CODE BEGIN 2 */
$ o" o; D# E& J `+ I
. o% k( K3 x) Y- ts_c30 = *(uint16_t *)(TS_CAL1_ADDR); //读取30℃时的ADC校准值
' l- n/ d6 \0 Y - . I6 m8 ?8 R5 f* t6 e3 n: M7 g% I
- ts_c110 = *(uint16_t *)(TS_CAL2_ADDR);//读取110℃时的ADC校准值 G) \8 o1 R T6 @5 S% s$ p
9 f5 D" q9 V6 h: n- HAL_ADCEx_Calibration_Start(&hadc1 , ADC_SINGLE_ENDED);//ADC校准1 g2 p2 t0 e5 J) @
) y+ D1 l& f; e% ^" [# r- HAL_ADC_Start_IT(&hadc1);//启动ADC并开启转换中断 f9 w5 ]1 u! J
) o" D. Y) | D6 E1 e3 ^- HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
5 I3 g* }7 ^0 E( B - # [, g# t8 D+ h$ s
- + N" }1 X$ P0 l% O5 ~, X& I4 e& i2 b a
- /* USER CODE END 2 */. q& E4 x: A, O# ]* S/ q' E, b
- , w8 ~+ Q& P2 w3 r1 y ^! W
- /* Infinite loop */
5 _2 X8 ]* B% V% j2 e* y - /* USER CODE BEGIN WHILE */. ?1 @$ K5 d! O) f3 G* x
- while (1)8 [3 U# O3 @7 x: p; k
- {
1 g5 U7 _/ ~5 d8 g5 m - /* USER CODE END WHILE */; x- C$ f2 N# D4 e M: v, h# g
. |% [" x/ W4 E5 j, a- /* USER CODE BEGIN 3 */# Q* d* y7 E: S% V2 i6 i
- 8 T/ [9 q$ V% I' H( @( R- v- B# M
- if (EndofCon_Flag!=0) } n; x& P/ S0 p; E
- {
6 |9 I( b% m" U) l. I$ l! S0 t - VBATVolt=(ADCResult[0]/4095.)* 3.3 * 3.; - [" p* N" V5 A! O( k' d, L
- . x) V, S0 s* F& p' W: k* z2 z1 ?# a
- VRefint=(ADCResult[1]/4095.) * 3.3; ' I, |- `' D8 R4 f
- - ^3 ~$ n7 E6 U) l8 c$ g
- Temperature = 30.+ (88.*(ADCResult[2]-((ts_c30/1.1))))/(ts_c110 - ts_c30);
/ F3 M" K7 N& L
8 B. R& i+ {7 | G" p- EndofCon_Flag=0;
! L' |5 d6 C8 N2 Q$ }4 u# K - ; d2 D/ _, G( V8 F
- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol), TX_Timeout);: A# e- N: @ Z# [9 ^
. W# y9 ]# o7 s8 J. X+ W* X. c- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_RESET); //for auxiliary test) t% c* F) [/ `
% Z( t8 ]; q6 L; o# `) a' n- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));
. m, x* `% [# Y+ c! W: `, h- Z+ d - 3 u" K1 C! L. Y9 G- ^. @
- while(Completed==0){}
# P" l0 e7 K3 C; B; e" { - Completed =0;
; v. u& ~- w" T( ?- H& j3 g - 1 C% e5 [- G# L: {& Y+ B. Y
- . A/ [4 }! O' D# p3 Y) [
- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol), TX_Timeout);4 O2 B9 k+ i% k: O# Q; O2 Z
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));' ]! d% t8 C) L1 p6 H+ L) c
0 X5 x2 M, z7 {2 F% f, b. K& W- while(Completed==0) {}$ T4 v2 T- F, A% U$ w
- Completed =0;" n( y* Q2 i% O7 m* B# W; f. P# ?
* y. d8 \, V* @4 u$ X; q
' Z+ f' q) X0 {( |3 H2 z6 |1 C- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol), TX_Timeout);
* ?' v3 d6 Y# Q- y6 ?/ { - HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol));
6 z p+ a1 u: W3 V
) z5 X ? U" d! T$ V, U, U$ r- while(Completed==0){}+ b$ r# p. }9 X- h
- Completed =0;! i& f \1 | e6 j3 J
5 X( k6 G0 [7 ]0 M5 Y- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_SET); //for auxiliary test& C: s" g$ {" G3 b+ n; |% K. D; f+ @0 @
- . N$ W) q7 b& U Z( r' c$ ]. s
- }
" q7 @; x0 M$ ^4 i - 0 |* D9 ]; b2 x
- }
$ [6 [; w, w- V$ r - /* USER CODE END 3 */
4 _* i) O+ o" H - }
% m/ N6 h2 U5 n. ~ - . Q2 O/ s6 h ~% K
- //ADC EOC 中断回调处理函数! P4 C4 D2 b1 P; K& C0 N( w
- void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
9 o2 i0 ~/ u1 t8 B. X d5 I/ c - {
8 g% y0 \( Y! P6 {9 g
0 Y- R- R- `9 B W1 _- ADCResult[convCNT]=HAL_ADC_GetValue(&hadc1); //获取转换结果并存入数组
; Q" m* N5 L2 C) a - ' ?: p8 }. P- e2 ?$ c# X# f# {
- convCNT++;9 C/ y, r# [* ]" {1 ~
0 @" D& \7 y8 ~9 G3 [- if(convCNT==3) & q- {. w. ^& V6 ~9 q3 k# q
- $ ] y. K" t9 B. t" ?, w- u
- {- x @" f; L- m# i4 |- v
- convCNT=0;/ j2 A9 _, d# K! d7 i% z4 Q7 K
% F$ ^( r: I! |( t- EndofCon_Flag=0xff;
5 U- u- Z5 a+ K - ) p/ U1 P& M) `! f
- sprintf(WDVol,"Internal PN Temperature: %5.3f \r\n",Temperature); & ~. \+ I# s/ ]! ?
- & e- m* d' E$ E! _0 ^& L$ d$ E4 `
- sprintf(InVol,"Internal Reference Volt: %5.3f \r\n",VRefint); 5 l# R0 i- T( l4 s
3 q7 b* ^2 \: o/ s4 {- sprintf(BatVol,"Current Battery Volt: %5.3f \r\n\r\n",VBATVolt);! v T( G1 J, h- P6 I2 c8 v8 L
- . Y' u2 e$ F) m$ g- C$ C( M9 ]
- }
) j1 K! x' k k/ J" \. R - : e3 i6 g# K/ D7 V6 e
- }
1 {3 o% Y! B- a+ o7 Z& _0 Q1 s
L. ^/ u% F1 y' C7 v0 C; x- //UART DMA 传输完成中断回调函数' O k+ Y: l Y) o1 L
0 h% H9 M& t8 _5 v$ z- void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
7 _/ @# Y+ @( G/ w% o( ^' { - {" Y1 @# {$ x/ j& [/ ~; r2 o. D$ E
3 ~% b! G6 T; c- Completed=0xff;
4 D+ C/ n' K3 b- I' {7 H( N - }
复制代码 2 y1 [4 X$ X" y
基于上面的配置和测试代码,我们就可以看到最终的结果了。定时器周期性地触发ADC,每得到3个ADC结果就进行数据处理,然后通过UART以DMA方式传输到串口终端。注意VBat电压是测量结果再乘以3得到的。
9 v% g/ e( v6 k
6 d! H& N0 s3 f- q$ f- S' h6 Z
. e( A3 J5 x2 F( Q, x$ T
/ @2 U+ ~" r0 @1 H" D0 W0 W8 N; q6 @( _: p+ p1 ]! a
针对上面的应用演示,最后给几点相关应用提醒:
( U( K& H( n# z- s" a/ a& p: n. Y" c0 X3 P
1、针对温度传感器做测量时,校准时使用的参考电压与实际应用不一致时要做换算,换算成相同参考电压的数据后再做计算。这点前面也提过了。' {, I( K- T! [5 i4 Z) d: ]0 M6 k( i( y. E# w
9 }. |. [% S$ Z6 K
2、使用TIMER的TRGO触发ADC,如果选择类似比较事件、更新事件来触发ADC时,此时ADC对触发极性的选择是无效的,或者说ADC的转换仅依赖于触发事件时间点。如果是选择TIMER的Ocref信号作为触发源,此时ADC的硬件触发的极性选择是有效的,可以是上沿或下沿触发,甚至是双沿触发。这时就得根据需要选择合适的触发沿。【可以进一步阅读本公众号文章《STM32定时器触发ADC的时序话题》】
/ w. c+ _ [! U
+ V( m9 q2 x0 d- P! }: \( B3、这里使用UART的DMA传输依次显示三个结果于串口终端,三个启动UART DMA传输的函数须保留适当时间间隔,即等上次传输完成后再启动下一次传输,因为这里每次传输使用的是同一DMA通道。否则没法全部正常输出。比如若把上面3次UART DMA传输的代码改成下面这样子:8 v" r4 e0 x5 j+ s6 i- j H6 u
% H8 z) L! A% ]* N! x* F$ n- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));' |7 g' O3 ]5 O; u! x
- 0 Y5 C0 l# w9 s! H2 e+ S
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));% V/ r& r) W' j/ _/ b
5 O8 Z+ p$ y0 v5 K2 j% T- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol))
复制代码
) ^9 S1 v, T( g* }. r! X0 L) `这时输出结果会变成下面的情形,总是只能看到一个结果的输出,即第一次启动的DMA传输结果。
8 m" y% t9 m. {0 W- R5 V7 W( @5 Z, \4 h# e; S/ [5 F
0 X z; D# ]/ ` ?$ `! N
; R& _" `2 x1 k. i* O7 r8 G如果想省事点,直接在相邻2次DMA传输间加上合适延时也行。我这里根据DMA传输完成事件来决定执行下一次发送。在DMA传输完成中断里设置Completed变量为非0值表示当前一轮DMA传输完成。& x9 C& N7 s& s+ O* _
K" k9 o+ k( n2 I2 [& W
4、对于那些在中断和主程序里都会被访问的变量,记得将它们冠以volatile。7 J/ V2 X3 l3 r' V: Y4 m
6 e$ H( g: J1 @; a" R4 n9 K下图的三路波形是我调试时辅助使用的。
& k& k$ }6 F) D6 a8 L6 ?4 o2 A" V$ Y$ T1 v
第一路表示计数器的计数变化,显然是单向向上计数模式。" N8 x% k+ s' g" k% I5 |9 l
/ z4 X5 M& Z. r
第二路是TIMER1通道1的PWM输出波形。
' b. D# k- U c3 z
5 D' v8 i1 ]5 i: L! q8 o第三路是我每次基于DMA实现UART发送时拉高拉低的波形。平常管脚电平为高,在实现DMA传输过程中拉低。
! _( J/ A* Y1 }# C8 K) ^3 Z! I
5 f; B- f4 ]: m% B5 D/ g
# x& w5 N3 j! r; ~9 l: n& }0 L/ W
如有侵权请联系删除
/ v8 I$ D7 e! c6 i/ I& h转载自: 茶话MCU. n4 ?; R4 d+ i) B$ k
* T4 `: g8 g7 z7 w. T \
& P O: M1 n9 V: \4 F/ e6 N) T
. i9 r/ [- z. { R5 V \ |