一、RTC简介
5 u- a. f% | r; K' Z3 w1 s 实时时钟的缩写是RTC(Real_Time Clock),核心是晶振,晶振频率一般为32768 Hz 。它为分频计数器提供精确的与低功耗的实基信号。它可以用于产生秒、分、时、日等信息。为了确保时钟长期的准确性,晶振必须正常工作,不能够受到干扰。RTC的晶振又分为:外部晶振和内置晶振。
P5 X' x% K C" R
) o7 i# ^: {5 q4 G# X7 m) w8 h' s RTC时间信息存储在后备寄存器(RTC_BKUP)中,在STM32中,通常采用一个32位计数器来计时,而不是用年月日时分秒的分组寄存器,因此在处理STM32的时间信息时(设置或读取),通常要求先处理时分秒时间,再处理年月日时间,以确保时间和日期值之间的一致性。
: L9 J! c5 ?, j2 O3 m
4 x& s) a2 F4 a RTC和后备寄存器一般不会被系统或电源复原源复位,当从待机模式唤醒时,也不会复位,但是要注意的是,停止供电会出现重置,数值恢复到初始设置,因此如果要保持长期有效,尤其是断电重启依然有效,需要配置独立供电电源。
6 [; s' @! n. G
+ Y* j' m) E. o7 }5 _ RTC时间是以振荡频率来计算的。故它本质上是一个计数器,只是通过换算呈现出日历时间信息。而一般的计数器都是16位的。又因为时间的准确性很重要,故震荡次数越低,时间的准确性越低。所以必定是个高次数。2^15 = 32768 ,而 32768 Hz = 2^15 即分频15次后为1Hz,周期 = 1s,因此RTC的时间基准是1秒。16位计数器最大值0XFFFFFFFF,按秒计算,能支持136年时长计算。* o; N# Q( j; s. D
2 t! N# U* G, S5 I4 [2 _6 _
- m4 t/ S$ s! Y; O3 f二、工程创建及配置! J# a- v& {2 p7 }. D" Y- P3 V
本文采用STM32L496VGTx-ali开发板来实现,该开发板是没有给RTC独立供电的,因此断电后会出现时间重置,但功能模拟演示还是OK的。本文假设已经移植了前面工程,并实现了lpuart1串口通信与电脑端的调试输出以及按键、LED灯驱动# \( p) m: D( F8 U4 x. s$ {
. b0 P) R1 L: |% t% M) V
假设已经实现了lpuart1串口通信及按键、LED灯功能,现在双击.ioc打开cubeMX配置界面,开启RTC功能。数据格式支持二进制格式和BCD格式,在HAL中采用RTC_FORMAT_BIN(0x00000000u)和RTC_FORMAT_BCD(0x00000001u)标识。) t* F. S0 w3 I6 e* J9 i
2 f9 t% W2 `1 q9 ]* `1 i; {. _1 ?
2 R6 n+ s- X: W/ f* \* b: U2 x6 z# l" |3 q( s" q" H
BCD(Binary Coded Decimal),即二-十进制编码,是用四位二进制码的 10 种组合表示十进制数0-9。这种编码至少需要用四位二进制码元,而四位二进制码元可以有16种组合。当用这些组合表示十进制数0-9时,有六种组合不用。0 z5 Y6 H9 q3 p/ H' c o
4 ]8 Y3 v* D0 U& ]2 Y
RTC功能开启后,时钟树视图可以关于RTC的选择器可以选择外部高速时钟、外部低速时钟、内部低速时钟,本文选择外部低速时钟(LSE)。" Q7 \% W# j1 m: z& g9 x& t
( Q0 U) n5 d. j9 H
5 p' b/ o# D) G# s; o8 L* ?9 k6 E% C! O) r* @
完成配置保存输出按钮(本文是为每个外设生成独立.h/.c源码),在生成的rtc.c文件中可以看到RTC初始化函数关于年月日、时分秒的设值和cubeMX上配置信息一致。9 v$ p$ `" U0 g* F) o2 p$ @
" q8 m, n1 P. s, F9 M0 \
备注:如果在cubeMX配置界面没有选择数据格式,年日、时分秒默认是0X前缀的。
3 G7 _0 S0 M; r$ r: p( M* k: G2 Q1 L
( J8 Z2 ^1 N' b* p+ ^+ b1 R* m6 `2 m
F/ V' O0 B3 J: w w 三、驱动代码设计实现' }5 b7 q4 @# N; q: b; ^; m" N
【1】在Core/Src/rtc.c文件中,调整RTC初始化函数,在RTC设置初始化时间、日期之前,加入后备寄存器的读取,判定是否已经进行过初始化,避免重复设置时间、日期而改写本该持续计数的RTC计数。
2 k/ P6 S" R) z# G3 z4 Y: S- void MX_RTC_Init(void)
+ n9 P5 a1 P& B# l8 U& ? - {
1 `4 w, {& b$ u0 h) P4 V8 y \ -
, }; Y6 |7 M' f5 b9 m5 }$ F6 B - /* USER CODE BEGIN RTC_Init 0 */
( G& ]4 X6 G6 z A - % q7 I! E, Q% W! {
- /* USER CODE END RTC_Init 0 */! G2 B9 N- |: t* }" D. Q
-
$ F( T4 Q1 X1 } m* i! } - RTC_TimeTypeDef sTime = {0};
; G% G! p r, {( u( d - RTC_DateTypeDef sDate = {0};
! `6 K; r/ n, [) y% i3 @0 n - " ^) } T9 R- D/ I
- /* USER CODE BEGIN RTC_Init 1 */
- p% i+ j+ G+ p$ m5 q& h - //新增, p- T! x. U/ j3 c1 q" n
- __HAL_RCC_PWR_CLK_ENABLE();//使能电源时钟PWR& Q" n+ l' |4 f5 D, S
- HAL_PWR_EnableBkUpAccess();//取消备份区域写保护
J4 F- h9 L* t4 H7 e d0 ~ - /* USER CODE END RTC_Init 1 */
9 a! }9 }! r& N - S; {8 o/ x# b4 h6 q& O
- /** Initialize RTC Only
1 V, J, w8 m2 M' ~ - */
6 S" A* m& C6 N, d. p( m* a - hrtc.Instance = RTC;3 r6 y5 ^+ S& N. X
- hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
) G+ X/ l, I2 i; P - hrtc.Init.AsynchPrediv = 127;% Z9 Y$ N# T' @2 l! C
- hrtc.Init.SynchPrediv = 255;
6 ^1 c0 g* o1 n! w* i - hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
4 W' p3 n Z9 q' R$ {6 |' A - hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;% ~2 I1 p% W$ r8 U9 U% O
- hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
! A8 {2 z6 n* Q1 | - hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
7 m1 a4 X% ~5 I2 n - if (HAL_RTC_Init(&hrtc) != HAL_OK)
7 N- L3 D& }& ]( L; W- D - {. ]. {3 ^' ]2 O+ O
- Error_Handler();9 S: ~; O F+ m- e' d" Q9 t0 Q
- }, [7 x: n+ c. r- t' e' K a2 B
- * o6 T: B* X! _( ~- Z
- /* USER CODE BEGIN Check_RTC_BKUP */: i; C) g- Y- q, L, @
- //新增) p- U2 ?9 [# y u+ z
- if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0X8888)//判断是否首次上电* M$ w4 ?( r- N3 \
- {
" I, j- V2 G* g) }/ w - HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0X8888); //标记数值(写入上电检查数值)5 w6 w8 {: D: v; W ]
- /* USER CODE END Check_RTC_BKUP */
6 ~& H" _: x2 z# ` -
4 A) e+ H& R3 s, W8 L# u; k+ e - /** Initialize RTC and set the Time and Date
+ N1 _# u4 q2 I& ` - *// M8 y; W' b/ K+ `+ V
- sTime.Hours = 12;1 C! z9 p" F5 u. U/ u* n& I
- sTime.Minutes = 30;9 |. B, s0 x. P. L( {+ [
- sTime.Seconds = 30;
0 i2 `& D S' b @! V- e/ }% j - sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;+ A& o, U. ]+ Y' a. w$ T% `8 i
- sTime.StoreOperation = RTC_STOREOPERATION_RESET;
2 g5 o" ]3 @* k/ U; s - if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)- T% U# W9 }7 X, f, a7 L- a
- {
6 ^& h" A9 l9 f" @' V" |: d" r - Error_Handler();
% d/ g+ N5 B; C4 t/ Y1 f7 M$ @* u4 A9 F - }2 H+ F7 V% { H* G: \
- sDate.WeekDay = RTC_WEEKDAY_TUESDAY;
3 q# z6 M9 I2 z* u5 W, ^7 ` - sDate.Month = RTC_MONTH_DECEMBER;$ y! k) P) o) Y) a `* e/ s
- sDate.Date = 20;
# b% c# _5 t; { - sDate.Year = 22;+ e. O9 Z" V3 s
- & h7 ^4 j9 f! Y0 u- f! w
- if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
$ [6 ]( g6 A3 R5 r9 Y4 Z; N - {
0 i" o2 g8 ~# ], x - Error_Handler();; ^6 G6 Q( c1 k* X
- }
* Q0 [! m# { h/ y - /* USER CODE BEGIN RTC_Init 2 */, o. L- t7 U) c( A/ H' A
- //新增% u) s$ X7 c! i" j! w2 g8 c$ S% }
- // printf("手动初始化测试!\r\n");//显示初始化信息
! Z8 X: ~" b) N- B: t - }- {. a. t5 ^* f# r4 r
- /* USER CODE END RTC_Init 2 */3 u2 K; N, X1 J) Y
-
) X9 B- \7 J. B - }
复制代码 , G1 ?6 m9 P$ i! ~5 X" a9 m
在main.c文件中,加入按键、LED灯、串口外设驱动头文件,
7 b, f; y8 ^' M3 L0 [! L/ Q- /* USER CODE BEGIN Includes */
& {* l) W2 t1 C& _7 q - #include "../../ICore/key/key.h"6 e# q7 u& H `- w3 e
- #include "../../ICore/led/led.h"! |, f% B3 k! H
- #include "../../ICore/print/print.h"
4 ~/ {$ H% }0 ]. N6 V: U/ j - #include "../../ICore/usart/usart.h"
* u( |# D6 @/ E& T9 s x' T# ~ - /* USER CODE END Includes */
复制代码 ! J- X+ j1 H o9 S$ i" D p% [
在main函数中声明RTC日期、时间读取缓存变量。, v p! b: |) L0 |' X
- /* USER CODE BEGIN 1 */
, |7 Z/ V0 a7 t \% e6 i - RTC_DateTypeDef RtcDate;# s% ]9 ^4 m5 R* Z. ?
- RTC_TimeTypeDef RtcTime;# K3 y3 E/ b. {& V& K' {0 p' K
- uint8_t time_set = 0;//用户设置时间标记/ z; f1 m9 v8 }3 t/ t
- /* USER CODE END 1 */
复制代码
9 j! k- F) A+ g( a9 `0 [ 在main函数中开启串口lpuart1的中断接收。
6 I9 n' |- C+ x8 D, M8 \! _- /* USER CODE BEGIN 2 */
! P$ e( q* w& g) {% O4 c - ResetPrintInit(&hlpuart1);0 X! e# D5 s4 E6 y
- HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断$ I+ c3 F6 h9 p6 k" M2 B; Z
- HLPUSART_RX_STA = 0;; K8 z' @, z6 j) i A2 t3 p
- printf("APP reset now!\r\n");
( C, s' \. b; ^6 ?& ]3 ~ - /* USER CODE END 2 */
复制代码 : a1 z( O5 E9 l
在main函数循环体中,实现按键KEY1获取RTC时间以及按键KEY2设置RCT时间,注意RTC设置时间、日期和RTC读取时间、日期时,数据格式保持一致,最好和CubeMX设置时一致,例如读取、写入RTC时间日期都采用RTC_FORMAT_BCD格式。$ C* F) o, o) E# Z' q
- /* USER CODE BEGIN WHILE */3 ?/ W& y7 K# |$ F+ h- K
- while (1)( u2 a% w! Q e* E3 ^0 H
- {
3 f* g1 m3 @; ^/ q! w3 Z. q8 W - if(HLPUSART_RX_STA&0xC000){//溢出或换行,重新开始
$ ?, K, R. w0 C; l, C* w5 ^8 o - printf("lpuart1:%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);- K$ z0 h( A, @& `& y/ ?- g) C
- if(time_set){
. U" f" E0 I, l& f; s3 I9 b" j, u - //将超级终端发过来的数据换算并写入RTC,读取数据为字符内容需要转换为数值4 y) @& J" N( P) L1 Z( r
- RtcDate.Year = (HLPUSART_RX_BUF[2]-0x30)*10+HLPUSART_RX_BUF[3]-0x30;//减0x30后才能得到十进制0~9的数据4 v7 H5 q) A1 w9 D$ q
- RtcDate.Month = (HLPUSART_RX_BUF[4]-0x30)*10+HLPUSART_RX_BUF[5]-0x30;+ B( M- T+ `/ D4 h8 R
- RtcDate.Date = (HLPUSART_RX_BUF[6]-0x30)*10+HLPUSART_RX_BUF[7]-0x30;
5 u) g1 z3 E, n& D" |9 b - RtcTime.Hours = (HLPUSART_RX_BUF[8]-0x30)*10+HLPUSART_RX_BUF[9]-0x30;$ C* T) g3 `- h6 d' Q) Q K; @6 J% V; r
- RtcTime.Minutes = (HLPUSART_RX_BUF[10]-0x30)*10+HLPUSART_RX_BUF[11]-0x30;
$ {2 J/ n S1 {: v) C1 g8 u - RtcTime.Seconds = (HLPUSART_RX_BUF[12]-0x30)*10+HLPUSART_RX_BUF[13]-0x30;
+ N+ ]: h* I9 i1 [1 k) y - //写入格式RTC_FORMAT_BCD或RTC_FORMAT_BIN,确保写入及读取数据格式一致即可5 l) z1 I8 W9 r. N/ W' a
- if (HAL_RTC_SetTime(&hrtc, &RtcTime, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序
: v! K: y# k# w4 v9 ]0 S$ L* s - {
1 q- G! T' i7 u$ X& F5 F& I: R! T - printf("写入时间失败!\r\n"); //显示写入失败
. c2 `- v4 n+ O/ \# v( }! [' A - }else if (HAL_RTC_SetDate(&hrtc, &RtcDate, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序5 w* t2 `' d7 z& J5 X1 A' ?2 W
- {
" ?9 d$ W2 \9 k8 f6 v G" }: f0 { - printf("写入日期失败!\r\n"); //显示写入失败0 S2 q+ i+ M* x! u, w( o& y
- }else printf("写入成功!\r\n");//显示写入成功
: w& \+ ]- I9 _ - time_set=0;
; y' E+ ^9 ^# _ - }
6 i1 p, W0 F4 K9 ~1 b& r# V+ y - HLPUSART_RX_STA=0;//接收错误,重新开始- d: F9 |" i6 `3 m
- HAL_Delay(100);//等待& {! H( N# _# O& A8 D- h& n
- }( D0 I0 E: K/ D3 @$ E
- if(KEY_1()) R. W7 M/ f2 Y1 O
- {, I* a1 |1 f, I6 t3 F" M6 X
- HAL_RTC_GetTime(&hrtc, &RtcTime, RTC_FORMAT_BCD);//读出时间值3 e2 p- U u% V ~6 _& o# R
- HAL_RTC_GetDate(&hrtc, &RtcDate, RTC_FORMAT_BCD);//一定要先读时间后读日期
+ b) M9 Q2 {+ ?9 ~; i2 X - printf(" RTC实时时钟测试\r\n");
8 J3 m6 {+ e, M# _ - printf(" 实时时间:%04d-%02d-%02d %02d:%02d:%02d \r\n",2000+RtcDate.Year,
% g! ^" d, B) O' b) ] - RtcDate.Month, RtcDate.Date,RtcTime.Hours, RtcTime.Minutes, RtcTime.Seconds);//显示日期时间- I& l" }3 j2 D8 S U
- }$ _& N6 Q$ ?) r& S
- if(KEY_2())
) C. L$ D9 G! a* s - {
/ j% K. C7 R) C( A( z _' T - printf(" 请输入设置时间,格式20221220183030,按回车键确定! \r\n");
9 [+ p, K; ^0 `! r- m - time_set=1;' t' `7 |" J! |% P9 D+ A
- }
4 R+ [, m3 t3 _% P( J! P! k0 K - /* USER CODE END WHILE */
复制代码
C( q, O. z- [1 t% G 注意和前面简介说过,确保时间和日期值之间的一致性,先处理时间(时分秒),在处理日期(年月日),在stm32l4xx_hal_rtc.c的HAL_RTC_GetTime函数注释中也有明确要求如下,“您必须在HAL_RTC_GetTime()之后调用HAL_RTC_GetDate()来解锁值以确保时间和日期值之间的一致性。读取RTC当前时间锁定日历阴影寄存器中的值,直到读取当前日期以确保时间和日期值之间的一致性。”:: F" q. W/ h. i- @& y8 ?9 I# z
8 H' p# z/ j! T) S# m+ w/ g
W# Y; U% ?1 {! c
) x* V, x) e/ X
# D. B' ?2 r" g/ _# s/ I* J四、编译及测试 W0 v& g+ _8 c: j& |& h
和本专栏其他博文一样,设置好工程输出目标格式支持,配置好运行调试支持后,点击编译工程及运行按钮,完成工程编辑及下载。 ; L. i" A* y9 Z) ?% A
& O; X# u. |7 R4 {% L1 W6 ~. k, j
6 [' l S$ ~2 C7 Z: ]8 V0 P; ?2 x$ v; V [9 F& e
3 }" X# f3 w' G4 D
打开串口助手工具,连接到开发板,测试
4 X+ H& O9 H& |+ J' _ x, H% l, p& f, R, \" @8 ^
【1】按键KEY1,读取RTC时间,读取出的时间就去前面在cubeMX 设置的时间,并开始计数,因此分秒有差异。3 o9 [3 \% r5 a/ G% c. w
9 H; A" m* D* h4 {/ W2 N' f E
8 E* V5 @0 R* E6 C
: N, L2 c( l9 I5 a3 g: W4 V 【2】按键KEY2设置时间,并按格式输入时间,然后在按键KEY1读取新的RTC时间,如下图所示,设置时间生效。# d0 _0 a$ G1 }& k2 P
' _8 j/ Z3 B0 x- L; f' w. v. r
/ O9 T+ f$ R9 o% L- ^9 _* e3 l4 M7 h1 Q1 V( A
【3】按开发板上的Reset按键,系统复位,再按键KEY2读取RTC时间,时间依然正常计数
7 ]! o4 ^6 G2 Y- e$ C/ s
5 T6 D( b7 m2 _+ I& X6 J) W
" p7 L3 `. v( x% n v1 E8 u8 A6 G2 M6 @/ K R
. y+ F& j7 y# U4 L2 S
【4】断电一会,然后重新上电,再按键KEY2读取RTC时间,时间恢复到CubeMX设置的初始时间(即为断电后,原来写入的标识无法保持,HAL_RTCEx_BKUPRead读取标记失败重新初始化)。
* R/ r, f9 g' S. f! c5 v# C
6 p( d8 B' g" V; n' |: M* t
2 n- U3 p8 A- w. V! N8 k0 i6 C, J+ T2 r" ]
————————————————
( H: u9 G7 ~) I$ H7 Z版权声明:py_free-物联智能如有侵权请联系删除
! I" }$ S6 e, {( A7 G) W- U5 {1 G$ j( V; V" ?# \, I6 M6 s
2 w; u! {3 V$ w1 G9 B! U7 E3 T
. J% ]. r. |! Y! s6 l: J6 w$ U# f |
瞎J13乱发,傻福玩意
F1系列的RTC是计时器,你帖子里用的不是STM32L496VGT吗?看过这个芯片的数据手册吗?MD,抄都TM抄不明白,下边这张图看得懂吗?傻福