一、RTC简介
- Z0 u) x, e& ?; h 实时时钟的缩写是RTC(Real_Time Clock),核心是晶振,晶振频率一般为32768 Hz 。它为分频计数器提供精确的与低功耗的实基信号。它可以用于产生秒、分、时、日等信息。为了确保时钟长期的准确性,晶振必须正常工作,不能够受到干扰。RTC的晶振又分为:外部晶振和内置晶振。
' Z0 H! z8 \6 U5 I. f$ I& L7 a- x4 t/ ?* {
RTC时间信息存储在后备寄存器(RTC_BKUP)中,在STM32中,通常采用一个32位计数器来计时,而不是用年月日时分秒的分组寄存器,因此在处理STM32的时间信息时(设置或读取),通常要求先处理时分秒时间,再处理年月日时间,以确保时间和日期值之间的一致性。
* \" M6 W, Y0 R# V ^/ ^7 [9 L5 O" W T# M; |5 @+ @
RTC和后备寄存器一般不会被系统或电源复原源复位,当从待机模式唤醒时,也不会复位,但是要注意的是,停止供电会出现重置,数值恢复到初始设置,因此如果要保持长期有效,尤其是断电重启依然有效,需要配置独立供电电源。. `- I$ A, c/ u: m
( h6 N I' q5 `4 R" O RTC时间是以振荡频率来计算的。故它本质上是一个计数器,只是通过换算呈现出日历时间信息。而一般的计数器都是16位的。又因为时间的准确性很重要,故震荡次数越低,时间的准确性越低。所以必定是个高次数。2^15 = 32768 ,而 32768 Hz = 2^15 即分频15次后为1Hz,周期 = 1s,因此RTC的时间基准是1秒。16位计数器最大值0XFFFFFFFF,按秒计算,能支持136年时长计算。
6 Q" k6 C7 z- d% l* H
6 n/ _6 P% i# |) U+ i% C0 f4 c4 H& H0 ^) b. S/ a
二、工程创建及配置
+ {/ N3 ?+ @ |% j 本文采用STM32L496VGTx-ali开发板来实现,该开发板是没有给RTC独立供电的,因此断电后会出现时间重置,但功能模拟演示还是OK的。本文假设已经移植了前面工程,并实现了lpuart1串口通信与电脑端的调试输出以及按键、LED灯驱动$ u8 s( i& A& e% p# L. \; J
% n: P$ b% @' C1 q. M Y$ b
假设已经实现了lpuart1串口通信及按键、LED灯功能,现在双击.ioc打开cubeMX配置界面,开启RTC功能。数据格式支持二进制格式和BCD格式,在HAL中采用RTC_FORMAT_BIN(0x00000000u)和RTC_FORMAT_BCD(0x00000001u)标识。7 |& C; `+ E2 x) `6 c
# ^0 H& X& D. B/ E9 ] F( ~
, b2 e. n9 o" E
+ v; e7 M4 K% A. }1 k2 e' ^1 V BCD(Binary Coded Decimal),即二-十进制编码,是用四位二进制码的 10 种组合表示十进制数0-9。这种编码至少需要用四位二进制码元,而四位二进制码元可以有16种组合。当用这些组合表示十进制数0-9时,有六种组合不用。
- a/ h% N& x. u' z7 k8 D9 u( k: |3 i8 y+ } b+ _
RTC功能开启后,时钟树视图可以关于RTC的选择器可以选择外部高速时钟、外部低速时钟、内部低速时钟,本文选择外部低速时钟(LSE)。4 d% _: k# y9 F8 Q; j
4 ]9 }7 q8 t0 L4 N' ~5 B
" R, \: [, \8 |0 [
6 T2 L R& m& s. E* z6 b
完成配置保存输出按钮(本文是为每个外设生成独立.h/.c源码),在生成的rtc.c文件中可以看到RTC初始化函数关于年月日、时分秒的设值和cubeMX上配置信息一致。
9 S+ H) M8 P) G: N% G) Z
8 I1 d& l8 _* h P+ ^- v: j- D 备注:如果在cubeMX配置界面没有选择数据格式,年日、时分秒默认是0X前缀的。
2 Z. g$ q' l; h( W( x1 Y
6 L/ R) @ x# S
5 B8 \' j8 Y9 J0 n$ Q
" V. y6 ]5 s9 u1 k, q 三、驱动代码设计实现) {( p- n6 {4 a% I6 q
【1】在Core/Src/rtc.c文件中,调整RTC初始化函数,在RTC设置初始化时间、日期之前,加入后备寄存器的读取,判定是否已经进行过初始化,避免重复设置时间、日期而改写本该持续计数的RTC计数。
2 H8 A8 s* Q/ q$ Z! A5 W1 u3 ]7 ?- void MX_RTC_Init(void)
9 Y& J0 d% l; x( D P' G - {) g% p" w9 f3 X5 ?2 @+ ?
-
% w x% N; I" t& ], ? - /* USER CODE BEGIN RTC_Init 0 */
: ]0 e1 }# l3 V# e* }7 q0 p* Z -
) \8 k: D+ u9 c$ _, Y0 A3 _ - /* USER CODE END RTC_Init 0 */ p* J. v: B4 o& X( y# p
-
9 V$ P2 `$ m8 g s/ W5 j - RTC_TimeTypeDef sTime = {0};
( N3 [. s4 l5 Q0 i% Y" I3 Z5 }* Q - RTC_DateTypeDef sDate = {0};
. D* M: u) x7 L& Z! V - " {6 Q! s n# q' _
- /* USER CODE BEGIN RTC_Init 1 */
+ t9 x+ f# H5 ?# w7 D6 R - //新增8 c0 r( `3 e3 Z) M
- __HAL_RCC_PWR_CLK_ENABLE();//使能电源时钟PWR; e; Y( U* }, K) S
- HAL_PWR_EnableBkUpAccess();//取消备份区域写保护
$ `" P3 D, [+ A2 ?! B' @ - /* USER CODE END RTC_Init 1 */
4 z& X3 o& D, M, i% H; i8 n1 I. i; w -
# _4 \! K/ L. a! H; [; q7 r - /** Initialize RTC Only
& [. v; @! L5 _5 ^( W% _0 d0 i; d. x - */
! R, c, j! Z; c0 o: W- u - hrtc.Instance = RTC;' B! F* }. {: S, q! s9 }/ ~
- hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
( Y6 j0 v' o' r& r' M/ l$ _% r; A - hrtc.Init.AsynchPrediv = 127;
! i [ `3 Y9 }7 O; e/ J: r - hrtc.Init.SynchPrediv = 255;
$ |2 ^# g3 t/ V - hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;5 [$ Y, j; c. h" @5 w- L
- hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;' ~4 T* O8 m2 G/ i# j
- hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
4 ^2 u* o: d/ ]( ? - hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;: U. \$ m$ T! Q" T! r
- if (HAL_RTC_Init(&hrtc) != HAL_OK)
! Y2 w" q, m4 Z8 c+ ^ - {! z% K0 I6 |+ b9 o# i: R/ S k
- Error_Handler();
6 j# C: h" P$ k& t( p, n - }1 M% T# F1 e' F: x; y- L2 U
-
. K: x1 G/ J$ @- C" L# V* o - /* USER CODE BEGIN Check_RTC_BKUP */4 q* `3 i. \, |' [7 U5 r
- //新增
; p% {# q, [# _5 F - if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0X8888)//判断是否首次上电
5 s. L2 I' C$ I9 ?) Y - {
; N) f8 ^/ t& a. S - HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0X8888); //标记数值(写入上电检查数值)7 H9 `; a3 m' @" T5 d/ H K7 d
- /* USER CODE END Check_RTC_BKUP */
* T) K* l1 O) V/ G9 b' B3 U - 6 p, {! j7 a" z' y0 _3 R$ N7 S( d
- /** Initialize RTC and set the Time and Date+ `# F7 t N+ W! ~$ m& a/ u# _1 T
- */
7 z# C' J. w, ^* T - sTime.Hours = 12;
. [2 N! G2 j% b7 c0 ]3 B2 C - sTime.Minutes = 30;& U$ |4 O' }. h2 w0 K0 v3 {
- sTime.Seconds = 30;
; N; H9 D. o' _4 i5 o - sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;- b8 M+ K' q/ ]4 f' k8 B: C( z
- sTime.StoreOperation = RTC_STOREOPERATION_RESET;& A8 L" J8 i8 h6 n0 i
- if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK): ^; n! u Y9 T: ]+ L1 d
- {2 e+ }- K( M D4 S: a
- Error_Handler();& g G+ [. Q; p# n
- }/ ~! O# T7 j- K; ]; B9 I
- sDate.WeekDay = RTC_WEEKDAY_TUESDAY;6 C# F3 q0 e0 R+ K
- sDate.Month = RTC_MONTH_DECEMBER;" h; _8 Y: P, b
- sDate.Date = 20;
2 c8 F4 Z& c+ P' P8 ~ - sDate.Year = 22;
' I4 N* u: T9 q4 s- U% l -
0 e' R, [+ e' \1 k - if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK): T( |! o& Y8 x
- {
, N9 U0 m7 v' i/ _$ ]7 A$ a" E! @' ? - Error_Handler();
* h: W" y# K) {. a$ [4 o# h1 m - }
2 ?0 w6 x* L/ g1 Y, S |1 p0 Q+ Q - /* USER CODE BEGIN RTC_Init 2 */
, N1 G' x( w2 `' i0 t9 \: ^- N - //新增
, _( o( ^) T& W T - // printf("手动初始化测试!\r\n");//显示初始化信息
7 ]: |0 K) l7 d6 ^ - }" J9 f1 D, B7 c( J: |; X% u" f
- /* USER CODE END RTC_Init 2 */
) [5 I k) ?; K" a) ^ - 8 d1 ?+ D5 ~, T6 ^7 U2 X2 [. f( P
- }
复制代码
8 o0 o) [$ Z& p |1 G 在main.c文件中,加入按键、LED灯、串口外设驱动头文件,
& ]" e! Q8 I( m" D% q: M) h- /* USER CODE BEGIN Includes */* v# T0 I1 j7 ~
- #include "../../ICore/key/key.h"6 t# X7 W. @4 N" u8 @: }, `
- #include "../../ICore/led/led.h" \. D5 f, w* \0 ?2 ~
- #include "../../ICore/print/print.h"; r/ P1 b/ _; w$ B! g. m4 S
- #include "../../ICore/usart/usart.h"
# Y* h7 k+ q+ l6 x2 [ - /* USER CODE END Includes */
复制代码
4 F$ \) @/ {5 D5 ?5 Q9 f 在main函数中声明RTC日期、时间读取缓存变量。; z& ?& v& [" b9 o& {! D
- /* USER CODE BEGIN 1 */
4 ~+ r2 r; F5 r& N& T- P - RTC_DateTypeDef RtcDate; ?6 ]7 E( L5 d2 N$ p b
- RTC_TimeTypeDef RtcTime;3 R0 ~- R* M }0 k5 [, m, z
- uint8_t time_set = 0;//用户设置时间标记$ P* _- `( C; K; f3 X$ n' F
- /* USER CODE END 1 */
复制代码 2 p" Y, I9 M; d
在main函数中开启串口lpuart1的中断接收。' t+ a6 L# @! A# C' h# R% @" T2 H
- /* USER CODE BEGIN 2 */% Q; V) K1 p! ~
- ResetPrintInit(&hlpuart1);9 j& `' y; ]5 ]- F) G- z
- HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断1 }$ v/ r+ X9 x( p6 ]! C# U1 u& X
- HLPUSART_RX_STA = 0;6 Y0 X: t S3 e6 a
- printf("APP reset now!\r\n");
; E: R% [/ b4 h% b i7 M8 d - /* USER CODE END 2 */
复制代码
# b; @. I5 V! L3 I" E7 b B. r 在main函数循环体中,实现按键KEY1获取RTC时间以及按键KEY2设置RCT时间,注意RTC设置时间、日期和RTC读取时间、日期时,数据格式保持一致,最好和CubeMX设置时一致,例如读取、写入RTC时间日期都采用RTC_FORMAT_BCD格式。/ ~9 t2 _7 U( S6 I% m
- /* USER CODE BEGIN WHILE */
! {4 A u! V: K2 k0 J - while (1)
w$ s9 z q, R r7 [- g - {: {( ^/ B( C8 z4 |# d1 U
- if(HLPUSART_RX_STA&0xC000){//溢出或换行,重新开始* y0 d* }( C8 o1 U$ i
- printf("lpuart1:%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);( Y6 {! c9 j; k. w1 N
- if(time_set){3 u$ u+ w* ], W/ g( |0 J
- //将超级终端发过来的数据换算并写入RTC,读取数据为字符内容需要转换为数值0 m# @. R, H, j: [ @
- RtcDate.Year = (HLPUSART_RX_BUF[2]-0x30)*10+HLPUSART_RX_BUF[3]-0x30;//减0x30后才能得到十进制0~9的数据5 {, ~( g0 @2 x" X. f2 U* |9 I
- RtcDate.Month = (HLPUSART_RX_BUF[4]-0x30)*10+HLPUSART_RX_BUF[5]-0x30;
) ~- @9 l8 q( k# X3 s" k+ S - RtcDate.Date = (HLPUSART_RX_BUF[6]-0x30)*10+HLPUSART_RX_BUF[7]-0x30;
- e2 v+ p% X+ U9 h. W4 V - RtcTime.Hours = (HLPUSART_RX_BUF[8]-0x30)*10+HLPUSART_RX_BUF[9]-0x30;
9 Z+ w5 m# n2 B! _' C" n - RtcTime.Minutes = (HLPUSART_RX_BUF[10]-0x30)*10+HLPUSART_RX_BUF[11]-0x30;- r, n2 T2 L* u$ z" }
- RtcTime.Seconds = (HLPUSART_RX_BUF[12]-0x30)*10+HLPUSART_RX_BUF[13]-0x30;0 h+ z0 l) b" G9 h
- //写入格式RTC_FORMAT_BCD或RTC_FORMAT_BIN,确保写入及读取数据格式一致即可' l, Y# t6 \0 ~, E
- if (HAL_RTC_SetTime(&hrtc, &RtcTime, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序
: ~. F) d0 F! @4 k( F$ I - {
8 l% m) c0 K' x6 D* q# j9 \6 i - printf("写入时间失败!\r\n"); //显示写入失败
. W, t. \' ^. ^3 R4 |/ n - }else if (HAL_RTC_SetDate(&hrtc, &RtcDate, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序* n; i- B" C+ Z+ B5 y% f
- {
7 \1 ]1 v& |9 b - printf("写入日期失败!\r\n"); //显示写入失败7 f7 R* I# n Z A& N1 U
- }else printf("写入成功!\r\n");//显示写入成功
- |5 T3 ?0 l. _; ~$ P7 z - time_set=0;4 Z4 c1 B/ {( u1 b: t8 H- r5 [, \
- }
+ `1 S+ j% b/ w% l - HLPUSART_RX_STA=0;//接收错误,重新开始+ b5 H$ w" i F* W) C
- HAL_Delay(100);//等待
7 [* x' l: E' E8 i& R: Y' Z - }
% J& Z$ `1 X+ b - if(KEY_1())
: U; { J# X1 u9 ^! [7 p4 S - {6 A8 Q$ I- a* d
- HAL_RTC_GetTime(&hrtc, &RtcTime, RTC_FORMAT_BCD);//读出时间值
+ H k, u8 p5 Z9 G) ?5 J1 P, {( [% j - HAL_RTC_GetDate(&hrtc, &RtcDate, RTC_FORMAT_BCD);//一定要先读时间后读日期, K/ ^' o8 o# W8 q0 n; L" y
- printf(" RTC实时时钟测试\r\n");
0 V9 Z1 o) o; L& a) B - printf(" 实时时间:%04d-%02d-%02d %02d:%02d:%02d \r\n",2000+RtcDate.Year,' C* b' r. s1 R& O4 Z( ]- a
- RtcDate.Month, RtcDate.Date,RtcTime.Hours, RtcTime.Minutes, RtcTime.Seconds);//显示日期时间
) C: u4 g: J+ h - }
: p; H$ Q3 X8 _ - if(KEY_2())% b+ y. R2 i, u0 J4 I) G3 c
- {& p. ]0 f7 g' I: J. @/ k
- printf(" 请输入设置时间,格式20221220183030,按回车键确定! \r\n");) T7 _% f( g# H' G4 K+ e% D
- time_set=1;
* g' }- u* X7 h* n# K6 b3 @; I1 G - }
( ]3 x& T! o7 W, ^ b - /* USER CODE END WHILE */
复制代码
Z4 @: E1 m. Y q- i; I 注意和前面简介说过,确保时间和日期值之间的一致性,先处理时间(时分秒),在处理日期(年月日),在stm32l4xx_hal_rtc.c的HAL_RTC_GetTime函数注释中也有明确要求如下,“您必须在HAL_RTC_GetTime()之后调用HAL_RTC_GetDate()来解锁值以确保时间和日期值之间的一致性。读取RTC当前时间锁定日历阴影寄存器中的值,直到读取当前日期以确保时间和日期值之间的一致性。”:
6 }1 ~" r( _; W/ H' i
, z* p4 G/ P9 G5 `9 L+ x9 \
* f8 H% w5 q- \- ~/ n8 O- U* a
- y e# l3 G; R! m, O5 \1 B
, w7 q7 U0 x$ f2 J/ l6 q5 P3 N! b四、编译及测试" m( T; K- q; W$ |" `5 i
和本专栏其他博文一样,设置好工程输出目标格式支持,配置好运行调试支持后,点击编译工程及运行按钮,完成工程编辑及下载。
; I/ b; D7 H5 C H# Z( n8 K7 q$ s* y6 X; W2 ]( G, v
4 @! k" m1 C# T9 I! _8 `; E8 T& U P3 y6 g, S
/ q7 r, h5 C2 _, D- f' @1 r 打开串口助手工具,连接到开发板,测试
( X& |1 S3 Z' j7 L% w9 X- f, h4 Y2 T% ^; d! U2 H
【1】按键KEY1,读取RTC时间,读取出的时间就去前面在cubeMX 设置的时间,并开始计数,因此分秒有差异。
" u5 c! f" L9 d( ^. s& b
" A, C3 Z8 I1 C& q; X- h7 p) }
T* x7 q o, L7 Z, n
* o0 z/ D2 W+ M L- l6 `
【2】按键KEY2设置时间,并按格式输入时间,然后在按键KEY1读取新的RTC时间,如下图所示,设置时间生效。
: j+ y |$ j/ q: ^- h/ g/ g" f% p( L8 H z8 z! |7 K2 e
* {/ h" o9 H0 }( O
_1 q. Z* c$ F- ?7 H s! q7 D 【3】按开发板上的Reset按键,系统复位,再按键KEY2读取RTC时间,时间依然正常计数
: k8 u0 l6 [- B5 L: |& b
! W2 s/ B# ?" c+ c! m. y( D" n( L
/ |( c2 y b$ N) p
: ~7 ^* o: v! s
1 u, z7 q" X) R6 @( x& G/ i 【4】断电一会,然后重新上电,再按键KEY2读取RTC时间,时间恢复到CubeMX设置的初始时间(即为断电后,原来写入的标识无法保持,HAL_RTCEx_BKUPRead读取标记失败重新初始化)。
6 D" c; G) P# l, A# I" E. y) [/ t/ r2 Z9 x) g
' j9 b: U: T, f0 x f
7 y1 g8 J6 k2 c" A2 \: @9 q, U5 u
————————————————
/ H) w9 N0 x2 \) y* F5 ^: W版权声明:py_free-物联智能如有侵权请联系删除 l2 U: q5 U q. b' q9 G
) j7 E1 { r5 u( C" `5 w4 w8 e- d( l
9 C( ^ `3 c' c7 Z |
瞎J13乱发,傻福玩意
F1系列的RTC是计时器,你帖子里用的不是STM32L496VGT吗?看过这个芯片的数据手册吗?MD,抄都TM抄不明白,下边这张图看得懂吗?傻福