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

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

[复制链接]
攻城狮Melo 发布时间:2023-4-6 16:45
一、RTC简介+ }# I5 D' Q* f2 U8 e4 _/ w
        实时时钟的缩写是RTC(Real_Time Clock),核心是晶振,晶振频率一般为32768 Hz 。它为分频计数器提供精确的与低功耗的实基信号。它可以用于产生秒、分、时、日等信息。为了确保时钟长期的准确性,晶振必须正常工作,不能够受到干扰。RTC的晶振又分为:外部晶振和内置晶振。
5 v7 K$ x. ^. Z9 ]% G  I- s  f4 q
        RTC时间信息存储在后备寄存器(RTC_BKUP)中,在STM32中,通常采用一个32位计数器来计时,而不是用年月日时分秒的分组寄存器,因此在处理STM32的时间信息时(设置或读取),通常要求先处理时分秒时间,再处理年月日时间,以确保时间和日期值之间的一致性。
) [4 C" m2 W+ H& y9 H
- d' z$ g2 M& X& |7 w* {9 t6 R/ \        RTC和后备寄存器一般不会被系统或电源复原源复位,当从待机模式唤醒时,也不会复位,但是要注意的是,停止供电会出现重置,数值恢复到初始设置,因此如果要保持长期有效,尤其是断电重启依然有效,需要配置独立供电电源。
+ s' x& J, {9 G" b2 e3 G- s. ~) r  U
        RTC时间是以振荡频率来计算的。故它本质上是一个计数器,只是通过换算呈现出日历时间信息。而一般的计数器都是16位的。又因为时间的准确性很重要,故震荡次数越低,时间的准确性越低。所以必定是个高次数。2^15 = 32768 ,而 32768 Hz = 2^15 即分频15次后为1Hz,周期 = 1s,因此RTC的时间基准是1秒。16位计数器最大值0XFFFFFFFF,按秒计算,能支持136年时长计算。
7 }5 L; S- f2 }1 b) \
, W2 }3 T( V1 B' q
( m/ T5 g5 w4 S1 ^7 M# P' x
二、工程创建及配置' q; ~/ l! x: t- s7 [* V4 e/ p
        本文采用STM32L496VGTx-ali开发板来实现,该开发板是没有给RTC独立供电的,因此断电后会出现时间重置,但功能模拟演示还是OK的。本文假设已经移植了前面工程,并实现了lpuart1串口通信与电脑端的调试输出以及按键、LED灯驱动
) f% t1 }/ r6 @% a1 `
0 D0 j- a# w9 a- S+ v( o0 f
        假设已经实现了lpuart1串口通信及按键、LED灯功能,现在双击.ioc打开cubeMX配置界面,开启RTC功能。数据格式支持二进制格式和BCD格式,在HAL中采用RTC_FORMAT_BIN(0x00000000u)和RTC_FORMAT_BCD(0x00000001u)标识。
& P- Y+ P: v- t) L
/ I3 w6 T; q* [8 ^
1927c21adfaa4522bbf8ef9117ae2483.png 4 Z" y7 G) a! N& a: x
7 g- m5 c- v( s7 {  _
         BCD(Binary Coded Decimal),即二-十进制编码,是用四位二进制码的 10 种组合表示十进制数0-9。这种编码至少需要用四位二进制码元,而四位二进制码元可以有16种组合。当用这些组合表示十进制数0-9时,有六种组合不用。) R7 k- u% n4 l8 J
, R% k( i- S# a: H
        RTC功能开启后,时钟树视图可以关于RTC的选择器可以选择外部高速时钟、外部低速时钟、内部低速时钟,本文选择外部低速时钟(LSE)。) H1 ~3 G5 Y; k8 s& y8 `

7 o& L( D4 j+ s8 E3 f  k
77fc485a203d4e63a71c21cde13eb335.png * j$ ?* n0 r. d0 |

' g) V' t, \/ K9 R! z, E         完成配置保存输出按钮(本文是为每个外设生成独立.h/.c源码),在生成的rtc.c文件中可以看到RTC初始化函数关于年月日、时分秒的设值和cubeMX上配置信息一致。
, q' w+ `3 n9 Q, q
" p+ f* i6 k! ^- Y6 L
        备注:如果在cubeMX配置界面没有选择数据格式,年日、时分秒默认是0X前缀的。% U( Q' F. q- P4 q

- L: v" v8 O" z, h/ P$ C/ v
629d8cf91bdc4be8b721bdc3bae4b3f7.png   r5 V) s' p- h) _8 P2 K, c+ P
4 F3 u% S/ F6 p5 h" A
三、驱动代码设计实现- C( [1 A* B! S: L
        【1】在Core/Src/rtc.c文件中,调整RTC初始化函数,在RTC设置初始化时间、日期之前,加入后备寄存器的读取,判定是否已经进行过初始化,避免重复设置时间、日期而改写本该持续计数的RTC计数。
& ^4 N7 C# L8 z8 h$ a
  1. void MX_RTC_Init(void)! Y1 g- o* \2 U4 S& G* p
  2. {+ F4 i; ~1 _+ z0 y. U

  3. : X* V, u1 s8 ^: K( H
  4.   /* USER CODE BEGIN RTC_Init 0 */; \+ C: A% o; y; W5 \3 J3 o

  5. 4 B2 t: c2 P5 H& `6 h; k- v
  6.   /* USER CODE END RTC_Init 0 */" s7 \/ T" Y1 ?$ S

  7. 0 K! W% W9 o% L: l; O
  8.   RTC_TimeTypeDef sTime = {0};+ E- \% ]+ u/ Q! U0 d* C
  9.   RTC_DateTypeDef sDate = {0};( J5 N6 O" S/ D7 A/ ~6 y* m/ p
  10. : W' ~' R; O6 A
  11.   /* USER CODE BEGIN RTC_Init 1 */
    6 N1 y4 e) B# A0 ~" }" w+ W
  12.     //新增& q3 A, |7 c1 U- r+ `
  13.   __HAL_RCC_PWR_CLK_ENABLE();//使能电源时钟PWR( W% V5 g" Z  y8 b
  14.    HAL_PWR_EnableBkUpAccess();//取消备份区域写保护
    ! o1 g6 o# L6 N0 z" g( @4 A$ X
  15.   /* USER CODE END RTC_Init 1 */- g  p3 {7 c5 J0 t; `- t" p5 ^
  16. 9 T5 [. d9 q3 V0 v& N; n' g
  17.   /** Initialize RTC Only2 _( l: z9 R) ]. `5 r; F
  18.   */2 A- X* @; G9 ]/ F+ H' q
  19.   hrtc.Instance = RTC;
    % V/ R6 M( m5 F' w+ ^. C0 n9 y! R
  20.   hrtc.Init.HourFormat = RTC_HOURFORMAT_24;# ~4 C! q! a( U' N" `( j- H, d: o
  21.   hrtc.Init.AsynchPrediv = 127;
    4 w& L6 L7 z5 T( T
  22.   hrtc.Init.SynchPrediv = 255;$ P# `( Q& l' x, j- ]
  23.   hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
    ! M+ g9 g4 |6 J, W
  24.   hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
    * p4 Z8 m" i4 Q7 P& x$ u
  25.   hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
    5 u1 O' m, L, B$ h6 A+ K& V8 L
  26.   hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;- {3 w8 [6 Y* p
  27.   if (HAL_RTC_Init(&hrtc) != HAL_OK)
    ; v3 Z7 P; D  b7 R
  28.   {% I) o2 S. P3 o6 |( U* X
  29.     Error_Handler();  {" x; j7 A8 p$ P% T# [7 M5 E
  30.   }) Q: J, r8 |7 I0 }

  31. * `, H0 T2 u# w; s% ^
  32.   /* USER CODE BEGIN Check_RTC_BKUP */# b( D0 D4 N8 B& g
  33.     //新增
    7 s5 `: L: g2 _3 c7 W0 H
  34.   if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0X8888)//判断是否首次上电
    ! R0 ~) q- I& ^1 d
  35.   {
    9 O6 a; t% H: Q8 b" \/ i/ |% O, x
  36.           HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0X8888); //标记数值(写入上电检查数值)0 C% @1 b: ]: n3 ?5 q* W: Y
  37.   /* USER CODE END Check_RTC_BKUP */
      B3 e$ \! j9 ^9 r+ b

  38. + I% k) c( h9 g, ~
  39.   /** Initialize RTC and set the Time and Date
    ; m* X% B( J) M) Z9 A4 Z
  40.   */1 c/ H/ D7 C9 T
  41.   sTime.Hours = 12;
    : q* Q# T. C6 S0 g0 ~8 @) h
  42.   sTime.Minutes = 30;
    ' a& ^6 z! O) H( A$ Z" g
  43.   sTime.Seconds = 30;
    & j( B7 x( p! L& n: h
  44.   sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;0 W- W+ B3 g6 q8 d( \& l
  45.   sTime.StoreOperation = RTC_STOREOPERATION_RESET;: t9 z% _+ o/ l& V# {% b
  46.   if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)4 G( Z6 ?2 d8 o
  47.   {( ~+ c0 E! j! m3 u& n! X+ N
  48.     Error_Handler();, ~. M7 [7 R& C' k1 n+ ^1 M1 D
  49.   }
    3 M/ o. D% m1 L4 y' l
  50.   sDate.WeekDay = RTC_WEEKDAY_TUESDAY;
    # d& {6 D( i! y  B" i  n: d
  51.   sDate.Month = RTC_MONTH_DECEMBER;
    ) W7 \- c/ u. n0 U2 o
  52.   sDate.Date = 20;- o9 `2 o- M0 _3 a: `$ R& N* N
  53.   sDate.Year = 22;
    1 A  D2 w6 Z/ \0 t! u' X2 K, A$ m

  54. & A  w' K# q: W1 C; ~- P7 D
  55.   if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
    6 X4 @7 u7 b/ R2 G8 \; |
  56.   {4 ]  r! o/ b& U' r. V3 l
  57.     Error_Handler();: X: L  X0 R* V5 j
  58.   }6 ~1 h; s- D  {+ ]2 y
  59.   /* USER CODE BEGIN RTC_Init 2 */
    / S7 ~3 ?, D& _: W( K
  60.     //新增
      I' p. p3 Z2 Q. A7 r
  61. //  printf("手动初始化测试!\r\n");//显示初始化信息0 m7 |$ a, b; k
  62.   }
    3 B; ^* W* D5 `0 E" K9 z
  63.   /* USER CODE END RTC_Init 2 */8 }) `- M- F/ e. k( _+ F) U; g  e

  64.   l) l/ t9 _% D  p* |0 {0 H9 T
  65. }
复制代码
+ i9 `, G" @: o8 v# M" @4 i7 [
        在main.c文件中,加入按键、LED灯、串口外设驱动头文件,- p1 r+ v) ]: O2 w# \* ^
  1. /* USER CODE BEGIN Includes */9 L* v8 T# {7 m; Z: _( ~4 ^7 y. ^9 q
  2. #include "../../ICore/key/key.h"
    , P" N- y) C/ v2 c& D1 ?
  3. #include "../../ICore/led/led.h"
    ( P2 K& ]1 `2 g5 Y
  4. #include "../../ICore/print/print.h"& n$ M8 g  `4 F0 Z, c( p
  5. #include "../../ICore/usart/usart.h"3 }) }6 h( J; g3 F8 u% H5 r
  6. /* USER CODE END Includes */
复制代码
2 b3 b- A! g: \
        在main函数中声明RTC日期、时间读取缓存变量。
% n7 l! d) l% \; J& q
  1.   /* USER CODE BEGIN 1 */) d# r' ~& Z0 N' Q$ g3 z. K6 d8 c. O' y
  2.   RTC_DateTypeDef RtcDate;
    + A8 s+ d. h3 l8 N8 V
  3.   RTC_TimeTypeDef RtcTime;
    0 e( n) |' l9 y3 o
  4.   uint8_t time_set = 0;//用户设置时间标记
    4 i8 f8 h8 k5 S: A8 \2 x% {
  5.   /* USER CODE END 1 */
复制代码
" r# S2 e' c7 M' ?! S/ \* W" C
        在main函数中开启串口lpuart1的中断接收。
! S8 h* {# b! t( q
  1.   /* USER CODE BEGIN 2 */
    7 G* Y/ m& s( r% G
  2.   ResetPrintInit(&hlpuart1);
    " L( b9 P# T+ y& v, L/ y
  3.   HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断
    6 a1 _. C; k/ o5 x  D
  4.   HLPUSART_RX_STA = 0;
    " U( ?4 V- z! _( M: |
  5.   printf("APP reset now!\r\n");
    + O9 ]- W' ~3 d- A* b
  6.   /* USER CODE END 2 */
复制代码
6 `' U6 ]# w* i
        在main函数循环体中,实现按键KEY1获取RTC时间以及按键KEY2设置RCT时间,注意RTC设置时间、日期和RTC读取时间、日期时,数据格式保持一致,最好和CubeMX设置时一致,例如读取、写入RTC时间日期都采用RTC_FORMAT_BCD格式。
/ L$ L5 X4 ]  Z3 g
  1. /* USER CODE BEGIN WHILE */+ q9 h( I6 c/ t- h% A- Q8 l6 u# l
  2.   while (1)
    - Z7 N- X( L5 ?$ L; U3 N. }
  3.   {" }: {/ b5 d. x" L% T9 d* Z
  4.           if(HLPUSART_RX_STA&0xC000){//溢出或换行,重新开始) Y7 g) g$ _5 S+ Y/ e* K
  5.                     printf("lpuart1:%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);5 G( @: G- Q. m, Y/ L& e9 d
  6.                     if(time_set){6 e- T( q/ X9 z. V' I' J5 C
  7.                             //将超级终端发过来的数据换算并写入RTC,读取数据为字符内容需要转换为数值, W1 S+ C2 ]  f7 N! s
  8.                     RtcDate.Year =  (HLPUSART_RX_BUF[2]-0x30)*10+HLPUSART_RX_BUF[3]-0x30;//减0x30后才能得到十进制0~9的数据
    1 d( K* F5 v( J& O% k* D/ [
  9.                   RtcDate.Month =  (HLPUSART_RX_BUF[4]-0x30)*10+HLPUSART_RX_BUF[5]-0x30;
    1 F  y- c: Z5 }% c9 |+ }, Y
  10.                   RtcDate.Date =  (HLPUSART_RX_BUF[6]-0x30)*10+HLPUSART_RX_BUF[7]-0x30;$ p, A4 W/ w( K( L" c4 U# j2 s4 o
  11.                   RtcTime.Hours =  (HLPUSART_RX_BUF[8]-0x30)*10+HLPUSART_RX_BUF[9]-0x30;
    , f* x( s8 n5 _* Q' Z9 R
  12.                   RtcTime.Minutes =  (HLPUSART_RX_BUF[10]-0x30)*10+HLPUSART_RX_BUF[11]-0x30;
    " \, Y! g; X5 ~1 X4 D
  13.                   RtcTime.Seconds =  (HLPUSART_RX_BUF[12]-0x30)*10+HLPUSART_RX_BUF[13]-0x30;% G( |  B2 u4 Q2 Q/ t& W% Q7 r
  14.                   //写入格式RTC_FORMAT_BCD或RTC_FORMAT_BIN,确保写入及读取数据格式一致即可) x# V2 q9 r$ P) x  D9 z- g
  15.                     if (HAL_RTC_SetTime(&hrtc,  &RtcTime, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序
    + x+ P! \% \. _9 x, ^
  16.                     {: M" {* l3 a  @7 _
  17.                         printf("写入时间失败!\r\n"); //显示写入失败* f: a9 i6 a4 Q# ?# Q, k
  18.                     }else if (HAL_RTC_SetDate(&hrtc,  &RtcDate, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序1 ~  [, h- {- F( V* T) L5 H
  19.                     {8 H9 }8 B- F( s4 b4 D
  20.                         printf("写入日期失败!\r\n"); //显示写入失败$ p8 H" U* H. l
  21.                     }else printf("写入成功!\r\n");//显示写入成功
    6 Y5 J& |- l. b* n) S0 W( h# \
  22.                     time_set=0;
    & E4 W* B5 z2 z0 B( _0 [: M8 Y, q1 n
  23.                     }
    8 {" }& |( C. d3 l$ e
  24.                     HLPUSART_RX_STA=0;//接收错误,重新开始
    2 Q  \. C9 `* F& B& K
  25.                     HAL_Delay(100);//等待
    ) ]9 ?8 a" y+ X# f
  26.             }
    9 p. I, U* b/ F" r8 m4 S/ L9 Z, _
  27.             if(KEY_1())( x% i" o: {( T9 D/ V  ^! T
  28.           {) v, k1 h" \* F" J0 y7 [; u
  29.                     HAL_RTC_GetTime(&hrtc, &RtcTime,  RTC_FORMAT_BCD);//读出时间值
    + j2 Q: @3 o  N3 P$ o9 c* m( T
  30.           HAL_RTC_GetDate(&hrtc, &RtcDate,  RTC_FORMAT_BCD);//一定要先读时间后读日期
    & N% M* H% T+ |' S
  31.           printf(" RTC实时时钟测试\r\n");) e* M  t5 v. u, E9 s$ J4 j
  32.           printf(" 实时时间:%04d-%02d-%02d  %02d:%02d:%02d  \r\n",2000+RtcDate.Year,
    ) d: |; g  ^( [+ a8 n' x' F+ L
  33.                           RtcDate.Month, RtcDate.Date,RtcTime.Hours, RtcTime.Minutes, RtcTime.Seconds);//显示日期时间
    ( M. d# ^0 W& I6 r& D
  34.           }# k3 p" e$ x) L- r1 _( Z- V
  35.             if(KEY_2())& O6 E; z7 N& c! E' A
  36.           {
    ! ~/ K. t3 s+ e4 c4 h0 |8 l
  37.                     printf(" 请输入设置时间,格式20221220183030,按回车键确定! \r\n");8 l- u+ N. ?# t  q6 Q
  38.                     time_set=1;
    + Q2 g/ \2 X, w8 s! T
  39.           }
    4 d6 I+ s6 J, h
  40.     /* USER CODE END WHILE */
复制代码
. J0 M$ }2 z( k( T, s
         注意和前面简介说过,确保时间和日期值之间的一致性,先处理时间(时分秒),在处理日期(年月日),在stm32l4xx_hal_rtc.c的HAL_RTC_GetTime函数注释中也有明确要求如下,“您必须在HAL_RTC_GetTime()之后调用HAL_RTC_GetDate()来解锁值以确保时间和日期值之间的一致性。读取RTC当前时间锁定日历阴影寄存器中的值,直到读取当前日期以确保时间和日期值之间的一致性。”:9 q; t6 K$ s" m' ~$ A# _
( ]; S- s3 A( w" {
27887a24a62d46a1b27a1e4efe8855d6.png - H5 R+ R( I" S% `  m

. h$ I9 O. W; p8 U+ Q4 O2 F2 i
& }7 i  ]/ r- P4 J0 f7 D
四、编译及测试
6 J; Y& R  W& B- J& }" }) E2 x        和本专栏其他博文一样,设置好工程输出目标格式支持,配置好运行调试支持后,点击编译工程及运行按钮,完成工程编辑及下载。       0 b  @: }& i1 o4 ?) @

, r$ N3 O) ~% G
e451e4c7dd9b4c5fa03817fca447c916.png & J/ ?9 b: y0 X. g5 \) b
3 {" _" P5 W9 t0 \
7 X' z6 ^0 ?* q( H# k' z" z
        打开串口助手工具,连接到开发板,测试
% t9 l7 G9 H% J# N
: G( V7 y4 P3 I9 F" m
        【1】按键KEY1,读取RTC时间,读取出的时间就去前面在cubeMX 设置的时间,并开始计数,因此分秒有差异。
; ~# e3 a0 y, y8 G4 c0 [2 T- ]; L; e5 Q% O
fb4cb2634b1c469cabfdac88376d1c3b.png
, U) d1 U9 i$ e: e) c) D+ K4 s: |$ H8 ]+ P! N- _
         【2】按键KEY2设置时间,并按格式输入时间,然后在按键KEY1读取新的RTC时间,如下图所示,设置时间生效。% ~& e& `- ?: M$ _" v

5 s2 U; H( @, Q) q7 m+ q3 b7 t
30dc865389204e619affac8dc1687db7.png 3 l9 _( s! N/ l

% U, y: ?, D- ~7 p         【3】按开发板上的Reset按键,系统复位,再按键KEY2读取RTC时间,时间依然正常计数
* z) R$ S- T4 E3 H2 x- e' x, u1 z
1862d58464bb469ab8f231cf549be7c0.png / W$ Q/ h9 g$ V  h) Z
2 i) q0 W/ ^) B* r1 V( B: T
5 t5 h$ p" X5 T2 I
         【4】断电一会,然后重新上电,再按键KEY2读取RTC时间,时间恢复到CubeMX设置的初始时间(即为断电后,原来写入的标识无法保持,HAL_RTCEx_BKUPRead读取标记失败重新初始化)。7 J2 ]4 B. @' ?, u
" [) p/ j. y) \* `
6d8f19e2a6ac4eaf959ccae14e9e980d.png
3 v$ Q. ~. b9 \3 K3 z/ o3 V7 ?* y

+ M8 g! C; `0 q. L# b: k————————————————  x0 B2 u, L) [
版权声明:py_free-物联智能如有侵权请联系删除1 F2 e6 E* J- d
! U8 \8 }. w- E7 L& \# |# r

) x! q5 Q& ~7 [% U4 v, N% h/ H2 h$ i% Y$ ^4 @+ 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 手机版