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

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

[复制链接]
攻城狮Melo 发布时间:2023-4-6 16:45
一、RTC简介( o/ F9 Y% D9 n' w1 C6 F. @
        实时时钟的缩写是RTC(Real_Time Clock),核心是晶振,晶振频率一般为32768 Hz 。它为分频计数器提供精确的与低功耗的实基信号。它可以用于产生秒、分、时、日等信息。为了确保时钟长期的准确性,晶振必须正常工作,不能够受到干扰。RTC的晶振又分为:外部晶振和内置晶振。
7 Y0 m* l2 \7 q; R% G2 o4 A, S
+ b: B, n% |2 U# N/ g6 S6 Y        RTC时间信息存储在后备寄存器(RTC_BKUP)中,在STM32中,通常采用一个32位计数器来计时,而不是用年月日时分秒的分组寄存器,因此在处理STM32的时间信息时(设置或读取),通常要求先处理时分秒时间,再处理年月日时间,以确保时间和日期值之间的一致性。: w  K! i1 A( K" }2 F  U: Y

0 O" U6 ?& Q" n) B" T$ V1 X        RTC和后备寄存器一般不会被系统或电源复原源复位,当从待机模式唤醒时,也不会复位,但是要注意的是,停止供电会出现重置,数值恢复到初始设置,因此如果要保持长期有效,尤其是断电重启依然有效,需要配置独立供电电源。: {# [/ K, a1 @5 g

+ Y2 ~; T7 k2 y9 s$ h8 c/ J        RTC时间是以振荡频率来计算的。故它本质上是一个计数器,只是通过换算呈现出日历时间信息。而一般的计数器都是16位的。又因为时间的准确性很重要,故震荡次数越低,时间的准确性越低。所以必定是个高次数。2^15 = 32768 ,而 32768 Hz = 2^15 即分频15次后为1Hz,周期 = 1s,因此RTC的时间基准是1秒。16位计数器最大值0XFFFFFFFF,按秒计算,能支持136年时长计算。# \" V7 |. C& C$ L( `) j
3 x0 k: _  R. H0 h
0 l/ n8 Z+ `8 D6 `  V* D
二、工程创建及配置( u  }) z3 C) e0 W$ P3 t' `
        本文采用STM32L496VGTx-ali开发板来实现,该开发板是没有给RTC独立供电的,因此断电后会出现时间重置,但功能模拟演示还是OK的。本文假设已经移植了前面工程,并实现了lpuart1串口通信与电脑端的调试输出以及按键、LED灯驱动
1 l8 ]6 Q7 c' y, o$ S
5 ]% S% |" P5 p  `- G
        假设已经实现了lpuart1串口通信及按键、LED灯功能,现在双击.ioc打开cubeMX配置界面,开启RTC功能。数据格式支持二进制格式和BCD格式,在HAL中采用RTC_FORMAT_BIN(0x00000000u)和RTC_FORMAT_BCD(0x00000001u)标识。  @) F9 h+ ~) S- @3 f9 n: v

$ B" `3 T$ v8 E9 D7 h
1927c21adfaa4522bbf8ef9117ae2483.png ) H4 z, l0 v; j: }. ^( a
. s7 M* S5 _4 L
         BCD(Binary Coded Decimal),即二-十进制编码,是用四位二进制码的 10 种组合表示十进制数0-9。这种编码至少需要用四位二进制码元,而四位二进制码元可以有16种组合。当用这些组合表示十进制数0-9时,有六种组合不用。
9 I  G" ?8 A6 X2 b/ ?0 ?

: h& s4 D' Y- ^/ s, n3 K3 h        RTC功能开启后,时钟树视图可以关于RTC的选择器可以选择外部高速时钟、外部低速时钟、内部低速时钟,本文选择外部低速时钟(LSE)。
7 g) Q# Q+ ]: r% u3 h
5 V$ l5 \( c4 f' c; h" v7 u
77fc485a203d4e63a71c21cde13eb335.png 1 V& e9 z: D1 }( l8 I
. e/ l" F, G: r+ V# ~  g
         完成配置保存输出按钮(本文是为每个外设生成独立.h/.c源码),在生成的rtc.c文件中可以看到RTC初始化函数关于年月日、时分秒的设值和cubeMX上配置信息一致。
* P, u6 Y4 R8 Z. M1 f6 C$ J+ ^, J  O

, P! x5 j( V/ ?4 _        备注:如果在cubeMX配置界面没有选择数据格式,年日、时分秒默认是0X前缀的。
+ i/ k6 R" `  Y+ |0 d
- q$ C- D0 u% x9 O
629d8cf91bdc4be8b721bdc3bae4b3f7.png % Q. X/ L7 r, v( G
1 i; n& z) Y2 M. G% E* m
三、驱动代码设计实现
2 D" b4 y( F; L5 Q. o5 r3 l        【1】在Core/Src/rtc.c文件中,调整RTC初始化函数,在RTC设置初始化时间、日期之前,加入后备寄存器的读取,判定是否已经进行过初始化,避免重复设置时间、日期而改写本该持续计数的RTC计数。
" c# P7 D' I* N7 f0 M0 R$ I: q5 Z8 ]
  1. void MX_RTC_Init(void)2 o# K+ e3 h2 l6 r- O6 E
  2. {
    * r/ J( j1 k! Q2 O$ P) G. [0 g

  3. + Q/ e/ n% z+ d( P0 N! o/ B1 H
  4.   /* USER CODE BEGIN RTC_Init 0 */" p* ]+ \5 E  n" Z; N

  5. ; x+ b0 {6 s' O# M- v6 l6 x8 q
  6.   /* USER CODE END RTC_Init 0 */, ]4 u( n( _' H3 o7 e8 B

  7. 2 T: B( m7 ]6 ?1 z
  8.   RTC_TimeTypeDef sTime = {0};1 |' P) z9 G* `1 J& Y
  9.   RTC_DateTypeDef sDate = {0};$ D/ u+ u# m7 K: i3 O

  10. 7 Q/ P  m& T) z1 d
  11.   /* USER CODE BEGIN RTC_Init 1 */' Y/ i! M4 f/ _
  12.     //新增/ Y: F8 p% G5 ]/ J! y* U
  13.   __HAL_RCC_PWR_CLK_ENABLE();//使能电源时钟PWR
    ' J' ?, c% u8 A
  14.    HAL_PWR_EnableBkUpAccess();//取消备份区域写保护" i( M: ]7 s: s" q. C, e' e8 X& d& S
  15.   /* USER CODE END RTC_Init 1 */
      Z: f( N, Z6 c5 V+ M: r

  16. 1 B5 f1 h2 _: e' m7 {
  17.   /** Initialize RTC Only
    ) D) a( I  [$ A* W
  18.   */
    8 F: ^3 l. j4 \- [3 T( W4 L+ C
  19.   hrtc.Instance = RTC;+ r- R! V# A' u9 \: ]4 F2 A0 `3 X
  20.   hrtc.Init.HourFormat = RTC_HOURFORMAT_24;  z) a1 r" M/ v  T1 Y/ I, G
  21.   hrtc.Init.AsynchPrediv = 127;0 N- z* _- V' h+ Z0 D' w% j  p
  22.   hrtc.Init.SynchPrediv = 255;
    8 |$ \' S6 z. X; V
  23.   hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;( T! A5 h$ z: \2 L. c6 p! ]
  24.   hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
    6 s4 Q- r; S- Q) R+ Y& a
  25.   hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
    ) d% j+ e; W2 z  z. Q/ t
  26.   hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;8 t* y% p/ c2 W* {
  27.   if (HAL_RTC_Init(&hrtc) != HAL_OK)
    2 i( y9 F( Q& G+ a, a
  28.   {
    + U' _8 E; Y- ^1 A) Z7 j( j8 i/ w
  29.     Error_Handler();7 c, t% v$ y' I& @
  30.   }5 Y. j) {. l* E' w/ \7 s

  31. 8 S5 \$ B3 i+ r
  32.   /* USER CODE BEGIN Check_RTC_BKUP */
    + Y$ h9 T* |& m) h0 t9 m0 k
  33.     //新增, h! ?: P/ i1 L; I0 A
  34.   if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0X8888)//判断是否首次上电
    4 f% u- X7 F( L/ ]3 F( l
  35.   {; [3 W( q: ]. F( D
  36.           HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0X8888); //标记数值(写入上电检查数值)
    + R4 j: B& K9 }4 y
  37.   /* USER CODE END Check_RTC_BKUP */
    9 U3 p; N6 y$ S% [1 F
  38. ! [+ {8 V: O# ~, E# u
  39.   /** Initialize RTC and set the Time and Date
    . n; v" ~5 c1 z$ t# J
  40.   */5 w3 ^2 _( C8 u, C
  41.   sTime.Hours = 12;
    5 W2 u2 j, V/ R8 S: z4 c
  42.   sTime.Minutes = 30;# L0 k; V' ]& \/ W8 @8 i0 K
  43.   sTime.Seconds = 30;, c" d& a1 }5 A( h& z6 r
  44.   sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;$ `* @# q. v( m; Z
  45.   sTime.StoreOperation = RTC_STOREOPERATION_RESET;: \4 y1 Y5 }" u2 T* N
  46.   if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
    ( R2 Q- A: ]( I  R" r
  47.   {
    4 ~( e! y1 Y7 X: P' L. }1 w
  48.     Error_Handler();
    3 X+ L- s9 u$ s8 O
  49.   }
    ; O8 S/ i8 t0 d: h
  50.   sDate.WeekDay = RTC_WEEKDAY_TUESDAY;
    5 m' i2 @& u0 \0 ^$ S
  51.   sDate.Month = RTC_MONTH_DECEMBER;4 r" l& P" d4 u
  52.   sDate.Date = 20;
    ' ^# P8 v/ l; f0 E2 i3 A( `' H
  53.   sDate.Year = 22;
    # t! [+ |$ S( f) p1 d: ^, \
  54. # p5 L# u+ J! s# E  A1 o
  55.   if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
    , V" [# X7 l% k" r) _8 Z
  56.   {% L% L9 M  D* ?7 k1 @/ w
  57.     Error_Handler();
    & }, \* ^) P& ?) p7 z& o4 O
  58.   }
    ! a( e2 x+ Q. B
  59.   /* USER CODE BEGIN RTC_Init 2 */
    & @0 |0 C3 @8 U/ M8 k
  60.     //新增# {9 ?) b* o% c8 ]0 u) v
  61. //  printf("手动初始化测试!\r\n");//显示初始化信息( U% T8 T8 U; g/ k+ ^
  62.   }7 L& g* v) A( L5 p' {+ |
  63.   /* USER CODE END RTC_Init 2 */
    * s. x9 j1 S( @& B5 s
  64. / ~. i/ H, N& K5 X+ G
  65. }
复制代码
; F$ f- |0 [% a, G9 X; ?( v
        在main.c文件中,加入按键、LED灯、串口外设驱动头文件,; W/ a3 r: j9 y; s
  1. /* USER CODE BEGIN Includes */
    & s! V3 d$ r' n" O' W
  2. #include "../../ICore/key/key.h"  B; q. D( Z4 q
  3. #include "../../ICore/led/led.h"
    7 T2 c9 S  E; y
  4. #include "../../ICore/print/print.h"0 v0 x% ~) _% _  \7 d) P& c. }0 d
  5. #include "../../ICore/usart/usart.h"3 U# t% K) f9 i- d  J, G7 A9 \
  6. /* USER CODE END Includes */
复制代码
/ e: ^$ m/ J* t7 s1 B
        在main函数中声明RTC日期、时间读取缓存变量。
5 N) M# Y2 s/ G6 B5 \: U$ c* B
  1.   /* USER CODE BEGIN 1 */
    % `4 U. R1 t* O; D0 x
  2.   RTC_DateTypeDef RtcDate;
    . F$ O% ]+ i& O; @. }, j
  3.   RTC_TimeTypeDef RtcTime;! Q6 A/ U: Z; l# e8 M2 L
  4.   uint8_t time_set = 0;//用户设置时间标记
    ( \. j  M- e- s  r* D* G
  5.   /* USER CODE END 1 */
复制代码

- \) J  K& q( i6 \        在main函数中开启串口lpuart1的中断接收。
9 b* m1 Y/ a* F- I" b6 q
  1.   /* USER CODE BEGIN 2 */- K' t  n* ?( T% ?& H, Z
  2.   ResetPrintInit(&hlpuart1);
    ' l" ~. ?9 T  p& {
  3.   HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断" C0 j2 Y) v& A4 s8 n% [
  4.   HLPUSART_RX_STA = 0;0 d/ p+ o; \7 H) @- f7 z
  5.   printf("APP reset now!\r\n");
    4 _) i' B. B* ]' O
  6.   /* USER CODE END 2 */
复制代码

8 S* q$ E/ u1 v' M) d        在main函数循环体中,实现按键KEY1获取RTC时间以及按键KEY2设置RCT时间,注意RTC设置时间、日期和RTC读取时间、日期时,数据格式保持一致,最好和CubeMX设置时一致,例如读取、写入RTC时间日期都采用RTC_FORMAT_BCD格式。
: ]( z8 ~$ A; P" X
  1. /* USER CODE BEGIN WHILE */6 b5 H; E  T9 q
  2.   while (1)
    ) D, [1 |( Y' }* d* \
  3.   {
    7 G4 n  p% }% n8 ~: s; a+ d8 P% W
  4.           if(HLPUSART_RX_STA&0xC000){//溢出或换行,重新开始
    1 p. \- e0 H6 q) \( v: k( S
  5.                     printf("lpuart1:%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
    . n4 f6 E- E4 r) _6 E) t4 O
  6.                     if(time_set){7 m% I  L2 c; S
  7.                             //将超级终端发过来的数据换算并写入RTC,读取数据为字符内容需要转换为数值
    0 q( `' c2 \% F& V9 C! I
  8.                     RtcDate.Year =  (HLPUSART_RX_BUF[2]-0x30)*10+HLPUSART_RX_BUF[3]-0x30;//减0x30后才能得到十进制0~9的数据
    6 |% T, L% H8 J# F; J
  9.                   RtcDate.Month =  (HLPUSART_RX_BUF[4]-0x30)*10+HLPUSART_RX_BUF[5]-0x30;7 X) \3 L+ X. P1 N4 D4 `" B. G& U
  10.                   RtcDate.Date =  (HLPUSART_RX_BUF[6]-0x30)*10+HLPUSART_RX_BUF[7]-0x30;
    2 p- [+ Y. K# ]' `) |
  11.                   RtcTime.Hours =  (HLPUSART_RX_BUF[8]-0x30)*10+HLPUSART_RX_BUF[9]-0x30;$ ?) T5 f$ @2 T& c9 s4 S' w; p
  12.                   RtcTime.Minutes =  (HLPUSART_RX_BUF[10]-0x30)*10+HLPUSART_RX_BUF[11]-0x30;  ?0 @3 Z; Q8 y3 c! V. [% a
  13.                   RtcTime.Seconds =  (HLPUSART_RX_BUF[12]-0x30)*10+HLPUSART_RX_BUF[13]-0x30;
    ( b" @7 ~/ e# B1 E
  14.                   //写入格式RTC_FORMAT_BCD或RTC_FORMAT_BIN,确保写入及读取数据格式一致即可" {& C# f  Z! |1 c
  15.                     if (HAL_RTC_SetTime(&hrtc,  &RtcTime, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序3 Z4 \0 @6 a+ ^# T5 q
  16.                     {
    $ s$ J2 P$ c4 w5 p
  17.                         printf("写入时间失败!\r\n"); //显示写入失败( }% J! {3 Z; r" y
  18.                     }else if (HAL_RTC_SetDate(&hrtc,  &RtcDate, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序
    2 `- _) e9 `; S0 [) `* ?9 e
  19.                     {
    , i/ A* G* W9 a
  20.                         printf("写入日期失败!\r\n"); //显示写入失败
    : H# U( o6 j' S& I# G$ j+ I+ {
  21.                     }else printf("写入成功!\r\n");//显示写入成功
    ; a% M+ t& |0 F. B0 C
  22.                     time_set=0;
    - c# S) _- @5 K1 S9 f
  23.                     }
    ; Y  }# A3 B3 _2 P% X
  24.                     HLPUSART_RX_STA=0;//接收错误,重新开始
    % P9 m& t  x* A2 g4 v5 y
  25.                     HAL_Delay(100);//等待
    3 K* K' |3 \2 ~
  26.             }9 }3 d( v4 Z- a, }% D7 Q) m
  27.             if(KEY_1())
    1 m# T2 k: |- x$ o
  28.           {
    * @+ b$ q5 ~4 j( G, {/ T
  29.                     HAL_RTC_GetTime(&hrtc, &RtcTime,  RTC_FORMAT_BCD);//读出时间值$ T2 H' w& W7 C# V
  30.           HAL_RTC_GetDate(&hrtc, &RtcDate,  RTC_FORMAT_BCD);//一定要先读时间后读日期
    6 X9 x9 t6 D9 K) \' u! N( [
  31.           printf(" RTC实时时钟测试\r\n");3 Y/ x) x- @4 q# a# w
  32.           printf(" 实时时间:%04d-%02d-%02d  %02d:%02d:%02d  \r\n",2000+RtcDate.Year,
    : F  w, x0 Q7 E, t5 i, q
  33.                           RtcDate.Month, RtcDate.Date,RtcTime.Hours, RtcTime.Minutes, RtcTime.Seconds);//显示日期时间, o* y# J& h7 ^- I, Q1 C4 z
  34.           }
    2 D& a4 q8 y& a# o; r- V3 l9 g3 ]+ ~
  35.             if(KEY_2())
    8 s' h6 H' a4 H' r# l, t  A4 D
  36.           {4 t/ m: }' ?5 ^# f1 u+ y5 D
  37.                     printf(" 请输入设置时间,格式20221220183030,按回车键确定! \r\n");/ x& S% h6 T% Q1 C) ]* _
  38.                     time_set=1;' U; R) n, G# v% i- ]
  39.           }
    " X9 s! a# k) m9 I( s. E- D, V; c
  40.     /* USER CODE END WHILE */
复制代码
' ?* v8 x. ?, r) q' I# z. t
         注意和前面简介说过,确保时间和日期值之间的一致性,先处理时间(时分秒),在处理日期(年月日),在stm32l4xx_hal_rtc.c的HAL_RTC_GetTime函数注释中也有明确要求如下,“您必须在HAL_RTC_GetTime()之后调用HAL_RTC_GetDate()来解锁值以确保时间和日期值之间的一致性。读取RTC当前时间锁定日历阴影寄存器中的值,直到读取当前日期以确保时间和日期值之间的一致性。”:" \" J6 M+ U4 o9 z

4 b) Y- S7 {* m# l
27887a24a62d46a1b27a1e4efe8855d6.png   c5 [- N; U1 Z. I7 p2 A6 V

3 S* `5 M$ }5 v+ U1 \
' h  F  @) L$ c4 u) r% J# ^( q
四、编译及测试
; q" n- U$ m$ j: ]5 k        和本专栏其他博文一样,设置好工程输出目标格式支持,配置好运行调试支持后,点击编译工程及运行按钮,完成工程编辑及下载。      
5 G2 Y( {: T; p, Q' B, Z* ^1 p5 Z. H8 g  m% T( |
e451e4c7dd9b4c5fa03817fca447c916.png
% ~6 W5 A6 ^6 z/ p; ?6 X5 }" W) z$ U
% X+ U& h0 E* T- \
: Z2 t* Z' X6 ^
        打开串口助手工具,连接到开发板,测试
; ]: Y; n' H  A8 o% {8 I

" T* C+ u6 _& d# x/ x: k1 |# H        【1】按键KEY1,读取RTC时间,读取出的时间就去前面在cubeMX 设置的时间,并开始计数,因此分秒有差异。
4 |' T7 a9 s4 O' f: c3 ~+ j/ ]& O# e/ I4 ?! J4 r$ l
fb4cb2634b1c469cabfdac88376d1c3b.png 5 ]& B. m5 U) W2 y/ w3 u, D

+ h, u/ V3 G7 B4 r" a         【2】按键KEY2设置时间,并按格式输入时间,然后在按键KEY1读取新的RTC时间,如下图所示,设置时间生效。3 l' t6 j" M3 [2 w

9 G8 r# _! y& C- J1 M' F- g5 ?; {
30dc865389204e619affac8dc1687db7.png 2 l4 `+ ?/ h0 T. Z; G& I! z/ H; a2 a
5 o4 [# v* m8 F/ I9 }4 z  b3 F
         【3】按开发板上的Reset按键,系统复位,再按键KEY2读取RTC时间,时间依然正常计数
# o9 B% r7 ?% ~" _' f! f9 i9 S: G* c/ \" ]9 K) |5 S
1862d58464bb469ab8f231cf549be7c0.png
" Z7 _( T. F4 l& S" R8 H
0 B9 N8 Y) Y/ r6 G) O

4 s, Z" ]2 T( r7 w. \" D         【4】断电一会,然后重新上电,再按键KEY2读取RTC时间,时间恢复到CubeMX设置的初始时间(即为断电后,原来写入的标识无法保持,HAL_RTCEx_BKUPRead读取标记失败重新初始化)。( k8 G" H) [( u1 s0 F8 C  X: v
$ I' s# U% Y3 w6 P; ^
6d8f19e2a6ac4eaf959ccae14e9e980d.png
- y. s' ^  ^* o) K- b, N# T; z
' ~- s8 ~8 x$ |9 t
————————————————
  `. K, T6 ^: t5 e! }" H+ d: D1 _版权声明:py_free-物联智能如有侵权请联系删除
4 J; m- l8 g' a7 |" K+ h0 z6 k. z( T! o# H/ h" R
- j7 T- S8 J" @* Y. k
' u. O8 M, e  e8 ], \& H+ g6 X
赞 收藏 评论1 发布时间:2023-4-6 16:45

举报

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

瞎J13乱发,傻福玩意

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

image.png

所属标签

相似技术帖

官网相关资源

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