一、RTC简介( ~6 g* f8 y* G8 h3 x7 O% h4 I
实时时钟的缩写是RTC(Real_Time Clock),核心是晶振,晶振频率一般为32768 Hz 。它为分频计数器提供精确的与低功耗的实基信号。它可以用于产生秒、分、时、日等信息。为了确保时钟长期的准确性,晶振必须正常工作,不能够受到干扰。RTC的晶振又分为:外部晶振和内置晶振。& @6 W4 ]) @1 B: l# P. g0 f
* W, q* a2 R4 P; j: r6 e% f7 |- l E/ ?0 Q RTC时间信息存储在后备寄存器(RTC_BKUP)中,在STM32中,通常采用一个32位计数器来计时,而不是用年月日时分秒的分组寄存器,因此在处理STM32的时间信息时(设置或读取),通常要求先处理时分秒时间,再处理年月日时间,以确保时间和日期值之间的一致性。
$ w1 N# n5 R* X) }4 t5 X8 \% U, `' }: J
RTC和后备寄存器一般不会被系统或电源复原源复位,当从待机模式唤醒时,也不会复位,但是要注意的是,停止供电会出现重置,数值恢复到初始设置,因此如果要保持长期有效,尤其是断电重启依然有效,需要配置独立供电电源。
9 h. ?. h% m+ o- g+ S4 e) r/ U
# Y; ] y, B7 i: F/ Z/ G0 K RTC时间是以振荡频率来计算的。故它本质上是一个计数器,只是通过换算呈现出日历时间信息。而一般的计数器都是16位的。又因为时间的准确性很重要,故震荡次数越低,时间的准确性越低。所以必定是个高次数。2^15 = 32768 ,而 32768 Hz = 2^15 即分频15次后为1Hz,周期 = 1s,因此RTC的时间基准是1秒。16位计数器最大值0XFFFFFFFF,按秒计算,能支持136年时长计算。
6 f" a3 p' ]+ S( \" g; X2 N! s. i) C, Q5 G' q2 M
* L- k# c0 ?6 n二、工程创建及配置
: D4 f! B) F+ t% W# ?9 V 本文采用STM32L496VGTx-ali开发板来实现,该开发板是没有给RTC独立供电的,因此断电后会出现时间重置,但功能模拟演示还是OK的。本文假设已经移植了前面工程,并实现了lpuart1串口通信与电脑端的调试输出以及按键、LED灯驱动
, C5 x; t: W) i1 L8 Q% ^! E7 [! {* |# m5 t
假设已经实现了lpuart1串口通信及按键、LED灯功能,现在双击.ioc打开cubeMX配置界面,开启RTC功能。数据格式支持二进制格式和BCD格式,在HAL中采用RTC_FORMAT_BIN(0x00000000u)和RTC_FORMAT_BCD(0x00000001u)标识。* H e2 N& {+ G8 i+ y0 e! T
5 \% j' O) r. f, u7 |4 P. X( c3 _8 C
" a1 w2 _% q0 j) J) w+ R1 b
3 R, Q' R; H0 [' K+ K8 U5 s BCD(Binary Coded Decimal),即二-十进制编码,是用四位二进制码的 10 种组合表示十进制数0-9。这种编码至少需要用四位二进制码元,而四位二进制码元可以有16种组合。当用这些组合表示十进制数0-9时,有六种组合不用。
7 F' @# H2 X+ G' {2 ^: U4 m" K! x8 K
RTC功能开启后,时钟树视图可以关于RTC的选择器可以选择外部高速时钟、外部低速时钟、内部低速时钟,本文选择外部低速时钟(LSE)。
& R- W% O i5 d' O. G! l% o( Y6 ?7 P3 h$ H: d% C
]4 o8 Z/ S- u4 f# k, r
) b! A6 i' d1 ]: x0 s8 p& M, L& T
完成配置保存输出按钮(本文是为每个外设生成独立.h/.c源码),在生成的rtc.c文件中可以看到RTC初始化函数关于年月日、时分秒的设值和cubeMX上配置信息一致。
/ {7 s1 H0 v* l' N2 g4 I
' `/ m7 E9 P1 S% C' P7 S) D5 m 备注:如果在cubeMX配置界面没有选择数据格式,年日、时分秒默认是0X前缀的。+ m( {& t$ G* I) A% @! c
" e" j( D6 G' F# }3 e) ^0 b
1 q! z! u7 {' G# |6 R5 K
8 g- t" Q2 i; p0 R
三、驱动代码设计实现7 |& Y+ ]9 T0 l" y' R, ^8 O2 K
【1】在Core/Src/rtc.c文件中,调整RTC初始化函数,在RTC设置初始化时间、日期之前,加入后备寄存器的读取,判定是否已经进行过初始化,避免重复设置时间、日期而改写本该持续计数的RTC计数。1 k: r4 M& A+ C [8 {0 C4 x
- void MX_RTC_Init(void)7 m, \) F9 l. \8 Y7 \
- {; @6 o: C2 C7 C7 I/ \ ^9 k
-
3 y1 Z" P% j; g" G( ~3 U+ B - /* USER CODE BEGIN RTC_Init 0 */
, j& _& `6 c% m* t5 b! o0 E u3 b -
! A2 K0 X# Y+ a: @6 B, h0 Q- e - /* USER CODE END RTC_Init 0 */) N* ]# i2 Q& A7 Z& a$ @
-
, X/ n% ~9 m, ^/ N2 q - RTC_TimeTypeDef sTime = {0};* g0 [7 L5 N; m" G$ Z
- RTC_DateTypeDef sDate = {0};
1 O- x" O+ g, ^% M$ o# \0 T -
1 M! ]$ \+ m) V8 c - /* USER CODE BEGIN RTC_Init 1 */
" o" K a0 ^ W0 \5 \ - //新增0 P* U( m; H2 h+ d( H% Z
- __HAL_RCC_PWR_CLK_ENABLE();//使能电源时钟PWR
: `" }' Q. q/ ]: t# Y- f - HAL_PWR_EnableBkUpAccess();//取消备份区域写保护
& z- B! {! i: j5 u! A* c I - /* USER CODE END RTC_Init 1 */" ]1 T& Q* {5 _5 } @: H
-
3 F0 z6 \ I( S: K% f# ?5 q - /** Initialize RTC Only7 b0 a; G0 Z, F$ J
- */6 m: j# X3 a" F! }$ B/ X. s9 v! r
- hrtc.Instance = RTC;
& o, l$ J! T# q# H - hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
# `% d y7 R* U' O* G' I0 o - hrtc.Init.AsynchPrediv = 127;& ~/ T( V y( i" V2 m. @3 x$ _
- hrtc.Init.SynchPrediv = 255;- c) O! i- y& m9 ?
- hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
, h/ X5 _5 P' ?8 x/ h$ `( g2 o, \6 q - hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
$ X& f6 ]. x# ?% y8 m V- M - hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
. h! [0 o2 X, k% n - hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;: K- w9 u9 d, {' F( S' O- Q* J
- if (HAL_RTC_Init(&hrtc) != HAL_OK)- \' Y6 M, x! R7 B( x! j
- {
0 O7 J+ u# z9 J% h0 \8 R. w - Error_Handler();/ I! a1 C: W8 x$ R( a( U
- }2 _4 H4 w/ J" g
-
9 h9 g9 A8 F3 o- Y' |( V - /* USER CODE BEGIN Check_RTC_BKUP */; L( m( I1 J; D* H9 S6 p
- //新增
" z( b) D7 U/ j. d; L5 T- |4 U - if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0X8888)//判断是否首次上电
$ q E7 o: h0 \9 p* S; D6 D5 w: H - {
8 a& Z4 s: p* B9 A1 A0 G - HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0X8888); //标记数值(写入上电检查数值)
; a7 s% T( T" \7 H q- W* w, d( Z - /* USER CODE END Check_RTC_BKUP */
6 p3 S' P; N9 |, k7 K4 N3 B2 x - " i$ T5 q6 _% I" U7 W- I: `
- /** Initialize RTC and set the Time and Date5 R1 w4 \* \. q) Z9 K
- */2 M, m+ `: Q7 _. m
- sTime.Hours = 12;8 P: h" q' c; H8 f$ b1 R I% K% D
- sTime.Minutes = 30;5 a: Y) t# n" d
- sTime.Seconds = 30;) @& z' B( z7 x- r4 ]/ a- j
- sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;; P0 Q) O4 u( c# y" j
- sTime.StoreOperation = RTC_STOREOPERATION_RESET;
9 L1 U% q$ y/ @% p6 P - if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
9 I2 R/ Q# z9 V - {
' P9 D' K' n5 }4 s2 ] - Error_Handler();
' V* n& z: t# R! E6 V; q- k1 t - }
- D7 z' o! k- [ - sDate.WeekDay = RTC_WEEKDAY_TUESDAY;
- ]7 `. J, X7 o9 y - sDate.Month = RTC_MONTH_DECEMBER;
, [( Q$ S- J A6 t" L, } - sDate.Date = 20;
! r5 N b7 S5 f3 _ - sDate.Year = 22;6 c- k4 }4 }- ] n
- / j% i- D$ K1 T
- if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
1 h: M* N% g) U1 e8 T9 U! T - {
5 O! _0 S/ K- R* C - Error_Handler();
$ N2 j; V" o) h' l - }: A+ A) k: n/ O, F1 s' }7 r
- /* USER CODE BEGIN RTC_Init 2 */" p. p7 ]8 m; E+ D9 |5 Q
- //新增) Z) a* O( E5 `8 N# d- G" M
- // printf("手动初始化测试!\r\n");//显示初始化信息
% D ^- }+ W( b1 U" g5 Y! z; h - }# l: z1 A9 r" x% N u1 j7 a5 h3 e
- /* USER CODE END RTC_Init 2 */
! @1 `& \! C# g8 S - 0 T9 o" S0 S+ k1 K1 c8 r% f+ J
- }
复制代码
8 c& b W9 d$ I! @: R2 e) }) {9 o0 a 在main.c文件中,加入按键、LED灯、串口外设驱动头文件,
# c+ x% m: E# I: x+ Y5 Z9 f$ }- /* USER CODE BEGIN Includes */3 P5 t+ |/ o) ?. l: V; W/ i
- #include "../../ICore/key/key.h"0 V/ K! U/ p2 e+ J: {* r
- #include "../../ICore/led/led.h"2 R- p' k) H4 k% h1 f
- #include "../../ICore/print/print.h"
" ]* }- y$ Y" `9 y. q K2 L - #include "../../ICore/usart/usart.h"
8 O, x# k' }* _6 R8 r/ W - /* USER CODE END Includes */
复制代码
) y6 ~; p# A7 q 在main函数中声明RTC日期、时间读取缓存变量。# R- O3 R& T9 R4 H
- /* USER CODE BEGIN 1 */
7 i0 n5 q6 h% ~2 m# n N9 q7 U - RTC_DateTypeDef RtcDate;
( O Y) ?5 s0 G, h4 b1 t) u - RTC_TimeTypeDef RtcTime;
0 j; m1 i. n3 p - uint8_t time_set = 0;//用户设置时间标记
1 x- \' v- y6 D, n: F4 ? - /* USER CODE END 1 */
复制代码
: g2 C/ Q( J' L" Q 在main函数中开启串口lpuart1的中断接收。
6 c5 ^1 f: R# `# x- /* USER CODE BEGIN 2 */
d2 m7 c! i1 X: i% x - ResetPrintInit(&hlpuart1);
+ i5 h; y2 m5 N' P |. Y+ a - HAL_UART_Receive_IT(&hlpuart1,(uint8_t *)&HLPUSART_NewData, 1); //再开启接收中断1 @1 T8 r c6 P6 o. f6 o9 s4 e- n7 ^
- HLPUSART_RX_STA = 0;9 H6 `0 s6 W( E0 m
- printf("APP reset now!\r\n");
# K! W# g3 q/ X4 ~ - /* USER CODE END 2 */
复制代码
; u/ T* t* ^9 J$ u" u# i 在main函数循环体中,实现按键KEY1获取RTC时间以及按键KEY2设置RCT时间,注意RTC设置时间、日期和RTC读取时间、日期时,数据格式保持一致,最好和CubeMX设置时一致,例如读取、写入RTC时间日期都采用RTC_FORMAT_BCD格式。
- }9 Z% V6 N7 V/ i/ i& p& D; i- /* USER CODE BEGIN WHILE */
* Q! k+ S4 e% ?0 ^3 f- e - while (1) k4 h6 c3 Q8 H- C
- {" f* @8 q7 E0 [# \- }. J
- if(HLPUSART_RX_STA&0xC000){//溢出或换行,重新开始- o1 ]' B) D% i; }! w$ I' i
- printf("lpuart1:%.*s\r\n",HLPUSART_RX_STA&0X0FFF, HLPUSART_RX_BUF);
) z7 |. o3 t$ o- h) f G - if(time_set){5 u) P, l9 |0 I4 T D4 X5 i
- //将超级终端发过来的数据换算并写入RTC,读取数据为字符内容需要转换为数值
2 H& f& x/ [' X7 K' U7 @ @8 v - RtcDate.Year = (HLPUSART_RX_BUF[2]-0x30)*10+HLPUSART_RX_BUF[3]-0x30;//减0x30后才能得到十进制0~9的数据0 g! d; S v9 x9 F& Y# X! F
- RtcDate.Month = (HLPUSART_RX_BUF[4]-0x30)*10+HLPUSART_RX_BUF[5]-0x30;
1 j; C* |" \, I6 T7 V - RtcDate.Date = (HLPUSART_RX_BUF[6]-0x30)*10+HLPUSART_RX_BUF[7]-0x30;
/ o, Z) a3 s0 e+ l# I* N! X - RtcTime.Hours = (HLPUSART_RX_BUF[8]-0x30)*10+HLPUSART_RX_BUF[9]-0x30;7 `+ H% @& P1 _9 P# Y( o
- RtcTime.Minutes = (HLPUSART_RX_BUF[10]-0x30)*10+HLPUSART_RX_BUF[11]-0x30;
) {5 [- P& j* y% d+ x, A - RtcTime.Seconds = (HLPUSART_RX_BUF[12]-0x30)*10+HLPUSART_RX_BUF[13]-0x30;) f9 M$ n' I, @) \( U
- //写入格式RTC_FORMAT_BCD或RTC_FORMAT_BIN,确保写入及读取数据格式一致即可
# \: `1 t1 ~ R - if (HAL_RTC_SetTime(&hrtc, &RtcTime, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序/ w) W: q3 S/ P8 Q, _! w3 f8 ^: n
- {
! m& r: G" X5 \ c1 ?" h& C7 ` - printf("写入时间失败!\r\n"); //显示写入失败
( X3 l7 k: a0 Z$ W5 E$ [ - }else if (HAL_RTC_SetDate(&hrtc, &RtcDate, RTC_FORMAT_BCD) != HAL_OK)//将数据写入RTC程序
, a6 s0 o/ ~8 @3 B) v - { w' e/ [: t2 f
- printf("写入日期失败!\r\n"); //显示写入失败5 G+ _8 n( D k/ |
- }else printf("写入成功!\r\n");//显示写入成功+ O# v( X) ~6 E
- time_set=0;8 _4 S" }/ w2 C2 c1 ]
- }
4 R a0 l0 {( o2 y- S0 I# [4 t# g - HLPUSART_RX_STA=0;//接收错误,重新开始
Y0 f% l' E( y/ Y( _, N/ M9 J - HAL_Delay(100);//等待
7 I: V' P0 M+ e( E4 A& i1 L - }
* Q" \! t) v- {3 b6 Z) P* | - if(KEY_1())4 M3 v* s$ O6 _8 t
- {
% P$ y% U7 r3 S. y3 g' s1 P z - HAL_RTC_GetTime(&hrtc, &RtcTime, RTC_FORMAT_BCD);//读出时间值
. j$ ^! T- Y9 C3 S: \( t - HAL_RTC_GetDate(&hrtc, &RtcDate, RTC_FORMAT_BCD);//一定要先读时间后读日期
9 z3 c9 o) i4 j( u2 q! ^% E X - printf(" RTC实时时钟测试\r\n");9 m, U, Z& `, j5 a1 M1 U
- printf(" 实时时间:%04d-%02d-%02d %02d:%02d:%02d \r\n",2000+RtcDate.Year,7 D! M% _+ @4 \7 ]2 C
- RtcDate.Month, RtcDate.Date,RtcTime.Hours, RtcTime.Minutes, RtcTime.Seconds);//显示日期时间. T$ Q5 a1 b" a
- }
; z) p3 [6 T% Q( Z3 G5 b - if(KEY_2())2 G, B3 A" q( p9 @; {& K
- {$ c, T* l4 K3 T; `! ~
- printf(" 请输入设置时间,格式20221220183030,按回车键确定! \r\n");
: B# c0 ^7 \, _/ U - time_set=1;
8 N- `4 u: D) s) k! x0 K1 Z - }
% W) U% p2 v' P. N c* u' s+ m* [ - /* USER CODE END WHILE */
复制代码
; ?& e1 I! u9 p1 J1 G9 F3 t 注意和前面简介说过,确保时间和日期值之间的一致性,先处理时间(时分秒),在处理日期(年月日),在stm32l4xx_hal_rtc.c的HAL_RTC_GetTime函数注释中也有明确要求如下,“您必须在HAL_RTC_GetTime()之后调用HAL_RTC_GetDate()来解锁值以确保时间和日期值之间的一致性。读取RTC当前时间锁定日历阴影寄存器中的值,直到读取当前日期以确保时间和日期值之间的一致性。”:; H0 B# K' d( x) Q7 U! J d
3 p, g) W( [7 r3 t5 m+ Z6 B
" k$ Q8 }5 V& ]! h& z1 Y1 {' z" Z' {+ e9 |& p5 h, d) Z; ^& Y! g
: \; f* h. a3 O- _( s8 S四、编译及测试
2 @* a$ N+ X2 T* S8 N, N% x 和本专栏其他博文一样,设置好工程输出目标格式支持,配置好运行调试支持后,点击编译工程及运行按钮,完成工程编辑及下载。 8 G& A0 E% P! b
! Q6 X! G0 L9 c1 B/ L4 F/ I# L! q5 f
& x( }+ H' @$ H1 a- H
+ v% |' j4 u$ w" H# ?$ a
6 z, ]! t' P. `8 o
打开串口助手工具,连接到开发板,测试
* c: n+ m; f+ e, w
, C1 e8 m. L( E6 U) l% P. ` 【1】按键KEY1,读取RTC时间,读取出的时间就去前面在cubeMX 设置的时间,并开始计数,因此分秒有差异。! A1 c4 v1 W6 o& }% ~
6 l+ ?; x% a8 k* [* y$ Y f5 J
; e0 H/ r; g. G2 T
9 x6 U4 A' f# k% S
【2】按键KEY2设置时间,并按格式输入时间,然后在按键KEY1读取新的RTC时间,如下图所示,设置时间生效。
8 }) F. O D' L3 O$ _5 @
* e* ^0 r5 U5 _3 P! w6 W; V2 n; c) ]
; l# x/ v% ~7 u$ E0 }
* d3 U0 {( Q4 ?" y; p: L/ o
【3】按开发板上的Reset按键,系统复位,再按键KEY2读取RTC时间,时间依然正常计数' l( Y" o- M/ q# P; M' }# z9 V
I. ]3 W, ]6 }8 T. s
# `8 H, ?6 ?9 q/ M( O, V1 o1 i0 l
. d" M% x8 ^' B. B8 @
4 b) h" z4 y+ S# b7 d
【4】断电一会,然后重新上电,再按键KEY2读取RTC时间,时间恢复到CubeMX设置的初始时间(即为断电后,原来写入的标识无法保持,HAL_RTCEx_BKUPRead读取标记失败重新初始化)。
, `' ^5 k+ Q" j2 K) E: p- Y* J' i
4 Y1 N5 R- ?% Z% x0 G5 _5 G6 u
" X( m4 E8 M( r; J/ `
. ~ h. D- i! P- C& H6 I( q, @6 X& O
————————————————" X7 q8 m! [0 e* F# u& g
版权声明:py_free-物联智能如有侵权请联系删除
1 O5 j; [) [ ^3 j1 N! z3 b/ ~6 g6 M
3 g- s: X, \1 @6 Z; h1 Y0 j
! K/ ?6 C9 }! D4 j# X5 J
) C" A* n7 O+ \* [1 r+ @3 I5 x4 a |
瞎J13乱发,傻福玩意
F1系列的RTC是计时器,你帖子里用的不是STM32L496VGT吗?看过这个芯片的数据手册吗?MD,抄都TM抄不明白,下边这张图看得懂吗?傻福