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

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

[复制链接]
攻城狮Melo 发布时间:2023-4-6 16:45
一、RTC简介# X) r2 \+ S7 C# I$ \
        实时时钟的缩写是RTC(Real_Time Clock),核心是晶振,晶振频率一般为32768 Hz 。它为分频计数器提供精确的与低功耗的实基信号。它可以用于产生秒、分、时、日等信息。为了确保时钟长期的准确性,晶振必须正常工作,不能够受到干扰。RTC的晶振又分为:外部晶振和内置晶振。/ V2 F: q9 G3 o

% |- e4 I. c# l& R5 Q6 z        RTC时间信息存储在后备寄存器(RTC_BKUP)中,在STM32中,通常采用一个32位计数器来计时,而不是用年月日时分秒的分组寄存器,因此在处理STM32的时间信息时(设置或读取),通常要求先处理时分秒时间,再处理年月日时间,以确保时间和日期值之间的一致性。3 m: u- P/ S% f# G6 \2 P" r* ?

' e' Z. v$ _9 s6 G; B8 _        RTC和后备寄存器一般不会被系统或电源复原源复位,当从待机模式唤醒时,也不会复位,但是要注意的是,停止供电会出现重置,数值恢复到初始设置,因此如果要保持长期有效,尤其是断电重启依然有效,需要配置独立供电电源。) c" `0 V) d0 N0 `- v: u; |, K1 K
. k8 N7 m9 |: j3 T) w% _) j: Q4 F
        RTC时间是以振荡频率来计算的。故它本质上是一个计数器,只是通过换算呈现出日历时间信息。而一般的计数器都是16位的。又因为时间的准确性很重要,故震荡次数越低,时间的准确性越低。所以必定是个高次数。2^15 = 32768 ,而 32768 Hz = 2^15 即分频15次后为1Hz,周期 = 1s,因此RTC的时间基准是1秒。16位计数器最大值0XFFFFFFFF,按秒计算,能支持136年时长计算。
9 S! w/ Z% `& u  e' v9 ^: u7 f' Z% t& ]* T- I

$ G! H$ x) [+ \7 V' c二、工程创建及配置; O6 k  d/ `1 U0 m
        本文采用STM32L496VGTx-ali开发板来实现,该开发板是没有给RTC独立供电的,因此断电后会出现时间重置,但功能模拟演示还是OK的。本文假设已经移植了前面工程,并实现了lpuart1串口通信与电脑端的调试输出以及按键、LED灯驱动
& j4 P2 D2 o' D
6 h  _6 ^1 j8 G& m( O0 n* [1 }
        假设已经实现了lpuart1串口通信及按键、LED灯功能,现在双击.ioc打开cubeMX配置界面,开启RTC功能。数据格式支持二进制格式和BCD格式,在HAL中采用RTC_FORMAT_BIN(0x00000000u)和RTC_FORMAT_BCD(0x00000001u)标识。4 Y4 `. x5 X- x$ ]" i
7 y8 ?+ a* o# R6 r
1927c21adfaa4522bbf8ef9117ae2483.png ! K& G6 }) B- ^6 c
& ?7 w. z5 C- S3 y. C+ ^3 f9 w, a
         BCD(Binary Coded Decimal),即二-十进制编码,是用四位二进制码的 10 种组合表示十进制数0-9。这种编码至少需要用四位二进制码元,而四位二进制码元可以有16种组合。当用这些组合表示十进制数0-9时,有六种组合不用。
; Y1 Y2 a3 r8 u$ `3 L7 H
/ z8 o7 L4 K+ z+ {" h
        RTC功能开启后,时钟树视图可以关于RTC的选择器可以选择外部高速时钟、外部低速时钟、内部低速时钟,本文选择外部低速时钟(LSE)。
1 Y/ R: Q1 ?: i" l1 R
9 V! V$ m+ y. T+ _6 H! x
77fc485a203d4e63a71c21cde13eb335.png " D  U& v5 v) b  E
8 \. I8 I1 h  t- V- N( U0 _
         完成配置保存输出按钮(本文是为每个外设生成独立.h/.c源码),在生成的rtc.c文件中可以看到RTC初始化函数关于年月日、时分秒的设值和cubeMX上配置信息一致。
& z+ O" j9 s6 Q

) v( M3 v8 B0 r+ b# x& B        备注:如果在cubeMX配置界面没有选择数据格式,年日、时分秒默认是0X前缀的。" L& W0 c& }+ \( ?

0 l6 F. `# w  \8 x% U
629d8cf91bdc4be8b721bdc3bae4b3f7.png
5 D8 \! V$ y/ B
3 A! e( G, X8 M1 p1 a8 l" W1 B 三、驱动代码设计实现, F* j6 T/ |1 v: m+ a* F
        【1】在Core/Src/rtc.c文件中,调整RTC初始化函数,在RTC设置初始化时间、日期之前,加入后备寄存器的读取,判定是否已经进行过初始化,避免重复设置时间、日期而改写本该持续计数的RTC计数。
! \: f# S% k3 W4 k
  1. void MX_RTC_Init(void)# S8 V0 r$ R  ?+ W4 E4 S4 b
  2. {+ N3 J- U- i! n

  3. & h% y- D/ ~9 C
  4.   /* USER CODE BEGIN RTC_Init 0 */6 s$ D$ h2 L0 i! c* ]; H5 _, T
  5. 7 W7 Y* U6 v, J; W2 N
  6.   /* USER CODE END RTC_Init 0 */
    ! j2 M. ~+ l9 r4 S2 {! O( A
  7. % U! [3 @4 Z# {5 r/ g2 f4 ~- z0 J
  8.   RTC_TimeTypeDef sTime = {0};
    , v9 t$ C: n3 @1 [8 E& I
  9.   RTC_DateTypeDef sDate = {0};
    ( p; x/ t6 [3 r  v7 M

  10. 5 x/ ~# i5 Y) A) z  x
  11.   /* USER CODE BEGIN RTC_Init 1 */% U3 z8 S: F! v) y
  12.     //新增8 S( v1 E7 e; i+ A
  13.   __HAL_RCC_PWR_CLK_ENABLE();//使能电源时钟PWR
    9 X8 z$ e0 M/ s$ [4 e
  14.    HAL_PWR_EnableBkUpAccess();//取消备份区域写保护) K5 `, a5 e1 T/ g
  15.   /* USER CODE END RTC_Init 1 */+ t+ K9 g7 @( M. ^, q% T! T

  16. 9 u7 `' {  r' F5 h
  17.   /** Initialize RTC Only: O" \- S- H( o7 Z5 K
  18.   */# w, p# Q7 l) q1 d5 Z! W& q
  19.   hrtc.Instance = RTC;! K- v$ `- v7 i6 t" ^
  20.   hrtc.Init.HourFormat = RTC_HOURFORMAT_24;( l, I7 ^. S4 z5 }( `
  21.   hrtc.Init.AsynchPrediv = 127;; k7 |, Z7 e+ u. g- c
  22.   hrtc.Init.SynchPrediv = 255;
    $ r3 l' R+ O3 N  S! u
  23.   hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
    # l5 b& b4 ~% k7 e8 a
  24.   hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;8 S3 M$ {0 n$ C- ?: {2 C( m; h
  25.   hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;. n# s; `9 ^) B. k
  26.   hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;  p# F$ V4 F3 I: A* d6 P+ P
  27.   if (HAL_RTC_Init(&hrtc) != HAL_OK)6 ?  ]2 P! ^/ K4 q- k& q+ H
  28.   {/ _6 G6 G6 R0 c* I
  29.     Error_Handler();
    - g" w. m  ?0 }4 T  B7 `) G+ m; V
  30.   }
    0 U* P! K8 R4 T$ K6 L% M
  31. ) ?- l- K7 F' L& C* {
  32.   /* USER CODE BEGIN Check_RTC_BKUP */- r6 R) T. E$ _
  33.     //新增. a" k$ N' e2 S1 \
  34.   if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0X8888)//判断是否首次上电
    # H4 t  G4 U& Y- B( \  |
  35.   {
    4 W: s/ x( |6 |8 ]9 L
  36.           HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0X8888); //标记数值(写入上电检查数值)
    9 @" a- U6 G  `6 p/ w2 E# B4 D
  37.   /* USER CODE END Check_RTC_BKUP */
    / B+ Y' g0 ~3 d4 H- G; i

  38. " j% W* J" C. \0 U$ w+ Y
  39.   /** Initialize RTC and set the Time and Date: A. z* p0 ^; V( `% Y
  40.   */
    " a  U" N4 N* a4 J3 A
  41.   sTime.Hours = 12;
    ' I5 a9 }  O) g% K
  42.   sTime.Minutes = 30;
    4 e, `8 o* e8 ^+ Z6 T$ l# I6 W
  43.   sTime.Seconds = 30;
    " C* g7 \/ D( H( \: u+ |, D
  44.   sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    ( Y8 p( i- P/ l! @
  45.   sTime.StoreOperation = RTC_STOREOPERATION_RESET;, l  V7 N5 J  k
  46.   if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
    $ i4 `1 l/ M/ M7 l% I
  47.   {
    2 {* D/ j: p6 [$ U5 _% R/ B7 I
  48.     Error_Handler();
    7 g& _( v) J. ~5 P; L5 W, U' Z
  49.   }" Z1 }8 k" Y$ s% P# s
  50.   sDate.WeekDay = RTC_WEEKDAY_TUESDAY;! a3 ~% [5 N: ^- z* B
  51.   sDate.Month = RTC_MONTH_DECEMBER;3 l2 Q" |! i5 n
  52.   sDate.Date = 20;
    . w! V) K, W' Y+ \" A% G) P$ _
  53.   sDate.Year = 22;+ T. c& m5 M& a% g- y. l/ B& K

  54. ; f$ @9 d3 _8 l; c
  55.   if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
    3 x# R* Y' A8 O! q+ u5 c
  56.   {* f1 v8 }  T9 [  K& L0 r
  57.     Error_Handler();
    1 u; b8 M+ T* L. K: \  e: c( e% O
  58.   }
    % d5 h! v9 m. w# N3 @1 O7 E* m
  59.   /* USER CODE BEGIN RTC_Init 2 */& K+ @' `' P6 I0 Z
  60.     //新增" H& a7 V2 y- k( Y
  61. //  printf("手动初始化测试!\r\n");//显示初始化信息) d9 z% e/ B6 m# h; i' r7 o
  62.   }
    ! {" O# l% n. z9 C3 @0 L+ k
  63.   /* USER CODE END RTC_Init 2 */& l; q) H1 t# x( Q$ H  Q! u

  64. 0 b! x- {! H3 S+ f: J, k& |9 F
  65. }
复制代码
& G$ @  K; P5 `. P+ F
        在main.c文件中,加入按键、LED灯、串口外设驱动头文件,
8 D4 E/ W0 E- L1 V* c5 N
  1. /* USER CODE BEGIN Includes */
    5 d3 O, ?6 Y0 R! L
  2. #include "../../ICore/key/key.h"
    6 s' J  e/ O, L3 v9 U# y
  3. #include "../../ICore/led/led.h"
    ( u2 R; x  z2 W6 F4 J
  4. #include "../../ICore/print/print.h"7 ^0 x. m& K* n8 `  H1 q
  5. #include "../../ICore/usart/usart.h"/ p, z6 Q$ t- P; V! ^' n# u0 A
  6. /* USER CODE END Includes */
复制代码
+ S2 `" v3 G: U5 E
        在main函数中声明RTC日期、时间读取缓存变量。
8 \. e6 z8 |; u  A
  1.   /* USER CODE BEGIN 1 */
    % Y7 ]4 N+ T* j! o# \
  2.   RTC_DateTypeDef RtcDate;
    : T- e$ I. V6 v* _
  3.   RTC_TimeTypeDef RtcTime;
    & m4 Z6 N* P0 [. z. ]
  4.   uint8_t time_set = 0;//用户设置时间标记
    2 c3 S8 ?1 k; Z
  5.   /* USER CODE END 1 */
复制代码
8 |8 }9 w  J* B
        在main函数中开启串口lpuart1的中断接收。
, Q9 Q7 C2 A' F7 u0 Q6 i1 M- L( f, Z
  1.   /* USER CODE BEGIN 2 */
    ; h' C6 C/ f, E2 U9 y9 v  G% H- F
  2.   ResetPrintInit(&hlpuart1);
    6 L) w$ ^2 D8 R; `3 I
  3.   HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断
    ( L" |- Z8 o6 R0 W7 k. s7 l$ v
  4.   HLPUSART_RX_STA = 0;
    * [/ i5 ^, a1 N; c+ x0 g
  5.   printf("APP reset now!\r\n");
    : \. E/ ^$ n3 ?! G7 g5 S% i
  6.   /* USER CODE END 2 */
复制代码

' j7 v- r7 i5 U: M        在main函数循环体中,实现按键KEY1获取RTC时间以及按键KEY2设置RCT时间,注意RTC设置时间、日期和RTC读取时间、日期时,数据格式保持一致,最好和CubeMX设置时一致,例如读取、写入RTC时间日期都采用RTC_FORMAT_BCD格式。
: \  `; }- o5 @
  1. /* USER CODE BEGIN WHILE */
    6 M+ n" n9 H6 j
  2.   while (1)) ^# ]5 A# w+ p: a7 i2 c, {0 ]% K
  3.   {
    6 q4 L+ H3 D# {$ u9 P  g
  4.           if(HLPUSART_RX_STA&0xC000){//溢出或换行,重新开始
    1 T: p  ^7 g: M: H$ B! J$ X2 y
  5.                     printf("lpuart1:%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
    0 X5 z" g- Y) A8 j- v/ }; c/ [
  6.                     if(time_set){
    ' ?# l. {2 h/ D, L" d2 ^% X
  7.                             //将超级终端发过来的数据换算并写入RTC,读取数据为字符内容需要转换为数值1 K' u! i+ A1 D8 Z: s1 w
  8.                     RtcDate.Year =  (HLPUSART_RX_BUF[2]-0x30)*10+HLPUSART_RX_BUF[3]-0x30;//减0x30后才能得到十进制0~9的数据
      Y7 N$ O8 l  a, W* R
  9.                   RtcDate.Month =  (HLPUSART_RX_BUF[4]-0x30)*10+HLPUSART_RX_BUF[5]-0x30;
    2 i9 [- ^# I- T4 S, Y( Q1 s' X
  10.                   RtcDate.Date =  (HLPUSART_RX_BUF[6]-0x30)*10+HLPUSART_RX_BUF[7]-0x30;, `& x6 A( a! k# K7 i/ k. [
  11.                   RtcTime.Hours =  (HLPUSART_RX_BUF[8]-0x30)*10+HLPUSART_RX_BUF[9]-0x30;
    ; q' T  n- j- P
  12.                   RtcTime.Minutes =  (HLPUSART_RX_BUF[10]-0x30)*10+HLPUSART_RX_BUF[11]-0x30;8 C! O! O& J/ l. n
  13.                   RtcTime.Seconds =  (HLPUSART_RX_BUF[12]-0x30)*10+HLPUSART_RX_BUF[13]-0x30;" a( G1 e4 L6 d3 T
  14.                   //写入格式RTC_FORMAT_BCD或RTC_FORMAT_BIN,确保写入及读取数据格式一致即可% C0 y( W$ k7 A- o; i
  15.                     if (HAL_RTC_SetTime(&hrtc,  &RtcTime, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序: r  f$ l! G& A' P1 `
  16.                     {
    5 q* ]1 p, k+ Y; O) k3 t; ~  l9 y0 x
  17.                         printf("写入时间失败!\r\n"); //显示写入失败7 y4 E1 [0 @9 X
  18.                     }else if (HAL_RTC_SetDate(&hrtc,  &RtcDate, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序! s/ n! T, X" w1 \
  19.                     {+ H" Y# m6 Q+ F, L6 z1 D
  20.                         printf("写入日期失败!\r\n"); //显示写入失败
    . I- O( U/ ]0 g9 t+ V8 K2 g4 b9 A
  21.                     }else printf("写入成功!\r\n");//显示写入成功
    0 \# h0 ], {) X1 r
  22.                     time_set=0;
    . E0 C% s: j& U: E( d0 f
  23.                     }% B5 j: X* }$ n5 B$ a
  24.                     HLPUSART_RX_STA=0;//接收错误,重新开始% Z% `6 L: ~4 J3 @' r/ I
  25.                     HAL_Delay(100);//等待
    2 H5 o  K( C8 B5 x' o
  26.             }8 K( }4 O" J' T
  27.             if(KEY_1())
    ! w! ?; }0 b6 Q! f' A; K
  28.           {
      B: r- `* V8 W% e3 L1 s, t
  29.                     HAL_RTC_GetTime(&hrtc, &RtcTime,  RTC_FORMAT_BCD);//读出时间值# f0 Q. x4 S3 y& h
  30.           HAL_RTC_GetDate(&hrtc, &RtcDate,  RTC_FORMAT_BCD);//一定要先读时间后读日期
    # f# M, `+ T3 a
  31.           printf(" RTC实时时钟测试\r\n");" T' z4 z5 P9 f! K& U
  32.           printf(" 实时时间:%04d-%02d-%02d  %02d:%02d:%02d  \r\n",2000+RtcDate.Year,9 f7 ~1 }) `# A$ V0 O( K
  33.                           RtcDate.Month, RtcDate.Date,RtcTime.Hours, RtcTime.Minutes, RtcTime.Seconds);//显示日期时间# Z5 }4 t; C2 a; a) y5 _5 i
  34.           }4 o) J% ^, L# q! P& d
  35.             if(KEY_2()); N+ r% A" Q/ \8 X
  36.           {3 X! J, S* S" m5 e
  37.                     printf(" 请输入设置时间,格式20221220183030,按回车键确定! \r\n");
    , y, {( \- W8 c9 x3 f/ ~: t- \
  38.                     time_set=1;
    4 Q. @& {$ x8 K+ F1 m4 C# @2 I9 I( N# E
  39.           }7 b# l2 f' r8 E, Y& c7 M
  40.     /* USER CODE END WHILE */
复制代码
9 F( K, n" S# Q% w
         注意和前面简介说过,确保时间和日期值之间的一致性,先处理时间(时分秒),在处理日期(年月日),在stm32l4xx_hal_rtc.c的HAL_RTC_GetTime函数注释中也有明确要求如下,“您必须在HAL_RTC_GetTime()之后调用HAL_RTC_GetDate()来解锁值以确保时间和日期值之间的一致性。读取RTC当前时间锁定日历阴影寄存器中的值,直到读取当前日期以确保时间和日期值之间的一致性。”:
' y" ]) r2 |! e/ h6 ^$ ?; _; h* M6 |0 e4 W2 v( c
27887a24a62d46a1b27a1e4efe8855d6.png
/ S* U" ~4 u9 Q/ |- X0 ~5 h5 Z9 r4 o2 Z, {8 U

; l' G; x+ h( a7 a四、编译及测试
2 h- E4 c4 T0 F9 S        和本专栏其他博文一样,设置好工程输出目标格式支持,配置好运行调试支持后,点击编译工程及运行按钮,完成工程编辑及下载。      
  J. e" U& G' f2 w  V8 t2 q$ x% c+ r! A5 k3 }. ^- {7 x
e451e4c7dd9b4c5fa03817fca447c916.png ; v1 K# V4 b2 W9 v

- `  p: z. ]( i2 v5 s1 z
7 R& c$ l5 T+ P: O) R1 J
        打开串口助手工具,连接到开发板,测试$ [' Q! L! o' }  {. Z8 o! i8 s: x
. S+ W( C2 X" b
        【1】按键KEY1,读取RTC时间,读取出的时间就去前面在cubeMX 设置的时间,并开始计数,因此分秒有差异。
+ S! r$ b8 L; [1 x, e4 o7 }; ~0 I9 M# r
fb4cb2634b1c469cabfdac88376d1c3b.png
; o' B) u6 i: F6 o5 _0 g, F
% R2 b$ H; o. Q: X& G2 h         【2】按键KEY2设置时间,并按格式输入时间,然后在按键KEY1读取新的RTC时间,如下图所示,设置时间生效。' s; I- J0 D7 ]9 p5 f
' [4 f) Q: [+ {4 i: Z
30dc865389204e619affac8dc1687db7.png
& n* v* `! Y6 \* |' @( j3 \
7 b$ z8 k7 O* z$ k( U         【3】按开发板上的Reset按键,系统复位,再按键KEY2读取RTC时间,时间依然正常计数
# H; |3 n7 J( K- j, P0 C& V1 W  w* K; F$ d
1862d58464bb469ab8f231cf549be7c0.png $ l7 Z" a) s  k8 K, e
  {: B. P) b( M; g+ e$ ~

, Y; A  w. z7 Y2 K( ^4 g         【4】断电一会,然后重新上电,再按键KEY2读取RTC时间,时间恢复到CubeMX设置的初始时间(即为断电后,原来写入的标识无法保持,HAL_RTCEx_BKUPRead读取标记失败重新初始化)。
; y* @1 [1 d. w/ D$ d/ y  b0 N* w  k  e
6d8f19e2a6ac4eaf959ccae14e9e980d.png 4 F7 Q9 [- R0 }2 ^* I; r0 e3 Y

# l! v+ ], ?1 ]" \" F+ `- D————————————————& a7 z! I1 Z4 j) `
版权声明:py_free-物联智能如有侵权请联系删除& a" {8 c% U) e" ~7 d: O: @: G
/ E; L! ?" s( A& G/ r

9 K! Q9 J4 E% x5 b
1 Z* o9 c  M9 K$ K/ H
赞 收藏 评论1 发布时间:2023-4-6 16:45

举报

1个回答
SMT64 回答时间:2026-6-21 14:08:55

瞎J13乱发,傻福玩意

F1系列的RTC是计时器,你帖子里用的不是STM32L496VGT吗?看过这个芯片的数据手册吗?MD,抄都TM抄不明白,下边这张图看得懂吗?傻福

image.png

image.png
image.png

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版