说明:驱动基于STm32G031K6测试,其他型号需自行做改动。
- I( Z* Z% _1 g; j% X
* R4 [5 W: @: ?. l8 M @# ZADC的初始化:/ B0 S8 U# X8 _! i6 M6 r: a# U$ e8 M
. B+ v6 _. O) k5 {; f, z2 Q) Z- #define ADCIO1_IN_CHANNEL LL_ADC_CHANNEL_5' c' i$ g5 [0 H" H* s- F
- #define ADCIO2_IN_CHANNEL LL_ADC_CHANNEL_7& g. Q3 m5 M: _) r/ S/ K) O
- uint8_t STM32LLADC1Init(void)$ d2 w6 y* p# h& |! ~9 N5 z
- {3 V) ^, ^) ]" Z! `
- LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};
7 C' v6 [; S9 e- a" P+ X% y8 f - LL_ADC_InitTypeDef ADC_InitStruct = {0}; n' K7 o: ~' k5 X5 u7 u, Q
. g5 [0 R8 x9 C( I2 A. P. G- LL_GPIO_InitTypeDef GPIO_InitStruct = {0};, g0 c1 L5 d1 I) [' g! k- Q
- Z: O: K) L8 t3 |- /* Peripheral clock enable */
+ d9 _! ?/ I+ |$ M - LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC); //使能ADC时钟
2 Y6 }( h2 T" X8 i& A4 Z \1 p/ a
( h8 n! J7 W' {# m+ n8 T6 ~. f- LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA); //谁能响应ADCioGpio时钟
; R/ K4 o2 n' C4 B# y7 f2 s - /**ADC1 GPIO Configuration
9 O& C7 U/ S( U. H6 z- z - PA5 ------> ADC1_IN5
9 R9 Y3 w+ A) t* f Z - PA7 ------> ADC1_IN7
; o( D5 P( O6 ?' d# t/ K# ^; O - */) _, c# y, A2 H$ S* e/ j& O
- GPIO_InitStruct.Pin = gpioADCIO1_IN_PIN; //ADC检测IO初始化; @; ~6 C9 x+ r4 }- h* r
- GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;2 [1 V7 `$ R1 d. h, J4 c+ p
- GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;: a" s6 B1 y# i& x3 ?
- LL_GPIO_Init(GPIOA, &GPIO_InitStruct);* x2 b* `3 n6 @: U$ F5 k
- : S. L3 U" K/ n7 {8 N; G0 e
- GPIO_InitStruct.Pin = gpioADCIO2_IN_PIN;
" k2 b# {3 f& t/ M/ v& P3 e' D - GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;0 l* c J4 [# C+ v+ }+ ]7 j
- GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;$ G0 Z `& V( L, R* z
- LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
: E9 V8 r" B( H* t* O - ; s8 _5 w$ W! k$ f- h. [& \
|0 T( { _1 u- /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
& {3 z% X. M6 C% `) O9 t* z - */* J- p8 i# w: ]% t7 }! B
- ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE; //ADC定时转化触发条件来源,可以为软件触发或硬件(外部中断、定时器)触发。# i, \2 ?6 R- W7 p4 w& V/ d- w; J
- ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_ENABLE_2RANKS; //ADC顺序采集通道的个数,根据自己需要的通道个数设置. N0 ?( [: Y* \0 ]/ h
- ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE; //
% R1 v, X$ E# z0 d P - ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE; //采集方式,单次采集还是连续采集/ M* T0 N. q; I( V
- ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED; //使能DMA,并使用无限传输,如果DMA保存方式为循环覆盖的话才可以使用无限传输。
4 p2 O; }( X) R3 _- p9 l - ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN; //采集的数据循环覆盖模式# c) H4 n* }: V$ X3 x
- LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);
* o5 y4 |* ~' m1 G9 [* F - LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_DISABLE); //失能过采样
: [; E) v9 R+ ?( R - LL_ADC_SetTriggerFrequencyMode(ADC1, LL_ADC_CLOCK_FREQ_MODE_HIGH); //采样时钟使用高频模式
. [, g) L0 n, z1 ?- C - LL_ADC_REG_SetSequencerConfigurable(ADC1, LL_ADC_REG_SEQ_CONFIGURABLE); //adc通道和硬件adc in是否强制对应,因为adc的io使用时不是连续的,所以这里使用可自定义配置: Y5 X* c0 Z3 t. E9 `
- LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_1, LL_ADC_SAMPLINGTIME_160CYCLES_5); //设置通道共用取样时间,根据需要自行选择
$ R& t. I- P$ e1 o4 `5 L* K* K- ~ - LL_ADC_DisableIT_EOC(ADC1); //禁用通道采样结束中断 D7 J6 |3 y; m- u1 h5 V4 H3 @
- LL_ADC_DisableIT_EOS(ADC1); //禁用序列采样结束中断,因为使用的是单次软件触发所以这里关闭这些中断
; t1 P6 b3 J# }4 V, F: V - ADC_InitStruct.Clock = LL_ADC_CLOCK_SYNC_PCLK_DIV2; //选择采样时钟来源
2 P6 x% r. [* l# P6 N8 @' v+ a - ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B; //采样分辨率+ }. A& z; {3 L0 ]' B' w+ f; M
- ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT; //采样数据对齐方式,右对齐高位补0,左对齐低位补0./ p2 _) }8 X* M5 y9 b6 ~0 M) R. u k
- ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE; //低功耗模式,使用DMA的话无法使用,这里关闭
/ B6 @4 g4 ^$ |& x8 L - LL_ADC_Init(ADC1, &ADC_InitStruct);
/ m5 l4 L3 B) z9 R8 f2 m |" L ? - /** Configure Regular Channel
1 G; v& J; w, \9 O" X1 `2 V8 } - */5 b* E; a) p1 m: N# H
- //ADC_RegularChannelConfig3 i( y1 B' i* _& X5 s9 |4 S1 X: i
- LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_1, ADCIO1_IN_CHANNEL); //将硬件通道ADCIO1_IN_CHANNEL映射到ADC通道1/ Y1 {1 G+ c- Y
- LL_ADC_SetChannelSamplingTime(ADC1, ADCIO1_IN_CHANNEL, LL_ADC_SAMPLINGTIME_COMMON_1); //设置通道采样时间8 H( n; ]6 U# R" p w. E) s
- LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_2, ADCIO2_IN_CHANNEL); //将硬件通道ADCIO1_IN_CHANNEL映射到ADC通道2$ I7 T! u; f& A) A
- LL_ADC_SetChannelSamplingTime(ADC1, ADCIO2_IN_CHANNEL, LL_ADC_SAMPLINGTIME_COMMON_1); //设置通道采样时间+ }6 a, ~+ m$ c
- 3 ~/ W0 ~6 Y3 b1 I8 g: q) ^
- /*采样数据转换时需要参考电压,参考电压可能是变化的,所以也有可能需要采集*/
0 ^. Y7 L! @# J5 S! G7 P' { - //LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_4, LL_ADC_CHANNEL_VREFINT);
- U% y0 k1 u- R9 c: t - //LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SAMPLINGTIME_COMMON_1);
+ D4 w" w; [7 J8 G3 \ - return 1;
0 w4 h I8 R# x/ X( q - }
复制代码 " z7 [! V& \8 } B0 _
激活并校准ADC:
. q4 q3 z8 f" s, t) W0 Q$ g' z. B' P* z
+ ~/ X- C/ L; f7 v f5 d- uint8_t STM32ActivateADC1(void)8 ~/ P& Y' Q. }" D) Q
- {
$ b+ n6 I. L& Y2 e& \ - __IO uint32_t wait_loop_index = 0U;$ Q2 F! A: H0 P0 \
- __IO uint32_t backup_setting_adc_dma_transfer = 0U;
- K& C/ X) ^- J% T" v I0 W r9 L - uint32_t Timeout = 0U; /* Variable used for timeout management */! s5 J: J% p1 q0 M
- /*## Operation on ADC hierarchical scope: ADC instance #####################*/
) A; ` O3 V3 U7 }+ T0 O' @1 b
% P/ q6 M9 e) V* A `/ x- /* Note: Hardware constraint (refer to description of the functions */2 y6 K# ^) w( c/ p6 D
- /* below): */
+ V' m# ^ O1 R0 X9 a1 y: A' Q - /* On this STM32 serie, setting of these features is conditioned to */8 \& }1 f! R& Y! r1 p- H
- /* ADC state: */, Y% q& e0 l' S
- /* ADC must be disabled. */
0 J3 M# s8 {7 d - /* Note: In this example, all these checks are not necessary but are */ I" p2 q% n4 g2 S7 i
- /* implemented anyway to show the best practice usages */
% }6 U8 }. s/ j% }3 M - /* corresponding to reference manual procedure. */ |- a0 w2 G) `8 _. f2 v- h7 K# `
- /* Software can be optimized by removing some of these checks, if */- s' \9 `( T8 S/ H X x; a
- /* they are not relevant considering previous settings and actions */
& u2 I/ c* R2 S: r& x% z7 k, z - /* in user application. */
) }# m! O* \* P - if (LL_ADC_IsEnabled(ADC1) == 0)
- K' @0 y* u& E9 S) l! J - {8 W4 b0 y& ?# w, G) G/ J# }
- /* Enable ADC internal voltage regulator */
" [" F- s: {+ [3 j3 \( z2 A' b% H - LL_ADC_EnableInternalRegulator(ADC1);/ C% _% m6 q+ F* {+ ^
- . e* l1 |+ R4 G& l9 R
- /* Delay for ADC internal voltage regulator stabilization. */7 m+ r4 @8 {* o; @2 q
- /* Compute number of CPU cycles to wait for, from delay in us. */* h9 A( ^& E" m$ { j q
- /* Note: Variable divided by 2 to compensate partially */2 ?/ d' F! h# W; `
- /* CPU processing cycles (depends on compilation optimization). */' P0 m m4 p# s" h0 B, j2 Z
- /* Note: If system core clock frequency is below 200kHz, wait time */; |. \" [' @9 i# v3 N
- /* is only a few CPU processing cycles. */4 g H7 Q8 w& g" ~2 d
- wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10);
9 i# ~# n9 t0 O - while(wait_loop_index != 0); X- [/ W) J" i; A( [' t
- {
& ]% H3 t o; ~1 Q: S. [ - wait_loop_index--;
7 a$ R+ h4 S+ u! W D L) z3 |2 l - }4 u2 l" y# |- s5 M
( u, ^* Z" r3 ~- /* Disable ADC DMA transfer request during calibration */% O d2 n" k) o1 @
- /* Note: Specificity of this STM32 serie: Calibration factor is */5 q" G( V3 M2 L4 T3 L8 x
- /* available in data register and also transfered by DMA. */) ~8 M) _) ]/ @8 |7 B6 J. v! o
- /* To not insert ADC calibration factor among ADC conversion data */. P( y3 W0 U) E
- /* in DMA destination address, DMA transfer must be disabled during */
* p+ H2 k" g5 o - /* calibration. */% m+ t0 S8 C- s S) |, F0 T2 A
- backup_setting_adc_dma_transfer = LL_ADC_REG_GetDMATransfer(ADC1);. L# c U& f; d5 }; L: \# E
- LL_ADC_REG_SetDMATransfer(ADC1, LL_ADC_REG_DMA_TRANSFER_NONE);
- j2 ^( z2 J3 n - 6 @( Z& g& |( v: \
- /* Run ADC self calibration */+ \6 N2 b: k) K5 X* |
- LL_ADC_StartCalibration(ADC1);
# J B6 A/ K' H) E( c0 R1 [% | J - " o3 p }9 L: |, j+ G
- /* Poll for ADC effectively calibrated */& p$ b' @* v9 U
- Timeout = ADC_CALIBRATION_TIMEOUT_MS;
& B% U. M% \ D3 r' k2 b - ) H# y& p9 m, A
- while (LL_ADC_IsCalibrationOnGoing(ADC1) != 0)
' L. ?2 n& L$ P [ o, | - {9 x+ U! ^5 y& e" Q5 \2 W! H/ D
- /* Check Systick counter flag to decrement the time-out value */
+ L. s! z+ v+ s - if (LL_SYSTICK_IsActiveCounterFlag())+ H! a- A- j R- H
- {
6 K4 u6 M- t n - if(Timeout-- == 0)
8 {2 C F4 X0 W( f$ s; H4 a- x - {6 A5 f( b( \( B" e9 v& Q
- return 0;
! Y. m& u7 R4 G% { - }- n* z% j2 F- |9 B m/ E
- }
2 q# K$ `' X) Q5 `, }5 [( A9 F& { - }' l; I+ c9 \5 q4 N3 k9 s* q
- . `, X, I7 }5 N) P
- /* Restore ADC DMA transfer request after calibration */# w! B* |# f8 U. e+ V4 t6 G7 {
- LL_ADC_REG_SetDMATransfer(ADC1, backup_setting_adc_dma_transfer);& L5 a) V8 \# p# G1 _) D( c
- . q6 @! C8 F/ @- R5 P' M
- /* Delay between ADC end of calibration and ADC enable. */5 x, u6 B l1 ~" }2 m) [/ ~6 x
- /* Note: Variable divided by 2 to compensate partially */: u& ^& o+ [3 P' z2 g5 X0 P1 ~
- /* CPU processing cycles (depends on compilation optimization). */3 r$ _; Q* V' D+ @; U; k
- wait_loop_index = (ADC_DELAY_CALIB_ENABLE_CPU_CYCLES >> 1);/ g5 i7 w8 }; T. a7 G2 a
- while(wait_loop_index != 0)5 J% B, w( N* \6 q0 C7 J
- {. \# i/ b7 p/ d& a5 |6 \- U
- wait_loop_index--;7 k/ ^5 Z1 T- a: a# G5 O# o
- }5 g( w6 ^+ H+ |: j1 u' k* }: q1 g; p
8 F* e3 d/ s/ r5 i- /* Enable ADC */% ~; W/ v+ h% a8 D& |3 W" L
- LL_ADC_Enable(ADC1);
" v) g* p; J6 }
. I" E8 G! X& a1 c6 R; v- /* Poll for ADC ready to convert */
/ H/ i( }) B/ j9 L* @1 B8 M - Timeout = ADC_ENABLE_TIMEOUT_MS;7 ?+ R" U0 J0 |- E8 U. d% R* w
- 1 I2 N0 k p* }$ K) Q8 \
- while (LL_ADC_IsActiveFlag_ADRDY(ADC1) == 0)
1 x' k( g: |- P% i8 V - {/ ]$ O. n5 q: v7 x
- /* Check Systick counter flag to decrement the time-out value */
1 @. a* U' S8 d+ p. _2 B - if (LL_SYSTICK_IsActiveCounterFlag())
6 K- U9 C' B( M - {% ^4 F8 k, f4 O; }. Y z1 r# j
- if(Timeout-- == 0)- R# V i& P& G! }
- {
7 _! @" ^% H+ o, l$ l$ e0 T+ ~ - return 0;
' I' a+ ^3 [: ?+ _ - }
* x$ q/ ]$ V8 n3 _3 p+ Z) s% t - }
4 M& o2 ]" a( u/ o U( P% R - }5 J# \' `! b! A( _0 l
- /* Note: ADC flag ADRDY is not cleared here to be able to check ADC */
$ k( `5 Q Z* h, L: l - /* status afterwards. */, O( j- G$ [* n
- /* This flag should be cleared at ADC Deactivation, before a new */$ O- _; M* H* z7 d
- /* ADC activation, using function "LL_ADC_ClearFlag_ADRDY()". */
' S V6 p0 s6 R W6 a( ` - }) D$ }1 h. H7 D. Q& A
- return 1;
0 i9 b8 f: Q- f, A' o1 d - }
复制代码
- h4 N+ }2 B& j# e因为ADC配置的是软件触发的单次采样,所以还需要有手动开始和结束采样的函数。
& r; {# ]- B' L; i) x2 r, d) g( d) Z3 J4 I, }# m
使能ADC采样:
$ ]8 ~! a* W6 |- E/ g% e8 l, @& y1 J% g, R8 u: `$ u
- uint8_t STM32ADC1SampleEnable(void)4 d% P. T1 f2 Z5 D& ]1 M5 u
- {) ~3 U/ r( P2 k7 \0 j
- uint32_t Timeout = ADC_ENABLE_TIMEOUT_MS;
" `" [! q& R& [# g+ [ W4 E. X - LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_4); //先使能dma再使能adc,以防采样数据丢失3 M" I% J- l4 m6 Y# _
- LL_ADC_Enable(ADC1);
/ B( C. h; [6 C ] - Timeout = ADC_ENABLE_TIMEOUT_MS;
. h. M" k, C2 i5 _4 [% @" W - while(LL_ADC_IsActiveFlag_ADRDY(ADC1) != SET)0 L; P/ f& `6 R5 w b+ B& n
- { \0 u U- _" k5 S
- if(Timeout-- == 0)
+ \5 y# k3 X% D% |( }) x - {; y* [+ `6 i) ^6 c4 m
- LOG(1,"STM32ADC1SampleEnable ERROR!!!");1 S/ @: K- l6 l$ V6 f! F, T+ @
- return 0;
7 s/ H$ V: I; x8 w - }
: A; c7 ?( O: G A k1 x( \/ @( r - }
) u: z" J& a( ? - LL_ADC_REG_StartConversion(ADC1);
" r+ l" T1 H- K2 _- A- a& X3 w - return 1;+ i! j# j v8 @" A$ z
- }
复制代码 ; _8 w% L- u) W7 h
失能ADC采样:
" x- @8 U+ W1 ]1 B2 l& C+ p# @" a4 n; e+ M3 O1 Q: s( F
- uint8_t STM32ADC1SampleDisable(void)
& Z' P! X( \$ o - {
* W& ~7 \- g3 y C" j S - uint32_t Timeout = ADC_DISABLE_TIMEOUT_MS;
8 p7 G& Y% @& Z r - LL_ADC_REG_StopConversion(ADC1);( D; _5 P5 P5 c- G) \
- while(LL_ADC_REG_IsConversionOngoing(ADC1) != 0)
0 x; \0 @$ C2 G - {
" |$ ?8 o" b/ |' U- u0 K, x' Z - if(Timeout-- == 0)" g7 P$ z* @: S' T8 N
- {, k. b5 n2 m2 ]- F* _
- LOG(1,"STM32ADC1SampleDisenable ERROR!!!");
7 m. X. L# n/ U/ S0 @ \$ G# _ - return 0;
; Q' E0 @0 z2 @ - }' [5 A* Q! e, l6 o$ C
- }- ~$ R+ F* d1 p
- LL_ADC_Disable(ADC1); //先关闭adc再断开dma,以防采样数据丢失
4 b! e& T e& {6 f8 a8 k% M - LL_DMA_DisableChannel(DMA1,LL_DMA_CHANNEL_4);
/ s& }5 h; c* U9 {' T7 v; K - return 1;
8 L% W4 x( t6 _" z - }
复制代码 4 S7 ?4 ~6 Y8 g* X: L
进行ADC数据采集流程(采用多次采样求平均值的方式):
, ]8 h, R4 s5 w' R2 L# B& |" E9 `+ f! n; D' z! H) ^! `( O U
1、当需要开始采样前,调用一次STM32ADC1SampleEnable()函数;0 \+ B8 ?( Y1 Z2 l r4 b8 N P
7 _1 U A2 K! x; z) y
2、在一个定时函数内调用LL_ADC_REG_StartConversion(ADC1)进行数据的转换(因为使用的是单次采样,所以每次进入定时函数时都需要调用),取出DMA数据进行累加;
9 D* M) F7 b; E- _0 @% C0 f+ _. L/ p( D7 k
3、达到需要的采样次数之后,对得到的数据进行运算,并调用STM32ADC1SampleDisable()函数关闭ADC通道。8 ?; C, B5 }! m" G; q, t
2 E& \$ F/ E9 D; K( ^' B. n以上方式适用于需要间隔一段时间获取一次ADC数据时使用,例如需要每隔1分钟获取电池电压时,可以进行1分钟倒计时,然后连续进行十次间隔10ms的ADC采样求平均值。/ G! r' H4 k9 Q0 _
7 ]2 i3 t* l- x( O* G+ R
f, F" b7 X8 A! ]9 D4 X |