|
什么是RTC RTC (Real Time Clock):实时时钟 RTC是个独立的定时器。RTC模块拥有一个连续计数的计数器,在相应的软件配置下,可以提供时钟日历的功能。修改计数器的值可以重新设置当前时间和日期 RTC还包含用于管理低功耗模式的自动唤醒单元。 / U( B8 E+ G1 ~5 ^1 J3 a. _/ N
* r+ B* }: Z9 m# X2 T' z
在断电情况下 RTC仍可以独立运行 只要芯片的备用电源一直供电,RTC上的时间会一直走。 ( C+ _$ k3 V ]' k" b
RTC实质是一个掉电后还继续运行的定时器,从定时器的角度来看,相对于通用定时器TIM外设,它的功能十分简单,只有计时功能(也可以触发中断)。但其高级指出也就在于掉电之后还可以正常运行。
' n& o# `7 g6 c& K, s2 Y1 c
两个 32 位寄存器包含二进码十进数格式 (BCD) 的秒、分钟、小时( 12 或 24 小时制)、星期几、日期、月份和年份。此外,还可提供二进制格式的亚秒值。系统可以自动将月份的天数补偿为 28、29(闰年)、30 和 31 天。 上电复位后,所有RTC寄存器都会受到保护,以防止可能的非正常写访问。 无论器件状态如何(运行模式、低功耗模式或处于复位状态),只要电源电压保持在工作范围内,RTC使不会停止工作。
- E9 [! K4 V; N) I u
0 x' ]& N* @1 f N4 c, a) v RCT特征:● 可编程的预分频系数:分频系数高为220。
. L" j1 v; G) d# Q ]1 C: d● 32位的可编程计数器,可用于较长时间段的测量。3 `; P9 X8 x; @$ ?
● 2个分离的时钟:用于APB1接口的PCLK1和RTC时钟(RTC时钟的频率必须小于PCLK1时钟 频率的四分之一以上)。
* K& v9 P5 ~6 n$ J9 k● 可以选择以下三种RTC的时钟源:
- e: w/ c: C' @2 i ● HSE时钟除以128;
0 Y& t# W; s7 w2 m6 g8 I ● LSE振荡器时钟;& U- a# T- Q" Q7 x3 a, Y
● LSI振荡器时钟 ● 2个独立的复位类型:5 N0 ^5 ~6 G$ G7 p- n# V
● APB1接口由系统复位;
! O# g3 B: f" Q; M. Q1 K( r ● RTC核心(预分频器、闹钟、计数器和分频器)只能由后备域复位 ● 3个专门的可屏蔽中断:
) I; p5 B3 C, O% C3 p ● 1.闹钟中断,用来产生一个软件可编程的闹钟中断。 ● 2.秒中断,用来产生一个可编程的周期性中断信号(长可达1秒)。 ● 3.溢出中断,指示内部可编程计数器溢出并回转为0的状态。 ! `/ p+ }/ e) s0 z6 `: f4 e
RTC时钟源:5 h- j/ X H2 W0 y3 n' M
三种不同的时钟源可被用来驱动系统时钟(SYSCLK): ● HSI振荡器时钟0 [7 E! Q2 ^, M
● HSE振荡器时钟: o @7 f, C$ z2 U* ] r
● PLL时钟 这些设备有以下2种二级时钟源: ● 40kHz低速内部RC,可以用于驱动独立看门狗和通过程序选择驱动RTC。 RTC用于从停机/待机模式下自动唤醒系统。
' l- L0 w* T5 S l x● 32.768kHz低速外部晶体也可用来通过程序选择驱动RTC(RTCCLK)。
! Y2 A; B+ K8 `5 h
% G ^- j: [( E. { RTC原理框图$ d2 m' ?1 B2 b& b! y
$ j' D- y) G, ^) N0 C7 [8 Z1 D( R& W o& _$ a1 ]9 t
1 H4 y" ?3 @1 R: C, V' zRTC时钟的框图还是比较简单的,这里我们把他分成 两个部分:
% H; J0 V1 i. b& ]APB1 接口:用来和 APB1 总线相连。 此单元还包含一组 16 位寄存器,可通过 APB1 总线对其进行读写操作。APB1 接口由 APB1 总 线时钟驱动,用来与 APB1 总线连接。 通过APB1接口可以访问RTC的相关寄存器(预分频值,计数器值,闹钟值)。 RTC 核心接口:由一组可编程计数器组成,分成 两个主要模块 。 . i; n. P J% ~ o
4 L0 H7 }) u% \! K1 T
g) ( x( }) ?/ @; M& Z% O, \/ O
4 ]! L n' Q+ u m
& L$ c$ m" D L
" B' I& L2 x' c5 F
. q' E% \3 u2 K6 z# _/ h/ ?4 R
第一个模块是 RTC 的 预分频模块,它可编程产生 1 秒的 RTC 时间基准 TR_CLK。RTC 的预分频模块包含了一个 20 位的可编程分频器(RTC 预分频器)。如果在 RTC_CR 寄存器中设置了相应的允许位,则在每个 TR_CLK 周期中 RTC 产生一个中断(秒中断)。) |* R* v5 Q; o
& V/ B+ @, }- k5 _* A
' i* c9 H9 ?6 Q
, f8 j* ^3 U6 r& j, }2 C
! s4 s1 D1 c( j& U( u
& D" |( P; d0 ^5 c, |/ ~& R6 Y$ t
% N L8 ?2 x' A , m) v/ H' _8 {. y# @* `$ D6 [
第二个模块是一个 32 位的可编程计数器 (RTC_CNT),可被初始化为当前的系统时间,一个 32 位的时钟计数器,按秒钟计算,可以记 录 4294967296 秒,约合 136 年左右,作为一般应用,这已经是足够了的。- D: \) |9 P1 w
1 `" \' u s v/ E+ l
% d; S6 u' @1 U$ f7 r$ p
RTC具体流程:RTCCLK经过RTC_DIV预分频,RTC_PRL设置预分频系数,然后得到TR_CLK时钟信号,我们一般设置其周期为1s,RTC_CNT计数器计数,假如1970设置为时间起点为0s,通过当前时间的秒数计算得到当前的时间。RTC_ALR是设置闹钟时间,RTC_CNT计数到RTC_ALR就会产生计数中断, - RTC_Second为秒中断,用于刷新时间,
- RTC_Overflow是溢出中断。
- RTC Alarm 控制开关机
* c/ h- B; ] f' _! b! j, U! c1 O; b& w6 t' J2 j
$ D6 k* k3 I) j x/ l7 P
+ n0 |* ?! j- R9 e. [0 i( y2 P
( I" ]" p- n. s6 }- o# L9 i9 Q' ^8 H
8 \. {' f; j9 N: [9 r. n7 y9 YRTC时钟选择使用HSE分频时钟或者LSI的时候,在主电源VDD掉电的情况下,这两个时钟来源都会受到影响,因此没法保证RTC正常工作.所以RTC一般都时钟低速外部时钟LSE,频率为实时时钟模块中常用的32.768KHz,因为32768 = 2^15,分频容易实现,所以被广泛应用到RTC模块.(在主电源VDD有效的情况下(待机),RTC还可以配置闹钟事件使STM32退出待机模式).
! M8 P! }7 f( P z0 G
7 }: F8 c$ y+ V
RTC复位过程除了RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器外,所有的系统寄存器都由系统复位或电源复位进行异步复位。
0 _- ]6 o8 b# \4 k E" S+ ^! {" [RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器仅能通过备份域复位信号复位。 系统复位后,禁止访问后备寄存器和RCT,防止对后卫区域(BKP)的意外写操作
- S( A) b. p/ @$ z. w! A
0 y; p% o; N0 q7 O7 C V, G4 q
读RTC寄存器RTC内核完全独立于APB1接口,软件通过APB1接口对RTC相关寄存器访问。但是相关寄存器只在RTC APB1时钟进行重新同步的RTC时钟的上升沿被更新。所以软件必须先等待寄存器同步标志位(RTC_CRL的RSF位)被硬件置1才读。 0 E9 e H& g; V
2 m+ C& B8 D7 [1 D$ h
配置RTC寄存器必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、
2 @2 ^3 F( J- IRTC_CNT、RTC_ALR寄存器。 另外,对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询; u3 }& V) r+ y8 T/ G
RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是’1’0 r' \+ N3 X, W
时,才可以写入RTC寄存器。
' r }8 y1 n3 A* {
. A- z# a1 O3 R. Y5 C9 j
RTC时钟源RTC是一个独立的时钟源
1 O& ]! d+ a1 ] 7 B/ U- A- G8 L7 K5 [" W* t
7 U& m2 R" u. n% R5 Z4 a
0 P0 f5 e' r& M6 Z$ p- J4 R6 J; b! u( F
RTC寄存器- RTC控制寄存器 (RTC_CRH, RTC_CRL)
- RTC预分频装载寄存器 (RTC_PRLH, RTC_PRLL)
- RTC预分频余数寄存器 (RTC_DIVH, RTC_DIVL)
- RTC计数器寄存器 (RTC_CNTH, RTC_CNTL)
- RTC闹钟寄存器 (RTC_ALRH ,RTC_ALRL); i+ P2 a2 k% \2 N a4 Y. Z
1 z) o4 B2 r0 F" W5 l
3 X; X1 f& r2 A1 \# _* r" ?/ D* _; P( }8 P
RTC控制寄存器高位——RTC_CRH 寄存器9 F/ ~7 y9 Z( y9 _, E! G+ s1 x
" I8 }$ V0 V. x* L9 t S3 W' a0 N }
作用:配置3个专门的可屏蔽中断(溢出中断、闹钟中断、秒中断)使能。
; |8 d7 f8 D& [, i5 i# V注意:系统复位后所有的中断被屏蔽,因此可通过写RTC寄存器来
# m" R2 h7 C3 ~8 j1 s1 O确保在初始化后没有挂起的中断请求。当外设正在完成前一次写操作时(标志位RTOFF=0),不
6 p5 |7 J, b. V N能对RTC_CRH寄存器进行写操作。 $ z% ]2 \- V3 d. U
4 ~8 w' z1 e3 {
RTC控制寄存器低位——RTC_CRL 寄存器' Y, C S* I1 l6 t6 {' ?
3 y3 v4 U. w- e& V* ~+ l- U7 Y, t7 Q, ]. L0 O' @ ~1 D7 P9 |
" u+ k, c2 O: O8 I% M! w
6 R6 `. E) Y: {一般用到该寄存器的 3,4,5位
$ |* i- W$ x7 t% A' `- 第 3 位为寄存器同步标志位,我们在修改控制寄存器 RTC_CRH/CRL 之前,必须先判断该位,是否已经同步了,如果没有则等待同步
- 第 4 位为配置标位,在软件修改 RTC_CNT/RTC_ALR/RTC_PRL 的值的时候,必须先软件置位该位,以允许进入配置模式
- 第 5 位为 RTC 操作位,该位由硬件操作,软件只读。通过该位可以判断上次对 RTC 寄存器的操作是否完成,如果没有,我们必须等待上一次操作结束才能开始下一次,也就是判断RTOFF位是否置位。- b4 I/ n% G# F6 m# U0 m: B+ V
( a* S' [6 I, Z 6 e1 V+ S" y& T
% H7 q( k2 E' j) B" P) j
三个位总结如下: ① 修改CRH/CRL寄存器,必须先判断RSF位,确定已经同步。7 _% r; x0 ^* ^. T& N: G6 L
② 修改CNT,ALR,PRL的时候,必须先配置CNF位进入配置模式,修改完之后,设置CNF位为0退出配置模式
( E/ u" S% s3 |, h+ y7 T ③ **同时在对RTC相关寄存器写操作之前,必须判断上一
# K; S$ ?; l* _( W; D5 u! o
: S8 z# [# h8 X$ C! c' o8 V
; w K4 D; O7 n$ W2 r) M4 f" l. w7 ?0 Q! B4 W7 [& s8 n1 [
2 k. l3 D6 [# Q4 s8 \. G* H6 ^/ @
7 h' Y& t N% j8 f" h/ q" q
RTC 预分频装载寄存器——(RTC_PRLH/RTC_PRLL) 寄存器
: q, x3 n/ C+ q) s) T$ o- v- y. B作用:配置 RTC 时钟的分频数, 比如我们使用外部 32.768K 的晶振作为时钟的输入频率,那么我们要设置这两个寄存器的值为 7FFFh(32767),就可获得周期为1秒钟的信号。
) t9 m! `5 M+ ?& C 9 g; v# O' M. \2 c. ^4 Q
- p" |5 t: j: D# r9 V0 _
5 f! B1 W; p# W# C) q2 w
% ]2 W. o" n0 h+ e/ V
RTC预分频器余数寄存器(RTC_DIVH、RTC_DIVL)% y- ^% s' a" l; P2 W+ C
作用: 和他的名字一样,获得余数,也就是获取更精确的计时,比如:0.1s ,0.01 s等 寄存器是只读寄存器,其值在RTC_PRL或RTC_CNT寄存器中的值发生改变后,由硬件重新装载。 : _* w& o6 A* X. R
6 k" o# h& h; D
3 s) U* b* e: v- c; v
$ ~. b: ?, A, e- u' t
4 g9 \. _4 `* S( X5 [ 6 ]" @9 K% W* ]; H" F+ e
( h3 x* U) ^: R, O$ q
RTC 计数器寄存器——RTC_CNTX 寄存器' h* o$ c K/ r1 x# X3 d! e( A6 R9 P9 F
作用:存放计数器内的计数值。也就是用来记录时钟时间 该寄存器由 2 个 16 位的寄存器组成 RTC_CNTH 和 RTC_CNTL,总共 32 位,当进行读操作时,直接返回计数器内的计数值(系统时间)& J: _+ k+ o# c$ b4 k
3 [/ V! @% I/ L+ p5 O6 M
8 D0 x8 M# w- ~6 }) D g
6 W, e# A, Y; M( @, h
2 b! ]& N* @+ t5 ~& X! s, R
. r+ z; }/ t K% P8 A* A! S/ b: F/ Y) E' d+ j" ^% c4 v
RTC 计数器寄存器——RTC 闹钟寄存器(RTC_ALRH、RTC_ALRL)$ t$ n5 p3 K6 K6 |+ o! p9 ?+ x: n8 W3 }
) }1 O# ~" p; }7 `* x% c' T) e
作用: RTC时钟中断控制寄存器 该寄存器也是由 2 个 16 位的寄存器组成 RTC_ALRH 和 RTC_ALRL,也就是32位,当可编程计数器的值与RTC_ALR中的32位值相等时,即触发一个闹钟事件,并且产生RTC闹钟中断。
; o( \' ^2 R9 {% U6 _$ ?1 H
; v) K( s3 B6 d% A
g/ ?6 ~+ P$ @8 l
* X7 D5 z: q V4 e4 W: [3 n5 G# R6 T( s! s8 @: R: C
) D7 K2 @ P& C0 s3 |3 m5 ?3 g. W# c" E8 Z; o
BKP备份寄存器
, a% y0 s' }& ]2 G备份寄存器是42个16位的寄存器。可用来存储84个字节数据。- K2 j2 o( x% Y) ]; V l& S
它们处在备份区域,当VDD电源切断,仍然由VBAT维持供电。 当系统在待机模式下被唤醒,或者系统复位或者电源复位,它们也不会复位。# c2 C- v* F$ D6 p3 Y" X5 r# E
执行以下操作将使能对后备寄存器和RTC访问: - 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备时钟。
- 设置寄存器PWR_CR的DBP位,使能对RTC和后备寄存器的访问2 b8 r+ Q& o3 [+ T3 F i
- w. j b& T; S9 a' d/ t
一般用 BKP 来存储 RTC 的校验值或者记录一些重要的数据, : r3 n6 M$ g4 p6 D2 A1 l* L3 T
3 M- }+ D$ s! ?1 p5 y g5 i1 b% V
0 |( l/ j/ ~6 R# `; L配置RTC寄存器:1.查询RTOFF位,知道RTOFF的值为1. 2.置CNF值为1,进入配置模式。 3.对一个或者多个RTC寄存器进行写操作。 4.清除CNF标志位,退出配置模式。 5.查询RTOFF,直到RTOFF位变1,已确认写操作已经完成。 仅当CNF标志位被清除时,写操作才能进行,这个操作至少需要3个RTCCLK周期。 % }' v) f0 S$ \& _2 @6 z
6 U- b% I: o3 F; f% |
RTC相关库函数- RTC时钟源和时钟操作函数:
! v: h& w i9 h+ d0 z8 F( [, T( T6 Y7 B# \9 J7 a
- void RCC_RTCCLKConfig(uint32_t CLKSource);//时钟源选择
+ I8 \6 v+ E' T, ^8 c$ H" R - void RCC_RTCCLKCmd(FunctionalState NewState)//时钟使能
复制代码 : B% R; O) [# f
' _+ c6 Z4 B. \* [7 {8 h- RTC配置函数(预分频,计数值):
) e( ^4 ]+ D- t4 j8 E F# p
- void RTC_SetPrescaler(uint32_t PrescalerValue);//预分频配置:PRLH/PRLL
9 A; @( a0 g! L: W& \ - void RTC_SetCounter(uint32_t CounterValue);//设置计数器值:CNTH/CNTL
5 x. O' ~: H* ]7 U* G+ O& G - void RTC_SetAlarm(uint32_t AlarmValue);//闹钟设置:ALRH/ALRL
复制代码 ) J) X' O5 M/ V
% {" s/ n5 }( B5 _
- RTC中断设置函数:) T- L+ ? S/ z/ E0 u
( s/ t0 N, w1 f8 Y/ J
6 O; t' G$ ]6 }8 v/ X7 h7 |
- void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);//CRH
复制代码
& a8 H) i: R* y, }0 s, Y( K, n3 `/ ~- RTC配置函数:7 }4 V$ ?8 f# b" s1 V% A
+ d4 N: p) T4 m9 V
- void RTC_EnterConfigMode(void);//允许RTC配置 :CRL位 CNF* t1 `4 T8 X. g( l" e
- void RTC_ExitConfigMode(void);//退出配置模式:CRL位 CNF
复制代码
9 `5 o, R! O" }+ S$ C I- RTC同步函数:6 v% T9 C8 {# D/ b: D' s
- void RTC_WaitForLastTask(void);//等待上次操作完成:CRL位RTOFF
, j' d7 [; I+ v - void RTC_WaitForSynchro(void);//等待时钟同步:CRL位RSF
复制代码 & `7 b( x R, B$ Y$ ?
% G; t' J2 `9 y# w9 A. r- RTC相关状态位获取清除函数:
. M9 N0 Q% f7 j) |$ n. ?
- FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);
& }; R) H4 G9 L( E5 ~( | - void RTC_ClearFlag(uint16_t RTC_FLAG);( U+ o2 E x U8 C' n7 `7 J
- ITStatus RTC_GetITStatus(uint16_t RTC_IT);
# \; D6 V5 |, w1 E" Z# k9 {+ Z - void RTC_ClearITPendingBit(uint16_t RTC_IT);
复制代码
* ?! {! J' s1 I* R- i% @- X$ ?9 |7 a+ ~6 C* H1 S6 S$ }# ~) e
- 其他相关函数(BKP等)7 u3 ~8 E: E# b! H' j' V# N
- PWR_BackupAccessCmd();//BKP后备区域访问使能
7 X2 u9 ]. q! f' b; ~/ q - RCC_APB1PeriphClockCmd();//使能PWR和BKP时钟- P s/ R4 N" g$ B9 }
- RCC_LSEConfig();//开启LSE,RTC选择LSE作为时钟源
% t+ F9 H4 D1 c! A! K; u5 T" l' ` - PWR_BackupAccessCmd();//BKP后备区域访问使能, x* D, f1 k: z% P! j: H
- uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);//读BKP寄存器* `% I0 P$ ? x# j9 X
- void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);//写BKP
7 n& ]& S; A- O# H- d
复制代码 7 o$ F, V' D/ J" L3 x( Z
3 m% J Z9 r% h9 j8 w配置RTC步骤
6 _7 ?, d% n* e4 H& q3 H- ①使能PWR和BKP时钟:3 q7 v3 z, M- k8 @; C
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
复制代码
$ @& O+ C$ ~' Y. c5 f, `. \/ r
9 i2 f2 p f7 P- ② 使能后备寄存器访问:( s, I& ?$ P0 j7 A- x% P6 \
6 h* z9 g. q% q5 ]
- PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问
复制代码 2 s/ P" r9 A6 I9 m5 ]0 _9 l
- ③复位备份区域,开启外部低速振荡器。
: U# v, C( J& C- D N) V
% ?% |, ~! W/ w) r1 Y/ ?# ]3 h
+ M0 y' W# P" Q3 i$ v+ z9 a& r' C
6 q9 T; c6 x$ C& x2 z; |- U: r/ f" A4 C/ k
- ④ 配置RTC时钟源,使能RTC时钟:/ P+ x( L6 E {" u$ u* D& m
* @6 a1 U$ s, t" b' u% h$ @
- RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择 LSE 作为 RTC 时钟(RCC_RTCCLKSource_LSI 和 RCC_RTCCLKSource_HSE_Div128)
8 f, k( G4 z, Q- l& d) k - RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟
复制代码
' U, E2 u, m* X% ]- ⑤ 设置RTC预分频系数:RTC_SetPrescaler();" _6 T& [% M6 Q- b5 X8 ~9 ?
- RTC_EnterConfigMode();/// 允许配置
% T. R5 J9 f& V0 H [ - RTC_SetPrescaler(32767); //设置RTC预分频的值
7 s' R0 W2 q6 S# J, Q/ [- _. Q8 N0 Z& T2 p7 F - RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
复制代码
4 U) Y6 n" B2 u! w8 g, ^
! K5 h: B( z" F5 W6 L9 a1 `( p- ⑥ 设置时间:RTC_SetCounter();
, p3 E7 ~# b9 E: f9 v3 m0 B
+ R, c4 C, Y, p8 [
- RTC_EnterConfigMode();/// 允许配置7 _7 N7 ^/ p4 |
- void RTC_SetCounter(uint32_t CounterValue);! ?9 R s% B, ~, b
- RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
复制代码
' m* r' a4 H5 S! x8 \ x# Y; D! e: F. h& \
/ ~( d, i) E7 I$ [6 _- ⑦开启相关中断(可选):
5 f5 s" K0 J9 m. L% t0 D( R) F
2 P! }5 g M* o0 `: \ _
- void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);//RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中断
复制代码 + X' o( i* z1 q1 d6 s
- ⑧编写中断服务函数:
1 b: Y* H# t0 X) F0 k( c& C T1 v4 O. K) [2 b, \$ Z* E
6 t& c1 a1 g, q9 W4 ^2 B; o d: u! _! {
- ⑨部分操作要等待写操作完成和同步。
& U0 h9 i5 X; ^4 d+ O) c$ ?9 k, b* ?, v) O8 {8 V
- RTC_WaitForLastTask();//等待最近一次对RTC寄存器的写操作完成
- Y" F$ x! R/ U4 K) ?5 L; U - RTC_WaitForSynchro(); //等待RTC寄存器同步
复制代码 / V* Z' j8 {+ ]/ K
8 s) _7 [ @7 g: U F1 |, L; r/ n
9 T: S, e- \8 v: T* N! |. d9 X
|
: F/ U. O' l B; d1 @ ?/ w