说明:驱动基于STm32G031K6测试,其他型号需自行做改动。0 h! W# V! `* x. o1 g0 j6 |/ V
) A' w" q! e: o8 A A; d: M6 G7 jADC的初始化:
& @! \: z& W% i. X' X
- E: f, d! g, t1 H, |, H. {- #define ADCIO1_IN_CHANNEL LL_ADC_CHANNEL_5$ a8 E3 Q! Z6 D
- #define ADCIO2_IN_CHANNEL LL_ADC_CHANNEL_7
4 ?+ T* q/ ^# |7 B3 `+ N3 N - uint8_t STM32LLADC1Init(void)+ u/ a+ ?& {" W9 F& Z
- {
' U+ ~6 b1 g; D6 ]9 O - LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};3 r$ v6 U) o: C4 T5 h! a8 n
- LL_ADC_InitTypeDef ADC_InitStruct = {0};
; d+ N0 I$ f1 x' k, { d# T - * @ t4 v3 a* N7 b" D
- LL_GPIO_InitTypeDef GPIO_InitStruct = {0};# V4 o2 p& E( X ?
- 9 [9 K9 C- ~1 j$ X" R' A! Y
- /* Peripheral clock enable */8 |7 p/ L) H- k. m. b
- LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC); //使能ADC时钟
3 `; R2 x/ L- m; N8 e& B& l - 5 x% W4 J% |# g' W5 {+ E# @* E
- LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA); //谁能响应ADCioGpio时钟& ~( Q4 m3 Y. q0 E$ P& ?
- /**ADC1 GPIO Configuration
# q; L# h0 o) `( g) B& M2 w - PA5 ------> ADC1_IN51 @3 C b! w2 U& B' K8 S) S+ f+ e
- PA7 ------> ADC1_IN7
2 b; ]3 q5 Y) w* j) e6 s* V; F - */
( Y2 V7 F( v1 {( T. [6 p - GPIO_InitStruct.Pin = gpioADCIO1_IN_PIN; //ADC检测IO初始化
# r2 U) \! H1 j3 j5 \ - GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;/ U" W+ @8 ~, P( b0 T
- GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;5 M! o( }/ k$ N0 P
- LL_GPIO_Init(GPIOA, &GPIO_InitStruct);6 L5 A0 i( W1 r8 X4 E. |# y( C3 _ i5 R
- + { B c. g0 W7 d
- GPIO_InitStruct.Pin = gpioADCIO2_IN_PIN;' L/ N7 [/ q1 Z m% Y% o
- GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
( V, \. f% d: {# u* @ - GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
! X, N0 z9 J0 ~1 h2 m: H$ a - LL_GPIO_Init(GPIOA, &GPIO_InitStruct);9 s; ]' D5 d: P
- ; [5 C/ q1 \+ g0 T! m+ I' ~' C
- ; R+ s" R, X9 b3 S
- /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) 1 }/ y, S' @+ l% s! H, j1 _1 C
- */) ?7 h$ m! k$ O* e P
- ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE; //ADC定时转化触发条件来源,可以为软件触发或硬件(外部中断、定时器)触发。
- L, ~2 V; x1 O8 `% ~ - ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_ENABLE_2RANKS; //ADC顺序采集通道的个数,根据自己需要的通道个数设置
0 O) E% G7 x% O1 _ S - ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE; //, ?5 R" T7 k d, E9 v9 ?
- ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE; //采集方式,单次采集还是连续采集5 D4 |9 F I# @- X
- ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED; //使能DMA,并使用无限传输,如果DMA保存方式为循环覆盖的话才可以使用无限传输。4 B0 M$ y* A$ h" e
- ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN; //采集的数据循环覆盖模式. T* M- s# g. h% d
- LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);
+ J# j C7 E3 V- R8 o0 q - LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_DISABLE); //失能过采样+ `+ ~" e% X4 Z7 s' p2 u
- LL_ADC_SetTriggerFrequencyMode(ADC1, LL_ADC_CLOCK_FREQ_MODE_HIGH); //采样时钟使用高频模式1 c5 x: r# M6 j3 [0 {. J4 K; E/ ^
- LL_ADC_REG_SetSequencerConfigurable(ADC1, LL_ADC_REG_SEQ_CONFIGURABLE); //adc通道和硬件adc in是否强制对应,因为adc的io使用时不是连续的,所以这里使用可自定义配置) u5 K/ X1 }0 {# R {: v, ]: k, d
- LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_1, LL_ADC_SAMPLINGTIME_160CYCLES_5); //设置通道共用取样时间,根据需要自行选择
8 c0 H8 X" t- m; X1 Y3 @& v9 I - LL_ADC_DisableIT_EOC(ADC1); //禁用通道采样结束中断
+ w, B: F; K9 X9 |* Z0 N1 K: Z - LL_ADC_DisableIT_EOS(ADC1); //禁用序列采样结束中断,因为使用的是单次软件触发所以这里关闭这些中断
0 z; x3 ] ^* {; [% ?. J" e/ h1 N/ g$ b - ADC_InitStruct.Clock = LL_ADC_CLOCK_SYNC_PCLK_DIV2; //选择采样时钟来源1 J- Y# { y9 v) W* W+ @
- ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B; //采样分辨率# ]4 d0 u D" \$ `5 {$ ]1 ~9 f4 a
- ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT; //采样数据对齐方式,右对齐高位补0,左对齐低位补0.; t/ S/ O% {; E, V4 q
- ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE; //低功耗模式,使用DMA的话无法使用,这里关闭: F; S4 n# l O7 V* {
- LL_ADC_Init(ADC1, &ADC_InitStruct);
# p* R9 R6 K3 Y$ X* |& z - /** Configure Regular Channel 7 y. h ?3 W5 ], ?$ V7 Y3 f9 O
- */ d! Y. a) J8 I( ]4 S
- //ADC_RegularChannelConfig% ~: S; p; v. d- U9 x* c1 }& ?
- LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_1, ADCIO1_IN_CHANNEL); //将硬件通道ADCIO1_IN_CHANNEL映射到ADC通道1) |% G. {+ X: y, ]( G8 i( [
- LL_ADC_SetChannelSamplingTime(ADC1, ADCIO1_IN_CHANNEL, LL_ADC_SAMPLINGTIME_COMMON_1); //设置通道采样时间
4 Y0 Z2 P O+ |# f - LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_2, ADCIO2_IN_CHANNEL); //将硬件通道ADCIO1_IN_CHANNEL映射到ADC通道2
/ Q6 R# s/ t+ O; V7 H% _2 E9 P' g& t3 d - LL_ADC_SetChannelSamplingTime(ADC1, ADCIO2_IN_CHANNEL, LL_ADC_SAMPLINGTIME_COMMON_1); //设置通道采样时间6 [4 I' A; E& U, I5 f5 h- c( H* A) N
- , @2 k7 s# Z: n" A
- /*采样数据转换时需要参考电压,参考电压可能是变化的,所以也有可能需要采集*/
7 @/ V7 q( O* ~7 P" K! B7 V - //LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_4, LL_ADC_CHANNEL_VREFINT);4 e( r: ?7 Q. _# M- @( Q8 N
- //LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SAMPLINGTIME_COMMON_1);
' D2 _5 t& W* ~1 a/ E - return 1;
" J7 m) t A8 L9 J - }
复制代码
+ j6 z' d, e% c+ I+ ~4 k激活并校准ADC:# H Q9 v. J7 w% N6 t# S4 ]* M
6 @4 b: T$ D* W+ W+ V! l: l2 O) m
- uint8_t STM32ActivateADC1(void)7 ^ Q/ T/ Z8 N5 [
- {
# R I* Y, A, H( j - __IO uint32_t wait_loop_index = 0U;% q* Z# k) ^7 Z$ I% V; W L0 X
- __IO uint32_t backup_setting_adc_dma_transfer = 0U;+ h2 p4 x+ _* u
- uint32_t Timeout = 0U; /* Variable used for timeout management */9 X% ?% M6 U; t! z9 N
- /*## Operation on ADC hierarchical scope: ADC instance #####################*/
+ n: l9 w1 W5 I' {) X, r1 q1 D - + G% x* m& W& B! B
- /* Note: Hardware constraint (refer to description of the functions */# c2 O5 k! r% |+ Q3 i
- /* below): */
6 F0 w5 _; ^# b$ r4 Q6 c) ? - /* On this STM32 serie, setting of these features is conditioned to */
/ B0 y. T+ B. _7 G; g% h - /* ADC state: */* K1 n: \/ {% e8 n$ W% s! E0 k
- /* ADC must be disabled. */* ]. ~" o/ ^; r; Q
- /* Note: In this example, all these checks are not necessary but are */. p% `/ W3 p! T- f4 }6 @( X
- /* implemented anyway to show the best practice usages */
6 u* A. [. O/ c% z p U ? - /* corresponding to reference manual procedure. */
0 e2 M! A9 a( T$ T7 u - /* Software can be optimized by removing some of these checks, if */% v) L' o* b$ g" i+ z$ Q
- /* they are not relevant considering previous settings and actions */
. r' p; j- |6 x e8 G7 u - /* in user application. */
- z# p* ~) Y3 l: |9 R$ p - if (LL_ADC_IsEnabled(ADC1) == 0)- C+ I" r) J* `2 f5 ]/ ~% D; F
- {: m, R" F, \; U. `
- /* Enable ADC internal voltage regulator */
& B. q4 p5 w/ T - LL_ADC_EnableInternalRegulator(ADC1);& F- Z7 \; M# w" z9 U0 n5 S
- " y6 \! M. L7 ~
- /* Delay for ADC internal voltage regulator stabilization. */4 _" X1 S; g* n' f9 A" z* [9 ^$ {& d
- /* Compute number of CPU cycles to wait for, from delay in us. */' J t7 L8 Z' U* O E0 ]# Y" O
- /* Note: Variable divided by 2 to compensate partially */1 E$ X6 ]4 ~& Y n' N5 F2 q$ ^
- /* CPU processing cycles (depends on compilation optimization). *// ^' P" f5 ^; m c, k: }2 H
- /* Note: If system core clock frequency is below 200kHz, wait time */' |$ i1 Y& k" J7 _" X9 c( b
- /* is only a few CPU processing cycles. */
. [. m, i) g0 P1 I - wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10);
: q: G! W" A i+ [- I4 F$ }1 D3 ~6 a - while(wait_loop_index != 0)
. I* @( N* ~! H - {
, J+ @+ U1 h2 O: V: U3 T - wait_loop_index--;
! E3 y- s: B1 Q6 x( `& F. W - }2 Z% _. [$ s/ O* o' w) c6 H( b/ \
- : |% z5 u. |4 J7 ?" ], A
- /* Disable ADC DMA transfer request during calibration */- Y$ N/ H: G$ ?2 y2 j9 ~; X
- /* Note: Specificity of this STM32 serie: Calibration factor is */
x$ x( z1 Y% g" M5 m - /* available in data register and also transfered by DMA. */
4 C( U2 x# P* W, n2 _3 }1 H - /* To not insert ADC calibration factor among ADC conversion data */% `- `0 h% E: Z4 ^. b" Y2 s2 l+ \
- /* in DMA destination address, DMA transfer must be disabled during *// M$ {3 P; V& O( y- g
- /* calibration. */3 L. \' R5 o: T! R
- backup_setting_adc_dma_transfer = LL_ADC_REG_GetDMATransfer(ADC1);
3 I3 s6 a) p: p' S) \! v" R# y) O - LL_ADC_REG_SetDMATransfer(ADC1, LL_ADC_REG_DMA_TRANSFER_NONE);
]* x! h" o3 U ~ - % O4 L ^+ F3 W) M1 z) Y. S
- /* Run ADC self calibration */
6 s% ] n; j; u- M3 P2 S+ d' m X4 m2 B - LL_ADC_StartCalibration(ADC1);
2 U8 S, I4 I1 s0 B: c
1 P' k* g6 l# h7 ^4 g- /* Poll for ADC effectively calibrated */" W' B9 T/ T8 I" s3 m) L
- Timeout = ADC_CALIBRATION_TIMEOUT_MS;
, p) D: Y0 b9 c; y, r9 z& v, r - + ^- ?3 P# X% s) k5 }1 d
- while (LL_ADC_IsCalibrationOnGoing(ADC1) != 0)( A3 ?$ h7 e7 w# `* b+ U) S
- {
& f. H1 p' \; W# p( x7 A/ E/ h* Q - /* Check Systick counter flag to decrement the time-out value */
# ~3 R% X- s7 l4 y; p: }' H' i - if (LL_SYSTICK_IsActiveCounterFlag())2 A* S, L/ K, l3 Q( Q
- {1 Y8 M; b% H- ]+ l0 ]
- if(Timeout-- == 0)2 D" ]; V k+ Q/ @" Y6 |8 M
- {' d. p3 e! T7 N; O. G9 U
- return 0;
' {% Q; Z# b1 _1 K+ a, o; | - }
: J, O# ^& P9 J$ W: a9 y( v% y - }
2 Z4 G9 c7 v Z/ L! ~4 e- J! H - }
4 U0 N A/ b6 z z4 S+ X: s - 4 p& v9 Q+ O, T8 Z$ `: i
- /* Restore ADC DMA transfer request after calibration */
+ Y# m2 x7 ?9 I8 t - LL_ADC_REG_SetDMATransfer(ADC1, backup_setting_adc_dma_transfer);
) z$ T& B% H# R z7 x G: W, G% O; V: e - + w/ W9 |. w- C+ ^; m# k; r; I
- /* Delay between ADC end of calibration and ADC enable. */
: w. \6 Z& k& P# z - /* Note: Variable divided by 2 to compensate partially */7 O: {$ b/ W: j: I3 Y& l9 |$ C* f
- /* CPU processing cycles (depends on compilation optimization). */
( y* \) F" p: { U - wait_loop_index = (ADC_DELAY_CALIB_ENABLE_CPU_CYCLES >> 1);7 B: _' ^ u6 d0 Q
- while(wait_loop_index != 0)
1 E/ W+ J/ w) J9 A9 X: v" A - {) Z0 Q8 b/ G% G( T' d
- wait_loop_index--;& w2 h. k' `0 D% O) `
- }# q* X; G0 H/ Z' Z& Y8 F! k
. S# e' b/ X# T6 C0 e- /* Enable ADC */
$ p% h3 z D. M4 c2 { t - LL_ADC_Enable(ADC1);* d! Q0 `4 j6 d% h
- , e4 H" r+ X" E( ?9 c2 K& U. R
- /* Poll for ADC ready to convert */
3 f0 L9 q7 _6 y3 Q8 M - Timeout = ADC_ENABLE_TIMEOUT_MS;/ D9 o# K8 U- N
- + e9 X6 Q! a4 h# Z h3 q3 E
- while (LL_ADC_IsActiveFlag_ADRDY(ADC1) == 0)' w& A$ x0 I1 ?: f% r T/ M6 _
- {
/ W4 C+ d( {' T- K - /* Check Systick counter flag to decrement the time-out value */7 F* p" M2 I. a P! k9 V2 I
- if (LL_SYSTICK_IsActiveCounterFlag())
( i9 o6 O) h% k! n) X4 R& } - { j( Y1 P& N5 w+ \
- if(Timeout-- == 0)4 p7 G6 z$ [0 W: {
- {1 P& w7 y% v8 f1 t% O% T. U) B1 }6 a
- return 0;
' O+ G3 r ]$ X, w) J- L" o" _ - }) _0 [: [. X/ P7 B c/ u
- }, w. _1 V# x+ _
- }5 w: v* c- z' X9 R+ D% u8 q
- /* Note: ADC flag ADRDY is not cleared here to be able to check ADC */
" p3 O$ q7 u/ |' M. L, r& @ - /* status afterwards. */
A) A0 k4 T9 {" E! S1 f$ _ - /* This flag should be cleared at ADC Deactivation, before a new */6 C1 j% J+ l) A! t, F
- /* ADC activation, using function "LL_ADC_ClearFlag_ADRDY()". */
6 S/ e: O: l+ E! t# G - }
$ E/ R3 v$ v2 R/ e: m% T- N - return 1;
1 h$ t% B& A. g, F" s1 E8 \8 I$ | - }
复制代码 4 E# ~" L2 d" K! j, d7 `
因为ADC配置的是软件触发的单次采样,所以还需要有手动开始和结束采样的函数。7 R4 w8 O. x, X/ {0 A7 U
- A( t* H) X( r* R' j" G _
使能ADC采样:
' r" f1 v5 v) P/ q) f3 ], z, Z$ `# }/ }4 B
- uint8_t STM32ADC1SampleEnable(void)! c+ `3 S& |& i- I/ }5 [
- {1 ?+ s1 M& L" p7 A& T2 ~1 N3 m {
- uint32_t Timeout = ADC_ENABLE_TIMEOUT_MS;
5 d" M3 e! U) R( Y" o$ U5 l - LL_DMA_EnableChannel(DMA1,LL_DMA_CHANNEL_4); //先使能dma再使能adc,以防采样数据丢失
$ @( w8 O+ \* L# c+ |4 d7 F - LL_ADC_Enable(ADC1);
2 z# D, P% j% F H, m9 \7 Q" [ - Timeout = ADC_ENABLE_TIMEOUT_MS;
5 Q4 W" h; s( b2 E& l6 F - while(LL_ADC_IsActiveFlag_ADRDY(ADC1) != SET)
" _% l, z- o$ e& } - {) X" k9 Q" S2 S; q T) _
- if(Timeout-- == 0)' x2 j4 C4 R [9 _" E* z* b
- {
* K# l) B5 g2 e - LOG(1,"STM32ADC1SampleEnable ERROR!!!");4 f' |/ @0 Y: W: I
- return 0;2 H h0 R3 A' b1 t9 u
- }
' u5 D! t5 l6 }1 `7 @5 p+ @ - }
! ^$ b; T2 u% U- f# B" F - LL_ADC_REG_StartConversion(ADC1);+ b7 N0 W5 n! M
- return 1;0 ~+ w. \6 ^. c3 a: M
- }
复制代码
+ W W ?# |$ u- p& t5 |失能ADC采样:
2 c6 J+ X2 Z" o
0 d) M2 `: b3 T2 w2 ] B% m2 {- uint8_t STM32ADC1SampleDisable(void)
" E [3 W1 F2 d! {; ?( [1 x - {
0 U: U4 {/ d. S8 H* M; q3 } - uint32_t Timeout = ADC_DISABLE_TIMEOUT_MS;
& o* C" a- F0 f( v - LL_ADC_REG_StopConversion(ADC1);
( K, x- R0 s# J& n2 N+ F - while(LL_ADC_REG_IsConversionOngoing(ADC1) != 0)
. H+ K* K- ?; t8 O - {
: I# S' ^8 Q% T+ I0 G3 ?5 W1 @ - if(Timeout-- == 0)
, ]7 n" H4 @* Q7 p0 f - {
- F; l( L2 y* A( ?5 g1 W# Z K. H - LOG(1,"STM32ADC1SampleDisenable ERROR!!!");" h. W" F; H4 O! P1 P
- return 0;3 y4 s; J5 v, T8 Q* u
- }
/ ?$ t1 w2 j, W6 S* | - }
5 H7 K3 m! M% d/ {8 R - LL_ADC_Disable(ADC1); //先关闭adc再断开dma,以防采样数据丢失
* n( ^% C1 Y( F( z7 L - LL_DMA_DisableChannel(DMA1,LL_DMA_CHANNEL_4);
* Y/ j, v0 Z6 H" P/ [$ g# w3 b& r - return 1;( U5 G3 G, I8 s& ]9 M+ b7 X
- }
复制代码 ( F* d: {0 o- |$ S, ~' V! Q
进行ADC数据采集流程(采用多次采样求平均值的方式):+ v# r5 O; O" u) g6 a% I
j& R9 h$ [) T) V+ M1、当需要开始采样前,调用一次STM32ADC1SampleEnable()函数;
. Y3 P9 h* \+ c. \ }: O1 A
7 [3 T6 P0 N n4 l- P6 W( t2、在一个定时函数内调用LL_ADC_REG_StartConversion(ADC1)进行数据的转换(因为使用的是单次采样,所以每次进入定时函数时都需要调用),取出DMA数据进行累加;
* _: @+ k0 N6 }( c5 ^: H. w8 P5 A: N: S
3、达到需要的采样次数之后,对得到的数据进行运算,并调用STM32ADC1SampleDisable()函数关闭ADC通道。
/ i3 J' V8 R. S% A, y6 X
0 P' w, K2 {7 V* p& B4 O6 Z以上方式适用于需要间隔一段时间获取一次ADC数据时使用,例如需要每隔1分钟获取电池电压时,可以进行1分钟倒计时,然后连续进行十次间隔10ms的ADC采样求平均值。
6 E' b" d1 z# d" L* E0 X8 l/ a4 g2 d& j* O5 q
' i4 y$ v; C9 _1 N
|