目錄
一、備份寄存器的功能
二、示例功能
三、項目設置
1、晶振、DEBUG、CodeGenerator、USART6
2、RTC
3、NVIC?
4、GPIO 及KEYLED
四、軟件設計
1、main.h
2、main.c
3、rtc.c
4、keyled.c、keyled.h
五、運行調試
????????本實例旨在介紹備份寄存器的作用。本實例繼續使用旺寶紅龍開發板STM32F407ZGT6 KIT V1.0。本實例將引用本文作者寫的其他文章作為參考文獻。
????????參考項目:細說STM32F407單片機RTC的基本原理及鬧鐘和周期喚醒功能的使用方法-CSDN博客 ?https://wenchm.blog.csdn.net/article/details/145575366
一、備份寄存器的功能
????????STM32F407的RTC有20個32位的備份寄存器,寄存器名稱為RTC_BKP0R~RTC_BKP19R。這些備份寄存器由備用電源VBAT供電,在系統復位或主電源關閉時,只要VBAT有電,備份寄存器的內容就不會丟失。所以,備份寄存器可以用來存儲一些用戶數據。
????????文件stm32f4xx_hal_rtc_ex.h中有讀寫備份寄存器的功能函數,其原型定義如下:
uint32_t HAL_RTCEx_BKUPRead(RTC_HandleTypeDef *hrtc,uint32_t BackupReg);
void HAL_RTCEx_BKUPWrite(RTC_HandleTypeDef *hrtc,uint32_t BackupReg,uint32_t Data);
????????其中,參數BackupReg是備份寄存器編號,文stm32f4xx_hal_rtc_ex.h定義了20個備份寄存器編號的宏,定義如下:
#define RTC_BKP_DR0 0x00000000U
#define RTC_BKP_DR1 0x00000001U
/*省略了中間的定義代碼*/
#define RTC_BKP_DR18 0x00000012U
#define RTC_BKP_DR19 0x00000013U
????????RTC也可以由VBAT供電,在系統復位或主電源關閉時,RTC的日歷不受影響。但是在參考項目中,因為在系統復位時調用了RTC初始化函數MX_RTC_Init(),而這個函數總是設置RTC的日期和時間,所以復位后,RTC的日期時間又從CubeMX里設置的初始日期時間開始了。
????????可以使用備份寄存器對參考項目進行修改,使得系統在復位時不再自動設置RTC的初始日期時間,而是由備份寄存器RTC_BKP_DR0的內容決定,而且可以把RTC當前時間保存到備份寄存器。
二、示例功能
????????本實例,將設計一個示例,演示備份寄存器的使用。示例具有如下的功能和操作流程。
- 在RTC初始化函數MX_RTC_Init()中增加代碼,讀取備份寄存器RTC_BKP_DR0的內容,如果值為0,就設置RTC為初始時間和日期,如果值為1,就不設置RTC的日期和時間。
- 在程序運行時,可以修改保存到備份寄存器RTC_BKP_DR0的值,可以保存0或1。
- 將RTC當前時間的時、分、秒數據保存到RTC_BKP_DR2到RTC_BKP_DR4的3個寄存器里。RTC_BKP_DR1里的值為1時,表示保存了RTC時間。
- 讀取備份寄存器RTC_BKP_DR0到RTC_BKP_DR4的內容,顯示到串口助手上。
- 使用RTC周期喚醒中斷,喚醒中斷周期1s,在喚醒中斷里讀取時間后在串口助手上顯示。本示例還用到4個按鍵和2個LED,所以繼續引用作者發布的其它參考項目中的KEYLED文件中的程序文件。
- 菜單設計:
[S2]KeyUp ? = Reset RTC time on startup. //按下S2鍵,用初始時間重置RTC
[S3]KeyDown = Read BKUP registers. //按下S3鍵,讀取備份寄存器
[S4]KeyLeft = Save current time to BKUP. //按下S4鍵,把當前時間保存到RTC備份寄存器
[S5]KeyRight= Change RTC time from BKUP. //按下S5鍵,用備份寄存器存儲的時間重置RTC時間
三、項目設置
1、晶振、DEBUG、CodeGenerator、USART6
? ? ? ? 與參考文章相同。?
2、RTC
????????在RTC的模式設置中,啟用時鐘源和日歷,設置WakeUp模式為Internal WakeUp。在參數設置部分,設置數據格式為Binary data format,設置喚醒時鐘為1Hz信號,喚醒周期數為0,這樣就會每秒產生一次喚醒中斷。
3、NVIC?
????????在NVIC中開啟周期喚醒中斷,設置RTC搶占優先級為1。
4、GPIO 及KEYLED
????????4個按鍵和2個LED。引用文件夾KEYLED的方法請參考本文作者發布的其它文章。
程序標簽 | 板上名稱 | 引腳名稱 | 引腳功能 | GPIO模式 | 默認電平 | 上拉或下拉 |
LED1 | D1 | PA6 | GPIO_Output | 推挽輸出 | High | 上拉 |
LED2 | D2 | PA4 | GPIO_Output | 推挽輸出 | High | 上拉 |
KeyRight | S5 | PF6 | GPIO_Input | 輸入 | 上拉 | |
KeyDown | S3 | PD3 | GPIO_Input | 輸入 | 上拉 | |
KeyLeft | S4 | PF7 | GPIO_Input | 輸入 | 上拉 | |
KeyUp | S2 | PA0 | GPIO Input | 輸入 | 上拉 |
?
四、軟件設計
1、main.h
/* USER CODE BEGIN Private defines */
void RTC_ToggleReset();
void RTC_SaveToBKUP();
void RTC_LoadFromBKUP();
void RTC_ReadBKUP();
/* USER CODE END Private defines */
2、main.c
/* USER CODE BEGIN Includes */
#include "keyled.h"
#include <stdio.h>
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
RTC_TimeTypeDef sTime; //RTC讀取時間和日期的結果數據
RTC_DateTypeDef sDate;uint8_t RTC_isReading=0; //RTC是否正在讀日期時間數據
/* USER CODE END PV */
/* USER CODE BEGIN 2 */__HAL_RTC_WAKEUPTIMER_DISABLE(&hrtc); //禁止RTC周期喚醒printf("Demo11_2_RTC_Backup:Using Backup Registers.\r\n");uint32_t iniRTC = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0); //讀取備份寄存DR0printf("Reset RTC time on startup: ");if ((iniRTC & 0x01)==0)printf("Yes.\r\n");elseprintf("No.\r\n");/* USER CODE END 2 */
/* Infinite loop *//* USER CODE BEGIN WHILE *///顯示菜單,也可以放在USER CODE BEGIN 2里printf("[S2]KeyUp = Reset RTC time on startup(LED2_ON).\r\n");printf("[S3]KeyDown = Read BKUP registers.\r\n");printf("[S4]KeyLeft = Save current time to BKUP.\r\n");printf("[S5]KeyRight= Change RTC time from BKUP.\r\n\r\n");__HAL_RTC_WAKEUPTIMER_ENABLE(&hrtc); //重啟RTC周期喚醒while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */KEYS curKey=ScanPressedKey(KEY_WAIT_ALWAYS);switch(curKey){case KEY_UP: //[S2]KeyUp,改變保存到備份寄存器DR0的數值0 or 1RTC_ToggleReset();break;case KEY_DOWN: //[S3]KeyDown,讀取5個備份寄存器的數據并顯示RTC_ReadBKUP();case KEY_LEFT: //[S4]KeyLeft,將RTC當前時間保存到備份寄存器RTC_SaveToBKUP();break;case KEY_RIGHT: //[S5]KeyRight,從備份寄存器讀取時間,改變RTC時間RTC_LoadFromBKUP();break;default:break;}HAL_Delay(300); //消抖}/* USER CODE END 3 */
????????main()函數里在完成了外設初始化之后,執行__HAL_RTC_WAKEUPTIMER_DISABLE (&hrtc),禁止了RTC周期喚醒單元。在進入while死循環之前,再開啟RTC的周期喚醒單元。
????????調用函數HAL_RTCEx_BKUPRead()讀取備份寄存器RTC_BKP_DR0的內容,如果寄存器的值為0,就會在MX_RTC_Init()里用CubeMX設置的初始日期和時間設置RTC的日期時間;如果寄存器的值不為0,在MX_RTC_Init()里就不會設置RTC的日期和時間。后一種情況下,在按復位鍵[S6]使系統復位時,RTC仍然保持連續的時間,因為RTC工作在備份域,不會在系統復位時復位。
????????然后,在串口助手上顯示了一個模擬菜單。
????????在while循環里讀取按鍵輸入,用4個按鍵模擬菜單項的選擇,按下某個按鍵后就執行與菜單項對應的功能。按鍵的響應代碼封裝為4個函數。
/* USER CODE BEGIN 4 */
//周期喚醒中斷回調函數
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{LED1_Toggle();RTC_isReading = 1; //RTC正在讀取數據,該變量的作用類似于RTOS中的二值信號量if (HAL_RTC_GetTime(hrtc, &sTime, RTC_FORMAT_BIN) == HAL_OK){//調用HAL_RTC_GetTime()之后必須調用HAL_RTC_GetDate()以解鎖數據,才能連續更新日期和時間HAL_RTC_GetDate(hrtc, &sDate, RTC_FORMAT_BIN);RTC_isReading = 0; //RTC結束了讀取數據//顯示時間hh:mm:sschar str[40];sprintf(str,"RTC Time = %2d:%2d:%2d",sTime.Hours,sTime.Minutes,sTime.Seconds);printf(" %s\r\n",str);}
}/* 翻轉保存備份寄存器RTC_BKP_DR0的值 */
void RTC_ToggleReset() //S2
{uint32_t iniRTC = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0);iniRTC = !iniRTC;if ((iniRTC & 0x01)==0){ //存儲值為0時,用CubeMX中的值復位日期時間printf("Reset RTC time on startup.\r\n");LED2_ON();}else{printf("Not reset RTC time on startup.\r\n");LED2_OFF();}//__HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc); //取消寫保護HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR0, iniRTC); //寫入備份寄存器DR0//__HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc); //開啟寫保護
}/* RTC時間保存到備份寄存器 */
void RTC_SaveToBKUP() //S4
{while (RTC_isReading) //如果RTC的WakeUp中斷里正在讀取數據,就等待其讀取完HAL_Delay(1);//__HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc); //取消寫保護HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1, 0x01); //0x01=保存了時間的標志HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR2, sTime.Hours); //使用全局變量sTime,不再讀取當前時間HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR3, sTime.Minutes);HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR4, sTime.Seconds);//__HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc); //開啟寫保護char timeStr[30];sprintf(timeStr,"%2d:%2d:%2d",sTime.Hours,sTime.Minutes,sTime.Seconds); //轉換為字符串,自動添加"\0"printf("Current time %s is saved in BKUP.\r\n",timeStr);
}/* 用保存的時間設置為RTC時間 */
void RTC_LoadFromBKUP() //S5
{uint32_t isTimeSaved = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1); //讀取備份寄存DR1if (isTimeSaved){RTC_TimeTypeDef time;time.Hours = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);time.Minutes = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR3);time.Seconds = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR4);time.DayLightSaving = RTC_DAYLIGHTSAVING_NONE; //這兩個參數還是有用的,如果不設置這2個參數,設置的小時數會自動減1time.StoreOperation = RTC_STOREOPERATION_SET;//HAL_Delay(10); //必須加這個延時,否則設置的時間會錯亂while(RTC_isReading) //如果RTC正在WakeUp中斷里讀取數據,就等待其讀完HAL_Delay(1);/*函數HAL_RTC_SetTime()內部在修改RTC的寄存器之前,會調用 __HAL_RTC_WRITEPROTECTION_DISABLE()取消寫保護,寫完之后,會調用 __HAL_RTC_WRITEPROTECTION_ENABLE() 重新開啟寫保護 */if (HAL_RTC_SetTime(&hrtc, &time, RTC_FORMAT_BIN) == HAL_OK)printf("Load and set BKUP time, success.\r\n");elseprintf("Load and set BKUP time, failure.\r\n");}elseprintf("No BKUP time is saved.\r\n");
}/* 讀取5個備份寄存器內容并顯示 */
void RTC_ReadBKUP() //S3
{uint32_t regValue;char regStr[40];regValue = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0); //0=系統復位時復位RTC時間sprintf(regStr,"Reset RTC ,BKP_DR0= %lu",regValue); //轉換為字符串,自動添加"\0"printf(" %s\r\n",regStr);regValue=HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1); //1=有時間數值sprintf(regStr,"Time is saved,BKP_DR1= %lu",regValue);printf(" %s\r\n",regStr);regValue=HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2); //Hoursprintf(regStr,"Saved time(Hour) ,BKP_DR2= %lu",regValue);printf(" %s\r\n",regStr);regValue=HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR3); //Minutesprintf(regStr,"Saved time(Min) , BKP_DR3= %lu",regValue);printf(" %s\r\n",regStr);regValue=HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR4); //Secondsprintf(regStr,"Saved time(Sec) , BKP_DR4= %lu",regValue);printf(" %s\r\n",regStr);
}//串口打印
int __io_putchar(int ch)
{HAL_UART_Transmit(&huart6,(uint8_t*)&ch,1,0xFFFF);return ch;
}
/* USER CODE END 4 */
????????回調函數HAL_RTCEx_WakeUpTimerEventCallback()處理RTC周期喚醒中斷。
????????其余的4個函數,是對4個菜單項的響應代碼的封裝。
3、rtc.c
/* USER CODE BEGIN Check_RTC_BKUP */uint32_t iniRTC = HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR0); //讀取備份寄存DR0if ((iniRTC & 0x01)) //非零,無需初始化RTC日期時間{if (HAL_RTCEx_SetWakeUpTimer_IT(&hrtc,0,RTC_WAKEUPCLOCK_CK_SPRE_16BITS) != HAL_OK)Error_Handler();return; //提前退出函數}/* USER CODE END Check_RTC_BKUP */
? ? ? ? 沙箱中添加的代碼就是用函數HAL_RTCEx_BKUPRead()讀取備份寄存器RTC_BKP_DR0的內容,如果寄存器的值不為0,就在執行完HAL_RTCEx_SetWakeUpTimer_IT()設置和啟動周期喚醒后,退出函數;如果寄存器的值為0,就繼續執行函數內后面的代碼。
????????這樣,就在函數MX_RTC_Init()中加入了用戶功能代碼,使得在系統復位時,RTC不必設置初始日期和時間。
4、keyled.c、keyled.h
? ? ? ? 引用KEYLED文件夾下的?keyled.c、keyled.h,使用方法請參考本文作者發布的其他文章。
五、運行調試
? ? ? ? 首次下載,或按S6鍵復位,顯示系統菜單。重要的信息是顯示當前從哪里重置RTC時間的信息。如果YES(LED2亮)則從RTC初始化值(15:30:0)重置RTC時間,否則不是;
? ? ? ? 按S2鍵切換從哪里重置RTC時間。比如,下圖,每次按下S6復位鍵,系統都從RTC初始化值重置時間,否則不重置時間,即當LED2燈不亮(DR0=1)時,按下復位鍵S6,不改變RTC時間,連續顯示RTC時間。
?
? ? ? ? ?按S4鍵,存儲當前時間到備份寄存器;按S3鍵,讀取備份存儲器內容并保存當前最新值到備份寄存器;按S5鍵,用備份寄存器內容重置RTC時間;
?