前言; y$ q7 F7 W0 Y" u4 X0 v# g" p
实时时钟(RTC)是一个独立的 BCD 定时器/计数器,用来提供准确的日历和时间信息。准确性是其重要的指标。! j4 Y: h. S6 E1 T/ M
' Y" C+ f$ J! A! A: y, z! e: v
问题1 h' \" V( i' L: C8 ]3 L1 [
某客户在其产品的设计中,使用了 STM32F429IIT6。客户在使用过程发现一个问题,虽然已经有使用电池对 VBAT 进行供电,但是在经常频繁的 VDD 上下电之后,发现时钟会比准确的时间慢几秒钟。
( w+ @" L/ N' Y! l; d& p1 Q& X6 e2 H
$ V4 _; s( I$ I; {- p3 m9 m r8 X调研
) h; B$ N( `/ u U! G1.了解问题! f+ e' G& q; S! @/ n
向客户了解其使用的固件库,得知他的程序是参考 STM32Cube_FW_F4_V1.3.0\Projects\STM324x9I_EVAL\Examples\RTC中的 RTC_Calendar 例程。于是找来 STM32439I-EVAL2 来进行验证,测试发现,
/ O( Z2 R6 v. \0 M: uSTM32Cube_FW_F4_V1.3.0\Projects\STM324x9I_EVAL\Examples\RTC 中的 RTC_Calendar 例程确实存在频繁上下电会导致时间变慢的情况;而对标准外设库6 n" x: V3 r/ s! ?7 w
STM32F4xx_DSP_StdPeriph_Lib_V1.4.0\Project\STM32F4xx_StdPeriph_Examples\RTC 中的 RTC_Calendar 例程进行测试,则不存在此问题。所以,怀疑 STM32Cube_FW_F4_V1.3.0\Projects\STM324x9I_EVAL\Examples\RTC 中的 RTC_Calendar例程存在 Bug。' F! [2 R0 H: ~% B1 M
; O c$ V1 t. L1 J8 K# x- r2.问题分析$ I2 \; T( a' o/ z. t5 p& ?0 h
仔细阅读 STM32Cube_FW_F4_V1.3.0\Projects\STM324x9I_EVAL\Examples\RTC 中的 RTC_Calendar 例程,分析一下main.c 主程序,“if(HAL_RTCEx_BKUPRead(&RtcHandle, RTC_BKP_DR0) != 0x32F2)”是用来判断 RTC 是否是已经被配置过的,所以怀疑的重点可放在这之前的“if(HAL_RTC_Init(&RtcHandle) != HAL_OK)”中的 HAL_RTC_Init()函数。$ V2 d+ l$ L+ S1 Y1 R5 W& w) x
1 L1 ~& O! n. J" D7 h5 y
4 L) e! Z3 g: I5 m进入位于 stm32f4xx_hal_rtc.c 中的 HAL_RTC_Init()函数,再进入其调用的位于 stm32f4xx_hal_msp.c 中的HAL_RTC_MspInit()函数,在这个函数中,可以看到以下代码:
' I1 E4 q+ ]# I) P/ K8 P- /*##-1- Configue LSE as RTC clock soucre ###################################*/
H7 X% W0 y C0 f2 m2 O - RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI | RCC_OSCILLATORTYPE_LSE;
0 K8 J( t+ p' O* \ - RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
5 E1 M4 h- m/ E2 v - RCC_OscInitStruct.LSEState = RCC_LSE_ON;% }4 T k% h) R7 U6 C
- RCC_OscInitStruct.LSIState = RCC_LSI_OFF;1 G3 q* Q1 q& }
- if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
0 u5 g1 C# v" q3 u. B8 P! \( I S - {
* n) y2 \& _7 |( P - Error_Handler();$ n6 E* O$ y4 G2 E6 }
- }8 i( P) ?' \- Y* i
- 5 ^. }6 s/ j; X( {3 L
- PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;0 s' _- c4 v- r! c6 S, @) p' z5 ^9 B
- PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;- X6 R8 V2 R2 K3 v, L: g
- if(HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
& ~4 A7 }* s U$ g$ a6 w; ]' r - {
8 u f0 j9 z; B1 |$ v - Error_Handler();
# C' E+ N& ?( w2 Z - }
复制代码
; J; @+ k2 a5 v) h+ z3 X这段代码中由于选中了 LSE,在其所调用的 HAL_RCC_OscConfig()中对 LSE 进行了重新配置。在 stm32f4xx_hal_rcc.c 中找到 HAL_RCC_OscConfig()函数,发现其对 LSE 重新配置的时候,对 LSE 进行关闭,然后再配置。
8 s# T% Q: k! @! R+ n7 t ^+ E
2 z" G7 u2 u0 e! \5 K4 T) d到此,回来再看 main.c,由于“if(HAL_RTC_Init(&RtcHandle) != HAL_OK)”位于“if(HAL_RTCEx_BKUPRead(&RtcHandle, RTC_BKP_DR0) != 0x32F2)”之前,从程序流程来看,每次 VDD 上电,都会进行一次 HAL_RTC_Init(),也就是说,每次上电都会有一个关闭 LSE 再打开的动作,这个动作多了,时间变慢的现象就变得很明显了。
& X8 E( E2 k/ \# C: Z# p/ Q1 F( h2 Z7 C# z1 {
3.问题解决
) H' J# s U, ?' ]$ z问题原因很明显了,那么解决办法也很简单,只需要将 HAL_RTC_Init()这个初始化函数挪到判断 RTC 是否是已经被配置过的if…else…语句里边就行了。如果是 RTC 已经被配置过的,就不需要再重新初始化一次了。如下:
7 h D* @3 Y" c9 f* Q1 s* U) b- /*##-1- Configure the RTC peripheral #######################################*/
3 x( }* h( l. g! ] v& S5 R - RtcHandle.Instance = RTC;. g7 Q9 c1 S* J3 J
- 1 Q3 M0 m7 _& ~. V6 Z; q1 S0 P
- /*##-2- Check if Data stored in BackUp register0: No Need to reconfigure RTC#*/
7 m. K. _4 C& X' n, }, E - /* Read the BackUp Register 0 Data */
3 k( e1 Z+ K% E" k9 H0 L - if(HAL_RTCEx_BKUPRead(&RtcHandle, RTC_BKP_DR0) != 0x32F2)
: g: n4 m5 T+ n - {0 ]( z# d7 R: F1 _
- /* Configure RTC prescaler and RTC data registers */0 c3 p# P8 E& R4 Z6 e" S" J
- /* RTC configured as follow:
4 ^* M4 B; ]" @ - * T7 I# j; i8 n: K$ x3 }
- - Hour Format = Format 242 I8 A, F1 S2 E6 w1 g- v
- - Asynch Prediv = Value according to source clock
9 L) j5 T( F5 X# t/ I - - Synch Prediv = Value according to source clock
* E8 y s; ^4 A8 X8 C- z1 ?* R. M - - OutPut = Output Disable$ n2 R3 }# c h
- - OutPutPolarity = High Polarity
5 t9 z" ^! c1 U - - OutPutType = Open Drain */
G6 V; B5 \# K7 p - RtcHandle.Init.HourFormat = RTC_HOURFORMAT_24;
2 k- C$ W7 e) A2 V( [- d* V - RtcHandle.Init.AsynchPrediv = RTC_ASYNCH_PREDIV;
9 n/ p5 B. O+ i - RtcHandle.Init.SynchPrediv = RTC_SYNCH_PREDIV;
6 r2 i, d$ r p! K - RtcHandle.Init.OutPut = RTC_OUTPUT_DISABLE;1 B: B. T+ n4 v: P7 v. J
- RtcHandle.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;5 }8 [! \, {* L+ A+ W2 l/ X6 W
- RtcHandle.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;1 O6 z7 \: P5 n, x* @" X$ b
1 x7 L# h- I4 v) Z# p0 g5 J3 f- if(HAL_RTC_Init(&RtcHandle) != HAL_OK). _7 q% x: j9 I$ d9 F2 c8 C( Y3 L( u
- {$ g7 o& f( {% Z# S" z! m) Z: ]
- /* Initialization Error */7 M8 Y4 Z& Q: C, `- Q: f$ W
- Error_Handler();* E$ U8 f" S0 t* n% I0 E
- }
% a3 N! y# h- |0 a& o
) {) F+ G" [9 G; J3 o- O# b) R5 Q- /* Configure RTC Calendar */
1 T: V5 R( T. q - RTC_CalendarConfig();- Q- ]: Z& j7 s4 X' e
- }
" A. \3 u7 n, o. N: ` - else- s" c( z- G7 S, ~ L
- {) ^$ W6 ?$ j9 W+ p. J( V
- /* Check if the Power On Reset flag is set */
0 Q- M8 d2 V5 }& A) M! O - if(__HAL_RCC_GET_FLAG(RCC_FLAG_PORRST) != RESET)$ ]" m0 u/ E9 s5 R$ j
- {) x, y8 @! h- L% `: `
- /* Power on reset occured: Turn LED2 on */ {/ u8 k# _6 o; y' r
- BSP_LED_On(LED2);
& B% u5 F' G) L" f - }
+ J. v& y7 b! D* M - /* Check if Pin Reset flag is set */
' l8 I' \# M" q. G: c; F - if(__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST) != RESET)
9 O; ^7 T: C7 s! c+ B1 a" A - {
+ ^0 x3 _, e! U3 S9 L - /* External reset occured: Turn LED4 on */
* q9 p5 z% m0 ] - BSP_LED_On(LED4);8 ^6 v ? v$ _* p- H& M _
- }/ e/ m5 O# q: K6 T" S. F) K
- 6 f6 i4 o+ Z- O
- /* Enable the PWR clock */
- Z4 V, U/ w2 k7 ]! I& o ] - __PWR_CLK_ENABLE();1 V# ~) g7 t3 ~# V
! u& r8 @/ Q$ Y% f( W) A- /* Allow access to RTC */
g, ?) B( n. M9 U' \5 `) c3 ^ - HAL_PWR_EnableBkUpAccess();
6 w1 r$ z6 I1 `! d! D) }
; h' J" P# E; r8 o2 ?( a- /* Wait for RTC APB registers synchronisation */0 F4 \6 V( Z+ b5 z" T& @
- if(HAL_RTC_WaitForSynchro(&RtcHandle) != HAL_OK)
/ ~1 K; {5 R$ s, }; G; G - {
2 w0 F" p. U7 j - /* synchronisation Error */
9 C3 H* d/ _! i$ M6 r: f - Error_Handler();
* }9 }1 f7 M! Y9 Z: Z - }
6 H" ?1 G3 ^) d - 3 |3 }0 }. Q" O; m! \( u- i; F
- /* Clear the RTC Alarm Flag */; g) S; J; Z6 N8 \4 @. R4 [/ z: C
- __HAL_RTC_ALARM_CLEAR_FLAG(&RtcHandle,RTC_FLAG_ALRAF);" m, z M7 W3 c! H; z* I* S6 g+ D
- 1 I, F# e6 G6 o# p4 N, r
- /* Clear the EXTI Line 17 Pending bit (Connected internally to RTC Alarm) */
% ~8 ^$ F4 s: ~% D$ D - __HAL_RTC_EXTI_CLEAR_FLAG(RTC_EXTI_LINE_ALARM_EVENT);
& B3 j; `$ F9 l* z3 Y0 x9 T O8 s - " w$ U9 w+ a" e
- /* Clear Reset Flag */# }9 X9 M8 Q+ V# m
- __HAL_RCC_CLEAR_RESET_FLAGS();' d0 V# |% c( n) ^
- }
复制代码
. g9 Z* ^( F8 Y8 w# D, p1 o' |结论 W* l9 H4 H& S, y
由于 STM32Cube_FW_F4_V1.3.0\Projects\STM324x9I_EVAL\Examples\RTC 中的 RTC_Calendar 例程没有注意到HAL_RTC_Init()函数里边会有关闭 LSE 的动作,而每次上电都会运行这个函数,每次上电都会导致时间变慢,上电的次数多了,变慢就很明显了。所以,例程上是有 Bug 的,需要进行修复。
' W. K) q% w. Z0 |
' N0 m' W! v. i2 ?: g; \0 e1 H/ T* z处理) I5 z7 [4 O. t) l6 [; C* c# G
需要将 HAL_RTC_Init()这个初始化函数的位置做个修改。如果 RTC 未被配置过,则进行配置;如果是已经被配置过的,就不需要再重新初始化一次了。标准外设库STM32F4xx_DSP_StdPeriph_Lib_V1.4.0\Project\STM32F4xx_StdPeriph_Examples\RTC 中的 RTC_Calendar 例程是没有问题的,参考此例程,修改得一基于 STM32Cube_FW_F4 的 RTC_Calendar 例程,见附件。) B+ k$ u# P' g
- p( c* \) v' z- C
* P `* I& Q4 L" v2 _+ e6 F4 L建议2 A9 i4 B0 q! ?7 {
上电时对 LSE 进行重新初始化可能会导致 RTC 计时不准确,所以在实际应用过程中应该对此注意一下。
- R! o( R% ]# Z3 U/ S
! u: }' l+ @6 i, ?0 x |