1、準(zhǔn)備材料
開發(fā)板(正點原子stm32f407探索者開發(fā)板V2.4)
STM32CubeMX軟件(Version 6.10.0)
keil μVision5 IDE(MDK-Arm)
ST-LINK/V2驅(qū)動
野火DAP仿真器
XCOM V2.6串口助手
2、實驗?zāi)繕?biāo)
使用STM32CubeMX軟件配置STM32F407開發(fā)板實現(xiàn)RTC周期喚醒、鬧鐘A/B事件功能,具體為在周期喚醒時利用串口輸出當(dāng)前RTC記錄時間,當(dāng)鬧鐘A/B事件發(fā)生時利用串口輸出鬧鐘A/B事件發(fā)生提示
3、實驗流程
3.0、前提知識
RTC的時鐘可以由外部低速時鐘LSE、外部高速時鐘HSE經(jīng)過2-31分頻和內(nèi)部RC振蕩LSI三種時鐘來源提供,但是一般我們都選擇使用32.768kHz的LSE作為RTC的時鐘源,因為32.768kHz的時鐘頻率可以經(jīng)過128次分頻,然后再經(jīng)過256次分頻得到一個較為精確的1Hz信號,此信號1s脈動一次,可以方便的用于更新日歷,如下圖所示 (注釋1)
另外RTC還有兩個可編程的鬧鐘A/B,如果設(shè)置了鬧鐘A/B的時間,則鬧鐘A/B設(shè)定時間會和當(dāng)前日歷時間對比,如果時間相等,會產(chǎn)生ALRA/BF事件
周期喚醒可以使用RTC內(nèi)部一個16位喚醒自動重載寄存器來實現(xiàn),周期喚醒的時鐘信號可以來自于更新日歷的1Hz(ck_spre)信號,也可以使用RTC時鐘的2/4/8/16分頻后的時鐘,設(shè)置該自動重載寄存器的值,根據(jù)時鐘頻率向上計數(shù),當(dāng)計數(shù)溢出時發(fā)生周期喚醒事件
鬧鐘A/B,周期喚醒產(chǎn)生的 ALRAF、 ALRBF和WUTF事件均可以輸出到復(fù)用引腳RTC_AF1(PC13)
STM32F407的RTC還有20個32位的備份寄存器,其名字從RTC_BKP_DR0到RTC_BKP_DR19,定義在stm32f4xx_hal_rtc_ex.h文件中,RTC和備份寄存器均由單片機(jī)的備用電源VBAT提供,主電源VDD/VDDA斷開不影響備份寄存器內(nèi)容存儲及RTC的正常運(yùn)行
3.1、CubeMX相關(guān)配置
3.1.0、工程基本配置
打開STM32CubeMX軟件,單擊ACCESS TO MCU SELECTOR選擇開發(fā)板MCU(選擇你使用開發(fā)板的主控MCU型號),選中MCU型號后單擊頁面右上角Start Project開始工程,具體如下圖所示
開始工程之后在配置主頁面System Core/RCC中配置HSE/LSE晶振,在System Core/SYS中配置Debug模式,具體如下圖所示
詳細(xì)工程建立內(nèi)容讀者可以閱讀“STM32CubeMX教程1 工程建立”
3.1.1 、時鐘樹配置
本文實驗中RTC時鐘信號源選擇為外部32.768kHz的低速時鐘LSE,首先需要在Pinout & Configuration頁面左邊System Core/RCC中將原來Disable狀態(tài)的Low Speed Clock(LSE)選擇為Crystal/Ceramic Resonator,表示外部低速時鐘LSE由32.768kHz的晶振提供,如下圖所示
然后還是在這個頁面,在Timers/RTC中單擊Activate Clock Source,激活時鐘源之后才可以對Clock Configuration頁面的時鐘修改,如下圖所示
最后在Clock Configuration頁面將輸出到RTC時鐘的時鐘源選擇為LSE,此時就已經(jīng)配置好了RTC的輸入時鐘為32.768kHz的LSE,如下圖所示
3.1.2、外設(shè)參數(shù)配置
本實驗需要需要初始化USART1作為信息輸出渠道,具體配置步驟請閱讀“STM32CubeMX教程9 USART/UART 異步通信”
單擊Pinout & Configuration頁面左邊Timers/RTC,在該頁面中間單擊Activate Calendar激活日歷
這里Alarm A、Alarm B和WakeUp均有Disable、Internal Alarm/WakeUp 和 Routed to AF1三個選項,分別表示不使用、單純內(nèi)部使用和輸出到復(fù)用引腳AF1(PC13)
注意由于AF1只有一個所以一旦某一個選擇輸出到了復(fù)用引腳AF1,其他便不可以設(shè)置,具體配置如下圖所示
然后對啟用的日歷、Alarm A、Alarm B和WakeUp參數(shù)做不同的配置,這里比較通俗易懂,具體配置請看下圖
3.1.3 、外設(shè)中斷配置
在Pinout & Configuration頁面左邊System Core/NVIC中勾選鬧鐘A/B中斷及周期喚醒中斷,然后選擇合適的中斷優(yōu)先級即可,另外串口中斷可以不打開,本節(jié)實驗輸出采用阻塞傳輸數(shù)據(jù)的方式輸出RTC時間
3.2、生成代碼
3.2.0、配置Project Manager頁面
單擊進(jìn)入Project Manager頁面,在左邊Project分欄中修改工程名稱、工程目錄和工具鏈,然后在Code Generator中勾選“Gnerate peripheral initialization as a pair of 'c/h' files per peripheral”,最后單擊頁面右上角GENERATE CODE生成工程,具體如下圖所示
詳細(xì)Project Manager配置內(nèi)容讀者可以閱讀“STM32CubeMX教程1 工程建立”實驗3.4.3小節(jié)
3.2.1、外設(shè)初始化函數(shù)調(diào)用流程
主函數(shù)中調(diào)用MX_RTC_Init()函數(shù)對RTC基本參數(shù)及日歷時間、日歷日期、鬧鐘A定時時間、鬧鐘B定時時間和周期喚醒等參數(shù)初始化/使能
在初始化RTC的函數(shù)HAL_RTC_Init()中調(diào)用了HAL_RTC_MspInit()函數(shù)完成了對RTC時鐘使能,NVIC使能,NVIC優(yōu)先級設(shè)置
如下圖所示為上述的函數(shù)調(diào)用流程
3.2.2、外設(shè)中斷函數(shù)調(diào)用流程
在stm32f4xx_it.c文件中新增了周期喚醒中斷服務(wù)函數(shù)RTC_WKUP_IRQHandler()
在該RTC_WKUP_IRQHandler()函數(shù)中調(diào)用了HAL_RTCEx_WakeUpTimerIRQHandler()函數(shù)處理周期回調(diào)事件
最終調(diào)用了虛函數(shù)HAL_RTCEx_WakeUpTimerEventCallback(),該函數(shù)需要用戶重新實現(xiàn)
如下圖所示為周期喚醒中斷函數(shù)調(diào)用流程
同時在stm32f4xx_it.c文件中新增了RTC鬧鐘A/B事件中斷服務(wù)函數(shù)RTC_Alarm_IRQHandler()
在該RTC_Alarm_IRQHandler()函數(shù)中調(diào)用了HAL_RTC_AlarmIRQHandler()函數(shù)處理鬧鐘A/B事件
最后在該函數(shù)中調(diào)用了虛函數(shù)HAL_RTC_AlarmAEventCallback()處理鬧鐘A事件,調(diào)用虛函數(shù)HAL_RTCEx_AlarmBEventCallback()處理鬧鐘B事件
如下圖所示為RTC鬧鐘A/B事件中斷函數(shù)調(diào)用流程
3.2.3、添加其他必要代碼
重新實現(xiàn)周期喚醒中斷回調(diào)函數(shù)HAL_RTCEx_WakeUpTimerEventCallback()在rtc.c中,具體實現(xiàn)代碼如下圖所示
源代碼如下
/*周期喚醒回調(diào)函數(shù)*/
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
if(HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN) == HAL_OK)
{
HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BIN);
char str[22];
sprintf(str,"RTC Time= %2d:%2d:%2d\r\n",sTime.Hours,sTime.Minutes,sTime.Seconds);
printf("%s", str);
}
HAL_GPIO_TogglePin(RED_LED_GPIO_Port,RED_LED_Pin);
}
重新實現(xiàn)鬧鐘A/B事件中斷回調(diào)函數(shù)HAL_RTC_AlarmAEventCallback()和HAL_RTCEx_AlarmBEventCallback()在rtc.c中,具體代碼如下所示
源代碼如下
/*鬧鐘A事件回調(diào)函數(shù)*/
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
char infoA[]="Alarm A(xx:xx:15) trigger: \r\n";
printf("%s", infoA);
HAL_GPIO_TogglePin(GREEN_LED_GPIO_Port,GREEN_LED_Pin);
}
/*鬧鐘B事件回調(diào)函數(shù)*/
void HAL_RTCEx_AlarmBEventCallback(RTC_HandleTypeDef *hrtc)
{
char infoB[]="Alarm B(xx:0:30) trigger: \r\n";
printf("%s", infoB);
HAL_GPIO_TogglePin(GREEN_LED_GPIO_Port,GREEN_LED_Pin);
}
此時的代碼可以正常運(yùn)行,但存在一個問題,復(fù)位后重新執(zhí)行RTC初始化函數(shù)會對RTC時間強(qiáng)制初始化為0:0:0,日期也會強(qiáng)制初始化,而我們想要設(shè)定的是當(dāng)我們需要其初始化時就初始化,當(dāng)一次初始化完畢之后,我不希望每次單片機(jī)復(fù)位時重新初始化
因此我們可以通過上述介紹的備份寄存器修復(fù)此BUG,我們在RTC通用初始化結(jié)束之后,RTC日期和時間初始化之前處,添加判斷RTC備份寄存器是否已被寫入1來決定是否需要初始化時間和日期,如果已被寫入1,則表示之前已完成日期和時間初始化,不需要再次重新初始化,因此啟動周期喚醒后直接退出函數(shù),如下圖代碼所示
源代碼如下
//讀取備份寄存R0
uint32_t iniRTC=HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0);
//非零
if((iniRTC & 0x01))
{
//使能周期喚醒
if(HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 0, RTC_WAKEUPCLOCK_CK_SPRE_16BITS) != HAL_OK)
Error_Handler();
//提前退出函數(shù),不初始化時間和日期
return;
}
何時改變/寫入RTC備份寄存器中的值呢?
這里筆者使用按鍵來控制,當(dāng)按下WK_UP按鍵時,就翻轉(zhuǎn)備份寄存器RTC_BKP_DR0中存儲的值,也就是說按下一次WK_UP按鍵,備份寄存器RTC_BKP_DR0中的值會在0/1之間改變,如下圖所示為主循環(huán)中的按鍵掃描程序
源代碼如下
uint32_t iniRTC = HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR0);
iniRTC = !iniRTC;
if(HAL_GPIO_ReadPin(WK_UP_GPIO_Port,WK_UP_Pin) == 1)
{
HAL_Delay(50);
if(HAL_GPIO_ReadPin(WK_UP_GPIO_Port,WK_UP_Pin) == 1)
{
HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR0, iniRTC);
printf("Write RTC_BKP_DR0 %d\r\n", iniRTC);
while(HAL_GPIO_ReadPin(WK_UP_GPIO_Port,WK_UP_Pin));
}
}
4、常用函數(shù)
/*RTC周期回調(diào)中斷服務(wù)函數(shù)*/
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
/*RTC鬧鐘A中斷服務(wù)函數(shù)*/
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
/*RTC鬧鐘B中斷服務(wù)函數(shù)*/
void HAL_RTCEx_AlarmBEventCallback(RTC_HandleTypeDef *hrtc)
/*查詢RTC時間*/
HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
/*查詢RTC日期*/
HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
/*讀RTC備份寄存器的值*/
uint32_t HAL_RTCEx_BKUPRead(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister)
/*寫RTC備份寄存器的值*/
void HAL_RTCEx_BKUPWrite(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister, uint32_t Data)
5、燒錄驗證
燒錄程序,通過串口助手觀察串口輸出信息,每隔1秒,串口助手收到開發(fā)板傳來的RTC時間信息,并且紅色LED每一秒狀態(tài)翻轉(zhuǎn)一次
當(dāng)時間到達(dá)0:0:15時,鬧鐘A觸發(fā),此時綠色LED燈狀態(tài)翻轉(zhuǎn)被點亮
隨著時間繼續(xù)流逝,當(dāng)時間到達(dá)0:0:30時,鬧鐘B觸發(fā),此時此時綠色LED燈狀態(tài)翻轉(zhuǎn)被熄滅
此后每分鐘的第15秒鬧鐘A會觸發(fā)一次,每小時的0分30秒鬧鐘B會觸發(fā)一次,具體串口輸出信息如下圖所示
按下WK_UP按鍵可以翻轉(zhuǎn)備份寄存器RTC_BKP_DR0內(nèi)存儲的值,當(dāng)備份寄存器RTC_BKP_DR0的值為1時,復(fù)位之后RTC的時間不會重置為0;
而當(dāng)備份寄存器RTC_BKP_DR0的值為0時,復(fù)位之后RTC的時間會被重新初始化為0:0:0,串口輸出信息如下圖所示
為什么上圖中RTC時間為0:0:15時刻的鬧鐘A沒有響應(yīng)?
這是因為備份寄存器RTC_BKP_DR0的值為1時,我們在MX_RTC_Init初始化函數(shù)中初始化完畢RTC之后直接啟動了周期喚醒然后整個函數(shù)就退出了,并沒有對RTC的鬧鐘A/B進(jìn)行初始化,如果你想兼顧兩者功能,也可以編寫程序不直接退出,而是繞過RTC時間和日期賦初值的代碼,然后執(zhí)行RTC的鬧鐘A/B的初始化
6、注釋詳解
注釋1:圖片來源STM32F4xx中文參考手冊 RM0090文章來源:http://www.zghlxwxcb.cn/news/detail-777170.html
參考資料
STM32Cube高效開發(fā)教程(基礎(chǔ)篇)文章來源地址http://www.zghlxwxcb.cn/news/detail-777170.html
到了這里,關(guān)于STM32CubeMX教程10 RTC 實時時鐘 - 周期喚醒、鬧鐘A/B事件和備份寄存器的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!