4 a3 U- T; ]: u$ y
7 e" o8 \" Z7 a2 X) i/ x
9 f9 n& x3 a6 Z1 B2 K很多STM32芯片里往往内置了专用的ADC通道,比方用来测量Vrefint,VBAT的分压或温度传感器的输出电压信号。不同系列所内置的模拟信号通道可能有差异。这里以STM32G4系列为例,它内置了对应于Vrefint,VBAT的三分之一分压和温度传感器的输出电压的专用模拟通道。
: X- f5 l& b2 G, u }# u
p/ o* M4 A; i! n- z7 H. V. @7 N2 s# a1 @
& c7 ~) _1 a9 R# O3 Z4 M
- G4 `9 _' b( `2 t9 }2 z* d! _* Y5 |, G4 \& Z2 N6 C) A! ?$ v1 X
下面的示例就是针对上述3个通道进行ADC,并测量相关电压和片内温度,最终得到3个结果,分别是VRefint电压,VBAT的电压,片内温度。
9 X5 T Z( U+ n ]
3 U% ~" Y7 j# a7 U. R8 s* x4 t& \- g: k3 j# ?3 Y: U" V
: X. `: u) P2 u7 L; j5 c
实现过程是这样的,大体分四步:【有点点麻雀虽小五脏俱全的味道图片】5 ?0 m+ P- W) b
. ]; z; v- s& W' I0 e1、TIMER1 更新事件触发ADC的转换;
: r7 K8 E& m2 y" q3 n
) j9 m2 ~, G& D6 E! k7 D2、CPU基于EOC中断获取ADC结果;
1 W z* a, Y; D3 k v
& g- M) Z' E9 }* ]3、对ADC结果进行换算,得到电压值和温度值存放在特定内存位置;( X. |" X/ J8 V. S. q2 P
" `' _9 r8 v# y' X
4、基于DMA传输通过UART将最终结果在串口终端显示;
4 W* U. b% S U8 q4 p: A
/ w+ @% o3 A9 ^& V其中,TIMER1的CH1输出PWM波形,其更新事件做ADC的转换启动信号。每次的TIMER更新事件触发ADC,3个通道扫描方式转换。这里的UART使用片内LPUART,使用它主要是考虑它跟板载虚拟串口直接相连,没有其它特别用意。
L" Q* A: a& g5 k
9 _3 k' B* d' @/ r: s, V- L/ V% l, C' U1 Y
6 L" m& i7 c3 q
我使用STM32G474Nucleo板来进行下面实验。其中VDD=3.3v,VBAT与VDD相连。另外,ADC模块的参考电压也是3.3v.' h) w5 d. z8 {2 O1 _$ ?
$ l+ t3 {' b; @! f- ^* D) ~/ N使用CubeMx图形化工具进行配置,先看TIMER配置:+ a0 t7 ~3 u, Y* `4 I
' D) D$ p+ y% Q2 Q# V& |
& \! w: ]( S) d6 @ f) ?+ I* ]
D [# N, ^) E5 s( Y再看看ADC的基本配置:
8 h+ ^- j; |. N' `, W& f" X4 [* B
, g3 n3 l& E `! X- N8 \
( h0 n' j# ^* }1 g8 R% a* q* l6 `' `5 x! B
LPUART的基本配置:
$ ]6 E( z3 B, W; k2 Q
; V- ]% t% q. R$ C& O. o
' ], l- N1 N% Q7 }6 C7 F, e5 P+ v6 A8 b0 `! F
因为要使用ADC中断和UART的DMA传输,记得做ADC的中断响应使能配置和LPUART的DMA配置,这里只使用UART的TX DMA功能。
" v. m( x5 G' a' {0 c5 d, @- u f$ ~. ~) w& ?! |' z
9 @* m; ~$ c [) r8 L3 H
1 l; U: w' T' @! @使用CubeMx主要配置主要是上面这些。
0 d( e8 I3 q1 S. K0 N. z! ^5 p& ]* @$ D- `
在组织用户代码前,先简单介绍下片内温度传感器的内容。该温度传感器针对不同温度有不同电压输出,其输出电压跟温度呈线性关系。ST公司针对片内温度传感器在两个特定温度【30℃和110℃或30℃和130℃】、基于特定参考电压【3v或3.3v,不同系列以数据手册为准】生成了1组校准值并存放于片内特定FLASH位置。# v7 c; ^8 }& [5 m
/ R9 ^$ A8 z+ m; B3 r6 B& m* z
STM32G4系列的校准值是在参考电压为3v,30℃和110℃条件下的两个值,在数据手册里还给出了校准值的片内存放地址。
% ~% f U- J" u2 f$ f {+ g; P
1 r1 G+ Q, }: x( q H' E* u3 F1 w2 v0 B; c
. f* J" ~- L* ?7 B2 {9 Y针对这个温度传感器的使用,ST公司在参考手册里还给出了计算公式。其实,有无这个公式无所谓,我们不难自行推理出来。【TS_DATA代表某时刻测得的传感器输出电压对应的转换值,TS_CAL1/TS_CAL2分别表示在30℃和110℃条件下基于传感器输出电压的转换值。】
) `( Q0 f- Y K3 M* ?1 ]- U1 c& Z+ ?: }. ]' a% c7 t D6 M1 }7 i
0 r5 P- d( w& Z8 c6 p5 i& j" j
- r: |* ]1 B9 I, {0 v另外,前面提过,ST公司在手册里给出了温度传感器的两个温度下的校准值,但要注意生成校准值的ADC模块所用参考电压跟我们实际应用时AD模块所用的参考基准电压可能不一致。如果不一致,就必须将ADC值换算成同一基准参考电压条件下的数据。目前在ST手册里也特别强调这点了。我把上面一副图再贴一遍于此【见黄色语句提醒】。. h' w# U6 M5 A5 g) ^6 P7 _ B
[# I! Q- C7 K0 `; x8 O( U
+ V" I7 B8 t( q( C+ W6 k
1 B6 T: ^5 N) {# S+ w
关于这点,我们也不难理解。同一待测信号、同一ADC模块在不同基准参考电压下转换值往往是不一样的。见下面示意图加以理解。
- p& n4 m4 n$ B- m# ]/ C4 c& ?6 D8 {( b5 V) {4 W
5 b9 X5 |. p. z. V6 {
|3 {+ s- v: y& E
完成各项配置后,创建软件工程。添加必需的用户代码:
1 I1 K( ~; N4 h
' P/ \* `9 J! h0 p/ k) C- #define TX_Timeout (9999) ^0 x' k2 e, O* s- w! r& o3 p
- ' h9 h4 d: s) @ l2 t% R
- #define TS_CAL1_ADDR (0x1FFF75A8) //用于计算温度传感器数据8 E" g! V: i9 S5 a* k
- #define TS_CAL2_ADDR (0x1FFF75CA) //用于计算温度传感器数据; h+ q0 b- c1 [7 `* [
0 l+ s- M t, b6 o" g; k& I8 H/ n- #define size1 (40)6 h( k H6 y" \4 ~
5 X$ O6 N m3 }( o' M- char WDVol[size1],BatVol[size1],InVol[size1];+ _8 O4 D0 W3 T$ K) e3 T( E
; P, J( c$ u! D4 b0 L2 P- uint16_t ts_c30,ts_c110;* k$ h1 t; ^9 W6 O7 f1 G
) z6 A: `$ h. Q: T* x- uint16_t ADCResult[3],convCNT;% D9 M! ? L! i- s" O
8 E+ G0 g4 d* B5 n6 E# p: {; R- volatile uint32_t Completed,EndofCon_Flag;
- g$ s! N4 l+ R k
' s) T9 ~6 r) G0 ?* o' z. r) t9 d2 j- float VBATVolt;//存放BBAT电压最终结果
1 d P0 m' E4 \, n3 K - float VRefint; //存放Vrefint电压最终结果. [; m& w( M0 V! g9 t" N
- float Temperature;//存放片内温度℃最终结果) u7 G1 b6 ^) y6 l2 X# s h
8 e; W5 O- B8 r* F4 S; `7 \$ f- int main(void)2 A" E4 q3 f/ Q7 C5 m
- {
' w& \& u( @4 Z" k& ?( ]) o - /* USER CODE BEGIN 1 */+ q% [ [& D' n W1 j
- $ V) Z: l& O2 x" ~: Q: f3 b
- /* USER CODE END 1 */
6 s2 Y. J1 r; V Z" m3 ]! Y4 j
% {* b5 a; C$ W& m- /* MCU Configuration--------------------------------------------------------*/2 W, V, ?2 N1 c. t
$ Q2 ^. T$ S, W- l k- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
0 o4 _: r" ~7 _- L" Y {6 s - HAL_Init();: P. A. f2 W4 d9 ]" E5 T
- . A/ x! I) K/ }" k" K
- /* USER CODE BEGIN Init */) ^' n% E' I! y: k i! p2 O
8 I+ Z4 k, W) k F& D; g- /* USER CODE END Init */
6 Z; H+ I( |9 z% F1 o+ s' x& d# |( \ - * [! @% P# F4 h( x0 I
- /* Configure the system clock */; G% P/ z- {: V! j7 v
- SystemClock_Config();8 W6 @1 V, ^- L, q! p6 J$ ~
- $ e. a% \2 P: G! c' m8 l
- /* USER CODE BEGIN SysInit */
, P9 x( R- y: s0 Q- }) E" y
2 s1 U& m6 O0 T4 R4 Z$ G) u8 ]- /* USER CODE END SysInit */4 O- J0 R6 U% g6 ]5 u( O" v# g
- S" H/ y! `; G2 g: N7 v/ o1 s
- /* Initialize all configured peripherals */
) }5 x7 m* S$ _% e. y - MX_GPIO_Init();6 o) ~4 n, P+ n: |) y) F
- MX_DMA_Init();
9 g' K" v2 O+ M6 H! ] - MX_ADC1_Init();
/ \/ k/ ^# i* R9 M - MX_LPUART1_UART_Init();3 g8 r* H R+ q; `
- MX_TIM1_Init();5 Z+ W5 G6 ]2 c$ {
- /* USER CODE BEGIN 2 */
W, o. E" w8 F! P! D - / w) X+ |4 j* `% R( K A6 ]" B/ k9 x
- ts_c30 = *(uint16_t *)(TS_CAL1_ADDR); //读取30℃时的ADC校准值- {2 C# C+ s" i; @0 A& d6 v
4 J) G- X# n# X& R8 k- ts_c110 = *(uint16_t *)(TS_CAL2_ADDR);//读取110℃时的ADC校准值
6 Q, d5 e& \3 c4 d( K# S8 b0 z" Z
9 k8 k+ G2 g4 x- V& X! R- HAL_ADCEx_Calibration_Start(&hadc1 , ADC_SINGLE_ENDED);//ADC校准2 p: k6 O$ U5 J' A
4 Z3 S4 v, ?6 {9 S- HAL_ADC_Start_IT(&hadc1);//启动ADC并开启转换中断5 @4 J" @7 j, n6 K
" V' T# n2 i6 P( f- HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);, Y7 W6 H& b9 c5 ?2 O
- * m& F& B* B$ _, f9 V2 m- Y
% D, f E$ R) D- ]$ N; x: m9 [- /* USER CODE END 2 */- Q& S3 }8 K) I8 x
- 4 h$ o7 J+ T7 w0 U& J
- /* Infinite loop */
% |) n s7 Z/ E. L6 W. H3 ? - /* USER CODE BEGIN WHILE */! r) B/ J0 I4 H3 g3 K
- while (1)$ ~3 l( ^- d# k# Z$ D2 S0 `7 p
- {
2 x& s( o5 s: \' Q& ~+ I - /* USER CODE END WHILE */
% X8 i+ @) i9 U$ }+ I5 G- a - . I, {4 J% h( t( v' X* ~, L
- /* USER CODE BEGIN 3 */" B: H+ a( h( _8 H
* b9 {0 [0 n# \3 f5 e8 M' G0 L2 d- if (EndofCon_Flag!=0) w, F, F% b3 A; p5 B8 Z
- {
( Z$ y2 ^9 J' x% v Y9 A - VBATVolt=(ADCResult[0]/4095.)* 3.3 * 3.;
) v. h7 ^1 h) j4 Z
, K+ v/ ^! F, {- VRefint=(ADCResult[1]/4095.) * 3.3; ; ~7 ~/ ~! }) p) {9 W
- 3 C5 F; f( c) f0 D
- Temperature = 30.+ (88.*(ADCResult[2]-((ts_c30/1.1))))/(ts_c110 - ts_c30);9 c* N& }' K# U4 ?3 C
- ( t& `) P# c; Z# E! w" L
- EndofCon_Flag=0;
! D& ^/ l [5 D' @% t( c( |
7 a, _/ W! m0 H$ J' n' ^- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol), TX_Timeout);
5 n( L& h8 d" X9 m: w7 N" y - - T0 i' y9 y9 h
- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_RESET); //for auxiliary test( Z% z9 J" ^1 ^* {
, O P0 Y, p; ~- ]( g6 V0 }- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));
% K% r" [9 R A# d+ X+ j' f - 5 G8 B0 k; J3 o+ ~7 ~( q
- while(Completed==0){}. J4 H" k7 X7 Y; |* i+ Y1 h
- Completed =0;
X/ j/ h0 l) e/ F$ u1 C- ^
7 C* G" Q8 C7 w* @
3 V0 n* b% j& A3 W1 C' z( c- X5 t- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol), TX_Timeout);& C0 z k6 L8 `( u
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));
9 ~* g0 ~6 t" y
! ^ j3 S/ O% _- while(Completed==0) {}- U8 c( r3 W/ s/ e' {
- Completed =0;
3 I9 i/ n9 V* Z, y7 k& a
7 i. s+ I, F* r4 c+ l
5 G E: {' d' |( e- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol), TX_Timeout);
5 Y! l* S% [$ V) @& g" v, \ o( W - HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol));
3 R! R+ N V% p1 [6 B+ ~4 Y$ t - $ l2 j9 E9 _+ g+ t" T3 R" R
- while(Completed==0){}0 J G! h8 O+ @! F4 X9 |5 m
- Completed =0;
! Q/ ~" \6 G: T4 P
6 e( y5 W* ^, L, c- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_SET); //for auxiliary test
; b4 P$ Q( U" B/ U3 f - ; ?8 F- |% N2 e" C) T/ S! `
- }
4 ]2 d! h: z$ U) F/ y
# @4 k& N2 u& B1 S9 E7 @0 X! c- }" `3 ]+ e/ b2 T `% q, }
- /* USER CODE END 3 */8 h' S3 ^: X3 G6 w0 Y5 l7 F8 |
- }
8 e/ Y) ^- b s2 ` z5 w6 P8 y. \ - * y" H! v1 Y: d. ^6 v
- //ADC EOC 中断回调处理函数; A! P( r4 E) R9 W
- void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
) v+ ?+ q% M8 H! T# _5 u% w+ ` - {) |+ n/ l( v/ }7 t! D
- ( A- B3 f: b/ n; s6 B
- ADCResult[convCNT]=HAL_ADC_GetValue(&hadc1); //获取转换结果并存入数组; p, C5 z" b# |; Z5 J e+ g
- , s7 h: @3 \2 [; i, r& e' D
- convCNT++;4 K, f9 u# l$ B$ Y1 | h0 _" Q4 Z
) ?. y" l8 z" V: J3 I; e3 [- if(convCNT==3) 9 F3 u( N* j% I: d
$ f$ l( q# J" j6 {0 r- {
. J8 g( V9 j; r* {' a - convCNT=0;
0 U& J5 c B6 z6 \) g$ T5 `$ n2 ^
, r" ^7 C5 F. |# O- EndofCon_Flag=0xff;
0 ]7 c( ]5 u5 Q: d0 W+ ?) i
' t7 z/ c' Y5 E5 K/ [# e. R y- sprintf(WDVol,"Internal PN Temperature: %5.3f \r\n",Temperature);
, [5 M- G( M1 r - / k' R6 \& J3 S, R/ E
- sprintf(InVol,"Internal Reference Volt: %5.3f \r\n",VRefint); 5 e4 o% `6 z/ D+ e1 R. s
- ' `1 w6 l+ E0 Q q( U# ]( h
- sprintf(BatVol,"Current Battery Volt: %5.3f \r\n\r\n",VBATVolt);
6 [( ^9 u( b* w% s* J% h5 c3 H, u" v/ \
5 I; y7 S7 x u$ _+ Y( Y0 `- }
, Z" f C( S0 B3 L
6 O% b8 p/ C& e* y! K0 s# |2 L- }
, f4 O1 k, R$ z- R ^
k- H2 T. m8 O$ i6 X0 F- //UART DMA 传输完成中断回调函数
- V: z, s) S* ^- c6 B
8 w; |7 }5 [1 I- ?% ~) u0 }0 M- void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)! @0 a$ A3 O. Q0 \
- {# Y( X- P) c+ b
- * ]7 z, o& j- O) x: M
- Completed=0xff;0 ^0 _) O# y% y7 c2 [+ h: P' {" x0 O
- }
复制代码 ! P/ {+ O7 w+ g$ i' f0 _2 Q
基于上面的配置和测试代码,我们就可以看到最终的结果了。定时器周期性地触发ADC,每得到3个ADC结果就进行数据处理,然后通过UART以DMA方式传输到串口终端。注意VBat电压是测量结果再乘以3得到的。, r0 @( K, L0 A) J9 |
2 _2 O+ j4 ^3 }; ^% J6 x4 `: y! f& V' ]. A& r! {$ e& J
+ Z6 S: o" @# ]
' q( U( W4 W9 V3 `
! i" | C1 V* q! B
针对上面的应用演示,最后给几点相关应用提醒: J. a, d8 Y& r3 o- r
5 Z) s6 r; M' L0 E4 m- {, _1、针对温度传感器做测量时,校准时使用的参考电压与实际应用不一致时要做换算,换算成相同参考电压的数据后再做计算。这点前面也提过了。1 n* V) W9 Q) s' z, \: W4 H" @
* U# e' I: x! n& ?
2、使用TIMER的TRGO触发ADC,如果选择类似比较事件、更新事件来触发ADC时,此时ADC对触发极性的选择是无效的,或者说ADC的转换仅依赖于触发事件时间点。如果是选择TIMER的Ocref信号作为触发源,此时ADC的硬件触发的极性选择是有效的,可以是上沿或下沿触发,甚至是双沿触发。这时就得根据需要选择合适的触发沿。【可以进一步阅读本公众号文章《STM32定时器触发ADC的时序话题》】. Y* w& v0 b' J9 j# u8 b
, y( B0 O, T2 [- y1 B: o
3、这里使用UART的DMA传输依次显示三个结果于串口终端,三个启动UART DMA传输的函数须保留适当时间间隔,即等上次传输完成后再启动下一次传输,因为这里每次传输使用的是同一DMA通道。否则没法全部正常输出。比如若把上面3次UART DMA传输的代码改成下面这样子:, X, o( |0 \+ w' O, k* C, U( e
9 X# c: p/ l2 m/ |4 Q1 g+ \- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));! z/ [, i# |$ J! Z1 W# l
- ; j+ n/ ^+ z; g8 O
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));
! }7 \. n5 r) N# } - 5 q" U. V. K0 S* x9 f& m6 b9 f1 a
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol))
复制代码
$ g* j2 H0 w$ S; Q; P/ @: I这时输出结果会变成下面的情形,总是只能看到一个结果的输出,即第一次启动的DMA传输结果。/ u1 G8 N& [# F0 o* y
6 r4 c! X: \' m
" E- W. y+ j6 m
! B# p& ~4 m# `: D# M! Z
如果想省事点,直接在相邻2次DMA传输间加上合适延时也行。我这里根据DMA传输完成事件来决定执行下一次发送。在DMA传输完成中断里设置Completed变量为非0值表示当前一轮DMA传输完成。% U3 U* ^5 F: W: u0 p2 R
9 ^: L$ B& }; p% ]/ k4、对于那些在中断和主程序里都会被访问的变量,记得将它们冠以volatile。
7 c& @7 ~! r3 w/ m h6 R
$ t( h7 a3 ^* d0 Q. \下图的三路波形是我调试时辅助使用的。& H9 ^9 D: F$ U5 d& X2 x% \" i
5 ^# M1 Q K q! t$ o2 [
第一路表示计数器的计数变化,显然是单向向上计数模式。% ]$ H/ e8 G# e- Q
% \) Z& x' B2 C' O9 U4 F4 n W" O
第二路是TIMER1通道1的PWM输出波形。+ F2 O, W( {9 }" e# D) p% I9 U! B
8 ^* ~, t) {, f& V" i第三路是我每次基于DMA实现UART发送时拉高拉低的波形。平常管脚电平为高,在实现DMA传输过程中拉低。
7 o w( n3 h6 M3 l
: h6 J, B7 d# j" L/ {
, j g1 n- Q$ c9 i. S1 }* F6 ]* t, p+ r. j# q; l: c' Y
如有侵权请联系删除6 _4 C$ W/ V* v- t, u9 x
转载自: 茶话MCU
8 V4 T2 S; C. H% |& |- T j) p' p( J* k+ ?" l' F
, I6 S- Q8 S- q
' t8 s$ P0 k* J. e, {( ^0 x$ E# `
|