寒假学习之stm32( 12)----RTC与BKP

RTC与BKP

犹记得当初学习51单片机的时候遇到的DS1302计时模块,当初第一次接触那样复杂的寄存器,也是十分困难的,现在的stm32内置了RTC模块,我们也可以认为RTC是内置的类似的DS1302模块。

1. RTC是个独立的定时器。RTC模块拥有一个连续计数的计数器,在相应的软件配置下,可以提供时钟日历的功能。修改计数器的值可以重新设置当前时间和日期

2. RTC模块和时钟配置系统(RCC_BDCR寄存器)是在后备区域,即在系统复位或从待机模式唤醒后RTC的设置和时间维持不变。但是在系统复位后,会自动禁止访问后备寄存器和RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护

听起来是不是和DS1302更像了??

让我们看看RTC的时钟源:
寒假学习之stm32( 12)----RTC与BKP_第1张图片

BKP寄存器:

1. 备份寄存器是42个16位的寄存器。可用来存储84个字节数据。
2. 它们处在备份区域,当VDD电源切断,仍然由VBAT维持供电。
3. 当系统在待机模式下被唤醒,或者系统复位或者电源复位,它们也不会复位。
4. 执行以下操作将使能对后备寄存器和RTC访问:
设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备时钟。
设置寄存器PWR_CR的DBP位,使能对RTC和后备寄存器的访问。

由于RTC只能进行记录数字,在掉电之后寄存器内存储的数字就没了,为了达到和DS1302类似的功能,我们需要将RTC记录的数字存储到BKP寄存器中,根据BKP掉电仍有备用电源保留时间的特性,进行对时间的储存。

配置RTC的一般步骤:

1. 使能PWR和BKP的时钟:
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

2. 使能后备寄存器访问:
    PWR_BackupAccessCmd(ENABLE);    //使能后备寄存器访问  
    if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050)      //从指定的后备寄     存器中读出数据:读出了与写入的指定数据不相乎
        {               
            BKP_DeInit();   //复位备份区域    
            RCC_LSEConfig(RCC_LSE_ON);  //设置外部低速晶振(LSE),使用外设低速晶振
            while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250)   //检查指定的RCC标志位设置与否,等待低速晶振就绪
            {
                temp++;
                delay_ms(10);
            }
        if(temp>=250)return 1;//初始化时钟失败,晶振有问题       

3. 配置RTC时钟源,使能RTC时钟
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);     //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟    
    RCC_RTCCLKCmd(ENABLE);  //使能RTC时钟  

4. 设置RTC预分配系数
    RTC_WaitForLastTask();  //等待最近一次对RTC寄存器的写操作完成
    RTC_EnterConfigMode();/// 允许配置  
    RTC_SetPrescaler(32767); //设置RTC预分频的值,等于外部晶振的频率-1

5. 设置时间
    RTC_WaitForLastTask();  //等待最近一次对RTC寄存器的写操作完成
    RTC_WaitForSynchro();       //等待RTC寄存器同步  
    RTC_SetCounter();

6. 开启相关中断
       RTC_WaitForLastTask();   //等待最近一次对RTC寄存器的写操作完成
       RTC_WaitForSynchro();        //等待RTC寄存器同步  
       RTC_ITConfig(RTC_IT_SEC, ENABLE);        //使能RTC秒中断

7. 编写中断服务函数
        RTC_IRQHandler();

8. *部分操作要等待操作完成,同步
   RTC_WaitForLastTask();//等待最近一次对RTC寄存器的写操作完成
   RTC_WaitForSynchro();    //等待RTC寄存器同步 

寒假学习之stm32( 12)----RTC与BKP_第2张图片

具体的代码片:


//实时时钟配置
//初始化RTC时钟,同时检测时钟是否工作正常
//BKP->DR1用于保存是否第一次配置的设置
//返回0:正常
//其他:错误代码

u8 RTC_Init(void)
{
    //检查是不是第一次配置时钟
    u8 temp=0;
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);    //使能PWR和BKP外设时钟   
    PWR_BackupAccessCmd(ENABLE);    //使能后备寄存器访问  

    if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050)      //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
    {               
        BKP_DeInit();   //复位备份区域    
        RCC_LSEConfig(RCC_LSE_ON);  //设置外部低速晶振(LSE),使用外设低速晶振
        while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250)   //检查指定的RCC标志位设置与否,等待低速晶振就绪
        {
            temp++;
            delay_ms(10);
        }

        if(temp>=250)return 1;//初始化时钟失败,晶振有问题       

        RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);     //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟    
        RCC_RTCCLKCmd(ENABLE);  //使能RTC时钟  

        RTC_WaitForLastTask();  //等待最近一次对RTC寄存器的写操作完成
        RTC_WaitForSynchro();       //等待RTC寄存器同步  
        RTC_ITConfig(RTC_IT_SEC, ENABLE);       //使能RTC秒中断

        RTC_WaitForLastTask();  //等待最近一次对RTC寄存器的写操作完成
        RTC_EnterConfigMode();/// 允许配置  
        RTC_SetPrescaler(32767); //设置RTC预分频的值

        RTC_WaitForLastTask();  //等待最近一次对RTC寄存器的写操作完成
        RTC_Set(2015,1,14,17,42,55);  //设置时间    
        RTC_ExitConfigMode(); //退出配置模式  

        BKP_WriteBackupRegister(BKP_DR1, 0X5050);   //向指定的后备寄存器中写入用户程序数据
    }
    else//系统继续计时
    {

        RTC_WaitForSynchro();   //等待最近一次对RTC寄存器的写操作完成
        RTC_ITConfig(RTC_IT_SEC, ENABLE);   //使能RTC秒中断
        RTC_WaitForLastTask();  //等待最近一次对RTC寄存器的写操作完成
    }

    RTC_NVIC_Config();//RCT中断分组设置                                
    RTC_Get();//更新时间    
    return 0; //ok

}                           
//RTC时钟中断
//每秒触发一次  
//extern u16 tcnt; 
void RTC_IRQHandler(void)
{        
    if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断
    {                           
        RTC_Get();//更新时间   
    }
    if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
    {
        RTC_ClearITPendingBit(RTC_IT_ALR);      //清闹钟中断     
      RTC_Get();                //更新时间  RTC_GetCounter() 
    printf("Alarm Time:%d-%d-%d %d:%d:%d\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//输出闹铃时间  

    }                                                
    RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);        //清闹钟中断
    RTC_WaitForLastTask();                                           
}
//判断是否是闰年函数
//月份   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; 
}                  
//设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准
//1970~2099年为合法年份
//返回值:0,成功;其他:错误代码.
//月份数据表                                          
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表   
//平年的月份日期表
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
    u16 t;
    u32 seccount=0;
    if(syear<1970||syear>2099)return 1;    
    for(t=1970;t//把所有年份的秒钟相加
    {
        if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
        else seccount+=31536000;              //平年的秒钟数
    }
    smon-=1;
    for(t=0;t//把前面月份的秒钟数相加
    {
        seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
        if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数       
    }
    seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加 
    seccount+=(u32)hour*3600;//小时秒钟数
    seccount+=(u32)min*60;   //分钟秒钟数
    seccount+=sec;//最后的秒钟加上去

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);    //使能PWR和BKP外设时钟  
    PWR_BackupAccessCmd(ENABLE);    //使能RTC和后备寄存器访问 
    RTC_SetCounter(seccount);   //设置RTC计数器的值

    RTC_WaitForLastTask();  //等待最近一次对RTC寄存器的写操作完成   
    return 0;       
}

//初始化闹钟       
//以1970年1月1日为基准
//1970~2099年为合法年份
//syear,smon,sday,hour,min,sec:闹钟的年月日时分秒   
//返回值:0,成功;其他:错误代码.
u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
    u16 t;
    u32 seccount=0;
    if(syear<1970||syear>2099)return 1;    
    for(t=1970;t//把所有年份的秒钟相加
    {
        if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
        else seccount+=31536000;              //平年的秒钟数
    }
    smon-=1;
    for(t=0;t//把前面月份的秒钟数相加
    {
        seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
        if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数       
    }
    seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加 
    seccount+=(u32)hour*3600;//小时秒钟数
    seccount+=(u32)min*60;   //分钟秒钟数
    seccount+=sec;//最后的秒钟加上去                
    //设置时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);    //使能PWR和BKP外设时钟   
    PWR_BackupAccessCmd(ENABLE);    //使能后备寄存器访问  
    //上面三步是必须的!

    RTC_SetAlarm(seccount);

    RTC_WaitForLastTask();  //等待最近一次对RTC寄存器的写操作完成   

    return 0;       
}


//得到当前的时间
//返回值:0,成功;其他:错误代码.
u8 RTC_Get(void)
{
    static u16 daycnt=0;
    u32 timecount=0; 
    u32 temp=0;
    u16 temp1=0;      
    timecount=RTC_GetCounter();  
    temp=timecount/86400;   //得到天数(秒钟数对应的)
    if(daycnt!=temp)//超过一天了
    {     
        daycnt=temp;
        temp1=1970; //从1970年开始
        while(temp>=365)
        {                
            if(Is_Leap_Year(temp1))//是闰年
            {
                if(temp>=366)temp-=366;//闰年的秒钟数
                else {temp1++;break;}  
            }
            else temp-=365;   //平年 
            temp1++;  
        }   
        calendar.w_year=temp1;//得到年份
        temp1=0;
        while(temp>=28)//超过了一个月
        {
            if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
            {
                if(temp>=29)temp-=29;//闰年的秒钟数
                else break; 
            }
            else 
            {
                if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年
                else break;
            }
            temp1++;  
        }
        calendar.w_month=temp1+1;   //得到月份
        calendar.w_date=temp+1;     //得到日期 
    }
    temp=timecount%86400;           //得到秒钟数        
    calendar.hour=temp/3600;        //小时
    calendar.min=(temp%3600)/60;    //分钟    
    calendar.sec=(temp%3600)%60;    //秒钟
    calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期   
    return 0;
}    
//获得现在是星期几
//功能描述:输入公历日期得到星期(只允许1901-2099年)
//输入参数:公历年月日 
//返回值:星期号                                                                                        
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
{   
    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);
}             

你可能感兴趣的:(stm32)