一、RTC简介
$ W9 [4 o# ]7 r% _7 q h, o 实时时钟的缩写是RTC(Real_Time Clock),核心是晶振,晶振频率一般为32768 Hz 。它为分频计数器提供精确的与低功耗的实基信号。它可以用于产生秒、分、时、日等信息。为了确保时钟长期的准确性,晶振必须正常工作,不能够受到干扰。RTC的晶振又分为:外部晶振和内置晶振。
4 L7 \! O9 i; x5 Y3 B$ o, U
* {& ?: {- B3 D+ o, L$ Q+ K RTC时间信息存储在后备寄存器(RTC_BKUP)中,在STM32中,通常采用一个32位计数器来计时,而不是用年月日时分秒的分组寄存器,因此在处理STM32的时间信息时(设置或读取),通常要求先处理时分秒时间,再处理年月日时间,以确保时间和日期值之间的一致性。6 L& `( q5 i( _5 R0 r! P- {8 m
% j; J- `! _- M4 ^5 p- z
RTC和后备寄存器一般不会被系统或电源复原源复位,当从待机模式唤醒时,也不会复位,但是要注意的是,停止供电会出现重置,数值恢复到初始设置,因此如果要保持长期有效,尤其是断电重启依然有效,需要配置独立供电电源。+ M4 d; f# V& E2 T1 b
1 d/ Q7 F" z1 Y- l1 c( ]
RTC时间是以振荡频率来计算的。故它本质上是一个计数器,只是通过换算呈现出日历时间信息。而一般的计数器都是16位的。又因为时间的准确性很重要,故震荡次数越低,时间的准确性越低。所以必定是个高次数。2^15 = 32768 ,而 32768 Hz = 2^15 即分频15次后为1Hz,周期 = 1s,因此RTC的时间基准是1秒。16位计数器最大值0XFFFFFFFF,按秒计算,能支持136年时长计算。
7 U3 m, D# G( }7 S1 d$ \# D, z; s2 h5 B9 R6 g
% F& h' _6 }' F) Q4 ?' h" \1 i* C
二、工程创建及配置; y! ~. S( Z# l; Z; S4 M
本文采用STM32L496VGTx-ali开发板来实现,该开发板是没有给RTC独立供电的,因此断电后会出现时间重置,但功能模拟演示还是OK的。本文假设已经移植了前面工程,并实现了lpuart1串口通信与电脑端的调试输出以及按键、LED灯驱动) D: O: b( `. N4 l) e4 P
4 D! ]9 L9 N/ O- b5 h
假设已经实现了lpuart1串口通信及按键、LED灯功能,现在双击.ioc打开cubeMX配置界面,开启RTC功能。数据格式支持二进制格式和BCD格式,在HAL中采用RTC_FORMAT_BIN(0x00000000u)和RTC_FORMAT_BCD(0x00000001u)标识。
5 b2 k1 t9 ~' e) G# y4 o6 G* }; O$ u
1 X O& b+ R" _+ H/ ]! ^
* }( h* S1 v2 d! \' ]8 N8 Y0 w
BCD(Binary Coded Decimal),即二-十进制编码,是用四位二进制码的 10 种组合表示十进制数0-9。这种编码至少需要用四位二进制码元,而四位二进制码元可以有16种组合。当用这些组合表示十进制数0-9时,有六种组合不用。( N" q3 O# p* ]- u! o3 \2 N3 |
* [3 L* z! Q0 E4 ?4 D9 E, E RTC功能开启后,时钟树视图可以关于RTC的选择器可以选择外部高速时钟、外部低速时钟、内部低速时钟,本文选择外部低速时钟(LSE)。) F& {" X6 o4 q0 k8 ]& J6 b% y
& k2 |1 _$ u9 Q" k, x9 J: Z0 E
/ h! L% ]6 Y+ A. z8 Y; A
9 ~2 H0 f: d( P6 f1 ~- s8 C 完成配置保存输出按钮(本文是为每个外设生成独立.h/.c源码),在生成的rtc.c文件中可以看到RTC初始化函数关于年月日、时分秒的设值和cubeMX上配置信息一致。
& L+ V% W4 m- J O6 H$ g5 G' N% _+ m7 U9 O1 r" j, i9 c
备注:如果在cubeMX配置界面没有选择数据格式,年日、时分秒默认是0X前缀的。
, m9 d6 R2 A f4 t* P8 I9 e- \( K4 Z- r6 C
) n$ Y# E$ X8 b; s p' d( U" D- p$ n2 q- y6 y" K& l0 p/ w" H; F
三、驱动代码设计实现
9 |) j' Z" l$ G- o4 o 【1】在Core/Src/rtc.c文件中,调整RTC初始化函数,在RTC设置初始化时间、日期之前,加入后备寄存器的读取,判定是否已经进行过初始化,避免重复设置时间、日期而改写本该持续计数的RTC计数。
( m( Q! T8 Z6 D; c1 h, l- void MX_RTC_Init(void)
$ ?* W; B- T0 Z' |! d - {0 ]4 P+ W4 j3 C1 K/ N
-
& O9 M' }# [- Y" b1 i: r3 N - /* USER CODE BEGIN RTC_Init 0 */
2 z f! G1 ~* K( _( z: o" ~ - 3 Y# c7 D. M3 r% g# j( N
- /* USER CODE END RTC_Init 0 */4 q' l! j1 K1 f3 |# @
-
3 |( r( \3 l& y" l - RTC_TimeTypeDef sTime = {0};
: F) O0 N: R& o% A" S( T' T - RTC_DateTypeDef sDate = {0};
, F7 |2 D7 u- Z, P -
6 r2 u6 X& O% v9 f6 Q' o - /* USER CODE BEGIN RTC_Init 1 */
) J' N1 J6 R$ w D& T8 U - //新增5 Q o$ ?' i3 }
- __HAL_RCC_PWR_CLK_ENABLE();//使能电源时钟PWR8 Q1 L6 @$ c7 k. Z# C
- HAL_PWR_EnableBkUpAccess();//取消备份区域写保护
1 P5 n* G! K. X - /* USER CODE END RTC_Init 1 */
1 I& p2 R2 z3 Z% \( C - 7 v- G& J( Y9 o+ J
- /** Initialize RTC Only
1 G8 f+ p# b" E) w - */
; b2 }! n1 k; o% o+ m - hrtc.Instance = RTC;1 B( z8 T$ U0 {% I* f; Z$ i. y
- hrtc.Init.HourFormat = RTC_HOURFORMAT_24;* [! k. b* R; {) ~
- hrtc.Init.AsynchPrediv = 127;3 E9 j' a' w! T/ \) V& ^+ d
- hrtc.Init.SynchPrediv = 255;6 J& U0 q* O/ G9 c3 \; u; z
- hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
0 i4 \& l2 J0 }% F - hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
5 ?' E2 h3 `) V N C; A - hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;4 T% |% `) z3 L: u3 K$ I; m; ~6 x
- hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
% b! Q* q' \6 U6 W* \. v! h9 g. L - if (HAL_RTC_Init(&hrtc) != HAL_OK)( a M* Q0 ]* Z0 v: k
- {9 v) N& F) |, F& h( _
- Error_Handler();
5 k+ \3 Q6 d# \% _8 f: ?2 D2 n - }
8 G2 F3 Z* E/ S+ j x$ P" B% o( f -
9 O& |! S1 t4 e - /* USER CODE BEGIN Check_RTC_BKUP */$ a" d3 T/ s' l) Q/ {5 j' @, c) q
- //新增
! X" N `" @: l$ t, J" a& Q - if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0X8888)//判断是否首次上电* `/ ~/ J& a3 C- y6 Y
- {* z Y# P% m/ H1 w1 a, |9 p3 V: O
- HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0X8888); //标记数值(写入上电检查数值)) V/ `# V b4 T+ Z, s5 R1 T: `
- /* USER CODE END Check_RTC_BKUP */
6 W% u, |2 H2 s( `4 Z; d -
) H, p& {7 l! t* ]4 ~; D/ n+ B - /** Initialize RTC and set the Time and Date/ w+ y! d) n* u1 {
- */$ _! P, |$ p* w' b7 @
- sTime.Hours = 12;5 J, f0 M5 p: d" M4 O% E- `1 ]
- sTime.Minutes = 30;" s; m# `7 c3 n/ m$ E
- sTime.Seconds = 30;
9 [! _5 B3 h/ B+ G- R( Q( m* R* @ - sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;2 o# c# f6 O5 E; Y: U, i
- sTime.StoreOperation = RTC_STOREOPERATION_RESET;- \1 k/ C" b* e0 [4 a2 V8 ]
- if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
$ }: C/ ^! H) q, @3 b. \' U) q - {
/ f v( z6 c) L N% x - Error_Handler();+ d" _( i) i7 U3 Q' P/ P8 V
- }
/ L( O* n3 d9 e: ?/ l( w" ^ - sDate.WeekDay = RTC_WEEKDAY_TUESDAY;
0 K* O0 C! @( [ - sDate.Month = RTC_MONTH_DECEMBER;% t5 a# \9 t# `) ?
- sDate.Date = 20;
$ P/ h9 B, R+ @9 a - sDate.Year = 22;
6 ^- A1 \) ^. b! N" a - 9 u4 c) D# x2 h0 ]7 I
- if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
$ I$ B! g u: c1 S0 x: y - {
- }2 d+ m% ~' c' E) N; u6 \ - Error_Handler();! i' n5 {+ T- q o: o% F$ x5 \6 _0 Z
- }
# `0 W$ L4 j8 u. A - /* USER CODE BEGIN RTC_Init 2 */
* f, B- k1 @# U. f7 b. P4 P - //新增8 z% ]! X4 K0 c0 y' g! z4 X
- // printf("手动初始化测试!\r\n");//显示初始化信息
9 t1 n% y5 I+ g$ \ - }, \, |( S+ i$ c8 Y& o! [
- /* USER CODE END RTC_Init 2 */
( x$ U' C7 @7 n: ^& O& M - # d# i: E' h: K7 Z) }1 }& j& a
- }
复制代码
8 N9 Y% N% d+ f 在main.c文件中,加入按键、LED灯、串口外设驱动头文件,1 U0 w: e, A7 t6 G/ P& I
- /* USER CODE BEGIN Includes */1 t6 a) f% H+ \
- #include "../../ICore/key/key.h"$ w0 _/ X- [% t( Z
- #include "../../ICore/led/led.h"
$ u! s1 F- N7 Y% Q - #include "../../ICore/print/print.h"
% c3 \) o; _& T' W% @7 f8 m# p( ] - #include "../../ICore/usart/usart.h"1 i8 _' }/ x. d* t
- /* USER CODE END Includes */
复制代码 - y6 y* m2 @' [7 C
在main函数中声明RTC日期、时间读取缓存变量。
& G# q0 @: ]' V$ B4 l, f Z- /* USER CODE BEGIN 1 */. S9 p& W3 U6 M7 H& V: l/ o/ \; ^
- RTC_DateTypeDef RtcDate;1 ]/ m4 N% m- L7 R, m
- RTC_TimeTypeDef RtcTime;, b7 } Q7 u, c9 G& n- J) S
- uint8_t time_set = 0;//用户设置时间标记1 v3 p4 E8 o* ~: B
- /* USER CODE END 1 */
复制代码 " V# M/ h& i l6 z: q2 g0 n; p
在main函数中开启串口lpuart1的中断接收。
- Y( i+ x4 A: }0 u$ `+ {. f- /* USER CODE BEGIN 2 */
9 g! X* ]* f0 G& T! k I i) L6 }) A - ResetPrintInit(&hlpuart1);
4 k* J4 ]: ?1 P( M9 G, b, G - HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断0 o/ j# }+ J/ [
- HLPUSART_RX_STA = 0;. w4 T8 L4 O: q5 Q( L+ z4 w
- printf("APP reset now!\r\n");
7 W- w) Y* d, I0 {" u" i% ~' ` - /* USER CODE END 2 */
复制代码 2 e. m. X7 a/ D: j0 p+ q! {1 n
在main函数循环体中,实现按键KEY1获取RTC时间以及按键KEY2设置RCT时间,注意RTC设置时间、日期和RTC读取时间、日期时,数据格式保持一致,最好和CubeMX设置时一致,例如读取、写入RTC时间日期都采用RTC_FORMAT_BCD格式。
8 C0 M& V' G r, c3 k- /* USER CODE BEGIN WHILE */
) i6 y' p0 N5 f2 r - while (1), K& s1 C; n1 I0 _) w; L
- {0 Q. P4 ~, t% T+ i, I0 }
- if(HLPUSART_RX_STA&0xC000){//溢出或换行,重新开始
5 N# X W8 \; a6 Z0 }1 m) V' f - printf("lpuart1:%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
" |, k) w: d: P; C1 I - if(time_set){
6 d, s' Q! z2 w/ D* P$ n: \ - //将超级终端发过来的数据换算并写入RTC,读取数据为字符内容需要转换为数值: u0 K& _$ Y! R
- RtcDate.Year = (HLPUSART_RX_BUF[2]-0x30)*10+HLPUSART_RX_BUF[3]-0x30;//减0x30后才能得到十进制0~9的数据
; w" H( _; k& P7 V! M( w' J' C* n$ n - RtcDate.Month = (HLPUSART_RX_BUF[4]-0x30)*10+HLPUSART_RX_BUF[5]-0x30;# N. J* G5 ?/ j, q, Z9 I
- RtcDate.Date = (HLPUSART_RX_BUF[6]-0x30)*10+HLPUSART_RX_BUF[7]-0x30;
' g' t" X k* x% D2 o - RtcTime.Hours = (HLPUSART_RX_BUF[8]-0x30)*10+HLPUSART_RX_BUF[9]-0x30; f) Z5 ^5 W+ f1 V1 X* b
- RtcTime.Minutes = (HLPUSART_RX_BUF[10]-0x30)*10+HLPUSART_RX_BUF[11]-0x30;2 t' l P. _/ h' X9 e+ i
- RtcTime.Seconds = (HLPUSART_RX_BUF[12]-0x30)*10+HLPUSART_RX_BUF[13]-0x30;
$ i1 n8 O, l7 }1 j( }/ T - //写入格式RTC_FORMAT_BCD或RTC_FORMAT_BIN,确保写入及读取数据格式一致即可
/ z$ ^, d' \3 W - if (HAL_RTC_SetTime(&hrtc, &RtcTime, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序/ X& x0 F8 b' `6 `, O7 g% x
- {3 } [( H! T1 ]/ J% ^& W
- printf("写入时间失败!\r\n"); //显示写入失败
& v3 o0 ?2 h; j0 U - }else if (HAL_RTC_SetDate(&hrtc, &RtcDate, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序) N) O1 y. a6 a6 c# y
- {- {+ H2 U8 P$ s: i* J
- printf("写入日期失败!\r\n"); //显示写入失败! N, Q. r' C Q
- }else printf("写入成功!\r\n");//显示写入成功
( b$ i4 ?6 |# J6 A8 _* a I; a - time_set=0;
1 C! ?. t g4 |2 \' H& _. y - }
* m1 t0 [% w& O1 Z; p5 n - HLPUSART_RX_STA=0;//接收错误,重新开始; x. w- s' g. [' s; \) V5 F7 f! s+ S
- HAL_Delay(100);//等待6 `( T- Z( d4 i( K8 ^4 N( C
- }
& q. o" ?6 g6 z2 b9 x7 r" b, F - if(KEY_1())) K4 r: p1 [' n- C6 E( c X
- {& |: {8 ~& M; e1 j! N
- HAL_RTC_GetTime(&hrtc, &RtcTime, RTC_FORMAT_BCD);//读出时间值
( |: D4 W9 D" Y/ A - HAL_RTC_GetDate(&hrtc, &RtcDate, RTC_FORMAT_BCD);//一定要先读时间后读日期
- ?4 `9 \! x' x# i Z& ]8 R - printf(" RTC实时时钟测试\r\n");
) d; |( P8 v0 T - printf(" 实时时间:%04d-%02d-%02d %02d:%02d:%02d \r\n",2000+RtcDate.Year,
7 M) F ]7 Y2 s s* a, y - RtcDate.Month, RtcDate.Date,RtcTime.Hours, RtcTime.Minutes, RtcTime.Seconds);//显示日期时间
6 j n, M- T/ P p - }
~1 x& |" K+ z' K9 [ - if(KEY_2())1 ^) K* G* f2 G
- {: o' P5 y2 ?+ I& q3 e1 ^
- printf(" 请输入设置时间,格式20221220183030,按回车键确定! \r\n");; N& d& o) X: ~) g
- time_set=1;/ u+ c' M* `3 e/ c+ ^+ H
- }
' ^+ Z+ x" ]$ E2 b - /* USER CODE END WHILE */
复制代码 3 ?& R5 E7 M; C/ Y: j
注意和前面简介说过,确保时间和日期值之间的一致性,先处理时间(时分秒),在处理日期(年月日),在stm32l4xx_hal_rtc.c的HAL_RTC_GetTime函数注释中也有明确要求如下,“您必须在HAL_RTC_GetTime()之后调用HAL_RTC_GetDate()来解锁值以确保时间和日期值之间的一致性。读取RTC当前时间锁定日历阴影寄存器中的值,直到读取当前日期以确保时间和日期值之间的一致性。”:7 ]5 [( p3 V. i( ~2 N6 O% l8 j
* G/ V' X! Q. E5 d" @ F* z
5 }. o9 ?0 l$ W( u% p, l; r" @
( W, [9 z5 I% s1 q! Z4 J/ a
6 ^7 n2 g% r6 c9 Y, }: ?4 y四、编译及测试
( F$ F, T- Z( X0 X* z 和本专栏其他博文一样,设置好工程输出目标格式支持,配置好运行调试支持后,点击编译工程及运行按钮,完成工程编辑及下载。 6 C& F. Q! ^% f' B" E% X& S1 e
$ { [: t6 J& E5 `( c1 ^9 W
! k& e5 K8 s3 j/ I K
. w) H; N( ^% N" v
& u* _7 B% ]# O6 p6 z3 Z7 Q6 U' ]
打开串口助手工具,连接到开发板,测试
, @" p5 e# v! F$ f
$ A$ J* [. A+ a8 G7 c& C9 J 【1】按键KEY1,读取RTC时间,读取出的时间就去前面在cubeMX 设置的时间,并开始计数,因此分秒有差异。
8 a' @% w3 n* l* L
0 b5 r! v3 [$ v T' t
; I7 \ ^! \/ ]3 a# O
7 W5 u8 G( ~) ~: q8 X3 S9 m; G/ ^
【2】按键KEY2设置时间,并按格式输入时间,然后在按键KEY1读取新的RTC时间,如下图所示,设置时间生效。, ?0 t, j7 U/ `5 F* l0 E
3 N6 u" w* l$ q
- ]) x# w8 c3 M* s6 U' f% C
6 K7 n& i r# S, S. |1 K+ F 【3】按开发板上的Reset按键,系统复位,再按键KEY2读取RTC时间,时间依然正常计数
" g5 N" |7 w, u; y* o& X0 O, v
' r6 t% J2 P% O0 N, z
2 @( G) L" _( k5 a; j2 i" P( E* [+ s* |
0 P' A* d0 Y; \& @ H8 v
【4】断电一会,然后重新上电,再按键KEY2读取RTC时间,时间恢复到CubeMX设置的初始时间(即为断电后,原来写入的标识无法保持,HAL_RTCEx_BKUPRead读取标记失败重新初始化)。; z8 v0 a" h: g. g2 u Q
& W1 j- @9 @% U- t
+ ]" H% E p* R0 H( m
, P! C1 U# }( a8 u
————————————————6 L# u0 X* r+ o) [6 u
版权声明:py_free-物联智能如有侵权请联系删除
5 z$ A& |- P% H; b X+ t
( t3 b% y3 @& r6 L. Z$ m
" c" G8 i y% V( x+ y
2 m5 l" } X3 G% V |
瞎J13乱发,傻福玩意
F1系列的RTC是计时器,你帖子里用的不是STM32L496VGT吗?看过这个芯片的数据手册吗?MD,抄都TM抄不明白,下边这张图看得懂吗?傻福