说明:驱动基于STm32G031K6测试,其他型号需自行做改动。' C3 z( K8 P7 l: R. m$ f2 j
' M$ ^* \& @8 h3 ?4 U% b: Q( XADC的初始化:
9 @* X q2 c& w
3 \" M: `5 k8 q2 M- #define ADCIO1_IN_CHANNEL LL_ADC_CHANNEL_5: L9 R7 E! p% U) x1 Z
- #define ADCIO2_IN_CHANNEL LL_ADC_CHANNEL_7
# N5 {( l& z% b4 L - uint8_t STM32LLADC1Init(void)8 b' Q9 T P8 x, u L6 X$ n3 l
- {
2 E) Z7 y6 m3 U: I- N4 D - LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};) B5 z1 `6 @' X Z3 Y8 E: W% |
- LL_ADC_InitTypeDef ADC_InitStruct = {0};, S% j+ Q9 Y G+ T
7 f2 `0 C" z! d- LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
/ ]3 I9 f C3 C2 b* W
7 w% w) I# `7 B9 d" \2 g* r- /* Peripheral clock enable */
# R+ j! ?0 N2 S" e - LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC); //使能ADC时钟
9 Q3 @8 z4 L% s/ p% W, v
0 X0 t4 b# p7 Y- LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA); //谁能响应ADCioGpio时钟% F* N( v& @; N; E* l
- /**ADC1 GPIO Configuration
8 i% ?6 c" m, x' H: l% }# Y3 Z I - PA5 ------> ADC1_IN5" {$ g5 ^% X0 J- j9 w" Y
- PA7 ------> ADC1_IN7
2 D/ k" a( G& a8 S1 e4 Q# z5 T0 n - */
* y3 g0 w5 K, Y% a: Q7 X2 X+ r9 b2 R - GPIO_InitStruct.Pin = gpioADCIO1_IN_PIN; //ADC检测IO初始化
( a0 M; K% C* x# } - GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
# S6 [* ]8 ]+ V% ]5 k7 m! B - GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
# A3 M; @% U6 g/ [+ m2 s0 \0 l4 Y - LL_GPIO_Init(GPIOA, &GPIO_InitStruct);9 j; B( E) o% F% C( ]. ~$ L
- ! _8 A: i* k6 E! a
- GPIO_InitStruct.Pin = gpioADCIO2_IN_PIN;& H& C/ G6 G& M4 f3 P3 a( o z
- GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
9 _9 V* ^" o+ P - GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;+ m3 U! \0 e+ V/ ]1 z
- LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
+ B% A) w/ @1 Z) ` - 5 y" i% |3 Q) r- R$ ?9 ~
# n" ?) @( M; @3 Z" f- z- /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
! G: o& G4 B, b& _ G ~) h7 p, ]4 v - */+ p3 B" a5 ]4 k
- ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE; //ADC定时转化触发条件来源,可以为软件触发或硬件(外部中断、定时器)触发。6 r2 R n2 a+ y+ i |
- ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_ENABLE_2RANKS; //ADC顺序采集通道的个数,根据自己需要的通道个数设置( u7 u) \/ f+ w$ A5 i" h, r/ q3 n4 H
- ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE; //
0 x8 ?' `* s9 [. A* \ - ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE; //采集方式,单次采集还是连续采集
2 w# H$ c! y. G - ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED; //使能DMA,并使用无限传输,如果DMA保存方式为循环覆盖的话才可以使用无限传输。
. `$ t' c* n' b2 U - ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN; //采集的数据循环覆盖模式
* X- K, F8 u( n/ [9 |/ v: b( M9 T - LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);& p3 B( S6 B) C& l
- LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_DISABLE); //失能过采样
9 U8 h" k: v) z; D! r& ^4 k2 [ - LL_ADC_SetTriggerFrequencyMode(ADC1, LL_ADC_CLOCK_FREQ_MODE_HIGH); //采样时钟使用高频模式5 c$ k, ~1 i' W f" i1 H
- LL_ADC_REG_SetSequencerConfigurable(ADC1, LL_ADC_REG_SEQ_CONFIGURABLE); //adc通道和硬件adc in是否强制对应,因为adc的io使用时不是连续的,所以这里使用可自定义配置% ]& q) @ e2 r$ ^
- LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_1, LL_ADC_SAMPLINGTIME_160CYCLES_5); //设置通道共用取样时间,根据需要自行选择7 ^7 _7 z; ]# m# p) L8 A/ U
- LL_ADC_DisableIT_EOC(ADC1); //禁用通道采样结束中断
3 t4 i' Z% L- L' j* A - LL_ADC_DisableIT_EOS(ADC1); //禁用序列采样结束中断,因为使用的是单次软件触发所以这里关闭这些中断
( l/ k& m4 N0 w3 ]( O - ADC_InitStruct.Clock = LL_ADC_CLOCK_SYNC_PCLK_DIV2; //选择采样时钟来源
% i! y2 x7 }6 v, S& ~6 J - ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B; //采样分辨率- ~$ j# u+ W4 G, q1 n% t- S; ~# ?
- ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT; //采样数据对齐方式,右对齐高位补0,左对齐低位补0.
0 R3 W. V) \* y6 ?, o c' q - ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE; //低功耗模式,使用DMA的话无法使用,这里关闭
2 C$ p/ p: S& o+ x# n - LL_ADC_Init(ADC1, &ADC_InitStruct);
: v3 H1 }2 @1 I2 m5 i - /** Configure Regular Channel
" |% L2 l! u4 h& G1 Z! @. _) w+ l - */
; |6 w" `* H' h% Z: s6 e2 j. d. a - //ADC_RegularChannelConfig
' x' z/ ?. \" M. J - LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_1, ADCIO1_IN_CHANNEL); //将硬件通道ADCIO1_IN_CHANNEL映射到ADC通道1
! Y, G$ Q5 l; q, P - LL_ADC_SetChannelSamplingTime(ADC1, ADCIO1_IN_CHANNEL, LL_ADC_SAMPLINGTIME_COMMON_1); //设置通道采样时间8 u, Q# G9 G" B: @; R
- LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_2, ADCIO2_IN_CHANNEL); //将硬件通道ADCIO1_IN_CHANNEL映射到ADC通道2: C9 {0 P$ d: s6 {, @( w6 c8 k# R
- LL_ADC_SetChannelSamplingTime(ADC1, ADCIO2_IN_CHANNEL, LL_ADC_SAMPLINGTIME_COMMON_1); //设置通道采样时间
2 c# B! ~2 o( v
* C& r& _' {# ~: F( M5 B4 c* B; `- /*采样数据转换时需要参考电压,参考电压可能是变化的,所以也有可能需要采集*/1 `" K. b7 U* @% N' t6 X
- //LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_4, LL_ADC_CHANNEL_VREFINT);
/ W" r% _. ]6 u - //LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SAMPLINGTIME_COMMON_1);
+ T: w1 E Q; Z0 L9 _2 }/ N# q& \ - return 1;
# s$ o. W+ K* P& l7 P# |0 N6 z - }
复制代码
7 @. S, @) ^5 a2 A+ M: t$ g" Y激活并校准ADC:
0 A4 m4 e, X" X2 P7 R9 _
. x. {5 {! V; {& ?. \- uint8_t STM32ActivateADC1(void)( e& T9 i+ w( E( V$ F
- {4 a% Q- M- z. _: A# V" v9 \4 T
- __IO uint32_t wait_loop_index = 0U;
3 p3 z" b2 ^+ w9 p! Y6 C' k - __IO uint32_t backup_setting_adc_dma_transfer = 0U;2 ?- Z( Z2 t0 ?- @+ G
- uint32_t Timeout = 0U; /* Variable used for timeout management */9 O! c. ?$ y4 n! J0 V4 U0 |3 u1 v0 p7 W
- /*## Operation on ADC hierarchical scope: ADC instance #####################*/6 W+ |) J! J; V1 {
( `0 ^0 a& e# t- /* Note: Hardware constraint (refer to description of the functions */
7 L4 z/ r/ e, M$ f, K - /* below): */
5 Q8 D$ w" U% o. e - /* On this STM32 serie, setting of these features is conditioned to */" y) u/ n2 p( P9 x
- /* ADC state: */
& V3 Z- m# Z' Z4 ?/ O - /* ADC must be disabled. */
0 K4 {6 V0 j( k ~6 r - /* Note: In this example, all these checks are not necessary but are */
+ p+ X4 R/ F e8 c, s - /* implemented anyway to show the best practice usages */
: i1 P- A+ g6 e7 r x - /* corresponding to reference manual procedure. */
1 Q9 `$ W& Q+ a; K$ n - /* Software can be optimized by removing some of these checks, if */& z$ y# o" T8 F3 _; k: R4 q# x, U
- /* they are not relevant considering previous settings and actions */
4 \2 S" Y- V9 p, V% z1 }! B - /* in user application. */' f1 L* Q( p: Z. m3 S. p, `
- if (LL_ADC_IsEnabled(ADC1) == 0)5 `* T1 e9 A# i$ v4 ]5 T
- {
" |3 S/ S) ?) `. `/ A, p - /* Enable ADC internal voltage regulator */% O3 P$ k: n/ }. W; \1 b
- LL_ADC_EnableInternalRegulator(ADC1);2 d) J7 \6 m+ z2 U- m
- 5 m+ x% G/ A9 `9 A d
- /* Delay for ADC internal voltage regulator stabilization. */
- ?. M# B( z: a; p* r) _9 r - /* Compute number of CPU cycles to wait for, from delay in us. */
' Q4 ^- [0 N# M8 u3 A+ j - /* Note: Variable divided by 2 to compensate partially */
2 w& O5 \4 D! _* _ - /* CPU processing cycles (depends on compilation optimization). */
1 D) X5 T: J1 Q% k* f9 E - /* Note: If system core clock frequency is below 200kHz, wait time */7 f$ Z2 Q/ v5 a4 c
- /* is only a few CPU processing cycles. */
, d' h" _ G& D ?. Q5 | - wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10);
) K* L+ i9 p9 N \7 M, S; k - while(wait_loop_index != 0)
' R% v. p1 N' \2 y5 L. v& l5 v - {# J4 c& h" c; ?2 q3 j( M
- wait_loop_index--; p1 m" _" ~! W( v( H0 C
- }
) x( c! c$ d4 r* i& Y1 O! V
5 f7 M: f* p/ x3 l8 R. \1 B5 @- /* Disable ADC DMA transfer request during calibration */
% n2 A o4 F b- V$ P& Y v8 [% D# M0 c - /* Note: Specificity of this STM32 serie: Calibration factor is */
0 ^7 f9 {5 F1 F V# S. ^ - /* available in data register and also transfered by DMA. */
: e0 [& w1 Q& y; o" U - /* To not insert ADC calibration factor among ADC conversion data */; [5 C3 K) B7 F' v" E
- /* in DMA destination address, DMA transfer must be disabled during */ V1 E- i1 ]1 A1 i
- /* calibration. */
8 F% M2 j/ j5 Y0 ~ - backup_setting_adc_dma_transfer = LL_ADC_REG_GetDMATransfer(ADC1);
$ \/ [$ N8 U" E" _; l - LL_ADC_REG_SetDMATransfer(ADC1, LL_ADC_REG_DMA_TRANSFER_NONE);9 i0 c5 _6 U7 B$ E
- 2 P* G8 B3 t; E$ }7 i0 i
- /* Run ADC self calibration */5 @7 \5 E( i$ e) X- q
- LL_ADC_StartCalibration(ADC1);
m3 i' |, ?+ V- {# k
( s' x- J& j- l. N& Q+ [$ m* n- /* Poll for ADC effectively calibrated */
3 a3 ~" f9 v3 W n8 B- Y - Timeout = ADC_CALIBRATION_TIMEOUT_MS;. C' f! |4 N! v9 Q
/ K: {- W8 t, e6 @( B/ N6 Q- while (LL_ADC_IsCalibrationOnGoing(ADC1) != 0)
7 x- m+ n7 |: {( t8 ]4 _% @0 b& F0 T - {
' Q0 g7 W; |0 @7 ^# X7 y0 x$ ] - /* Check Systick counter flag to decrement the time-out value */
$ Q5 Q! v. ^* Q5 r2 A - if (LL_SYSTICK_IsActiveCounterFlag())' H8 ^ `8 Z. u( d. t: Q3 e
- {3 {+ C1 H3 n/ k" v) p6 }3 A2 [: z! w
- if(Timeout-- == 0)! @% h) K; d3 ]
- {
# s" ^5 C$ O! A0 `3 X5 w8 D - return 0;8 s8 I, Z0 H8 x( q3 c6 m
- }
, o5 D6 x: n0 q5 W* D - }/ G+ k3 ?$ E ]- s% y; L
- }+ ]3 ^- z! C7 P6 ^7 r! [: Y
- 9 G& H6 z6 ^$ F: @7 E& i' ?5 D) d! r
- /* Restore ADC DMA transfer request after calibration */
9 \' D4 a& H0 a) A - LL_ADC_REG_SetDMATransfer(ADC1, backup_setting_adc_dma_transfer);8 N/ W% M* T9 `/ s3 _. t1 `
- - g1 h. S) s9 o% b h2 x" Q
- /* Delay between ADC end of calibration and ADC enable. */
" C5 x- w: h1 j* X - /* Note: Variable divided by 2 to compensate partially */
/ J) L. I/ Z2 ]0 h4 x( z+ @4 Q - /* CPU processing cycles (depends on compilation optimization). */1 \& w& c: t2 S" P7 g E0 s- X
- wait_loop_index = (ADC_DELAY_CALIB_ENABLE_CPU_CYCLES >> 1);
/ @0 U0 P/ n8 d1 l+ L - while(wait_loop_index != 0)2 v; ?/ E4 x1 R! ^
- {( X; S# L" t# [
- wait_loop_index--; T. p1 G5 r6 K2 ~! Q* E; b, I6 r$ S- ]
- }
& D( j) M* P& n6 x, S
2 V+ v* ^/ W; g( m- /* Enable ADC */ S% a+ S! g# H0 h Y4 t3 f, }+ s
- LL_ADC_Enable(ADC1);+ R9 F" A {9 x/ l U
) R* T* L4 G" A4 _9 n- /* Poll for ADC ready to convert */
8 I/ G8 ^2 H, j- D5 E3 f/ x/ ~ - Timeout = ADC_ENABLE_TIMEOUT_MS;
. y4 B2 K8 V+ x4 X8 r7 W
- g+ P0 z9 Y- f6 n" B0 t8 y: R- while (LL_ADC_IsActiveFlag_ADRDY(ADC1) == 0)
9 }: u- T- A6 W( h. n: d - {# G7 d1 A0 U. r, f; l* u5 u+ U% s
- /* Check Systick counter flag to decrement the time-out value */
( t* J7 p2 Z5 {1 y7 } - if (LL_SYSTICK_IsActiveCounterFlag())
: S# D. L2 u3 I+ M- ~ - {
- p- X# k5 U5 K/ F - if(Timeout-- == 0)
( j7 @8 @- f8 ?& Q; l) e5 w - {
! X2 Y8 i& F3 A9 v/ f: O - return 0;
+ z g7 z9 Q+ b( q5 W - }
) b+ r2 J( [3 X! x/ ?6 l - }1 I, o$ n5 B6 ?7 L* Z: e
- }
5 i& C% x s7 O! ` - /* Note: ADC flag ADRDY is not cleared here to be able to check ADC */
* E' H. b6 {9 M/ f7 j- R! [ - /* status afterwards. */0 q. L) a3 D+ R7 v6 t- g! @# X
- /* This flag should be cleared at ADC Deactivation, before a new */
; Y, A/ _" G7 L) G- H& D( N - /* ADC activation, using function "LL_ADC_ClearFlag_ADRDY()". */# ^: R4 |" L' T
- }
2 K- O5 z. m- T+ ~. \7 S9 C8 @ - return 1;2 w2 l5 r; S6 f" o
- }
复制代码 8 F; t$ _& m" }3 Y+ t: T+ u
因为ADC配置的是软件触发的单次采样,所以还需要有手动开始和结束采样的函数。7 L, ?1 _6 N% s
6 R) ~1 b( X1 w+ U* p, B5 E& s
使能ADC采样:
) \ z' ^" z" ?% c. N
' M# b7 `3 @: \: Q5 T0 ?1 h- uint8_t STM32ADC1SampleEnable(void)# O& p" I4 R9 z" i
- {9 Z" _0 _: x% r/ H' b+ S
- uint32_t Timeout = ADC_ENABLE_TIMEOUT_MS;
. i% i% g5 D2 z" j& _ - LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_4); //先使能dma再使能adc,以防采样数据丢失- P7 W: }3 y) l+ Y/ a1 [
- LL_ADC_Enable(ADC1);
3 Q( X* X. b/ x, O - Timeout = ADC_ENABLE_TIMEOUT_MS;
1 C2 l$ D1 `, A/ ^) X3 q - while(LL_ADC_IsActiveFlag_ADRDY(ADC1) != SET)
$ v) C1 v" O# L) b. T; @* z - {, R6 M1 E! j/ } W. s ?
- if(Timeout-- == 0), n! y# v9 t/ a. Z
- {) t# G, m- M' I9 `
- LOG(1,"STM32ADC1SampleEnable ERROR!!!"); Z( g9 q9 O2 |3 I
- return 0;$ @7 a( A, `: H+ c5 J
- }
, e) p! b* S! ~ - }
* B( F+ k) L0 V7 j, I, U - LL_ADC_REG_StartConversion(ADC1);, ^ x" t; o$ ^
- return 1;
+ ]- M8 ?: b! M; V - }
复制代码
7 i5 N9 {" H7 ^! B( @失能ADC采样:
. x# B+ I" S0 @6 H9 r" k; c7 P# R! _* ]" ~# U; Z3 g: e
- uint8_t STM32ADC1SampleDisable(void) N3 ^/ C: k7 z; w5 h) x5 {6 u' _
- {: \( k; E8 H" P: `' u# n
- uint32_t Timeout = ADC_DISABLE_TIMEOUT_MS; : A$ d; e8 W1 }* w3 _5 @
- LL_ADC_REG_StopConversion(ADC1);6 O6 L3 @/ W0 J
- while(LL_ADC_REG_IsConversionOngoing(ADC1) != 0)
" I. p. M7 Q) C. C0 s - {
4 z1 g" Y1 Y% l7 J - if(Timeout-- == 0)
9 N) Y B) A+ B9 u+ e; f' h - {
$ B/ t0 o( _) r# Z2 |/ `2 z - LOG(1,"STM32ADC1SampleDisenable ERROR!!!");
- Z& H0 N7 S9 \! X4 S - return 0;
; b! L+ H& O) I& ]' t2 B3 V1 j - }; M2 E" y7 C( r1 |, ~3 J. Y+ o
- }
Y( g) z5 F1 A/ p - LL_ADC_Disable(ADC1); //先关闭adc再断开dma,以防采样数据丢失
* Z w7 B; H3 G - LL_DMA_DisableChannel(DMA1,LL_DMA_CHANNEL_4);2 M# v4 F- J) Y+ V
- return 1;" ~7 o% S+ p9 U9 z! ?
- }
复制代码
- G6 X! L2 D# u4 b' J- ~进行ADC数据采集流程(采用多次采样求平均值的方式):' B- L6 d5 i1 L+ T7 [, K
* p) [, t( H; F6 h/ ]
1、当需要开始采样前,调用一次STM32ADC1SampleEnable()函数;% x. w; ?4 V. T( m/ S, s
8 l6 W6 J. O- X- p$ u5 k2、在一个定时函数内调用LL_ADC_REG_StartConversion(ADC1)进行数据的转换(因为使用的是单次采样,所以每次进入定时函数时都需要调用),取出DMA数据进行累加;
8 L& K& E4 k2 `" m: S. ?: q3 c% X3 I$ b$ N; [) Y! C( P: a8 P
3、达到需要的采样次数之后,对得到的数据进行运算,并调用STM32ADC1SampleDisable()函数关闭ADC通道。' n) x* \- T# n1 M
9 e4 x4 f! L+ ]' K以上方式适用于需要间隔一段时间获取一次ADC数据时使用,例如需要每隔1分钟获取电池电压时,可以进行1分钟倒计时,然后连续进行十次间隔10ms的ADC采样求平均值。" z& T5 P" v3 O* z
5 a! j! y! R' q$ t1 P2 L0 I8 h
! w {9 f- f5 c h |