4 ~9 H9 j$ L- N8 g) i' e8 T1 n- @+ v
3 Y) v) Q1 R( \8 P4 d) R
5 Z, A' ?& [* w+ [% O很多STM32芯片里往往内置了专用的ADC通道,比方用来测量Vrefint,VBAT的分压或温度传感器的输出电压信号。不同系列所内置的模拟信号通道可能有差异。这里以STM32G4系列为例,它内置了对应于Vrefint,VBAT的三分之一分压和温度传感器的输出电压的专用模拟通道。$ J2 }1 W% X* B# s8 I d' z
$ I4 p+ C/ t3 z7 t+ w# D, X% l- f3 S& w# v/ e" O! h& f
( _. Q$ T1 m. S( ^/ m+ f) E/ s+ F o& S; b2 K, l7 K R) c
: p" d9 y8 y8 w) b下面的示例就是针对上述3个通道进行ADC,并测量相关电压和片内温度,最终得到3个结果,分别是VRefint电压,VBAT的电压,片内温度。
& e# B& Z' N* H: `5 H- R
- c% N. P9 i: J" {, z; ]3 \ o; p% v
, \+ R5 l3 b4 b
实现过程是这样的,大体分四步:【有点点麻雀虽小五脏俱全的味道图片】
8 h! u; F. k4 \( D( ~! @. r D2 _8 Z7 c. {3 [4 f0 A0 b# B" _; }* ?8 X
1、TIMER1 更新事件触发ADC的转换;
" v: q/ C" V* r2 J1 n* D, l& i: ?/ r- M7 m+ I
2、CPU基于EOC中断获取ADC结果;" v; r. j7 I( z3 ~2 H
8 o K' x% U0 W# ?3、对ADC结果进行换算,得到电压值和温度值存放在特定内存位置;; n% t9 i. C5 j: K7 B( u6 r
: a5 X, ]8 W' n9 X) d. B# Q3 ^% G4、基于DMA传输通过UART将最终结果在串口终端显示;. G& D6 X% l3 B
4 b% }# n h) h) k; I; ~+ E其中,TIMER1的CH1输出PWM波形,其更新事件做ADC的转换启动信号。每次的TIMER更新事件触发ADC,3个通道扫描方式转换。这里的UART使用片内LPUART,使用它主要是考虑它跟板载虚拟串口直接相连,没有其它特别用意。
3 K% I/ @4 G" e- u0 x
! R2 J, F( W0 y' X+ o& {1 Z; C% y8 g$ x4 h. J$ I2 k. h
' l$ r1 D% d5 o. F' l3 M
我使用STM32G474Nucleo板来进行下面实验。其中VDD=3.3v,VBAT与VDD相连。另外,ADC模块的参考电压也是3.3v.
8 f/ K; H, }! d2 L1 {+ Y% W+ ]5 x# q& b' Q
使用CubeMx图形化工具进行配置,先看TIMER配置:
2 Z6 Q) E4 O; i8 s- T1 |7 p& Z' [, u" B9 d6 r, T; {
( q5 t( c. ?. r L9 J
* f, L& T* T$ h2 `& k3 a9 t$ w
再看看ADC的基本配置:# q6 i5 E$ Z t# `
& ^" `' | a0 V4 z: S$ D L' |& [! \9 I) b! L
: R7 u M% g8 ]% TLPUART的基本配置:
- \7 q& Y7 ?8 ]: P4 J! l8 T1 Q
- F- o- m) H. h, ]
* Q; ?$ r$ G1 G1 F5 F: ^; }( F+ g; ~/ J- z
因为要使用ADC中断和UART的DMA传输,记得做ADC的中断响应使能配置和LPUART的DMA配置,这里只使用UART的TX DMA功能。* d- B( }2 Z ]/ z9 }0 y
5 Z8 T3 j* O$ h- E4 e- O4 s
/ H1 e7 {4 k; S& ^2 G- c; J2 ?7 ~( D8 I% x% W
使用CubeMx主要配置主要是上面这些。
8 [2 `& j' n7 {2 a2 A% d; l
; y6 I) K/ d' d% m在组织用户代码前,先简单介绍下片内温度传感器的内容。该温度传感器针对不同温度有不同电压输出,其输出电压跟温度呈线性关系。ST公司针对片内温度传感器在两个特定温度【30℃和110℃或30℃和130℃】、基于特定参考电压【3v或3.3v,不同系列以数据手册为准】生成了1组校准值并存放于片内特定FLASH位置。+ Z0 t5 R7 p! X3 v W; N& {' w7 G
, k: L6 Y0 L, J3 o9 t+ rSTM32G4系列的校准值是在参考电压为3v,30℃和110℃条件下的两个值,在数据手册里还给出了校准值的片内存放地址。/ k0 U# g6 U- P9 e# n8 i
2 s. {0 b5 [# e7 j
! n" \4 |' x* j# m1 H0 t* N! W* a& R) H
针对这个温度传感器的使用,ST公司在参考手册里还给出了计算公式。其实,有无这个公式无所谓,我们不难自行推理出来。【TS_DATA代表某时刻测得的传感器输出电压对应的转换值,TS_CAL1/TS_CAL2分别表示在30℃和110℃条件下基于传感器输出电压的转换值。】6 S: a' z9 v9 C Q8 \7 b
3 w, P8 Q: M0 C% b) q/ q
+ q4 f. a9 [3 k o/ w
* ^4 U6 _4 `2 a& I, S" W4 w& ~0 _另外,前面提过,ST公司在手册里给出了温度传感器的两个温度下的校准值,但要注意生成校准值的ADC模块所用参考电压跟我们实际应用时AD模块所用的参考基准电压可能不一致。如果不一致,就必须将ADC值换算成同一基准参考电压条件下的数据。目前在ST手册里也特别强调这点了。我把上面一副图再贴一遍于此【见黄色语句提醒】。
r7 b5 ]4 F( D( x% \4 o
8 i1 T5 h0 r6 F2 b5 ~6 E7 r+ A2 X y. w w- Y3 p B% h
8 Y3 e$ A0 N0 Y: k$ u关于这点,我们也不难理解。同一待测信号、同一ADC模块在不同基准参考电压下转换值往往是不一样的。见下面示意图加以理解。
7 D1 B. T" y" Z
- h! O Y& R( ^' `( S m+ p( F1 n/ }/ M2 h0 Z6 r
$ e0 h! Z- M3 B: O4 k. v完成各项配置后,创建软件工程。添加必需的用户代码:
. K* `8 l/ @$ I: b3 Y) v& x2 k+ ] x' c$ e
- #define TX_Timeout (9999)" v5 j2 ^; {/ n1 D4 g8 i f) d
/ w7 l H' B& P2 T; u* S- #define TS_CAL1_ADDR (0x1FFF75A8) //用于计算温度传感器数据) i1 s' o% ?9 `
- #define TS_CAL2_ADDR (0x1FFF75CA) //用于计算温度传感器数据' Q; ^) @( ?1 \! d2 t
, W s) D! I; L* ^3 ^, K1 \! [- #define size1 (40)
1 w! G- \0 }+ j3 n6 `
2 \5 ?3 Y# J! ]- char WDVol[size1],BatVol[size1],InVol[size1];
& E; y0 L. Y& S* R
6 z9 ?* u$ ~' ]) l6 q+ _, V* m- uint16_t ts_c30,ts_c110;: d9 @2 @6 p# \8 s- N* Z5 p* {7 x2 t
8 D: k7 J$ o) y1 j- uint16_t ADCResult[3],convCNT;
$ V# b' H0 O% T1 M* z& E - 4 G. n5 |) M" a# ~
- volatile uint32_t Completed,EndofCon_Flag;* |1 y! P: r& [
( ?' X/ m1 j& V( `+ x6 W% I- float VBATVolt;//存放BBAT电压最终结果
; d7 E, }/ \0 {1 I" N- h1 f - float VRefint; //存放Vrefint电压最终结果5 f2 F3 [; ? ^) M' X$ P. Q' D
- float Temperature;//存放片内温度℃最终结果+ M; l5 t0 _+ T
5 W5 |( D6 b6 G/ A& [# Z; B- int main(void)
9 p% Q8 A7 m0 F& o - {
5 w( w/ P$ _0 D5 b* i - /* USER CODE BEGIN 1 */
8 _; _) W6 n4 X" D8 o; k5 M7 k0 H
# _$ M0 W7 W6 x% z- /* USER CODE END 1 */! y& Z; @! k; `: \) d' f
- - M5 w, ?* ?" e* v9 m0 H" I
- /* MCU Configuration--------------------------------------------------------*/
, Z7 I8 k' O2 X. k" } - $ K- R7 c! c, p& F( r0 _; A$ @
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */9 h" \0 d' {3 Q- w, h
- HAL_Init();
% G. j& N3 ~4 S. O& g - ' S+ s. r+ z: k- d
- /* USER CODE BEGIN Init */
, r8 E- k: E) p! D% L - ' P4 |/ A- i8 _+ B. \
- /* USER CODE END Init */
, q2 ~2 N# `/ r% S - 5 H8 @- d. F: \3 q! X1 c* K
- /* Configure the system clock */
3 {: n; t/ J: p; F) c6 D - SystemClock_Config();
8 d& b% ~$ h, G& c - 8 [' D' Y' J2 C% d$ s/ y7 X
- /* USER CODE BEGIN SysInit */
' {- V0 `& I+ X6 Y( a, t
, c* i, x* {9 v- /* USER CODE END SysInit */
( u3 Y9 M/ b3 y L
# {) c( S& w. @0 H/ y- /* Initialize all configured peripherals */) @+ W$ l& J, J' ^; ?
- MX_GPIO_Init();
0 x# r" S/ `( }1 s* n - MX_DMA_Init();
& H! S5 m% Q) H2 [* ~5 ^, O0 l - MX_ADC1_Init();* B5 w3 F# M' r1 C4 V4 L& W F) r+ k
- MX_LPUART1_UART_Init();
( J. d6 B P; u2 Q; O! @1 N - MX_TIM1_Init();% \% m8 V. R/ `, V! t' |& n9 U- }# \
- /* USER CODE BEGIN 2 */
+ @7 H0 d& K8 n$ i
8 b4 f" u6 S- h; {" P- ts_c30 = *(uint16_t *)(TS_CAL1_ADDR); //读取30℃时的ADC校准值
5 L J: {4 j( X Q2 x! D - 5 \- Q3 ~7 Z4 q) O7 y e$ P
- ts_c110 = *(uint16_t *)(TS_CAL2_ADDR);//读取110℃时的ADC校准值
& h, X5 |# o0 {- ?$ H) F' D% H
9 c' y1 v- m& l) c2 x0 L( s& F- HAL_ADCEx_Calibration_Start(&hadc1 , ADC_SINGLE_ENDED);//ADC校准/ B; Y) ?) w. T# T k+ k
- / q/ {1 E( |0 k% O! e
- HAL_ADC_Start_IT(&hadc1);//启动ADC并开启转换中断& e' [2 _ a# B! T9 C8 y
- 5 A1 q3 G7 `2 ~: V% I0 g
- HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); b0 m) r4 A7 [: _% Q
- # h+ l+ T. C$ L* ^+ n3 l
- " t, M* [* O6 W1 _; G
- /* USER CODE END 2 */ j& N3 M P) N$ W7 p
0 }6 Z$ }$ t4 |2 g- /* Infinite loop */
+ d5 w Q5 f7 A/ M, c- m* ?5 I - /* USER CODE BEGIN WHILE */
: x& K# k( j7 {9 } - while (1)+ r t) W* I$ E) |2 F4 ^$ d
- {( q; J2 g/ e" z7 T, V/ C
- /* USER CODE END WHILE */; W, {2 o1 L2 ^; h* o4 y
2 K$ S2 `% [$ }: e- /* USER CODE BEGIN 3 *// q) w- g% O+ s4 f4 Z0 a* L
- ! x5 R# n; Q ]. e2 A
- if (EndofCon_Flag!=0)( ?. q0 [5 M% o& S, Y/ y& w" k
- {7 T* T8 w! m& V% ?; Y, |/ D1 r
- VBATVolt=(ADCResult[0]/4095.)* 3.3 * 3.; " u0 b% K7 V+ {/ U
; f$ X9 q" _8 y: L. Q1 d: z- VRefint=(ADCResult[1]/4095.) * 3.3; 3 u9 X, N0 O+ q0 Y$ g! Y. E" q' l
- % q( K2 H& j- I9 x* J4 o
- Temperature = 30.+ (88.*(ADCResult[2]-((ts_c30/1.1))))/(ts_c110 - ts_c30);; l- |" K7 a* ~
+ f1 A4 t" B) b/ e/ m4 F2 {2 o- EndofCon_Flag=0;
0 ^! i' w: {3 e' x7 i+ p - ' z' W; \8 b; U/ w8 O% p
- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol), TX_Timeout);
1 Q7 y, b. M5 E - * g, S* O( k: B2 v
- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_RESET); //for auxiliary test
3 }, |5 R' j# K4 e6 {
" R h) J+ S+ I, k( S- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));
0 J. y! Z* N* i" _ - ; \3 o+ F+ B$ L& l
- while(Completed==0){}
: {3 V$ \6 W6 {* A8 w- M! G ] - Completed =0;
8 C V! T' h5 b$ O" ^ - ! ^. U5 {# h8 r7 o
- ( e' |$ [+ Q9 b1 V! K! Z0 c0 ?. N
- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol), TX_Timeout);
0 E1 y3 x" K, r" d# S% I - HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));
$ v' d" o6 S; B* T. l9 s7 \ - 3 J7 T0 K; V: O) z
- while(Completed==0) {}7 K }+ h9 q. |3 o
- Completed =0;
1 U5 j3 P" e F) A* v
) l# ?. Y5 n5 A3 h: [
+ y% z* z$ B6 j- b* w+ ?# k- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol), TX_Timeout);6 s- Q) d) s ^
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol));* |* X S0 |, u; S
- 1 C3 I/ z+ u8 b6 F9 }
- while(Completed==0){}
* S$ j$ j" A% Z - Completed =0;! o( ~7 ]. F; Y8 W; {9 i
- # E7 [5 }9 ?& {
- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_SET); //for auxiliary test1 |7 k+ r/ r* ~3 C* T: X- T: j
a, i) A% Y. d$ B% F- X: T/ w- }
6 k2 e {" J( ~- w" p& Y5 n - / y7 ~' e1 w5 M+ a. r9 I* ^& j
- }
( ~1 e$ r2 l$ H, ] - /* USER CODE END 3 */
+ M* H/ N" D5 z1 A# s2 {' h( Q - }6 i S9 Y! p% l% v% z
- 6 ?! F3 r2 g$ { G
- //ADC EOC 中断回调处理函数5 i. j- K4 [& C8 j% o' v, k y
- void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
. j5 p R: x/ Y7 L, {, P - {
0 n9 h1 w% K$ Z" A1 j1 q
8 o4 N6 g0 D) U0 b5 I' R O- ADCResult[convCNT]=HAL_ADC_GetValue(&hadc1); //获取转换结果并存入数组& {' X' C8 Z) I. x/ t4 ~; E. K0 m( [ }
- : _. c6 z3 G5 o: B; H
- convCNT++;* ^8 Q7 F8 C5 L; m% ^, ~
- $ J+ c) `. W( S* S- _, ]
- if(convCNT==3)
1 S4 q3 Z' W) ~4 E: } - , C" S% A L$ x6 c5 A( k6 Z8 e5 h
- {; C0 v& s0 @+ l) O" g* i2 ^6 ~
- convCNT=0;: Q) ]8 v& L# @
1 }% k* M8 Z6 j v! H5 k- EndofCon_Flag=0xff;. H0 v M, W2 e1 w6 W9 j
; `9 C" X1 h' E8 i. t- sprintf(WDVol,"Internal PN Temperature: %5.3f \r\n",Temperature);
4 @( W, ^( _" M
* V) H) O$ A$ }+ e& |, k, N- sprintf(InVol,"Internal Reference Volt: %5.3f \r\n",VRefint);
- {8 O4 o5 \$ U
- t/ S( Y- N D; e7 d2 {- sprintf(BatVol,"Current Battery Volt: %5.3f \r\n\r\n",VBATVolt);" y8 W1 E5 ]0 \1 T
: E6 Y4 O- I# F1 o& G- }
. q+ G/ A0 a' j! C7 s4 m4 }6 X* Z; c
% ^: X1 w7 X" j- R; E+ i1 g- }( w" l7 L* N2 \' I) z7 g
2 w, u, g5 y. |8 Z7 e* j- //UART DMA 传输完成中断回调函数+ S0 m; b' J6 f) ]
- 6 v+ \* E6 f9 r+ X5 p
- void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)7 |: x8 Q+ M5 a+ b# S
- {
1 y; T2 {5 w' _2 ?& d
# J0 [" o- l: @3 t- Completed=0xff;
: V- J$ i" S. m% n) d - }
复制代码 ( s! [- S# ^! Y! B S1 q
基于上面的配置和测试代码,我们就可以看到最终的结果了。定时器周期性地触发ADC,每得到3个ADC结果就进行数据处理,然后通过UART以DMA方式传输到串口终端。注意VBat电压是测量结果再乘以3得到的。
2 ~. N, z, S6 u6 X2 x0 A( I; ]7 h* [7 s2 B) a: \) ?- w
" ^9 O0 [6 H0 ^9 `* G: E
. {6 v) Y4 [8 d/ y
% J8 W% P5 e9 G/ [$ a, Y& T; s! [1 v7 `
针对上面的应用演示,最后给几点相关应用提醒:2 b, J3 I- X) \( O) @- [7 E s0 M
4 s: ?" C: b5 Q" D$ m
1、针对温度传感器做测量时,校准时使用的参考电压与实际应用不一致时要做换算,换算成相同参考电压的数据后再做计算。这点前面也提过了。
1 W( L6 ]1 M! m, H) `
$ W2 c* U* k# s/ F8 G3 c2、使用TIMER的TRGO触发ADC,如果选择类似比较事件、更新事件来触发ADC时,此时ADC对触发极性的选择是无效的,或者说ADC的转换仅依赖于触发事件时间点。如果是选择TIMER的Ocref信号作为触发源,此时ADC的硬件触发的极性选择是有效的,可以是上沿或下沿触发,甚至是双沿触发。这时就得根据需要选择合适的触发沿。【可以进一步阅读本公众号文章《STM32定时器触发ADC的时序话题》】
U# x9 [5 H, o& b9 |2 W8 x1 e6 O
: a8 D1 v; @% C. \ T3、这里使用UART的DMA传输依次显示三个结果于串口终端,三个启动UART DMA传输的函数须保留适当时间间隔,即等上次传输完成后再启动下一次传输,因为这里每次传输使用的是同一DMA通道。否则没法全部正常输出。比如若把上面3次UART DMA传输的代码改成下面这样子:
8 I: r8 s2 t; L6 V5 L. H
" S( d/ ^* V/ b4 ?- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));
% W, {; O, x) p( ? |7 l - ( R& t# ~4 N. }5 `7 e$ r
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));
! z! F+ z1 W) f, q: o- D - / L, g; n O( Z9 ]
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol))
复制代码
6 w& v3 j. Z( U这时输出结果会变成下面的情形,总是只能看到一个结果的输出,即第一次启动的DMA传输结果。7 z- k. A$ I* n; X! ?
7 l4 y( c$ X% J( X, w
/ j" W* A& C$ U0 v j8 K5 _
- f4 [3 K$ W$ s/ u7 J如果想省事点,直接在相邻2次DMA传输间加上合适延时也行。我这里根据DMA传输完成事件来决定执行下一次发送。在DMA传输完成中断里设置Completed变量为非0值表示当前一轮DMA传输完成。9 D& a* d1 N, m/ H* s
4 g) `$ G* I {) v, J
4、对于那些在中断和主程序里都会被访问的变量,记得将它们冠以volatile。. I+ E+ q5 V W) N. L
1 J$ m+ x5 K* Z. g下图的三路波形是我调试时辅助使用的。# Y+ u9 o- V8 K3 a
, G5 T' I3 z9 H2 u) L, d5 v
第一路表示计数器的计数变化,显然是单向向上计数模式。4 P; z) Q( C) d
( l! z2 z! G. T& ^) c( i2 `第二路是TIMER1通道1的PWM输出波形。! w. n7 ]% X: C5 d* [) x8 i
% X+ ^. g: u. ?) o第三路是我每次基于DMA实现UART发送时拉高拉低的波形。平常管脚电平为高,在实现DMA传输过程中拉低。
9 O& Q0 c& y3 s+ z N
( J5 B2 t" s) U; g4 N+ u$ Q+ t/ c! V: K: E$ P5 X5 H2 [
6 J1 Q8 M, k* l% V! T如有侵权请联系删除5 C! k$ | P. C1 G+ J# i
转载自: 茶话MCU6 K* Q* V4 f& q4 I
; N! W$ |1 ^, S, B: r
) e. L& _3 e- `7 h, |. f' o2 p7 d$ _# t Q" p9 s0 V
|