STM32的RTC學習筆記
Mcu:STM32F103RBT6
1、RTC簡介
RTC(Real Time Clock)實時時鐘,是STM32片內的一個外設,這個外設使用起來跟普通定時器有一點區別,他是獨立的一個定時器,並且能產生兩個中斷,秒中斷和鬧鐘中斷,他的時鐘源可以由外部或內部驅動,由使用者選擇,一些教程說RTC使用內部低速時鐘(LSI)的時鐘頻率不準,可能跑久了以後就會出現誤差。
2、RTC配置流程
1、使能RTC外設時鐘
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//RTC時鐘使能(電源)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);//RTC時鐘使能(備份)
2、使能備份寄存器訪問
PWR_BackupAccessCmd(ENABLE);//使能備份寄存器訪問
開啓後才能對備份寄存器進行訪問,後期可以講數據寫進備份寄存器裏,以防掉電數據丟失。
3、初始化備份寄存器
BKP_DeInit();//初始化備份配置,即復位備份區域
4、時鐘源選擇與使能
RCC_LSICmd(ENABLE);//使能內部低速時鐘
while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == 0 );//內部低速時鐘是否已開啓
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);//配置RTC時鐘爲內部低速時鐘
RCC_RTCCLKCmd(ENABLE);//使能RTC時鐘
因爲開發板原因,我用的是內部低速時鐘,時鐘頻率爲40K,先使能低速時鐘源,等待使能完畢,然後再配置它爲RTC的時鐘源。
5、寫RTC寄存器
RTC_WaitForSynchro();//等待寄存器校準
RTC_WaitForLastTask();//等待RTC寄存器寫入完成(每次向RTC寄存器寫入後都要調用這個函數)
RTC_ITConfig(RTC_IT_SEC,ENABLE);
RTC_WaitForLastTask();//等待RTC寄存器寫入完成(每次向RTC寄存器寫入後都要調用這個函數)
Rtc_Nvic_Config();
RTC_SetPrescaler(39999);//設置預分頻係數
RTC_WaitForLastTask();//等待RTC寄存器寫入完成(每次向RTC寄存器寫入後都要調用這個函數)
RTC_SetCounter(hour*3600+min*60+sec);//設置當前時間,單位爲秒
RTC_WaitForLastTask();//等待RTC寄存器寫入完成(每次向RTC寄存器寫入後都要調用這個函數)
<RTC_WaitForSynchro>等待寄存器校準函數是用於等待RTC的計數寄存器,鬧鐘寄存器和預分頻寄存器進行同步;
<RTC_WaitForLastTask>函數則用於,每次向RTC寫入數據時候,都要等待數據寫入完成後再來進行下面操作;
配置RTC中斷我是用於中斷判斷是否計數值溢出,計數值溢出則已經計了86400個數(一天86400秒),後清0;
預分頻係數配置爲1Hz,則計數器每一秒計一個數,而預分頻係數值爲n(輸入的值)+1,所以40kHz要分配成1Hz要填入數值爲39999;
設置當前時間也是設置當前的計數值,我只設置時間,沒有日期。
6、中斷優先級
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStruct.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
因爲沒有使用其他中斷,所以中斷優先級隨意。
7、中斷服務函數
uint16_t data = 31;(自己定義的全局變量)
void RTC_IRQHandler(void)
{
if(RTC_GetITStatus(RTC_IT_SEC) == 1)
{
RTC_ClearITPendingBit(RTC_IT_SEC);//清楚中斷標誌位
RTC_WaitForLastTask();//等待RTC寄存器寫入完成(每次向RTC寄存器寫入後都要調用這個函數)
if(RTC_GetCounter() == 86399)
{
data++;//每過一天,日期加1,一天爲86400秒
RTC_SetCounter(0); //計數值一到64000就清零,爲午夜12點
RTC_WaitForLastTask();//等待RTC寄存器寫入完成(每次向RTC寄存器寫入後都要調用這個函數)
}
}
}
7、LCD屏顯示函數
void RtcTime_Display(void)
{
uint32_t Time_temp = 0;
static uint8_t Run_Ping = 0;//閏年爲1,平年爲0
static uint16_t hour = 0,min = 0,sec = 0,mon = 12,year = 20;
uint8_t Rtc_arr[20],Counter_arr[20],Rtc_date[20];
Time_temp = RTC_GetCounter();
//判斷一年是閏年還是平年:
if(((year%4==0)&&(year%100!=0))||(year%400==0))Run_Ping = 1;
else Run_Ping = 0;
//判斷一個月有30天還是31天:
if(((mon==1)||(mon==3)||(mon==5)||(mon==7)||(mon==8)||(mon==10)||(mon==12))&&(data>31))
{
data = 1;
mon++;
}
if(((mon==4)||(mon==6)||(mon==9)||(mon==11))&&(data>30))
{
data = 1;
mon++;
}
//特殊月份2月做特殊處理:
if((mon==2)&&(Run_Ping==1)&&(data>29))
{
data = 1;
mon++;
}
else if((mon==2)&&(Run_Ping==0)&&(data>28))
{
data = 1;
mon++;
}
//準備跨年:
if(mon>12)
{
mon = 1;
year++;
}
//時分秒:
hour = Time_temp/3600;
min = Time_temp%3600/60;
sec = Time_temp%3600%60;
//計數值打印:RTC_GetCounter
sprintf((char *)Counter_arr,"counter = %5d",Time_temp);
LCD_DisplayStringLine(Line6,Counter_arr);
//打印日期,順便打印潤平年:
if(Run_Ping==1)
{
sprintf((char *)Rtc_date," 20%2d-%2d-%2d Run",year,mon,data);
LCD_DisplayStringLine(Line3,Rtc_date);
}
else
{
sprintf((char *)Rtc_date," 20%2d-%2d-%2d Ping",year,mon,data);
LCD_DisplayStringLine(Line3,Rtc_date);
}
//打印時間:
sprintf((char *)Rtc_arr," %2d:%2d:%2d",hour,min,sec);
LCD_DisplayStringLine(Line4,Rtc_arr);
}
心得:
1、RTC在配置上跟普通定時器不一樣,但是用法一樣,用RTC做一個時鐘和用一個通用定時器做一個時鐘基本的邏輯是一樣的,但在F429這種更高級一點的MCU裏添加了月份星期的寄存器,使用起來可能更方便;
2、一定要初始化備份寄存器的配置,不然在後面的寄存器校準就會死循環卡死,因爲它在一直等待校準完成,或者可以寫一個延時退出;
3、每次向RTC的寄存器寫入後都要等待寫入完成,若不等待,有可能寫入不進去,比如設置當前時間,就不是自己設置的當前時間開始跑了。