一、STM32F1 RTC介绍

1.1 RTC简介

  STM32 的实时时钟( RTC)是一个独立的定时器。 STM32 的 RTC 模 块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的 功能。修改计数器的值可以重新设置系统当前的时间和日期。 RTC模块和时钟配置是在后备区域,无论器件状态如何(运行模式、 低功耗模式或处于复位状态),只要保证后备区域供电正常,RTC便不会 停止工作,所以通常会在后备区域供电端加一个纽扣电池,即使主电源 停止供电,后备电源也会启动供电,从而保证RTC时钟不停的运行,只有 当主电源和后备纽扣电池都没有电的时,RTC才停止工作。 从 RTC 的定时器特性来说,它是一个 32 位的计数器,只能向上计 数。它的时钟来源有三种,分别为高速外部时钟的 128 分频( HSE/128 )、 低速内部时钟 LSI 以及低速外部时钟 LSE。

1.2电源

电池备份区域

使用电池或其他电源连接到VBAT脚上,当VDD断电时,可以保存备份寄存器的内容和维持RTC的

功能。

VBAT脚也为RTC、LSE振荡器和PC13至PC15供电,这保证当主要电源被切断时RTC能继续工作。切换到VBAT供电由复位模块中的掉电复位功能控制。

如果应用中没有使用外部电池,VBAT必须连接到VDD引脚上。

1.3备份寄存器(BKP)简介

  备份寄存器是42个16位的寄存器,可用来存储84个字节的用户应用程序数据。他们处在备份域里,当VDD电源被切断,他们仍然由VBAT维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。

此外,BKP控制寄存器用来管理侵入检测和RTC校准功能。

复位后,对备份寄存器和RTC的访问被禁止,并且备份域被保护以防止可能存在的意外的写操作。执行以下操作可以使能对备份寄存器和RTC的访问。

  ● 通过设置寄存器RCC_APB1ENR的PWREN和BKPEN位来打开电源和后备接口的时钟

  ● 电源控制寄存器(PWR_CR)的DBP位来使能对后备寄存器和RTC的访问。

1.4实时时钟(RTC)

RTC简介

  实时时钟是一个独立的定时器。RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。

  RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后,RTC的设置和时间维持不变。

  系统复位后,对后备寄存器和RTC的访问被禁止,这是为了防止对后备区域(BKP)的意外写操作。执行以下操作将使能对后备寄存器和RTC的访问:(BKP中也提到过)

  ● 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟

  ● 设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。

二、功能描述

1、概述

  RTC由两个主要部分组成(参见下图)。第一部分(APB1接口)用来和APB1总线相连。此单元还包含一组16位寄存器,可通过APB1总线对其进行读写操作(参见16.4节)。APB1接口由APB1总线时钟驱动,用来与APB1总线接口。

  另一部分(RTC核心)由一组可编程计数器组成,分成两个主要模块。

  第一个模块是RTC的预分频模块,它可编程产生最长为1秒的RTC时间基准TR_CLK。RTC的预分频模块包含了一个20位的可编程分频器(RTC预分频器)。如果在RTC_CR寄存器中设置了相应的允许位,则在每个实时时钟(RTC)TR_CLK周期中RTC产生一个中断(秒中断)。

  第二个模块是一个32位的可编程计数器,可被初始化为当前的系统时间。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时间相比较,如果RTC_CR控制寄存器中设置了相应允许位,比较匹配时将产生一个闹钟中断。

2、复位过程

  除了RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器外,所有的系统寄存器都由系统复位或电源复位进行异步复位。

  RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器仅能通过备份域复位信号复位。

3、读RTC寄存器

RTC核完全独立于RTC APB1接口。

软件通过APB1接口访问RTC的预分频值、计数器值和闹钟值。但是,相关的可读寄存器只在与RTC与 APB1时钟进行重新同步的RTC时钟的上升沿被更新RTC标志也是如此的。这意味着,如果APB1接口曾经被关闭,而读操作又是在刚刚重新开启APB1之后,则在第一次的内部寄存器更新之前,从APB1上读出的RTC寄存器数值可能被破坏了(通常读到0)。下述几种情况下能够发生这种情形:

● 发生系统复位或电源复位

● 系统刚从待机模式唤醒(参见第4.3节:低功耗模式)。

● 系统刚从停机模式唤醒(参见第4.3节:低功耗模式)。

所有以上情况中,APB1接口被禁止时(复位、无时钟或断电)RTC核仍保持运行状态。

因此,若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置’1’。

注:RTC的 APB1接口不受WFI和WFE等低功耗模式的影响。

4、写RTC寄存器

必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器。

另外,对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器。

三、STM32F1 RTC配置步骤

  • 使能PWR和BKP时钟。调用函数:RCC_APB1PeriphClockCmd();
  • 使能后备寄存器访问。调用函数:PWR_BackupAccessCmd();
  • 配置RTC时钟源,使能RTC时钟。调用函数:RCC_RTCCLKConfig();RCC_RTCCLKCmd();
  • 如果使用LSE,要打开LSE:RCC_LSEConfig(RCC_LSE_ON);
  • 设置RTC预分频系数。调用函数:RTC_SetPrescaler();
  • 设置时间。调用函数:RTC_SetCounter();
  • 开启相关中断(如果需要)。调用函数:RTC_ITConfig();
  • 编写中断服务函数。调用函数:RTC_IRQHandler();
  • 部分操作要等待写操作完成和同步。调用函数:RTC_WaitForLastTask();RTC_WaitForSynchro();

四、程序举例

编写RTC控制程序 本章所要实现的功能是:设置RTC时间日期初值,在RTC秒中断内使用 串口打印出RTC日期和时间,D1指示灯闪烁提示系统运行。

程序框架如下 :

(1)初始化RTC,设置RTC时间日期初值 (2)开启RTC的秒中断,编写RTC中断函数, (3)在RTC中断内更新时间并打印输出 (4)编写主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 1 #ifndef _rtc_H
2 #define _rtc_H
3
4 #include "system.h"
5
6
7 u8 RTCx_Init(void);
8 void RTC_GET(void);
9
10 typedef struct
11 {
12 u8 hour;
13 u8 min;
14 u8 sec;
15 }_calender;
16
17 extern _calender calender;
18
19
20
21 #endif

分析RTC_Init()函数:RTC初始化函数。

  初始化时按照之前的RTC一般步骤进行配置,这里需要注意的是,为了区分是否是第一次执行RTC_Init()函数,必须判断后配寄存器中是否写如果某个值(向BKP_DR1寄存器写入0xA0A0,写入其他的数字也可以)如果写入不用再初始化。

为什么要区分是否执行过RTC_Init?

  如果由于 断电/ 复位/唤醒等待 等因素,程序中断但RTC时钟以及后备寄存器区域还在执行;等恢复供电重新启动程序时,这不能再对RTC时钟进行初始化,否则一直初始化,那么RTC作为时钟就没什么实际作用。

1
2
3
4
5
6
7
8
9
1 if (BKP_ReadBackupRegister(BKP_DR1) != 0xA0A0//(从指定的后备寄存器中读出数据)判断是否初始化过
2 {
3   //第一次进行初始化(RTC_Init)
4   BKP_WriteBackupRegister(BKP_DR1, 0XA0A0); //向指定的后备寄存器中写入用户程序数据
5 }
6 else//(已经初始化过)系统继续计时
7 {
8 //不是第一次进行初始化(RTC_Init)
9 }

代码44:使用外部低速晶振(LSE)时需要检查指定的RCC相应的标志位是否设置,等待低速晶振就绪。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
 1 #include "rtc.h"
2 #include "systick.h"
3 #include "ustrt.h"
4
5
6 _calender calender;
7
8
9 void RTC_NVIC_Config() //设置RTC中断优先级
10 {
11 NVIC_InitTypeDef NVIC_InitStruct;
12
13 NVIC_InitStruct.NVIC_IRQChannel=RTC_IRQn;
14 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;
15 NVIC_InitStruct.NVIC_IRQChannelSubPriority=3;
16 NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE ;
17 NVIC_Init(&NVIC_InitStruct);
18
19 }
20
21 void RTC_GET() //获取 RTC 计数器的值并进行处理
22 {
23 u32 timedata;
24 timedata=RTC_GetCounter();
25 calender.hour=timedata/3600;
26 calender.min=timedata%3600/60;
27 calender.sec=timedata%3600%60;
28 }
29
30 //返回0:初始化失败
31 //返回1:初始化成功
32 u8 RTCx_Init()
33 {
34 u8 time;
35 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
36 RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
37 PWR_BackupAccessCmd(ENABLE);
38
39
40 if(BKP_ReadBackupRegister(BKP_DR1)!=0xA0a0) //从指定的后备寄存器读数据)检查是不是第一次配置时钟
41 {
42 BKP_DeInit(); //将后备寄存器初始化
43 RCC_LSEConfig(RCC_LSE_ON); //将RCC_LSE时钟开启
44 while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET && time<250) //检测LSE时钟是否开启
45 {
46 time++;
47 delay_ms(10);
48 }
49 if(time>=250)
50 {
51 return 1;
52 }
53 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //配置RTC的时钟为LSE
54 RCC_RTCCLKCmd(ENABLE); //RTC时钟输入开启
55 RTC_WaitForLastTask(); //等待直到RTC寄存器上的上一次写操作完成。
56 RTC_WaitForSynchro(); //等待,直到RTC寄存器(RTC_CNT、RTC_ALR和RTC_PRL)与RTC APB时钟同步。
57 RTC_ITConfig(RTC_IT_SEC, ENABLE);
58 RTC_WaitForLastTask(); //等待直到RTC寄存器上的上一次写操作完成。
59 RTC_EnterConfigMode(); // 允许配置
60 RTC_SetPrescaler(32767); //设置 RTC 预分频的值
61 RTC_WaitForLastTask(); //等待直到RTC寄存器上的上一次写操作完成。
62 RTC_SetCounter(0x1111); //设置 RTC 计数器的值 初始化时间17:34:55
63 RTC_ExitConfigMode(); //退出 RTC 配置模式
64 BKP_WriteBackupRegister(BKP_DR1,0xA0a0); //向指定的后备寄存器中写入用户程序数据
65
66 }
67 else //(系统之前已经进行过相应初始化)系统继续计时
68 {
69 RTC_WaitForLastTask(); //等待直到RTC寄存器上的上一次写操作完成。
70 RTC_WaitForSynchro(); //等待,直到RTC寄存器(RTC_CNT、RTC_ALR和RTC_PRL)与RTC APB时钟同步。
71 RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能或者失能指定的 RTC 中断
72 }
73
74 RTC_NVIC_Config(); //RCT中断优先级别设置
75 RTC_GET(); //获取 RTC 计数器的值
76 return 0;
77 }
78
79 void RTC_IRQHandler(void) //RTC中断函数
80 {
81 if(RTC_GetITStatus(RTC_IT_SEC)!=0) //检查指定的 RTC 中断发生与否(秒中断)
82 {
83 RTC_GET();
84 printf("RTC_Time:%d:%d:%d\r\n",calender.hour,calender.min,calender.sec);
85 }
86 RTC_ClearITPendingBit(RTC_IT_SEC); //清除 RTC 的中断待处理位
87 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 1 #include "system.h"
2 #include "led.h"
3 #include "systick.h"
4 #include "ustrt.h"
5 #include "rtc.h"
6
7 int main()
8 {
9 u8 i=0;
10
11
12 SysTick_Init(72);
13 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
14 LED_Init();
15 ustrt_Init(9600);
16 RTCx_Init();
17
18 while(1)
19 {
20
21 i++;
22 if(i%20==0)
23 {
24 led1=!led1;
25 }
26 delay_ms(10);
27 }
28 }

配置RCT基本步骤(参考二)-添加了程序但没接42khz晶振,因此在初始化阶段不通过

  1. 使能PWR和BKP外设时钟
  2. 使能后备寄存器访问
  3. 判断从指定的后备寄存器中读出数据:
    如果读出数据与写入的数据相同,说明已经配置过了不需要重新配置,只要等待最近一次对RTC寄存器的写操作完成和使能RTC秒中断即可,如果读出数据与写入数据不相同,则需要重新配置。修改写入时间时一定要记得把两个0x5051修改成其他任意十六位数据。这样才可以重新修改写入时间。
  4. 复位备份区域
  5. 设置外部低速晶振(LSE),使用外设低速晶振
  6. 检查指定的RCC标志位设置与否,等待低速晶振就绪,这一步尤为重要,
    如果晶振出现问题整个系统是不会正常运行的。如果是硬件问题不容查出来。
  7. 设置RTC时钟(RTCCLK),选择LSE作为RTC时钟
  8. 使能RTC时钟
  9. 等待最近一次对RTC寄存器的写操作完成
  10. 等待RTC寄存器同步
    (注意9、10两步很重要,每次操作RTC寄存器时都要重复一次9、10两步)
  11. 使能RTC秒中断
  12. 等待最近一次对RTC寄存器的写操作完成
  13. 允许配置
  14. 设置RTC预分频的值:
    计算方式32768/(32767+1) = 1Hz 周期刚好是1秒。
  15. 等待最近一次对RTC寄存器的写操作完成
  16. 设置时间
  17. 退出配置模式,防止误操作
  18. 向指定的后备寄存器中写入用户程序数据
  19. RCT中断分组设置
  20. 更新时间

配置代码参考

RTC.c文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/************************************RTC.c文件***************************/
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "rtc.h"

_calendar_obj calendar;//时钟结构体

u32 timecount=0;

static void RTC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //RTC全局中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级1位,从优先级3位
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //先占优先级0位,从优先级4位
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能该通道中断
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}

//实时时钟配置
//初始化RTC时钟,同时检测时钟是否工作正常
//BKP->DR1用于保存是否第一次配置的设置
//返回0:正常
//其他:错误代码
u8 RTC_Init(void)
{
//检查是不是第一次配置时钟
u8 temp=0;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
if (BKP_ReadBackupRegister(BKP_DR1) != 0x5051) //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
{
BKP_DeInit(); //复位备份区域
RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250) //检查指定的RCC标志位设置与否,等待低速晶振就绪
{
temp++;
delay_ms(10);
}
if(temp>=250)return 1; //初始化时钟失败,晶振有问题
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟
RCC_RTCCLKCmd(ENABLE); //使能RTC时钟
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_WaitForSynchro(); //等待RTC寄存器同步
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_EnterConfigMode(); // 允许配置
RTC_SetPrescaler(32767); //设置RTC预分频的值,计算方式32768/(32767+1) = 1Hz 周期刚好是1秒。
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_Set(2020,2,16,22,16,00); //设置时间
RTC_ExitConfigMode(); //退出配置模式
BKP_WriteBackupRegister(BKP_DR1, 0X5051); //向指定的后备寄存器中写入用户程序数据
}
else //系统继续计时
{
RTC_WaitForSynchro(); //等待最近一次对RTC寄存器的写操作完成
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
}
RTC_NVIC_Config(); //RCT中断分组设置
RTC_Get(); //更新时间
return 0; //ok

}
//RTC时钟中断
//每秒触发一次
//extern u16 tcnt;
void RTC_IRQHandler(void)
{
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断
{
RTC_Get();//更新时间
}
if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
{
RTC_ClearITPendingBit(RTC_IT_ALR); //清闹钟中断
RTC_Get(); //更新时间
printf("Alarm Time:%d-%d-%d %d:%d:%d\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//输出闹铃时间

}
RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清闹钟中断
RTC_WaitForLastTask();
}
//判断是否是闰年函数
//月份 1 2 3 4 5 6 7 8 9 10 11 12
//闰年 31 29 31 30 31 30 31 31 30 31 30 31
//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
//输入:年份
//输出:该年份是不是闰年.1,是.0,不是
u8 Is_Leap_Year(u16 year)
{
if(year%4==0) //必须能被4整除
{
if(year%100==0)
{
if(year%400==0)return 1;//如果以00结尾,还要能被400整除
else return 0;
}else return 1;
}else return 0;
}
//设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准
//1970~2099年为合法年份
//返回值:0,成功;其他:错误代码.
//月份数据表
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
//平年的月份日期表
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
u16 t;
u32 seccount=0;
if(syear<1970||syear>2099)return 1;
for(t=1970;t<syear;t++) //把所有年份的秒钟相加
{
if(Is_Leap_Year(t))seccount+=31622400; //闰年的秒钟数
else seccount+=31536000; //平年的秒钟数
}
smon-=1;
for(t=0;t<smon;t++) //把前面月份的秒钟数相加
{
seccount+=(u32)mon_table[t]*86400; //月份秒钟数相加
if(Is_Leap_Year(syear)&&t==1)seccount+=86400; //闰年2月份增加一天的秒钟数
}
seccount+=(u32)(sday-1)*86400; //把前面日期的秒钟数相加
seccount+=(u32)hour*3600; //小时秒钟数
seccount+=(u32)min*60; //分钟秒钟数
seccount+=sec; //最后的秒钟加上去

RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能RTC和后备寄存器访问
RTC_SetCounter(seccount); //设置RTC计数器的值

RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
return 0;
}

//初始化闹钟
//以1970年1月1日为基准
//1970~2099年为合法年份
//syear,smon,sday,hour,min,sec:闹钟的年月日时分秒
//返回值:0,成功;其他:错误代码.
u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
u16 t;
u32 seccount=0;
if(syear<1970||syear>2099)return 1;
for(t=1970;t<syear;t++) //把所有年份的秒钟相加
{
if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
else seccount+=31536000; //平年的秒钟数
}
smon-=1;
for(t=0;t<smon;t++) //把前面月份的秒钟数相加
{
seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数
}
seccount+=(u32)(sday-1)*86400; //把前面日期的秒钟数相加
seccount+=(u32)hour*3600; //小时秒钟数
seccount+=(u32)min*60; //分钟秒钟数
seccount+=sec; //最后的秒钟加上去
//设置时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
//上面三步是必须的!

RTC_SetAlarm(seccount);

RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成

return 0;
}


//得到当前的时间
//返回值:0,成功;其他:错误代码.
u8 RTC_Get(void)
{
static u16 daycnt=0;

u32 temp=0;
u16 temp1=0;
timecount=RTC_GetCounter();
temp=timecount/86400; //得到天数(秒钟数对应的)
if(daycnt!=temp) //超过一天了
{
daycnt=temp;
temp1=1970; //从1970年开始
while(temp>=365) //计算出来的天数大于等于一年的天数
{
if(Is_Leap_Year(temp1))//是闰年
{
if(temp>=366)
{
temp-=366;//闰年的秒钟数
}
else
{
temp1++;
break;
}
}
else temp-=365; //平年
temp1++;
}
calendar.w_year=temp1;//得到年份
temp1=0;
while(temp>=28)//超过了一个月
{
if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
{
if(temp>=29) //如果天数超过29天
{
temp-=29; //闰年的秒钟数
}
else break; //小于29天,不到闰年3月,直接跳出。
}
else
{
if(temp>=mon_table[temp1])
{
temp-=mon_table[temp1];//平年
}
else break;
}
temp1++;
}
calendar.w_month=temp1+1; //得到月份
calendar.w_date=temp+1; //得到日期
}
temp=timecount%86400; //得到秒钟数
calendar.hour=temp/3600; //小时
calendar.min=(temp%3600)/60; //分钟
calendar.sec=(temp%3600)%60; //秒钟
calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期
return 0;
}
//获得现在是星期几
//功能描述:输入公历日期得到星期(只允许1901-2099年)
//输入参数:公历年月日
//返回值:星期号
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
{
u16 temp2;
u8 yearH,yearL;

yearH=year/100; yearL=year%100;
// 如果为21世纪,年份数加100
if (yearH>19)yearL+=100;
// 所过闰年数只算1900年之后的
temp2=yearL+yearL/4;
temp2=temp2%7;
temp2=temp2+day+table_week[month-1];
if (yearL%4==0&&month<3)temp2--;
return(temp2%7);
}

RTC.h文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/************************************RTC.h文件***************************/
#ifndef __RTC_H
#define __RTC_H
#include "sys.h"

//时间结构体
typedef struct
{
vu8 hour;
vu8 min;
vu8 sec;
//公历日月年周
vu16 w_year;
vu8 w_month;
vu8 w_date;
vu8 week;
}_calendar_obj;
extern _calendar_obj calendar; //日历结构体

extern u8 const mon_table[12]; //月份日期数据表
void Disp_Time(u8 x,u8 y,u8 size);//在制定位置开始显示时间
void Disp_Week(u8 x,u8 y,u8 size,u8 lang);//在指定位置显示星期
u8 RTC_Init(void); //初始化RTC,返回0,失败;1,成功;
u8 Is_Leap_Year(u16 year);//平年,闰年判断
u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);
u8 RTC_Get(void); //更新时间
u8 RTC_Get_Week(u16 year,u8 month,u8 day);
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//设置时间
#endif

总结

本次使用的是STM32F103开发板,很多参考程序使用的是STM32F4,因此,其中很多定义和函数都没有!本程序其实还得添加许多头文件
例如:

1
2
3
#include "stm32f10x_pwr.h"
#include "stm32f10x_bkp.h"
#include "stm32f10x_rtc.h"

由于开发板没有32.768KHz辅助晶振,需要等以后买了再继续测试!

注意:

F1没有F4的这个功能,F1只有一个计数器,所以需要自己根据这个计数器来计算出来得到,F4就有4个寄存器,所以可以直接读出来


相关链接(侵删)

  1. (stm32f103学习总结)—RTC独立定时器—实时时钟实验
  2. STM32F103的RTC实时时钟配置

=================我是分割线=================

欢迎到公众号来唠嗑: