说明:驱动基于STm32G031K6测试,其他型号需自行做改动。
5 c; Y! p/ C& ^2 V) J; c/ n4 h c$ P3 ~. R8 d& ]
ADC的初始化:
- V! u+ f9 I( d: j: |$ b5 _% K$ n8 h* D8 l
- #define ADCIO1_IN_CHANNEL LL_ADC_CHANNEL_5
& _! w! J, \9 P8 M - #define ADCIO2_IN_CHANNEL LL_ADC_CHANNEL_7
' I; e' i) X) k: n1 X) @1 {& B# e - uint8_t STM32LLADC1Init(void)# S# L6 d' l1 l5 Q* H, @, r
- {
5 s' M$ L# t# X$ ~0 v9 }. E - LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};
, `6 G) i% d# O: [ - LL_ADC_InitTypeDef ADC_InitStruct = {0};) f$ C' J1 W# p* Z* m' o
- % t; E( x+ T' n# ?& q* c
- LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
! F- l0 \! L% `; c- l1 R( ]6 g- E
3 \3 C/ N, J! u1 W" K6 Q. ?0 D- /* Peripheral clock enable */3 N5 W7 ]( n5 d
- LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC); //使能ADC时钟
# Y9 Y3 o6 L- o! l - + k2 \9 X' H: P4 ?/ H% ^$ U
- LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA); //谁能响应ADCioGpio时钟
4 B& n9 f: @" Y' j2 U5 ]0 R7 K2 l - /**ADC1 GPIO Configuration * K0 Q, c1 W1 g( d0 W$ @5 u# O" B8 v) Q
- PA5 ------> ADC1_IN5+ U3 v# P, S' ^6 v
- PA7 ------> ADC1_IN7
( t6 y% V$ n; g! g7 k - */
' w2 a; t+ J) x' _) O7 @ - GPIO_InitStruct.Pin = gpioADCIO1_IN_PIN; //ADC检测IO初始化' M7 N' |3 ~" x$ }0 U8 r4 S4 M" g
- GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
0 L. T1 K% L H4 Q) x$ I5 Z/ G - GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;! x2 h, O1 H3 p0 V- U
- LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
% `0 s. P' ?+ e$ ]1 l0 ~ - 2 X7 R$ p* D' F9 ]7 U
- GPIO_InitStruct.Pin = gpioADCIO2_IN_PIN;* \% {0 Q6 D- i; q
- GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;* j1 n% [+ i/ \. O) w( D0 |* Z
- GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
Y; {* V, R+ f A+ D* M+ `& { - LL_GPIO_Init(GPIOA, &GPIO_InitStruct);4 f; l6 r0 V0 v" P
. L" L' m+ j2 B& B$ u; b
% } S U8 u% R/ `$ x- /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) Y ]( N' d, W' K2 X8 U$ d
- */
3 w H! `7 c; W7 U& H& [ - ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE; //ADC定时转化触发条件来源,可以为软件触发或硬件(外部中断、定时器)触发。6 n6 X+ i$ f7 B/ d3 \8 h& b: O5 v
- ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_ENABLE_2RANKS; //ADC顺序采集通道的个数,根据自己需要的通道个数设置
s3 q# Y: f$ @, b - ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE; //3 d. a' V. k: ^7 z3 e2 B
- ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE; //采集方式,单次采集还是连续采集
$ X" E2 h8 ?2 ^( C, |& U - ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED; //使能DMA,并使用无限传输,如果DMA保存方式为循环覆盖的话才可以使用无限传输。
8 S' l0 A8 K5 G( d, e; {# E - ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN; //采集的数据循环覆盖模式
6 P' [ u4 k, w; ] - LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);8 T) I7 \7 v6 J& A" G3 v
- LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_DISABLE); //失能过采样, [. A9 _. D. p( A. G6 C' H) Y
- LL_ADC_SetTriggerFrequencyMode(ADC1, LL_ADC_CLOCK_FREQ_MODE_HIGH); //采样时钟使用高频模式 p/ R& A- D, Z) h
- LL_ADC_REG_SetSequencerConfigurable(ADC1, LL_ADC_REG_SEQ_CONFIGURABLE); //adc通道和硬件adc in是否强制对应,因为adc的io使用时不是连续的,所以这里使用可自定义配置
- |6 O, L# A. {0 Z; c& Q - LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_1, LL_ADC_SAMPLINGTIME_160CYCLES_5); //设置通道共用取样时间,根据需要自行选择8 Q. v8 u: s2 H$ K8 J
- LL_ADC_DisableIT_EOC(ADC1); //禁用通道采样结束中断: B7 l9 `+ L" W
- LL_ADC_DisableIT_EOS(ADC1); //禁用序列采样结束中断,因为使用的是单次软件触发所以这里关闭这些中断% c& N8 G+ A' r# Y
- ADC_InitStruct.Clock = LL_ADC_CLOCK_SYNC_PCLK_DIV2; //选择采样时钟来源# l0 E& y- T7 W! N
- ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B; //采样分辨率
+ f `# K' ~8 _4 ? - ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT; //采样数据对齐方式,右对齐高位补0,左对齐低位补0.
& [& w1 ~& V# |. J - ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE; //低功耗模式,使用DMA的话无法使用,这里关闭9 X/ i; b( f$ E8 q/ b$ p
- LL_ADC_Init(ADC1, &ADC_InitStruct);
1 x# P- L7 n; P4 R% O0 }- Z - /** Configure Regular Channel
( a! C6 j' o6 e" W4 ?5 P! g: d5 s - */7 R s. W- f' k% W! D
- //ADC_RegularChannelConfig
/ ~$ t( b. v1 R6 L2 x7 N - LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_1, ADCIO1_IN_CHANNEL); //将硬件通道ADCIO1_IN_CHANNEL映射到ADC通道1( l. D* V( m0 K( A$ N
- LL_ADC_SetChannelSamplingTime(ADC1, ADCIO1_IN_CHANNEL, LL_ADC_SAMPLINGTIME_COMMON_1); //设置通道采样时间
- K% {$ h1 R* Z1 r1 R% r6 o H - LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_2, ADCIO2_IN_CHANNEL); //将硬件通道ADCIO1_IN_CHANNEL映射到ADC通道2
; _5 _' U. N7 i% `7 Z% ]$ H - LL_ADC_SetChannelSamplingTime(ADC1, ADCIO2_IN_CHANNEL, LL_ADC_SAMPLINGTIME_COMMON_1); //设置通道采样时间! U) ~+ |* k) ]7 A8 j9 I
! B+ n/ u. K( `# ?/ L8 S3 g- /*采样数据转换时需要参考电压,参考电压可能是变化的,所以也有可能需要采集*/
! F/ h; w8 t7 O8 T' h5 f/ X2 w - //LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_4, LL_ADC_CHANNEL_VREFINT);. \& X* A4 T+ R8 P! n" W
- //LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SAMPLINGTIME_COMMON_1);" L* o$ n \! F6 E0 J+ M/ ?" E
- return 1;
, }& n7 X: o0 D6 r - }
复制代码 % l9 H5 Z) p4 A* q
激活并校准ADC:
( V' M8 f5 K. V5 v0 z5 w8 ]6 A! H% k( C% R ]
- uint8_t STM32ActivateADC1(void)
5 _2 w% V7 e: @6 b - {2 _& H& d& h3 t
- __IO uint32_t wait_loop_index = 0U;
4 o1 i9 e' \4 G1 T6 _3 r( J - __IO uint32_t backup_setting_adc_dma_transfer = 0U;! Q& U+ }- {$ d; X' c3 o
- uint32_t Timeout = 0U; /* Variable used for timeout management */: B/ e4 g1 k9 S4 l
- /*## Operation on ADC hierarchical scope: ADC instance #####################*/
# x; m- a1 C. b% m6 B* ?
3 l1 `+ i' J! ^4 q" I9 I9 k- /* Note: Hardware constraint (refer to description of the functions */6 Z8 k2 Q9 D8 n, }5 d
- /* below): */
7 o4 e# v& q, Z# o- z - /* On this STM32 serie, setting of these features is conditioned to */
% G! a2 k4 c. S8 D - /* ADC state: */
0 l- m: g- e% c( } D0 e$ L - /* ADC must be disabled. */# G& |2 D. ?* a- j7 t* V
- /* Note: In this example, all these checks are not necessary but are */) [ s: z: t$ ^8 |0 \
- /* implemented anyway to show the best practice usages */
* B) d" J) ~0 P% h6 u. B q - /* corresponding to reference manual procedure. */4 c2 U# i0 A! ^
- /* Software can be optimized by removing some of these checks, if */; g8 H& G: ^0 Q3 b. `$ l$ ~
- /* they are not relevant considering previous settings and actions *// |' K+ S' t6 `' z/ w9 M
- /* in user application. */, F* L: R4 w' g8 n2 z1 {- p
- if (LL_ADC_IsEnabled(ADC1) == 0): N; r& [4 t. X" U- M- S
- {
8 f8 m+ u8 l3 d' A - /* Enable ADC internal voltage regulator */
7 i5 M3 z9 ~( \6 N4 P - LL_ADC_EnableInternalRegulator(ADC1);. K4 `+ Y" b( J! Q. z, n5 h5 f
8 C! ^+ ]# K; I& e! j- /* Delay for ADC internal voltage regulator stabilization. */
3 P% ]2 n: q1 m, ^ - /* Compute number of CPU cycles to wait for, from delay in us. */
8 M: y3 d. m" S# v- W - /* Note: Variable divided by 2 to compensate partially */# R/ Q. B% R: i- E4 K
- /* CPU processing cycles (depends on compilation optimization). */6 Z0 _$ B( s9 }5 W8 s: W3 j
- /* Note: If system core clock frequency is below 200kHz, wait time *// J- j4 x' o, }9 f3 m# p
- /* is only a few CPU processing cycles. */" H# X1 j7 @% A% a) n9 J1 K
- wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10);
1 s- f4 I# _6 s - while(wait_loop_index != 0)0 X. e- Y- R6 M F* x- Z p6 n
- {
6 a- i( P% s4 Y& { ~/ i - wait_loop_index--;
7 }2 V) L" s" H( H - }- p$ |# C8 [2 C6 J9 n
5 `6 e) s: Q$ J) p; F. b" q/ p- /* Disable ADC DMA transfer request during calibration */
7 i$ F2 s5 W9 O+ y5 p; L/ @) f* ?3 p - /* Note: Specificity of this STM32 serie: Calibration factor is */: y& }* i- @3 P2 N
- /* available in data register and also transfered by DMA. */5 p: g6 \ v; e# g/ k+ Q6 v% ~9 \
- /* To not insert ADC calibration factor among ADC conversion data */' n1 p1 O3 ?7 r# `9 ?% E$ A* t
- /* in DMA destination address, DMA transfer must be disabled during */
$ a# E) X+ E7 M. [9 t - /* calibration. *// f: J8 Z6 S7 ]) |; u+ B
- backup_setting_adc_dma_transfer = LL_ADC_REG_GetDMATransfer(ADC1);9 h) H( M% i+ V6 @( d) O6 n0 V
- LL_ADC_REG_SetDMATransfer(ADC1, LL_ADC_REG_DMA_TRANSFER_NONE);
( s; a$ Y+ Z9 ]+ I - 5 |3 r' G6 r1 Q
- /* Run ADC self calibration */
5 d \1 q V6 p - LL_ADC_StartCalibration(ADC1);
5 {, e$ Q% {0 l8 z! n5 {
2 |! O% X2 R: \9 ] `- /* Poll for ADC effectively calibrated */
8 K4 r! c' P: L6 Y6 p0 I - Timeout = ADC_CALIBRATION_TIMEOUT_MS;4 o3 T& c1 i3 b# r0 v5 I" C2 X2 `+ {
/ B4 Q( R$ u. z3 n3 x* b- while (LL_ADC_IsCalibrationOnGoing(ADC1) != 0)9 ]- |! F/ N" G8 p+ |: H
- {- {# v7 t9 y; {5 c9 K4 e) B$ Q0 a
- /* Check Systick counter flag to decrement the time-out value */
; a) a% [5 v7 H2 ` - if (LL_SYSTICK_IsActiveCounterFlag())4 {" Y8 X# Z0 p. G' u
- {
1 m0 E8 R5 ^) ` - if(Timeout-- == 0)
; }# e0 b B. P" { - {
' w! C# ?- a! d. U2 b/ x& I/ `) T% M8 G- y - return 0;
( N& u3 h' ?; m6 s7 E - } i6 c' g D! i: u# b' C
- }
1 E" _6 i( b, v$ \4 o" e% K+ s6 G - }
/ i" @. `$ E8 D% S
( E& {0 Q6 @6 E7 t9 @1 f! l- /* Restore ADC DMA transfer request after calibration */
$ F* w A- o. T7 P Q% m9 } - LL_ADC_REG_SetDMATransfer(ADC1, backup_setting_adc_dma_transfer);
+ N& d. b7 H. o- t3 P# d8 U `" j - 5 e, ~' C4 m. r! w7 n0 l
- /* Delay between ADC end of calibration and ADC enable. */" k2 o4 p F% ]
- /* Note: Variable divided by 2 to compensate partially */
1 T1 X" G% A f3 E# L - /* CPU processing cycles (depends on compilation optimization). */! u+ j; o* H# _- l) D4 E9 Q7 [ r
- wait_loop_index = (ADC_DELAY_CALIB_ENABLE_CPU_CYCLES >> 1);
: L# v. B) c3 ]) d& d& i! t% s - while(wait_loop_index != 0)
; d" @1 f5 |& v - {5 Z X" p9 E$ U, f4 h6 M* F
- wait_loop_index--;
& I; S( A% ]9 w - }
. \: G5 D! f$ r Y - + @8 Z f3 i" }! u4 H
- /* Enable ADC */
6 j, \ Z9 y' g4 |4 j - LL_ADC_Enable(ADC1);
4 N+ d+ A' n) }/ N* R! u
3 O; q& S3 _# H* j- r; h- /* Poll for ADC ready to convert */
- t5 q' e0 j& Y6 r - Timeout = ADC_ENABLE_TIMEOUT_MS;
6 ]( i; s6 W5 w: O/ X$ M - , j: P3 e+ S' a
- while (LL_ADC_IsActiveFlag_ADRDY(ADC1) == 0)
3 k: }& t' n& L; B# @ - {$ \! c$ s: D I
- /* Check Systick counter flag to decrement the time-out value */1 Q; F- M2 Z/ _
- if (LL_SYSTICK_IsActiveCounterFlag())
; x2 x4 W! w% H - {
6 s6 y& k* p* R$ b5 P, f" u - if(Timeout-- == 0): J% Z- V, c' M+ h9 N9 B
- {
6 j, @. G& H: t/ `2 i; o$ A9 Q4 V: b - return 0;( E4 O% u! D1 e9 Q* z+ L9 I
- }0 x- R k+ {: J$ E. r9 }2 p1 @5 A
- }
- O6 D5 G) [( H! x) ^" H, m0 F" l - }5 a: b ~$ }( {* p' e2 @. m8 M
- /* Note: ADC flag ADRDY is not cleared here to be able to check ADC */7 ? [3 R. o2 V1 i0 C; ^
- /* status afterwards. */$ D& s5 Y# U5 q0 ?
- /* This flag should be cleared at ADC Deactivation, before a new */
% ^* x+ ^5 s0 M3 I5 S o, a) T) L - /* ADC activation, using function "LL_ADC_ClearFlag_ADRDY()". */
d2 t% p* R; O3 x, |0 n - }7 h7 v, K" e9 K6 I! Z T
- return 1;
* r6 d$ o3 w, x6 q - }
复制代码 : `3 X3 {$ C% V/ w# Q) v* @
因为ADC配置的是软件触发的单次采样,所以还需要有手动开始和结束采样的函数。
# O q" H+ W1 E& v3 f2 w* x9 n, _ y+ O
使能ADC采样:
. Q9 i( Q3 ~; a( ]) z6 M0 I6 S4 n% W
- uint8_t STM32ADC1SampleEnable(void)
7 U: m4 s+ K- ~7 x - {
: m2 p+ R @1 N' x3 r( O# c7 Y# d - uint32_t Timeout = ADC_ENABLE_TIMEOUT_MS;
! H9 _. ^/ l0 Q( Q: e; ? - LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_4); //先使能dma再使能adc,以防采样数据丢失) ?, z2 ]- K- S: t4 ~. R
- LL_ADC_Enable(ADC1);
9 h; h: d/ b& P - Timeout = ADC_ENABLE_TIMEOUT_MS;
7 T% b/ k8 M3 [/ T! Z: C - while(LL_ADC_IsActiveFlag_ADRDY(ADC1) != SET)2 q9 R2 {3 i$ O- v+ R
- {
3 i8 s* _. v. \; x - if(Timeout-- == 0)$ `+ z2 v/ Y: _$ d1 K9 a
- {
" K0 A0 d' X" O, e' v; Q - LOG(1,"STM32ADC1SampleEnable ERROR!!!");
3 k& U8 J; h8 [2 W$ ^# t9 X4 ? - return 0;
$ ~3 x0 [1 z# y4 g1 N: [$ c" o: } - }$ c( P7 U+ `2 T
- }
% f6 \( K) f" c+ h% {0 n$ h( Y! D - LL_ADC_REG_StartConversion(ADC1);
7 L; v% d, }; N2 l B9 U - return 1;
: ]- P$ g9 f1 A6 _) N, N) h5 u - }
复制代码
. e! L& Y' D6 D. g# s! C# i/ @7 ]失能ADC采样:
, Y3 o8 r8 Z) r, w
. w! _ U# [: G- uint8_t STM32ADC1SampleDisable(void)% \9 {1 ^2 G: o( M M$ h
- {
# i* @# m( Y! L% j# ~# O: O6 w - uint32_t Timeout = ADC_DISABLE_TIMEOUT_MS;
+ G7 u$ X, ]3 s2 f3 J - LL_ADC_REG_StopConversion(ADC1);
: y' f8 u- q) }+ E$ } - while(LL_ADC_REG_IsConversionOngoing(ADC1) != 0)8 _ f, W% ^+ q& k
- {6 ]% g8 m- x) p- F
- if(Timeout-- == 0)# C: C5 n$ ~! N" C4 w, w9 e
- {
# E- m, f# Y# |, y$ c - LOG(1,"STM32ADC1SampleDisenable ERROR!!!");
5 X$ S: ~% d: h2 h# b4 A& A9 a - return 0;
" q* N, `' S8 u( y) \( e - }7 S0 x4 L% `* s2 \5 a
- }9 Q3 o2 Y; @/ t: K/ L# k9 d
- LL_ADC_Disable(ADC1); //先关闭adc再断开dma,以防采样数据丢失) C. s6 S) M* k- M' M/ L
- LL_DMA_DisableChannel(DMA1,LL_DMA_CHANNEL_4);; G1 t( u8 l& w& `" g0 l
- return 1;7 ^! \7 {: D3 B
- }
复制代码 2 }' ~6 h; }: }/ P( H* C: ^
进行ADC数据采集流程(采用多次采样求平均值的方式):; i% u) c8 m% E2 C$ \ r
8 c2 B# c5 f1 A1、当需要开始采样前,调用一次STM32ADC1SampleEnable()函数;
& _1 ?7 b& n& C5 I; W# `4 j# f; l
2、在一个定时函数内调用LL_ADC_REG_StartConversion(ADC1)进行数据的转换(因为使用的是单次采样,所以每次进入定时函数时都需要调用),取出DMA数据进行累加;* V- d3 r8 u. G3 r3 N& ~. i. n7 U. u
. Z6 V; O) M" M1 `8 O# C( ^
3、达到需要的采样次数之后,对得到的数据进行运算,并调用STM32ADC1SampleDisable()函数关闭ADC通道。6 C) G3 k& D+ b& o% _
; Z6 b0 l# W. J+ \: J
以上方式适用于需要间隔一段时间获取一次ADC数据时使用,例如需要每隔1分钟获取电池电压时,可以进行1分钟倒计时,然后连续进行十次间隔10ms的ADC采样求平均值。2 o2 |8 m. s. c" c) \# @5 u4 i
, {# C& R1 f) D- J2 N6 Y0 y' Q8 z5 U3 t' b) j( e8 L) x3 }/ J5 \' C
|