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

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

[复制链接]
攻城狮Melo 发布时间:2023-4-6 16:45
一、RTC简介
/ p$ x2 e) q# x        实时时钟的缩写是RTC(Real_Time Clock),核心是晶振,晶振频率一般为32768 Hz 。它为分频计数器提供精确的与低功耗的实基信号。它可以用于产生秒、分、时、日等信息。为了确保时钟长期的准确性,晶振必须正常工作,不能够受到干扰。RTC的晶振又分为:外部晶振和内置晶振。
% G! J! i! B5 R. z+ q6 A7 J. d. r$ P4 Y# c: X
        RTC时间信息存储在后备寄存器(RTC_BKUP)中,在STM32中,通常采用一个32位计数器来计时,而不是用年月日时分秒的分组寄存器,因此在处理STM32的时间信息时(设置或读取),通常要求先处理时分秒时间,再处理年月日时间,以确保时间和日期值之间的一致性。
& E! O! E6 `7 u$ E8 ?0 a2 Z& O: }- q
( u$ c% Z3 t9 c3 X        RTC和后备寄存器一般不会被系统或电源复原源复位,当从待机模式唤醒时,也不会复位,但是要注意的是,停止供电会出现重置,数值恢复到初始设置,因此如果要保持长期有效,尤其是断电重启依然有效,需要配置独立供电电源。& g* Z; M8 K# u2 i" T
1 g# R5 {/ W2 D) j* Z# o
        RTC时间是以振荡频率来计算的。故它本质上是一个计数器,只是通过换算呈现出日历时间信息。而一般的计数器都是16位的。又因为时间的准确性很重要,故震荡次数越低,时间的准确性越低。所以必定是个高次数。2^15 = 32768 ,而 32768 Hz = 2^15 即分频15次后为1Hz,周期 = 1s,因此RTC的时间基准是1秒。16位计数器最大值0XFFFFFFFF,按秒计算,能支持136年时长计算。- k5 l5 s/ T) r6 U; x

* y/ _. R, P/ i* e

& J. {+ Y# F3 {0 \二、工程创建及配置& O8 S' b, I: s; o5 z# Q2 o
        本文采用STM32L496VGTx-ali开发板来实现,该开发板是没有给RTC独立供电的,因此断电后会出现时间重置,但功能模拟演示还是OK的。本文假设已经移植了前面工程,并实现了lpuart1串口通信与电脑端的调试输出以及按键、LED灯驱动, u' \  j# w0 J- G9 @  q

5 |# H9 U# P+ I! f0 M" c        假设已经实现了lpuart1串口通信及按键、LED灯功能,现在双击.ioc打开cubeMX配置界面,开启RTC功能。数据格式支持二进制格式和BCD格式,在HAL中采用RTC_FORMAT_BIN(0x00000000u)和RTC_FORMAT_BCD(0x00000001u)标识。9 w8 F0 n2 e2 ?- a6 ~( T. k: ^7 h

$ V! n2 _: X8 b1 ?3 N/ ?; l( g
1927c21adfaa4522bbf8ef9117ae2483.png 7 x0 d/ S6 v6 R8 w8 R% U

: y* v- u' D0 h* C7 d9 F' `# Z         BCD(Binary Coded Decimal),即二-十进制编码,是用四位二进制码的 10 种组合表示十进制数0-9。这种编码至少需要用四位二进制码元,而四位二进制码元可以有16种组合。当用这些组合表示十进制数0-9时,有六种组合不用。9 n! `' e; b3 B3 m0 l, |/ X% E
% _& x6 d& r; T& l  m
        RTC功能开启后,时钟树视图可以关于RTC的选择器可以选择外部高速时钟、外部低速时钟、内部低速时钟,本文选择外部低速时钟(LSE)。
* n$ H4 Q! I' J5 H. W+ z% [% {8 J* V+ v! e# j
77fc485a203d4e63a71c21cde13eb335.png + n# C' ]( w) r
, H7 c' m; [1 }5 m% ^
         完成配置保存输出按钮(本文是为每个外设生成独立.h/.c源码),在生成的rtc.c文件中可以看到RTC初始化函数关于年月日、时分秒的设值和cubeMX上配置信息一致。
7 I  o) [6 C' W+ V8 s) L
/ ~% k+ y/ Q( z$ ^+ W
        备注:如果在cubeMX配置界面没有选择数据格式,年日、时分秒默认是0X前缀的。2 b/ T" K5 c2 {* Q
( ?1 a' I- @- j" n
629d8cf91bdc4be8b721bdc3bae4b3f7.png 0 e0 A5 m# k: Z3 D
( f8 @' N% Q; n% ~
三、驱动代码设计实现
2 Z% t1 @/ i& k        【1】在Core/Src/rtc.c文件中,调整RTC初始化函数,在RTC设置初始化时间、日期之前,加入后备寄存器的读取,判定是否已经进行过初始化,避免重复设置时间、日期而改写本该持续计数的RTC计数。0 G/ p5 d# Y7 Q+ S
  1. void MX_RTC_Init(void)
    $ B. Z( [# H( S; m7 v
  2. {
    * f8 p0 v( N" p' n3 ]3 Z

  3. . G3 \8 T+ n( a* K4 \
  4.   /* USER CODE BEGIN RTC_Init 0 */
    # C% i! c9 \7 l
  5. 6 \4 C% _! n8 L+ N! _
  6.   /* USER CODE END RTC_Init 0 */2 ~6 j$ t( m( f4 J; V* t
  7. 7 Z) g  R$ L% i7 N$ ~, w
  8.   RTC_TimeTypeDef sTime = {0};
    : C6 U$ z/ [/ q2 R: `# F; r& {
  9.   RTC_DateTypeDef sDate = {0};
    8 h9 a( I4 D8 c) a6 b) x9 X' ]9 j: \
  10. ' O/ H  g# g: b9 o3 h5 X
  11.   /* USER CODE BEGIN RTC_Init 1 */" B& n  U7 A  j2 c
  12.     //新增6 `* T" e9 p! Z1 L0 ~9 _
  13.   __HAL_RCC_PWR_CLK_ENABLE();//使能电源时钟PWR
    1 T3 M% r6 N" F' X7 D
  14.    HAL_PWR_EnableBkUpAccess();//取消备份区域写保护% h( ~) C2 ]; t: u- ~! T' w7 Q
  15.   /* USER CODE END RTC_Init 1 */
    / M+ k+ h  t. A2 V& J

  16. $ z: @: ^8 W, L
  17.   /** Initialize RTC Only
    . L4 [1 d) y, M! j+ @; \
  18.   */
      r/ f* x  N- t* L6 ]
  19.   hrtc.Instance = RTC;
    : N1 i' p1 F) X6 k7 |; b0 Y; X0 `
  20.   hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
    2 X4 @1 r7 I7 ~: w# O
  21.   hrtc.Init.AsynchPrediv = 127;( ~# G. m, e2 W. }: _
  22.   hrtc.Init.SynchPrediv = 255;3 c+ W, _3 C& j& @1 `! u
  23.   hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;6 M9 Z4 o& B8 q& b" S
  24.   hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
    " U  i2 _0 j8 |: o, y  w6 r- F! `
  25.   hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;! P9 P. j  q. [
  26.   hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;+ F! d$ a# ]: x8 [: c
  27.   if (HAL_RTC_Init(&hrtc) != HAL_OK)6 G' {% o: ?7 z6 p2 R: n6 r
  28.   {
    5 \9 I+ A$ r' M* I$ Y! J& @' X
  29.     Error_Handler();
    3 d+ N5 t" i, {: P
  30.   }2 \4 k8 b$ l2 a

  31. 2 k2 u9 B' q$ S; F* ^% j2 F
  32.   /* USER CODE BEGIN Check_RTC_BKUP */
    : ?4 y1 {6 P6 [2 i, x, C2 F
  33.     //新增
    5 U" \. N0 K! x! N8 p
  34.   if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0X8888)//判断是否首次上电
    ( J9 \6 Y' I3 c7 t2 V( q
  35.   {
    4 z! L! F% c- n: s9 M
  36.           HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0X8888); //标记数值(写入上电检查数值)5 Y7 ?& ~: L0 o1 ]* F! m7 \& d. r
  37.   /* USER CODE END Check_RTC_BKUP */! y% e8 M/ b; `! l5 U- z
  38. : @0 L$ ?+ X: e5 h6 N# t$ Y, E: ?. C
  39.   /** Initialize RTC and set the Time and Date
    ' G/ `+ f6 j$ g9 W/ D$ N7 w
  40.   */+ E2 j! n# A1 F6 e! p4 ?
  41.   sTime.Hours = 12;/ k, l& L5 |! J9 M, |) A8 A
  42.   sTime.Minutes = 30;, S1 b% [. A8 A/ n$ ?! a; ^
  43.   sTime.Seconds = 30;
    0 M# u. Y0 L+ y* I- {: ^. N6 d) n
  44.   sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    . I8 r6 T+ f8 g) ?3 @/ L1 z" A9 t
  45.   sTime.StoreOperation = RTC_STOREOPERATION_RESET;) B8 V; [$ S$ E& C# G, o
  46.   if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
    ' `# t  l6 k5 _
  47.   {5 n9 A$ K% l+ T6 Y
  48.     Error_Handler();) d: u6 o! c- L  t/ ]
  49.   }
    # A5 V* C2 a# V! K4 u
  50.   sDate.WeekDay = RTC_WEEKDAY_TUESDAY;: Q0 D" b3 p$ V  I4 @7 w) o  [
  51.   sDate.Month = RTC_MONTH_DECEMBER;' f9 Q7 ]6 c! M1 P9 l  e
  52.   sDate.Date = 20;
    0 r! y. d3 C0 e5 H0 Y6 S6 ]* c7 s
  53.   sDate.Year = 22;! _9 o+ V  e; c6 X) J8 @5 `$ _
  54. ' `+ P& O0 E' `0 k$ e& L$ i
  55.   if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)  w+ @. F8 U  q) o0 @
  56.   {
    ) G# w1 G2 I/ h1 P& \
  57.     Error_Handler();% e! S# v9 b* n) E, i7 f1 {" o
  58.   }' u+ N! L8 I! A1 |& z  f
  59.   /* USER CODE BEGIN RTC_Init 2 */
    $ V& H0 v  i9 [$ h. u# K3 o5 G
  60.     //新增" H6 R4 ?/ X) [, [) W6 T5 n; s+ O. p# W
  61. //  printf("手动初始化测试!\r\n");//显示初始化信息3 z  B) J- g; T% f/ G9 O
  62.   }
    7 I$ ~; T' @: e( t8 K* a9 ^8 t
  63.   /* USER CODE END RTC_Init 2 */# A3 Q# o$ u+ N0 W" m6 u

  64. 7 v) P! w# |8 V2 ^
  65. }
复制代码

- c# l) }4 s! p3 R1 A8 H        在main.c文件中,加入按键、LED灯、串口外设驱动头文件,- f4 J. X1 v3 W4 Q- |9 ]
  1. /* USER CODE BEGIN Includes */
    + o  S: W  `- i' e: d
  2. #include "../../ICore/key/key.h"
    " {+ C7 y0 I# W5 {4 S
  3. #include "../../ICore/led/led.h"9 y' c7 f# O  M8 G8 Q/ w+ H
  4. #include "../../ICore/print/print.h"
    5 |6 D1 e5 C7 |1 X; p- H- `4 u+ I& B
  5. #include "../../ICore/usart/usart.h"& ~! C+ C" X( ~2 V" K4 ~$ d
  6. /* USER CODE END Includes */
复制代码
4 X6 ?( a' m* u5 J+ D6 r  c
        在main函数中声明RTC日期、时间读取缓存变量。
# \  D' X9 h1 q: B3 Y+ X. d, e3 Q
  1.   /* USER CODE BEGIN 1 */, z% f, Y. N- U+ z- P1 f
  2.   RTC_DateTypeDef RtcDate;# O( v" x/ A- h; o! t/ O% s* K
  3.   RTC_TimeTypeDef RtcTime;3 l0 X( K, s7 S0 K
  4.   uint8_t time_set = 0;//用户设置时间标记
    1 {" E8 n. q: @. p' h: q3 M6 C5 L
  5.   /* USER CODE END 1 */
复制代码

7 o8 g" A  I, e9 w; A( g& \  @        在main函数中开启串口lpuart1的中断接收。/ S" h3 l+ q" h$ d) o- U
  1.   /* USER CODE BEGIN 2 */& ?. P) U  [- C) E9 p- G/ O4 [( G2 F
  2.   ResetPrintInit(&hlpuart1);; n, P) l, h1 {* N5 {
  3.   HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断
    & w9 Z8 W9 A8 y( g
  4.   HLPUSART_RX_STA = 0;) {5 O4 t: k! }! Q
  5.   printf("APP reset now!\r\n");
    ( S/ [. Z+ v2 G! m! R" Q6 d
  6.   /* USER CODE END 2 */
复制代码
, i( R: _% B( l; H
        在main函数循环体中,实现按键KEY1获取RTC时间以及按键KEY2设置RCT时间,注意RTC设置时间、日期和RTC读取时间、日期时,数据格式保持一致,最好和CubeMX设置时一致,例如读取、写入RTC时间日期都采用RTC_FORMAT_BCD格式。
4 I) B+ j3 N5 V  o/ [
  1. /* USER CODE BEGIN WHILE */
    ! x( u/ v8 @& V; u' ]+ n
  2.   while (1)
    ; ]7 d& X! f7 [9 Q
  3.   {1 I0 ^+ U2 s" B4 J: i
  4.           if(HLPUSART_RX_STA&0xC000){//溢出或换行,重新开始1 H  F; v4 K9 W; J
  5.                     printf("lpuart1:%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
    ) X- J" l# _& |, R0 \) W
  6.                     if(time_set){6 Z( l2 C) D+ n% j; Q9 X
  7.                             //将超级终端发过来的数据换算并写入RTC,读取数据为字符内容需要转换为数值. N& \3 g! B; @# M
  8.                     RtcDate.Year =  (HLPUSART_RX_BUF[2]-0x30)*10+HLPUSART_RX_BUF[3]-0x30;//减0x30后才能得到十进制0~9的数据
    & y6 T& B  K/ |" m) \9 H5 G7 c4 a1 |
  9.                   RtcDate.Month =  (HLPUSART_RX_BUF[4]-0x30)*10+HLPUSART_RX_BUF[5]-0x30;+ b; s' H! p/ e
  10.                   RtcDate.Date =  (HLPUSART_RX_BUF[6]-0x30)*10+HLPUSART_RX_BUF[7]-0x30;3 ~: I! V, W( c# G' Q
  11.                   RtcTime.Hours =  (HLPUSART_RX_BUF[8]-0x30)*10+HLPUSART_RX_BUF[9]-0x30;
    + N( B+ A0 H# P; |5 w0 J5 ~
  12.                   RtcTime.Minutes =  (HLPUSART_RX_BUF[10]-0x30)*10+HLPUSART_RX_BUF[11]-0x30;4 \! x  n, `0 G, S. E) W" a
  13.                   RtcTime.Seconds =  (HLPUSART_RX_BUF[12]-0x30)*10+HLPUSART_RX_BUF[13]-0x30;
    : g: G9 z8 @! ?0 J- i$ _
  14.                   //写入格式RTC_FORMAT_BCD或RTC_FORMAT_BIN,确保写入及读取数据格式一致即可8 f" Q  M+ A4 {1 d
  15.                     if (HAL_RTC_SetTime(&hrtc,  &RtcTime, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序
    4 \7 K* l; j- I. g% E4 P
  16.                     {! D& u2 R- K$ L$ j4 G; n
  17.                         printf("写入时间失败!\r\n"); //显示写入失败
    ) G$ W; R! g3 a! C9 Z+ g/ v
  18.                     }else if (HAL_RTC_SetDate(&hrtc,  &RtcDate, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序3 x; M  C' W( K0 O# U1 w
  19.                     {
    % B; _! G+ ?2 J4 q  ~
  20.                         printf("写入日期失败!\r\n"); //显示写入失败
    , ?5 D- q: f! n
  21.                     }else printf("写入成功!\r\n");//显示写入成功0 D4 [% Z7 c0 ?% i' o( R
  22.                     time_set=0;5 ?4 U" [; i+ c3 O$ S6 D8 g
  23.                     }8 s0 g( S6 \! [9 i6 C; M
  24.                     HLPUSART_RX_STA=0;//接收错误,重新开始
    ! X1 n& i3 ~! Z
  25.                     HAL_Delay(100);//等待
    # K: A7 u7 n, d, I9 A/ T! j5 S
  26.             }
    0 V$ |: H) N8 E; C
  27.             if(KEY_1())
    0 W3 B7 S7 P% a7 R* ?- e
  28.           {% e& X, `9 S5 T0 ~& ~, b
  29.                     HAL_RTC_GetTime(&hrtc, &RtcTime,  RTC_FORMAT_BCD);//读出时间值# z- D) l1 p' `9 ]
  30.           HAL_RTC_GetDate(&hrtc, &RtcDate,  RTC_FORMAT_BCD);//一定要先读时间后读日期
    & L) K  |. V% `8 e0 l0 @3 V
  31.           printf(" RTC实时时钟测试\r\n");
    ) T9 h/ H' @$ O2 m' ^
  32.           printf(" 实时时间:%04d-%02d-%02d  %02d:%02d:%02d  \r\n",2000+RtcDate.Year,
    ; A  i1 X. q1 ]. K3 a' l
  33.                           RtcDate.Month, RtcDate.Date,RtcTime.Hours, RtcTime.Minutes, RtcTime.Seconds);//显示日期时间
    ' z2 J$ }" Z8 c5 ^1 H2 `2 Y9 c
  34.           }9 ?7 h  C3 k+ r1 c2 G8 D' d' w
  35.             if(KEY_2())
    % y7 ]4 u  W. k: n8 B" ?
  36.           {0 c3 q: {& A; c/ p; S$ i* t
  37.                     printf(" 请输入设置时间,格式20221220183030,按回车键确定! \r\n");
    ( Q' O8 q7 V9 `3 O5 N
  38.                     time_set=1;
    ! L! B2 S  E6 E8 e/ z
  39.           }
    . o' j: B" M9 P, ^; T
  40.     /* USER CODE END WHILE */
复制代码

2 v  Y+ V& C# N! @$ {3 F7 _7 h         注意和前面简介说过,确保时间和日期值之间的一致性,先处理时间(时分秒),在处理日期(年月日),在stm32l4xx_hal_rtc.c的HAL_RTC_GetTime函数注释中也有明确要求如下,“您必须在HAL_RTC_GetTime()之后调用HAL_RTC_GetDate()来解锁值以确保时间和日期值之间的一致性。读取RTC当前时间锁定日历阴影寄存器中的值,直到读取当前日期以确保时间和日期值之间的一致性。”:
' t) ?: z4 O# j) |, s' Y) ?2 T! Z, w1 f/ q* |
27887a24a62d46a1b27a1e4efe8855d6.png 2 h/ v3 g. n) U  I

/ s7 @4 m8 X7 e* Z

1 P: B" T+ x0 E* T1 `  U' j0 q四、编译及测试
8 x, H* \' e  L# a        和本专栏其他博文一样,设置好工程输出目标格式支持,配置好运行调试支持后,点击编译工程及运行按钮,完成工程编辑及下载。       4 s' b( s& x; X  Q- x

4 x. f, w0 t2 [1 |! W( ~
e451e4c7dd9b4c5fa03817fca447c916.png
+ K+ k3 n1 u3 e0 Y- ?
1 y) F6 c/ i( _4 v, c3 ?

' `1 o# P* p4 y* E7 r        打开串口助手工具,连接到开发板,测试
+ ^/ @3 h) |8 _

1 G6 i. u1 Q- H/ H        【1】按键KEY1,读取RTC时间,读取出的时间就去前面在cubeMX 设置的时间,并开始计数,因此分秒有差异。
$ [$ T& g' Z  x. Q0 g5 a' B# P- l  ^; e" Y; k
fb4cb2634b1c469cabfdac88376d1c3b.png 1 U+ z- l/ l$ a0 a# \- ?* {; ^" b

4 J# e: ]# V# b% x4 G4 `5 z4 c; D         【2】按键KEY2设置时间,并按格式输入时间,然后在按键KEY1读取新的RTC时间,如下图所示,设置时间生效。: X- E7 N: ^8 f  y! _

! |5 a5 a: P6 c: \; l
30dc865389204e619affac8dc1687db7.png
' j4 N7 z/ h9 X  s0 f3 f1 F& m, @& u, x* q8 x
         【3】按开发板上的Reset按键,系统复位,再按键KEY2读取RTC时间,时间依然正常计数+ p$ v: _! C4 d* j& q$ ?8 q4 @' f
* W* a* Z+ i  R& j! y
1862d58464bb469ab8f231cf549be7c0.png / V, I6 Y2 C1 ~8 D% L3 S8 i( Y
. c; n! Y& p0 ~4 j/ g$ `/ l

+ j$ Z, B9 r& h" Z: v% p         【4】断电一会,然后重新上电,再按键KEY2读取RTC时间,时间恢复到CubeMX设置的初始时间(即为断电后,原来写入的标识无法保持,HAL_RTCEx_BKUPRead读取标记失败重新初始化)。" W$ k% J! q$ Q' O5 p

1 t* o& V/ D) O
6d8f19e2a6ac4eaf959ccae14e9e980d.png   S8 u' a/ Z+ T8 y
8 Q) C' u& i2 p3 i
————————————————
9 J) E4 l# L$ r/ N/ D! h版权声明:py_free-物联智能如有侵权请联系删除
3 k  D  L: g$ C4 X- Y
4 [. o& a( d  h1 `2 N0 P! v3 u
. B( h  `$ {# `0 O& H8 w1 e; r4 j

6 o: _* v6 d% j" P/ ]
赞 收藏 评论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 手机版