说明:驱动基于STm32G031K6测试,其他型号需自行做改动。
! `4 o$ t( V( u* j. i7 V3 I a* S2 ~6 |+ q6 L# Q
ADC的初始化:3 H1 ~4 c& e- E' u& ^) X
! t& G/ A- O4 {) l4 S& ~* V2 ~
- #define ADCIO1_IN_CHANNEL LL_ADC_CHANNEL_5
" b G1 k) X0 V! C) V: F - #define ADCIO2_IN_CHANNEL LL_ADC_CHANNEL_76 }6 Q2 G) s g) U9 P& U
- uint8_t STM32LLADC1Init(void)( l! B0 Z$ h- _( ]/ H
- {
+ \! p9 ?, B$ s) M( f - LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};# g2 H6 ?: t6 G. L
- LL_ADC_InitTypeDef ADC_InitStruct = {0};
6 L( z' T* t0 Q: @/ _7 Y. ^
1 j* n$ a+ N' A8 Q2 u* d- LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
" K5 b/ {! }/ P2 s
a- P) f! k0 w: P* B- /* Peripheral clock enable */$ Z- t4 @! s- o: ?
- LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC); //使能ADC时钟
% t( U. P# P6 @- [
6 T, P. j/ {9 W4 R9 ^- LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA); //谁能响应ADCioGpio时钟
+ @ m# m% c2 u+ o/ y4 K2 l+ } - /**ADC1 GPIO Configuration
0 T, G$ v7 X* d' {: ^1 Z, y1 H - PA5 ------> ADC1_IN5
& ?+ @( L9 d, K) Z) m i a# J) D - PA7 ------> ADC1_IN7 ) Z! }! G) [6 ?; [3 U1 @# Y% c
- */
+ K: |& P& v" k9 N4 U* a' x5 e( c! j - GPIO_InitStruct.Pin = gpioADCIO1_IN_PIN; //ADC检测IO初始化6 m2 ~/ e! V m* I* d' z6 z7 k* b
- GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
7 L8 o* D' E8 C; a$ M9 p3 C - GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
1 s; t$ B6 K% }" u* s( O1 i0 A - LL_GPIO_Init(GPIOA, &GPIO_InitStruct); z S7 a7 D' q
- # Z6 @: i4 O, F6 J
- GPIO_InitStruct.Pin = gpioADCIO2_IN_PIN;7 v2 L( b- Q9 D. u' E, N7 ?
- GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;. g2 L9 C' P* E. I
- GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;* r( H8 ]1 C0 O. B j
- LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
- e# k4 K2 H; |: H) r1 B r
- T3 m' t5 Q) ~6 C: W3 |( I
! s) b) [- y* H2 ~) Z0 @5 _- /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) & [' }! Q, L/ b) K
- */
) U7 l: }8 K1 r5 P, h; |4 n - ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE; //ADC定时转化触发条件来源,可以为软件触发或硬件(外部中断、定时器)触发。
7 O$ W+ e6 E+ b; h% R - ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_ENABLE_2RANKS; //ADC顺序采集通道的个数,根据自己需要的通道个数设置7 U' d d8 z+ z4 a
- ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE; //) p5 ?: v4 e8 r+ V2 \
- ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE; //采集方式,单次采集还是连续采集
* ?9 p* ~' u7 V5 H9 [$ F - ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED; //使能DMA,并使用无限传输,如果DMA保存方式为循环覆盖的话才可以使用无限传输。
7 \5 z6 m/ C- I8 {4 w0 J - ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN; //采集的数据循环覆盖模式( } B. N/ f" G9 l e+ \' K
- LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);2 S p- C/ j; M9 `/ Z
- LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_DISABLE); //失能过采样
+ R, w3 n5 {( K% f, I - LL_ADC_SetTriggerFrequencyMode(ADC1, LL_ADC_CLOCK_FREQ_MODE_HIGH); //采样时钟使用高频模式- Y: r' h, F2 }/ p1 B! ]3 a3 x" k
- LL_ADC_REG_SetSequencerConfigurable(ADC1, LL_ADC_REG_SEQ_CONFIGURABLE); //adc通道和硬件adc in是否强制对应,因为adc的io使用时不是连续的,所以这里使用可自定义配置8 L) u" S" E( {8 d
- LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_1, LL_ADC_SAMPLINGTIME_160CYCLES_5); //设置通道共用取样时间,根据需要自行选择
7 i) }/ e5 M" Y3 D$ O) w) t- R - LL_ADC_DisableIT_EOC(ADC1); //禁用通道采样结束中断
& {, N; p* J8 ]6 ^# A - LL_ADC_DisableIT_EOS(ADC1); //禁用序列采样结束中断,因为使用的是单次软件触发所以这里关闭这些中断
8 d6 }' G( A. u, V# X# H - ADC_InitStruct.Clock = LL_ADC_CLOCK_SYNC_PCLK_DIV2; //选择采样时钟来源
; W, X0 `) s& a* h9 ~ - ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B; //采样分辨率 O0 ]( W5 _/ }* @# L+ A5 U
- ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT; //采样数据对齐方式,右对齐高位补0,左对齐低位补0.+ Z. w, ^8 T7 M. [
- ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE; //低功耗模式,使用DMA的话无法使用,这里关闭1 \9 p& K3 Y' n9 `/ i: g
- LL_ADC_Init(ADC1, &ADC_InitStruct);
: ?- r ]( U; u - /** Configure Regular Channel
0 G2 Z7 N/ r' `. [3 q+ B - */
; h1 w a: H. ~8 I; A - //ADC_RegularChannelConfig
% @5 u) m- z3 P& B - LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_1, ADCIO1_IN_CHANNEL); //将硬件通道ADCIO1_IN_CHANNEL映射到ADC通道1' ^" o4 `/ a q- T' l2 ]
- LL_ADC_SetChannelSamplingTime(ADC1, ADCIO1_IN_CHANNEL, LL_ADC_SAMPLINGTIME_COMMON_1); //设置通道采样时间# F n% O1 {7 E! J3 c# }7 d' ?; d" Z
- LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_2, ADCIO2_IN_CHANNEL); //将硬件通道ADCIO1_IN_CHANNEL映射到ADC通道29 G3 }+ K; A% V/ B4 }3 K& x
- LL_ADC_SetChannelSamplingTime(ADC1, ADCIO2_IN_CHANNEL, LL_ADC_SAMPLINGTIME_COMMON_1); //设置通道采样时间
% J2 k6 a4 ~8 u. S: U - * B3 ?- h/ X! b$ h/ Z1 u
- /*采样数据转换时需要参考电压,参考电压可能是变化的,所以也有可能需要采集*/( u/ f. k- N, {( b
- //LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_4, LL_ADC_CHANNEL_VREFINT);9 ?* _+ g. Q* }, q- k( E* j' J; K
- //LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SAMPLINGTIME_COMMON_1);) {8 m* w6 ^' Z* b2 w; D
- return 1;/ `6 d" R: w. X( C) E: o' c
- }
复制代码 7 ~- p- r4 g/ E% @- v
激活并校准ADC:
6 V- x! {* e( O$ [% F- j# F" q' U9 u( F2 _0 u
- uint8_t STM32ActivateADC1(void)
/ O( a8 t9 @) T2 G0 D# b2 M2 x) M - {% V6 f0 }% l- q( l) @
- __IO uint32_t wait_loop_index = 0U;) ^9 @& a$ n/ M; l/ I
- __IO uint32_t backup_setting_adc_dma_transfer = 0U;8 I1 D: Q# F: K7 @6 t" I
- uint32_t Timeout = 0U; /* Variable used for timeout management */6 D2 O# Y# N h$ l
- /*## Operation on ADC hierarchical scope: ADC instance #####################*/
$ H5 C( L3 r* U; }9 O. Y( v7 C
3 O5 S- z! t0 i8 n5 n2 C i- /* Note: Hardware constraint (refer to description of the functions */
/ M4 g( R' \; C0 A; V* s) d - /* below): */( T& y, Q; a! G8 B( ? W
- /* On this STM32 serie, setting of these features is conditioned to */" @0 Z, A9 b7 ~! M! ]
- /* ADC state: */1 }4 Y; M' G, o1 t2 } z
- /* ADC must be disabled. */. s" Z, J& O- q! f( p
- /* Note: In this example, all these checks are not necessary but are */2 }! `7 y8 V8 C" \
- /* implemented anyway to show the best practice usages */
' e& H7 r/ E( j d - /* corresponding to reference manual procedure. */
5 d; V3 T/ i4 I - /* Software can be optimized by removing some of these checks, if */7 c* R, Q+ t9 |- u$ @' V# I
- /* they are not relevant considering previous settings and actions */
5 r. ]+ `0 }$ G2 z" z/ s - /* in user application. */
' ~: y! ~8 I- k' j - if (LL_ADC_IsEnabled(ADC1) == 0)! G' D/ A$ a# H6 V+ f0 @
- {' E- q7 Q! _, Q/ q" U4 k% I
- /* Enable ADC internal voltage regulator */
4 s4 v; F1 v2 H; P# K - LL_ADC_EnableInternalRegulator(ADC1);
1 l/ P- D, S; C6 I% j* y
2 q8 ]* ~) `4 S' q- /* Delay for ADC internal voltage regulator stabilization. */
! O& Z- h/ C2 ? - /* Compute number of CPU cycles to wait for, from delay in us. */
( C; }! v+ j! H+ v - /* Note: Variable divided by 2 to compensate partially */
S2 O1 I- {' n1 E5 i/ \2 x' q( N7 v c - /* CPU processing cycles (depends on compilation optimization). */
' q5 c5 P& c- B7 d _ - /* Note: If system core clock frequency is below 200kHz, wait time */7 q& ]/ `/ N3 p! a
- /* is only a few CPU processing cycles. */
v" j( m% B B0 `4 y - wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10);9 |: K0 |$ s, O$ q
- while(wait_loop_index != 0)+ N; c* I! {/ t2 k3 [- F; E, k
- {" e% `: _" i C; s- X# D' i- P- C
- wait_loop_index--;
8 q) |4 K! \- ~+ s, f4 y1 T' G( D. _ - }: f# o. e% \" B6 A+ T: S! R( k4 b# e
: J b; v6 S8 {( J- /* Disable ADC DMA transfer request during calibration */# q, x1 y; H( d' J5 B& j4 y
- /* Note: Specificity of this STM32 serie: Calibration factor is */( m- z& G# m% \' }! \* W
- /* available in data register and also transfered by DMA. */4 }" O! V. |- q, Q1 m! {
- /* To not insert ADC calibration factor among ADC conversion data */
5 V3 i j7 j- @4 e1 k3 i- D - /* in DMA destination address, DMA transfer must be disabled during */
( S6 U) C+ f0 @; W& u4 G9 Q7 W - /* calibration. */
0 \1 J2 j8 I9 ` - backup_setting_adc_dma_transfer = LL_ADC_REG_GetDMATransfer(ADC1);3 K, R9 L- x0 J7 j3 T( x
- LL_ADC_REG_SetDMATransfer(ADC1, LL_ADC_REG_DMA_TRANSFER_NONE);9 m0 [8 o# y7 R0 s5 _: u6 |& m' L% l
- h# b- [( s! m$ x- r! c- /* Run ADC self calibration */6 _- e+ Y9 [" S, g; A
- LL_ADC_StartCalibration(ADC1);
* S8 W7 |$ y! G/ \9 g. c/ c" Q m6 @5 C - $ ~# V1 h6 I% x- i, j) I
- /* Poll for ADC effectively calibrated */0 c4 P4 ^" p9 F1 H6 m3 q
- Timeout = ADC_CALIBRATION_TIMEOUT_MS;
2 [/ f3 t Z1 E' K9 c* a
* X0 W+ S4 ]2 g4 }9 m$ {. S0 [& i8 ?- while (LL_ADC_IsCalibrationOnGoing(ADC1) != 0)
8 e" V4 \, {$ h4 r; O- J, h; E - {+ A1 Y; r' h# u& b# \; |; i9 p
- /* Check Systick counter flag to decrement the time-out value */8 ~4 u5 ~/ ~" E V4 S4 {
- if (LL_SYSTICK_IsActiveCounterFlag())9 @: q! a0 ^# v5 F3 b" x( K J
- {: I2 W8 v( W5 C ]( n6 l% }- Z
- if(Timeout-- == 0)" e/ @* j6 w# B- l
- {( A( |' `7 Q; S( l
- return 0; f: }% |' v3 X% L
- }1 h4 C" X6 g5 z x5 {$ X
- }
) d2 V, B. Z# ~, l - }- K0 {" j! g. q6 v% r1 ?
' e$ u$ A9 Q( ]- /* Restore ADC DMA transfer request after calibration */
; n) v3 Y( D# d e$ e6 f* r - LL_ADC_REG_SetDMATransfer(ADC1, backup_setting_adc_dma_transfer);9 G. l6 H" V6 W" M$ E( v
- $ g z4 I6 V! I. A+ t
- /* Delay between ADC end of calibration and ADC enable. */$ O7 A: U) L: c2 G& [4 Z: H
- /* Note: Variable divided by 2 to compensate partially */
' w* m1 ^' r! d! g. D! t& y1 u$ H - /* CPU processing cycles (depends on compilation optimization). */( u# _! k* R# Y
- wait_loop_index = (ADC_DELAY_CALIB_ENABLE_CPU_CYCLES >> 1);2 i; C% G1 i+ w0 K. c: }
- while(wait_loop_index != 0)
4 f' Q1 e$ a+ y5 [7 @, s - {
' \7 x' {% ?) M* i( d6 S7 @6 A - wait_loop_index--;
; u; l' H# B3 U6 G6 O9 z - }% X- g$ ?1 e, Y; i( S% \& |0 C
- 2 z. ^' f; a& T
- /* Enable ADC */
2 N/ S1 j+ @! ]/ Z* V6 g" C - LL_ADC_Enable(ADC1);: h% X/ w5 [6 e% _8 p
+ ~4 b5 u1 E# {4 ]' ]. f- /* Poll for ADC ready to convert */
3 C z% T5 @% S1 t( g' K - Timeout = ADC_ENABLE_TIMEOUT_MS;
( `$ ^: L/ G9 z. y - - j: }6 M* O3 d3 ]8 I8 x
- while (LL_ADC_IsActiveFlag_ADRDY(ADC1) == 0)0 P, H1 |, N! V$ A( q: X
- {
) Y9 K* T' k. B0 e - /* Check Systick counter flag to decrement the time-out value */! W$ y5 Z$ q0 H/ T: e
- if (LL_SYSTICK_IsActiveCounterFlag())8 k8 b' ?% P" H* V! Y
- {
" y0 i/ S5 H7 ?6 o( f- j8 O5 _ - if(Timeout-- == 0)4 t% u( N0 K l. S
- {7 Q- @4 a% E" x& ]/ u( J. R1 Y
- return 0;! S4 c+ ^% Q! U- C7 d8 H0 A0 ^
- }
+ o! _3 Z4 B" C9 X$ Y* S8 v$ Y; v% I6 ^ - }+ w" H9 B5 u, M3 F/ p4 ?8 m: Q! z
- }3 C8 }. o; f9 N- e
- /* Note: ADC flag ADRDY is not cleared here to be able to check ADC */
. v/ M3 |* {# { - /* status afterwards. */
2 _" {' h% L8 B! n - /* This flag should be cleared at ADC Deactivation, before a new */3 |' w8 Z8 b5 E7 d
- /* ADC activation, using function "LL_ADC_ClearFlag_ADRDY()". */5 }( i" v4 X# T0 h7 h! }* F( P
- }3 X r( h9 ?9 ]- S5 V' w
- return 1;0 q3 z: J) o$ J( w
- }
复制代码 " J! L) Z1 s/ N" A
因为ADC配置的是软件触发的单次采样,所以还需要有手动开始和结束采样的函数。' j. z& x9 J- J- u
6 M+ f6 i5 N+ T7 Z使能ADC采样:
% A, o' j5 E/ n3 t% t
4 k* k( p5 `8 P$ Z4 T2 ~- uint8_t STM32ADC1SampleEnable(void)
' v0 P* G0 E' z- w) y7 v- w - {. E( N* [( ^1 Z0 q1 l
- uint32_t Timeout = ADC_ENABLE_TIMEOUT_MS; # V5 n' [% _' l8 {' Y8 D! N
- LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_4); //先使能dma再使能adc,以防采样数据丢失
1 n/ E+ c+ U& U) r6 ^6 t9 p) j) D - LL_ADC_Enable(ADC1);
" K. E2 w; }5 U, O3 u2 g% M - Timeout = ADC_ENABLE_TIMEOUT_MS;
7 y1 W% X: l% c$ Y. d - while(LL_ADC_IsActiveFlag_ADRDY(ADC1) != SET)
0 I" }3 @: s& f9 o% f6 [4 f - {
% _7 M9 @( q& u K/ ~1 y4 A* X& f - if(Timeout-- == 0)
+ c& `2 k! I# I- |4 @. i. ` - {! _+ f7 q( @+ v9 z
- LOG(1,"STM32ADC1SampleEnable ERROR!!!");
6 h- a, t& k3 {: y. K& v - return 0;
5 d9 S! n6 w8 |& p* l1 t - }( Q& f1 `, A' s3 w
- }7 Y3 ^+ y% g% {+ `0 E: w* E
- LL_ADC_REG_StartConversion(ADC1);0 @3 g* Q; v' k0 R3 K8 w
- return 1;
3 H+ [! ]8 t6 J. g/ q6 S9 X - }
复制代码 , u" \5 z) H, R B* U. f% y
失能ADC采样:
; _! ~2 V) Q. F" f9 M
% f9 T# |3 N% u0 k7 U- uint8_t STM32ADC1SampleDisable(void)
& `7 l& s! P0 U7 k c8 f$ O6 y - {7 w& d4 H% A; Y! C* {! t$ I) L
- uint32_t Timeout = ADC_DISABLE_TIMEOUT_MS; T, B# r8 W- C' @/ A
- LL_ADC_REG_StopConversion(ADC1);
9 L/ F; G) D; M0 K/ { - while(LL_ADC_REG_IsConversionOngoing(ADC1) != 0)
9 ^- V* m/ K: S) V* K - {
6 i+ S" j- l6 t* j2 p9 A6 u - if(Timeout-- == 0)3 _4 j4 ^% @$ B5 V7 O. ^
- {
6 A+ p+ |% }( ~ - LOG(1,"STM32ADC1SampleDisenable ERROR!!!");
4 h/ j2 z9 w1 A- b) {: G - return 0;( a4 _2 I9 [4 r4 b1 [, z+ j8 q8 f' @
- }2 f# Z4 ` c; d+ J; K5 a/ r) ]
- }6 K3 _* z- q T5 ^5 T1 @
- LL_ADC_Disable(ADC1); //先关闭adc再断开dma,以防采样数据丢失4 w) H0 _. e# T' q- N8 n
- LL_DMA_DisableChannel(DMA1,LL_DMA_CHANNEL_4);
+ N. G+ h, }7 T5 N) {. g" h - return 1;
0 f1 P. }6 f: @5 |! _7 h - }
复制代码 : }. `6 ^7 p% [: l( v( ]* N6 w
进行ADC数据采集流程(采用多次采样求平均值的方式):
" w. h$ T: P; _ u+ V" X$ m. a8 t; v) r/ A1 k8 W/ w4 P
1、当需要开始采样前,调用一次STM32ADC1SampleEnable()函数;( ^8 n& |: G, U$ [
0 k$ m( B# ^- Y9 U1 f/ b3 p
2、在一个定时函数内调用LL_ADC_REG_StartConversion(ADC1)进行数据的转换(因为使用的是单次采样,所以每次进入定时函数时都需要调用),取出DMA数据进行累加;
+ V: ~3 J) r Z4 J9 |+ s6 c: A- r2 Y4 Z" M) q7 h
3、达到需要的采样次数之后,对得到的数据进行运算,并调用STM32ADC1SampleDisable()函数关闭ADC通道。+ Q7 e+ H, n6 |* V! O, y
/ v. q0 c4 ^; @3 [以上方式适用于需要间隔一段时间获取一次ADC数据时使用,例如需要每隔1分钟获取电池电压时,可以进行1分钟倒计时,然后连续进行十次间隔10ms的ADC采样求平均值。
% e2 R2 F$ K# ~8 Y1 ~/ {& B
& d) g$ }7 ^) f; B: X4 c" S$ W
# B: K3 R/ C4 K6 P* n8 z- I |