$ E2 |9 z8 q. o6 _
4 v/ W" R, e x% P$ j; Y
2 Q$ `( a1 U' T) b很多STM32芯片里往往内置了专用的ADC通道,比方用来测量Vrefint,VBAT的分压或温度传感器的输出电压信号。不同系列所内置的模拟信号通道可能有差异。这里以STM32G4系列为例,它内置了对应于Vrefint,VBAT的三分之一分压和温度传感器的输出电压的专用模拟通道。
9 c, Y9 b8 w2 ?* M! D! x
/ a7 i) b5 R9 p- b/ ?" c# _( S; [) g B% z& c
* p7 p9 M4 K# j: i1 `1 o; X# C: x% V
# s! c% G$ u7 z! A1 e
下面的示例就是针对上述3个通道进行ADC,并测量相关电压和片内温度,最终得到3个结果,分别是VRefint电压,VBAT的电压,片内温度。
* K e' v! L( R& U2 t
* W) ?! G) f8 O, @# l6 ?% z; b, f' L, A6 \( b% A! s: ]
9 g3 k1 G7 S: F! n' u( [实现过程是这样的,大体分四步:【有点点麻雀虽小五脏俱全的味道图片】
B2 H+ f+ i6 i& x9 h4 B* H" Q+ _: g5 \7 I
1、TIMER1 更新事件触发ADC的转换;
) i5 n5 x, O r. x* E- u0 h
. M& ]& s# U) c+ g( W2、CPU基于EOC中断获取ADC结果;/ m1 d1 |8 i ~" n' z0 I7 O2 `- [
9 Q% o) z X5 n$ Z) V% @& ^: V( A
3、对ADC结果进行换算,得到电压值和温度值存放在特定内存位置;# O B N4 o7 K p! L0 Q' W
& C* g7 [6 \5 s: s
4、基于DMA传输通过UART将最终结果在串口终端显示;3 R$ a8 A, g# Z7 D
1 j) E; b, n M+ `4 j" n其中,TIMER1的CH1输出PWM波形,其更新事件做ADC的转换启动信号。每次的TIMER更新事件触发ADC,3个通道扫描方式转换。这里的UART使用片内LPUART,使用它主要是考虑它跟板载虚拟串口直接相连,没有其它特别用意。
! d! A% O7 @7 D; G8 {8 b3 T, v* d' q
1 L3 u! B0 v% n3 N: k! Y" X; q( D8 N. J
, K, y/ k, ^" u% @2 n4 _& x我使用STM32G474Nucleo板来进行下面实验。其中VDD=3.3v,VBAT与VDD相连。另外,ADC模块的参考电压也是3.3v.' H* ]0 e4 [: n6 H3 O* l" [ s
/ o7 K* m; T* [8 b" Q" m5 b; o! r
使用CubeMx图形化工具进行配置,先看TIMER配置:, B( S5 W6 Q1 u( [: M7 p
0 w, X ~8 ]) j4 S) ?
: F5 E! z, X- ]& Z" K7 G3 l+ L4 E* D
再看看ADC的基本配置:
6 K$ [7 Q% p+ B/ D% O4 C0 |6 z6 i& w, e1 @2 s. k- P6 l
& e' j3 t S/ R
9 G" s6 X1 g4 f5 b5 CLPUART的基本配置:
4 P6 A/ @/ P f$ b8 u
8 j+ m2 R: p: W+ ~8 ^
. ^1 O: M2 ^/ F9 e; P
- R: {5 J* S8 ?4 Y2 Y因为要使用ADC中断和UART的DMA传输,记得做ADC的中断响应使能配置和LPUART的DMA配置,这里只使用UART的TX DMA功能。
, J1 Y7 Y( p: H) m
/ ~ V S: j" w& }9 A; q* L: C5 ~, h9 k2 u" F' y8 E! l) }% O. P
! X, c6 C: ]8 T) G使用CubeMx主要配置主要是上面这些。
! x& u" s, I" j; F
$ k8 \+ u/ H$ S! K9 b; {2 I) ?在组织用户代码前,先简单介绍下片内温度传感器的内容。该温度传感器针对不同温度有不同电压输出,其输出电压跟温度呈线性关系。ST公司针对片内温度传感器在两个特定温度【30℃和110℃或30℃和130℃】、基于特定参考电压【3v或3.3v,不同系列以数据手册为准】生成了1组校准值并存放于片内特定FLASH位置。
' [% O+ [) J. f$ o7 i4 D
, i2 H2 Z4 ?, OSTM32G4系列的校准值是在参考电压为3v,30℃和110℃条件下的两个值,在数据手册里还给出了校准值的片内存放地址。- |3 z- p+ x6 D, W
9 U- @# {" l/ `- P- [
8 P3 I, t3 S$ L7 E& s: i' ]
" M h* T4 w8 u
针对这个温度传感器的使用,ST公司在参考手册里还给出了计算公式。其实,有无这个公式无所谓,我们不难自行推理出来。【TS_DATA代表某时刻测得的传感器输出电压对应的转换值,TS_CAL1/TS_CAL2分别表示在30℃和110℃条件下基于传感器输出电压的转换值。】
8 T) g4 u5 r' u5 `& l' w7 V
4 {( f" p4 l2 g) c
2 D$ @+ s7 i/ ?3 Q' K
5 Q" G2 h( w2 V- ^1 u, I) w另外,前面提过,ST公司在手册里给出了温度传感器的两个温度下的校准值,但要注意生成校准值的ADC模块所用参考电压跟我们实际应用时AD模块所用的参考基准电压可能不一致。如果不一致,就必须将ADC值换算成同一基准参考电压条件下的数据。目前在ST手册里也特别强调这点了。我把上面一副图再贴一遍于此【见黄色语句提醒】。
% U# x- U9 j, h4 H4 \4 C4 X6 c% P# T
, R: M4 I, M% i
+ V! J3 M% S4 k5 ~( p) H
关于这点,我们也不难理解。同一待测信号、同一ADC模块在不同基准参考电压下转换值往往是不一样的。见下面示意图加以理解。
! a Q9 U% h/ J5 }- X) f6 l, H
) X d/ B/ R% w4 ^, T% e4 S
& V" W$ u/ U# w8 U8 w- I
% M! a$ ]+ Z( P/ h完成各项配置后,创建软件工程。添加必需的用户代码:
4 V" P$ s3 J" h7 W/ A5 c1 G/ Y7 c4 H1 \2 S' g
- #define TX_Timeout (9999) Y5 ~5 L) \# O4 F3 A# d# ]+ a, r0 U
- & s' I1 o9 f! o1 o9 l/ Z
- #define TS_CAL1_ADDR (0x1FFF75A8) //用于计算温度传感器数据
: h# W( a; G. ~+ z `7 [: } - #define TS_CAL2_ADDR (0x1FFF75CA) //用于计算温度传感器数据) P8 I) c0 {8 T( h8 O6 u G( S8 T: A! l
# W3 e" K G2 w1 M- C% ]# F0 \% V1 O% o- #define size1 (40)
- S5 _9 N2 }6 M - 6 R0 O* a5 F$ f7 n
- char WDVol[size1],BatVol[size1],InVol[size1];
+ }; v. _1 L( }+ Y7 s* S/ f - / [& T7 h0 Y# n& C
- uint16_t ts_c30,ts_c110;
7 N( H/ X2 y% \$ h7 [2 w% X! f4 U - 8 G2 y1 o/ j6 ]/ D0 _8 u
- uint16_t ADCResult[3],convCNT;3 h7 i0 M1 k$ a4 } @
- 8 ?+ _9 U4 c5 z. v4 ?
- volatile uint32_t Completed,EndofCon_Flag;
9 _! e g3 w7 F, S0 @0 o p' B9 V
b. R. F1 {4 @& M" G8 m- K0 U) V- float VBATVolt;//存放BBAT电压最终结果
x+ d; ]+ g* \. f/ R H - float VRefint; //存放Vrefint电压最终结果* [2 c0 w1 _* A5 {. c. X
- float Temperature;//存放片内温度℃最终结果
K- U& L/ W6 b# V0 V- p/ I" R7 Y2 q% I - * ?. B1 A( @4 I
- int main(void)+ Y& h, a) Y: u: A8 v$ C
- {" e$ n% P$ x9 x: l- [# s6 a' J
- /* USER CODE BEGIN 1 */
[/ {: T5 k' ^' H% K2 m* K: F7 f
$ z# w- Y Y3 h: g- /* USER CODE END 1 */- @/ }6 H/ U/ y# [% q
& @! B" p* K% A- x! ~* e# r9 v5 z+ r- /* MCU Configuration--------------------------------------------------------*/
- I q+ x3 ^2 ^0 \- j0 I+ {
* }7 |0 r$ L/ n N8 v+ f- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
( ^8 |! O, `- s. b7 C7 P - HAL_Init();
- T9 x0 i B6 O9 H3 U - 4 ?2 U P2 B! V2 U6 I' z, I
- /* USER CODE BEGIN Init */
7 J8 t, z& v. e
2 K% o. W7 X5 w/ ]- /* USER CODE END Init */
, S& N% E5 ?7 A8 r: U0 T4 z
c- k' b8 P/ Z" T0 M& j- /* Configure the system clock */
: o8 `* U1 b' t - SystemClock_Config();: r: P0 l, H) G
5 r7 q9 L* f/ m6 g- /* USER CODE BEGIN SysInit */
g. K2 `# Z% n. ]5 ?* H
8 N3 ?0 B9 X7 p* r- e+ C. [- U- /* USER CODE END SysInit */
: Q0 t% z1 \0 L9 v# U - 5 E' H- a6 J P% k
- /* Initialize all configured peripherals */. l. r2 L x6 L
- MX_GPIO_Init();
+ V/ R0 |9 e( x; E6 t, v - MX_DMA_Init();
( f0 O A; T) v$ m: \% t5 Y - MX_ADC1_Init();" R! j5 _4 v: U( k6 x$ g0 y
- MX_LPUART1_UART_Init();: n6 H( m+ T: j' R8 n/ |) _! r
- MX_TIM1_Init();' S/ B% Y( p* t- G, D# P
- /* USER CODE BEGIN 2 */* ~2 M, P7 |0 Z( ~$ M5 y# D9 U0 `! v1 a
- ! O( t- J: O5 B/ F4 d( i8 `
- ts_c30 = *(uint16_t *)(TS_CAL1_ADDR); //读取30℃时的ADC校准值9 U5 z( u1 L2 C. {2 h' [+ p
) o2 {; V2 i: u6 k. r. \' d- ts_c110 = *(uint16_t *)(TS_CAL2_ADDR);//读取110℃时的ADC校准值
+ w8 D* v7 i# n7 [+ J. y0 q - ' b6 b6 F0 L# g
- HAL_ADCEx_Calibration_Start(&hadc1 , ADC_SINGLE_ENDED);//ADC校准
0 v) I3 Z6 u) O+ }( Y
) Y; l$ S: \+ p8 p* s8 T- Z; a* }- HAL_ADC_Start_IT(&hadc1);//启动ADC并开启转换中断3 M1 N& z8 m: S* Z% z1 m- d- x9 x
- , t- i4 b0 ^" T4 w; o g1 P' o
- HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
9 R$ q. e3 a/ y. N - + L2 G8 Q' o1 O4 B$ f% ? {* {
- ' s3 a; a9 {1 Y$ }( M2 V
- /* USER CODE END 2 */
- B- c4 \" v# s4 q% l
0 G6 n% l( F+ W- /* Infinite loop */2 G4 P- a2 U1 w
- /* USER CODE BEGIN WHILE */
4 T6 v" C% x: A" r" j2 p+ S( J - while (1)/ }, L X* b* j7 u
- {
6 z5 B+ U% x" f+ ^' e, [1 c - /* USER CODE END WHILE */& v# q+ g& p, M% V; X4 q, i5 M4 v
- " u" v" w; E' B
- /* USER CODE BEGIN 3 */) w% D# ]1 x; E3 S3 ?* N5 k/ K
- d7 H9 w1 V; q( M# ?8 Q, z& e
- if (EndofCon_Flag!=0). l) e+ C9 [# t0 j
- {6 P/ u) G# E; i* x
- VBATVolt=(ADCResult[0]/4095.)* 3.3 * 3.; 9 F6 X6 T6 ?5 N- F5 @" t5 v1 Y0 ]
- 4 V* ]7 [" l! o, O( j8 R; m1 ?
- VRefint=(ADCResult[1]/4095.) * 3.3; # x* P) U- x5 T1 U( Z
, i( T# A2 Q( p& Q3 g K- Temperature = 30.+ (88.*(ADCResult[2]-((ts_c30/1.1))))/(ts_c110 - ts_c30);% H4 a( c! c* S: |$ X; r( l
$ \! T# q4 e! ]+ e0 v: C4 E- EndofCon_Flag=0;
! m3 } |4 R2 w/ e# Y
: ~, ~" T5 E, n# [ N- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol), TX_Timeout);4 w; e) X# e/ H( P2 ]. I2 |( }
m L+ _) a. h8 E B" c* P' T* M/ ]- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_RESET); //for auxiliary test
- \0 s9 D) P; S5 \+ \. V4 z - : K$ n1 w, j8 Q/ F( u" r1 L! {
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));
2 Y7 A4 q/ a* Q L* o) {$ N" i7 g
& |4 P- [* a+ M- while(Completed==0){}
9 c9 p' d( m* n3 Y+ f L - Completed =0;
5 J: V- o, ?' |4 `2 v) G' B
j- s- J2 n5 D7 w- . [' D+ s! P, K' W' [& m0 f+ @
- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol), TX_Timeout);' }7 z' K+ y. z7 q
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));9 t q( g. C8 X5 h3 K
- 3 L3 Y6 s$ b2 f' X4 I% @
- while(Completed==0) {}! z/ P' }; a" |* x# i. Y
- Completed =0;# A# o) L5 e4 K, a3 P" n; N! c+ I
3 B* w( J# N o# l) M% V- _- 9 D K/ x& ?8 n
- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol), TX_Timeout);# M1 l& b) x! ` I9 B; R, `
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol));/ f: c4 K5 A) A F2 n8 I* f) Y6 H
( ]# `" J% M) q& Q; f- while(Completed==0){}7 J1 ^& \' s4 K; c* M, t, A* D% l6 A
- Completed =0;
) \/ q# u" z: [0 h# d2 X7 ] - H4 m [8 J$ R) d
- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_SET); //for auxiliary test
! E( P* i" [3 b
6 B; E; Y( }5 d' z' L- Q2 V& F" F n- } 0 d; f) m8 W# t9 f
- " W5 u! D: n9 O5 \
- }+ h; j3 V7 m9 h
- /* USER CODE END 3 */
* A* t" C) j3 r- `6 v& b4 P5 z - }
/ |; J, n6 q4 _* k$ z - 6 Y6 M) ^% F; C( n
- //ADC EOC 中断回调处理函数; k( ^: G: D- {6 ^4 ?: a
- void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)5 N8 [# n1 q% L) U6 y
- {
1 A/ j- X4 ]2 ?% x8 Z - 8 Z" E0 ]' U2 `! E! `! A
- ADCResult[convCNT]=HAL_ADC_GetValue(&hadc1); //获取转换结果并存入数组
; n$ W- T8 t1 l; t - - i2 r% e$ c) a5 z
- convCNT++;, C8 O, \5 N0 W) ?" z! D
( M, V5 @$ C/ r$ r- if(convCNT==3) & H4 a* b8 F- R" u; m
- ) j x) U" h$ x, X
- {
$ c3 r% K2 `& Q) @, M ^ - convCNT=0;
9 {5 ~+ B3 P, R9 N - - h/ ~4 b4 G5 {& E0 E8 @$ k
- EndofCon_Flag=0xff;
# {1 Z9 y. X: \, s/ K# S
4 z( f4 s& j( E8 v% z* v- sprintf(WDVol,"Internal PN Temperature: %5.3f \r\n",Temperature); & l5 J7 m/ k! s4 Y. P' n
- 8 ?0 X0 {8 ]3 j$ d
- sprintf(InVol,"Internal Reference Volt: %5.3f \r\n",VRefint);
3 R; `. l' x4 ^8 G - 2 p3 m+ j# t& G* p6 V o: l! p
- sprintf(BatVol,"Current Battery Volt: %5.3f \r\n\r\n",VBATVolt);
* X$ _4 ?; G z( y
9 |& B5 l, Q& _0 F( f7 I" b- J- }- ]7 l% o8 Q& K( K3 e
; K; r7 ~8 K, b- }2 l% F j0 {/ M- h
' r& @; M; Q$ i- //UART DMA 传输完成中断回调函数
$ h5 ^4 A' D. ]. T, _% K* ? - 0 R, i! B0 ]; Z. m
- void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)# j" ]2 l2 S1 s! {" g
- {
9 L( h2 P: t# t/ V3 g- k4 Q7 A - S1 K2 a9 {( n y
- Completed=0xff;
* O. I! }& N! _+ D, s, T - }
复制代码 8 G1 T; u3 I ^' a
基于上面的配置和测试代码,我们就可以看到最终的结果了。定时器周期性地触发ADC,每得到3个ADC结果就进行数据处理,然后通过UART以DMA方式传输到串口终端。注意VBat电压是测量结果再乘以3得到的。
# }4 e1 |& Y* [' l
6 Z; L! C+ X) ?! ?: h {
/ l4 y( x( S: b: @' {) t7 q7 F! F/ o% j/ e4 c, _. d
6 O9 V+ {. N5 R' G- A' t' n: L
0 c/ e' J: X( M( p% i/ V1 C" a
针对上面的应用演示,最后给几点相关应用提醒:. B! e, p5 `/ |: s* ^2 x) ^) h# I
7 C" p9 s. Q9 `- U6 N1、针对温度传感器做测量时,校准时使用的参考电压与实际应用不一致时要做换算,换算成相同参考电压的数据后再做计算。这点前面也提过了。
B/ f9 x8 L" ^9 ?' W2 W) F; M; T
% l1 p. I' O* `/ S2、使用TIMER的TRGO触发ADC,如果选择类似比较事件、更新事件来触发ADC时,此时ADC对触发极性的选择是无效的,或者说ADC的转换仅依赖于触发事件时间点。如果是选择TIMER的Ocref信号作为触发源,此时ADC的硬件触发的极性选择是有效的,可以是上沿或下沿触发,甚至是双沿触发。这时就得根据需要选择合适的触发沿。【可以进一步阅读本公众号文章《STM32定时器触发ADC的时序话题》】) F/ e1 E# d7 v/ w5 a
7 K1 s5 V0 K8 L" U# b) S* U$ e
3、这里使用UART的DMA传输依次显示三个结果于串口终端,三个启动UART DMA传输的函数须保留适当时间间隔,即等上次传输完成后再启动下一次传输,因为这里每次传输使用的是同一DMA通道。否则没法全部正常输出。比如若把上面3次UART DMA传输的代码改成下面这样子:+ Y2 y% b j* `9 c$ Q+ \: D
+ D6 \4 w% Z" t1 L$ ]" s* l; T
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));3 d$ t3 l( w8 m6 S. q
2 d% W1 C$ G0 m* _6 z3 L+ B+ }- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));
8 ^1 |9 I( C( l( D - , Z8 G4 h6 d& g, B) f3 `4 R
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol))
复制代码 3 _ V; ~- |8 a
这时输出结果会变成下面的情形,总是只能看到一个结果的输出,即第一次启动的DMA传输结果。" K3 G# n* a% p
/ F+ M2 F2 h( y! K$ z: S F: @0 s6 v) j9 K
" R3 m- g# _1 F8 a如果想省事点,直接在相邻2次DMA传输间加上合适延时也行。我这里根据DMA传输完成事件来决定执行下一次发送。在DMA传输完成中断里设置Completed变量为非0值表示当前一轮DMA传输完成。
* o' j' J6 ]1 T; G- ^( }& c' m9 X* ~7 Z; `$ W
4、对于那些在中断和主程序里都会被访问的变量,记得将它们冠以volatile。% y# b! F3 ?: x R* u
o9 N [, C; [8 q0 J下图的三路波形是我调试时辅助使用的。8 N( [! W( p7 |7 M4 r
+ M& Z" Y7 F0 Q M
第一路表示计数器的计数变化,显然是单向向上计数模式。: J3 P1 X1 j9 V! Q
; }; l+ r/ A8 Q0 K4 n/ ~* N
第二路是TIMER1通道1的PWM输出波形。& P, |3 o; t! S! T& T
$ A* A# @- Z, e0 y' m8 c H第三路是我每次基于DMA实现UART发送时拉高拉低的波形。平常管脚电平为高,在实现DMA传输过程中拉低。
x6 f6 K/ n( J: c0 G1 {) j/ {- d$ C- l' t
, N, k0 S' R* P2 `! P& [6 ~+ g; }& r4 C* V" z2 h7 Q/ W* h
如有侵权请联系删除: N1 R' y5 J% J4 Q
转载自: 茶话MCU
7 k4 l/ c `4 z. [! E5 T
( g! p8 E7 I; |* C% i & d o2 [; b+ @
7 e% O* p* p3 J/ }$ K+ | |