一:STM32RTC定时器知识:
RTC是个独立的定时器。RTC模块拥有一个连续计数的计数器,在相应的软件配置下,可以提供时钟日历的功能。修改计数器的值可以重新设置当前时间和日期。
RTC模块和时钟配置系统(RCC_BDCR寄存器)是在后备区域,即在系统复位或从待机模式唤醒后RTC的设置和时间维持不变。但是在系统复位后,会自动禁止访问后备寄存器和RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护。
二:RTC的特征如下:
可编程的预分频系数:分频系数最高为2
32位的可编程计数器,可用于较长时间段的测量。
2个分离的时钟:用于APB1接口的PCLK1和RTC时钟(RTC时钟的频率必须小于PCLK1时钟频率的四分之一以上)。
可以选择以下三种RTC的时钟源:
HSE时钟除以128:
LSE振荡器时钟;
LSI振荡器时钟(详见6.2.8节RTC时钟)。
2个独立的复位类型:
APB1接口由系统复位;
RTC核心(预分频器、闹钟、计数器和分频器)只能由后备域复位(详见6.1.3节)。
3个专门的可屏蔽中断:
闹钟中断,用来产生一个软件可编程的闹钟中断。
秒中断,用来产生一个可编程的周期性中断信号(最长可达1秒)。
溢出中断,指示内部可编程计数器溢出并回转为0的状态。
三:RTC的组成有两部分如下所示:
APB1接口:用来和APB1总线相连。通过APB1接口可以访问RTC的相关寄存器(预分频值,计数器值,闹钟值)。
RTC核心:由一组可编程计数器组成。分两个主要模块。
第一个是RTC预分频模块,它可以编程产生最长1秒的RTC时间基TR_CLK。如果设置了秒中断允许位,可以产生秒中断。
第二个是32位的可编程计数器,可被初始化为当前时间。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时间相比,当匹配时候如果设置了闹钟中断允许位,可以产生闹钟中断。
四: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寄存器的写操作完成
RTC_WaitForSynchro();//等待RTC寄存器同步
软件代码如下:
void RTC_Set(u16 Tmp_YY,u8 Tmp_MM,u8 Tmp_DD,u8 Tmp_hh,u8 Tmp_mm,u8 Tmp_ss); u8 Is_Leap_Year(u16 year); /******************************************************************************* * Function Name : RTC_Configuration * Description : Configures the RTC. * Input : None * Output : None * Return : None *******************************************************************************/ void RTC_Configuration(void) { int delay; /* Enable PWR and BKP clocks */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); /* Allow access to BKP Domain */ PWR_BackupAccessCmd(ENABLE); /* Reset Backup Domain */ BKP_DeInit(); RCC_LSEConfig(RCC_LSE_ON); do { /* delay about 10ms */ for(delay = 0;delay < 200000;delay++); /* Enable LSE */ }while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); /* Wait till LSE is ready */ /* Select LSE as RTC Clock Source */ RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); /* Enable RTC Clock */ RCC_RTCCLKCmd(ENABLE); /* Wait for RTC registers synchronization */ RTC_WaitForSynchro(); /* Wait until last write operation on RTC registers has finished */ RTC_WaitForLastTask(); /* Enable the RTC Second */ RTC_ITConfig(RTC_IT_SEC, ENABLE); /* Wait until last write operation on RTC registers has finished */ RTC_WaitForLastTask(); /* Set RTC prescaler: set RTC period to 1sec */ RTC_SetPrescaler(32767); /* RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1) */ /* Wait until last write operation on RTC registers has finished */ RTC_WaitForLastTask(); } /******************************************************************************* RTC_Init 实时时钟初始化 描述:通过读取备份寄存器的值,判定实时时钟是否进行已经初始化,否则进行初始化并置时间初值 本程序在主程序之前进行初始化 *******************************************************************************/ void RTC_Init(void) { if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) { /* Backup data register value is not correct or not yet programmed (when the first time the program is executed) */ /* RTC Configuration */ RTC_Configuration(); /* Adjust time by values entred by the user on the hyperterminal */ RTC_Set(2011,1,1,0,0,0); BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); } else { /* Check if the Power On Reset flag is set */ if(RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET) { } /* Check if the Pin Reset flag is set */ else if(RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET) { } RCC_RTCCLKCmd(ENABLE); //使能RTC时钟 /* Wait for RTC registers synchronization */ RTC_WaitForSynchro(); /* Enable the RTC Second */ RTC_ITConfig(RTC_IT_SEC, ENABLE); /* Wait until last write operation on RTC registers has finished */ RTC_WaitForLastTask(); } RCC_ClearFlag(); //清标志 } /******************************************************************************* Is_Leap_yesr 描述:判断输入的年份是闰年么 输入参数:year 返回参数:1闰年 0非闰年 月份天数-年份对应表 月份 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 ********************************************************************************/ u8 Is_Leap_Year(u16 year) { if(year%4 == 0) //闰年必须被4整除 { if(year%100 == 0) { if(year%400 == 0) //如果以00结尾,还必须被400整除 return 1; else return 0; } else return 1; } else return 0; } /******************************************************************************* * Function Name : Time_Regulate * Description : 把用户输入的日历时钟转换成秒值,写入RTC寄存器 * Input : hour min second * Output : None * Return : None 附加描述: 以时间1970.1.1为基准时间 以1970~2099为合法年份 *******************************************************************************/ const u8 mon_table[12] = {31,28,31,30,31,30,31,31,30,31,30,31}; //平年的月份日期表 void RTC_Set(u16 Tmp_YY,u8 Tmp_MM,u8 Tmp_DD,u8 Tmp_hh,u8 Tmp_mm,u8 Tmp_ss) { u16 t; u32 seccount =0; if((Tmp_YY < 2000) || (Tmp_YY > 2099)) { Tmp_YY = 2000; //年份的合法性判断 } if((Tmp_MM > 12) || (Tmp_MM < 1)) { Tmp_MM = 1; //月份的合法性判断 } if((Tmp_DD > 31) || (Tmp_DD < 1)) { Tmp_DD = 1; //日期的合法性判断 } if(Tmp_hh > 23) { Tmp_hh = 0; //小时的合法性判断 } if(Tmp_mm > 59) { Tmp_mm = 0; //分钟的合法性判断 } if(Tmp_ss > 59) { Tmp_ss = 0; //秒的合法性判断 } //1.计算从基准年份1970到目前设定值年份的秒钟数 for(t=1970;t<Tmp_YY;t++) { if(Is_Leap_Year(t)) seccount += 31622400; //闰年的秒数 else seccount += 31536000; //平年的秒数 } //2.把前面的月份的秒数相加 Tmp_MM-=1; for(t=0;t<Tmp_MM;t++) { seccount += (u32)mon_table[t]*86400; //月份秒钟数相加 if((Is_Leap_Year(Tmp_YY)) && (t ==1)) seccount += 86400; } //3.把日期,小时,分,秒的秒钟数加上去 seccount += (u32)(Tmp_DD-1)*86400; seccount += (u32)Tmp_hh*3600; seccount += (u32)Tmp_mm*60; seccount += (u32)Tmp_ss; //4.初始化 RTC 寄存器 RCC->APB1ENR |= 1<<28; //使能电源时钟 RCC->APB1ENR |= 1<<27; //使能备份时钟 PWR->CR |= 1<<8; //取消备份区写保护 PWR_BackupAccessCmd(ENABLE); RTC_WaitForLastTask(); RTC_SetCounter(seccount); RTC_WaitForLastTask(); } /********************************************************************** 获取实时时钟日历值 输入参数: 返回值:将日历写入Calendar的起始地址,共6个字节 将时间写入CurrTime的起始地址,共6个字节 **********************************************************************/ void RTC_Get(u16 *Calendar,u16 *CurrTime) { u16 year,month,day,hour,min,second; volatile static u16 daycnt; u32 timecount = 0; u32 temp = 0; u32 temp1 = 0; timecount = RTC_GetCounter(); //读取实时时钟 temp = timecount / 86400; if(daycnt != temp) //天数发生了变化,则重新计算日历 { daycnt = temp; temp1 = 1970; //起始基准年份 while(temp >= 365) { if(Is_Leap_Year(temp1)) { if(temp >= 366) temp -= 366; else { // temp1 ++; //该处代码需要去掉,否到导致闰年的最后一天显示错误 break; } } else temp -= 365; temp1++; } year = temp1; //得到年份 temp1 = 0; while(temp >= 28) { if((Is_Leap_Year(year)) && (temp1==1)) { if(temp >= 29) temp -= 29; else break; } else { if(temp >= mon_table[temp1]) temp -= mon_table[temp1]; else break; } temp1 ++; } month = temp1+1; //得到月份 day = temp+1; //得到日期 *Calendar = year;Calendar++; *Calendar = month;Calendar++; *Calendar = day; } temp = timecount%86400; //得到秒钟数 hour = temp/3600; //得到小时 temp1 = temp%3600; min = temp1/60; //得到分钟 second = temp1%60; //得到秒钟 *CurrTime = hour;CurrTime++; *CurrTime = min;CurrTime++; *CurrTime = second; }
如上述代码所示,该RTC代码在很多例程中都使用该部分代码;该读取RTC的代码计算有个问题,导致闰年的最后一天显示错误哦,
while(temp >= 365) { if(Is_Leap_Year(temp1)) { if(temp >= 366) temp -= 366; else { // temp1 ++; break; }
如上述所示,需要将temp1代码屏蔽掉,否则在闰年的最后一天显示错误,例如:2024-12-31 会显示成2025-13-1 ;