) [1 A; v! N9 @1 }$ j1 P3 \1 A" z
3 | n$ U7 F" x2 T
3 m0 G) E/ x B9 Q3 |- q' W T+ Q很多STM32芯片里往往内置了专用的ADC通道,比方用来测量Vrefint,VBAT的分压或温度传感器的输出电压信号。不同系列所内置的模拟信号通道可能有差异。这里以STM32G4系列为例,它内置了对应于Vrefint,VBAT的三分之一分压和温度传感器的输出电压的专用模拟通道。; e, y2 I! O) b+ z$ e) Z( i
: o0 x4 F: C9 g& X) ]/ B
5 G4 w# A' N2 Q4 s; d T
# `: `8 w6 g- f+ M
. |9 e% u4 u4 ^( \9 x. ~5 Q4 |+ Z4 T0 _' t0 ]& o+ v$ E B
下面的示例就是针对上述3个通道进行ADC,并测量相关电压和片内温度,最终得到3个结果,分别是VRefint电压,VBAT的电压,片内温度。
h) ^9 t; V: q8 i3 p! h3 d9 r6 T0 [+ r& r% y3 f' y
9 G1 z: j- c' }
7 B$ y% e& P& [& K; t# J% m B实现过程是这样的,大体分四步:【有点点麻雀虽小五脏俱全的味道图片】
0 [' ~) W* m7 ^0 [; J$ l1 O2 X' N* S& E. h: l0 _& n
1、TIMER1 更新事件触发ADC的转换;
1 \- K" L f, ]# l) I; a
( k7 c& X6 m$ M: X' u& F- l* \2、CPU基于EOC中断获取ADC结果;9 T& M0 c) h5 ^6 @
* w6 _- p4 z { Z, l6 @
3、对ADC结果进行换算,得到电压值和温度值存放在特定内存位置;; S1 Y* m: e# t3 J
8 w8 o5 Z- r/ I5 r9 t2 }4、基于DMA传输通过UART将最终结果在串口终端显示;$ }" u- C& `+ O. a5 R' J8 o% c
% m/ N" e+ u5 {# m- [$ r: K0 K0 u
其中,TIMER1的CH1输出PWM波形,其更新事件做ADC的转换启动信号。每次的TIMER更新事件触发ADC,3个通道扫描方式转换。这里的UART使用片内LPUART,使用它主要是考虑它跟板载虚拟串口直接相连,没有其它特别用意。5 N% K+ w R- e$ @/ Y9 G
7 N& |" m+ Z/ T5 e7 @
! K7 q# z- e) m; u
q- j3 k2 U; v- k我使用STM32G474Nucleo板来进行下面实验。其中VDD=3.3v,VBAT与VDD相连。另外,ADC模块的参考电压也是3.3v., |& h! Z' x0 d" S/ f6 @4 |
' o4 d+ r" s Q3 D, \. ^3 Q4 a使用CubeMx图形化工具进行配置,先看TIMER配置:
7 H- N. R5 P! B) [8 O
6 j) ]( [4 I; T( s: N! P: ^2 ?6 z! `& S; v( ~* f
& {$ J# \- V0 X+ v再看看ADC的基本配置:# t* s1 c- O9 P: b! b0 Z
" T7 n2 E, o* f, Y9 G- G* E3 d/ a
6 t4 N3 `8 e6 C% z0 \% o z$ ~
LPUART的基本配置:8 n4 m( l8 ^) |
: i& p) H/ ]- ? _/ k/ O3 \2 ]5 H3 r
5 E* o! p+ R$ J7 r" i8 G5 T" T- W
- E6 b6 T, w6 i: s. g1 O因为要使用ADC中断和UART的DMA传输,记得做ADC的中断响应使能配置和LPUART的DMA配置,这里只使用UART的TX DMA功能。9 l8 ^; P: q5 [; g8 U
; h" f7 b" t8 {
. {8 b" y, n& P% `
# d! U5 W- _- e; B: A+ |. J使用CubeMx主要配置主要是上面这些。) T* J2 s* q2 V& k5 a7 j5 Z4 |
* D% N0 ^* n1 r. }2 t3 G
在组织用户代码前,先简单介绍下片内温度传感器的内容。该温度传感器针对不同温度有不同电压输出,其输出电压跟温度呈线性关系。ST公司针对片内温度传感器在两个特定温度【30℃和110℃或30℃和130℃】、基于特定参考电压【3v或3.3v,不同系列以数据手册为准】生成了1组校准值并存放于片内特定FLASH位置。! [0 ^8 Y# t/ k
# f3 @# q: W6 b$ b: x# u! `STM32G4系列的校准值是在参考电压为3v,30℃和110℃条件下的两个值,在数据手册里还给出了校准值的片内存放地址。& B% N! u: S, k n4 H$ L
$ K- a7 `: A) x1 E& U
2 }, x- h* m3 t+ N& W' D
7 H" Q1 a" S0 K0 ]% C% _针对这个温度传感器的使用,ST公司在参考手册里还给出了计算公式。其实,有无这个公式无所谓,我们不难自行推理出来。【TS_DATA代表某时刻测得的传感器输出电压对应的转换值,TS_CAL1/TS_CAL2分别表示在30℃和110℃条件下基于传感器输出电压的转换值。】! {4 h4 u( H& k9 Q% P; F+ ?+ v
' Q1 O# z5 n. z( _2 ~+ G$ V7 g- w8 w( s5 h
0 c2 _( }! i- R
% S3 R7 h2 I' t1 _另外,前面提过,ST公司在手册里给出了温度传感器的两个温度下的校准值,但要注意生成校准值的ADC模块所用参考电压跟我们实际应用时AD模块所用的参考基准电压可能不一致。如果不一致,就必须将ADC值换算成同一基准参考电压条件下的数据。目前在ST手册里也特别强调这点了。我把上面一副图再贴一遍于此【见黄色语句提醒】。& M7 H) i. u2 k& c C* n. A, }
+ h3 M& y6 s( c7 M* P
+ Q. J( F; H/ U
$ w' [7 T3 Q% K: b2 T: h关于这点,我们也不难理解。同一待测信号、同一ADC模块在不同基准参考电压下转换值往往是不一样的。见下面示意图加以理解。: y& z% ^- B# C8 c
6 ` K: {3 D* O! T8 v2 a9 H
* X- r1 c9 u$ X/ c
; r9 Y) _- |) G4 X. v! V- O$ q完成各项配置后,创建软件工程。添加必需的用户代码:
3 ~: \; S* z0 E4 W( r s: l7 U0 f4 T6 @1 L& n
- #define TX_Timeout (9999)3 z+ R3 x' `9 L& H9 c
- & n* j& i6 S; C: X) ]
- #define TS_CAL1_ADDR (0x1FFF75A8) //用于计算温度传感器数据
0 V1 P) j% v9 X' O; A - #define TS_CAL2_ADDR (0x1FFF75CA) //用于计算温度传感器数据
2 C* U! w; g; ^! Q - 7 ]! K. M& \4 }) i
- #define size1 (40)
: _- x% Q6 c3 B, \) v - " i+ ?( E5 ], ]/ R1 d
- char WDVol[size1],BatVol[size1],InVol[size1];$ N( t, X+ b' q9 Y: y9 n$ e3 |
- : h% ] E( d! y9 n K
- uint16_t ts_c30,ts_c110;8 a$ ], W P* [. x6 `
( b7 J2 o: O+ L) ?* ^) K7 N/ G- uint16_t ADCResult[3],convCNT;: S9 W# p! c7 I4 x! b) v
) Z3 K6 ^3 v; k) v- }- volatile uint32_t Completed,EndofCon_Flag;# K, U0 v5 Q0 f9 f7 c6 k
- , r, W% Q1 h* }& z# Q7 j6 ~
- float VBATVolt;//存放BBAT电压最终结果8 [9 g4 q+ f9 G2 |- T% J' l
- float VRefint; //存放Vrefint电压最终结果) P+ C! r1 a' `: F9 o b4 q+ S
- float Temperature;//存放片内温度℃最终结果: G. ]6 o/ l# X5 m1 k+ w
' \7 P9 D- V2 Y2 r- int main(void)8 D- ], w6 T9 y
- {- o& K8 z6 e/ C6 w3 d. P* H
- /* USER CODE BEGIN 1 */
& M: w" U. P+ L0 j - " P U" J' S j. T
- /* USER CODE END 1 */
: [! G& M. o9 v& `: N/ L
8 C$ D% E/ u2 ?- g1 n- /* MCU Configuration--------------------------------------------------------*/
: A: H3 z1 [; a. `: @+ M/ \
5 e$ a& ]' o9 S% d- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */: m0 y3 J9 \) `' R8 h
- HAL_Init();
) s* N% S' B9 F/ b, s
2 j% q3 f# X* ?6 t H- /* USER CODE BEGIN Init */
& c" n+ h1 f$ U" a2 P L - ) r: r$ W; M7 y0 Y
- /* USER CODE END Init */
; d+ c* M0 @4 y. o/ A" ] - 0 T! R' E4 r! h1 g7 M2 V V
- /* Configure the system clock */8 t7 } {4 M# F9 @2 U
- SystemClock_Config();5 K4 c" }2 u: }0 f' S4 w" H
/ ^1 `2 p1 B- e% k- /* USER CODE BEGIN SysInit */$ ]* f! B2 G: ^& m* l, N
- 9 O6 V$ z3 e2 A8 `/ M5 w7 ?4 g
- /* USER CODE END SysInit */
! t& o: y6 d0 \ T a
$ }1 ]: S; J5 A- /* Initialize all configured peripherals */' P- u% q$ y8 T
- MX_GPIO_Init();' e, g2 t+ P$ e
- MX_DMA_Init();
: g. y- _/ K3 w+ x8 J6 f3 q - MX_ADC1_Init();6 v- ~2 I& g' s9 D+ \+ ]
- MX_LPUART1_UART_Init();' V% h8 n! j8 U& M3 d
- MX_TIM1_Init();/ o( P4 P! _( V/ G) p/ p/ g
- /* USER CODE BEGIN 2 *// f z0 P/ s Z2 p( D
- 8 Q+ U( |5 d( @
- ts_c30 = *(uint16_t *)(TS_CAL1_ADDR); //读取30℃时的ADC校准值/ o6 O) d, e7 @5 _' c' p
1 [* H/ K' V. S; a- ts_c110 = *(uint16_t *)(TS_CAL2_ADDR);//读取110℃时的ADC校准值
; F& N9 X0 U' y$ Q% d
; Q' I8 n# j* X! b, v( Y6 E5 ?- HAL_ADCEx_Calibration_Start(&hadc1 , ADC_SINGLE_ENDED);//ADC校准/ X7 k) I# B# o- e" j: ]& x& r b
- W; x7 R( Y5 q. K& z- HAL_ADC_Start_IT(&hadc1);//启动ADC并开启转换中断
3 t4 |% s Y" Y& u Q
' x7 ^* _ t% f; b" f0 f& l- HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);$ U9 p; K9 s/ F- N) E0 F
, S" m* ^5 k6 m: _
4 e7 @ E) `( ~; {- M; _- /* USER CODE END 2 *// I0 ~+ h: e7 r
2 _+ C, Z1 ?6 r0 a- /* Infinite loop */
- ~" u6 y' t" J- L - /* USER CODE BEGIN WHILE */
) h) _9 b# \: L, T. B - while (1)3 `: X" c4 i0 O% x9 C$ n# L
- {* i, C5 |1 ^0 P& [" D9 X
- /* USER CODE END WHILE */
& ~2 Q7 d) ~0 K! u1 T- K
1 F F* R, v2 W- /* USER CODE BEGIN 3 */4 a% M' E S" T
- , T8 y% S) I* f" P& t
- if (EndofCon_Flag!=0)
: r3 w r5 y9 p - { z, i+ |& Y/ [/ Z! A, g: s
- VBATVolt=(ADCResult[0]/4095.)* 3.3 * 3.;
" P( V# Y/ X5 o/ p! s9 Z
7 C. R- `; ~5 C5 n6 U5 ]) i- VRefint=(ADCResult[1]/4095.) * 3.3;
: X: s2 m' V( \4 v
: Z( Y- K4 d/ l/ V3 Y- Temperature = 30.+ (88.*(ADCResult[2]-((ts_c30/1.1))))/(ts_c110 - ts_c30);/ n3 D: r6 G6 Z4 B7 U
' C; Q/ w# e$ S0 z- EndofCon_Flag=0;
; h7 p9 S H, l - . A& { c: Y' @
- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol), TX_Timeout);
& R+ Q5 j8 a* b7 L: S6 C& R. @& y - & F$ g8 u8 G6 Y4 _" B. Q0 n6 k! _3 @& I
- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_RESET); //for auxiliary test' n+ q0 Z4 }; \' X q l2 {
- ) j- M/ q. S: {7 s2 f+ o4 U3 y
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));
" s4 _0 K+ j) {2 P2 `
8 ]" C+ D" E& p8 p# N1 q- while(Completed==0){}
! M" Y: X: ]. B0 {& I6 Y - Completed =0;
# s! m# {: k, f/ }" j l
9 G6 A1 d" [3 J+ [. B' }) l
5 i' G1 G/ U, _" Z8 `+ v* y. G- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol), TX_Timeout);+ }) t$ ]1 \# k# m9 t
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));
0 Y5 v3 r, p& S; o H! S! G
* X/ G) w+ {' C8 d" Q$ L G: @8 d- while(Completed==0) {}
0 B) S" U! Y2 ^3 @ - Completed =0;
, ^( B5 {* S4 i; x& d4 } A: r" [ - 5 i5 I, C, V/ ~
, G/ t+ @$ a: b- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol), TX_Timeout);/ G$ Z6 i/ A3 X3 h' d* `
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol));
0 `+ s/ I7 I0 \7 p - 2 m+ V. \$ l9 R% j6 O0 F# N3 }& C
- while(Completed==0){}4 Z% G9 q* k. a. i0 s+ N7 t6 m
- Completed =0;. ]0 C8 @+ V6 Y/ e4 r
0 ~6 y+ Z6 ~; B$ @. m7 m0 E: r9 t- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_SET); //for auxiliary test
! G, J) W* R; T0 D+ D0 U- e
: G9 a( p$ h$ E+ M1 G- }
* @4 O0 L! x: ~5 P! {2 q9 R - : v) {3 d, t) S
- }
- f9 r: z0 }2 S* r: M% o - /* USER CODE END 3 */* O/ a9 y: i C
- }
* K. b0 F) m# F" G$ P4 G3 o
* e# a* w% }" {& i- //ADC EOC 中断回调处理函数
6 h+ f* v5 r: [ - void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)) Q' I) Q' r; V# c& L7 s, a
- {5 l) o. |2 m B5 y% D" I; k; J
- 4 ^: ]) L4 |# f3 [
- ADCResult[convCNT]=HAL_ADC_GetValue(&hadc1); //获取转换结果并存入数组
- m" [$ E* z+ H) _* w: K - * v$ s& }: Y5 e( X' K# t2 ?. y
- convCNT++;
" D& }2 m1 d4 B2 @# C
2 d% X) I6 q2 {2 y$ v- }- if(convCNT==3)
' }) {- Q' C2 i* K$ Z - ! @6 M* W! M4 j6 u" o) C
- {9 _1 L4 T1 v. ]! H5 k0 }3 g; A
- convCNT=0;
T6 n7 s& Q' K - 9 S# P6 p+ i; f4 h+ i1 {
- EndofCon_Flag=0xff;. Y7 [" y- x; o' a6 q
$ u( J3 |2 b* C& v) k- sprintf(WDVol,"Internal PN Temperature: %5.3f \r\n",Temperature); 4 g% b3 x% R& j+ b. U q) t& Y
- ' q) L( P3 M. S* t
- sprintf(InVol,"Internal Reference Volt: %5.3f \r\n",VRefint); : q: T/ ?$ J. }! {9 S) K( q0 q$ @
- 7 `1 h. x' c/ U& R. |
- sprintf(BatVol,"Current Battery Volt: %5.3f \r\n\r\n",VBATVolt);' }$ B- n4 W& \: h* B$ z
- 5 u* i1 I% X8 u# Q
- }
4 l. p1 z# y# [. [1 X - ( R, `) h9 U) j3 w; i
- }
& C& Q$ q) x3 k+ ?5 D$ K, r
# [. ]8 Q% {6 x5 P- //UART DMA 传输完成中断回调函数
; s D. [: o4 R+ d- m
. d: ]0 w! A+ j1 B* Y3 u- void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
' n" W6 y$ d0 @& o2 R3 {/ i: {$ \9 [ - {3 a! X6 ?, E; D0 D/ F9 N
( ]( U2 W( w; X" N: J- Completed=0xff;
6 J ~ K: W/ a' v, c2 m - }
复制代码 8 H* U5 t: w E# p+ q, }
基于上面的配置和测试代码,我们就可以看到最终的结果了。定时器周期性地触发ADC,每得到3个ADC结果就进行数据处理,然后通过UART以DMA方式传输到串口终端。注意VBat电压是测量结果再乘以3得到的。- Q, l% u! ~% S! x+ u
2 Z& O% K( {! Z
& b* l# m2 h, |3 ~0 v F2 I# p& g# a7 x# U
5 Y8 J/ [" ]4 t+ J8 s% H
# k( s9 b l t* ~5 _ |5 Z" A( z针对上面的应用演示,最后给几点相关应用提醒:; ~& h. `$ W3 r* K, D1 F" ]
* [' E$ a: l F
1、针对温度传感器做测量时,校准时使用的参考电压与实际应用不一致时要做换算,换算成相同参考电压的数据后再做计算。这点前面也提过了。9 V/ b1 F0 X$ [2 U
- N& x+ O9 O! q) d: ]2、使用TIMER的TRGO触发ADC,如果选择类似比较事件、更新事件来触发ADC时,此时ADC对触发极性的选择是无效的,或者说ADC的转换仅依赖于触发事件时间点。如果是选择TIMER的Ocref信号作为触发源,此时ADC的硬件触发的极性选择是有效的,可以是上沿或下沿触发,甚至是双沿触发。这时就得根据需要选择合适的触发沿。【可以进一步阅读本公众号文章《STM32定时器触发ADC的时序话题》】
4 q2 y, U; q, ]4 U! f4 P$ Q
$ O5 O- _ {/ r8 S s0 G7 c8 q2 T) o3、这里使用UART的DMA传输依次显示三个结果于串口终端,三个启动UART DMA传输的函数须保留适当时间间隔,即等上次传输完成后再启动下一次传输,因为这里每次传输使用的是同一DMA通道。否则没法全部正常输出。比如若把上面3次UART DMA传输的代码改成下面这样子:/ q# T/ C* N1 m8 G: Q3 U
v$ X: e0 G5 i" B3 ]6 W) m( Y- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));$ r/ r" @/ m2 b% k u3 r" `
- # m# y1 v/ F- B* q
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));
$ f! h+ N, g+ U9 \! e6 T( Q1 @# w" z
& c* r# o7 ]( @+ I) p0 |- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol))
复制代码
0 n8 X3 p# |. I6 M' y这时输出结果会变成下面的情形,总是只能看到一个结果的输出,即第一次启动的DMA传输结果。* U8 L# c/ o4 c% |, Z" |) T5 a
4 F$ y; c& C. Q
' @" D" [+ o% x2 n6 ]. Z) p: u
4 D, b9 b& _- E8 \
如果想省事点,直接在相邻2次DMA传输间加上合适延时也行。我这里根据DMA传输完成事件来决定执行下一次发送。在DMA传输完成中断里设置Completed变量为非0值表示当前一轮DMA传输完成。/ R* C& i; x' K# k: A" B
; e; ?5 X, Z N% s4、对于那些在中断和主程序里都会被访问的变量,记得将它们冠以volatile。, m7 R+ x0 ?2 w8 Y9 i# f: _6 F: }
$ B# y; `- a' E. z+ L
下图的三路波形是我调试时辅助使用的。
0 z' B' O2 K5 b9 N: s+ {' t* S& `8 f T' B. a$ }
第一路表示计数器的计数变化,显然是单向向上计数模式。$ `9 t2 T; e3 W& M; A( o
7 v0 a" }* G3 k7 a第二路是TIMER1通道1的PWM输出波形。
$ W% O. P* ^: T) C. N
. b$ Z- n2 ?5 q% t9 a第三路是我每次基于DMA实现UART发送时拉高拉低的波形。平常管脚电平为高,在实现DMA传输过程中拉低。
" w7 h$ R" S1 _$ h0 Y4 a
' C, a, w* ?$ y+ C# {7 `. d
3 q7 t& ^ Q" \8 A( Y! R3 {
+ N) m: s; G' U7 q+ ~% x8 @' L如有侵权请联系删除
1 J2 @' H4 c9 \5 J- {: F转载自: 茶话MCU
6 r% E' t; d) e% ~8 r7 e; e1 p7 R- J- ~1 ]$ F! a
9 @! T' W. R; p8 s6 X$ C4 ^
$ ]8 F x9 J7 E0 K: W G2 g: V8 ?$ C |