stm32——RTC實時時鐘
一、關於時間
2038年問題
在計算機應用上,2038年問題可能會導致某些軟件在2038年無法正常工作。所有使用UNIX時間表示時間的程序都將將受其影響,因爲它們以自1970年1月1日經過的秒數(忽略閏秒)來表示時間。這種時間表示法在類Unix(Unix-like)操作系統上是一個標準,並會影響以其C編程語言開發給其他大部份操作系統使用的軟件。
在大部份的32位操作系統上,此“time_t”數據模式使用一個有正負號的32位元整數(signedint32)存儲計算的秒數。也就是說最大可以計數的秒數爲 2^31次方 可以算得:
二、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 時鐘參數。
- 查詢 RTOFF 位,直到 RTOFF 的值變爲’1’
- 置 CNF 值爲 1 ,進入配置模式
- 對一個或多個 RTC 寄存器進行寫操作
- 清除 CNF 標誌位,退出配置模式
- 查詢 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); } }