一、RTC簡介
RTC(Real Time Clock)實(shí)時(shí)時(shí)鐘,它是一個(gè)獨(dú)立的定時(shí)器。RTC模塊擁有一組連續(xù)計(jì)數(shù)的計(jì)數(shù)器,在相應(yīng)軟件配置下,可提供時(shí)鐘日歷的功能。修改計(jì)數(shù)器的值可以重新設(shè)置系統(tǒng)當(dāng)前的時(shí)間和日期。
RTC模塊和時(shí)鐘配置都是在后備區(qū)域,無論單片機(jī)處于何種狀態(tài),只要保證后備區(qū)正常供電,RTC就會(huì)一直工作。
二、STM32的RTC
2.1 主要特性
? 可編程的預(yù)分頻系數(shù) :分頻系數(shù)最高為2^20
? 32位的可編程計(jì)數(shù)器 ,可用于較長時(shí)間段的測量
? 可以選擇以下三種RTC的時(shí)鐘源 ─ HSE時(shí)鐘除以128 ─ LSE振蕩器時(shí)鐘 ─ LSI振蕩器時(shí)鐘
? 3個(gè)專門的可屏蔽中斷 ─ 鬧鐘中斷,用來產(chǎn)生一個(gè)軟件可編程的鬧鐘中斷 ─ 秒中斷,用來產(chǎn)生一個(gè)可編程的周期性中斷信號(最長可達(dá)1秒) ─ 溢出中斷,指示內(nèi)部可編程計(jì)數(shù)器溢出并回轉(zhuǎn)為0的狀態(tài)
22.2 RTC框圖介紹
RTC框圖
? RTCCLK通常選擇低功耗32.768kHz外部晶振(LSE)
? RTC預(yù)分頻器通常設(shè)置為32768,LES時(shí)鐘經(jīng)過RTC預(yù)分頻器,輸入頻率變?yōu)?Hz,也就是1秒
? RTC_CNT輸入時(shí)鐘為1Hz時(shí),1s加1次
? RTC_ALR是用來做鬧鐘的,RTC_CNT的值會(huì)與RTC_ALR的值進(jìn)行比較,二者相等時(shí),會(huì)產(chǎn)生鬧鐘中斷
三、訪問后備區(qū)域步驟
STM32系統(tǒng)復(fù)位之后,對后備寄存器和RTC的訪問被禁止 ,這是為了防止對后備區(qū)域(BKP)的意外寫操作。執(zhí)行以下操作,可以訪問后備區(qū)域寄存器
完成上面的設(shè)置之后,就可以操作后備寄存器。第一次通過APB1總線訪問RTC時(shí),需要等待APB1和RTC同步,確保讀取出來的RTC的寄存器值是正確的。如果同步之后,一直沒有關(guān)閉APB1和RTC外設(shè)接口,就不需要再同步了。
如果內(nèi)核需要對RTC寄存器寫入數(shù)據(jù),在內(nèi)核發(fā)送指令后,RTC會(huì)在3個(gè)RTCCLK時(shí)鐘之后,開始寫入數(shù)據(jù)。每次寫入時(shí),必須要檢查RTC關(guān)閉操作標(biāo)志位RTOFF是否置1來判斷是否寫操作完成。
四、RTC配置步驟
? 使能電源時(shí)鐘和后備域時(shí)鐘,開啟RTC后備寄存器寫訪問
? 復(fù)位備份區(qū)域,開啟外部低速振蕩器(LSE)
? 選擇RTC時(shí)鐘,并使能
? 設(shè)置RTC的分頻系數(shù),配置RTC時(shí)鐘
? 更新配置,設(shè)置RTC中斷分組
? 編寫RTC中斷服務(wù)函數(shù)
五、RTC程序配置
55.1 RTC結(jié)構(gòu)體定義
// RTC結(jié)構(gòu)體
typedef struct
{
// 時(shí)分秒
u8 hour;
u8 min;
u8 sec;
// 年月日周
u16 w_year;
u8 w_month;
u8 w_date;
u8 week;
}_calendar;
5.2 RTC初始化函數(shù)
/*
*==============================================================================
*函數(shù)名稱:RTC_Init
*函數(shù)功能:初始化RTC
*輸入參數(shù):無
*返回值:0:成功;1:失敗
*備 注:無
*==============================================================================
*/
u8 RTC_Init (void)
{
u8 temp=0; // 超時(shí)監(jiān)控變量
// 結(jié)構(gòu)體定義
NVIC_InitTypeDef NVIC_InitStructure;
// 使能PWR和BKP外設(shè)時(shí)鐘
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
PWR_BackupAccessCmd(ENABLE); // 使能后備寄存器訪問
// 檢測是否是第一次配置RTC
// 配置時(shí)會(huì)想RTC寄存器寫入0xA0A0,如果讀出的數(shù)據(jù)不是0xA0A0,認(rèn)為是第一次配置RTC
if (BKP_ReadBackupRegister(BKP_DR1) != 0xA0A0)
{
BKP_DeInit(); // 復(fù)位備份區(qū)域
RCC_LSEConfig(RCC_LSE_ON); // 設(shè)置外部低速晶振(LSE),使用外設(shè)低速晶振
// 等待低速晶振就緒
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp< 250)
{
temp++;
delay_ms(10);
}
// 初始化時(shí)鐘失敗,晶振有問題
if(temp >=250)
{
return 1;
}
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); // 設(shè)置RTC時(shí)鐘(RTCCLK),選擇LSE作為RTC時(shí)鐘
RCC_RTCCLKCmd(ENABLE); // 使能RTC時(shí)鐘
RTC_WaitForLastTask(); // 等待最近一次對RTC寄存器的寫操作完成
RTC_WaitForSynchro(); // 等待RTC寄存器同步
RTC_ITConfig(RTC_IT_SEC, ENABLE); // 使能RTC秒中斷
RTC_WaitForLastTask(); // 等待最近一次對RTC寄存器的寫操作完成
RTC_EnterConfigMode(); // 允許配置
RTC_SetPrescaler(32767); // 設(shè)置RTC預(yù)分頻的值
RTC_WaitForLastTask(); // 等待最近一次對RTC寄存器的寫操作完成
RTC_Set_Date(2023,6,26,11,15,00); // 設(shè)置初始時(shí)間
RTC_ExitConfigMode(); // 退出配置模式
BKP_WriteBackupRegister(BKP_DR1, 0XA0A0); // 向指定的后備寄存器中寫入用戶程序數(shù)據(jù)
}
// 系統(tǒng)繼續(xù)計(jì)時(shí)
else
{
RTC_WaitForSynchro(); // 等待最近一次對RTC寄存器的寫操作完成
RTC_ITConfig(RTC_IT_SEC, ENABLE); // 使能RTC秒中斷
RTC_WaitForLastTask(); // 等待最近一次對RTC寄存器的寫操作完成
}
// 配置RTC中斷分組
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; // RTC全局中斷
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 先占優(yōu)先級1位,從優(yōu)先級3位
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 先占優(yōu)先級0位,從優(yōu)先級4位
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能該通道中斷
NVIC_Init(&NVIC_InitStructure); // 根據(jù)NVIC_InitStruct中指定的參數(shù)初始化外設(shè)NVIC寄存器
RTC_Get_CurDate(); // 獲取當(dāng)前時(shí)間
return 0; // 配置成功
}
初始化函數(shù)使用時(shí),可以用while等待初始化成功,但是需要增加一個(gè)超時(shí)檢測,這里簡單給出一個(gè)寫法,如果1s內(nèi),RTC沒有初始化成功,直接跳過
u32 tempVar = 0; // 初始化RTC時(shí)的超時(shí)計(jì)數(shù)變量
while (RTC_Init() && tempVar < 100) // RTC初始化
{
delay_ms (10);
// 10ms自加1
tempVar = tempVar + 1;
}
5.3 設(shè)置年月日,時(shí)分秒
/*
*==============================================================================
*函數(shù)名稱:RTC_Set_Date
*函數(shù)功能:設(shè)置RTC的年月日,時(shí)分秒
*輸入?yún)?shù):無
*返回值:0:成功;1:失敗
*備 注:時(shí)間范圍為1970年到2099年,可修改
*==============================================================================
*/
u8 RTC_Set_Date (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 < syear;t ++) // 把所有年份的秒鐘相加
{
// 閏年的秒鐘數(shù)
if(Is_Leap_Year(t))
{
seccount += 31622400;
}
// 平年的秒鐘數(shù)
else
{
seccount += 31536000;
}
}
smon -= 1;
for(t = 0;t < smon;t ++) // 把前面月份的秒鐘數(shù)相加
{
seccount += (u32)mon_table[t] * 86400; // 月份秒鐘數(shù)相加
// 閏年2月份增加一天的秒鐘數(shù)
if(Is_Leap_Year(syear) && t == 1)
{
seccount += 86400;
}
}
seccount += (u32)(sday-1) * 86400; // 把前面日期的秒鐘數(shù)相加
seccount += (u32)hour * 3600; // 小時(shí)秒鐘數(shù)
seccount += (u32)min*60; // 分鐘秒鐘數(shù)
seccount += sec; // 最后的秒鐘加上去
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); // 使能PWR和BKP外設(shè)時(shí)鐘
PWR_BackupAccessCmd(ENABLE); // 使能RTC和后備寄存器訪問
RTC_SetCounter(seccount); // 設(shè)置RTC計(jì)數(shù)器的值
RTC_WaitForLastTask(); // 等待最近一次對RTC寄存器的寫操作完成
return 0;
}
5.4 判斷閏年函數(shù)
/*
*==============================================================================
*函數(shù)名稱:Is_Leap_Year
*函數(shù)功能:判斷輸入年份是否為閏年
*輸入?yún)?shù):無
*返回值:0:不是閏年;1:是閏年
*備 注:四年一閏;百年不閏,四百年再閏
*==============================================================================
*/
u8 Is_Leap_Year (u16 year)
{
// 是否能被4整除
if(year % 4 == 0)
{
// 是否能被100整除
if(year % 100 == 0)
{
// 如果以00結(jié)尾,還要能被400整除
if(year % 400 == 0)
{
return 1;
}
// 是100的倍數(shù),但是不是400的倍數(shù)
else
{
return 0;
}
}
// 是4的倍數(shù),不是100的倍數(shù)
else
{
return 1;
}
}
// 不是4的倍數(shù)
else
{
return 0;
}
}
5.5 獲取當(dāng)前年月日,時(shí)分秒
/*
*==============================================================================
*函數(shù)名稱:RTC_Get_CurDate
*函數(shù)功能:獲取當(dāng)前年月日,時(shí)分秒
*輸入?yún)?shù):無
*返回值:0:成功;1:失敗
*備 注:無
*==============================================================================
*/
u8 RTC_Get_CurDate (void)
{
// 存儲(chǔ)上一次的總天數(shù)值,用來監(jiān)測時(shí)間變化是否超過一天
static u16 daycnt = 0;
u32 timecount = 0;
// 臨時(shí)計(jì)算變量
u32 temp = 0;
u16 temp1 = 0;
timecount = RTC_GetCounter(); // 獲取當(dāng)前總秒數(shù)
temp = timecount / 86400; // 得到總天數(shù)
// 超過一天了
if(daycnt != temp)
{
daycnt = temp; // 更新當(dāng)前總天數(shù)值
temp1 = 1970; // 從1970年開始,計(jì)算當(dāng)前年份
while(temp >= 365)
{
// 是閏年
if(Is_Leap_Year(temp1))
{
// 已經(jīng)過完了366天
if(temp >= 366)
{
temp -= 366; // 閏年的天數(shù)
}
// 剛過完365天,當(dāng)前是第366天
else
{
temp1 ++; // 年份加1
break;
}
}
// 是平年
else
{
temp -= 365; // 平年的天數(shù)
}
temp1 ++; // 年份加1
}
calendar.w_year = temp1; // 得到年份
temp1=0; // 清零臨時(shí)計(jì)算變量
// 此時(shí)temp為小于一年的天數(shù),開始計(jì)算當(dāng)前月份
while(temp >= 28) // 超過了一個(gè)月
{
// 當(dāng)年是閏年的2月份
if(Is_Leap_Year(calendar.w_year) && temp1 == 1)
{
// 是閏年的二月份且天數(shù)大于等于29天
if(temp >= 29)
{
temp -= 29; // 閏年的2月份天數(shù)
}
// 是閏年的2月份,天數(shù)小于閏年2月份天數(shù)
else
{
break;
}
}
// 是平年
else
{
// 查詢月份天數(shù)表
if(temp >= mon_table[temp1])
{
// 超過當(dāng)月天數(shù),減去
temp -= mon_table[temp1];
}
else
{
break;
}
}
temp1 ++; // 月份加1
}
// 加1是因?yàn)樵路荼?a target="_blank">索引是0~11
calendar.w_month = temp1 + 1; // 得到月份
// 當(dāng)前日期為已經(jīng)過去的天數(shù)加1
calendar.w_date = temp + 1; // 得到日期
}
temp = timecount % 86400; // 得到秒鐘數(shù)
calendar.hour = temp / 3600; // 小時(shí)
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;
}
5.6 獲取星期幾
該函數(shù)設(shè)計(jì)是根據(jù)蔡勒公式設(shè)計(jì),程序如下
/*
*==============================================================================
*函數(shù)名稱:RTC_Get_Week
*函數(shù)功能:獲取當(dāng)前是星期幾
*輸入?yún)?shù):year:當(dāng)前年;month:當(dāng)前月;day:當(dāng)前日
*返回值:星期幾
*備 注:無
*==============================================================================
*/
u8 RTC_Get_Week (u16 year,u8 month,u8 day)
{
u16 temp;
u8 yearH,yearL;
yearH = year / 100;
yearL = year % 100;
// 如果為21世紀(jì),年份數(shù)加100
if (yearH > 19)
{
yearL += 100;
}
// 所過閏年數(shù)只算1900年之后的
temp = yearL + yearL / 4;
temp = temp % 7;
temp = temp + day + table_week[month - 1];
if (yearL % 4 == 0 && month < 3)
{
temp --;
}
return(temp % 7);
}
5.7 中斷服務(wù)函數(shù)
/*
*==============================================================================
*函數(shù)名稱:RTC_IRQHandler
*函數(shù)功能:RTC中斷服務(wù)函數(shù)
*輸入?yún)?shù):無
*返回值:無
*備 注:更新時(shí)間
*==============================================================================
*/
void RTC_IRQHandler(void)
{
// 秒中斷
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
{
RTC_Get_CurDate(); // 獲取當(dāng)前時(shí)間
// 串口打印當(dāng)前時(shí)間
printf("RTC Time:%d-%d-%d %d:%d:%d Week:%dn",calendar.w_year,calendar.w_month,calendar.w_date,
calendar.hour,calendar.min,calendar.sec,calendar.week);
}
RTC_ClearITPendingBit(RTC_IT_SEC | RTC_IT_OW); //清除秒中斷標(biāo)志位
RTC_WaitForLastTask(); // 等待最近一次對RTC寄存器的寫操作完成
}
六、拓展
在實(shí)際使用時(shí),通常會(huì)通過網(wǎng)絡(luò)授時(shí),也就是利用WIFI模塊連接網(wǎng)絡(luò),請求API獲得初始時(shí)間。但是可能會(huì)存在些許差異。比如請求API后,獲得的時(shí)間為2023.06.26.14:48:00。實(shí)際單片機(jī)解析出時(shí)間時(shí)已經(jīng)過去了幾秒或者十幾秒,或者其他問題導(dǎo)致了實(shí)際解析出時(shí)間后已經(jīng)與實(shí)際值有差距。此時(shí)就需要對時(shí)間進(jìn)行矯正。博主在實(shí)際應(yīng)用時(shí)差了14s,這里貼一下當(dāng)時(shí)的矯正程序??赡艽蠹矣貌坏剑@里只是覺得思考的過程有意思,所以貼出來分享一下。
void RTC_Time_Correct(void) // 開機(jī)時(shí)間校正
{
// 加14秒不滿1分鐘
if (gTimeSec < 46)
{
gTimeSec = gTimeSec + 14;
}
// 加14秒滿1分鐘
else if (gTimeSec >= 46)
{
gTimeSec = gTimeSec + 14 - 60;
// 分鐘數(shù)需要加1
// 加1分鐘不滿1小時(shí)
if (gTimeMin < 59)
{
gTimeMin = gTimeMin + 1;
}
// 分鐘數(shù)加1滿1小時(shí)
else if (gTimeMin == 59)
{
gTimeMin = 0;
// 小時(shí)數(shù)需要加1
// 加1小時(shí)不滿1天
if (gTimeHour < 23)
{
gTimeHour = gTimeHour + 1;
}
// 加1小時(shí)滿1天
else if (gTimeHour == 23)
{
gTimeHour = 0;
// 天數(shù)需要加1
// 天數(shù)小于28直接加1
if (gTimeDay < 28)
{
gTimeDay = gTimeDay + 1;
}
// 天數(shù)等于28
else if (gTimeDay == 28)
{
// 當(dāng)前為二月
if (gTimeMon == 2)
{
// 閏年
if (Is_Leap_Year(gTimeYear))
{
gTimeDay = gTimeDay + 1;
}
// 當(dāng)前為2月且不是閏年
else
{
gTimeDay = 0; // 天數(shù)置零
gTimeMon = gTimeMon + 1; // 月份加1
}
}
}
// 天數(shù)等于30
else if (gTimeDay == 30)
{
// 當(dāng)前月份只有30天
if (gTimeMon == 2 || gTimeMon == 4 || gTimeMon == 6 || gTimeMon == 9
|| gTimeMon == 11)
{
gTimeDay = 0;
gTimeMon = gTimeMon + 1;
}
// 當(dāng)前月份有31天
else
{
gTimeDay = gTimeDay + 1;
}
}
// 天數(shù)等于31
else if (gTimeDay == 31)
{
gTimeDay = 0;
// 加1月不滿1年
if (gTimeMon != 12)
{
gTimeMon = gTimeMon + 1;
}
else
{
gTimeMon = 1;
gTimeYear = gTimeYear + 1;
}
}
}
}
}
}
-
實(shí)時(shí)時(shí)鐘
+關(guān)注
關(guān)注
4文章
245瀏覽量
65813 -
STM32
+關(guān)注
關(guān)注
2270文章
10904瀏覽量
356365 -
計(jì)數(shù)器
+關(guān)注
關(guān)注
32文章
2256瀏覽量
94646 -
定時(shí)器
+關(guān)注
關(guān)注
23文章
3250瀏覽量
114924 -
RTC
+關(guān)注
關(guān)注
2文章
539瀏覽量
66651
發(fā)布評論請先 登錄
相關(guān)推薦
評論