9.1 實驗內容
通過本實驗主要學習以下內容:
? RTC簡介
? RTC復位
? RTC實現萬年歷
? RTC使用注意事項
9.2 實驗原理
9.2.1 RTC簡介
RTC(Real Time Clock)——實時時鐘定時器,可以用作日歷。RTC 電路分兩個電源域部分,其一位于備份域中,該部分包括一個 32 位的累加計數器、一個鬧鐘、一個預分頻器、一個分頻器以及RTC時鐘配置寄存器。備份域這部分電路不會因為系統復位或者MCU進入低功耗而丟失數據,所以在系統復位或MCU從低功耗下喚醒,RTC 的設置和時間都可以保持不變。另一部分位于VDD 電源域中,該部分只包括 APB 接口以及一組控制寄存器。
關于RTC的兩個電源域及分布在兩個電源域中的內容,需要讀者牢記,否則會由于細節(jié)沒處理好導致RTC工作異常。
以下為GD32F303的RTC框圖:
圖中RTC_CNT為計數值,每個SC_CLK時鐘到來時,這個計數值增加+1。SC_CLK時鐘源有三個:LXTAL(外部低速晶振)、IRC40K(內部40K晶振)和HAXTAL/128,三個時鐘源經過RTC_DIV產生SC_CLK。RTC_DIV的配置是通過RTC_PSC加載所得,RTC_PSC由寄存器RTC 預分頻寄存器高位 (RTC_PSCH)和RTC 預分頻寄存器低位 (RTC_PSCL)配置,有效位20bit,即RTC的時鐘分配器最大置位2的20次方,完全足夠應用了。
了解了上面的內容,RTC的工作原理就很好理解了。舉個例子,RTC的時鐘源選擇LXTAL,頻率為32.768KHz,然后設置RTC_PSC為32768,那么SC_CLK即為1Hz,也就是說,每秒鐘RTC_CNT值+1,所以我們只要獲得一段時間內RTC_CNT值增加了多少數,也就知道經過了多少秒。
我們從手冊中可以看到RTC_CNT由RTC 計數寄存器高位 (RTC_CNTH)和RTC 計數寄存器低位 (RTC_CNTL)設置,這兩個寄存器組合起來的有效位為32bit,即RTC_CNT可以記錄2的32次方,即4,294,967,296個數,按照每秒增加一次的話,可以記錄136多年,
除了基礎的記時間的功能,RTC還有一個鬧鐘功能,RTC運行時,當RTC_CNT的值增加到和RTC_ALRM(由RTC 鬧鐘寄存器高位 (RTC_ALRMH)和RTC 鬧鐘寄存器低位 (RTC_ALRML)設置)相等時,則會產生ALRM中斷,當然,程序中需要實現使能ALARM中斷(ALRMIE)。
RTC還有另外兩個中斷,一個是秒中斷,另一個是溢出中斷。秒中斷好理解,即每秒鐘進入一個中斷;溢出中斷則是當RTC_CNT溢出時產生中斷。
9.2.2 RTC復位
這里把RTC的復位單獨作為一個章節(jié)來說,是因為這里很容易出錯導致想要實現的功能無法實現。
前面說過,RTC分為兩個電源域——備份域和VDD電源域,而一般的復位比如NRST腳復位、軟件復位等只能復位VDD和VDDA電源域 ,而無法復位備份域;備份域復位需要VBAT掉電或者通過備份域控制寄存器( RCU_BDCTL)的BKPDRST來進行備份域復位。
上節(jié)中的RTC框圖中被深色框住的即屬于備份域,這里提到一個很容易出錯的地方,即RTC的時鐘源選擇。從框圖看到時鐘源由RTCSRC[1:0]來設置,這個位域屬于備份域控制寄存器( RCU_BDCTL)。
備份域控制寄存器( RCU_BDCTL):
寄存器描述中指出,一旦RTC的時鐘源選擇后,除了將備份域復位,否則時鐘不能被改變。舉個例子:一個產品選擇LXTAL作為RTC時鐘,但可能因為某些原因LXTAL停振了,需要將時鐘源切換到IRC40K,程序如何實現呢?沒錯,需要復位備份域(控制BKPDRST位)才能重新選擇時鐘源,但一旦備份域進行了復位,包括RTC_CNT等數據都會丟失,所以在備份域復位前需要對RTC內的各個數據進行保存處理,待備份域復位后再重新寫入。
9.2.3 RTC實現萬年歷
本實驗用RTC做一個萬年歷,其中還需要考慮到閏年閏月的情況。實驗設置的基準時間是1970年,即當RTC_CNT為0時,為1970年,實驗最高可記錄到2106年(1979+136)。
9.3 硬件設計
本實驗實現每秒鐘通過串口打印實時時間,即需要使用到開發(fā)板USB串口模塊。
9.4 代碼解析
9.4.1 RTC配置
在driver_rtc.c中定義了RTC配置函數rtc_configuration
C
void rtc_configuration(void)
{
/*使能備份域和PMU時鐘*/
rcu_periph_clock_enable(RCU_BKPI);
rcu_periph_clock_enable(RCU_PMU);
/*使能備份域寫功能*/
pmu_backup_write_enable();
/*備份域復位*/
bkp_deinit();
/*依據選擇的時鐘源進行時鐘配置*/
/*使用LXTAL*/
/* 使能 LXTAL */
rcu_osci_on(RCU_LXTAL);
/* 等待LXTAL Ready */
rcu_osci_stab_wait(RCU_LXTAL);
/*選擇LXTAL作為RTC時鐘*/
rcu_rtc_clock_config(RCU_RTCSRC_LXTAL);
/*使用IRC40K*/
/* 使能 IRC40K*/
rcu_osci_on(RCU_IRC40K);
/* 等待IRC40K Ready */
rcu_osci_stab_wait(RCU_IRC40K);
/*選擇IRC40K作為RTC時鐘*/
rcu_rtc_clock_config(RCU_RTCSRC_IRC40K);
/* 使能LXTAL */
rcu_osci_on(RCU_HXTAL);
/* 等待HXTAL Ready */
rcu_osci_stab_wait(RCU_HXTAL);
/*選擇HXTAL/128作為RTC時鐘*/
rcu_rtc_clock_config(RCU_RTCSRC_HXTAL_DIV_128);
/*使能RTC時鐘*/
rcu_periph_clock_enable(RCU_RTC);
/*等待RTC寄存器同步*/
rtc_register_sync_wait();
/*等待上一次操作完成*/
rtc_lwoff_wait();
/*使能RTC秒中斷*/
rtc_interrupt_enable(RTC_INT_SECOND);
/*等待上一次操作完成*/
rtc_lwoff_wait();
/*依據選擇的時鐘源來設置分頻,使RTC周期為1s*/
rtc_prescaler_set(32767);
rtc_prescaler_set(40000);
rtc_prescaler_set(HXTAL_VALUE/128);
/*等待上一次操作完成*/
rtc_lwoff_wait();
}
時鐘源通過driver_rtc.h中的宏定義來選擇:
C
//#define RTC_CLOCK_SOURCE_IRC40K
//#define RTC_CLOCK_SOURCE_HXTAL_DIV_128
9.4.2 萬年歷實現
在bsp_rtc.c中定義了實現萬年歷的幾個函數:rtc_time_set、rtc_time_display、is_leap_year等。
rtc_time_set函數——第一次需要手動設置當前時間:
C
uint32_t rtc_time_set(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second)
{
uint16_t t;
uint32_t seccount = 0;
if (bkp_read_data(BKP_DATA_0) != 0xA5A5)
{
rtc_configuration();
if(year < 1970 || year > 2099)
return 1;
for(t = 1970; t < year; t++){
if(is_leap_year(t)){
seccount += 31622400;
}else{
seccount += 31536000;
}
}
month -= 1;
for(t=0; t < month; t++){
seccount += (uint32_t)month_table[t] * 86400;
if(is_leap_year(year) && t==1){
seccount+=86400;
}
}
seccount += (uint32_t)(day-1) * 86400;
seccount += (uint32_t)hour * 3600;
seccount += (uint32_t)minute * 60;
seccount += second;
rtc_counter_set(seccount);
bkp_write_data(BKP_DATA_0, 0xA5A5);
return 0;
}
else
{
if (rcu_flag_get(RCU_FLAG_PORRST) != RESET){
printf("Power On Reset occurred....\r\n");
}else if (rcu_flag_get(RCU_FLAG_EPRST) != RESET){
printf("External Reset occurred....\r\n");
}
rcu_all_reset_flag_clear();
rcu_periph_clock_enable(RCU_PMU);
pmu_backup_write_enable();
rtc_register_sync_wait();
rtc_interrupt_enable(RTC_INT_SECOND);
rtc_lwoff_wait();
return 0;
}
}
該函數中在第一次上電運行時,會進行初始時間設置,然后寫入特定數據到備份域數據寄存器中。當發(fā)生系統復位但Vbat未掉電的情況下,則不會重新進行時間設置,但需要重新開啟秒時鐘中斷,因為SCIE處于VDD電源域而不在備份域。
rtc_time_display函數——打印實時時間:
C
void rtc_time_display(uint32_t timevar)
{
static uint16_t daycnt = 0;
uint32_t temp = 0;
uint16_t temp1 = 0;
temp = timevar / 86400;
if(daycnt != temp) {
daycnt = temp;
temp1 = 1970;
while(temp >= 365){
if(is_leap_year(temp1)){
if(temp >= 366)
temp-=366;
else
break;
}else
temp -= 365;
temp1++;
}
calendar.years = temp1;
temp1=0;
while(temp >= 28)
{
if(is_leap_year(calendar.years) && temp1 == 1){
if(temp >= 29)
temp -= 29;
else
break;
}else{
if(temp >= month_table[temp1])
temp -= month_table[temp1];
else
break;
}
temp1++;
}
calendar.months = temp1 + 1;
calendar.days = temp + 1;
}
temp = timevar % 86400;
calendar.hours = temp / 3600;
calendar.minutes = (temp % 3600) / 60;
calendar.seconds = (temp % 3600) % 60;
printf("Time: %0.4d-%0.2d-%0.2d,%0.2d:%0.2d:%0.2d\r\n", calendar.years, calendar.months, calendar.days, calendar.hours, calendar.minutes, calendar.seconds);
}
is_leap_year函數——判斷當前年是否為閏年:
C
uint8_t is_leap_year(uint16_t year)
{
if((year%4 == 0 && year % 100 != 0) || (year % 400 == 0)){
return 1;
}else{
return 0;
}
}
9.4.3 main函數和中斷函數實現
以下為main函數代碼:
C
int main(void)
{
delay_init();//delay函數初始化
bsp_uart_init(&BOARD_UART);//BOARD_UART串口初始化
nvic_irq_enable(RTC_IRQn,1,0);//打開RTC的NVIC
rtc_time_set(year_set,month_set,day_set,hour_set,minute_set,second_set); //設置當前時間
while (1){
/* 判斷是否到1s了*/
if (timedisplay == 1){
/* 顯示實時時間*/
rtc_time_display(rtc_counter_get());
timedisplay = 0;
}
}
}
本例程main函數首先進行了延時函數初始化,再初始化開發(fā)板USB串口,開啟RTC的NVIC后設置了當前時間,在while(1)循環(huán)中等待RTC數據更新,然后將實時時間打印出來。
中斷函數代碼:
C
void RTC_IRQHandler(void)
{
if (rtc_flag_get(RTC_FLAG_SECOND) != RESET){
/* 清除RTC秒中斷標志位*/
rtc_flag_clear(RTC_FLAG_SECOND);
timedisplay = 1;
/* 等待上一次操作完成 */
rtc_lwoff_wait();
}
}
9.5 實驗結果
為了驗證萬年歷是否工作正常,設定初始時間為2022年12月31日23時59分50s,當程序開始運行時,每秒鐘打印時間,經過10s后,可以看到時間變?yōu)?023年1月1日0時0分0秒,說明萬年歷有效。
紅楓派開發(fā)板使用手冊:??????????????????????????????????????????????????GD32F303紅楓派使用手冊 - 飛書云文檔 (feishu.cn)