5 c8 m: Z0 W j5 t
8 F' g6 r; w" ~9 G: I8 ~6 ?7 e
5 o( Y+ V7 @/ X9 i& @很多STM32芯片里往往内置了专用的ADC通道,比方用来测量Vrefint,VBAT的分压或温度传感器的输出电压信号。不同系列所内置的模拟信号通道可能有差异。这里以STM32G4系列为例,它内置了对应于Vrefint,VBAT的三分之一分压和温度传感器的输出电压的专用模拟通道。
- W9 H5 a1 ~& I8 ~# I1 f2 T' f3 ] g0 A4 L1 G8 v# O
4 @& p; J( F4 v7 l9 u+ K& o* N
5 j& u8 O7 c% N* s' P+ @* T) O5 K t# F" _% P2 L/ B
1 l' p$ P' h$ n @) I: J) l下面的示例就是针对上述3个通道进行ADC,并测量相关电压和片内温度,最终得到3个结果,分别是VRefint电压,VBAT的电压,片内温度。
! E' h# } z7 Q
- [* h( \# d; d; x$ `" ` n! k; f4 p/ M1 o
: ~. l8 T6 C1 T8 `" P3 z1 Q实现过程是这样的,大体分四步:【有点点麻雀虽小五脏俱全的味道图片】 a z# W" n: ^1 I$ d' U; J3 R6 E
) Y! R, U6 F$ U7 c2 c' t1、TIMER1 更新事件触发ADC的转换;; ^5 [! i4 a) c2 k2 {$ l, l# d
) H6 {: d* j2 f9 x2、CPU基于EOC中断获取ADC结果;
4 J0 _0 [- F' e1 \% g% `
2 S8 T3 I( [1 k' u- a4 |3、对ADC结果进行换算,得到电压值和温度值存放在特定内存位置;
! s N1 O0 a7 @( { m$ y$ I2 M: F# @) L7 z2 \! _" F
4、基于DMA传输通过UART将最终结果在串口终端显示;
# g3 Z5 Z( C- ?$ ^+ W
+ D/ S5 T8 n, i1 R3 E0 R" `: I其中,TIMER1的CH1输出PWM波形,其更新事件做ADC的转换启动信号。每次的TIMER更新事件触发ADC,3个通道扫描方式转换。这里的UART使用片内LPUART,使用它主要是考虑它跟板载虚拟串口直接相连,没有其它特别用意。
" S; G% i+ W. X/ d" r) Z- D e& X: @) l# X
6 ?. x4 o1 W: w& a# N8 @( B5 n
/ D; L; K. {+ }# K5 ^我使用STM32G474Nucleo板来进行下面实验。其中VDD=3.3v,VBAT与VDD相连。另外,ADC模块的参考电压也是3.3v.
5 l# a* v' O6 M! E% j/ N* c1 T/ C0 T+ \5 r6 y9 K
使用CubeMx图形化工具进行配置,先看TIMER配置:* {5 X' e e% m1 g3 m# _
/ M' C$ ^1 h2 l2 W. p5 y
8 M8 h z1 ^- n) y1 k
1 O. @) u: V2 q1 o0 h8 s( ?, X
再看看ADC的基本配置:- d v$ Z+ u- ?6 v6 P/ Q" c$ l/ {
/ z5 H. h2 t5 b$ C
8 z9 [5 A' o4 N3 P0 B- Y7 X! `* z4 ~; l
LPUART的基本配置:
# N* Y& z. U. e! M* L( g8 P7 k+ r- r7 i a. W3 g
- u' q- O2 B& M3 o
* ^! {- R3 ^0 Z U# p1 g! d
因为要使用ADC中断和UART的DMA传输,记得做ADC的中断响应使能配置和LPUART的DMA配置,这里只使用UART的TX DMA功能。 S- F0 P: g" ?* F* I8 r/ p
' t/ G8 E" C: K0 P/ f/ u
" J) N8 l4 i) e' Z+ y
' F6 n- M' I4 ^
使用CubeMx主要配置主要是上面这些。# |6 p/ {' R0 l G0 ^) C
, @4 q; I3 T4 }0 I% E在组织用户代码前,先简单介绍下片内温度传感器的内容。该温度传感器针对不同温度有不同电压输出,其输出电压跟温度呈线性关系。ST公司针对片内温度传感器在两个特定温度【30℃和110℃或30℃和130℃】、基于特定参考电压【3v或3.3v,不同系列以数据手册为准】生成了1组校准值并存放于片内特定FLASH位置。6 z( a* ~( U, }) o
. P \' l1 [' {& e9 T( {* O# ^STM32G4系列的校准值是在参考电压为3v,30℃和110℃条件下的两个值,在数据手册里还给出了校准值的片内存放地址。
6 Q. ^, p9 X6 R+ E0 q7 H D3 V9 u/ J
1 l4 T. j8 e8 _# l7 u" u, A
7 @; ?, z: {" Q5 k9 } y
+ ], v6 C% k1 m1 ?( ~. E( R/ l针对这个温度传感器的使用,ST公司在参考手册里还给出了计算公式。其实,有无这个公式无所谓,我们不难自行推理出来。【TS_DATA代表某时刻测得的传感器输出电压对应的转换值,TS_CAL1/TS_CAL2分别表示在30℃和110℃条件下基于传感器输出电压的转换值。】
w! y! U3 E( q9 S( n" Q/ ~9 l' P5 ~0 T6 F5 B
; B, F! Q" E" L8 L' l7 @) l8 H% `( Y# ^' X+ `
另外,前面提过,ST公司在手册里给出了温度传感器的两个温度下的校准值,但要注意生成校准值的ADC模块所用参考电压跟我们实际应用时AD模块所用的参考基准电压可能不一致。如果不一致,就必须将ADC值换算成同一基准参考电压条件下的数据。目前在ST手册里也特别强调这点了。我把上面一副图再贴一遍于此【见黄色语句提醒】。: G. C( h, `4 {9 M
3 W6 E5 f9 c- x9 e, L# @
1 o Q) U% D' L- e
+ R) P% B& X# E1 j8 d
关于这点,我们也不难理解。同一待测信号、同一ADC模块在不同基准参考电压下转换值往往是不一样的。见下面示意图加以理解。' |# }3 |! D8 l0 [
5 S4 P: J8 ~3 _ ?+ O0 G
. E& ?9 h! H7 w. `2 j% J5 Q$ [8 R) X- e6 D; e
完成各项配置后,创建软件工程。添加必需的用户代码:6 N& [/ N3 i5 }9 Y5 p& f4 w( U
$ o* q) x% X; o5 D s9 e6 S
- #define TX_Timeout (9999)8 t6 l4 }' t# v; ]
- 0 t6 y1 {- r/ `/ K# w4 H4 x
- #define TS_CAL1_ADDR (0x1FFF75A8) //用于计算温度传感器数据% ?) i6 H, Z* W1 o4 z
- #define TS_CAL2_ADDR (0x1FFF75CA) //用于计算温度传感器数据
: `; z; d& |- T9 G$ K: [ - ' P0 ?; M3 n: x7 _. v5 B5 W
- #define size1 (40)
# ^/ R2 t. h% z
$ n& |+ T. ~& C8 i- char WDVol[size1],BatVol[size1],InVol[size1];
+ C# T! j9 Z* x( o: ] - " k0 j0 `* p! X) u# O
- uint16_t ts_c30,ts_c110;
' H* R2 n5 N7 @ n/ e$ o0 [# Z - ' |7 y' [3 i& D: L
- uint16_t ADCResult[3],convCNT;0 G! ]$ `. P! ?9 H) @8 x4 g
" C3 p1 A( z( A/ L6 {$ i0 b- volatile uint32_t Completed,EndofCon_Flag; Y l- i: d& n" q
- 8 E* h9 U+ r( N
- float VBATVolt;//存放BBAT电压最终结果/ l# N# y# s; g$ j! `* A
- float VRefint; //存放Vrefint电压最终结果7 q2 p0 O. P6 r9 I! Y
- float Temperature;//存放片内温度℃最终结果) ^- L( ^3 l! l* _) \ s
- ! {2 _: S1 S+ v' q( v
- int main(void)
6 W6 D+ N. S9 x% l! d, K. O - {
$ c1 b/ r: |% z - /* USER CODE BEGIN 1 */1 P! }9 m8 Q& y$ Z) K
( _% ]4 w: K5 a1 K- /* USER CODE END 1 */( a3 ?% f+ q* Y
- 9 L6 @! O( O9 a/ e
- /* MCU Configuration--------------------------------------------------------*/3 L6 P: i% H# k4 {+ d. p
- 0 c8 s m* E d5 T- @+ J2 q
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */2 M! ^; T! }4 p2 h
- HAL_Init();
5 ]* P3 G1 J+ n% x7 j5 P! E; S
9 h, `- h& J, Q6 a w% d! V" r- /* USER CODE BEGIN Init */
& h: \3 u: p% i9 ]& S1 K
5 w( y7 v, S1 Y! E6 Z: @; t& R- /* USER CODE END Init */
: R: {' x5 L& |2 L - 2 M- O) u9 {! j `: o( z4 W
- /* Configure the system clock */
0 r/ S; @4 |9 E0 k, N3 H5 W0 r - SystemClock_Config();7 R) Z" D) U+ Y: W$ r
- " j! Q* @0 |0 G: v
- /* USER CODE BEGIN SysInit */
}# B+ l/ i# ~3 p( J$ ~' \0 _ - - X" I$ O4 W; o* b" R k6 _- X% g
- /* USER CODE END SysInit */* R P# _5 j' V" g' I
; I& t$ V; w( {; p' K0 m2 W1 e- /* Initialize all configured peripherals */
( I, K% v$ x6 @+ i3 n - MX_GPIO_Init();; \/ U) R! ^9 B0 C4 ~+ S
- MX_DMA_Init();
0 G5 R2 W7 Q: ?' ^7 V+ [. \' n - MX_ADC1_Init();
# \3 e) {9 H7 z5 F0 \. q - MX_LPUART1_UART_Init();8 g3 K; U/ f9 I) { ]2 o2 Z \0 H
- MX_TIM1_Init();/ _. c0 r$ | x& m. P* s
- /* USER CODE BEGIN 2 */
; }( J! E2 [6 g- k" e4 v& j
$ q6 z: ?. S% p! C. d0 o6 N, Q- ts_c30 = *(uint16_t *)(TS_CAL1_ADDR); //读取30℃时的ADC校准值
`+ s; ]& A* e0 p0 w' ^4 w( } - % p0 X% r/ Z0 l, G) m; D4 P. ^
- ts_c110 = *(uint16_t *)(TS_CAL2_ADDR);//读取110℃时的ADC校准值
2 _$ e2 ?* i c' @( ~* ?
$ g9 q t2 y2 J& j0 a/ Z9 \9 l- HAL_ADCEx_Calibration_Start(&hadc1 , ADC_SINGLE_ENDED);//ADC校准7 L d" Z: @2 V, B7 x6 i) y4 U
3 ?2 p$ x7 o5 l- c- HAL_ADC_Start_IT(&hadc1);//启动ADC并开启转换中断
! I/ |! b! X/ B% z - + t" X* C% z% B
- HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
9 g* ]# w5 B: _ B' |
! v( q9 v2 D" u4 @1 U- ) O& S8 x( l; J. u: J% t; v
- /* USER CODE END 2 */& H9 T' v7 M4 g+ d1 K
( P7 N- |2 L5 g: e- /* Infinite loop */6 o2 ^5 F0 z8 ~, Y3 Y8 U8 V
- /* USER CODE BEGIN WHILE */
) C) O2 Z d3 k5 ~' w5 P T - while (1)
7 n( d4 F- T& u$ ~% V# ~ - {
& i3 ^! I' E: E2 K: \0 U9 `. b - /* USER CODE END WHILE */6 K( {) H( ^1 O* ~, F! z. P8 ^
- 9 S* Q$ x( p( N+ t% c4 V; r" e
- /* USER CODE BEGIN 3 */1 L5 s `; d4 w( m; D) W
- : g; r4 l- X' T7 W2 `
- if (EndofCon_Flag!=0), M: n+ b/ `( p9 T; z
- {
+ y# ?# d0 r- w3 s" E3 [ - VBATVolt=(ADCResult[0]/4095.)* 3.3 * 3.; m! R$ y* G6 O4 W
- & S6 k* n- ]$ Z. `( _
- VRefint=(ADCResult[1]/4095.) * 3.3;
5 t5 Q5 C y/ O8 S2 H0 ?) K' r ^2 a - 1 g5 V5 y- a M2 Z
- Temperature = 30.+ (88.*(ADCResult[2]-((ts_c30/1.1))))/(ts_c110 - ts_c30);
# @7 ?( Z, L5 h7 Z5 a/ }' e! a' [
. ]* B+ a! v, Q) }+ W2 q- EndofCon_Flag=0;
2 n) Q* p( f0 o+ X' ^( W
/ n' l* T* c4 m% c/ w- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol), TX_Timeout);
- e' I9 M! u6 ?) n" M
1 K$ f2 B7 X6 w# O- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_RESET); //for auxiliary test( ^9 N; f! ?3 m3 x+ }
% f) r6 |' V- \! ^: E7 o- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));0 r8 Q; v" A! G/ P: S
- - f5 {& d* S& N X
- while(Completed==0){}
3 p/ a! G C8 ~" \ - Completed =0;
3 h6 v4 l; w h( j) c# [ - # S5 B8 g1 e: D. p& }6 B( R. {
- 5 v! t( S: `9 b7 q( H
- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol), TX_Timeout);
+ I* k% {8 h' F, B - HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));
0 }4 B5 C' g" \+ ~ - 7 s# z7 U9 ~( h' I
- while(Completed==0) {}1 K8 D6 d0 Q+ k* [5 u
- Completed =0;1 ?; j8 T" {- F5 i8 c& k n* @) v8 U
- 7 g* M1 d9 Z) m' C0 X1 I
2 ^) N- O9 c5 E2 K# s/ ^7 M3 v- //HAL_UART_Transmit(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol), TX_Timeout);
P: n6 A; ^' x2 J& X/ l - HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol));
* }( b7 P. b9 w# [0 Y - ) N- X" D6 S0 c+ ]$ D4 q9 L
- while(Completed==0){}
; E: q% _4 n' f3 P% N - Completed =0;
) H( A' U" D0 J: R/ Z" C - 9 H% M y; V8 M2 K) S
- HAL_GPIO_WritePin( GPIOC,GPIO_PIN_3,GPIO_PIN_SET); //for auxiliary test
' \; j# ]' i8 r4 q) V8 p
4 h3 g! M* R. I0 p$ t- }
) H- Q5 T% Z; h8 K0 v5 d, \ - " l: v+ e* _0 }$ H! I$ _
- }
7 u5 e7 S. ~& J& ?% i( x - /* USER CODE END 3 */
. B7 l5 [" y b/ n7 P - }
" I! R- q- N* j+ ]( ^ - 9 b3 f0 F, f1 Y! ~
- //ADC EOC 中断回调处理函数1 j1 G- n8 {' V6 l- @) ~& x
- void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
+ g9 ~- O8 Q1 g; l( k# y( t) k - {
V0 B5 R, p( p. | - 2 a5 P# s% Q' Z9 `9 l* X, Z/ q: D7 v+ F
- ADCResult[convCNT]=HAL_ADC_GetValue(&hadc1); //获取转换结果并存入数组2 w1 k5 @( Q9 w# A
- & @, ^- [3 J: j
- convCNT++;5 d, q+ H. Y2 I8 u
- ! C3 p0 L9 U# y
- if(convCNT==3) ) `! U( v+ \, n& X7 S
& C- d, m/ j! e7 w- {+ x! c2 N' {* \) C, R
- convCNT=0;4 g9 l3 i, K! ]& {! S. d/ ]8 M% V1 p
# q8 a+ y Y% j( o; U* s8 Y, b- EndofCon_Flag=0xff;
) D: s r/ \+ K! C8 B6 R; `
4 M. z5 X' H4 D6 f- sprintf(WDVol,"Internal PN Temperature: %5.3f \r\n",Temperature);
% M+ k# N4 i* O, m6 A" U" u! K
4 c) ~1 }) s0 o! W( |, _6 J8 T- sprintf(InVol,"Internal Reference Volt: %5.3f \r\n",VRefint);
: D, K8 P0 M0 M Q; A0 {2 d' y - * m0 ^' X# L" r% ?
- sprintf(BatVol,"Current Battery Volt: %5.3f \r\n\r\n",VBATVolt);
" X5 \8 o2 u, n( @- w0 t
$ s- g/ i z5 F, i- }! m1 E5 `8 r( \0 b
1 Q _+ a$ f% y' S; N% _- }
4 E K& H9 R# b' f+ }- i8 L3 D$ y
- x3 Q$ M+ [3 v- //UART DMA 传输完成中断回调函数
! q& x& O' d, y/ L - * z% E8 w* y- X5 m. I% ^! d/ ?
- void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); K T. D& h A: K' K
- {
3 }9 Y) Z2 A& p# w5 U
! s3 W$ y( ?% }) ?: \- V- Completed=0xff;9 u3 E( d1 Y E+ X8 y& h
- }
复制代码
8 p5 R) \! Y* u: Y% e# O! ]/ r基于上面的配置和测试代码,我们就可以看到最终的结果了。定时器周期性地触发ADC,每得到3个ADC结果就进行数据处理,然后通过UART以DMA方式传输到串口终端。注意VBat电压是测量结果再乘以3得到的。
/ q. u: _& T- K2 W% B% M! q
4 r; \$ r- d/ s8 F: ~
& R' N7 @1 @5 `* f2 N
3 e- S) W* o* J& X$ ]* f+ V5 n& A D5 H" g H
/ z b0 D+ n! m# T1 r) T- P: v
针对上面的应用演示,最后给几点相关应用提醒:
% }4 U+ f* r1 P- G, W
: s1 Q1 ~! d7 U3 ?, C* v/ V; n1、针对温度传感器做测量时,校准时使用的参考电压与实际应用不一致时要做换算,换算成相同参考电压的数据后再做计算。这点前面也提过了。4 @5 J" x7 g' T/ V# @ |2 ~
3 p0 v4 y: [- o8 u$ N6 U2、使用TIMER的TRGO触发ADC,如果选择类似比较事件、更新事件来触发ADC时,此时ADC对触发极性的选择是无效的,或者说ADC的转换仅依赖于触发事件时间点。如果是选择TIMER的Ocref信号作为触发源,此时ADC的硬件触发的极性选择是有效的,可以是上沿或下沿触发,甚至是双沿触发。这时就得根据需要选择合适的触发沿。【可以进一步阅读本公众号文章《STM32定时器触发ADC的时序话题》】: A! Z) k3 O! x$ ?1 n
J% ^/ W- g0 r1 M$ o3、这里使用UART的DMA传输依次显示三个结果于串口终端,三个启动UART DMA传输的函数须保留适当时间间隔,即等上次传输完成后再启动下一次传输,因为这里每次传输使用的是同一DMA通道。否则没法全部正常输出。比如若把上面3次UART DMA传输的代码改成下面这样子:/ A p$ P! T) E
g- m" C! ?- v9 [- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)WDVol ,sizeof(WDVol));
& @: W/ M b# \8 Q - ( f5 I6 x- d) _2 \) K- P) e
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)InVol ,sizeof(InVol));
2 Q/ q( T8 w9 p, i0 O1 c$ T - " F: _ D! p. R* D5 o
- HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)BatVol ,sizeof(BatVol))
复制代码
% j( e5 T! J$ x# W$ U, Z4 R这时输出结果会变成下面的情形,总是只能看到一个结果的输出,即第一次启动的DMA传输结果。% F6 {3 _1 e5 G. p# l' Y
: a5 _. ~) O9 x; t8 m5 E$ y. p5 s6 U8 U8 m
5 L# g, ?; n& x s e6 _$ E如果想省事点,直接在相邻2次DMA传输间加上合适延时也行。我这里根据DMA传输完成事件来决定执行下一次发送。在DMA传输完成中断里设置Completed变量为非0值表示当前一轮DMA传输完成。3 {) o6 f' @9 k) a
* q2 M% w! `! P4、对于那些在中断和主程序里都会被访问的变量,记得将它们冠以volatile。
( L: _0 ^+ q5 P/ I+ p$ _0 m2 w
& O7 s7 l A; k9 y0 x下图的三路波形是我调试时辅助使用的。5 t& [) M5 A1 e7 f7 a D4 S1 _
q- [/ U2 ~& e! {0 t( p第一路表示计数器的计数变化,显然是单向向上计数模式。
. u5 N8 T6 M, K
: B( J% t0 a( K: K4 P第二路是TIMER1通道1的PWM输出波形。
9 Q" X& S3 G5 @& [/ k: Q' z) u" d, a y0 {6 @% W9 }" u
第三路是我每次基于DMA实现UART发送时拉高拉低的波形。平常管脚电平为高,在实现DMA传输过程中拉低。. |, a5 A1 R, B& m8 R& G1 E
$ a/ ^, I7 r7 K3 }! h
4 d% ^3 A( D k( Q) t: s, P, H* V8 V
如有侵权请联系删除
/ l8 _: {" y' A& ^" R转载自: 茶话MCU
+ s0 \5 K2 \. U$ Q+ [
1 i& v& N6 z2 ^ $ T( q8 M7 V/ G
- r; b) H% k M1 t$ y7 }
|