主要内容; w# i$ x& Z/ u& J! v
1) RTC特征与原理;
2 u) S$ N! G8 v. x- E9 X1 P2) BKP备份寄存器特征与原理;
. m, x) y! p% N) r) g. `3) RTC常用寄存器+库函数介绍;
0 y" Q+ c: `2 f; O4) 相关实验代码解读。* r; j( r& |+ ~
实验内容:
) r% t- X* W! J4 G- A因为没有买LCD屏,所以计划通过串口实时更新时间。系统运行后,串口调试助手实时显示当前时间,可通过USMART工具,修改当前时间和闹钟时间,闹钟触发后,蜂鸣器会叫1s时长。板子正常运行时,LED0和LED1交替闪烁。
4 ]9 i" ^5 h P8 N
1 M0 m" M4 d$ i# ]/ Z1. RTC实时时钟特征与原理6 v: f" Z2 `& u
1.1 RTC (Real Time Clock)称实时时钟。RTC是独立的定时器,RTC模块拥有一个连续计数的计数器,在相应的软件配置下,可以提供时钟日历的功能,修改计数器的值可以重新设置当前时间和日期;
& O9 P! F' E2 W4 `5 A0 [* J, D. _% X# b# `! I
1.2 RTC模块和时钟配置系统(RCC_BDCR寄存器)是在后备区域,即在系统复位或从待机模式唤醒后RTC的设置和时间维持不变。但是在系统复位后,会自动禁止访问后备寄存器和RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护;
- N: P5 s6 ]/ {( U# v- A
+ {1 C9 }! |2 P7 h1.3 RTC特征如下图所示:
. g5 B4 X- D5 V' a( K, e- S& z$ A( q( x# k- ?; F$ A6 D
% p7 g8 z% {5 m$ |$ ]7 ?( S- t
- S5 T2 Z& u# I5 X8 ^ A1.4 RTC工作原理框图& ^1 \( G& o* l9 j$ Y" \; h8 \/ Y. _
! s: J/ v [6 e0 }3 T2 l
1 ]& h8 X' P# [. z7 D! x5 F
3 T: {; J$ f* U6 r. l3 U( Q6 I/ I1.4.1 RTC预分频器,RTCCLK除以RTC_PRL的重装载值后,值为分频后的TR_CLK的值,一般情况下TR_CLK设1Hz;
' S* e* k1 {! P N' z" L/ u+ L1.4.2 RTC预分频器,RTC_DIV装载从RTC_PRL的重装载值,每个RTCCLK周期值减1,至0后会溢出;7 Z l& w9 t+ P' F
1.4.3 待机时维持供电,RTC_CNT,每个TR_CLK周期加1,直至溢出,触发溢出中断;当RTC_ALR所设值等于RTC_CNT实时值时,触发闹钟中断;每个TR_CLK周期产生一次中断。8 R% C3 {% t F, y0 d) x
由原理框图可知RTC的组成:
6 a6 ?4 `: }# b/ n8 H S- MAPB1接口:用来和APB1总线相连。通过APB1接口可以访问RTC的相关寄存器(预分频值,计数器值,闹钟值)。
2 J0 j8 t+ Y$ y0 B/ X# L9 Q( X3 R: DRTC核心:由一组可编程计数器组成,分两个主要模块:
3 ]6 n$ Q' n: n5 J1)第一个是RTC预分频模块,它可以编程产生最长1秒的RTC时间基TR_CLK。如果设置了秒中断允许位,可以产生秒中断;
! k) }5 G/ y% P3 J! K" C2)第二个是32位的可编程计数器,可被初始化为当前时间。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时间相比,当匹配时候如果设置了闹钟中断允许位,可以产生闹钟中断。
- h4 s6 V. f9 T- w+ h: n. S备注:RTC内核完全独立于APB1接口,软件通过APB1接口对RTC相关寄存器访问。但是相关寄存器只在RTC APB1时钟进行重新同步的RTC时钟的上升沿被更新。所以软件必须先等待寄存器同步标志位(RTC_CRL的RSF位)被硬件置1才读。$ N/ g6 k0 K) z. Q+ n% g
! V0 w- V. ] y! k- S
1.5 RTC时钟源(有三个时钟源可选,一般采用外部时钟)
- K6 L6 b7 h: x, S# n6 l5 K) ?7 r
- G/ k( E! Y& a G6 F- D
: M: T0 r: Z. |3 M7 s
& G, a# v2 [: l0 m3 Q/ q
2. BKP备份寄存器2 n9 U2 l: m; ^
2.1 备份寄存器是42个16位的寄存器,可用来存储84个字节数据;
0 J: z& H: X( S+ N2 I8 S ^2.2 它们处在备份区域,当VDD电源切断,仍然由VBAT(板子上的纽扣电池)维持供电;
! O% a+ U: q* q2.3 当系统在待机模式下被唤醒,或者系统复位或者电源复位,它们也不会复位;
) p: }8 M5 `5 B/ X2.4 执行以下操作将使能对后备寄存器和RTC访问:" y; k0 }1 T" Q6 R3 S
2.4.1 置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备时钟;! y. W7 k \3 [9 r
2.4.2 设置寄存器PWR_CR的DBP位,使能对RTC和后备寄存器的访问。) J1 {* `$ O( A$ g, H, i
2.5 BKP备份寄存器常用来保存一些系统配置信息和相关标志位。% ^. O. N# E( r% W7 L' p
$ R- }( Y k, \
% \3 ?* @. N4 o6 T3 f* ~! c# L* b( ]% c6 t4 p. y. e. A6 F
3. RTC常用寄存器
7 ~8 f8 @+ _4 W5 k* e; p( ~0 X4 ~: d5 X3.1 RTC控制寄存器 (RTC_CRH(高位), RTC_CRL(低位));
: K* d2 E* T4 g# I! F" q5 B; J
- t- X- D, t [4 h2 K. L/ {
b2 Y; `- g9 r* c G2 a, R2 G
3 b! C; X! C5 Y( Q R
, P4 ?* r/ Q% c3 B( U
8 m8 M6 y6 P `3.1.1 修改CRH/CRL寄存器,必须先判断RSF位,确定已经同步;
8 m# P( [. Y9 i. f3.1.2 修改CNT,ALR,PRL的时候,必须先配置CNF位进入配置模式,修改完之后,设置CNF位为0退出配置模式;0 O, H0 M% S' M. n1 m) v+ Q
3.1.3 同时在对RTC相关寄存器写操作之前,必须判断上一次写操作已经结束,也就是判断RTOFF位是否置位。1 }2 ~( f6 G7 Y& |8 J9 Q
3.2 RTC预分频装载寄存器 (RTC_PRLH, RTC_PRLL);
: z! U' t5 N: p; f( z' T$ T& e& ~$ ?& U' z B
5 w- A4 h% R9 c% C/ C
+ X4 {$ G# A3 @5 F3.3 RTC预分频余数寄存器 (RTC_DIVH, RTC_DIVL);" x7 k; l/ w! ], D* V" `: `
' ~1 B4 ^' Y, A, h6 \5 @
" x0 {4 R3 i( a" h5 y
+ h- L, d; c& w$ |3.4 RTC计数器寄存器 (RTC_CNTH, RTC_CNTL);
, Z; J$ g8 |3 q
5 `. G' m* Z5 I) b7 y" \
# q. s' h; u* M5 ]- K$ |
/ c: V e1 d8 j
3.5 RTC闹钟寄存器 (RTC_ALRH, RTC_ALRL);* G7 ~- ^: U0 k' X9 v7 O
: F3 \0 g5 ^+ K# T" ^1 O
" h6 d* {) G# W
, H$ U7 o( _: j4 n3 }3.6 配置RTC寄存器# |, ?/ i6 {+ @4 r. b) V/ i
& P8 d2 R" M7 o5 h
4 }1 i8 K' w: ^3 l) A
( f. }7 U8 k- g3.7 读RTC寄存器, ~ B$ q1 V5 u/ ^# X. m, o" u
6 t Z. b3 s* a; d2 J: y
' y: k: Z: P6 w0 ]1 e" _. S: m8 ^/ v! }/ e0 d$ F( V
4. RTC常用库函数' @- {& A) K; [$ V& I* O1 e' ^
4.1 STM32提供的官方库函数文件名为:6 R% W' b+ s1 t
stm32f10x_rtc.c 和stm32f10x_rtc.h;
0 T o- [) a' P, M/ N4.2 具体库函数为:0 t5 Y. E% [ J1 t1 A
0 a% m1 W" \$ z; {) g% _- void RTC_ITConfig(uint16_t RTC_IT, FunctionalStateNewState);: Z D7 T1 m% M/ j
- void RTC_EnterConfigMode(void);! y0 d) @& S( \7 C; G
- void RTC_ExitConfigMode(void);
+ X4 Z- d4 V/ g7 I( a* T' X5 ? - uint32_t RTC_GetCounter(void);
( \' z9 |$ a' d - void RTC_SetCounter(uint32_t CounterValue);8 M( R0 ?1 M3 b: x: w
- void RTC_SetPrescaler(uint32_t PrescalerValue);$ A; O5 Z- m4 H; J" u
- void RTC_SetAlarm(uint32_t AlarmValue);
/ P- M8 F" {6 v( { - uint32_t RTC_GetDivider(void);
3 f) `. ^$ k% D - void RTC_WaitForLastTask(void);
9 t( Y- Q+ @/ b. t4 {2 P' N - void RTC_WaitForSynchro(void);; ~* {; w0 x. G
- FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);" k2 Z V; Y: A) v3 ]3 g
- void RTC_ClearFlag(uint16_t RTC_FLAG);* ]4 P$ a+ v \* q6 \7 Q, |+ a
- ITStatus RTC_GetITStatus(uint16_t RTC_IT);" G* v- ~! ]2 f
- void RTC_ClearITPendingBit(uint16_t RTC_IT);
复制代码
% w! E) c% O1 I4.3 RTC时钟源和时钟操作函数:. P. s" g7 Y; L: W* I. w3 ^2 N
( ^1 v& H' q6 f* ?- Q
- void RCC_RTCCLKConfig(uint32_t CLKSource); //时钟源选择
' Y% Y9 B! X, k# g; O0 h - void RCC_RTCCLKCmd(FunctionalState NewState); //时钟使能
复制代码
5 E" c. F6 X# F' q1 A$ w1 {4.4 RTC配置函数(预分频,计数值):
; w+ q! Z) A! u
$ q5 y h* [; l- void RTC_SetPrescaler(uint32_t PrescalerValue); //预分频配置:PRLH/PRLL
9 W' w! [1 j4 W2 w) v - void RTC_SetCounter(uint32_t CounterValue); //设置计数器值:CNTH/CNTL
: o! V8 e# h) r: I - void RTC_SetAlarm(uint32_t AlarmValue); //闹钟设置:ALRH/ALRL; J8 S* B% E: R7 ~) u7 b
复制代码 & c& U( A( c( a6 D
4.5 RTC中断设置函数:9 T5 F u2 l) B, `" B1 m. B Z+ ?
( }, \: i4 ~5 l. H8 J' Z- void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState); //CRH
复制代码
! |& X+ W5 X$ s2 b' |4.6 RTC允许配置和退出配置函数:
- L4 D5 G& r2 b% ~- g
7 d; R7 T. B( ~4 E1 f7 k( Y4 U- void RTC_EnterConfigMode(void); //允许RTC配置 : CRL位 CNF
. e$ P3 @; {6 x2 B6 j7 N1 k! O - void RTC_ExitConfigMode(void); //退出配置模式 : CRL位 CNF
复制代码
+ l+ E3 P& C& E1 B9 Z4.7 同步函数:! J* J; x1 E% ~5 T/ g
1 z) z5 K1 A3 S) B+ B) L9 Q- void RTC_WaitForLastTask(void); //等待上次操作完成:CRL位RTOFF8 D! _# D G) k! M/ J) t/ \' a
- void RTC_WaitForSynchro(void); //等待时钟同步:CRL位RSF
复制代码
2 N2 Y3 H: }6 s5 i! y" e9 q' ]: S. A4.8 相关状态位获取清除函数:
* E# J8 I4 p: V2 z0 \: ]8 z, F5 i) W, h R9 y. @7 p
- FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);% \0 H3 l9 z1 n
- void RTC_ClearFlag(uint16_t RTC_FLAG); , g/ P0 @$ _; k2 s
- ITStatus RTC_GetITStatus(uint16_t RTC_IT); _6 O6 L- O$ l0 h7 C" h
- void RTC_ClearITPendingBit(uint16_t RTC_IT);
复制代码
; }& p9 g7 |3 U. E9 j/ x1.9 其他相关函数(BKP等)
]2 [9 _2 @1 h8 G) Z
J0 m; P/ E$ k8 r9 d& o& ^* n- PWR_BackupAccessCmd(); //BKP后备区域访问使能
* H4 \- b g/ o e8 I - RCC_APB1PeriphClockCmd(); //使能PWR和BKP时钟
. N. r* |2 [8 X0 _( K5 A - RCC_LSEConfig(); //开启LSE,RTC选择LSE作为时钟源
- w& ~) P' T4 n# H- r - PWR_BackupAccessCmd(); //BKP后备区域访问使能 / n4 N3 z9 F- F1 N/ {# M' {
- uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR); //读BKP寄存器 2 h" ^# G4 x/ B/ c' A( Q" Y
- void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data); //写BKP
复制代码
! A* K6 X3 F; K/ @4 ^2 p: _0 ]4 P5. RTC一般配置步骤& Q0 W: K9 k. r* r
5.1 使能PWR和BKP时钟:$ d/ n( `: l1 k' n. w' F
1 m1 S" o/ g1 @$ R
- RCC_APB1PeriphClockCmd();
复制代码 2 p; W; k( U e+ j* y1 v6 ^
5.2 使能后备寄存器访问:
0 ?1 o; l8 v1 O- y; c# g) [2 ~! ?$ Z3 {. Z+ a& S. w7 _# X
5 B' g6 E0 `; P1 k; F' D
5.3. X* i0 X( V0 E
配置RTC时钟源,使能RTC时钟:/ k( }; n# e0 y7 H! k) x5 D5 w7 C
, a1 o/ S* K4 X7 o$ X
- RCC_RTCCLKConfig();
4 n' o+ ~' [( x# o7 f" H8 ~) P4 p - RCC_RTCCLKCmd();
复制代码
8 y6 t! Z! g% `. B y如果使用LSE,要打开LSE:RCC_LSEConfig(RCC_LSE_ON);
% _" Q/ Y5 b8 D) M6 V1 [ ~, ^3 ?( u) {: K% |! P" S1 {7 V& s2 \
5.4 设置RTC预分频系数:2 b9 Z0 [$ T$ X) l7 J
" U' y4 l L3 |
1 B" ?, d: H8 O6 |4 P$ x( f9 p
5.5 设置时间:7 n- V4 H9 y5 V
0 T" k( H/ M# |5 L* Y0 q: l* l E' P
5.6 开启相关中断(如果需要):6 f6 X. b! U/ d, {
5.7 编写中断服务函数:
" Q4 U! [2 z& p/ ^. M& t5.8 部分操作要等待写操作完成和同步。& @# _: @4 f1 x$ w K; ?: P
1 ]; w# u, q( S! |. ]7 H& Z
- RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 V6 ]' i n8 t3 T* s S
- RTC_WaitForSynchro(); //等待RTC寄存器同步
复制代码 ; R! t I) k. k* I1 C
6. 相关实验代码
5 `8 J; d' N( W6.1 rtc.h头文件代码) F2 _5 t7 {, E2 j( k$ n
% o8 u) @( u! U" G- #ifndef __RTC_H
E r4 C; ^: G0 K" M U& ~: u - #define __RTC_H ! q7 [, w- }( D5 f8 R+ f
- #include "stm32f10x.h"" s1 e h5 j- u# b7 L
- //定义一个时间结构体//
% [; r& \3 @: b1 Z - typedef struct , ~, R; D( j5 F4 \3 L
- {
/ V: _$ R. [. ?+ l4 d; {1 [ - vu8 hour; //时 T; F6 L$ `2 _6 q1 [
- vu8 min; //分
% G% ]5 Q, ~$ O+ p - vu8 sec; //秒
- t9 t) G0 v d- Z - vu16 w_year; //年' }( g4 K: M$ F \
- vu8 w_month; //月
t& i- k) g$ w2 Q% Q, j - vu8 w_date; //日
; L7 U8 ~( E& [3 J& T - vu8 week; //周 8 j: K. ?) V- T0 }4 P" y' _) }
- }_calendar_obj; 3 Y+ t8 L! B& e, r9 @
- extern _calendar_obj calendar; //日历结构体,已在rtc.c中申明了//
2 E9 d7 L3 q1 h" K8 d$ B - extern u8 const mon_table[12]; //月份日期数据表,常数5 b9 s `6 I( P( {8 N* z- \. C
- void Disp_Time(u8 x,u8 y,u8 size); //在制定位置开始显示时间% p3 t. \! b: l J! z+ o5 K
- void Disp_Week(u8 x,u8 y,u8 size,u8 lang); //在指定位置显示星期
. R; \# \: d6 B! A' C2 f$ e - u8 RTC_Init(void); //初始化RTC,返回0,失败;1,成功;# [9 g/ N7 a$ }; ?% x
- u8 Is_Leap_Year(u16 year); //平年,闰年判断,返回1是,0不是) t) |+ G! H1 B! t
- u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec); //闹钟设定+ R. }; G5 y8 x# s, s
- u8 RTC_Get(void); //获取时间
7 r$ e4 b4 y$ M6 Z; z9 B, y+ G. L) J - u8 RTC_Get_Week(u16 year,u8 month,u8 day); //获取日期,返回0成功,其他失败
) y8 b. F. X6 Q/ I - u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec); //设置时间
1 U+ z& q0 p- n2 B8 m7 p - #endif
复制代码
@( O* O8 b$ M V6 Z6.2 rtc.c文件代码
5 B7 V' s) W6 Y+ e1 O
: ]) _4 s8 c9 `- #include "beep.h"
5 T" B. R3 u1 m; i8 S3 d - #include "rtc.h" 2 O8 T! N; t1 s* w' I
- #include "sys.h"
# z) U& h% f z4 C7 q$ p - #include "delay.h"! E+ L" \! M. P9 U: `
- #include "usart.h"
/ t/ E7 w" a- ^: h1 {+ g8 J! F - //时钟结构体//
r! v! ]( z& }' E E - _calendar_obj calendar; 8 B0 |% \1 Z- T/ g& a- G% U8 E
- static void RTC_NVIC_Config(void)
R9 q9 x( V6 t; c - {
1 s( a8 H' u# s2 G5 }, j - NVIC_InitTypeDef NVIC_InitStructure;1 m) k9 D( h+ g# ^
- NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //RTC全局中断; i1 Q: x- [$ g
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级1位,从优先级3位9 x( f) g+ u9 |0 p7 E2 f1 I* m& g: Y
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //抢占优先级0位,从优先级4位
2 X+ P, x/ v; I/ K! C2 E - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能该通道中断" X- e6 Q& G# ^ _! ^0 g8 v
- NVIC_Init(&NVIC_InitStructure);
' S" I2 X3 c% E! b! Q. B7 i - }8 `9 r; w/ f" I6 |! _5 }1 ]
- //实时时钟配置& a$ S, J" k- z' h4 m+ `
- //初始化RTC时钟,同时检测时钟是否工作正常
* V- N7 ?4 r; f3 d/ {1 \ - //BKP->DR1用于保存是否第一次配置的设置4 v. @8 w# l( ~- E; j; U( U7 |
- //返回0:正常; 其他:错误代码
8 \8 t" x) E7 Q/ {% V, p - u8 RTC_Init(void)/ F/ l5 ?% q9 u- V
- {; z& M- W% u% n" h% D
- //检查是不是第一次配置时钟//2 K5 C, g& h6 d% O3 I
- u8 temp=0;
' \. u) U: w& I# n1 A& d% B - //*****第一步*****//; p: i/ u4 z+ S
- //电源控制PWR,当主电源VDD掉电后,通过VBAT脚为实时时钟(RTC)和备份寄存器(BKP)提供电源//
& l9 _6 v" \5 n+ Q - //使能PWR和BKP外设时钟//
8 P2 N. U# I/ R: m a; C5 L - RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
}9 Z5 l6 c5 g2 ~6 a - //*****第二步*****//
4 J+ a4 o/ x# H2 v9 i8 n! [ - //使能后备寄存器访问,位带操作,实际对电源控制寄存器PWR_CR的第八位DBP写1,允许写入RTC和BKP//
+ p4 H7 Q! j* P$ g- {- C4 a+ y - PWR_BackupAccessCmd(ENABLE); " @; F, z- Z* v! U9 _" E' w
- //读取BKP_DR1寄存器0~15位的值,判断是否与0x5050一致//: }+ w. r2 j# v- r8 R
- if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050)
- S, A, J/ H5 G - { r) [! c6 `; [+ _1 S
- //如果不一致,说明第一次配置,则进行如下操作//
% J6 k/ F5 L0 J$ {% _& O - BKP_DeInit(); //复位备份区域//
# v" J7 i! f6 X0 O% y - RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振//
- e& q2 Q. N: e( J. C - //备份域控制寄存器RCC_BDCR,位1,LSERDY为0时未就绪,为1时就绪//3 L; ? W6 O2 Z, Y8 X7 C3 d' p
- while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250). c/ }% y. J8 T T
- {$ ]0 N$ G; Q9 U6 b) y% z" s
- temp++;
" Q: @. ~# y8 q+ `( P - delay_ms(10);' a! J) o/ }) Z- A% f+ E4 w% Y
- }
% G% y; x; e3 L6 `- J8 G - if(temp>=250)return 1; //初始化时钟失败,晶振有问题,结束u8 RTC_Init(void)函数//
# T3 \6 i. \' _ - //若temp<250,则继续// ' {+ a! r: Q+ S, d5 v) E
- //*****第三步*****//
; ^" d6 u1 K- N% D1 r9 F0 R/ \ - RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟//
7 Q( l. K K$ o2 A$ a0 M - RCC_RTCCLKCmd(ENABLE); //使能RTC时钟// * J# z: a' S& L7 P. e) L& Z, ^
- //*****配置RTC寄存器开始*****// $ D; w& S5 q4 q6 s
- //*****第六步*****//' x6 q. d7 ~; z0 k$ {- x
- //RTC控制寄存器低位(RTC_CRL),位5,RTOFF为0时仍在写操作,为1时写操作完成,该函数循环执行,直到RTOFF为1//
4 H6 G% S' z$ g5 ] - RTC_WaitForLastTask();
3 s0 T. v4 n& _8 H! q, O# i - //RTC控制寄存器低位(RTC_CRL),位3,RSF为0时寄存器未同步,为1时寄存器同步,该函数循环执行,直到 RSF 为1//- [, t; |/ O, R `; r* K
- //因为修改CRH/CRL寄存器,必须先判断RSF位,确定已经同步// ' w1 g& o, c1 E0 @6 N
- RTC_WaitForSynchro();
7 k. }% p E% v" s* o3 C- E - RTC_ITConfig(RTC_IT_SEC | RTC_IT_ALR, ENABLE); //对控制寄存器高位(RTC_CRH)操作,使能RTC秒中断//9 p- \' H3 q2 o
- RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成// - V& `2 n, T8 @
- //*****第四步和第五步*****//
/ H. Q' z m' \: y% \ - //RTC控制寄存器低位(RTC_CRL),位4,CNF置1,进入配置模式//
9 c8 P; U7 `$ M; l; y - RTC_EnterConfigMode();
$ J1 Z& p' \: n/ Y( }$ y$ b5 |- ^ - RTC_SetPrescaler(32767); //设置RTC预分频的值//$ ]5 c3 \+ p3 a1 z
- RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成//
! @$ e7 s. j% Y8 U- G - RTC_Set(2015,1,14,17,42,55); //设置时间,纯c语言代码//
9 ]. h* T5 R$ h2 }+ d6 X - //RTC控制寄存器低位(RTC_CRL),位4,CNF置0,退出配置模式//
: d9 \7 \ T: \( y* j - RTC_ExitConfigMode(); ) q2 G' U1 z, w+ n8 b5 d
- //写入BKP_DR1寄存器0~15位的值//% Q4 d# y* _! v& C+ M3 h
- BKP_WriteBackupRegister(BKP_DR1, 0X5050); //向指定的后备寄存器中写入用户程序数据//: q' h F# A% h2 c V; O
- }
2 E) l% T' y" e! Q! V - else //如果,不是第一次配置,系统继续计时//
! c' X+ T* z9 W - {
0 x7 y# r" B5 J" k5 I4 [0 \- O' u: | - RTC_WaitForSynchro(); //等待寄存器同步// ' m3 K/ P e1 I- w& w
- RTC_ITConfig(RTC_IT_SEC | RTC_IT_ALR, ENABLE); 0 N A8 s! q2 [0 I
- RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成//) }0 P9 v; m8 S4 j S b* w
- }
& Z9 c* T& m l5 D' b, ] - RTC_NVIC_Config(); //RCT中断分组设置//
& ]# x9 S5 r" ^3 [( D - RTC_Get(); //更新时间//% v% \7 U4 F$ C* o D6 s4 I' A1 O
- return 0; //ok//
3 Q( H+ E- x( E) t& f3 y - } ; G" g" }6 g3 c4 u- r
- //*****第七步,编写中断服务函数*****//
) T& B2 \& W# g1 x+ _ - //秒中断发生,更新时间,闹钟中断发生,更新时间并向串口发送时间//
/ h# r* l" @3 w: a1 i - void RTC_IRQHandler(void)' ^8 j$ o; C2 M; X! ]! L" K6 g
- {
" r# `% A% a E5 {, A' P - if(RTC_GetITStatus(RTC_IT_ALR)!= RESET) //判断闹钟中断是否发生//
1 O8 C2 |# x" d1 \$ A; {( M8 i - {. p6 ]+ E% E2 A# v7 e
- //RTC控制寄存器低位(RTC_CRL),位1,ALRF置0,清闹钟中断// . C& @+ z) S" e0 Z+ m1 }7 t
- RTC_ClearITPendingBit(RTC_IT_ALR); ; C+ a% x L5 N: P- G% {+ Y' i
- RTC_Get(); //更新时间//
: t( \) O$ @; H2 e" a - printf("闹钟时间:%d-%d-%d %d:%d:%d\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);. V6 I6 G5 r9 L+ N
- BEEP=1;! I3 S* n4 e4 O5 q7 P5 g
- delay_ms(1000);
$ X. P* j j, h* p4 ` H$ z* ?, N - BEEP=0; ' r! y+ p6 d1 B
- } / Q2 G3 c! C$ j* ^1 n
- if (RTC_GetITStatus(RTC_IT_SEC) != RESET) //判断秒钟中断是否发生//1 C4 p7 D, s- ?4 x0 C$ T1 a4 j
- {
' [# J$ q( t% F- D2 K8 M/ \* O5 S$ c - RTC_Get();
7 W0 z, q5 l) ?# y. L& @0 N - printf("闹钟时间:%d-%d-%d %d:%d:%d\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);
; h: F$ o2 K% [ - } * F0 i* G1 e5 h9 [4 f
- ; i( b& j0 _+ k: K! [, ?) E
- RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清闹钟中断和溢出标志//
+ @: F+ G5 e* C# l5 e+ j2 I6 Y - RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成//
( g4 j: l4 w: D, n: @ - }
1 f" R4 ^6 U# w - //判断是否是闰年函数,能被4整除,但不能被100整除的年是闰年,能被400整除的年是闰年//
* l4 X; k- Y+ h - //月份 1 2 3 4 5 6 7 8 9 10 11 122 W( h" E! R3 j
- //闰年 31 29 31 30 31 30 31 31 30 31 30 31
: x0 g. q3 P3 [ - //非闰年/平年 31 28 31 30 31 30 31 31 30 31 30 317 I) e6 ^6 [. L3 _2 J8 K
- //输入:年份
% S, S+ h- O) w7 i - //输出:该年份是不是闰年.1,是.0,不是
4 a# V: M/ d) ?& O - u8 Is_Leap_Year(u16 year)
6 s/ u6 Q! x( v7 s% ` - { + c9 y( O6 L$ O3 Z7 u4 c) `# D
- if(year%4==0) //判断是否能被4整除,能则继续;不能则不是闰年,且返回0//
7 J! H. c7 y$ }6 n7 e - { , ~ I8 Y8 j2 K3 G
- if(year%100==0) //判断是否能被100整除,能则继续;不能则是闰年,且返回1//
, `2 L, d7 f' [4 [) @ - { o% z- W7 {) Q! K; x9 J* H
- if(year%400==0)return 1; //判断是否能被400整除,能则是闰年,且返回1;不能则不是闰年,且返回0// ! Z& q2 C- ]7 t2 r- H) p0 s8 H
- else return 0;
}, P3 q7 }1 r: ?+ y$ R; {8 ?: x$ N& u - }else return 1;
. \" W* U, n+ Y; { - }else return 0;
. o2 P+ |1 C5 t- N - }
+ G7 u% i; g+ ?' ^8 h' N - //设置时钟,把输入的时钟转换为秒钟,以1970年1月1日为基准,1970~2099年为合法年份//
( X8 U/ U; v0 L: u" x' H - //并将值传至RTC计数器寄存器高位(RTC_CNTH)和RTC计数器寄存器低位(RTC_CNTL)中//
0 Q" m: H! C* n+ c' \- D - //返回值:0,成功;其他:错误代码//
9 T8 Q7 m! T: D6 U* U - //平年的月份日期表,闰年的话,就把28改29//
% H$ Q/ w, }+ G4 G# y7 X - const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
]8 Q8 u n/ k" A1 p- F* F' \ - u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)* D/ d6 [" Q8 @0 I, p- K$ t, O
- {6 ?# V9 j# @& X8 _+ M
- u16 t;% v- t8 X* q9 R- e8 V: P
- u32 seccount=0;
9 J' K- ]/ ^/ J& E - if(syear<1970||syear>2099)return 1; //如果年份不在1970~2099年之内,则报错,函数停止//
' Y7 H0 f1 u+ j' t" k2 v; r n - for(t=1970;t<syear;t++) //把所有年份的秒钟相加//
' r! A0 `! G) y - {* }, ?' ^9 J* b5 H' P
- if(Is_Leap_Year(t))seccount+=31622400; //闰年的秒钟数//; E3 c6 C! X* g9 _# E& n
- else seccount+=31536000; //平年的秒钟数,少一天// B; v) Y d& F, |
- }. F7 q, y. z5 V
- smon-=1;6 @" d7 `0 b' |6 U2 v
- for(t=0;t<smon;t++) //把前面月份的秒钟数相加//
2 o" `& m8 f5 I( L. A - {: l2 v( W: d, s
- seccount+=(u32)mon_table[t]*86400; //月份秒钟数相加//' K9 H" U2 m" i* A4 N
- if(Is_Leap_Year(syear)&&t==1)seccount+=86400; //闰年2月份增加一天的秒钟数 //
8 h% Q4 H- |8 l) ^3 |4 l/ x - }
- l& r0 I l6 n1 |! U! B( | - seccount+=(u32)(sday-1)*86400; //把前面日期的秒钟数相加//
0 r1 E8 w4 i5 G* k3 [, C+ ` - seccount+=(u32)hour*3600; //小时秒钟数//
. c- I% {2 n; G- Q% w - seccount+=(u32)min*60; //分钟秒钟数//
8 c* E( t0 L* ^2 Z% O1 T! V - seccount+=sec; //最后的秒钟加上去//6 l- @ e& E( ?3 k% E, n. N* G4 S7 x
- //以上,已经把某年某月某日某时某分某秒的时间,用秒计数起来//
c \1 d/ b( J4 o. f* C7 } - RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟//
! E [! h& F1 K - PWR_BackupAccessCmd(ENABLE); //使能RTC和BKP访问//
7 S. c) _) L$ a/ J/ c4 z L - RTC_SetCounter(seccount); //设置RTC计数器的值//' i% P+ T0 b* A) }5 H2 b) z" ?: k
- RTC_WaitForLastTask();
7 O' q. E* [! z - return 0;
0 b4 b7 o' u+ `: A c$ L" d - }
0 i8 o/ d; y# J- a% X6 O1 x1 I* B; q - //初始化闹钟,以1970年1月1日为基准,1970~2099年为合法年份,syear,smon,sday,hour,min,sec:年月日时分秒//
+ l$ b% ]2 I5 Q - //返回值:0,成功;其他:错误代码.//! e8 J% ?5 N: z2 [" }- f P2 M
- u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)6 p6 ~% R* w/ m# k
- {
) t3 Y, F. c. r8 a* C2 [ - u16 t;
+ g+ k& k2 s5 K9 e, v - u32 seccount=0;
3 m' D) H' E; f: a+ Y0 ] - if(syear<1970||syear>2099)return 1; 3 ?) |4 T# _! r
- for(t=1970;t<syear;t++) //把所有年份的秒钟相加//
7 }( \2 B6 A: {7 P9 Z A, a# M - {* C) e0 S, x+ j7 f( o
- if(Is_Leap_Year(t))seccount+=31622400; //闰年的秒钟数//
/ ]' p; F5 S; |; p - else seccount+=31536000; //平年的秒钟数//1 g8 [" c$ f; l5 Y, `9 Z/ K
- }
; L2 W; c5 L6 y5 ~$ @( o7 [) o+ ~7 v - smon-=1;
: e- X0 L2 Y$ o: m - for(t=0;t<smon;t++) //把前面月份的秒钟数相加//3 R& z6 ~4 F9 _7 [$ E1 Z
- {
, P% N3 d! E0 P4 c' q" _/ Q0 w - seccount+=(u32)mon_table[t]*86400; //月份秒钟数相加//) B# ~5 W- W4 f" a4 Y0 g i1 l
- if(Is_Leap_Year(syear)&&t==1)seccount+=86400; //闰年2月份增加一天的秒钟数// * I5 x7 i( ^. d% H6 g4 B9 {
- }' f6 N1 F/ g4 H
- seccount+=(u32)(sday-1)*86400; //把前面日期的秒钟数相加//1 p! Q; r) f% x( Y
- seccount+=(u32)hour*3600; //小时秒钟数//+ z/ B' F/ P$ G* }
- seccount+=(u32)min*60; //分钟秒钟数//
/ H! |$ @: Z9 O- o w ] - seccount+=sec; //最后的秒钟加上去//
3 I0 |# {' h6 Z; z# W - //设置时钟//
% ~, B; O3 X/ R. S, a6 { - RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟//
1 _! M4 A4 Y8 ?2 a" U4 |" X - PWR_BackupAccessCmd(ENABLE); //使能RTC和BKP访问// * A; ~, Z/ G" U( ]1 r! C! C7 f
- RTC_SetAlarm(seccount); //设置RTC闹钟寄存器值//0 P! l R/ S+ G u. H$ R* ~0 B
- RTC_WaitForLastTask(); * ~8 H1 e6 }- t _3 \8 G# c" \
- return 0; , W; j3 j" e$ f- L# T8 R
- }4 H \7 _* w2 l# P% C
- //得到当前的时间,将秒转换成年月日时分秒//7 |. X4 i" B9 C- I' L# L7 v
- //返回值:0,成功;其他:错误代码.//7 S/ {1 k/ l, n5 a
- u8 RTC_Get(void)' T2 [9 K R3 S' C. e5 j+ L* g
- {
) y4 i5 k* i6 {3 {. B, o - static u16 daycnt=0;
; P+ i- K; B3 g& v4 g) M& ]4 L% |+ O - u32 timecount=0;
: T5 Q A) N9 _8 i. t - u32 temp=0;
& n. Y. U: T. |. n) H - u16 temp1=0; # O. `. \" o$ j. l$ y# ^ h
- timecount=RTC_GetCounter(); //获取16位RTC计数器寄存器高位RTC_CNTH和低位RTC_CNTL的值,返回32位数据//, ?7 \0 v& O: `. K& A; v" o
- temp=timecount/86400; //得到天数//) J. J; n" A" S6 }& H
- if(daycnt!=temp) //超过一天了4 w$ y& e7 q. S+ ~' f+ }: T& z
- {
$ E+ Z% b4 n$ I; b - daycnt=temp;1 f# b/ C2 C) e; S7 Y- S) ^
- temp1=1970; //从1970年开始//
- o; |" a4 y5 K( H - while(temp>=365) //当temp≥365天时//
- q) L' f3 e" W - {
5 L |3 w ~5 b - if(Is_Leap_Year(temp1)) //闰年判断,闰年366天,平年365天//
/ m) f* a5 E+ `* B/ H) T. s - {
# w7 Q9 g6 v2 G& [% h' k- W6 W - if(temp>=366)
$ n2 R/ S3 e' u; w6 \0 @ - temp-=366; //闰年,temp减366天//
* J" I# T" Y1 `; u4 G - else break; //当年是闰年,且是365天时,跳出循环//7 H. Y& O8 e& t( f9 Z+ { ^1 u
- }
- i' d0 ^# i. a1 k1 D9 v - else temp-=365; //平年,temp减365天// " B1 {( B) ], ^1 l& j
- temp1++; //平年,temp1加1年//
9 Y5 `0 }- G, v$ u. i. H O - } W0 z& A2 J5 S) g# z7 X# n
- calendar.w_year=temp1; //得到年份temp1//
' ?8 H4 |4 Z8 E4 W. M$ b/ h - temp1=0; //temp1至0,开始计算月份//
2 r3 w7 f* P, Z- L3 ?- I - while(temp>=28) //当28≤temp<365天时//
! M5 f7 M# z& i4 N/ }* X- V6 p - {& C" F+ M& `: v5 A4 b% G* k9 m
- //temp1从0至11分别对应1月到12月//4 |: [; r% ^1 z% |$ R6 z. G
- if(Is_Leap_Year(calendar.w_year)&&temp1==1) //当是闰年,且为2月份时,执行下面语句//
2 {: A& U- M2 J2 J2 z' f4 I9 k - {
0 v/ G) S- X% h% f2 {* H* k - if(temp>=29)temp-=29;
- t9 S6 f; K7 e o2 `' [* f - else break; ; i7 n1 o0 p( R" g6 P V
- }
8 ~, H8 o% m9 q+ t! x9 p - else //平年,执行else//
j' P Q; t" W3 b& c _9 w - {' t1 ]9 `! l; b) k( x
- if(temp>=mon_table[temp1]) 7 N- e5 i. N) q
- temp-=mon_table[temp1]; ( r J4 z4 x) Y/ j- s
- else break;. U3 h: j; { x; K8 M, ?2 m
- }: e1 Z. f. f: B& c
- temp1++;
3 d" u/ n1 M4 j) f8 i8 M - }, D% |* ~ G% s" w
- calendar.w_month=temp1+1; //得到月份//8 ~3 ?% q5 k6 ` I& O
- calendar.w_date=temp+1; //得到日期// ' X; e2 v; U3 s; X, }6 `
- }2 x' g% g2 e: X# R
- temp=timecount%86400; //得到秒钟数//
2 d' u8 o! X, G* b* Z$ { - calendar.hour=temp/3600; //小时//
6 Z: |3 E3 b5 z, x& b - calendar.min=(temp%3600)/60; //分钟//
+ a) m7 y9 {# S g, V - calendar.sec=(temp%3600)%60; //秒钟//
2 I, u4 l% s7 P2 Z6 b$ A - calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date); //获取星期//
+ A% h: x* @7 c; m5 W: w - return 0;
' ~9 s' ^4 Q b6 K - } + x* }4 G0 B% ~1 E
- //获得现在是星期几2 G% e. }% K3 m- r, ^4 U
- //功能描述:输入公历日期得到星期(只允许1901-2099年)
) h+ [9 e# c0 P/ f3 [ - //输入参数:公历年月日 ! o, P. \5 Y- I3 U- ]
- //返回值:星期号 ( G6 F0 v. K- t5 a* ]+ [* C
- u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表?//
! e& h2 B+ y- y - u8 RTC_Get_Week(u16 year,u8 month,u8 day); n4 W9 H, c* ^% _' V
- { . \4 m: x; P* [; I$ U- @
- u16 temp2;
, H0 E. D% R! R5 u9 b0 o0 h# { - u8 yearH,yearL;6 |$ E5 P% ~( r
- yearH=year/100; yearL=year%100; " ^* _* S1 x+ X b7 ^. G- z, Y
- //如果为21世纪,年份数加100// / {+ E$ L; t3 r* ^
- if (yearH>19)' z) b* I" G7 f/ }
- yearL+=100;2 [0 c7 d$ l. Y
- //所过闰年数只算1900年之后的// ; J/ w+ D) [8 G6 u
- temp2=yearL+yearL/4;
- P( `" z. T& t - temp2=temp2%7; 1 g: ~% _ ], L# F, Z) b6 e! D
- temp2=temp2+day+table_week[month-1];2 y) |0 b" X" z5 W+ I, @) r" P
- if (yearL%4==0&&month<3)temp2--;
; }% }% X \& j3 L+ V9 M0 m - return(temp2%7);
$ \! ^; {# Z6 A( _ - }
复制代码 ! O y( {% K9 K8 L4 J$ z+ Y% x
6.3 main.c文件代码; ~ x; p& j% x. e/ C# w) t- W
( R% ?- ^0 P, ] j1 w7 x9 K- #include "led.h"
2 C _! M3 j: `) b+ A, X - #include "beep.h"* h. j+ h3 C; n; Q' O
- #include "rtc.h"
: d# \- X" |$ i - #include "delay.h"$ k4 Z# u! `' N+ J
- #include "sys.h"& E# i6 Y; G) L7 V! @, N, @' _$ b
- #include "usart.h" 9 f6 R" m' ~5 @8 n+ k9 a0 v) j4 Q
- #include "usmart.h"
( ~0 C+ ~& Y$ e- \2 P, E6 ` - //主函数//
) L: T. B3 V* K7 l" M( U - int main(void)
) y" v/ ~+ z0 d1 _1 V! ` - { ( v( w* |3 G2 ~1 J& }& u
- u8 t=0; ' S& w6 r0 J+ [; Z% T" Z0 Q
- delay_init(); //延时函数初始化//
# Y0 g% H$ a T3 I& i1 ?9 B - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断优先级分组为组2:2位抢占优先级,2位响应优先级//+ m' K. L7 b# @' B" ?4 p$ E# F. X4 ?0 q
- uart_init(115200); //串口初始化为115200//
: W" n( e7 I$ m9 h' x4 t - LED_Init(); //LED端口初始化//( V6 c$ t8 W: |
- BEEP_Init();, z$ Y0 s0 p! I) ~
- BEEP=1;" L2 S4 ] K4 z3 D6 _
- delay_ms(1000);3 [/ ]1 A" `1 N2 n8 P
- BEEP=0;$ Z9 r( b" X+ S7 V, M6 W
- LED0=0; //LED0亮//0 g7 F# _( [) j/ p4 h6 x- Q; _
- usmart_dev.init(SystemCoreClock/1000000); //初始化USMART// 3 V6 z6 B' @+ S# f
- RTC_Init(); //RTC初始化// / D. Q i) Q! z4 h9 i% s
- while(1)+ W4 N7 l* O6 `% X
- {
1 E# V& j4 m* x+ d- \ - if(t!=calendar.sec)
' I4 B M( `9 O$ s; A, r5 u3 F - {
+ C# p0 W; q" D4 [" x! g0 O. z - t=calendar.sec; //如果calendar.sec不更新,则t不更新// o0 W% ]3 ^0 ^7 Y9 Y- r8 ~
- LED0=!LED0; //每次calendar.sec更新,LED0和LED1交替闪烁//
! E- I3 a1 J5 X7 O) | - LED1=!LED1;
3 u! t& F& j% d" A7 h* B0 P - }
1 e' {# K) w2 m. O3 q5 L6 g) w - delay_ms(10); . J8 F. z) h4 ~5 L! u3 H. a
- }
# w+ Y& f, i7 d. _$ ^ - }
复制代码 5 y+ W7 O5 [" V
最后调试效果如下图所示
! }9 b# e A9 G0 ~ o8 o6 {5 V* A
f: t8 |: _$ f+ x" n
( X5 f* s% o( Y3 J! W# M# z; p
( h( n/ {5 H7 E8 h8 C————————————————
. d! a& M; W$ P( Q版权声明:天亮继续睡1 Z7 Y7 C8 m# q, Y
6 Z0 z5 D7 ]5 h2 u2 r
) d7 n; f1 q" D& K |