你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32CubeIDE实时时钟(RTC)经验分享

[复制链接]
攻城狮Melo 发布时间:2023-4-6 16:45
一、RTC简介
/ g' N, `4 A0 ^8 Z) y        实时时钟的缩写是RTC(Real_Time Clock),核心是晶振,晶振频率一般为32768 Hz 。它为分频计数器提供精确的与低功耗的实基信号。它可以用于产生秒、分、时、日等信息。为了确保时钟长期的准确性,晶振必须正常工作,不能够受到干扰。RTC的晶振又分为:外部晶振和内置晶振。, J! E3 Y2 s/ f4 t1 @
, Y3 n3 T1 {/ t- E! y" _  D% z1 D1 X
        RTC时间信息存储在后备寄存器(RTC_BKUP)中,在STM32中,通常采用一个32位计数器来计时,而不是用年月日时分秒的分组寄存器,因此在处理STM32的时间信息时(设置或读取),通常要求先处理时分秒时间,再处理年月日时间,以确保时间和日期值之间的一致性。; B+ F+ A' u8 D" m3 I
; {' R$ t( M$ V/ T5 p7 D
        RTC和后备寄存器一般不会被系统或电源复原源复位,当从待机模式唤醒时,也不会复位,但是要注意的是,停止供电会出现重置,数值恢复到初始设置,因此如果要保持长期有效,尤其是断电重启依然有效,需要配置独立供电电源。3 E) @& G' {! N; g! w) V
: j) C$ f8 _1 D; h1 l6 P) W
        RTC时间是以振荡频率来计算的。故它本质上是一个计数器,只是通过换算呈现出日历时间信息。而一般的计数器都是16位的。又因为时间的准确性很重要,故震荡次数越低,时间的准确性越低。所以必定是个高次数。2^15 = 32768 ,而 32768 Hz = 2^15 即分频15次后为1Hz,周期 = 1s,因此RTC的时间基准是1秒。16位计数器最大值0XFFFFFFFF,按秒计算,能支持136年时长计算。
' p" U! u) I$ q: {& j7 a/ n
! Y: V7 Z2 \* X5 k5 d

3 r8 K! G% Z" N6 W7 K% I二、工程创建及配置* R$ _% {: G9 P6 e1 N2 k
        本文采用STM32L496VGTx-ali开发板来实现,该开发板是没有给RTC独立供电的,因此断电后会出现时间重置,但功能模拟演示还是OK的。本文假设已经移植了前面工程,并实现了lpuart1串口通信与电脑端的调试输出以及按键、LED灯驱动/ @# @6 Z) d0 {
% b/ p) E; B, X! C( l; P
        假设已经实现了lpuart1串口通信及按键、LED灯功能,现在双击.ioc打开cubeMX配置界面,开启RTC功能。数据格式支持二进制格式和BCD格式,在HAL中采用RTC_FORMAT_BIN(0x00000000u)和RTC_FORMAT_BCD(0x00000001u)标识。
* O" n$ W8 y; _; V( Y" a. I  m9 H
1927c21adfaa4522bbf8ef9117ae2483.png
0 P7 ~3 {3 K7 k+ r# Y& r
+ P  n' U; g/ F( ^         BCD(Binary Coded Decimal),即二-十进制编码,是用四位二进制码的 10 种组合表示十进制数0-9。这种编码至少需要用四位二进制码元,而四位二进制码元可以有16种组合。当用这些组合表示十进制数0-9时,有六种组合不用。
" P# q6 K+ `/ \9 h8 Q  I( ?

: r( I3 d% b1 S0 Q) q- @        RTC功能开启后,时钟树视图可以关于RTC的选择器可以选择外部高速时钟、外部低速时钟、内部低速时钟,本文选择外部低速时钟(LSE)。5 u6 w3 i" W. b$ Q8 o# {
( w) j0 W) b1 G3 t
77fc485a203d4e63a71c21cde13eb335.png 5 m0 E0 u, _. m6 _
* G7 ^, X1 p+ D( L
         完成配置保存输出按钮(本文是为每个外设生成独立.h/.c源码),在生成的rtc.c文件中可以看到RTC初始化函数关于年月日、时分秒的设值和cubeMX上配置信息一致。* X# q1 j9 N4 X+ y2 [3 ?8 [% a0 u) j
6 m' u0 F( ]7 ]* X- y6 Y, b+ w
        备注:如果在cubeMX配置界面没有选择数据格式,年日、时分秒默认是0X前缀的。
- P- l, x/ B- l0 w2 e/ u, P/ T9 H2 |) v" D) m, M
629d8cf91bdc4be8b721bdc3bae4b3f7.png 9 n1 c/ n6 E2 a; m

) Z) I$ R2 C4 B2 @6 ?' C 三、驱动代码设计实现5 H1 D1 I" A& E  {0 q
        【1】在Core/Src/rtc.c文件中,调整RTC初始化函数,在RTC设置初始化时间、日期之前,加入后备寄存器的读取,判定是否已经进行过初始化,避免重复设置时间、日期而改写本该持续计数的RTC计数。
" I" P& P* V6 ^" S
  1. void MX_RTC_Init(void)
    2 J1 N4 P1 K* E. M
  2. {$ [0 T! j  G6 h" D

  3.   g+ O& d; M! y
  4.   /* USER CODE BEGIN RTC_Init 0 */6 t: i# ^& Y3 }
  5. ) }& Q  n; I* j
  6.   /* USER CODE END RTC_Init 0 */
    ) T  ^# J) R% T1 \

  7. 3 i8 W) F8 F# K/ e/ o& Q/ P' t
  8.   RTC_TimeTypeDef sTime = {0};
    ' ]$ U2 ~# Y9 F. P' s8 S  Q
  9.   RTC_DateTypeDef sDate = {0};6 r, l. Q! a  Z4 T* a3 j* r  j4 y
  10. ) n: ^' m! S% M; s
  11.   /* USER CODE BEGIN RTC_Init 1 */
    . m& T- z5 R: _0 {
  12.     //新增0 ]: ^% `3 c/ ^5 ^5 C& y! q7 v7 e
  13.   __HAL_RCC_PWR_CLK_ENABLE();//使能电源时钟PWR
    - n8 H: ^& @( ]! p* X
  14.    HAL_PWR_EnableBkUpAccess();//取消备份区域写保护
    3 o. E* q: r4 U0 H6 L3 }
  15.   /* USER CODE END RTC_Init 1 */6 T& x- a8 [4 ^; R* M, l6 z
  16. , ]/ n* R! k: M# D9 B5 W9 S
  17.   /** Initialize RTC Only
    ! l5 A4 W3 }, D/ `6 [
  18.   */3 p& C) a4 l' f. Q5 D
  19.   hrtc.Instance = RTC;8 F9 a- X. E" e  `
  20.   hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
    8 g7 {& b/ J, x" m. W5 i
  21.   hrtc.Init.AsynchPrediv = 127;
    : ^  J& c9 y: X# a
  22.   hrtc.Init.SynchPrediv = 255;
    ; J1 L5 \: A* E. U
  23.   hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
    2 d6 F0 l; `+ ~( e
  24.   hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
    8 E. a; C  ?! ]% I' L% N: A
  25.   hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
    ; G! B- h( b5 n. ]0 |( Z7 F
  26.   hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
      k0 b3 X! u/ b
  27.   if (HAL_RTC_Init(&hrtc) != HAL_OK)
    9 _- Y8 o. ]# F" I% N, F! A7 {
  28.   {
    ; }0 `9 S2 }2 B" y: m
  29.     Error_Handler();# F+ H1 i( d9 b( m& c4 a
  30.   }
    - a$ o0 q$ ^' C4 E$ H6 a

  31. " `2 @5 P6 N9 M8 S; w
  32.   /* USER CODE BEGIN Check_RTC_BKUP */
    7 v# K2 F. l2 Y' U3 q8 m' I+ P8 J" u
  33.     //新增
    3 w3 J7 ?* o+ n" Q
  34.   if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0X8888)//判断是否首次上电
    ) F% J+ [- V: n& ^
  35.   {: e) d2 d; i" X7 P7 |+ ^; y
  36.           HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0X8888); //标记数值(写入上电检查数值)
    8 L: C. R9 k9 f6 F- v* G( v& L6 H5 X
  37.   /* USER CODE END Check_RTC_BKUP */
    % W5 ^9 I4 g  m2 x3 D. C1 V( o0 S

  38. + E5 e6 b* L5 x& f* p: W% [
  39.   /** Initialize RTC and set the Time and Date* ?. ]  G# e/ A9 G  m0 c
  40.   */
    1 }) b7 q7 Y8 U$ W3 v. @
  41.   sTime.Hours = 12;2 p' }7 ?# Q2 z; D& y
  42.   sTime.Minutes = 30;
    " W, E+ E$ Y6 ]# a
  43.   sTime.Seconds = 30;, s+ _4 g; c4 K
  44.   sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    % L  V' c9 U$ ^; d+ \
  45.   sTime.StoreOperation = RTC_STOREOPERATION_RESET;: X1 }$ n& }6 P* W& M- G: E* {
  46.   if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)  o, D& ~" t9 I5 ~# n' u
  47.   {
    ) g9 v& ?0 J, D8 n8 ?" [4 l
  48.     Error_Handler();
    % e  Q0 l& Q  m7 L; N- U7 O
  49.   }
    / \1 K; @: }( e. J
  50.   sDate.WeekDay = RTC_WEEKDAY_TUESDAY;$ g- Q/ o1 l% H( ?% v# y
  51.   sDate.Month = RTC_MONTH_DECEMBER;
    : [0 l( x4 p: w3 N4 {/ i7 h, f9 E
  52.   sDate.Date = 20;! E. D; Y( ^) h6 o! s# {  {
  53.   sDate.Year = 22;6 a' u- I9 v6 i0 M0 m$ j

  54. 5 E2 F0 t/ u: F0 w
  55.   if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
      A) G  @" B) I  G
  56.   {
    ; o" m" l* G  @" P
  57.     Error_Handler();
      e! i- E7 x) L: w' p
  58.   }- p2 l3 x9 T; M3 v* Y1 i
  59.   /* USER CODE BEGIN RTC_Init 2 */
    5 L- D5 a' x! x3 q5 c" v/ I. d$ A+ Z
  60.     //新增8 J2 K: |7 D/ S4 _4 D! S2 k
  61. //  printf("手动初始化测试!\r\n");//显示初始化信息
    3 d3 b! C8 P3 ^( t7 `
  62.   }
      K9 a: u) Z2 i: i6 \; w9 i# q
  63.   /* USER CODE END RTC_Init 2 */
    6 l1 Z5 w. v$ s3 F* {, c4 H9 w

  64. ; ]' p8 G' b& Y" I) _5 v( O% m4 X; n
  65. }
复制代码
+ A7 |( g3 r; I$ @
        在main.c文件中,加入按键、LED灯、串口外设驱动头文件,
* H2 Q1 z% h7 C
  1. /* USER CODE BEGIN Includes */
    # p% s  L5 b' V: g+ T6 u' l
  2. #include "../../ICore/key/key.h"
    8 N; f& e1 ?1 P
  3. #include "../../ICore/led/led.h"
    1 n5 X4 [  `% t- O! T7 n* {1 \
  4. #include "../../ICore/print/print.h"
    6 G9 E8 M5 d1 T4 P$ w
  5. #include "../../ICore/usart/usart.h"" Q) [  e* {2 o6 S' ^9 t
  6. /* USER CODE END Includes */
复制代码
# |" G5 A: Y& N+ A1 u
        在main函数中声明RTC日期、时间读取缓存变量。! D* O1 K4 J! |% w
  1.   /* USER CODE BEGIN 1 */
    + E" F! ^9 b' O4 L- L! O4 J
  2.   RTC_DateTypeDef RtcDate;. O( G* }. i: Q! k- L
  3.   RTC_TimeTypeDef RtcTime;; [% S! |% F9 Y- f0 d  Y; P; K6 h8 P: y
  4.   uint8_t time_set = 0;//用户设置时间标记" j7 f+ S) U. u  K$ t
  5.   /* USER CODE END 1 */
复制代码
& u. h3 Q3 K- L: ~9 A  i8 k
        在main函数中开启串口lpuart1的中断接收。; I; V: n  H2 B5 `
  1.   /* USER CODE BEGIN 2 */  g0 W) b& O  N# M1 `
  2.   ResetPrintInit(&hlpuart1);
    2 N( ?) Y: E, S9 C2 S4 m  R
  3.   HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断
    6 _( {" _- n  ^; o5 R
  4.   HLPUSART_RX_STA = 0;1 W9 v% ~, }1 \
  5.   printf("APP reset now!\r\n");
    6 M8 P( Z) l% h3 @# M, ~& I
  6.   /* USER CODE END 2 */
复制代码
0 a1 \: T- ?" P$ h$ V
        在main函数循环体中,实现按键KEY1获取RTC时间以及按键KEY2设置RCT时间,注意RTC设置时间、日期和RTC读取时间、日期时,数据格式保持一致,最好和CubeMX设置时一致,例如读取、写入RTC时间日期都采用RTC_FORMAT_BCD格式。
5 ^# a8 A( T5 x4 S  [
  1. /* USER CODE BEGIN WHILE */
    / Z+ {8 U6 |" S! ^7 l! ?3 Y
  2.   while (1)
    ; E3 _) ?4 ?, y. E1 x
  3.   {  G$ N1 d* m6 t9 F. p2 c7 V
  4.           if(HLPUSART_RX_STA&0xC000){//溢出或换行,重新开始/ \$ V/ g/ i" T; B, A
  5.                     printf("lpuart1:%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
    ' E0 o2 {, o0 }9 G) o% W
  6.                     if(time_set){1 I8 P- R) S/ e( ^7 C! d+ d& X, ~
  7.                             //将超级终端发过来的数据换算并写入RTC,读取数据为字符内容需要转换为数值% }& R: h- v8 b  l/ _
  8.                     RtcDate.Year =  (HLPUSART_RX_BUF[2]-0x30)*10+HLPUSART_RX_BUF[3]-0x30;//减0x30后才能得到十进制0~9的数据
    - B: O) V/ g; b7 L( [) t
  9.                   RtcDate.Month =  (HLPUSART_RX_BUF[4]-0x30)*10+HLPUSART_RX_BUF[5]-0x30;3 o) C4 Q, K) k3 G& X: D3 s
  10.                   RtcDate.Date =  (HLPUSART_RX_BUF[6]-0x30)*10+HLPUSART_RX_BUF[7]-0x30;
    " V% s9 u! ]- m8 \8 p
  11.                   RtcTime.Hours =  (HLPUSART_RX_BUF[8]-0x30)*10+HLPUSART_RX_BUF[9]-0x30;2 w& e8 W1 Q$ H2 r
  12.                   RtcTime.Minutes =  (HLPUSART_RX_BUF[10]-0x30)*10+HLPUSART_RX_BUF[11]-0x30;
    % i7 V! T! o0 x: s) T8 S
  13.                   RtcTime.Seconds =  (HLPUSART_RX_BUF[12]-0x30)*10+HLPUSART_RX_BUF[13]-0x30;
    6 q# ]: X  r6 M
  14.                   //写入格式RTC_FORMAT_BCD或RTC_FORMAT_BIN,确保写入及读取数据格式一致即可# z6 {% `/ Z0 D. Y  W2 i) F2 r
  15.                     if (HAL_RTC_SetTime(&hrtc,  &RtcTime, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序! R  g/ N0 x4 c% x. E$ D- w# w
  16.                     {: Y; W0 d. n# W; K8 J: A$ m( I
  17.                         printf("写入时间失败!\r\n"); //显示写入失败
    3 ]( F. T2 V* T' N1 B2 S
  18.                     }else if (HAL_RTC_SetDate(&hrtc,  &RtcDate, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序
    ' P3 j7 w1 M) u: P9 M1 q$ _
  19.                     {
    ( {% D  K1 \6 Q% }( Q/ d7 w
  20.                         printf("写入日期失败!\r\n"); //显示写入失败3 o+ R( I6 a9 |- D6 b
  21.                     }else printf("写入成功!\r\n");//显示写入成功
    3 k- I3 P  i* Z
  22.                     time_set=0;' H7 c1 M! ]* P; V# x
  23.                     }
    - K  y# C* w1 g1 ]
  24.                     HLPUSART_RX_STA=0;//接收错误,重新开始
    ) M, F# b2 Z3 P2 m% N) r, l: _$ Y
  25.                     HAL_Delay(100);//等待
    2 X3 y: ?: c8 I
  26.             }
    - B" t3 L+ l- i
  27.             if(KEY_1())* N# Q1 D  W3 }/ ^
  28.           {3 f. C9 A1 z* w8 p
  29.                     HAL_RTC_GetTime(&hrtc, &RtcTime,  RTC_FORMAT_BCD);//读出时间值
    : Y" T; [* X+ G1 ]' |) u
  30.           HAL_RTC_GetDate(&hrtc, &RtcDate,  RTC_FORMAT_BCD);//一定要先读时间后读日期) e- e! T6 S1 Q" m+ d/ i
  31.           printf(" RTC实时时钟测试\r\n");( u# c' I/ K- N6 Q7 z9 {% ^
  32.           printf(" 实时时间:%04d-%02d-%02d  %02d:%02d:%02d  \r\n",2000+RtcDate.Year,
    ; s# D2 r* H" A: v3 j
  33.                           RtcDate.Month, RtcDate.Date,RtcTime.Hours, RtcTime.Minutes, RtcTime.Seconds);//显示日期时间
    " b) L, s) |  {5 O3 s
  34.           }
    - S% ?5 L9 W( [* J4 K
  35.             if(KEY_2())0 y( _7 r0 [4 L1 q2 C" h, H0 x
  36.           {6 a; ]* e5 \! l1 _" g# H
  37.                     printf(" 请输入设置时间,格式20221220183030,按回车键确定! \r\n");: L5 n% I. X. ~, K6 S3 x5 @3 o* M% K5 \
  38.                     time_set=1;
    ; D+ K; J$ P1 T9 a9 f2 P
  39.           }* t, K- K, W5 d9 ~4 p
  40.     /* USER CODE END WHILE */
复制代码
! C1 B: t7 O7 U0 F% o; ^3 o
         注意和前面简介说过,确保时间和日期值之间的一致性,先处理时间(时分秒),在处理日期(年月日),在stm32l4xx_hal_rtc.c的HAL_RTC_GetTime函数注释中也有明确要求如下,“您必须在HAL_RTC_GetTime()之后调用HAL_RTC_GetDate()来解锁值以确保时间和日期值之间的一致性。读取RTC当前时间锁定日历阴影寄存器中的值,直到读取当前日期以确保时间和日期值之间的一致性。”:
, Y* p1 s" k8 r5 D
* h5 B. {7 |' i! F% E
27887a24a62d46a1b27a1e4efe8855d6.png 3 N9 g1 Z7 T4 q; I! x  u

- b0 p7 [. [* v/ U* j

) W" l" \. v; g0 D; h四、编译及测试3 ?# h8 s; C2 u2 j' v1 ^) C0 e
        和本专栏其他博文一样,设置好工程输出目标格式支持,配置好运行调试支持后,点击编译工程及运行按钮,完成工程编辑及下载。       0 a1 @" C: q& W

# I  O- J" q* ^7 I0 I
e451e4c7dd9b4c5fa03817fca447c916.png
( Y, B; C: F* i. B: q# J# N' V9 I. C1 Q3 |/ t7 k
! n7 S& P; K9 c8 c- {8 u9 g; e$ p
        打开串口助手工具,连接到开发板,测试3 b  l7 a0 Q( e. p: N  {

/ f1 `8 U* O8 _; |/ b1 J$ _        【1】按键KEY1,读取RTC时间,读取出的时间就去前面在cubeMX 设置的时间,并开始计数,因此分秒有差异。0 ]0 f! Z* F/ c$ L+ L+ a/ Q

4 j, U: g3 O8 m1 e" \: L- k9 [4 M
fb4cb2634b1c469cabfdac88376d1c3b.png
2 w* R$ \# G. r7 H* a
' p2 V# i' o5 R5 g1 Y$ U2 @         【2】按键KEY2设置时间,并按格式输入时间,然后在按键KEY1读取新的RTC时间,如下图所示,设置时间生效。& z' o# G7 C, U; O3 w
& N1 T' }, s  j; N8 O3 ~0 S
30dc865389204e619affac8dc1687db7.png ) i3 C# ^- A. h

9 p# v( ]! t) V. b! L8 ~         【3】按开发板上的Reset按键,系统复位,再按键KEY2读取RTC时间,时间依然正常计数
5 H3 R3 t6 {* ^2 M1 c: y/ `$ R
5 y( W; I5 T# v4 V$ T5 h/ o
1862d58464bb469ab8f231cf549be7c0.png 7 e0 E' g4 Y/ `
. J+ N+ D  k% j! O

) R" \0 @1 p6 Z- i' I1 R         【4】断电一会,然后重新上电,再按键KEY2读取RTC时间,时间恢复到CubeMX设置的初始时间(即为断电后,原来写入的标识无法保持,HAL_RTCEx_BKUPRead读取标记失败重新初始化)。
* B2 N+ U- N# G* f( a! w! F6 X% h( B8 f! j4 d7 K
6d8f19e2a6ac4eaf959ccae14e9e980d.png
% C3 U: ]/ r. \& e; ^# V
' B: J( N6 v2 R2 q& F3 ^
————————————————
% o; n6 N' J# ^$ |版权声明:py_free-物联智能如有侵权请联系删除
) b' y; [# r% v9 r+ _4 \/ y# |2 e- h1 {8 e

4 k, z, ~7 J3 X' I" D! E5 u' Q. ^$ f' L. S' `9 G: E
收藏 评论0 发布时间:2023-4-6 16:45

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版