"RTC"是Real Time Clock 的简称,意为实时时钟。stm32提供了一个秒中断源和一个闹钟中断源。
RTC的技术器是一个32位的计数器,使用32.768khz的外部晶振。
2038年问题
在计算机应用上,2038年问题可能会导致某些软件在2038年无法正常工作。所有使用UNIX时间表示时间的程序都将受其影响,因为它们以自1970年1月1日经过的秒数(忽略闰秒)来表示时间。这种时间表示法在类Unix(Unix-like)操作系统上是一个标准,并会影响以其C编程语言开发给其他大部份操作系统使用的软件。
在大部份的32位操作系统上,此“time_t”数据模式使用一个有正负号的32位元整数(signedint32)存储计算的秒数。也就是说最大可以计数的秒数为 2^31次方 可以算得:
2^31/3600/24/365 ≈ 68年
所以依照此“time_t”标准,在此格式能被表示的最后时间是2038年1月19日03:14:07,星期二(UTC)。超过此一瞬间,时间将会被掩盖(wrap around)且在内部被表示为一个负数,并造成程序无法工作,因为它们无法将此时间识别为2038年,而可能会依个别实作而跳回1970年或1901年。
对于PC机来说,时间开始于1980年1月1日,并以无正负符号的32位整数的形式按秒递增,这与UNIX时间非常类似。可以算得:
2^32/3600/24/365 ≈ 136年
到2116年,这个整数将溢出。
Windows NT使用64位整数来计时。但是,它使用100纳秒作为增量单位,且时间开始于1601年1月1日,所以NT将遇到2184年问题。
苹果公司声明,Mac在29,940年之前不会出现时间问题!
由于RTC是一个32位计数器,同样其计时时间是有限的。库函数中使用到了C标准时间库,时间库中的计时起始时间是1900年,可以知道时间库中不是用 有符号位的32位整数来表示时间的,否则在1968年就已经溢出了。如果用32位无符号整数计时,其溢出时间为2036年左右,所以会遇到这个问题。
直接操作寄存器中,可以自由设定这个时间戳起始的年份,RTC的32位寄存器存储的只是距离这个起始年份的总秒数,所以不会遇到这个问题。而且可以用无符号32位的二进制表示时间,这意味着此类系统的时间戳可以表示更多的秒数。但是由于其使用32位寄存器表示秒数,最大只能计时到136年后。
本例实现使用stm32每秒输出一次当前的时间,并设置一个闹钟,到时间时输出提醒信息。
直接操作寄存器
RTC实时时钟的操作原则是 在每次读写前都要保证上一次读写完成。
代码较多,使用到的寄存器请参见手册 (system.h 和 stm32f10x_it.h 等相关代码参照 )
User/main.c
11
|
extern const u8* Week_Table[7];
|
21
|
Nvic_Init(0,0,RTC_IRQChannel,0); //设置中断
|
28
|
//Rtc_TIME_AutoSet(); //将当前编译时间作为RTC开始时间
|
29
|
Rtc_TIME_Set(2012,7,7,20,50,0); //设定开始时间 参数说明:年,月,日,时,分,秒
|
31
|
Rtc_ALARM_Set(2012,7,7,20,50,30); //设定闹钟事件时间
|
41
|
RCC->APB2ENR|=1<<2; //使能PORTA时钟
|
43
|
GPIOA->CRL&=0x0000FFFF; // PA0~3设置为浮空输入,PA4~7设置为推挽输出
|
44
|
GPIOA->CRL|=0x33334444;
|
48
|
GPIOA -> CRH&=0xFFFFF00F; //设置USART1 的Tx(PA.9)为第二功能推挽,50MHz;Rx(PA.10)为浮空输入
|
49
|
GPIOA -> CRH|=0x000008B0;
|
User/stm32f103x_it.c
01
|
#include "stm32f10x_it.h"
|
11
|
//extern void Wwdg_Feed(void);
|
12
|
//extern u16 Read_Bkp(u8 reg);
|
13
|
extern void Rtc_Get(void);
|
14
|
extern const u8* Week_Table[7];
|
16
|
void RTC_IRQHandler(void)
|
19
|
if(RTC->CRL&0x0001) //秒钟中断
|
24
|
printf("\r\n Time : %d - %d - %d,%d : %d : %d ,Today is %s \r\n",
|
37
|
Week_Table[timer.week]
|
41
|
if(RTC->CRL&0x0002) //闹钟中断
|
45
|
printf("\r\nIt's time to do sth.\r\n");
|
47
|
RTC->CRL &= ~(0x0002); //清除闹钟中断
|
51
|
RTC->CRL &= 0x0FFA; //清除溢出,秒钟中断
|
53
|
while(!(RTC->CRL &(1<<5))); //等待RTC寄存器操作完成
|
Library/src/rtc.c
005
|
tm timer; //定义时钟结构体,主函数直接可以调用此结构体读出时间
|
008
|
const u8 Days_Table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
|
009
|
const u8 Month_Table[12][3]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
|
011
|
const u8* Week_Table[7]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
|
014
|
u8 const _Week[12]={0,3,3,6,1,4,6,2,5,0,3,5};
|
018
|
RCC->APB1ENR |= 1<<28; //使能PWR时钟
|
019
|
RCC->APB1ENR |= 1<<27; //使能BKP时钟,RTC校准在BKP相关寄存器中
|
020
|
PWR->CR |= 1<<8; //取消BKP相关寄存器写保护
|
022
|
//RCC->BDCR |= 1<<16; //备份区域软复位
|
023
|
//RCC->BDCR |= ~(1<<16); //备份区域软复位结束
|
025
|
RCC->BDCR |= 1<<0; //外部低速时钟(LSE)使能
|
027
|
while(!(RCC->BDCR & 0x02)); //等待外部时钟就绪
|
029
|
RCC->BDCR |= 1<<8; //LSE作为RTC时钟
|
030
|
RCC->BDCR |= 1<<15; //RTC时钟使能
|
032
|
while(!(RTC->CRL & (1<<5))); //等待RTC寄存器最后一次操作完成
|
033
|
while(!(RTC->CRL & (1<<3))); //等待RTC寄存器同步完成
|
035
|
RTC->CRH |= 0x07; //允许溢出中断[2],闹钟中断[1],秒中断[0],CRH寄存器低三位有效
|
037
|
while(!(RTC->CRL & (1<<5))); //等待RTC寄存器最后一次操作完成
|
039
|
RTC->CRL |= 1<<4; //进入配置模式
|
041
|
RTC->PRLL = 32767; //设定分频值
|
043
|
//Rtc_TIME_AutoSet(); //将当前编译时间写入寄存器
|
044
|
//Rtc_TIME_Set(2012,7,7,20,50,0); //年,月,日,时,分,秒
|
046
|
RTC->CRL &= ~(1<<4); //退出配置模式,开始更新RTC寄存器
|
047
|
while(!(RTC->CRL & (1<<5))); //等待RTC寄存器最后一次操作完成
|
053
|
void Rtc_TIME_Set(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second)
|
057
|
sec = Date_TO_Sec(year,month,date,hour,minute,second);
|
059
|
//printf("\nRtc TIME Set Sec = %x\n",sec);
|
061
|
RCC->APB1ENR |= 1<<28; //使能PWR时钟,方便独立调用此函数
|
062
|
RCC->APB1ENR |= 1<<27; //使能BKP时钟
|
063
|
PWR->CR |= 1<<8; //取消写保护
|
065
|
RTC-> CRL |= 1<<4; //允许配置
|
067
|
RTC-> CNTL = sec&0xffff; //取低16位
|
068
|
RTC-> CNTH = sec>>16; //取高16位
|
070
|
RTC-> CRL &= ~(1<<4); //开始RTC寄存器更新
|
072
|
while(!(RTC->CRL&(1<<5))); //等待RTC寄存器操作完成
|
081
|
// 普通年能整除4且不能整除100的为闰年。(如2004年就是闰年,1900年不是闰年)
|
082
|
// 世纪年能整除400的是闰年。(如2000年是闰年,1900年不是闰年)
|
085
|
u8 Is_LeapYear(u16 year)
|
087
|
if(year%4==0) //必须能被4整除
|
092
|
return 1; //如果以00结尾,还要能被400整除
|
104
|
//将时间转化为到1970年1月1日的总秒数
|
105
|
//Bugs:此函数秒数会多20左右,所以函数返回值做了校正,校正后没有问题
|
107
|
u32 Date_TO_Sec(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second)
|
112
|
if(year >= 1970 && year<= 2106) //判断是否为合法年份,RTC的时间是从1970开始,只能由32位表示秒数,最大只能到2106年左右
|
116
|
if(Is_LeapYear(t)) //判断是否为闰年
|
126
|
sec += (u32) Days_Table[t]*86400;
|
127
|
if(Is_LeapYear(year) && t== 1) //闰年加一天的秒钟数
|
132
|
sec += (u32)(date-1)*86400; //本月日期的秒数累加
|
133
|
sec += (u32)(hour)*3600;
|
134
|
sec += (u32)(minute)*60;
|
138
|
return sec-20; //校正20秒,原因不详
|
146
|
//__DATE__ 获取编译日期, 格式为: Jul 7 2012
|
147
|
//__TIME__ 获取编译时间, 格式为: 14:54:44
|
149
|
void Rtc_TIME_AutoSet()
|
152
|
u8 mon,date,sec,min,hour;
|
154
|
u8 *_date = __DATE__;
|
155
|
u8 *_time = __TIME__;
|
161
|
if(Month_Table[i][j] == _date[j]) mon = i; //得到月份
|
166
|
if(_date[4]==' '){ //得到日期
|
167
|
date=_date[5]-'0'; //-'0'操作将字符型转换为整型,参考ASCII码的转换,eg '7'-'0' =7
|
169
|
date=10*(_date[4]-'0')+_date[5]-'0';
|
173
|
year=1000*(_date[7]-'0')+100*(_date[8]-'0')+10*(_date[9]-'0')+_date[10]-'0'; //得到年份
|
174
|
hour=10*(_time[0]-'0')+_time[1]-'0'; //得到小时
|
175
|
min=10*(_time[3]-'0')+_time[4]-'0';
|
176
|
sec=10*(_time[6]-'0')+_time[7]-'0';
|
179
|
//printf("\n%d-%d-%d %d:%d:%d\n",year,mon,date,hour,min,sec);
|
181
|
Rtc_TIME_Set(year,mon,date,hour,min,sec);
|
188
|
u32 secs,days,temp,years = 1970,months = 0;
|
190
|
secs = RTC->CNTH; //读取RTC的当前时间值(距1970年的总秒数)
|
194
|
//printf("\nRtc_Get Sec = %x\n",secs);
|
202
|
if(Is_LeapYear(years)) //是闰年
|
214
|
timer.year = years; //得到年份
|
218
|
if(Is_LeapYear(years) && months ==1) //判断是否为闰年的第二月
|
225
|
if(temp >= Days_Table[months])
|
226
|
temp -= Days_Table[months];
|
234
|
timer.month = months+1; //得到月数
|
235
|
timer.date = temp+1; //得到日期
|
238
|
temp = secs % 86400; //得到剩余秒数
|
239
|
timer.hour = temp/3600; //得到小时
|
240
|
timer.minute = (temp%3600)/60;
|
241
|
timer.second = (temp%3600)%60;
|
242
|
timer.week = Rtc_DAY_Get(timer.year,timer.month,timer.date);
|
249
|
u8 Rtc_DAY_Get(u16 year,u8 month,u8 day)
|
258
|
if( yearH > 19 ) yearL += 100;
|
262
|
temp = yearL+yearL/4;
|
264
|
temp = temp + day + _Week[month-1];
|
266
|
if( yearL%4 == 0 && month < 3 ) temp--;
|
273
|
void Rtc_ALARM_Set(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second)
|
278
|
sec = Date_TO_Sec(year,month,date,hour,minute,second);
|
281
|
RTC-> CRL |= 1<<4; //允许配置
|
283
|
//while(!(RTC->CRL&(1<<5))); //RTOFF为1 才可以写入ALRL和ALRH寄存器
|
285
|
RTC-> ALRL = sec&0xffff; //取低16位
|
286
|
RTC-> ALRH = sec>>16; //取高16位
|
288
|
RTC-> CRL &= ~(1<<4); //开始RTC寄存器更新
|
290
|
while(!(RTC->CRL&(1<<5))); //等待RTC寄存器操作完成
|
Library/inc/rtc.h
18
|
void Rtc_TIME_Set(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second);
|
19
|
u8 Is_LeapYear(u16 year);
|
20
|
u32 Date_TO_Sec(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second);
|
21
|
void Rtc_TIME_AutoSet(void);
|
23
|
void Rtc_ALARM_Set(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second);
|
24
|
u8 Rtc_DAY_Get(u16 year,u8 month,u8 day);
|
这里用到了MDK的两个关键字 __DATE__ 和 __TIME__获得当前编译的日期和时间,详见代码注释
库函数操作
ANSI C语言所提供的time.h的头文件中关于unix时间戳是从1900年开始的,和直接操作寄存器不同,所以如果unix时间戳中读出年份为100,则正确年份为1900+100=2000
代码如下:
main.c
001
|
#include "stm32f10x.h"
|
007
|
void RCC_Configuration(void);
|
008
|
void GPIO_Configuration(void);
|
009
|
void NVIC_Configuration(void);
|
010
|
void USART_Configuration(void);
|
011
|
void RTC_Configuration(void);
|
015
|
void SetAlarm(struct tm t);
|
016
|
void SetCalendarTime(struct tm t);
|
017
|
void SetUnixTime(time_t);
|
018
|
struct tm ConvUnixToCalendar(time_t t);
|
019
|
u32 ConvCalendarToUnix(struct tm t);
|
020
|
u32 GetUnixTime(void);
|
025
|
struct tm CurrentTime = {0,30,10,11,4,2011};
|
026
|
struct tm AlarmTime = {5,30,10,11,4,2011};
|
032
|
GPIO_Configuration();
|
033
|
NVIC_Configuration();
|
034
|
USART_Configuration();
|
037
|
SetCalendarTime(CurrentTime);
|
039
|
while(1){ TimeShow(); }
|
047
|
Time = GetUnixTime();
|
048
|
CurrentTime = ConvUnixToCalendar(Time);
|
050
|
printf("\r\n Time : %d - %d - %d,%d : %d : %d \r\n",
|
061
|
void SetCalendarTime(struct tm t)
|
063
|
SetUnixTime(ConvCalendarToUnix(t));
|
066
|
void SetUnixTime(time_t t)
|
068
|
RTC_WaitForLastTask();
|
069
|
RTC_SetCounter((u32)t);
|
070
|
RTC_WaitForLastTask();
|
073
|
void SetAlarm(struct tm t)
|
075
|
RTC_WaitForLastTask();
|
076
|
RTC_SetAlarm(ConvCalendarToUnix(t));
|
077
|
RTC_WaitForLastTask();
|
080
|
u32 GetUnixTime(void)
|
082
|
return (u32)RTC_GetCounter();
|
085
|
u32 ConvCalendarToUnix(struct tm t)
|
091
|
struct tm ConvUnixToCalendar(time_t t)
|
094
|
t_tm = localtime(&t);
|
095
|
t_tm->tm_year += 1900;
|
100
|
void GPIO_Configuration(void)
|
102
|
GPIO_InitTypeDef GPIO_InitStructure;
|
103
|
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
|
104
|
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
|
105
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
|
106
|
GPIO_Init(GPIOA , &GPIO_InitStructure);
|
108
|
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
|
109
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
|
110
|
GPIO_Init(GPIOA , &GPIO_InitStructure);
|
113
|
void RTC_Configuration(void)
|
115
|
PWR_BackupAccessCmd(ENABLE);
|
117
|
RCC_LSEConfig(RCC_LSE_ON);
|
118
|
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
|
119
|
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
|
120
|
RCC_RTCCLKCmd(ENABLE);
|
122
|
RTC_WaitForSynchro();
|
123
|
RTC_WaitForLastTask();
|
125
|
RTC_ITConfig(RTC_IT_SEC|RTC_IT_ALR,ENABLE);
|
126
|
RTC_WaitForLastTask();
|
128
|
RTC_SetPrescaler(32767);
|
129
|
RTC_WaitForLastTask();
|
133
|
void RCC_Configuration(void)
|
135
|
/* 定义枚举类型变量 HSEStartUpStatus */
|
136
|
ErrorStatus HSEStartUpStatus;
|
141
|
RCC_HSEConfig(RCC_HSE_ON);
|
143
|
HSEStartUpStatus = RCC_WaitForHSEStartUp();
|
144
|
/* 判断HSE起是否振成功,是则进入if()内部 */
|
145
|
if(HSEStartUpStatus == SUCCESS)
|
147
|
/* 选择HCLK(AHB)时钟源为SYSCLK 1分频 */
|
148
|
RCC_HCLKConfig(RCC_SYSCLK_Div1);
|
149
|
/* 选择PCLK2时钟源为 HCLK(AHB) 1分频 */
|
150
|
RCC_PCLK2Config(RCC_HCLK_Div1);
|
151
|
/* 选择PCLK1时钟源为 HCLK(AHB) 2分频 */
|
152
|
RCC_PCLK1Config(RCC_HCLK_Div2);
|
154
|
FLASH_SetLatency(FLASH_Latency_2);
|
156
|
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
|
157
|
/* 选择锁相环(PLL)时钟源为HSE 1分频,倍频数为9,则PLL输出频率为 8MHz * 9 = 72MHz */
|
158
|
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
|
162
|
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
|
163
|
/* 选择SYSCLK时钟源为PLL */
|
164
|
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
|
165
|
/* 等待PLL成为SYSCLK时钟源 */
|
166
|
while(RCC_GetSYSCLKSource() != 0x08);
|
168
|
/* 打开APB2总线上的GPIOA时钟*/
|
169
|
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1, ENABLE);
|
171
|
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP, ENABLE);
|
176
|
void USART_Configuration(void)
|
178
|
USART_InitTypeDef USART_InitStructure;
|
179
|
USART_ClockInitTypeDef USART_ClockInitStructure;
|
181
|
USART_ClockInitStructure.USART_Clock = USART_Clock_Disable;
|
182
|
USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low;
|
183
|
USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge;
|
184
|
USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable;
|
185
|
USART_ClockInit(USART1 , &USART_ClockInitStructure);
|
187
|
USART_InitStructure.USART_BaudRate = 9600;
|
188
|
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
|
189
|
USART_InitStructure.USART_StopBits = USART_StopBits_1;
|
190
|
USART_InitStructure.USART_Parity = USART_Parity_No;
|
191
|
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
|
192
|
USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
|
193
|
USART_Init(USART1,&USART_InitStructure);
|
195
|
USART_Cmd(USART1,ENABLE);
|
199
|
void NVIC_Configuration(void)
|
201
|
NVIC_InitTypeDef NVIC_InitStructure;
|
203
|
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
|
204
|
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
|
205
|
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
|
206
|
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
|
207
|
NVIC_Init(&NVIC_InitStructure);
|
217
|
int fputc(int ch,FILE *f)
|
219
|
USART_SendData(USART1,(u8) ch);
|
220
|
while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET);
|
stm32f10x_it.c:
01
|
#include "stm32f10x_it.h"
|
07
|
void RTC_IRQHandler(void)
|
09
|
if(RTC_GetFlagStatus(RTC_FLAG_ALR) != RESET){
|
10
|
printf("\r\nIt's time to do sth.\r\n");
|
16
|
RTC_ClearITPendingBit(RTC_IT_ALR|RTC_IT_SEC);
|
阅读(2624) | 评论(0) | 转发(0) |