一、RTC简介
! @2 a. q: \' ]5 T 实时时钟的缩写是RTC(Real_Time Clock),核心是晶振,晶振频率一般为32768 Hz 。它为分频计数器提供精确的与低功耗的实基信号。它可以用于产生秒、分、时、日等信息。为了确保时钟长期的准确性,晶振必须正常工作,不能够受到干扰。RTC的晶振又分为:外部晶振和内置晶振。
* Z7 ]) O8 B8 e" S5 d% V- u
% |! ]( M# c s RTC时间信息存储在后备寄存器(RTC_BKUP)中,在STM32中,通常采用一个32位计数器来计时,而不是用年月日时分秒的分组寄存器,因此在处理STM32的时间信息时(设置或读取),通常要求先处理时分秒时间,再处理年月日时间,以确保时间和日期值之间的一致性。2 Q+ ~2 K' e8 o) V M7 b8 F
% C7 e: `, {( B* `0 D1 J2 |1 H6 H, t RTC和后备寄存器一般不会被系统或电源复原源复位,当从待机模式唤醒时,也不会复位,但是要注意的是,停止供电会出现重置,数值恢复到初始设置,因此如果要保持长期有效,尤其是断电重启依然有效,需要配置独立供电电源。' H6 q' V1 J) _7 ?
5 T; V0 [1 j: V, C
RTC时间是以振荡频率来计算的。故它本质上是一个计数器,只是通过换算呈现出日历时间信息。而一般的计数器都是16位的。又因为时间的准确性很重要,故震荡次数越低,时间的准确性越低。所以必定是个高次数。2^15 = 32768 ,而 32768 Hz = 2^15 即分频15次后为1Hz,周期 = 1s,因此RTC的时间基准是1秒。16位计数器最大值0XFFFFFFFF,按秒计算,能支持136年时长计算。
3 s- F% a/ C% ^
2 D9 o" R! ?* N* ^6 L9 |
v s* e, S9 |- A, W( D二、工程创建及配置
" D. j# G' L/ W- n" Q 本文采用STM32L496VGTx-ali开发板来实现,该开发板是没有给RTC独立供电的,因此断电后会出现时间重置,但功能模拟演示还是OK的。本文假设已经移植了前面工程,并实现了lpuart1串口通信与电脑端的调试输出以及按键、LED灯驱动) H* T2 f M; d; d# U
s/ {0 f* c' M: T1 O 假设已经实现了lpuart1串口通信及按键、LED灯功能,现在双击.ioc打开cubeMX配置界面,开启RTC功能。数据格式支持二进制格式和BCD格式,在HAL中采用RTC_FORMAT_BIN(0x00000000u)和RTC_FORMAT_BCD(0x00000001u)标识。0 R3 H5 G2 [" L4 [$ B4 Z! n
7 A: \4 t* v: V0 E
% X) L+ i+ O, j5 g9 }& j) }' Z5 V% Q9 ^& w0 m9 L9 h7 I
BCD(Binary Coded Decimal),即二-十进制编码,是用四位二进制码的 10 种组合表示十进制数0-9。这种编码至少需要用四位二进制码元,而四位二进制码元可以有16种组合。当用这些组合表示十进制数0-9时,有六种组合不用。
; q; x; Z" h& s1 e; b6 l7 d( S
2 T& O' Z2 O: m6 M8 Q. I RTC功能开启后,时钟树视图可以关于RTC的选择器可以选择外部高速时钟、外部低速时钟、内部低速时钟,本文选择外部低速时钟(LSE)。
8 j' B# g, R$ v( N* K- D K n4 N4 e6 N! ^9 U. O* Y) G
4 b4 l( c k6 w7 l1 `2 E
! g0 W- _$ I, o& u3 Z) q 完成配置保存输出按钮(本文是为每个外设生成独立.h/.c源码),在生成的rtc.c文件中可以看到RTC初始化函数关于年月日、时分秒的设值和cubeMX上配置信息一致。
5 O/ A4 H6 j( S. o% H# n2 g+ u% V
备注:如果在cubeMX配置界面没有选择数据格式,年日、时分秒默认是0X前缀的。
% j6 U- Z1 q+ H, k4 ^, f5 M6 Z
6 X4 p! J F# v( [5 F2 k! z
! a6 P+ ]1 N1 F' _; M& `
5 m, n7 \. G$ h 三、驱动代码设计实现8 J2 }# Q$ e1 |( V: y- c
【1】在Core/Src/rtc.c文件中,调整RTC初始化函数,在RTC设置初始化时间、日期之前,加入后备寄存器的读取,判定是否已经进行过初始化,避免重复设置时间、日期而改写本该持续计数的RTC计数。
, O3 u6 m1 ~! A' d. x- void MX_RTC_Init(void)
U, d1 O2 t6 e6 g* y; ^$ l. R - {- q% o* v2 S( I- t0 o: c
-
3 [& S2 n; g/ A* G - /* USER CODE BEGIN RTC_Init 0 */
# e, X H8 j, x5 ^# j -
" L* l2 U; W. [! _ - /* USER CODE END RTC_Init 0 */# T) Y# e) t% A3 @7 Y
- 0 X U( _% q& l6 t5 W8 Z; N! f
- RTC_TimeTypeDef sTime = {0};
2 Q; V$ c, d3 @; Y% ~$ F - RTC_DateTypeDef sDate = {0};0 c# o' h; M- R' R
- 3 q$ M4 `' F) g% M0 x& E1 Z8 x
- /* USER CODE BEGIN RTC_Init 1 */
: e- S' T2 ^, R, S( M2 j- Q - //新增
% A6 x# q( U, g; C3 M - __HAL_RCC_PWR_CLK_ENABLE();//使能电源时钟PWR# e* U& Z$ r; i" v. K: u
- HAL_PWR_EnableBkUpAccess();//取消备份区域写保护7 ^1 `2 ` ]. x/ O
- /* USER CODE END RTC_Init 1 */
: I' b! q+ E2 S% B& h) K - 7 }& q( W G7 \/ K. J$ d% s
- /** Initialize RTC Only8 j! g: f1 l6 u% c7 h
- */
) I1 E/ A0 I V - hrtc.Instance = RTC;
# @) T+ e1 q: X3 D ^( a - hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
" K1 D6 N* {7 k3 n% l1 B# `, t - hrtc.Init.AsynchPrediv = 127;
! D. \+ u" L' J! L9 b. O9 q) ] - hrtc.Init.SynchPrediv = 255;" H" `8 Q- j4 ^
- hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;1 o; v$ q, ?! K5 M8 C& G
- hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;8 A6 u- [+ A5 G: ~6 l
- hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
. _- F1 q! u g2 B8 M/ R0 Z' t - hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
- X7 Q6 X" [$ m0 Z* k, J# t - if (HAL_RTC_Init(&hrtc) != HAL_OK)6 h' H& ]5 r3 ^; j* D" S; D% b
- {$ D$ B! a7 b7 O9 ?* d+ ]1 R. u
- Error_Handler();
7 B: p) n! O) J, l' {9 E; T5 h - }
# }# i( Y3 T' T- T* Z$ V: @* q -
: a' B' L0 W& @: \, b - /* USER CODE BEGIN Check_RTC_BKUP */
5 b% t3 z5 m) i% p: ^9 f T - //新增
d9 N0 p1 F+ Y1 A - if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0X8888)//判断是否首次上电+ n8 C0 q: T8 J" S+ c: b
- {9 b9 i& ?2 @; m' c9 ~2 e
- HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0X8888); //标记数值(写入上电检查数值)
0 h0 e; b" N0 t# `0 G5 g - /* USER CODE END Check_RTC_BKUP */
$ X; t% S" _4 K4 } -
) }2 E% J+ L8 V5 I0 E4 Q o5 b - /** Initialize RTC and set the Time and Date9 y3 r: C6 ~ ]$ E. ~
- */
2 i5 m2 j, ]: W - sTime.Hours = 12;
1 H, `$ T* H# I5 A - sTime.Minutes = 30;7 o& [ ]5 C8 Z- O1 j
- sTime.Seconds = 30;
$ f; e4 d; G6 W' r. N4 k) i - sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
1 v0 Z9 u4 y0 r" B9 p# c$ j! E# I5 I - sTime.StoreOperation = RTC_STOREOPERATION_RESET;
* B, l; z7 ^$ e - if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)5 _! m1 d7 |% |* E; I
- {" c' m" @! Y% t* s5 K
- Error_Handler();! }1 T3 p. s! h6 J# e+ c+ |0 K
- }0 ?4 C2 ^- T, Y7 A {* E: K
- sDate.WeekDay = RTC_WEEKDAY_TUESDAY;
: g2 x: @# c( L - sDate.Month = RTC_MONTH_DECEMBER;9 W: @. p8 q2 z
- sDate.Date = 20;
& @/ D" f* ]' g$ C5 Q! n - sDate.Year = 22;
! b& y) }: y- \ - 2 D5 |% p) u+ s. f$ d
- if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
2 h4 s; L; d5 F0 Q. G8 l* M7 n - {
; h) W4 D7 \/ C: [ i" G f: b - Error_Handler();
0 e6 C: a: m( `0 z/ ^ - }. p' b3 u. l' v
- /* USER CODE BEGIN RTC_Init 2 */
3 u6 k1 x% \& A! Q) G1 i - //新增8 N, E, I& R" l8 v# ~
- // printf("手动初始化测试!\r\n");//显示初始化信息
/ e$ R! w( H x- L$ y - }1 ]0 H! o2 X7 O" L0 C# M7 I1 J
- /* USER CODE END RTC_Init 2 */4 a3 Y7 ~( o% f8 ^
- 6 u' {; Z* _# t2 z/ K6 L& v) P l
- }
复制代码 + v, w) T# C, I
在main.c文件中,加入按键、LED灯、串口外设驱动头文件,
2 N5 _$ x+ ~( a- d6 e$ x6 K: R- /* USER CODE BEGIN Includes */& [; F6 h4 `* x3 z
- #include "../../ICore/key/key.h"
3 ~+ w2 a2 i; J6 n- [ - #include "../../ICore/led/led.h"
& X2 R9 m! B+ i3 F* l# w+ Y( f - #include "../../ICore/print/print.h"' o/ t, {9 o+ f" X/ H
- #include "../../ICore/usart/usart.h"
, S. \! [) l% a - /* USER CODE END Includes */
复制代码
! M3 d* y9 E4 {8 x0 w, f& f, F7 h 在main函数中声明RTC日期、时间读取缓存变量。
- c. ?9 k+ W- j# l( s- w; J$ v D- /* USER CODE BEGIN 1 */! U7 ^5 r: v# O3 m. U
- RTC_DateTypeDef RtcDate;
0 S! y" J1 w) r& u+ n5 J' F - RTC_TimeTypeDef RtcTime;
# ?5 y$ y# D: p6 }$ Y4 P - uint8_t time_set = 0;//用户设置时间标记
* {5 Q! X% ^( D$ G. y - /* USER CODE END 1 */
复制代码 " u$ X5 s! F4 z" Y' {
在main函数中开启串口lpuart1的中断接收。( ^$ `% X; i' _: s' f; F5 ?. u
- /* USER CODE BEGIN 2 */: l! g+ K3 Z* @$ a6 ^
- ResetPrintInit(&hlpuart1);' z' y3 p; T2 q) h4 T
- HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断
1 q2 G; T w( z6 p+ `& t7 s - HLPUSART_RX_STA = 0;
/ ^: J; X" k0 V. m5 ^) |1 Q$ g - printf("APP reset now!\r\n");4 k( h- G3 g$ V8 W1 S; X
- /* USER CODE END 2 */
复制代码 ) {0 e1 |4 W4 l" Q# U! U5 F; \" f M( Z
在main函数循环体中,实现按键KEY1获取RTC时间以及按键KEY2设置RCT时间,注意RTC设置时间、日期和RTC读取时间、日期时,数据格式保持一致,最好和CubeMX设置时一致,例如读取、写入RTC时间日期都采用RTC_FORMAT_BCD格式。6 N0 ~' A+ m; a; W& g
- /* USER CODE BEGIN WHILE */
* Q" L# A f: k/ Q9 Y% w+ n% { \ - while (1)
$ ~) a9 B- M% ^9 g# _# t& s0 ` - {- ^- K8 d; d4 `
- if(HLPUSART_RX_STA&0xC000){//溢出或换行,重新开始) E8 j# c1 @+ @' U5 c" u
- printf("lpuart1:%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);( g ?6 e: [/ R3 P6 u+ S" E; G
- if(time_set){8 u8 h( ]/ M" P; ^1 j# Q6 }& I
- //将超级终端发过来的数据换算并写入RTC,读取数据为字符内容需要转换为数值
" l$ B3 ] H( \' {' h0 z* o8 ?9 s - RtcDate.Year = (HLPUSART_RX_BUF[2]-0x30)*10+HLPUSART_RX_BUF[3]-0x30;//减0x30后才能得到十进制0~9的数据
& L A' X G3 w* }( ~% D: s - RtcDate.Month = (HLPUSART_RX_BUF[4]-0x30)*10+HLPUSART_RX_BUF[5]-0x30;
% d) x. Q% u M. o o1 t - RtcDate.Date = (HLPUSART_RX_BUF[6]-0x30)*10+HLPUSART_RX_BUF[7]-0x30;
: h" `9 f+ _( I2 t6 B6 k" ~ - RtcTime.Hours = (HLPUSART_RX_BUF[8]-0x30)*10+HLPUSART_RX_BUF[9]-0x30;
, v3 y) J8 t" o - RtcTime.Minutes = (HLPUSART_RX_BUF[10]-0x30)*10+HLPUSART_RX_BUF[11]-0x30;
" v; l# J0 C0 e - RtcTime.Seconds = (HLPUSART_RX_BUF[12]-0x30)*10+HLPUSART_RX_BUF[13]-0x30;
2 N+ K# D, B$ n6 o+ | - //写入格式RTC_FORMAT_BCD或RTC_FORMAT_BIN,确保写入及读取数据格式一致即可* k% r5 H( P8 n n4 F; N3 I9 {% G
- if (HAL_RTC_SetTime(&hrtc, &RtcTime, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序
, e8 I8 f [5 x+ i - {/ m# a: f) `7 U z
- printf("写入时间失败!\r\n"); //显示写入失败
m! \1 L% w! I; t - }else if (HAL_RTC_SetDate(&hrtc, &RtcDate, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序2 N7 X9 |9 s: o$ V& E) M
- {" K3 u/ K! h$ c7 k, v6 b0 C
- printf("写入日期失败!\r\n"); //显示写入失败' P+ z3 V, n; F0 m* r+ c! ]
- }else printf("写入成功!\r\n");//显示写入成功% K3 Z* }1 L, l$ s% q
- time_set=0;- Z \+ K4 D/ S+ W! F
- }
2 ?$ z* B# g6 O& P& ^" @ - HLPUSART_RX_STA=0;//接收错误,重新开始' e3 }+ k5 c( s) d( P& G7 O! m) X5 k, ?
- HAL_Delay(100);//等待
+ s) M: X2 ]2 y( |( V - }
& v' |4 s& s1 w# M: N. K. s - if(KEY_1()), L8 B+ c; u3 Q& _
- {4 s8 x9 b' q$ W8 s& c5 `4 ^' n( [
- HAL_RTC_GetTime(&hrtc, &RtcTime, RTC_FORMAT_BCD);//读出时间值! c% t. r3 u) A( H/ _" `
- HAL_RTC_GetDate(&hrtc, &RtcDate, RTC_FORMAT_BCD);//一定要先读时间后读日期9 V% J! Y3 @& Q
- printf(" RTC实时时钟测试\r\n");8 p) o& O! Z' \" I
- printf(" 实时时间:%04d-%02d-%02d %02d:%02d:%02d \r\n",2000+RtcDate.Year,
) h# E1 G3 m! @5 K - RtcDate.Month, RtcDate.Date,RtcTime.Hours, RtcTime.Minutes, RtcTime.Seconds);//显示日期时间
4 m, n# V( c) H - }7 X" b2 \; w" @4 z
- if(KEY_2()) j: U2 L" x: |, p
- {( M7 O; I+ D+ P4 n' Q8 F% U
- printf(" 请输入设置时间,格式20221220183030,按回车键确定! \r\n");5 N. D8 `2 ~' q
- time_set=1;& h+ z/ `- o& d" U% b/ C: C
- }5 ]! l, o7 C ?% r1 i. b/ n2 a
- /* USER CODE END WHILE */
复制代码
& `3 ]4 u+ n0 E 注意和前面简介说过,确保时间和日期值之间的一致性,先处理时间(时分秒),在处理日期(年月日),在stm32l4xx_hal_rtc.c的HAL_RTC_GetTime函数注释中也有明确要求如下,“您必须在HAL_RTC_GetTime()之后调用HAL_RTC_GetDate()来解锁值以确保时间和日期值之间的一致性。读取RTC当前时间锁定日历阴影寄存器中的值,直到读取当前日期以确保时间和日期值之间的一致性。”:. e k$ W: {) h9 s, I2 O) v Y
1 `+ A$ ^; T% Q( M
% \% ~4 a% i* D: }
0 @+ U* |! r' x5 X
: n6 u# E7 A$ ^0 ^+ g, b, a! K0 k* O四、编译及测试
+ m) \0 E2 d; R0 y. V/ O6 B 和本专栏其他博文一样,设置好工程输出目标格式支持,配置好运行调试支持后,点击编译工程及运行按钮,完成工程编辑及下载。
# a) j$ [2 ^+ ?) Q) p! d: T5 C$ M$ w- G3 Z, q
; o! M: l+ d" d3 O* j& }& M1 y8 \. i0 ]' u6 \; ^+ k, A
1 H: x4 l8 q# z7 v8 \ 打开串口助手工具,连接到开发板,测试
) S) b) U: f- }
) R' l7 h& P0 o" D/ z& ] 【1】按键KEY1,读取RTC时间,读取出的时间就去前面在cubeMX 设置的时间,并开始计数,因此分秒有差异。
; O" i5 f( a( X4 I
- x" C. ]( \/ i& q, ^
- d f2 U( l0 Q- R- ~ A
( `- O' I* m3 k" b% C1 W
【2】按键KEY2设置时间,并按格式输入时间,然后在按键KEY1读取新的RTC时间,如下图所示,设置时间生效。
' p$ h( U# U# _% x& D8 R0 Q4 k9 L4 ^6 t& {/ D9 ?6 F
" ?0 h! e! Q8 T: h" j& p2 M+ d8 y
% H( r& ~- j# Z/ `+ p1 C: ?3 T+ k p 【3】按开发板上的Reset按键,系统复位,再按键KEY2读取RTC时间,时间依然正常计数
& W/ n9 t# g- D+ }2 @& Q# ^! b4 O9 m5 m; P1 m! J
9 B4 t/ F) E: {
( t# Y J* G8 L
$ r, T: K0 p2 X% ~6 F6 W3 U, y 【4】断电一会,然后重新上电,再按键KEY2读取RTC时间,时间恢复到CubeMX设置的初始时间(即为断电后,原来写入的标识无法保持,HAL_RTCEx_BKUPRead读取标记失败重新初始化)。
# |/ ~* V/ M `! G; V5 @. n1 {4 N7 `; ?! s$ e9 p. ]! G
) b% c# k- I: l7 x; f
* c; R" e9 A1 |5 }+ I. c———————————————— N, k' w+ h) n( `
版权声明:py_free-物联智能如有侵权请联系删除
" U/ v0 P3 w: e6 v2 E
! s; h' G7 d7 h W2 Z7 {( U3 K
" s- \% J5 a: y9 c
|
瞎J13乱发,傻福玩意
F1系列的RTC是计时器,你帖子里用的不是STM32L496VGT吗?看过这个芯片的数据手册吗?MD,抄都TM抄不明白,下边这张图看得懂吗?傻福