前言
$ c) f$ e8 J& h0 W" a7 u, X% m% uSTM32F30x 系列的 12 位 SAR ADC 有很多鲜明的特色性能,比如采样率可以达到 5 MSPS,可支持差分输入,等等。但是,由于设计的不同,在使用上也有不少不太一样的地方,我们在使用 STM32F30x 的 ADC 外设的时候,还是要仔细了解一些使用的细节。 |* v" ?" d' D' \
4 D5 p) M+ I9 @% z7 }6 ~# n( d
. ]3 F5 Q7 v( D2 a, T0 `
问题
# v0 W3 q3 R" v某客户在其产品的设计中,使用了 STM32F302CCT6。客户在使用过程发现 ADC 在工作情况下会有各种各样奇奇怪怪的问题。
2 f4 o7 C {3 ?. n, ?+ C* Z. C* l( E3 w7 e8 x, A
9 N. F5 b+ G" l& h$ z$ t: v8 @
调研# `( N% j2 k" d* c4 x* _- w. i# u
1.了解问题& C7 M; D5 ?' v W6 k0 _# c+ x
客户在开发中使用了 STM32F30x 的标准外设库 STM32F30x_DSP_StdPeriph_Lib_V1.2.3,在其程序设计中,ADC1 是在使用的时候才打开的,在不使用的时候将 ADC1 关闭。通过调用 void ADC_DisableCmd(ADC_TypeDef* ADCx)子程序,执行ADC_DisableCmd(ADC1)将 ADC1 关闭。
( ^( y! R' @" v- p
) V0 ^. H" d- |: K2 ^& u( ^5 Z7 J* t) y仔细察看程序,发现程序中在 ADC1 的“打开→关闭→打开→关闭→…”循环的关闭中,执行了两次ADC_DisableCmd(ADC1)。' ]) n+ W& s6 S; }6 S5 q/ k
& f2 J$ V! G, y2.问题分析
, H* A6 B0 B6 V/ R& |0 q6 X8 Z* d% Z通过学习 STM32F30x 的参考手册,可以知道 STM32F30x 在对 ADC 进行关闭的操作与其他系列是不一样的;其他系列只要将 ADON 位清零就可以停止转换并使 ADC 进入掉电模式,而 STM32F30x 则不一样,它是通过置位 ADDIS 位来关闭 ADC的。在 void ADC_DisableCmd(ADC_TypeDef* ADCx)程序中也可以看到这一点。& i. v: |: V% ?, d+ V- S; [0 K
- void ADC_DisableCmd(ADC_TypeDef* ADCx)1 O. Q0 c" `% ^2 v. U3 P1 d
- {
1 D( Z+ k3 h! W2 p+ Z; M, e0 a0 _/ g - /* Check the parameters */
: U: R2 R7 a; a - assert_param(IS_ADC_ALL_PERIPH(ADCx));
/ U, z: |9 g1 p" A6 Z/ | - /* Set the ADDIS bit */
6 X1 O3 _. o! j1 R* i9 ^5 i. [) e9 k - ADCx->CR |= ADC_CR_ADDIS;
e B0 A) w: {' B* o$ o+ _. H; E3 v - }
复制代码
# A4 u- f/ ]: n& v! \( h8 A但是,问题来了,ADDIS 位是在什么情况下都可以置位的吗?9 T6 |2 C7 F& l, d8 f H" n X
我们来看一下参考手册中对关闭 ADC 的软件流程的描述:
+ G3 ?. d) o" q; D9 j' {4 ~, v8 p6 u5 H9 X' m
! X% _$ H) E& V, S/ m
# q! Q! D2 O( n: }2 \! m
从描述中,我们可以知道:在置位 ADDIS 之前,必须先检测 ADSTART 位和 JADSTART 位,确保他们为零,也就是说没有正在进行的 A/D 转换。在 ADC Control register - ADCx_CR 寄存器中对 ADDIS 的描述也注明了:( D4 \* [1 g% m6 r$ y
9 V0 {* W* A5 A4 T, `
- ~# ~% Z P L
$ k' \& i# D7 ~" U% \. Z2 N# L
关于这一点,大多数人都是会注意到的。但是,注意这个就够了吗?关于 ADC 的控制位,在参考手册特别使用一个小节对向控制位写访问的限制进行详细描述,此小节为“Contraints when writing the ADC control bits”,在这一小节中,有一句话值得注意:5 X+ C; `7 v8 s
$ Y. \8 C* i$ y2 W* G1 ~6 ?
% B z" j; Z5 x; s, q7 N
' ]" \: ?# @1 S8 B注意这里的用词 only if,它的意思是“只有在 ADC 是打开状态,而且没有正在等待的关闭 ADC 的请求的情况下,也就是在ADEN=1 且 ADDIS=0 的情况下,才允许软件对 ADCx_CR 寄存器中的 ADSTART,JADSTART 和 ADDIS 位进行操作。在底下的“Note”注意中写道:7 U1 M, x5 H, X4 X
! V/ K7 ?3 A$ d
/ V3 w- W, B5 P! o
2 B. K" ~5 H, c1 ~这个注意说,这些禁止的 ADC 写访问行为是没有硬件保护去禁止的,错误的操作行为将导致 ADC 进入一个未知的状态。要恢复这种状态,必须将 ADC 彻底关闭(将 ADCx_CR 中的所有位全清零)。
' @$ M0 @- S5 p( i7 D0 X( \+ I
/ c. t' s! [1 _9 L* e所以,现在可以知道,ADC1 工作不正常的原因正是因为连续执行了两次 ADC_DisableCmd(ADC1)。第一次执行ADC_DisableCmd(ADC1)时,当 ADC1 已经有效关闭时,ADEN 和 ADDIS 都被硬件清零,这个时候第二次再去写 ADDIS 位就是个错误的行为了,将会导致 ADC1 进入未知状态。
) o6 H0 G! k/ e
: ]) q! u# f% G8 @1 r9 r* n3.问题解决
7 u0 z1 k, R1 _9 [在解决问题之前,先来看一下 STM32Cube_FW_F3_V1.2.0 库中对 ADC 进行关闭的操作。打开 stm32f3xx_hal_adc_ex.c 文件,找到 static HAL_StatusTypeDef ADC_Disable(ADC_HandleTypeDef* hadc)函数,其程序内容为:
5 t2 k+ |2 }& ^" H1 g+ j- static HAL_StatusTypeDef ADC_Disable(ADC_HandleTypeDef* hadc)( _. k) L/ u' v' }6 E. r1 B$ ~8 n
- {
& c9 d6 g' n4 H% x2 a4 j - uint32_t tickstart = 0;
$ r s8 V: q! H/ [
* H- G. L. r) d1 Y7 `& k3 z" ~- /* Verification if ADC is not already disabled: */; Z# y. A( n4 w
- /* Note: forbidden to disable ADC (set bit ADC_CR_ADDIS) if ADC is already */
3 w1 f; u# _1 S+ l - /* disabled. */% s/ _) P7 J w0 J
- if (__HAL_ADC_IS_ENABLED(hadc) != RESET )
0 k. F9 c T6 i, g5 q y Q5 q( J* [ - {* a; }& Q5 }, X) P3 }3 S3 D
- /* Check if conditions to disable the ADC are fulfilled */
: m P) l( t; e$ E B - if (__HAL_ADC_DISABLING_CONDITIONS(hadc) != RESET)' w" x( w0 g% I! \' n* G
- {
( j, h9 `7 t4 D1 L6 x: q - /* Disable the ADC peripheral */" k( R9 ]8 b+ X- U( \
- __HAL_ADC_DISABLE(hadc);
% V S S- O8 t& b' Z/ \. r) j - }
5 ?) @0 f2 K4 g5 m" o6 z - else) }+ S7 F# }# ` B# ~$ a1 b" K
- {; ~: c& ?8 M4 V4 x7 q X
- /* Update ADC state machine to error */5 m9 n" X$ I8 ]4 t
- hadc->State = HAL_ADC_STATE_ERROR;4 I' Z4 x; S; Q4 M4 G% G4 t" t: s
c& q$ W y6 Z1 o! T: _/ K2 o/ V- /* Set ADC error code to ADC IP internal error */1 ~ D$ s0 o4 _( L' I" f0 W, J6 p
- hadc->ErrorCode |= HAL_ADC_ERROR_INTERNAL;
8 q5 b4 q% i2 w/ S" e* K- g% D - + l- I) i {! ?% z p d
- return HAL_ERROR;
( W3 O6 s* W) L* b - }' j9 R* j- @3 T4 E
- s% m8 e- M6 u# o$ y
- /* Wait for ADC effectively disabled */$ U9 O# J; t* |: d$ K
- tickstart = HAL_GetTick();2 \. l, X' c0 ?
5 y! }" Y! O X7 k2 W" k @6 s- while(HAL_IS_BIT_SET(hadc->Instance->CR, ADC_CR_ADEN))& m2 ?4 A2 s, I7 q
- {& z8 ?0 e4 \* I& D* W' {% g x
- if((HAL_GetTick()-tickstart) > ADC_DISABLE_TIMEOUT)& Q# `6 i+ Y8 i7 `. |
- {; ?7 C$ w) Y& u' w/ h: O
- /* Update ADC state machine to error */+ g2 p; `/ o' T
- hadc->State = HAL_ADC_STATE_ERROR;
& V7 h% h7 Q; Q4 V+ R+ N1 H - : T p: c& S ~; _5 W. d
- /* Set ADC error code to ADC IP internal error */
+ H5 A4 i! z6 U0 h2 I - hadc->ErrorCode |= HAL_ADC_ERROR_INTERNAL; o7 ?: v5 J4 }% F4 F
- - Z& H$ f2 U* f" k
- return HAL_ERROR;
- x V! n5 G, ?2 f- P9 b - }+ o1 S+ i# p- ^' F5 {- U: O
- }% T4 s: G! p- d# ^8 g$ ]" ?/ P/ H" f: R
- }# P! p; N I) Z1 O& U+ e) g
8 ?" o: B7 y" T) z4 z# B2 E& a3 Q3 ]- /* Return HAL status */, V- z* c; o8 |- d0 S1 U4 m
- return HAL_OK;( B7 l3 K, g" S/ j+ C1 ]
- }
复制代码
" r" {$ g& X' v4 }在这个函数注释中有个“Note: forbidden to disable ADC (set bit ADC_CR_ADDIS) if ADC is already disabled.”,再次告诉我们“禁止在 ADC 已经被关闭的情况下再次关闭 ADC”。然后,程序在运行中先对 ADC 是否已经被关闭进行了判断,如果已经被关闭,则不进行关闭操作;未被关闭情况下才会执行关闭操作。
8 W; D$ S$ }6 H! Z; k
5 g5 q6 E: f: V; Y- w所以,在使用标准外设库的时候,我们也可以参考 Cube 库中的这种操作来进行改善。- p9 K9 O( L0 J" I
可以考虑对 void ADC_DisableCmd(ADC_TypeDef* ADCx)程序进行修改:( l8 M0 Y; y0 [& b, q
- void ADC_DisableCmd(ADC_TypeDef* ADCx)
' k i9 v* f" Y+ Q/ G - {
6 m& O. [) g) M% N9 I - /* Check the parameters */# w/ t6 t1 k8 V) N/ a
- assert_param(IS_ADC_ALL_PERIPH(ADCx));
. }" A$ I1 C3 ]$ a$ Q1 ^ - ' M8 x$ C$ p3 k r2 i$ O3 c0 q
- if (((ADCx->CR & (ADC_CR_ADEN | ADC_CR_ADDIS)) == ADC_CR_ADEN) && ((ADCx->ISR &* c C5 U: M* X
- ADC_FLAG_RDY) == ADC_FLAG_RDY))6 S- V4 Z* ^$ }2 |4 G/ s- O
- {
* g8 |+ t% W3 Z( O - if (( ADCx->CR & (ADC_CR_JADSTART | ADC_CR_ADSTART | ADC_CR_ADEN)) == ADC_CR_ADEN)
# D$ `" w9 ?6 j8 H0 p - {
# h7 [( F& ]5 [6 C+ L6 \ - /* Set the ADDIS bit */ k0 t6 m/ m& l; _3 b& N8 [8 Q0 w0 _
- ADCx->CR |= ADC_CR_ADDIS;$ h! U e6 o+ i' R
- /* Clear ADC_FLAG_EOSMP and ADC_FLAG_RDY flags */
. H& J, B9 K5 X& F8 F( l, E - ADCx->ISR = (uint32_t)ADC_FLAG_EOSMP;
' z! J' C' f; c- Y - ADCx->ISR = (uint32_t)ADC_FLAG_RDY;1 q9 G9 b0 j4 U. s
- /* Wait for ADC effectively disabled */
7 |: c2 A8 P$ M7 v+ c, h3 L8 K - while ((ADCx->CR & ADC_CR_ADEN) == ADC_CR_ADEN)4 t# H8 @2 P# c0 H5 s
- {}
& J; w, b. h* c& h$ L8 }. r - }- e+ `0 O' n4 Q! L% e8 u
- }$ _4 `# h+ r B& W5 q( c) g
- }
复制代码 & W- k/ }& W8 o3 B. g3 h
当然,不动 void ADC_DisableCmd(ADC_TypeDef* ADCx)程序也可以,只需在用户程序中对这些限制进行判断即可。上面的while 循环中,也可以加入超时退出机制。9 w# G, o0 S. \& D7 R2 H! i
& n5 l9 v8 ~; W& R" ~: X' q, M4 P! Y g! u1 Z2 F$ ~; \$ r# B
结论
4 ?$ F; [/ ]- V2 Q连续两次对 ADDIS 控制位进行写 1,是错误的操作行为,将会导致 ADC 进入未知状态,工作不正常。
0 o1 E5 C. Y" ~& Q7 N+ K h5 L7 i' |
# Y8 @0 }0 {$ O R4 S
6 i: i( p( Z2 v1 O处理8 \8 ?( F' J% Z2 y: X
修改程序,避免错误的操作行为。
) {* ^- Y+ Q2 S5 R" x
" ]) L3 f N, r0 r1 N% z& ? [% T) |/ V
建议2 ~6 Z3 b5 d9 U
在使用 STM32F30x 系列的 ADC 外设时,必须要对控制位的操作限制有明确的了解。在 STM32F30x 的“快速 ADC 模块”培训资料中,也有对此进行描述,如下:% G* G) T! ?+ \% R1 w; ], Q
+ _: d* t8 R3 x, Z
v ^% \4 y: N5 H8 V
# l9 h! p0 b: ~3 G最重要还是要看参考手册,多注意一些小细节。
2 }. Y" s& P. U. M5 b, ]) o3 l6 t# p9 c* F' j' f# y4 b9 l
|