stm32——RTC實時時鐘

stm32——RTC實時時鐘

一、關於時間

  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使用說明

  "RTC"是Real Time Clock 的簡稱,意爲實時時鐘。stm32提供了一個秒中斷源和一個鬧鐘中斷源,修改計數器的值可以重新設置系統當前的時間和日期。

  RTC模塊之所以具有實時時鐘功能,是因爲它內部維持了一個獨立的定時器,通過配置,可以讓它準確地每秒鐘中斷一次。但實際上,RTC就只是一個定時器而已,掉電之後所有信息都會丟失,因此我們需要找一個地方來存儲這些信息,於是就找到了備份寄存器。其在掉電後仍然可以通過鈕釦電池供電,所以能時刻保存這些數據。

  配置RTC前須知:

  BKP:

  RTC模塊和時鐘配置系統的寄存器是在後備區域的(即BKP),通過BKP後備區域來存儲RTC配置的數據可以讓其在系統復位或待機模式下喚醒後,RTC裏面配置的數據維持不變。

  PWR:

  PWR爲電源的寄存器,我們需要用到的是電源控制寄存器(PWR_CR),通過使能PWR_CR的DBP位來取消後備區域BKP的寫保護。

  RTC:

  由一組可編程計數器組成,分成兩個模塊。第一個模塊是RTC的預分頻模塊,它可編程產生最長爲1秒的RTC時間基準TR_CLK。RTC的預分頻模塊包含了一個20位的可編程分頻器(RTC)TR_CLK 週期中RTC產生一箇中斷(秒中斷)。第二個模塊是一個32位的可編程計數器,可被初始化爲當前的系統時間。系統時間按TR_CLK週期累加並與存儲在RTC_ALR寄存器中的可編程時間相比較,如果RTC_CR控制寄存器中設置了相應允許位,比較匹配時,將產生一個鬧鐘中斷。

 

  

 

  下面講解下配置整體過程:

   第一步: 通過設置寄存器 RCC_APB1ENR 的 PWREN 和 BKPEN 位來打開電源和後備接口的時鐘
   調用庫函數:

    RCC_APB1PeriphClockCmd (RCC_APB1Periph_PWR | RCC_APB1Periph_BKP,ENABLE );

   第二步:電源控制寄存器(PWR_CR) 的 DBP 位來使能對後備寄存器和 RTC 的訪問

   調用庫函數:

    PWR_BackupAccessCmd(ENABLE );

   第三步:初始化復位 BKP 寄存器 

   調用庫函數:

   BKP_DeInit ();

   第四步:設置 RTCCLK,如下圖:

  

 

   我們需要將 RTCCLK 設置爲 LSE OSC  這個 32.768KHZ 的晶振。

   調用的庫函數:  

   RCC_LSEConfig (RCC_LSE_ON);

   While(!RCC_GetFlagStatus (RCC_FLAG_HSERDY));//設置後需要等待啓動

 

   第五步:將 RTC 輸入時鐘 選擇爲 LSE 時鐘輸入並使能 RTC,等待 RTC 和 APB 時鐘同步

   調用庫函數:

   RCC_RTCCLKConfig (RCC_RTCCLKSource_LSE);//選擇 LSE 爲 RTC 設備的時鐘

   RCC_RTCCLKCmd (ENABLE );//使能 

   RTC RTC_WaitForSynchro();//等待同步

  第六步:配置 RTC 時鐘參數。

  1. 查詢 RTOFF 位,直到 RTOFF 的值變爲’1’
  2. 置 CNF 值爲 1 ,進入配置模式
  3. 對一個或多個 RTC 寄存器進行寫操作
  4. 清除 CNF 標誌位,退出配置模式
  5. 查詢 RTOFF,直至 RTOFF 位變爲’1’ 以確認寫操作已經完成。僅當 CNF 標誌位被清除時,寫操作才能進行,這個過程至少需要 3 個 RTCCLK 週期。

  按照上述步驟用庫函數來配置:

複製代碼
/* 1.    查詢 RTOFF 位,直到 RTOFF 的值變爲’1’ */

RTC_WaitForLastTask();//大家可以打開函數庫看看這個函數內部的代碼,就是查詢 RTOFF的值

/*

2.置 CNF 值爲 1 ,進入配置模式

3.對一個或多個 RTC 寄存器進行寫操作

4.清除 CNF 標誌位,退出配置模式

*/

RTC_SetPrescaler(32767); // 這裏配置了預分頻值,大家可以打開函數庫看看這個函數的內部的代碼,裏面就有包含了 2、3、4 講述的操作。

/*
每完成一個操作一般都要查詢 RTOFF 來判斷是否 RTC 正在更新數據,如果是則等待它完成!!!
*/
RTC_WaitForLastTask();//等待更新結束

RTC_ITConfig(RTC_IT_SEC, ENABLE);//配置秒中斷

RTC_WaitForLastTask();//等待更新結束
複製代碼

 

三、程序演示

  rtc.h

複製代碼
#ifndef __RTC_H
#define __RTC_H
#include "stm32f10x.h"

//時間結構體
typedef struct 
{
    vu8 hour;
    vu8 min;
    vu8 sec;            
    //公曆年月日周
    vu16 w_year;
    vu8  w_month;
    vu8  w_date;
    vu8  week;     
}_calendar_obj;                     
extern _calendar_obj calendar;
void RCC_Configuration(void);
void RTC_Init(void);
u8 RTC_Set(u16 year,u8 mon,u8 day,u8 hour,u8 min,u8 sec);
u8 RTC_Get(void);
#endif
複製代碼

  rtc.c

複製代碼
#include "rtc.h"
_calendar_obj calendar;    //時鐘結構體
//平均的月份日期表
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
/*rtc中斷向量配置*/
void NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
    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);
}

void RTC_Configuration(void)

{
    /* 使能PWR和BKP時鐘 */
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_BKP,ENABLE);
    /* 使能對後備寄存器的訪問 */ 
    PWR_BackupAccessCmd(ENABLE);
    /* 復位BKP寄存器 */ 
    BKP_DeInit();
    /* 使能LSE */ 
    RCC_LSEConfig(RCC_LSE_ON);
    /*等待啓動完成 */
    while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) {}
    /* 將 RTC時鐘設置爲LSE這個32.768KHZ的晶振*/ 
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
    /* 使能RTC Clock */ 
    RCC_RTCCLKCmd(ENABLE);
    /* 等待同步 */ 
    RTC_WaitForSynchro();
    /* 等待對RTC寄存器最後的寫操作完成*/             
    RTC_WaitForLastTask();
    /* 配置了預分頻值: 設置RTC時鐘週期爲1s */
    RTC_SetPrescaler(32767); /* RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)*/
    /* 等待對RTC寄存器最後的寫操作完成 */
    RTC_WaitForLastTask();
    /* 使能RTC秒中斷 */ 
    RTC_ITConfig(RTC_IT_SEC, ENABLE);
    /* 等待對RTC寄存器最後的寫操作完成 */         
    RTC_WaitForLastTask();

void RTC_Init(void)
{
    /*如果是第一次配置時鐘,則執行RCC_Configuration()進行配置*/
    if(BKP_ReadBackupRegister(BKP_DR1)!=0x1016)
    {
            RCC_Configuration();
            RTC_Set(2016,5,11,9,7,55);
            GPIO_SetBits(GPIOD, GPIO_Pin_13);//點亮D1
            BKP_WriteBackupRegister(BKP_DR1, 0x1016);//向執行的後備寄存器中寫入用戶程序數據
    }
    else
    {
        RTC_WaitForSynchro();//等待RTC寄存器同步完成
        RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能RTC秒中斷
        RTC_WaitForLastTask();//等待最近一次對RTC寄存器的寫操作完成
        GPIO_SetBits(GPIOG, GPIO_Pin_14);//點亮D2
    }
    NVIC_Configuration();
    RTC_Get();//更新時間
}
u8 Is_Leap_Year(u16 pyear)
{
    if(pyear%4==0)//首先需能被4整除
    {
        if(pyear%100==0)
        {
            if(pyear%400==0)    return 1;//如果以00結尾,還要能被400整除
            else    return 0;
        }
        else
            return 1;
    }
    else
        return 0;
}
/*
*設置時鐘
*把輸入的時鐘轉換爲秒鐘
*以1970年1月1日爲基準
*1970~2099年爲合法年份
返回值:0,成功;其它:錯誤
*/
u8 RTC_Set(u16 year,u8 mon,u8 day,u8 hour,u8 min,u8 sec)
{
    u16 t;
    u32 secCount=0;
    if(year<1970||year>2099)
        return 1;//³ö´í
    for(t=1970;t<year;t++)    //把所有年份的秒鐘相加
    {
        if(Is_Leap_Year(t))//閏年
            secCount+=31622400;//閏年的秒鐘數
        else
            secCount+=31536000;    
    }
    mon-=1;//先減掉一個月再算秒數(如現在是5月10日,則只需要算前4個月的天數,再加上10天,然後計算秒數)
    for(t=0;t<mon;t++)
    {
        secCount+=(u32)mon_table[t]*86400;//月份秒鐘數相加
        if(Is_Leap_Year(year)&&t==1)
            secCount+=86400;//閏年,2月份增加一天的秒鐘數
    }
    
    secCount+=(u32)(day-1)*86400;//把前面日期的秒鐘數相加(這一天還沒過完,所以-1)
    secCount+=(u32)hour*3600;//小時秒鐘數
    secCount+=(u32)min*60;//分鐘秒鐘數
    secCount+=sec;
//    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR    | RCC_APB1Periph_BKP,ENABLE);
//    PWR_BackupAccessCmd(ENABLE);
    RTC_SetCounter(secCount);//設置RTC計數器的值
    RTC_WaitForLastTask();    //等待最近一次對RTC寄存器的寫操作完成
    RTC_Get();//更新時間
    return 0;
}

/*
得到當前的時間
成功返回0,錯誤返回其它
*/
u8 RTC_Get(void)
{
        static u16 dayCount=0;
        u32 secCount=0;
        u32 tmp=0;
        u16 tmp1=0;
        secCount=RTC_GetCounter();
        tmp=secCount/86400;//得到天數
        if(dayCount!=tmp)//超過一天
        {
            dayCount=tmp;
            tmp1=1970;//從1970年開始
            while(tmp>=365)
            {
                if(Is_Leap_Year(tmp1))//是閏年
                {
                    if(tmp>=366)    
                        tmp-=366;//減掉閏年的天數
                    else
                    {
                    //    tmp1++;
                        break;
                    }
                }
                else
                    tmp-=365;//平年
                tmp1++;
            }
            calendar.w_year=tmp1;//得到年份
            tmp1=0;
            while(tmp>=28)//超過一個月
            {
                if(Is_Leap_Year(calendar.w_year)&&tmp1==1)/當年是閏年且輪循到2月
                {
                    if(tmp>=29)    
                        tmp-=29;
                    else
                        break;
                }
                else
                {
                    if(tmp>=mon_table[tmp1])//平年
                        tmp-=mon_table[tmp1];
                    else
                        break;
                }
                tmp1++;
            }
            calendar.w_month=tmp1+1;//得到月份,tmp1=0表示1月,所以要加1
            calendar.w_date=tmp+1;    //得到日期,因爲這一天還沒過完,所以tmp只到其前一天,但是顯示的時候要顯示正常日期
        }
        tmp=secCount%86400;//得到秒鐘數
        calendar.hour=tmp/3600;//小時
        calendar.min=(tmp%3600)/60;//分鐘
        calendar.sec=(tmp%3600)%60;//
        return 0;
}
/*
RTC時鐘中斷
每秒觸發一次
*/
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_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);//清鬧鐘中斷
    RTC_WaitForLastTask();                                                   
}
複製代碼

  main.c

複製代碼
#include "stm32f10x.h"
#include "usart1.h"
#include "LED.h"
#include "delay.h"
#include "flash.h"
#include "rtc.h"
#include "stdio.h"
int main(void)
{
    u8 t=0;
    USART1_Config();
    GPIO_Configuration();
    RTC_Init();
    while(1)
    {
        if(t!=calendar.sec)
        {
            t=calendar.sec;
            printf("\r\n now is %d 年 %d 月 %d 日 %d 時 %d 分 %d 秒 \r\n ",
       calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec); } Delay(
0x02FFFF); } }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章