STM32学习100步之第三十九-四十步——RTC实时时钟

RTC介绍

RTC和后备寄存器通过一个开关供电,在Voo有效时该开关选择Voo供电,否则由VBAT引脚供电。后备寄存器(10个16位的寄存器)可以用于在关闭Voo时,保存20个字节的用户应用数据。RTC和后备寄存器不会被系统或电源复位源复位;当从待机模式唤醒时,也不会被复位。
实时时钟具有–组连续运行的计数器,可以通过适当的软件提供日历时钟功能,还具有闹钟中断和阶段性中断功能。RTC的驱动时钟可以是一个使用外部晶体的32.768kHz的振荡器、内部低功耗RC振荡器或高速的外部时钟经128分频。内部低功耗RC振荡器的典型频率为40kHz。为补偿天然晶体的偏差,可以通过输出一个512Hz的信号对RTC的时钟进行校准。RTC具有一个32位的可编程计数器,使用比较寄存器可以进行长时间的测量。有一“个20位的预分频器用于时基时钟,默认情况下时钟为32.768kHz时,它将产生一一个1秒长的时间基准。

RTC初始化过程

有如下几种函数类型

void RTC_First_Config(void);//首次启用RTC的设置
void RTC_Config(void);//实时时钟初始化
u8 Is_Leap_Year(u16 year);//判断是否是闰年函数                    
u8 RTC_Get(void);//读出当前时间值	
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);//写入当前时间
u8 RTC_Get_Week(u16 year,u8 month,u8 day);//按年月日计算星期

其中首次启用RTC的设置存在的必要性,RTC不仅使用主电源供电而且使用后备电源供电,当主电源也断电之后,后备电源可能会供电,但如果后被电源断电之后,后备存储器存储的内容丢失,RTC内部的相关寄存器值会丢失,这时上电之后需要对RTC的所有寄存器设置,让RTC处于走时工作状态。综上所述,如果RTC的后备电源有过断电情况(所有数据均丢失)或者第一次使用RTC时需要使用首次RTC设置。

具体代码如下:

void RTC_First_Config(void){ //首次启用RTC的设置
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);//启用PWR和BKP的时钟(from APB1)
    PWR_BackupAccessCmd(ENABLE);//后备域解锁
    BKP_DeInit();//备份寄存器模块复位
    RCC_LSEConfig(RCC_LSE_ON);//外部32.768KHZ晶振开启   
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);//等待稳定    
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//RTC时钟源配置成LSE(外部低速晶振32.768KHZ)    
    RCC_RTCCLKCmd(ENABLE);//RTC开启    
    RTC_WaitForSynchro();//开启后需要等待APB1时钟与RTC时钟同步,才能读写寄存器    
    RTC_WaitForLastTask();//读写寄存器前,要确定上一个操作已经结束
    RTC_SetPrescaler(32767);//设置RTC分频器,使RTC时钟为1Hz,RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)   
    RTC_WaitForLastTask();//等待寄存器写入完成	
    //当不使用RTC秒中断,可以屏蔽下面2条
//    RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能秒中断   
//    RTC_WaitForLastTask();//等待写入完成
}

其中可以通过设置

  RTC_SetPrescaler(32767);//设置RTC分频器,使RTC时钟为1Hz,RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)

中的参数来进行时钟的矫正、微调。

void RTC_Config(void){ //实时时钟初始化
    //在BKP的后备寄存器1中,存了一个特殊字符0xA5A5
    //第一次上电或后备电源掉电后,该寄存器数据丢失,表明RTC数据丢失,需要重新配置
    if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5){//判断寄存数据是否丢失       
        RTC_First_Config();//重新配置RTC        
        BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);//配置完成后,向后备寄存器中写特殊字符0xA5A5
    }else{
		//若后备寄存器没有掉电,则无需重新配置RTC
        //这里我们可以利用RCC_GetFlagStatus()函数查看本次复位类型
        if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET){
            //这是上电复位
        }
        else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET){
            //这是外部RST管脚复位
        }       
        RCC_ClearFlag();//清除RCC中复位标志

        //虽然RTC模块不需要重新配置,且掉电后依靠后备电池依然运行
        //但是每次上电后,还是要使能RTCCLK
        RCC_RTCCLKCmd(ENABLE);//使能RTCCLK        
        RTC_WaitForSynchro();//等待RTC时钟与APB1时钟同步

        //当不使用RTC秒中断,可以屏蔽下面2条
//        RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能秒中断        
//        RTC_WaitForLastTask();//等待操作完成
    }

这里值得注意的是,0xA5A5是作为标志检查位而写入的,当后备电源断电之后,RTC相关寄存器和后备寄存器的值必定会丢失,这时可以根据此来判断是否需要进行首次RTC设置,进行首次RTC值设置之后,还应该对相应的一个后备寄存器写入该值,以便下次进行检查,代码中还有上电复位和外部SET复位,可向相应的if语句中写入上电复位和外部SET复位对应的不同效果,而且作为初学者,我们只需要调用一般的初始化函数即可,因为该函数中已经包含首次RTC设置的判断语句,除非强制首次RTC设置。另外初学者只要学会调用下面两条程序即可

u8 RTC_Get(void);                  //读出当前时间值
{ //写入当前时间(1970~2099年有效),
	u16 t;
	u32 seccount=0;
	if(syear<2000||syear>2099)return 1;//syear范围1970-2099,此处设置范围为2000-2099       
	for(t=1970;t=365){
		     if(Is_Leap_Year(temp1)){//是闰年
			     if(temp>=366)temp-=366;//闰年的秒钟数
			     else {temp1++;break;} 
		     }
		     else temp-=365;       //平年
		     temp1++; 
		}  
		ryear=temp1;//得到年份
		temp1=0;
		while(temp>=28){//超过了一个月
			if(Is_Leap_Year(ryear)&&temp1==1){//当年是不是闰年/2月份
				if(temp>=29)temp-=29;//闰年的秒钟数
				else break;
			}else{
	            if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
	            else break;
			}
			temp1++; 
		}
		rmon=temp1+1;//得到月份
		rday=temp+1;  //得到日期
	}
	temp=timecount%86400;     //得到秒钟数      
	rhour=temp/3600;     //小时
	rmin=(temp%3600)/60; //分钟     
	rsec=(temp%3600)%60; //秒钟
	rweek=RTC_Get_Week(ryear,rmon,rday);//获取星期  
	return 0;
}  

其中写时间函数是需要将对应的时间值按照形参列表写入RTC中的32位计数器,返回值若为0,则代表写入成功。读函数则是对RTC中32位计数器中的值读出来,按照一定的算法存储在全局变量中,需要注意的是,读函数将对应时间的对于的周也读了出来,因此初学者不必调用计算周的函数因为该函数已经在读函数中调用,此外计算闰年的函数也不需要调用,读和写函数中均调用了闰年判断函数,和写函数一样,若返回值为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
//输入:年份
//输出:该年份是不是闰年.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;
}  

u8 RTC_Get_Week(u16 year,u8 month,u8 day){ //按年月日计算星期(只允许1901-2099年)//已由RTC_Get调用    
	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); //返回星期值
}

需要注意的是周获取函数中返回值0代表周日,1-6分别代表周一至周六。

你可能感兴趣的:(STM32学习100步之第三十九-四十步——RTC实时时钟)