& w% N: |- _1 E- q, _
- n0 @1 b& F. _, ?
$ S* A6 A* L- H+ C0 ?
很多STM32芯片里往往内置了专用的ADC通道,比方用来测量Vrefint,VBAT的分压或温度传感器的输出电压信号。不同系列所内置的模拟信号通道可能有差异。这里以STM32G4系列为例,它内置了对应于Vrefint,VBAT的三分之一分压和温度传感器的输出电压的专用模拟通道。* T1 t) z6 J* q) p; {7 [; \: |
* N4 R1 Q& K3 {3 i* u* y
+ F' ^& t2 T4 T9 d3 X1 ~/ g5 `6 W2 N: i
) k9 a, V# r' k5 @. s' f; o4 f
, \" i0 M1 ?, C8 R6 K4 v6 p# a% Y* s* M下面的示例就是针对上述3个通道进行ADC,并测量相关电压和片内温度,最终得到3个结果,分别是VRefint电压,VBAT的电压,片内温度。+ `3 E2 v: z' j" [1 U3 G
9 q$ E$ s& O1 h
9 Z4 P% l! J/ i; X* q$ {& D
) g2 T( L/ X' ]- G5 r! f& f实现过程是这样的,大体分四步:【有点点麻雀虽小五脏俱全的味道图片】
I) @. K% V7 U9 z- R$ r6 H9 ]( D1 l( ~* v! V/ |( {
1、TIMER1 更新事件触发ADC的转换;7 Y, M) R; L+ f' q- D
$ n4 y- G& ~6 Y4 k3 v2、CPU基于EOC中断获取ADC结果;2 g" b5 X& Q, R
/ Y7 k2 p A% X- ^- x3、对ADC结果进行换算,得到电压值和温度值存放在特定内存位置;* m* t& W: \) S s( e5 y
+ h/ K+ C, _4 p, b( a% t4、基于DMA传输通过UART将最终结果在串口终端显示;! g. j) e. k# @5 V* s* Q. F i; H
* i9 }3 V" m8 ~; X& a" U, B其中,TIMER1的CH1输出PWM波形,其更新事件做ADC的转换启动信号。每次的TIMER更新事件触发ADC,3个通道扫描方式转换。这里的UART使用片内LPUART,使用它主要是考虑它跟板载虚拟串口直接相连,没有其它特别用意。' m, E: n5 _' c. ^" t
) ], b# o: w* Y
$ s0 p2 Y* f* M% X# n H) r
# E, @% u! j; `7 ], C7 ~. J我使用STM32G474Nucleo板来进行下面实验。其中VDD=3.3v,VBAT与VDD相连。另外,ADC模块的参考电压也是3.3v.5 K" w; @% s. r' s9 @4 ]; s
4 r, V1 E& A! l1 N
使用CubeMx图形化工具进行配置,先看TIMER配置:+ _+ c5 g' e/ R7 M+ k, Q* L
! M. u) X I$ b% X2 G
+ q" Z- _) D# j! H- W& v: X" m. c' W* M: F( @
再看看ADC的基本配置:
( s3 M0 a% C- o( O7 k# q/ {4 ~7 G8 K0 b4 Z
5 G" n% h' n R# c
* S" O3 a4 @+ E, b& h6 H4 \7 fLPUART的基本配置:
* [: P' K* C& u- {: p6 k
# B5 O' ?! V% o
; F9 \/ f$ O8 X
" c4 p. W3 W( z$ o4 X! g6 z% E因为要使用ADC中断和UART的DMA传输,记得做ADC的中断响应使能配置和LPUART的DMA配置,这里只使用UART的TX DMA功能。
. Y- V6 F& `) y- Y- g2 W
" h3 q% Y s* R8 Y2 R: [& {
+ @. X4 S x6 g8 U
% u9 o' m. |/ M使用CubeMx主要配置主要是上面这些。( M1 D9 z [8 _) G' e* H
6 H. x" c; J+ H5 _0 N4 U: @
在组织用户代码前,先简单介绍下片内温度传感器的内容。该温度传感器针对不同温度有不同电压输出,其输出电压跟温度呈线性关系。ST公司针对片内温度传感器在两个特定温度【30℃和110℃或30℃和130℃】、基于特定参考电压【3v或3.3v,不同系列以数据手册为准】生成了1组校准值并存放于片内特定FLASH位置。6 H A0 J8 k2 @$ _5 X% [/ t/ I2 Z( @
7 g- ]# q" s! O; zSTM32G4系列的校准值是在参考电压为3v,30℃和110℃条件下的两个值,在数据手册里还给出了校准值的片内存放地址。
- z" u* p, V1 x$ c
, i- e3 _/ p S6 f1 z. x; |/ L; ~! q& ^- f$ {9 H4 L$ M
7 ?2 t( \5 ?0 M1 m
针对这个温度传感器的使用,ST公司在参考手册里还给出了计算公式。其实,有无这个公式无所谓,我们不难自行推理出来。【TS_DATA代表某时刻测得的传感器输出电压对应的转换值,TS_CAL1/TS_CAL2分别表示在30℃和110℃条件下基于传感器输出电压的转换值。】
9 }* `8 _# L( p7 j7 l; R( F3 p: T* E2 B1 p# k! m
: N' [. H" e6 L
: H9 r7 e' B8 z1 S另外,前面提过,ST公司在手册里给出了温度传感器的两个温度下的校准值,但要注意生成校准值的ADC模块所用参考电压跟我们实际应用时AD模块所用的参考基准电压可能不一致。如果不一致,就必须将ADC值换算成同一基准参考电压条件下的数据。目前在ST手册里也特别强调这点了。我把上面一副图再贴一遍于此【见黄色语句提醒】。3 a& T: b* ~" o& o
1 k+ I8 ~( a$ [3 N+ f" a
. e% x+ Z& t2 o$ j+ D
$ m; O$ f; S1 @1 v关于这点,我们也不难理解。同一待测信号、同一ADC模块在不同基准参考电压下转换值往往是不一样的。见下面示意图加以理解。
! U4 Y4 v( c: K0 Q( @+ `( D' H; a) D
6 C" w; ^+ K9 f ?1 [1 a! {# R6 n) K
! f& m3 c m& q! w
完成各项配置后,创建软件工程。添加必需的用户代码:$ O8 h: X$ P+ D7 F5 f
j- Q+ A6 c; r: V# @+ G
- #define TX_Timeout (9999)6 I% f8 V0 n0 \- m: C& E) v5 f( `
- ) _- }# E; X: z2 m8 N4 Y' y; z
- #define TS_CAL1_ADDR (0x1FFF75A8) //用于计算温度传感器数据
( W$ S0 A& u# b4 R - #define TS_CAL2_ADDR (0x1FFF75CA) //用于计算温度传感器数据
. G* J4 X1 p6 p
1 D' \3 B" {5 O! f8 R- #define size1 (40)
3 [0 o( i' i y7 n* B - - `3 P7 ~3 \( C+ m
- char WDVol[size1],BatVol[size1],InVol[size1];
, u4 P* e8 X! \( R
4 \2 b0 R1 _$ \& G/ `' V! h" k- uint16_t ts_c30,ts_c110;
6 z6 l1 ?. b! R. g$ y( ^$ C5 V! } - / P( v- e2 Y9 I$ l
- uint16_t ADCResult[3],convCNT;
- j3 E( F) o* q' w9 H3 t# @) u0 n - * b% T3 D, Z) f9 E. \) s; t
- volatile uint32_t Completed,EndofCon_Flag;
7 s) T, O2 L( S' d4 q( [, V1 M - % x, a# n$ ]2 ?2 }
- float VBATVolt;//存放BBAT电压最终结果
/ R# C7 U, x$ W- A z$ p - float VRefint; //存放Vrefint电压最终结果6 u* O3 I; v0 V' b A% I C
- float Temperature;//存放片内温度℃最终结果* I8 d5 f& ]& T2 A
% x( e( D C$ e7 Q- int main(void)2 W* z: ^1 x; X+ j L: m
- {
2 S: R$ I: ^2 |1 p* B3 Q5 n" A, d - /* USER CODE BEGIN 1 */
6 }: |9 m ~( _" L
4 F+ B* }1 ?2 ]* c- /* USER CODE END 1 */( g0 _! w; t( b4 {) L
& j& t3 s9 ~5 T# A- /* MCU Configuration--------------------------------------------------------*/
0 o2 t8 Q/ \: Z! [1 X9 N& W x4 }
8 X8 U5 C$ U6 S( [, ], u8 L- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
7 U' E: U/ A3 t; u1 [ J# E - HAL_Init();
' U( n, t9 q( J$ l$ a8 v - . J2 y* i9 r. h6 u6 J4 J
- /* USER CODE BEGIN Init */ K: H0 e; k& L- K0 X$ F2 V; m2 E4 W
- 4 u( D, n& ~ ]& g2 N
- /* USER CODE END Init */1 R& \7 h+ j6 U0 I, P3 t0 e
- / J, Z( n) E& v
- /* Configure the system clock */
/ ?( h9 g. H3 r/ H1 z- X, ^ - SystemClock_Config();* z- T, a. N h+ w: H: @8 z
1 P( H) ?1 `, s9 J6 p- /* USER CODE BEGIN SysInit */
q+ v0 P' p, Z+ ^
" Y" g) c: o5 U4 K( h- /* USER CODE END SysInit */% ]" b9 \ T! ~/ N) U; V
) B+ J8 x' ]% a6 K7 z( j7 Z- /* Initialize all configured peripherals */
5 g4 D) h! ?9 }& l, T - MX_GPIO_Init();+ V% u+ Q- | Y! P8 q3 w5 X
- MX_DMA_Init();' v1 ^* [% F0 D( x( P4 U4 v
- MX_ADC1_Init();0 O- D. ^1 C% x) V ?7 I
- MX_LPUART1_UART_Init();
% O/ S3 A' P4 [& R5 e - MX_TIM1_Init();$ h/ k- f7 K, F' |! K4 v8 W
- /* USER CODE BEGIN 2 */6 j, | c* V& v0 B
- 3 `" O. W& ?8 x
- ts_c30 = *(uint16_t *)(TS_CAL1_ADDR); //读取30℃时的ADC校准值
2 _$ j% l2 j, `0 c* y - 1 i2 [( b: J6 A& G$ O
- ts_c110 = *(uint16_t *)(TS_CAL2_ADDR);//读取110℃时的ADC校准值 P7 C0 j& E+ [1 e' m0 m0 w
- 0 x4 Q9 W* z6 S, ` a
- HAL_ADCEx_Calibration_Start(&hadc1 , ADC_SINGLE_ENDED);//ADC校准
% Y; g; U' D$ S! L
7 g2 X' U8 G3 A/ R1 A- HAL_ADC_Start_IT(&hadc1);//启动ADC并开启转换中断& n N |( f& }9 V0 L3 z' s
- 2 e1 B7 K/ V# x" M; y2 e- K# a9 k
- HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
9 z2 k9 I1 I$ l9 s4 \ - 9 V: c6 Z! `5 J3 [% D
! h, r/ q9 Q- x& Z' v3 m- /* USER CODE END 2 */
/ G# ~5 f- i( Y+ l" B - 8 o# h1 o3 k) A7 a3 x
- /* Infinite loop */
- o8 `& n) u& a% P9 f5 x4 R - /* USER CODE BEGIN WHILE */- E8 q% {: n0 b F& r
- while (1)% H. w- h! U) U: E: M+ H; E
- {1 v8 j+ e, {; s0 H- ?; a
- /* USER CODE END WHILE */
: i' `8 ~' X8 f$ E5 o8 y
7 x9 q# m. q: g6 u |/ ~- /* USER CODE BEGIN 3 */
8 J( S- |+ D2 s
# d7 r5 J! F: C& {$ L- W- if (EndofCon_Flag!=0); f9 M$ D" s0 q) }% ]( _
- {/ v5 ~- | F( |* U6 W) M* b
- VBATVolt=(ADCResult[0]/4095.)* 3.3 * 3.; 6 S/ P* s3 q; B/ a5 S
( H" G. ~7 S; ^! F- VRefint=(ADCResult[1]/4095.) * 3.3; $ @! d: K- x" ~
* B3 T4 q8 w* q( e- Temperature = 30.+ (88.*(ADCResult[2]-((ts_c30/1.1))))/(ts_c110 - ts_c30);5 ^. X+ ?2 t# t/ s: X0 m! `
- M2 ]: _6 J# X5 |* ~: l0 z- EndofCon_Flag=0;
; Q0 ]( R3 B! \& b$ b$ E/ ^ - # m V: `- J4 M# o
- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol), TX_Timeout);; I- G+ @4 J. y; N
9 a2 U& j0 b: }, z- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_RESET); //for auxiliary test
* [, Y+ E/ \: f* n - 1 N$ \; x; Z* n5 G* H4 B! G
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));
' F% N: n' }/ I K) h - # f$ {8 ]0 ?8 [! B% h R4 e
- while(Completed==0){}
' ?8 O) I' {6 D, K8 U' B - Completed =0;
2 O- e2 N0 r2 @1 z - / g& l( l. q( g
, Q4 |1 T0 a: s- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol), TX_Timeout);! e/ c! a7 \) ~) {1 g, e" Q
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));
' ^5 q9 \) \8 L) O0 L C" r9 ^
9 a- H6 B7 S! O6 L, E( w- while(Completed==0) {}
3 {! z3 o: e$ I+ G3 n! H. c/ O4 @! J - Completed =0;! i3 A4 d/ E% F' s) T( R
- # d9 q! J* n3 M4 r8 b" t/ ?
) e# M" [1 R4 q- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol), TX_Timeout);
' Q8 |7 {- |; c! u( g# P - HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol));
' u8 t; x% n3 J, c, w7 T3 M" k$ j - & e. L- P+ E6 A c2 H
- while(Completed==0){}2 I6 u+ | H0 j/ ]5 h# X# t, Q
- Completed =0;. }$ m/ u0 [4 b2 r2 X; W# b. B- T
- + P$ ?" k- O5 T/ U- n
- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_SET); //for auxiliary test; Z5 ]/ F- u% N+ V6 B0 Z
- n# @$ V! P9 Z1 z6 W: z
- } 4 C6 I/ Q7 f/ g0 ?7 }. Z6 h
- % p6 o' v* O$ H6 E
- }) ]: ?* Q) v9 Y0 m0 G
- /* USER CODE END 3 */
6 j, g3 }0 v: @# }) I" F ^( F - }9 K- [& K1 h/ k" o* d
( B6 k* h2 x: V/ G: E- h- //ADC EOC 中断回调处理函数' y1 [0 h, V3 w! @4 H# Z8 Z
- void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)# A9 R* q0 Z) K7 x3 w
- {
0 w- H3 g3 Q- P& d8 n/ J3 ^ - " P% F N# ^. y: p8 u! i
- ADCResult[convCNT]=HAL_ADC_GetValue(&hadc1); //获取转换结果并存入数组
1 @ R' A5 V; h- e% @# ^# k* j - 0 q# d+ b9 q0 _( i- {, p0 l
- convCNT++;8 x1 z8 z8 f% o, F
3 R4 f V8 M& \: A+ f: [- if(convCNT==3) 8 h9 y. U( C3 Q9 r8 l; L4 ^$ p
- 7 U4 p6 F. {- u8 T, y
- {
- v- F. {$ y- y' ]5 k9 T - convCNT=0;
. x9 m. v" `/ Q: G# S6 \
! I6 J( b; e: R5 |, E- EndofCon_Flag=0xff;
6 d$ T3 V5 G% E2 ~4 P4 [ - - W% n: c2 F0 } m# P8 h. V
- sprintf(WDVol,"Internal PN Temperature: %5.3f \r\n",Temperature); % X# o) r& [7 L0 ~4 H& {
- & H7 f! J) ~7 _' ]4 A
- sprintf(InVol,"Internal Reference Volt: %5.3f \r\n",VRefint); 2 G7 p1 ~+ V; D% m; x
- , A$ Z( }5 u) F' _
- sprintf(BatVol,"Current Battery Volt: %5.3f \r\n\r\n",VBATVolt);( l; X8 q- Z3 r
- 8 I2 A6 o( X! X
- }, E" U2 e' S( r" q
- $ N/ ~7 d! A& }6 n
- }5 h, y' c9 @ z( u
: L4 x; S' U* |' t% |/ U8 Y8 s- //UART DMA 传输完成中断回调函数6 c& z# c0 o9 G/ I' |8 B3 h
- , e; Z5 [9 U0 E* u p4 ^5 J
- void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
+ w; d0 W1 F3 V7 G6 G6 j! Q. q - {- w, \7 e! g, V3 s
3 h9 g3 Y$ g8 Q7 M$ D- Completed=0xff;
1 q/ B/ W' ~+ f2 Y# Z: U! r$ ~4 D; c! ] - }
复制代码
$ }6 }% J2 L* a/ { B基于上面的配置和测试代码,我们就可以看到最终的结果了。定时器周期性地触发ADC,每得到3个ADC结果就进行数据处理,然后通过UART以DMA方式传输到串口终端。注意VBat电压是测量结果再乘以3得到的。. R. C* [) G9 T" o
F& q/ U5 ~8 D( ]/ F5 N/ G0 I
& c( Z% t9 W* r5 `
. W* S' T; ~2 B! W$ Q6 K8 R. w0 f0 W2 W( ?6 a% Z( z" g. T
针对上面的应用演示,最后给几点相关应用提醒:
1 M. p: V+ x3 a3 K' b6 d
: K5 Q9 S( H3 i, Y" l) x1、针对温度传感器做测量时,校准时使用的参考电压与实际应用不一致时要做换算,换算成相同参考电压的数据后再做计算。这点前面也提过了。
9 R$ _( T- b( h* d4 W9 K1 O
7 K& c1 E; n" B `2、使用TIMER的TRGO触发ADC,如果选择类似比较事件、更新事件来触发ADC时,此时ADC对触发极性的选择是无效的,或者说ADC的转换仅依赖于触发事件时间点。如果是选择TIMER的Ocref信号作为触发源,此时ADC的硬件触发的极性选择是有效的,可以是上沿或下沿触发,甚至是双沿触发。这时就得根据需要选择合适的触发沿。【可以进一步阅读本公众号文章《STM32定时器触发ADC的时序话题》】$ @/ k) ?8 E1 k: c% [7 d
5 o- b; T! n) S0 h: I
3、这里使用UART的DMA传输依次显示三个结果于串口终端,三个启动UART DMA传输的函数须保留适当时间间隔,即等上次传输完成后再启动下一次传输,因为这里每次传输使用的是同一DMA通道。否则没法全部正常输出。比如若把上面3次UART DMA传输的代码改成下面这样子:
; Q9 M1 I, h! Z* i4 W r
- a6 e( Z+ c) L; k( n6 X J, ?( ^- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));
0 r# ?. c1 h4 |9 p( Z - 6 j! D: G0 s8 \4 \) b! I; l( i9 [
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));+ ]1 N: b8 b+ \8 I
4 s3 ^ I' b0 ~: V1 q8 f- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol))
复制代码
/ [ `2 D# q# p3 \9 A9 A& v这时输出结果会变成下面的情形,总是只能看到一个结果的输出,即第一次启动的DMA传输结果。
! x0 Q" f: x2 t r3 M4 v0 c$ j; q: m/ n% `. ]# O$ d9 c1 m
6 w# _% q y1 @
' J; G, ]4 n1 D% C% [- V) e- @9 Z
如果想省事点,直接在相邻2次DMA传输间加上合适延时也行。我这里根据DMA传输完成事件来决定执行下一次发送。在DMA传输完成中断里设置Completed变量为非0值表示当前一轮DMA传输完成。( B) s* N5 T* h0 k2 {" ?% B6 g2 A
! h3 [' b4 @ h' N6 H' e
4、对于那些在中断和主程序里都会被访问的变量,记得将它们冠以volatile。
8 t9 n; D% J7 |/ K! z1 h) e$ P$ Q: i' N8 ]: |4 Y
下图的三路波形是我调试时辅助使用的。5 E# C, t; H! b1 C$ b
# Z: X7 ~& |/ m y! ]3 r8 {第一路表示计数器的计数变化,显然是单向向上计数模式。1 @( H" |* n9 {7 ?1 r
2 O5 w9 x/ l) i" t! H; ^" K第二路是TIMER1通道1的PWM输出波形。* b( B8 W3 P9 z# y
6 e0 w8 m3 Y# Q, T: w第三路是我每次基于DMA实现UART发送时拉高拉低的波形。平常管脚电平为高,在实现DMA传输过程中拉低。* c9 y7 `/ I' a* n
" h/ _ n: |0 k+ A) M, o6 P- }* N6 R, Y
% I/ l* J' [. z& d& C
N3 H1 ?! G4 Z1 m+ A3 I0 ~如有侵权请联系删除1 N' N' c9 U* G3 X5 {6 z9 f
转载自: 茶话MCU
h4 _4 ]; z8 G4 U' A2 ~, @
+ H* n6 {; a' B7 }" m 0 X/ P9 _5 N0 v" r
. f. K) e: b5 L) e5 T8 w( q5 v
|