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

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

[复制链接]
攻城狮Melo 发布时间:2023-4-6 16:45
一、RTC简介5 t* e3 n$ Z9 a6 I' o& R9 q# ?) x
        实时时钟的缩写是RTC(Real_Time Clock),核心是晶振,晶振频率一般为32768 Hz 。它为分频计数器提供精确的与低功耗的实基信号。它可以用于产生秒、分、时、日等信息。为了确保时钟长期的准确性,晶振必须正常工作,不能够受到干扰。RTC的晶振又分为:外部晶振和内置晶振。4 j9 y, p! m) j* f6 s5 M
. T7 J1 e, v$ ^9 Y$ A
        RTC时间信息存储在后备寄存器(RTC_BKUP)中,在STM32中,通常采用一个32位计数器来计时,而不是用年月日时分秒的分组寄存器,因此在处理STM32的时间信息时(设置或读取),通常要求先处理时分秒时间,再处理年月日时间,以确保时间和日期值之间的一致性。
$ t! H# y. c0 q7 X2 R" p; x. U, v( Z8 P7 f' G3 d
        RTC和后备寄存器一般不会被系统或电源复原源复位,当从待机模式唤醒时,也不会复位,但是要注意的是,停止供电会出现重置,数值恢复到初始设置,因此如果要保持长期有效,尤其是断电重启依然有效,需要配置独立供电电源。9 i1 ]  \/ D1 d

1 O9 m! e7 ~) I: [3 [4 Y; {1 p. w        RTC时间是以振荡频率来计算的。故它本质上是一个计数器,只是通过换算呈现出日历时间信息。而一般的计数器都是16位的。又因为时间的准确性很重要,故震荡次数越低,时间的准确性越低。所以必定是个高次数。2^15 = 32768 ,而 32768 Hz = 2^15 即分频15次后为1Hz,周期 = 1s,因此RTC的时间基准是1秒。16位计数器最大值0XFFFFFFFF,按秒计算,能支持136年时长计算。$ X6 B+ ?5 e, h* b
8 ~# J: |2 N0 _" e2 ?: ?/ k/ ]' Q
7 @% |, o+ Q6 x' N9 y6 `
二、工程创建及配置
6 q$ U+ O& ]9 k( n/ W& |0 |. D        本文采用STM32L496VGTx-ali开发板来实现,该开发板是没有给RTC独立供电的,因此断电后会出现时间重置,但功能模拟演示还是OK的。本文假设已经移植了前面工程,并实现了lpuart1串口通信与电脑端的调试输出以及按键、LED灯驱动; h, ~3 E7 X' D+ S
9 a" Z/ u+ w2 s+ X
        假设已经实现了lpuart1串口通信及按键、LED灯功能,现在双击.ioc打开cubeMX配置界面,开启RTC功能。数据格式支持二进制格式和BCD格式,在HAL中采用RTC_FORMAT_BIN(0x00000000u)和RTC_FORMAT_BCD(0x00000001u)标识。
$ ?$ l; E$ C3 L7 t: u$ _" E! q! V) V! {* N& u1 \0 r5 C4 k
1927c21adfaa4522bbf8ef9117ae2483.png & C+ J+ q, K: L2 O, ?) f  p% w5 O

5 I) h9 m9 r- A0 N- Z         BCD(Binary Coded Decimal),即二-十进制编码,是用四位二进制码的 10 种组合表示十进制数0-9。这种编码至少需要用四位二进制码元,而四位二进制码元可以有16种组合。当用这些组合表示十进制数0-9时,有六种组合不用。0 g& V# ^) U  a8 e/ w5 n4 O
( |" F; C" w9 g
        RTC功能开启后,时钟树视图可以关于RTC的选择器可以选择外部高速时钟、外部低速时钟、内部低速时钟,本文选择外部低速时钟(LSE)。
- V% l( ?2 T: I6 A# x8 x2 X# `# \
3 U+ M) s8 u3 w* f8 d9 K
77fc485a203d4e63a71c21cde13eb335.png ) B3 w0 D) i, F! R3 M! w
, x8 ^& F1 }; B7 H0 j
         完成配置保存输出按钮(本文是为每个外设生成独立.h/.c源码),在生成的rtc.c文件中可以看到RTC初始化函数关于年月日、时分秒的设值和cubeMX上配置信息一致。
7 M  C( V: e* Q# s7 S7 v4 `

. k, G6 A) x0 D- j3 F        备注:如果在cubeMX配置界面没有选择数据格式,年日、时分秒默认是0X前缀的。, f9 w; U7 x2 M; h

4 E/ x& T: z. v2 j; _+ C; Y8 t. b
629d8cf91bdc4be8b721bdc3bae4b3f7.png 5 y7 q6 t* {5 b& y/ x; T

8 u& Q/ L3 q1 V0 R( c0 h 三、驱动代码设计实现
1 [$ I& K7 H, p# j# y$ ]        【1】在Core/Src/rtc.c文件中,调整RTC初始化函数,在RTC设置初始化时间、日期之前,加入后备寄存器的读取,判定是否已经进行过初始化,避免重复设置时间、日期而改写本该持续计数的RTC计数。
) R' M# ]* U  t; T! z( B
  1. void MX_RTC_Init(void)
    : ~0 i* M6 r2 J/ x: o! v
  2. {/ I' v5 n$ N& I' l" |" f

  3. 2 a- w% U; k+ ?+ T/ |
  4.   /* USER CODE BEGIN RTC_Init 0 */1 e! q, S$ p* {& L, u3 P% C) j/ i
  5. . @' P6 G" C: Y2 x/ t* j
  6.   /* USER CODE END RTC_Init 0 */7 x% s) i& B3 x: f  L; E  C( Y

  7. . g/ K; q) ]: j8 u, c* D
  8.   RTC_TimeTypeDef sTime = {0};4 ^3 V/ d5 Q4 c& u
  9.   RTC_DateTypeDef sDate = {0};1 E* o6 q' d) B2 q. I+ y

  10. 3 R: f# ~% ~5 a( L% X' M* u3 C
  11.   /* USER CODE BEGIN RTC_Init 1 */$ w8 s: |  V0 y2 f
  12.     //新增) v0 `1 E" K9 c9 @
  13.   __HAL_RCC_PWR_CLK_ENABLE();//使能电源时钟PWR
    0 c2 E: Z9 \! n& f9 M
  14.    HAL_PWR_EnableBkUpAccess();//取消备份区域写保护' k) a* a2 c4 n7 G
  15.   /* USER CODE END RTC_Init 1 */' k* x. p/ ~: E' ], X" u$ K3 m
  16. 3 ^" M6 X+ a, x& {* D; m
  17.   /** Initialize RTC Only
    8 H8 N4 r; A7 g9 B+ w/ k  ?
  18.   */
    " q5 a1 M: O; i" t
  19.   hrtc.Instance = RTC;( V% g4 o; ?1 N! s# i" W5 m$ E/ S
  20.   hrtc.Init.HourFormat = RTC_HOURFORMAT_24;5 b8 m! Y+ K1 J9 @6 o
  21.   hrtc.Init.AsynchPrediv = 127;4 u2 u! \- l' ~2 j3 |- P2 U- X4 d8 s
  22.   hrtc.Init.SynchPrediv = 255;
    7 m, @9 B+ @& A) F+ t6 G
  23.   hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
    ! S+ V9 C7 g, E  n
  24.   hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;* w( G- D: X/ ~
  25.   hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;- q& m8 q. H  D5 b& ]
  26.   hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;0 S/ a% f0 }+ f8 J3 q$ s- [
  27.   if (HAL_RTC_Init(&hrtc) != HAL_OK)
    . [- w4 T& G# x9 B8 ^4 O2 P, A
  28.   {0 x# W% _1 P, l+ e  m
  29.     Error_Handler();3 {( a' x& K+ @$ |8 n2 _, f
  30.   }
    8 V4 Y; A& T0 r+ g5 u2 D) u

  31. & `  W* z! R0 I; `- r
  32.   /* USER CODE BEGIN Check_RTC_BKUP */
    ) Y) l: t  [1 Y+ @- ?4 u, A- O
  33.     //新增! ~, X, a+ t' q4 C- @
  34.   if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0X8888)//判断是否首次上电3 _- p+ }+ U, ~) h8 p& O9 p2 h
  35.   {+ {; e, a* N) L9 J
  36.           HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0X8888); //标记数值(写入上电检查数值)
    ) C  G1 j) x  q# B2 P
  37.   /* USER CODE END Check_RTC_BKUP */- P- {5 X! o0 x# R- s

  38. ! k, C0 n$ y7 I) h3 f
  39.   /** Initialize RTC and set the Time and Date
    ! x9 j6 D; b  Q1 g7 t+ p& u- ?" {
  40.   */$ v1 A2 _" {. w+ W9 K$ t. L
  41.   sTime.Hours = 12;  `# d: Q5 p! \( ~
  42.   sTime.Minutes = 30;- t9 B: p  m! `* Y
  43.   sTime.Seconds = 30;% G& U1 u+ F6 v% N7 R
  44.   sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;$ F5 X: D( o# b
  45.   sTime.StoreOperation = RTC_STOREOPERATION_RESET;
    : r$ M0 [* J* Q/ R" g
  46.   if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)( v% }* Q: r$ i) H& k
  47.   {8 Q2 H9 Y5 J: m8 Y
  48.     Error_Handler();6 G; k6 M9 S8 p' \
  49.   }
      c  T2 ]5 @9 k: q& N
  50.   sDate.WeekDay = RTC_WEEKDAY_TUESDAY;
      r7 d9 J, W5 n4 [
  51.   sDate.Month = RTC_MONTH_DECEMBER;
    : x* P# P7 P& I! s5 ~
  52.   sDate.Date = 20;; b; }5 \5 @/ K, k# |, T5 A. {
  53.   sDate.Year = 22;; i! i3 Y7 I# I/ h1 k

  54. 4 n% _) a1 u+ Z: ^
  55.   if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
    3 v2 G- V* b. a; V: `
  56.   {
    , k: O* a$ _+ @! o' W6 _
  57.     Error_Handler();# _! O, @" D2 d8 S; k. N) }* t
  58.   }
    ( H0 G; I# A/ w: d! R
  59.   /* USER CODE BEGIN RTC_Init 2 */0 ]) D. m  D3 ]5 n8 z) b$ N
  60.     //新增/ ~% s# K% Y) p( I: Y* F) t
  61. //  printf("手动初始化测试!\r\n");//显示初始化信息$ O2 _, p0 w' T+ V: c/ R
  62.   }
      r$ w9 c# N1 X! Y: q3 K
  63.   /* USER CODE END RTC_Init 2 */
    9 g& e7 m5 Q# M) G& J6 ~& c

  64. ( Q) R5 j" \& Q  `$ G3 L1 A, h- Z$ l
  65. }
复制代码

4 p  S* U0 f0 e4 D. y' {        在main.c文件中,加入按键、LED灯、串口外设驱动头文件,
& _9 t" }- j% h8 d7 _$ j, f
  1. /* USER CODE BEGIN Includes */
    ) N6 H& g  e$ [' q4 m" z1 o
  2. #include "../../ICore/key/key.h"
    0 d# b- P* F% f% m5 w
  3. #include "../../ICore/led/led.h"8 F& }, s' o8 h* I( a# z
  4. #include "../../ICore/print/print.h"
    - k) ?' N1 u6 A3 d3 f$ E/ P8 _
  5. #include "../../ICore/usart/usart.h"- Z5 l$ ~$ ~+ |: `' V3 R
  6. /* USER CODE END Includes */
复制代码

9 d: P" q! P: w8 H: r1 Y        在main函数中声明RTC日期、时间读取缓存变量。/ B( M4 D0 {( W
  1.   /* USER CODE BEGIN 1 */
    4 {' M6 U$ R4 x( j
  2.   RTC_DateTypeDef RtcDate;
    2 u. K5 _+ b) L, `/ g
  3.   RTC_TimeTypeDef RtcTime;2 Z; N5 F9 s4 m/ f9 `9 n3 U4 r
  4.   uint8_t time_set = 0;//用户设置时间标记1 @( S2 W4 P' B' y8 C
  5.   /* USER CODE END 1 */
复制代码

& X# K' r. |1 M$ B2 U; z; S+ h        在main函数中开启串口lpuart1的中断接收。
$ ?! t/ t8 i) K9 v1 {8 O
  1.   /* USER CODE BEGIN 2 */
    # P; B+ o+ T6 h$ C
  2.   ResetPrintInit(&hlpuart1);
    1 W, C1 `0 ?( U
  3.   HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断2 g6 R0 Z% d' T* Z3 R  T' \
  4.   HLPUSART_RX_STA = 0;
    : j6 H' ~- H" K
  5.   printf("APP reset now!\r\n");
    6 Y) e  r( ~1 I) o
  6.   /* USER CODE END 2 */
复制代码
, e/ ]8 @5 h( ^5 ?; j' u5 A% b
        在main函数循环体中,实现按键KEY1获取RTC时间以及按键KEY2设置RCT时间,注意RTC设置时间、日期和RTC读取时间、日期时,数据格式保持一致,最好和CubeMX设置时一致,例如读取、写入RTC时间日期都采用RTC_FORMAT_BCD格式。
$ q, k% F! p& u/ B
  1. /* USER CODE BEGIN WHILE */
      Z) K- n! Q) L' W* r! `
  2.   while (1)4 b" W" y6 J1 I) t& Q
  3.   {! Z; o% j! \( G& x5 H
  4.           if(HLPUSART_RX_STA&0xC000){//溢出或换行,重新开始+ A: Q2 \; H, {; h( [1 Z
  5.                     printf("lpuart1:%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);( ^4 s7 `9 a# ^4 z5 C1 k
  6.                     if(time_set){
    7 k+ z# r9 I- H- I
  7.                             //将超级终端发过来的数据换算并写入RTC,读取数据为字符内容需要转换为数值
    6 B8 D" p6 D8 z
  8.                     RtcDate.Year =  (HLPUSART_RX_BUF[2]-0x30)*10+HLPUSART_RX_BUF[3]-0x30;//减0x30后才能得到十进制0~9的数据
    , E+ W0 R% \8 x( L' a; l
  9.                   RtcDate.Month =  (HLPUSART_RX_BUF[4]-0x30)*10+HLPUSART_RX_BUF[5]-0x30;! _& Q( n/ a! c$ q/ r3 I% ~
  10.                   RtcDate.Date =  (HLPUSART_RX_BUF[6]-0x30)*10+HLPUSART_RX_BUF[7]-0x30;0 {, J; o- ~$ D! y# }0 L
  11.                   RtcTime.Hours =  (HLPUSART_RX_BUF[8]-0x30)*10+HLPUSART_RX_BUF[9]-0x30;3 f5 w& m5 w- o
  12.                   RtcTime.Minutes =  (HLPUSART_RX_BUF[10]-0x30)*10+HLPUSART_RX_BUF[11]-0x30;7 e. `, H; @0 O7 Z
  13.                   RtcTime.Seconds =  (HLPUSART_RX_BUF[12]-0x30)*10+HLPUSART_RX_BUF[13]-0x30;
    7 L# U2 m: L- ~2 d6 c' \+ Q
  14.                   //写入格式RTC_FORMAT_BCD或RTC_FORMAT_BIN,确保写入及读取数据格式一致即可" C2 R' k) }  D* E2 h
  15.                     if (HAL_RTC_SetTime(&hrtc,  &RtcTime, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序
    8 r! p. J$ |' g
  16.                     {
    & u$ `% I7 F+ J  N0 v. w! `$ K; i
  17.                         printf("写入时间失败!\r\n"); //显示写入失败
    ( F3 m# s( J8 J$ z7 l5 A
  18.                     }else if (HAL_RTC_SetDate(&hrtc,  &RtcDate, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序
    : y8 A! I6 q3 n6 ?4 g
  19.                     {
    7 o# K( {# X) d! K
  20.                         printf("写入日期失败!\r\n"); //显示写入失败
    9 y3 G& {$ [3 \2 ^- W
  21.                     }else printf("写入成功!\r\n");//显示写入成功8 l3 K% A+ b; y) }! l. }6 b
  22.                     time_set=0;. U7 P2 Z# z5 }
  23.                     }7 z. t6 a. e% [. @/ i' a9 u
  24.                     HLPUSART_RX_STA=0;//接收错误,重新开始
    4 U! ?9 T( }4 c  ]6 K
  25.                     HAL_Delay(100);//等待! ]1 p6 Q+ y, u5 ^
  26.             }! w& s9 }- i- x
  27.             if(KEY_1())$ J4 U4 p6 c6 z( Z- Z9 s4 N
  28.           {  w3 h0 }( \) m9 P5 d3 a* T
  29.                     HAL_RTC_GetTime(&hrtc, &RtcTime,  RTC_FORMAT_BCD);//读出时间值
    ' A7 Z0 y8 s" N$ X
  30.           HAL_RTC_GetDate(&hrtc, &RtcDate,  RTC_FORMAT_BCD);//一定要先读时间后读日期
    - T, t8 `. T2 M' S3 y1 j
  31.           printf(" RTC实时时钟测试\r\n");6 d4 E' {! p2 e9 r
  32.           printf(" 实时时间:%04d-%02d-%02d  %02d:%02d:%02d  \r\n",2000+RtcDate.Year,: x' e: [( O) n6 E/ q: ^# m+ [* l/ v
  33.                           RtcDate.Month, RtcDate.Date,RtcTime.Hours, RtcTime.Minutes, RtcTime.Seconds);//显示日期时间* z/ U8 W, [9 G- X0 b% a. {
  34.           }
    9 O7 ?5 Y/ q9 t1 `4 z2 r- \" ~
  35.             if(KEY_2())( s7 A- @. E: O
  36.           {
    9 C! B0 |- ]4 p9 Y+ R2 D
  37.                     printf(" 请输入设置时间,格式20221220183030,按回车键确定! \r\n");
    4 ^. A6 J! _% N. z8 r& M: a
  38.                     time_set=1;
    2 ?; ~' |  _3 s# s. \/ [1 X  _0 `
  39.           }& o$ c6 V. [8 `5 |# h; C
  40.     /* USER CODE END WHILE */
复制代码

, P3 C5 J% C1 i5 i) |7 s         注意和前面简介说过,确保时间和日期值之间的一致性,先处理时间(时分秒),在处理日期(年月日),在stm32l4xx_hal_rtc.c的HAL_RTC_GetTime函数注释中也有明确要求如下,“您必须在HAL_RTC_GetTime()之后调用HAL_RTC_GetDate()来解锁值以确保时间和日期值之间的一致性。读取RTC当前时间锁定日历阴影寄存器中的值,直到读取当前日期以确保时间和日期值之间的一致性。”:
* ~0 V4 z% r  Q5 Z4 |+ r- p. E# N2 T
27887a24a62d46a1b27a1e4efe8855d6.png
+ T8 T% Z0 ^) M4 S
. J* C+ |- c9 k
6 S* [9 C/ y! j7 }+ J  Q; ]# [
四、编译及测试
% B" L3 `" Y% z9 F* j# s        和本专栏其他博文一样,设置好工程输出目标格式支持,配置好运行调试支持后,点击编译工程及运行按钮,完成工程编辑及下载。      
* O) Q+ C4 b: ]* l' r/ q! J7 r# E' t& D" J- R8 P( @! b+ e
e451e4c7dd9b4c5fa03817fca447c916.png
: M. b3 J* w+ G9 M# E' N9 O: F, z) h

- t  m7 l' P2 ]' o        打开串口助手工具,连接到开发板,测试
- q; |/ o3 O5 G. }+ s+ j8 I9 y
- X, p. H# S, e/ W( y) v0 x
        【1】按键KEY1,读取RTC时间,读取出的时间就去前面在cubeMX 设置的时间,并开始计数,因此分秒有差异。% |* e7 C/ ?' Y% H

' f5 P/ v! {5 t# R  A2 x+ B
fb4cb2634b1c469cabfdac88376d1c3b.png
: V1 Q1 V" _& N  S4 B# N  r, H  S: S  |) z4 ?
         【2】按键KEY2设置时间,并按格式输入时间,然后在按键KEY1读取新的RTC时间,如下图所示,设置时间生效。
; K2 k& s; ^% n( ?9 Q' D' U. D* K8 U+ W) M
30dc865389204e619affac8dc1687db7.png
* x& Y3 w7 L# R7 L0 C
1 o  X  D& {- ^* n$ n  ~         【3】按开发板上的Reset按键,系统复位,再按键KEY2读取RTC时间,时间依然正常计数
. S3 u  X2 b4 k/ f% c2 X/ w! a4 [
# B9 F7 J$ ~: I
1862d58464bb469ab8f231cf549be7c0.png
  `8 g, m$ k' V; a9 {# I0 s% L( x, `$ M
- D, {! L" D3 b7 x, u
         【4】断电一会,然后重新上电,再按键KEY2读取RTC时间,时间恢复到CubeMX设置的初始时间(即为断电后,原来写入的标识无法保持,HAL_RTCEx_BKUPRead读取标记失败重新初始化)。
, f' U6 u" P: q4 T( o# T8 f2 t+ G- q; C& H7 b; k
6d8f19e2a6ac4eaf959ccae14e9e980d.png
9 I3 S5 H& {' ~! {$ w! h
& q* A  x- U6 |; s, |0 O8 C
————————————————% G! N4 A" j8 M' p# K
版权声明:py_free-物联智能如有侵权请联系删除& a. z; y# N. O4 G! g7 B' y' M

0 F( k7 e1 E4 o  `

6 ^. U+ }1 g  \3 B8 [0 {# H$ K* i
赞 收藏 评论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 手机版