目录
1. STM32 RTC
1.1 RTC简介
STM32的实时时钟(RTC)是一个独立的定时器。 STM32的RTC模块拥有一 组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
1.2 结构框图
2. 软件配置
RTC 相关库函数在 stm32f10x_rtc.c 和 stm32f10x_bkp.h 文件中。
(1)使能电源时钟和后备域时钟,开启 RTC 后备寄存器写访问
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
PWR_BackupAccessCmd(ENABLE);
(2)复位备份区域,开启外部低速振荡器
BKP_DeInit();
RCC_LSEConfig(RCC_LSE_ON);
(3)选择 RTC 时钟,并使能
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
RCC_RTCCLKCmd(ENABLE);
(4)设置 RTC 的分频以及配置 RTC 时钟
RTC_EnterConfigMode();
RTC_ExitConfigMode();
void RTC_SetPrescaler(uint32_t PrescalerValue);
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);
void RTC_SetCounter(uint32_t CounterValue);
(5)更新配置,设置 RTC 中断分组
RTC_ExitConfigMode();
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);
(6)编写 RTC 中断服务函数
RTC_IRQHandler
FlagStatus RTC_GetFlagStatus(uint32_t RTC_FLAG);
RTC_ClearITPendingBit(RTC_IT_SEC);
3. 硬件设计
4. 软件设计
4.1 功能描述
4.2 软件实现
(1)初始化 RTC,设置 RTC 时间日期初值
(2)开启 RTC 的秒中断,编写 RTC 中断函数
(3)在 RTC 中断内更新时间并打印输出
(4)编写主函数
4.2.1 rtc.c
1.#include "rtc.h"
2.
3._calendar calendar;//时钟结构体
4.
5.static void RTC_NVIC_Config(void)
6.{
7. NVIC_InitTypeDef NVIC_InitStructure;
8. NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //RTC全局中断
9. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级1位,从优先级3位
10. NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //先占优先级0位,从优先级4位
11. NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能该通道中断
12. NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
13.}
14.
15.
16./* ---------------------------------函数声明-------------------------------------
17.*函数名称:u8 RTC_Iint(void)
18.*输入参数:无
19.*返回参数:u8(返回值=0,初始化正常;返回值=1,初始化失败)
20.*功 能:初始化RTC
21.*作 者:lph
22.*日 期:2024/04/25
23.------------------------------------------------------------------------------ */
24.u8 RTC_Init(void)
25.{
26. //检查是不是第一次配置时钟
27. u8 temp=0;
28. RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP,ENABLE); //使能PWR和BKP外设时钟
29. PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
30. if (BKP_ReadBackupRegister(BKP_DR1) != 0xA0A0) //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
31. {
32. BKP_DeInit(); //复位备份区域
33. RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振
34. while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250) //检查指定的RCC标志位设置与否,等待低速晶振就绪
35. {
36. temp++;
37. delay_ms(10);
38. }
39. if(temp>=250)return 1;//初始化时钟失败,晶振有问题
40. RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟
41. RCC_RTCCLKCmd(ENABLE); //使能RTC时钟
42. RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
43. RTC_WaitForSynchro(); //等待RTC寄存器同步
44. RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断
45. RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
46. RTC_EnterConfigMode();// 允许配置
47. RTC_SetPrescaler(32767); //设置RTC预分频的值
48. RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
49. RTC_Set(2024,4,25,20,37,40); //设置时间
50. RTC_ExitConfigMode(); //退出配置模式
51. BKP_WriteBackupRegister(BKP_DR1, 0XA0A0); //向指定的后备寄存器中写入用户程序数据
52. }
53. else//系统继续计时
54. {
55. RTC_WaitForSynchro(); //等待最近一次对RTC寄存器的写操作完成
56. RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断
57. RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
58. }
59. RTC_NVIC_Config();//RCT中断分组设置
60. RTC_Get();//更新时间
61. return 0; //ok
62.
63.}
64.
65.
66.//RTC时钟中断
67.//每秒触发一次
68.//extern u16 tcnt;
69.void RTC_IRQHandler(void)
70.{
71. if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断
72. {
73. RTC_Get();//更新时间
74. printf("RTC Time:%d-%d-%d %d:%d:%d\r\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//输出闹铃时间
75.
76. }
77. if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
78. {
79. RTC_ClearITPendingBit(RTC_IT_ALR); //清闹钟中断
80. RTC_Get(); //更新时间
81. printf("Alarm Time:%d-%d-%d %d:%d:%d\r\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//输出闹铃时间
82.
83. }
84. RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清闹钟中断
85. RTC_WaitForLastTask();
86.}
87.
88.
89.//判断是否是闰年函数
90.//月份 1 2 3 4 5 6 7 8 9 10 11 12
91.//闰年 31 29 31 30 31 30 31 31 30 31 30 31
92.//非闰年 31 28 31 30 31 30 31 31 30 31 30 31
93.//输入:年份
94.//输出:该年份是不是闰年.1,是.0,不是
95.u8 Is_Leap_Year(u16 year)
96.{
97. if(year%4==0) //必须能被4整除
98. {
99. if(year%100==0)
100. {
101. if(year%400==0)return 1;//如果以00结尾,还要能被400整除
102. else return 0;
103. }else return 1;
104. }else return 0;
105.}
106.
107.
108.//月份数据表
109.u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表
110.//平年的月份日期表
111.const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
112.
113.
114./* ---------------------------------函数声明-------------------------------------
115.*函数名称:RTC_Set
116.*输入参数:syear:年;smon:月;sday:日;
117. hour:时;min:分 ;sec:秒
118.*返回参数:u8(0,成功
119. 1,失败)
120.*功 能:RTC设置日期时间函数(以1970年1月1日为基准,把输入的时钟转换为秒钟)——1970~2099年为合法年份
121.*作 者:lph
122.*日 期:2024/04/25
123.------------------------------------------------------------------------------ */
124.u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
125.{
126. u16 t;
127. u32 seccount=0;
128. if(syear<1970||syear>2099)return 1;
129. for(t=1970;t<syear;t++) //把所有年份的秒钟相加
130. {
131. if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
132. else seccount+=31536000; //平年的秒钟数
133. }
134. smon-=1;
135. for(t=0;t<smon;t++) //把前面月份的秒钟数相加
136. {
137. seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
138. if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数
139. }
140. seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
141. seccount+=(u32)hour*3600;//小时秒钟数
142. seccount+=(u32)min*60; //分钟秒钟数
143. seccount+=sec;//最后的秒钟加上去
144.
145. RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
146. PWR_BackupAccessCmd(ENABLE); //使能RTC和后备寄存器访问
147. RTC_SetCounter(seccount); //设置RTC计数器的值
148.
149. RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
150. return 0;
151.}
152.
153.
154.//初始化闹钟
155.//以1970年1月1日为基准
156.//1970~2099年为合法年份
157.//syear,smon,sday,hour,min,sec:闹钟的年月日时分秒
158.//返回值:0,成功;其他:错误代码.
159.u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
160.{
161. u16 t;
162. u32 seccount=0;
163. if(syear<1970||syear>2099)return 1;
164. for(t=1970;t<syear;t++) //把所有年份的秒钟相加
165. {
166. if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
167. else seccount+=31536000; //平年的秒钟数
168. }
169. smon-=1;
170. for(t=0;t<smon;t++) //把前面月份的秒钟数相加
171. {
172. seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
173. if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数
174. }
175. seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
176. seccount+=(u32)hour*3600;//小时秒钟数
177. seccount+=(u32)min*60; //分钟秒钟数
178. seccount+=sec;//最后的秒钟加上去
179. //设置时钟
180. RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
181. PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
182. //上面三步是必须的!
183.
184. RTC_SetAlarm(seccount);
185.
186. RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
187.
188. return 0;
189.}
190.
191.
192.//得到当前的时间
193.//返回值:0,成功;其他:错误代码.
194.u8 RTC_Get(void)
195.{
196. static u16 daycnt=0;
197. u32 timecount=0;
198. u32 temp=0;
199. u16 temp1=0;
200. timecount=RTC_GetCounter();
201. temp=timecount/86400; //得到天数(秒钟数对应的)
202. if(daycnt!=temp)//超过一天了
203. {
204. daycnt=temp;
205. temp1=1970; //从1970年开始
206. while(temp>=365)
207. {
208. if(Is_Leap_Year(temp1))//是闰年
209. {
210. if(temp>=366)temp-=366;//闰年的秒钟数
211. else {temp1++;break;}
212. }
213. else temp-=365; //平年
214. temp1++;
215. }
216. calendar.w_year=temp1;//得到年份
217. temp1=0;
218. while(temp>=28)//超过了一个月
219. {
220. if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
221. {
222. if(temp>=29)temp-=29;//闰年的秒钟数
223. else break;
224. }
225. else
226. {
227. if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
228. else break;
229. }
230. temp1++;
231. }
232. calendar.w_month=temp1+1; //得到月份
233. calendar.w_date=temp+1; //得到日期
234. }
235. temp=timecount%86400; //得到秒钟数
236. calendar.hour=temp/3600; //小时
237. calendar.min=(temp%3600)/60; //分钟
238. calendar.sec=(temp%3600)%60; //秒钟
239. calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期
240. return 0;
241.}
242.
243.
244.//获得现在是星期几
245.//功能描述:输入公历日期得到星期(只允许1901-2099年)
246.//输入参数:公历年月日
247.//返回值:星期号
248.u8 RTC_Get_Week(u16 year,u8 month,u8 day)
249.{
250. u16 temp2;
251. u8 yearH,yearL;
252.
253. yearH=year/100; yearL=year%100;
254. // 如果为21世纪,年份数加100
255. if (yearH>19)yearL+=100;
256. // 所过闰年数只算1900年之后的
257. temp2=yearL+yearL/4;
258. temp2=temp2%7;
259. temp2=temp2+day+table_week[month-1];
260. if (yearL%4==0&&month<3)temp2--;
261. return(temp2%7);
262.}
4.2.2 rtc.h
1.#ifndef __rtc_H
2.#define __rtc_H
3.
4.#include "system.h"
5.#include "SysTick.h"
6.#include "usart.h"
7.
8.//时间结构体
9.typedef struct
10.{
11. u8 hour;
12. u8 min;
13. u8 sec;
14.
15. //公历日月年周
16. u16 w_year;
17. u8 w_month;
18. u8 w_date;
19. u8 week;
20.}_calendar;
21.
22.extern _calendar calendar; //日历结构体
23.
24.u8 RTC_Init(void); //初始化RTC,返回0,失败;1,成功;
25.u8 Is_Leap_Year(u16 year);//平年,闰年判断
26.u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);
27.u8 RTC_Get(void); //更新时间
28.u8 RTC_Get_Week(u16 year,u8 month,u8 day);
29.u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//设置时间
30.
31.#endif
4.2.3 main.c
1./* Includes ------------------------------------------------------------------*/
2.#include "system.h"
3.#include "LED.h"
4.#include "SysTick.h"
5.#include "USART.h"
6.#include "rtc.h"
7.
8./* 主函数main() --------------------------------------------------------------*/
9.int main()
10.{
11. // 自定义变量
12. u8 i=0;
13.
14. // 初始化
15. SysTick_Iint(72);
16. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 中断分组
17. LED_Init();
18. USART1_Iint(115200);
19. RTC_Init();
20.
21. while(1)
22. {
23.
24. }
25.}