6 N0 E6 g7 U+ `* g/ J1 v% n) U
6 [0 L" v* ^; A6 }
$ M' R j( u7 u5 s* U很多STM32芯片里往往内置了专用的ADC通道,比方用来测量Vrefint,VBAT的分压或温度传感器的输出电压信号。不同系列所内置的模拟信号通道可能有差异。这里以STM32G4系列为例,它内置了对应于Vrefint,VBAT的三分之一分压和温度传感器的输出电压的专用模拟通道。3 ~* B8 B& L: M& H! q( ]
9 X' ^, I" H. l( A4 w j. V" L
2 ^: j- |0 T: |9 k2 `: G# v
- i9 n; P" g% z6 z* O) R. t
& ^5 }5 f, E d a$ c$ s! e+ b0 }" @5 L" c) T. @7 u
下面的示例就是针对上述3个通道进行ADC,并测量相关电压和片内温度,最终得到3个结果,分别是VRefint电压,VBAT的电压,片内温度。/ @, Z- V& f. q1 n
2 ~3 s3 C$ b$ N! r# B- a8 ^. s h* {8 y
2 Y4 m- ]6 W$ z8 q实现过程是这样的,大体分四步:【有点点麻雀虽小五脏俱全的味道图片】7 u- R2 o1 \% y6 K/ o# Z/ O6 `
- Q5 y( I2 u& B6 X$ }
1、TIMER1 更新事件触发ADC的转换;9 u: C# q: f$ h- p6 o& r5 k
# p) D3 m- _1 T& M2、CPU基于EOC中断获取ADC结果;: C6 @. l6 ~9 y0 \
& c9 y! s! n! Q) ~3、对ADC结果进行换算,得到电压值和温度值存放在特定内存位置;
% b$ `# L6 k6 R) X
& k/ z2 u# B% Q! H5 y4、基于DMA传输通过UART将最终结果在串口终端显示;7 ?- |8 c6 Q- f; H; G, e
* G1 z L* R# M0 f" m" T% X其中,TIMER1的CH1输出PWM波形,其更新事件做ADC的转换启动信号。每次的TIMER更新事件触发ADC,3个通道扫描方式转换。这里的UART使用片内LPUART,使用它主要是考虑它跟板载虚拟串口直接相连,没有其它特别用意。" o% \, H4 M8 ?
: U" V, Z8 {/ p$ ^( _( Z- j6 ^8 `7 ` j- |8 w* w( s i
$ X1 x! D) w! {6 r3 D" \6 n o
我使用STM32G474Nucleo板来进行下面实验。其中VDD=3.3v,VBAT与VDD相连。另外,ADC模块的参考电压也是3.3v.
2 |; {) S. `5 z- b% O2 P
8 o/ O u }/ G, ]使用CubeMx图形化工具进行配置,先看TIMER配置:' r4 M0 q c: Z) ~& M3 R- m/ w
/ d* u6 I2 u& I- m# e/ | \! X1 l8 ]
) }2 ^! v0 K# C. O( R0 u2 U再看看ADC的基本配置:( o' g+ b' n S2 l$ r& m
4 u5 B- w3 f3 z; p8 g; T, W' ?8 G
. M- x' E6 x" E% d. A( E I( y# E, O$ [, g ` B
LPUART的基本配置:
" m3 Z( z3 y# c4 R, M: O4 {3 H: ^) [8 K, Q: x
8 A! j$ |" d) S, L% N2 A3 K( W" v, ~& x
# _4 p; O g9 w" D+ Q# m因为要使用ADC中断和UART的DMA传输,记得做ADC的中断响应使能配置和LPUART的DMA配置,这里只使用UART的TX DMA功能。5 }- m: h. g' B
, T, ^0 }( {! I! R3 c9 S
1 a4 z( v6 Q. g- p, I9 L/ }; U+ N) n3 M8 y4 r% Y' G; Y
使用CubeMx主要配置主要是上面这些。& R' x# i; w3 E, B& T
# i8 m/ R4 c8 X9 X% i在组织用户代码前,先简单介绍下片内温度传感器的内容。该温度传感器针对不同温度有不同电压输出,其输出电压跟温度呈线性关系。ST公司针对片内温度传感器在两个特定温度【30℃和110℃或30℃和130℃】、基于特定参考电压【3v或3.3v,不同系列以数据手册为准】生成了1组校准值并存放于片内特定FLASH位置。
0 e; H* G& ]5 H$ h0 }8 z0 j: q4 J; v9 L7 \
STM32G4系列的校准值是在参考电压为3v,30℃和110℃条件下的两个值,在数据手册里还给出了校准值的片内存放地址。
7 g; ?! B6 B4 {% Q* a
9 d7 ?! ?8 d0 i/ }/ O% T1 H* n R0 u! b Z5 K: h J n$ N
- |# y% I" h$ @5 C$ m
针对这个温度传感器的使用,ST公司在参考手册里还给出了计算公式。其实,有无这个公式无所谓,我们不难自行推理出来。【TS_DATA代表某时刻测得的传感器输出电压对应的转换值,TS_CAL1/TS_CAL2分别表示在30℃和110℃条件下基于传感器输出电压的转换值。】7 O1 j9 Y4 ~5 q% ~
- u& _2 H, P" W' e
7 C% f& _) ]9 v
0 m9 K5 f& `4 R: w3 Q' H, a o) a) U
另外,前面提过,ST公司在手册里给出了温度传感器的两个温度下的校准值,但要注意生成校准值的ADC模块所用参考电压跟我们实际应用时AD模块所用的参考基准电压可能不一致。如果不一致,就必须将ADC值换算成同一基准参考电压条件下的数据。目前在ST手册里也特别强调这点了。我把上面一副图再贴一遍于此【见黄色语句提醒】。
/ l1 \' S. O7 j" T8 a2 F
2 H* T1 w* x h9 [0 n; i, t! H6 N2 U, L$ K% Z
2 ?, d5 b$ [. ]
关于这点,我们也不难理解。同一待测信号、同一ADC模块在不同基准参考电压下转换值往往是不一样的。见下面示意图加以理解。
' x( c0 H( W8 B' N7 [+ I8 D# V" w- E4 \4 |
1 d! C! ~* C4 W' d+ Y/ |) F( n8 }2 P( P* N( L1 N1 _
完成各项配置后,创建软件工程。添加必需的用户代码:2 w! ]/ L1 X9 y: M; {/ Z5 J
0 W) G- ^* j/ O' X3 a
- #define TX_Timeout (9999)" u: C' ?3 x6 R2 t
0 U" |: b6 f) B3 N6 G- #define TS_CAL1_ADDR (0x1FFF75A8) //用于计算温度传感器数据
1 K9 I9 R: H) m3 n* v- R* l - #define TS_CAL2_ADDR (0x1FFF75CA) //用于计算温度传感器数据0 S0 {/ c5 Y9 N/ l% Y m
- 7 R0 t: }2 B" G- a
- #define size1 (40)
5 v$ U- u7 I4 E
) u1 F& v v4 a7 i3 ~- char WDVol[size1],BatVol[size1],InVol[size1];* A* p) a/ x' Q
R( `. n9 K' a, y* q" k( Q- uint16_t ts_c30,ts_c110;
/ F7 O5 C' W9 a+ x, E" M: ?
K8 A' ]+ g6 M- a) M6 w4 H- uint16_t ADCResult[3],convCNT;/ X( G0 e0 R0 |' n
- , N1 G8 r4 J! G! _( r$ w4 h: v" F! Z; }
- volatile uint32_t Completed,EndofCon_Flag;' ?9 l& D+ [, @. u4 f/ c' p
- & \5 B3 w3 d6 [% Q1 c1 ~
- float VBATVolt;//存放BBAT电压最终结果9 V( D- N4 ?2 n' v1 ]- i# N
- float VRefint; //存放Vrefint电压最终结果" d' B' E# @! g9 q. l) M
- float Temperature;//存放片内温度℃最终结果
3 v C3 }" h) S! e - 4 D2 c6 o* h/ h6 a9 y. n x
- int main(void)- N3 H# V0 L2 b9 M+ [3 h' C8 X2 x
- {# M6 m; E4 s5 @& p; z
- /* USER CODE BEGIN 1 */$ r4 r! S8 q" |% C6 D
; O7 X0 Z/ ^1 h$ J4 X8 `- /* USER CODE END 1 */
6 x7 H8 a0 b& L! |. `( k$ L - 2 F$ p# J6 }# W2 m9 z
- /* MCU Configuration--------------------------------------------------------*/
" @4 R: C3 b/ s, X8 V" @ - 8 [1 H6 f8 F( _" C3 p/ d' ?' L
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
0 F2 }$ j: G' e6 c" C6 K) R- K! L - HAL_Init();9 O+ Q2 o. E* m: G3 { i
- ' i4 h1 S9 Z4 B' o. R4 |
- /* USER CODE BEGIN Init */
$ b, L. p+ R3 R) v2 D3 {. Q/ N/ A
0 \7 ^. f" ?5 j5 |- /* USER CODE END Init */
$ g& M1 a7 {" T# S) a9 N/ w0 i9 J - 2 g; g! ^% v2 t6 e: G" e
- /* Configure the system clock */
) c# d& R9 D: k8 ^+ I - SystemClock_Config();
3 |4 r1 U3 ?9 \5 d! @ - : ?8 U* H9 W9 F2 ~+ P) R
- /* USER CODE BEGIN SysInit */" y+ E: y, R ?1 D# ?
- # ?: q! C1 |5 I
- /* USER CODE END SysInit */( r" x, V# i8 Z/ V4 l7 z
- ' l/ s: {/ Q; ^0 K/ R) b9 F
- /* Initialize all configured peripherals */
, \. \" i) h o- p - MX_GPIO_Init();4 ^9 H+ u# h$ [* y, X+ {# h
- MX_DMA_Init();5 v3 M4 R; g7 `/ p* |
- MX_ADC1_Init();
! n; I# E( g& H% j, ^ - MX_LPUART1_UART_Init();
+ q/ E8 L, Z$ M! r4 h - MX_TIM1_Init();1 O$ k; i4 m( n$ w7 b2 u" b
- /* USER CODE BEGIN 2 */
2 J* h6 E1 k1 B3 l. t+ `. x - - j s" ]# N7 ^) g; r
- ts_c30 = *(uint16_t *)(TS_CAL1_ADDR); //读取30℃时的ADC校准值7 `3 E, H# ]7 N
- , N' z1 N; ]) N+ d4 m, E: j
- ts_c110 = *(uint16_t *)(TS_CAL2_ADDR);//读取110℃时的ADC校准值
$ h& U* S+ l( b: e/ z0 T! W% I( { - / m4 N3 b K/ H) W
- HAL_ADCEx_Calibration_Start(&hadc1 , ADC_SINGLE_ENDED);//ADC校准: i/ n" m; H# s( }. d9 Y/ U+ K
- + i9 x$ T1 h$ P+ I+ Y
- HAL_ADC_Start_IT(&hadc1);//启动ADC并开启转换中断
& l. o! ^( B4 X& @ - 9 Q o. L# h# x/ C+ E" w& i: Q& H u
- HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);; c4 p3 O4 H6 }# M# y# n8 T4 V
- $ }2 A# F1 e. z7 B
0 {' [8 e8 i1 ~4 I( C& I- /* USER CODE END 2 */
; R/ Z. b0 {1 {& K
3 K* V2 K. b# b" g- /* Infinite loop */" r2 A5 a+ ]" O9 O- L
- /* USER CODE BEGIN WHILE */
+ u4 R$ W( R# P9 b+ Q - while (1)
* z; ~# w. ^4 C# s1 x0 e2 [, e% _ - {
8 N3 d) i3 f2 u( [7 Q - /* USER CODE END WHILE */% _: _2 N" k5 a5 M8 @6 B
+ X6 I ~9 g% ^1 B% z7 Z- /* USER CODE BEGIN 3 */# O5 e F* t/ ]: d- I7 a
- - X1 W- l. B* O3 O! W
- if (EndofCon_Flag!=0)
# ?* F8 D( ]7 h* c+ S, P' _ - {
. |6 f, e- p' `: _' Z - VBATVolt=(ADCResult[0]/4095.)* 3.3 * 3.;
& S5 w/ {! m3 `4 L ~) E5 F: J
. }0 a8 T( I, f6 q# I# F- VRefint=(ADCResult[1]/4095.) * 3.3;
5 ~1 y, o, n" l5 R( A0 s - - G6 {; l, B7 H. M. M3 }& c
- Temperature = 30.+ (88.*(ADCResult[2]-((ts_c30/1.1))))/(ts_c110 - ts_c30);
2 O8 x/ e& S6 ~& D; O - , W6 j. L5 U0 p7 i/ t3 y* p" x3 N. I7 k
- EndofCon_Flag=0;! a5 b) |' a1 V5 I8 R2 X: m
" O' t# ^0 s) l7 F) y# ?: b- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol), TX_Timeout);
& k& K8 W O6 E$ p
8 d- }% q+ @ a$ G; s- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_RESET); //for auxiliary test% O( E- C/ m% n' f# o
- \9 g* p" o+ C8 P/ ?3 K
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));) a& D6 e3 n' e2 v9 d
- d' ]5 I4 M) P1 e- while(Completed==0){}4 b# k: l: x* l! G- b
- Completed =0; n( j8 E5 {- N; `+ J3 F; G1 G
5 |$ I9 C& P6 k1 z, }) f) J- & F& n1 r0 m' j' i
- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol), TX_Timeout);
7 U% m" k, v7 ~# u6 k - HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));5 l' _% Q3 j7 f: h, U$ ]
- 5 _. E8 D8 g) \! x+ _
- while(Completed==0) {}
' g0 e4 `: `( Z* {. ~' ]- I+ I - Completed =0;
8 ]. |1 y8 l: K4 N
1 m& l. c3 v! Z$ J5 r- t) L- ) @% ^7 A/ Z* x
- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol), TX_Timeout);" p& _$ C2 U/ S) t
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol));" I' z% ]% r3 Z# U9 r
- 7 }1 ]5 K, Q! u+ H+ Q' t" ?
- while(Completed==0){}) y5 U; A7 k0 }: q- y5 d
- Completed =0;6 f0 q$ G0 o! P6 C
2 n( M& }3 O7 a" ?. W2 s) S- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_SET); //for auxiliary test) B! V9 T1 O/ E+ N
( l7 z; C4 X# H- j- } 9 J" Y* S2 V- O' `' o
- 1 m4 p3 r0 K- C! a* m
- }
& U. O& i) k+ u" @2 q, G - /* USER CODE END 3 */
' N+ N! \- w! e2 m9 W; x - }
4 ~: @5 U$ x$ m: c% t9 t/ h
; ?8 E: N* w& s2 |9 ?+ m$ I H- //ADC EOC 中断回调处理函数
( f9 X; N1 j, @& h) L - void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
+ S! E( o. z0 C) a5 B% l& p - {1 u$ _3 P& H! G% h
- / c; j4 q0 N3 F b% `& c% L
- ADCResult[convCNT]=HAL_ADC_GetValue(&hadc1); //获取转换结果并存入数组& p/ F3 O# ]7 P9 S" |0 s$ |4 d5 _: A
" F# x( \# p7 ?' G; b- convCNT++;
8 @- b) t' i3 E4 s) B- J - , M& z( _; k6 N* v; F
- if(convCNT==3)
. q8 ]' Z* c0 I* b
7 {4 x2 w) A! h& @- {
+ B( _! d; ]. y - convCNT=0;! @2 c( t0 Y6 ? B( ~6 U6 q$ [
- / [2 J1 A) Z9 n6 ]% s4 J
- EndofCon_Flag=0xff;
6 ^# ?* D/ K( l' V" i2 s5 g/ ]* h
0 ~: y0 f! h( U- sprintf(WDVol,"Internal PN Temperature: %5.3f \r\n",Temperature);
+ `1 h# F# m( K& H - 5 [* B0 X' l' Y# l! L$ g. a
- sprintf(InVol,"Internal Reference Volt: %5.3f \r\n",VRefint);
. ?, N: U4 E8 o$ D ^0 [5 o - ' w# `% ^% F# C) Q7 j2 [ e. ]
- sprintf(BatVol,"Current Battery Volt: %5.3f \r\n\r\n",VBATVolt);
8 ^/ v5 ^. }1 m) |! i - ( O0 d, ~4 x) z. q T! c3 |' O8 R
- }
' T. M" p; f% g1 D+ F" t
- g1 n/ w( c9 U Q4 O: f- }6 j* `. k. f& V+ s0 ^% G$ |9 c3 l
* s$ p6 A. R, l4 ^/ V) o' e3 p- //UART DMA 传输完成中断回调函数. i; F; r7 U* T2 O
- 1 p: o0 N* s( w3 R8 w( f
- void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)+ e2 W( @1 E! h
- {
1 J/ n3 V% s5 r* u7 i
4 L+ o# s/ \2 L S+ s: d% G- Completed=0xff;
8 v% k# M. O% G9 k8 ^4 K - }
复制代码
[/ P p3 l7 F1 I6 M基于上面的配置和测试代码,我们就可以看到最终的结果了。定时器周期性地触发ADC,每得到3个ADC结果就进行数据处理,然后通过UART以DMA方式传输到串口终端。注意VBat电压是测量结果再乘以3得到的。+ t! E1 h; m0 Y+ S' \; R
% S- W$ H u# C/ j% R7 w/ n( u' l& l6 L8 R
, ?& Y& K& h! k# E9 a
{* J' \+ O. o* J9 c& D' z
! d5 d/ q% H" O0 n
针对上面的应用演示,最后给几点相关应用提醒:
5 o% G5 z# m# k. t( [/ R2 C# s3 p1 G } r
1、针对温度传感器做测量时,校准时使用的参考电压与实际应用不一致时要做换算,换算成相同参考电压的数据后再做计算。这点前面也提过了。( L$ ?+ G7 |7 H, G! u1 `
0 Z/ L1 L b: j% ^6 { k% p2、使用TIMER的TRGO触发ADC,如果选择类似比较事件、更新事件来触发ADC时,此时ADC对触发极性的选择是无效的,或者说ADC的转换仅依赖于触发事件时间点。如果是选择TIMER的Ocref信号作为触发源,此时ADC的硬件触发的极性选择是有效的,可以是上沿或下沿触发,甚至是双沿触发。这时就得根据需要选择合适的触发沿。【可以进一步阅读本公众号文章《STM32定时器触发ADC的时序话题》】
! f- z) N( c+ m6 h
1 Q, K9 I: V! n) G5 c$ u3、这里使用UART的DMA传输依次显示三个结果于串口终端,三个启动UART DMA传输的函数须保留适当时间间隔,即等上次传输完成后再启动下一次传输,因为这里每次传输使用的是同一DMA通道。否则没法全部正常输出。比如若把上面3次UART DMA传输的代码改成下面这样子:1 l8 k% [/ R7 P9 |+ i
9 o$ [( W1 ~$ o) a+ V4 l3 ~
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));2 F9 d: ~( p- |8 H5 k3 H
0 G! T) y4 ]0 o3 k3 D4 Y( _- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));: k; f* Z% |! l6 d
- + D L+ r$ d$ M
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol))
复制代码 1 h- S3 q a+ m \+ U
这时输出结果会变成下面的情形,总是只能看到一个结果的输出,即第一次启动的DMA传输结果。/ [ j, a- I/ _
8 c, p( L$ v) O
7 N7 |6 W' N5 D0 \8 b1 |* f( y
如果想省事点,直接在相邻2次DMA传输间加上合适延时也行。我这里根据DMA传输完成事件来决定执行下一次发送。在DMA传输完成中断里设置Completed变量为非0值表示当前一轮DMA传输完成。8 b, U+ l: W) V4 [
! r3 B( l) p3 i$ O/ [4、对于那些在中断和主程序里都会被访问的变量,记得将它们冠以volatile。
4 k+ D! F. K. K7 U: G- q9 v: ? \; {
1 V) g, C( K4 l/ f+ d下图的三路波形是我调试时辅助使用的。
) H- n% l' L* I" E+ v- u# J7 A* ]& m* R. p( _ \1 }
第一路表示计数器的计数变化,显然是单向向上计数模式。: G5 p9 m% ?0 C! O/ X0 d, s F, B
# M4 m# J; I# j第二路是TIMER1通道1的PWM输出波形。2 `& i5 J# l8 `+ u; C) y
; z5 ]+ `% A+ L! w9 Q第三路是我每次基于DMA实现UART发送时拉高拉低的波形。平常管脚电平为高,在实现DMA传输过程中拉低。; E9 A4 G0 @7 b* Y
' }( o' M+ W B/ R3 S* _
+ ]6 q9 z' _! c9 f: ^9 \4 P$ i* E! ?0 y$ i Y
如有侵权请联系删除- J, K# k) M- Q) a
转载自: 茶话MCU
' G: D: |/ _' _/ d) ] U6 I& x
4 ?# g% z2 K/ W7 { ) ]( Y. U. b; n( }6 a# x9 g
6 W7 p, A6 _; s# S4 R |