"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 等相关代码参照 stm32 直接操作寄存器开发环境配置)
User/main.c
#include#include "system.h" #include "usart.h" #include "rtc.h" #define LED1 PAout(4) #define LED2 PAout(5) void Gpio_Init(void); extern const u8* Week_Table[7]; int main(void) { Rcc_Init(9); //系统时钟设置 Usart1_Init(72,9600); Nvic_Init(0,0,RTC_IRQChannel,0); //设置中断 Gpio_Init(); Rtc_Init(); //Rtc_TIME_AutoSet(); //将当前编译时间作为RTC开始时间 Rtc_TIME_Set(2012,7,7,20,50,0); //设定开始时间 参数说明:年,月,日,时,分,秒 Rtc_ALARM_Set(2012,7,7,20,50,30); //设定闹钟事件时间 LED1 = 1; while(1); } void Gpio_Init(void) { RCC->APB2ENR|=1<<2; //使能PORTA时钟 GPIOA->CRL&=0x0000FFFF; // PA0~3设置为浮空输入,PA4~7设置为推挽输出 GPIOA->CRL|=0x33334444; //USART1 串口I/O设置 GPIOA -> CRH&=0xFFFFF00F; //设置USART1 的Tx(PA.9)为第二功能推挽,50MHz;Rx(PA.10)为浮空输入 GPIOA -> CRH|=0x000008B0; }
User/stm32f103x_it.c
#include "stm32f10x_it.h" #include "system.h" #include "stdio.h" #include "rtc.h" #define LED1 PAout(4) #define LED2 PAout(5) #define LED3 PAout(6) #define LED4 PAout(7) //extern void Wwdg_Feed(void); //extern u16 Read_Bkp(u8 reg); extern void Rtc_Get(void); extern const u8* Week_Table[7]; void RTC_IRQHandler(void) { if(RTC->CRL&0x0001) //秒钟中断 { LED4 = !LED4; Rtc_Get(); printf("\r\n Time : %d - %d - %d,%d : %d : %d ,Today is %s \r\n", timer.year, timer.month, timer.date, timer.hour, timer.minute, timer.second, Week_Table[timer.week] ); } if(RTC->CRL&0x0002) //闹钟中断 { LED3 = 1; printf("\r\nIt's time to do sth.\r\n"); RTC->CRL &= ~(0x0002); //清除闹钟中断 } RTC->CRL &= 0x0FFA; //清除溢出,秒钟中断 while(!(RTC->CRL &(1<<5))); //等待RTC寄存器操作完成 }
Library/src/rtc.c
#include#include "rtc.h" #include "stdio.h" tm timer; //定义时钟结构体,主函数直接可以调用此结构体读出时间 //平年的月份日期表,月份缩写表 const u8 Days_Table[12]={31,28,31,30,31,30,31,31,30,31,30,31}; const u8 Month_Table[12][3]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; const u8* Week_Table[7]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}; //月修正数据表 u8 const _Week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; void Rtc_Init(void) { RCC->APB1ENR |= 1<<28; //使能PWR时钟 RCC->APB1ENR |= 1<<27; //使能BKP时钟,RTC校准在BKP相关寄存器中 PWR->CR |= 1<<8; //取消BKP相关寄存器写保护 //RCC->BDCR |= 1<<16; //备份区域软复位 //RCC->BDCR |= ~(1<<16); //备份区域软复位结束 RCC->BDCR |= 1<<0; //外部低速时钟(LSE)使能 while(!(RCC->BDCR & 0x02)); //等待外部时钟就绪 RCC->BDCR |= 1<<8; //LSE作为RTC时钟 RCC->BDCR |= 1<<15; //RTC时钟使能 while(!(RTC->CRL & (1<<5))); //等待RTC寄存器最后一次操作完成 while(!(RTC->CRL & (1<<3))); //等待RTC寄存器同步完成 RTC->CRH |= 0x07; //允许溢出中断[2],闹钟中断[1],秒中断[0],CRH寄存器低三位有效 while(!(RTC->CRL & (1<<5))); //等待RTC寄存器最后一次操作完成 RTC->CRL |= 1<<4; //进入配置模式 RTC->PRLH = 0x0000; RTC->PRLL = 32767; //设定分频值 //Rtc_TIME_AutoSet(); //将当前编译时间写入寄存器 //Rtc_TIME_Set(2012,7,7,20,50,0); //年,月,日,时,分,秒 RTC->CRL &= ~(1<<4); //退出配置模式,开始更新RTC寄存器 while(!(RTC->CRL & (1<<5))); //等待RTC寄存器最后一次操作完成 } //设定RTC开始计时时间 void Rtc_TIME_Set(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second) { u32 sec; sec = Date_TO_Sec(year,month,date,hour,minute,second); //printf("\nRtc TIME Set Sec = %x\n",sec); RCC->APB1ENR |= 1<<28; //使能PWR时钟,方便独立调用此函数 RCC->APB1ENR |= 1<<27; //使能BKP时钟 PWR->CR |= 1<<8; //取消写保护 RTC-> CRL |= 1<<4; //允许配置 RTC-> CNTL = sec&0xffff; //取低16位 RTC-> CNTH = sec>>16; //取高16位 RTC-> CRL &= ~(1<<4); //开始RTC寄存器更新 while(!(RTC->CRL&(1<<5))); //等待RTC寄存器操作完成 } //判断是否是闰年函数 // //判断方法: // 普通年能整除4且不能整除100的为闰年。(如2004年就是闰年,1900年不是闰年) // 世纪年能整除400的是闰年。(如2000年是闰年,1900年不是闰年) // //返回: 1,是闰年 0,不是闰年 u8 Is_LeapYear(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日的总秒数 //Bugs:此函数秒数会多20左右,所以函数返回值做了校正,校正后没有问题 //待优化 u32 Date_TO_Sec(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second) { u16 t; u32 sec; if(year >= 1970 && year<= 2106) //判断是否为合法年份,RTC的时间是从1970开始,只能由32位表示秒数,最大只能到2106年左右 { for(t= 1970 ;t CNTH; //读取RTC的当前时间值(距1970年的总秒数) secs <<= 16; secs += RTC->CNTL; //printf("\nRtc_Get Sec = %x\n",secs); days = secs/86400; if(days > 0) //超过一天 { temp = days; while(temp >= 365) { if(Is_LeapYear(years)) //是闰年 { if(temp >= 366) temp -= 366; //闰年的天数 else break; }else{ temp -= 365; } years++; } timer.year = years; //得到年份 while(days >= 28) { if(Is_LeapYear(years) && months ==1) //判断是否为闰年的第二月 { if(temp >= 29) temp -= 29; else break; }else{ if(temp >= Days_Table[months]) temp -= Days_Table[months]; else break; } months++; } timer.month = months+1; //得到月数 timer.date = temp+1; //得到日期 } temp = secs % 86400; //得到剩余秒数 timer.hour = temp/3600; //得到小时 timer.minute = (temp%3600)/60; timer.second = (temp%3600)%60; timer.week = Rtc_DAY_Get(timer.year,timer.month,timer.date); } //判断当前为星期几 u8 Rtc_DAY_Get(u16 year,u8 month,u8 day) { u16 temp; u8 yearH,yearL; yearH = year/100; yearL = year%100; // 如果为21世纪,年份数加100 if( yearH > 19 ) yearL += 100; // 所过闰年数只算1900年之后的 temp = yearL+yearL/4; temp = temp%7; temp = temp + day + _Week[month-1]; if( yearL%4 == 0 && month < 3 ) temp--; return(temp%7); } //设定闹钟时间 void Rtc_ALARM_Set(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second) { u32 sec; sec = Date_TO_Sec(year,month,date,hour,minute,second); RTC-> CRL |= 1<<4; //允许配置 //while(!(RTC->CRL&(1<<5))); //RTOFF为1 才可以写入ALRL和ALRH寄存器 RTC-> ALRL = sec&0xffff; //取低16位 RTC-> ALRH = sec>>16; //取高16位 RTC-> CRL &= ~(1<<4); //开始RTC寄存器更新 while(!(RTC->CRL&(1<<5))); //等待RTC寄存器操作完成 }
Library/inc/rtc.h
#includetypedef struct { u8 hour; u8 minute; u8 second; u16 year; u8 month; u8 date; u8 week; }tm; extern tm timer; void Rtc_Init(void); void Rtc_TIME_Set(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second); u8 Is_LeapYear(u16 year); u32 Date_TO_Sec(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second); void Rtc_TIME_AutoSet(void); void Rtc_Get(void); void Rtc_ALARM_Set(u16 year,u8 month,u8 date,u8 hour,u8 minute, u8 second); 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
#include "stm32f10x.h" #include "stdio.h" #include "time.h" #define PRINTF_ON 1 void RCC_Configuration(void); void GPIO_Configuration(void); void NVIC_Configuration(void); void USART_Configuration(void); void RTC_Configuration(void); void TimeShow(void); void SetAlarm(struct tm t); void SetCalendarTime(struct tm t); void SetUnixTime(time_t); struct tm ConvUnixToCalendar(time_t t); u32 ConvCalendarToUnix(struct tm t); u32 GetUnixTime(void); vu32 Display; struct tm CurrentTime = {0,30,10,11,4,2011}; struct tm AlarmTime = {5,30,10,11,4,2011}; int main(void) { RCC_Configuration(); GPIO_Configuration(); NVIC_Configuration(); USART_Configuration(); RTC_Configuration(); SetCalendarTime(CurrentTime); SetAlarm(AlarmTime); while(1){ TimeShow(); } } void TimeShow(void) { u32 Time = 0; if(Display) { Time = GetUnixTime(); CurrentTime = ConvUnixToCalendar(Time); printf("\r\n Time : %d - %d - %d,%d : %d : %d \r\n", CurrentTime.tm_year, CurrentTime.tm_mon, CurrentTime.tm_mday, CurrentTime.tm_hour, CurrentTime.tm_min, CurrentTime.tm_sec); Display = 0; } } void SetCalendarTime(struct tm t) { SetUnixTime(ConvCalendarToUnix(t)); } void SetUnixTime(time_t t) { RTC_WaitForLastTask(); RTC_SetCounter((u32)t); RTC_WaitForLastTask(); } void SetAlarm(struct tm t) { RTC_WaitForLastTask(); RTC_SetAlarm(ConvCalendarToUnix(t)); RTC_WaitForLastTask(); } u32 GetUnixTime(void) { return (u32)RTC_GetCounter(); } u32 ConvCalendarToUnix(struct tm t) { t.tm_year -=1900; return mktime(&t); } struct tm ConvUnixToCalendar(time_t t) { struct tm *t_tm; t_tm = localtime(&t); t_tm->tm_year += 1900; return *t_tm; } void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA , &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA , &GPIO_InitStructure); } void RTC_Configuration(void) { PWR_BackupAccessCmd(ENABLE); BKP_DeInit(); RCC_LSEConfig(RCC_LSE_ON); while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE); RTC_WaitForSynchro(); RTC_WaitForLastTask(); RTC_ITConfig(RTC_IT_SEC|RTC_IT_ALR,ENABLE); RTC_WaitForLastTask(); RTC_SetPrescaler(32767); RTC_WaitForLastTask(); } void RCC_Configuration(void) { /* 定义枚举类型变量 HSEStartUpStatus */ ErrorStatus HSEStartUpStatus; /* 复位系统时钟设置*/ RCC_DeInit(); /* 开启HSE*/ RCC_HSEConfig(RCC_HSE_ON); /* 等待HSE起振并稳定*/ HSEStartUpStatus = RCC_WaitForHSEStartUp(); /* 判断HSE起是否振成功,是则进入if()内部 */ if(HSEStartUpStatus == SUCCESS) { /* 选择HCLK(AHB)时钟源为SYSCLK 1分频 */ RCC_HCLKConfig(RCC_SYSCLK_Div1); /* 选择PCLK2时钟源为 HCLK(AHB) 1分频 */ RCC_PCLK2Config(RCC_HCLK_Div1); /* 选择PCLK1时钟源为 HCLK(AHB) 2分频 */ RCC_PCLK1Config(RCC_HCLK_Div2); /* 设置FLASH延时周期数为2 */ FLASH_SetLatency(FLASH_Latency_2); /* 使能FLASH预取缓存 */ FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); /* 选择锁相环(PLL)时钟源为HSE 1分频,倍频数为9,则PLL输出频率为 8MHz * 9 = 72MHz */ RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); /* 使能PLL */ RCC_PLLCmd(ENABLE); /* 等待PLL输出稳定 */ while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); /* 选择SYSCLK时钟源为PLL */ RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); /* 等待PLL成为SYSCLK时钟源 */ while(RCC_GetSYSCLKSource() != 0x08); } /* 打开APB2总线上的GPIOA时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP, ENABLE); } void USART_Configuration(void) { USART_InitTypeDef USART_InitStructure; USART_ClockInitTypeDef USART_ClockInitStructure; USART_ClockInitStructure.USART_Clock = USART_Clock_Disable; USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low; USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge; USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable; USART_ClockInit(USART1 , &USART_ClockInitStructure); USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx; USART_Init(USART1,&USART_InitStructure); USART_Cmd(USART1,ENABLE); } void NVIC_Configuration(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } #if PRINTF_ON int fputc(int ch,FILE *f) { USART_SendData(USART1,(u8) ch); while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET); return ch; } #endif
stm32f10x_it.c:
#include "stm32f10x_it.h" #include "stdio.h" extern vu32 Display; void RTC_IRQHandler(void) { if(RTC_GetFlagStatus(RTC_FLAG_ALR) != RESET){ printf("\r\nIt's time to do sth.\r\n"); }else{ Display =1 ; } RTC_ClearITPendingBit(RTC_IT_ALR|RTC_IT_SEC); }
上一篇:stm32 BKP寄存器操作[操作寄存器+库函数]
下一篇:stm32 i2c通信 [操作寄存器+库函数]
推荐阅读最新更新时间:2024-03-16 15:32