说明:驱动基于STm32G031K6测试,其他型号需自行做改动。2 ]3 s9 z$ y$ W5 e4 S1 S7 [7 L
* N# W; k8 N2 m7 V0 p7 G( h5 FADC的初始化:2 ]: n* }7 M ^$ r
9 J+ T6 O/ n0 Z" z7 z- #define ADCIO1_IN_CHANNEL LL_ADC_CHANNEL_51 O1 S3 s2 |8 K8 ^/ F* `- ^; O
- #define ADCIO2_IN_CHANNEL LL_ADC_CHANNEL_7* q: r8 T1 K4 b/ b' W
- uint8_t STM32LLADC1Init(void)
5 } f1 e, _- n9 {* `! p - {
, u2 P( P8 u$ y* D - LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};( c! U/ |) g- {$ G# x( U
- LL_ADC_InitTypeDef ADC_InitStruct = {0};! ~2 x- S- Q* X1 _. K1 o
- ) F# G) q; W, S& ^
- LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
2 `, k/ F- \. ?( m7 I - * o, p& n7 g* `8 {4 A. t
- /* Peripheral clock enable */- e$ E! x% g. F. c: p1 y7 i
- LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC); //使能ADC时钟
) d3 V) Z: { `+ ~! G7 E
5 E; B2 j" X$ Z8 z2 g- LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA); //谁能响应ADCioGpio时钟
7 `; K2 |* u8 ~! ]& V! ^ - /**ADC1 GPIO Configuration
0 @& f) E+ l) r( n7 x9 ^3 U2 q - PA5 ------> ADC1_IN5
( @" m( [1 x& w. b - PA7 ------> ADC1_IN7 7 d7 d5 V5 Z3 C
- */
, ~$ h6 F9 B, n4 p9 W - GPIO_InitStruct.Pin = gpioADCIO1_IN_PIN; //ADC检测IO初始化
. K# N! l: v) _% x" b8 I - GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;, A6 a# C% {) J
- GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; v$ e# s" m- K9 Y# l
- LL_GPIO_Init(GPIOA, &GPIO_InitStruct); n0 I$ @* y: Y# V5 r/ k' K) S
7 C( |& M/ c9 t0 G2 r& {% i- GPIO_InitStruct.Pin = gpioADCIO2_IN_PIN;8 B7 g8 h8 F. Q9 x- l
- GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;* e0 l) t, ]2 W
- GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
4 O: J' {4 v8 n: P - LL_GPIO_Init(GPIOA, &GPIO_InitStruct);9 e% p# g$ V3 T7 m$ W+ M
- " }/ K) R! t' Z. S8 e- J( Z7 B1 R5 T
8 t" w: U; `( ~2 V- /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
2 T& l& q/ {; H9 h2 k- Z; k - */7 F6 T+ Z! [' Q- w
- ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE; //ADC定时转化触发条件来源,可以为软件触发或硬件(外部中断、定时器)触发。5 ^2 V5 e; H8 _
- ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_ENABLE_2RANKS; //ADC顺序采集通道的个数,根据自己需要的通道个数设置* `! m' r$ j% H: B/ r
- ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE; //
) ]# U/ Q6 I& p! {* U - ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE; //采集方式,单次采集还是连续采集
. Q9 U9 J; K: h1 K# k - ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED; //使能DMA,并使用无限传输,如果DMA保存方式为循环覆盖的话才可以使用无限传输。: u1 S2 Y2 b+ e* S2 o
- ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN; //采集的数据循环覆盖模式 m2 J* \; l" K V' ^9 g
- LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);8 c) a) w, w' r+ G; i
- LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_DISABLE); //失能过采样
. m4 a# ^4 R& Z0 |! `! @1 F, ` - LL_ADC_SetTriggerFrequencyMode(ADC1, LL_ADC_CLOCK_FREQ_MODE_HIGH); //采样时钟使用高频模式
- {# o7 e, I% e - LL_ADC_REG_SetSequencerConfigurable(ADC1, LL_ADC_REG_SEQ_CONFIGURABLE); //adc通道和硬件adc in是否强制对应,因为adc的io使用时不是连续的,所以这里使用可自定义配置+ ^0 E0 @$ n$ F8 ?- t# ~& d
- LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_1, LL_ADC_SAMPLINGTIME_160CYCLES_5); //设置通道共用取样时间,根据需要自行选择" \& J7 t6 ~8 c- w4 L8 P; J
- LL_ADC_DisableIT_EOC(ADC1); //禁用通道采样结束中断
5 e" V" D% z; ~1 J6 `' c - LL_ADC_DisableIT_EOS(ADC1); //禁用序列采样结束中断,因为使用的是单次软件触发所以这里关闭这些中断& c/ Z: s9 i. P7 ~8 {
- ADC_InitStruct.Clock = LL_ADC_CLOCK_SYNC_PCLK_DIV2; //选择采样时钟来源* F7 l6 {0 c& w3 ? k. \5 e
- ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B; //采样分辨率& h$ G6 c; `* u j( p5 `- S
- ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT; //采样数据对齐方式,右对齐高位补0,左对齐低位补0., F' q% T% `" U
- ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE; //低功耗模式,使用DMA的话无法使用,这里关闭
& K1 o u1 A) B% ~, z, S5 {% v( @5 X - LL_ADC_Init(ADC1, &ADC_InitStruct);
& G& N- _2 W5 \ - /** Configure Regular Channel , b z6 ]. t* r2 a6 s; S4 M5 ?
- */
# Q. Q. x+ R0 H" _ - //ADC_RegularChannelConfig7 Z( h5 l3 B. K$ Q( h
- LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_1, ADCIO1_IN_CHANNEL); //将硬件通道ADCIO1_IN_CHANNEL映射到ADC通道12 `3 n9 R( ?7 D4 }! r; z
- LL_ADC_SetChannelSamplingTime(ADC1, ADCIO1_IN_CHANNEL, LL_ADC_SAMPLINGTIME_COMMON_1); //设置通道采样时间: c/ Q, `# |3 T+ w! h/ m5 w( z9 c
- LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_2, ADCIO2_IN_CHANNEL); //将硬件通道ADCIO1_IN_CHANNEL映射到ADC通道2
) B; N4 E ^. ^0 a9 [0 i0 s/ C1 @ - LL_ADC_SetChannelSamplingTime(ADC1, ADCIO2_IN_CHANNEL, LL_ADC_SAMPLINGTIME_COMMON_1); //设置通道采样时间- u. t, x. n0 C; y
; M: b% s3 A5 @1 w- /*采样数据转换时需要参考电压,参考电压可能是变化的,所以也有可能需要采集*/) k2 p# U! J& I2 n" `. M
- //LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_4, LL_ADC_CHANNEL_VREFINT);
+ q4 E6 x: x" b+ P - //LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SAMPLINGTIME_COMMON_1);
% n, C9 o3 X5 E: f - return 1;
@+ i$ M% O1 K6 I v4 c - }
复制代码
" n5 o0 k7 [7 }5 |激活并校准ADC:
8 j- t/ |$ Z9 u$ N3 G
( x5 P: |- r# V5 {8 d$ e- uint8_t STM32ActivateADC1(void)
) J4 D" ?* `' t& }" h5 s - {) r! z+ z# _/ r E- B2 w! U
- __IO uint32_t wait_loop_index = 0U;9 ?# Z6 S' ]2 N# E4 p! ~
- __IO uint32_t backup_setting_adc_dma_transfer = 0U;) a8 X. P! q; g" _! S* r4 S
- uint32_t Timeout = 0U; /* Variable used for timeout management */! t4 s' y1 Q' v: }5 n( l2 i9 n4 D' O
- /*## Operation on ADC hierarchical scope: ADC instance #####################*/
2 {5 H" ?: h+ ~' ^* ~7 ]- ]6 W - 4 L0 z% ^# a: {! ~: a9 u
- /* Note: Hardware constraint (refer to description of the functions */+ b9 |0 a, E# j; C9 `( K- e0 f% m9 h
- /* below): */; J2 p) r9 \ C/ K3 B( C/ Q
- /* On this STM32 serie, setting of these features is conditioned to */+ |; B& A1 S; q1 L
- /* ADC state: */+ H+ [ G" |" ?+ W, Q7 ^' b
- /* ADC must be disabled. */$ O+ u9 D0 c7 |- K6 h8 n
- /* Note: In this example, all these checks are not necessary but are */
4 I$ f0 K+ i3 a/ j) _ - /* implemented anyway to show the best practice usages */
/ v4 J6 i5 P; j, X7 [# }0 n - /* corresponding to reference manual procedure. */: ^( e! D q8 f. `: h; k% K
- /* Software can be optimized by removing some of these checks, if */7 C; M0 V$ a2 }" o
- /* they are not relevant considering previous settings and actions */1 p) e! y5 @" M J% [' ?4 o7 h
- /* in user application. */
) H( ~" L0 B- t* R1 t: q - if (LL_ADC_IsEnabled(ADC1) == 0)7 u" q8 m$ y( k+ I" n3 T
- {/ R6 I! G W0 O. f! b
- /* Enable ADC internal voltage regulator */) A9 c# R9 n# z
- LL_ADC_EnableInternalRegulator(ADC1);
8 M1 v6 D9 d1 ~8 U8 e2 `1 i - & p/ E% Z: |9 M+ x( I8 B0 u
- /* Delay for ADC internal voltage regulator stabilization. */! ^$ Z' b) y$ Y3 v+ b
- /* Compute number of CPU cycles to wait for, from delay in us. */$ C) z* g4 h2 u& Z
- /* Note: Variable divided by 2 to compensate partially */1 Q' `& P9 }; S" e5 e
- /* CPU processing cycles (depends on compilation optimization). */
5 ?1 T) Z; H4 m/ P' D4 G& a - /* Note: If system core clock frequency is below 200kHz, wait time */
x; U& P9 R/ }& I - /* is only a few CPU processing cycles. */
& n8 B4 |' b" i% e6 r6 ^! Z( V - wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10);0 H) S' K( t% X0 n9 A( P
- while(wait_loop_index != 0)1 D+ E8 X" c- c& T m9 P2 C- e
- {
# Y7 x. w( ]4 V6 c# X4 H - wait_loop_index--;
$ w% W p5 w {* O* i: F - }! u9 g2 Y5 R7 A1 O/ ?( U4 C+ H! ^
Z' ~5 m0 N$ k- /* Disable ADC DMA transfer request during calibration */, c" i1 @2 L- A& P
- /* Note: Specificity of this STM32 serie: Calibration factor is */& W1 B* S% j* H. R+ h$ H
- /* available in data register and also transfered by DMA. */
" z3 P; B$ a& F9 G& U - /* To not insert ADC calibration factor among ADC conversion data */
; O) P0 k, K, t; [1 V! Y' m - /* in DMA destination address, DMA transfer must be disabled during */7 _4 A: A" H# a8 P) k3 I- z: k
- /* calibration. */7 ^7 ^7 Q9 j8 O) c0 Z; E$ k
- backup_setting_adc_dma_transfer = LL_ADC_REG_GetDMATransfer(ADC1);. L6 J" s+ B1 w* ~
- LL_ADC_REG_SetDMATransfer(ADC1, LL_ADC_REG_DMA_TRANSFER_NONE);
( a5 @9 c. }5 z- A. l) o" _1 ^0 V4 v9 a
& s, b! w6 M$ g1 t2 F- /* Run ADC self calibration */
5 z" s6 J- z4 l4 a9 d' g3 f - LL_ADC_StartCalibration(ADC1);
8 `4 o8 Q8 e- {5 M! y! L9 O: H
4 G% \; p5 k& ^+ {7 R0 I0 M2 N, {- /* Poll for ADC effectively calibrated */
; `& C7 v2 q2 y9 a - Timeout = ADC_CALIBRATION_TIMEOUT_MS;$ r( T( y" O9 G4 G) d; |
- ) A# \3 x. \( g8 b+ K% t
- while (LL_ADC_IsCalibrationOnGoing(ADC1) != 0)6 d2 i# E! |6 k0 A% N' B* A
- {
. e8 r1 O" J( u( F8 r' { - /* Check Systick counter flag to decrement the time-out value */0 `3 W5 v) A" M% O+ d
- if (LL_SYSTICK_IsActiveCounterFlag())- ~0 {! B! n- H1 @1 q+ M
- {
8 v* C: m7 m; ?" Y( |# U2 { - if(Timeout-- == 0)
( u7 T8 D& T+ P: w/ A( v5 w - {
: j2 z6 L# f" [ - return 0;
( l* J& X" d/ G+ [* A - }
8 ~5 {/ v! d% y# A7 e s% Z0 Z - }
, Y; ?. k9 \0 A - }
0 C( c, ?2 Q- c9 H% w
& ^0 M* V7 g7 V6 G b$ w( C6 G+ y0 N- /* Restore ADC DMA transfer request after calibration */
' I; j& I- c3 w& p( Q4 u - LL_ADC_REG_SetDMATransfer(ADC1, backup_setting_adc_dma_transfer);
: r- X/ d: ?, k; K
9 W/ x4 p" ]) H6 S- J# R- /* Delay between ADC end of calibration and ADC enable. */& d: m8 [+ h& n$ q
- /* Note: Variable divided by 2 to compensate partially */
$ t }) D4 [( l: K7 g8 `4 L - /* CPU processing cycles (depends on compilation optimization). */
5 x J, H& L: A7 w, b3 P! V - wait_loop_index = (ADC_DELAY_CALIB_ENABLE_CPU_CYCLES >> 1);8 X p. K/ I& S8 u a/ M1 F4 A$ Q
- while(wait_loop_index != 0)
0 W6 O. M$ L: K+ Q$ R) Z. [ - {
i: r/ W+ I+ M0 Q - wait_loop_index--;
7 [, ~; G2 ~4 a: e. Z' W$ m; ^ - }; X( F. v" y7 b1 [, m- X
- ' ^4 i& T6 x: d1 q$ d& d7 n
- /* Enable ADC */' r, X+ U }5 }8 `4 Y0 ?
- LL_ADC_Enable(ADC1);4 L% J7 g' B1 P% T7 r0 T% ^
; _ k4 K- W3 ~ b" V2 p4 r- /* Poll for ADC ready to convert */
2 w, ^; o) J. ] - Timeout = ADC_ENABLE_TIMEOUT_MS;
( M/ [5 i) j- p( |- K - 4 |2 w/ l) Q# w+ \: O% y i% K0 Q
- while (LL_ADC_IsActiveFlag_ADRDY(ADC1) == 0)
$ N1 m6 e" l) ? - {7 x, f0 t5 a( S% {
- /* Check Systick counter flag to decrement the time-out value */
9 }4 b. o" x z6 u - if (LL_SYSTICK_IsActiveCounterFlag())
# h6 m9 {$ R( @5 f - {: i$ \3 H+ U* f9 {, f0 R
- if(Timeout-- == 0)
/ p* _, _, ^* S: ]/ j( T - {. g- d1 I9 ^6 G0 K
- return 0;3 f- j4 @+ h# g
- }
. Y# F) A2 J9 c4 I2 o3 u: b1 P U - }
2 c9 S0 D8 r& q5 s- C- u7 E - }
4 X: o" o, T, u- b - /* Note: ADC flag ADRDY is not cleared here to be able to check ADC */
5 w5 a9 B z' T$ y. U. C - /* status afterwards. */5 i+ g0 s' ^6 j1 @" d* K
- /* This flag should be cleared at ADC Deactivation, before a new */
. ]+ d9 V4 I3 {$ n+ t - /* ADC activation, using function "LL_ADC_ClearFlag_ADRDY()". */3 ~5 W& l! t ^5 ~* @5 T, M1 r" f
- }
c. _& t: G* J" F; I4 i - return 1;
% O# |6 ^* @& E! a" \+ m. D - }
复制代码 # H& v- H' y: O* m' v5 L3 F% K
因为ADC配置的是软件触发的单次采样,所以还需要有手动开始和结束采样的函数。
$ t: c! ~- ]/ l1 I- w# `# K; y0 E3 s+ x: e1 {% l( e
使能ADC采样:
' D0 R0 g+ U* ]$ j; c9 `6 {- I; n/ d
+ r8 z* R, _4 r L0 d3 [. a4 u- uint8_t STM32ADC1SampleEnable(void)
( w: ~" v1 v: G1 E" C - {1 {9 W. H1 J% ~
- uint32_t Timeout = ADC_ENABLE_TIMEOUT_MS;
: ~4 z: B2 Y3 z0 m - LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_4); //先使能dma再使能adc,以防采样数据丢失
7 v& s: `& |3 R& I2 ]7 {- _ - LL_ADC_Enable(ADC1);
4 z+ G7 M; P1 N8 k) ` ~% A& i5 b - Timeout = ADC_ENABLE_TIMEOUT_MS;
* e* G+ i2 j; C - while(LL_ADC_IsActiveFlag_ADRDY(ADC1) != SET)# e& H" g( w9 b& z3 ~+ V
- {# q% }- K0 q: L3 F9 Q) c& t
- if(Timeout-- == 0)1 I J7 F; Y) a1 y+ I2 T! ^+ g* i" y
- {- k! M) K* w, p B8 }
- LOG(1,"STM32ADC1SampleEnable ERROR!!!");7 ^ p/ _' t2 u& T9 s8 ^
- return 0;
( m9 _& P3 J( h! P1 @# Y - }' U: z0 h) c, }, \
- }, x' u h _# E* k! W! |
- LL_ADC_REG_StartConversion(ADC1);
# I1 ~- O( D: P" z& L, W* z - return 1;+ i. p0 V& b" L$ w! ]
- }
复制代码
W5 W6 K* ], b8 q, M" Q0 q失能ADC采样:# Z7 }' O3 Q+ f% \
! ^6 {5 W. ` z+ q- uint8_t STM32ADC1SampleDisable(void); f- G+ U3 k! [: |5 \
- {6 R6 M( k+ r, J1 k7 k3 ]0 R
- uint32_t Timeout = ADC_DISABLE_TIMEOUT_MS; ( _7 O9 F( q3 b
- LL_ADC_REG_StopConversion(ADC1);8 I7 s1 U. R _
- while(LL_ADC_REG_IsConversionOngoing(ADC1) != 0); u% a2 x; S1 s
- {0 H6 ?9 t/ f" v' q* `
- if(Timeout-- == 0)- H; ]/ G* V5 N# v
- {) h8 J' I; x* _ R S. l
- LOG(1,"STM32ADC1SampleDisenable ERROR!!!");
. y# p3 w; F% Q' `/ H/ ?+ }- a3 b4 [ - return 0;
: u2 T% D4 p& I6 r - }
/ V8 C4 v1 O! W$ G3 q - }9 b4 Y0 l5 n5 Y3 f1 _* S: ^
- LL_ADC_Disable(ADC1); //先关闭adc再断开dma,以防采样数据丢失. k6 S% Q0 G+ b5 z: i* v7 e
- LL_DMA_DisableChannel(DMA1,LL_DMA_CHANNEL_4);* g- C3 E* q* F$ H5 p
- return 1;
* X! [6 u. \' u H6 [1 k# N - }
复制代码
- @+ w1 i% @/ S( K/ B; j+ `* P6 s; l进行ADC数据采集流程(采用多次采样求平均值的方式):
7 h1 I' |, g6 s+ ~: j0 g% D; i2 S p3 w, K5 }* n
1、当需要开始采样前,调用一次STM32ADC1SampleEnable()函数; o) O7 T) s) V! V0 D
2 H) v% ]; l0 |. `' L
2、在一个定时函数内调用LL_ADC_REG_StartConversion(ADC1)进行数据的转换(因为使用的是单次采样,所以每次进入定时函数时都需要调用),取出DMA数据进行累加;
" a( L; u. d' {4 _, q& ]- i5 i/ x) K3 U$ @
3、达到需要的采样次数之后,对得到的数据进行运算,并调用STM32ADC1SampleDisable()函数关闭ADC通道。( A# E$ E) k+ C% n; E. F
; H9 c2 E( M. D8 F' |以上方式适用于需要间隔一段时间获取一次ADC数据时使用,例如需要每隔1分钟获取电池电压时,可以进行1分钟倒计时,然后连续进行十次间隔10ms的ADC采样求平均值。9 h- N% n& L( _ n* `$ n. b
* U! V) X+ D& r7 d/ d% D; U$ I3 y& x7 k2 j; f4 L5 J( D3 J
|