Unix時間戳
Unix時間戳,也稱為POSIX時間或Epoch時間,是一種在Unix和類Unix操作系統中使用的時間表示方法。它表示的是自1970年1月1日00:00:00 UTC(協調世界時)至當前時間經過的秒數,不考慮閏秒。Unix時間戳通常以秒為單位,也可以表示為毫秒、微秒等更小的時間單位。
Unix時間戳的特點:
-
簡單性:Unix時間戳是一個10位數的整數(在32位系統中),表示從Epoch至某一時刻經過的秒數,易于計算和處理。
-
通用性:Unix時間戳被廣泛用于Unix、Linux、macOS、Windows等操作系統中,以及各種編程語言和數據庫系統中。
-
連續性:Unix時間戳是連續的,不受時區、夏令時等因素的影響。
-
易轉換:Unix時間戳可以方便地轉換為各種日期時間格式,如ISO 8601、RFC 2822等。
BKP備份寄存器
BKP是“Backup Registers”的縮寫,中文意思是備份寄存器。它用于存儲用戶應用程序數據,在主電源VDD(2.0~3.6V)被切斷的情況下,這些數據仍然由備用電源VBAT(1.8~3.6V)維持供電。BKP寄存器在STM32微控制器中通常用于存儲重要的數據,如RTC(實時時鐘)的校驗值或其他關鍵信息,即使在系統斷電的情況下也能保持數據不丟失。BKP寄存器是位于備份域的,當VDD電源被切斷,它們仍然由VBAT維持供電。當系統在待機模式下被喚醒,或系統復位或電源復位時,它們也不會被復位。在STM32中,BKP寄存器的數量可能有所不同,例如STM32F103系列有10個16位寬度的BKP寄存器。
RTC實時時鐘
在秒計數器讀取時間,得到秒數,然后使用time.h里的localtime函數,就可以直到年月日時分秒的信息了,再填充struct tm結構體,用mktime函數得到秒數,寫入32位計數器即可
1. 輸入時鐘源
-
PCLK1:這是來自微控制器的時鐘信號,用于驅動RTC預分頻器和備份區域。
-
RTCCLK:這是RTC模塊的時鐘輸入,可以由外部晶振或內部RC振蕩器提供。
2. RTC預分頻器
-
RTC_PRL 和 RTC_DIV:這些寄存器用于設置RTC時鐘的預分頻值。通過調整這些值,可以控制RTC計數器的時鐘頻率。RTC_DIV來一個時鐘自減一次,計數器溢出一次,產生一個輸出脈沖,分頻后是1hz
3. RTC計數器
-
82位可編程計數器 RTC_CNT:這是RTC的核心計數器,用于跟蹤時間。它是一個可編程的計數器,可以配置為不同的時間單位(如秒、分鐘、小時等)。
4. RTC報警
-
RTC_ALR:RTC報警寄存器,用于設置一個特定的時間點,當RTC計數器達到這個時間點時,可以觸發一個中斷或事件。
5. RTC控制寄存器(RTC_CR)
-
SECIE, ALRIE, OWIE:這些是RTC的中斷使能位,分別用于使能秒中斷、報警中斷和溢出中斷。
-
SECIF, ALRIF, OWIF:這些是RTC的中斷標志位,用于指示相應的中斷事件是否發生。
6. 中斷控制器
-
NVIC中斷控制器:RTC模塊可以通過NVIC(嵌套向量中斷控制器)向微控制器的主處理器發送中斷請求。
7. 備用區域
-
APB1總線和APB1接口:RTC模塊通過APB1總線與微控制器的其他部分通信。APB1接口用于配置RTC和訪問其寄存器。
8. 喚醒功能
-
WKUP pin:這是RTC的喚醒引腳,可以在RTC計數器達到預設值時喚醒微控制器。
首先三個時鐘煊LSE當作RTCCLK,RTCCLK通過預分頻器對時鐘進行分頻,余數寄存器是一個自減計數器,存儲當前的計數值,重裝寄存器是計數目標,決定分頻值,分頻之后,得到1hz的秒計數信號,通過CNT32位計數器,一秒自增一次
配置數據選擇器,選擇時鐘來源;配置重裝寄存器,可以選擇分頻系數;配置32位計數器,可以進行日期時間的讀寫
代碼示例
代碼1:讀寫備份寄存器
?
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"uint8_t KeyNum; //定義用于接收按鍵鍵碼的變量uint16_t ArrayWrite[] = {0x1234, 0x5678}; //定義要寫入數據的測試數組
uint16_t ArrayRead[2]; //定義要讀取數據的測試數組int main(void)
{/*模塊初始化*/OLED_Init(); //OLED初始化Key_Init(); //按鍵初始化/*顯示靜態字符串*/OLED_ShowString(1, 1, "W:");OLED_ShowString(2, 1, "R:");/*開啟時鐘*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //開啟PWR的時鐘RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); //開啟BKP的時鐘/*備份寄存器訪問使能*/PWR_BackupAccessCmd(ENABLE); //使用PWR開啟對備份寄存器的訪問while (1){KeyNum = Key_GetNum(); //獲取按鍵鍵碼if (KeyNum == 1) //按鍵1按下{ArrayWrite[0] ++; //測試數據自增ArrayWrite[1] ++;BKP_WriteBackupRegister(BKP_DR1, ArrayWrite[0]); //寫入測試數據到備份寄存器BKP_WriteBackupRegister(BKP_DR2, ArrayWrite[1]);OLED_ShowHexNum(1, 3, ArrayWrite[0], 4); //顯示寫入的測試數據OLED_ShowHexNum(1, 8, ArrayWrite[1], 4);}ArrayRead[0] = BKP_ReadBackupRegister(BKP_DR1); //讀取備份寄存器的數據ArrayRead[1] = BKP_ReadBackupRegister(BKP_DR2);OLED_ShowHexNum(2, 3, ArrayRead[0], 4); //顯示讀取的備份寄存器數據OLED_ShowHexNum(2, 8, ArrayRead[1], 4);}
}
沒有備用電源,主電源掉電后不會備份
代碼2:是實時時鐘
第一步:開啟PWR和BKP時鐘,使能BKP和RTC訪問
第二步:啟動RTC時鐘(手動開啟LSE,這個時鐘默認關閉)
第三步:配置RTCCLK這個數據選擇器,指定LSE為RTCCLK
第四步:調用等待同步、等待上一次操作完成的函數
第五步:配置預分頻器,給PRL重裝寄存器一個合適的分頻值,以確保輸出給計數器的頻率是1HZ
第六步:配置CNT的值,給RTC一個初始時間
#include "stm32f10x.h" // Device header
#include <time.h>uint16_t MyRTC_Time[] = {2023, 1, 1, 23, 59, 55}; //定義全局的時間數組,數組內容分別為年、月、日、時、分、秒void MyRTC_SetTime(void); //函數聲明/*** 函 數:RTC初始化* 參 數:無* 返 回 值:無*/
void MyRTC_Init(void)
{/*開啟時鐘*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //開啟PWR的時鐘RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); //開啟BKP的時鐘/*備份寄存器訪問使能*/PWR_BackupAccessCmd(ENABLE); //使用PWR開啟對備份寄存器的訪問if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) //通過寫入備份寄存器的標志位,判斷RTC是否是第一次配置//if成立則執行第一次的RTC配置{RCC_LSEConfig(RCC_LSE_ON); //開啟LSE時鐘while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET); //等待LSE準備就緒RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //選擇RTCCLK來源為LSERCC_RTCCLKCmd(ENABLE); //RTCCLK使能RTC_WaitForSynchro(); //等待同步RTC_WaitForLastTask(); //等待上一次操作完成RTC_SetPrescaler(32768 - 1); //設置RTC預分頻器,預分頻后的計數頻率為1HzRTC_WaitForLastTask(); //等待上一次操作完成MyRTC_SetTime(); //設置時間,調用此函數,全局數組里時間值刷新到RTC硬件電路BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); //在備份寄存器寫入自己規定的標志位,用于判斷RTC是不是第一次執行配置}else //RTC不是第一次配置{RTC_WaitForSynchro(); //等待同步RTC_WaitForLastTask(); //等待上一次操作完成}
}//如果LSE無法起振導致程序卡死在初始化函數中
//可將初始化函數替換為下述代碼,使用LSI當作RTCCLK
//LSI無法由備用電源供電,故主電源掉電時,RTC走時會暫停
/*
void MyRTC_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);PWR_BackupAccessCmd(ENABLE);if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5){RCC_LSICmd(ENABLE);while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET);RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);RCC_RTCCLKCmd(ENABLE);RTC_WaitForSynchro();RTC_WaitForLastTask();RTC_SetPrescaler(40000 - 1);RTC_WaitForLastTask();MyRTC_SetTime();BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);}else{RCC_LSICmd(ENABLE); //即使不是第一次配置,也需要再次開啟LSI時鐘while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET);RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);RCC_RTCCLKCmd(ENABLE);RTC_WaitForSynchro();RTC_WaitForLastTask();}
}*//*** 函 數:RTC設置時間* 參 數:無* 返 回 值:無* 說 明:調用此函數后,全局數組里時間值將刷新到RTC硬件電路*/
void MyRTC_SetTime(void)
{time_t time_cnt; //定義秒計數器數據類型struct tm time_date; //定義日期時間數據類型time_date.tm_year = MyRTC_Time[0] - 1900; //將數組的時間賦值給日期時間結構體time_date.tm_mon = MyRTC_Time[1] - 1;time_date.tm_mday = MyRTC_Time[2];time_date.tm_hour = MyRTC_Time[3];time_date.tm_min = MyRTC_Time[4];time_date.tm_sec = MyRTC_Time[5];time_cnt = mktime(&time_date) - 8 * 60 * 60; //調用mktime函數,將日期時間轉換為秒計數器格式//- 8 * 60 * 60為東八區的時區調整RTC_SetCounter(time_cnt); //將秒計數器寫入到RTC的CNT中RTC_WaitForLastTask(); //等待上一次操作完成
}/*** 函 數:RTC讀取時間* 參 數:無* 返 回 值:無* 說 明:調用此函數后,RTC硬件電路里時間值將刷新到全局數組*/
void MyRTC_ReadTime(void)
{time_t time_cnt; //定義秒計數器數據類型struct tm time_date; //定義日期時間數據類型time_cnt = RTC_GetCounter() + 8 * 60 * 60; //讀取RTC的CNT,獲取當前的秒計數器//+ 8 * 60 * 60為東八區的時區調整time_date = *localtime(&time_cnt); //使用localtime函數,將秒計數器轉換為日期時間格式MyRTC_Time[0] = time_date.tm_year + 1900; //將日期時間結構體賦值給數組的時間MyRTC_Time[1] = time_date.tm_mon + 1;MyRTC_Time[2] = time_date.tm_mday;MyRTC_Time[3] = time_date.tm_hour;MyRTC_Time[4] = time_date.tm_min;MyRTC_Time[5] = time_date.tm_sec;
}
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"int main(void)
{/*模塊初始化*/OLED_Init(); //OLED初始化MyRTC_Init(); //RTC初始化/*顯示靜態字符串*/OLED_ShowString(1, 1, "Date:XXXX-XX-XX");OLED_ShowString(2, 1, "Time:XX:XX:XX");OLED_ShowString(3, 1, "CNT :");OLED_ShowString(4, 1, "DIV :");while (1){MyRTC_ReadTime(); //RTC讀取時間,最新的時間存儲到MyRTC_Time數組中OLED_ShowNum(1, 6, MyRTC_Time[0], 4); //顯示MyRTC_Time數組中的時間值,年OLED_ShowNum(1, 11, MyRTC_Time[1], 2); //月OLED_ShowNum(1, 14, MyRTC_Time[2], 2); //日OLED_ShowNum(2, 6, MyRTC_Time[3], 2); //時OLED_ShowNum(2, 9, MyRTC_Time[4], 2); //分OLED_ShowNum(2, 12, MyRTC_Time[5], 2); //秒OLED_ShowNum(3, 6, RTC_GetCounter(), 10); //顯示32位的秒計數器OLED_ShowNum(4, 6, RTC_GetDivider(), 10); //顯示余數寄存器}
}
?
?如果RTC晶振起振不了,可備選內部低數時鐘LSI
if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) //通過寫入備份寄存器的標志位,判斷RTC是否是第一次配置//if成立則執行第一次的RTC配置{RCC_LSEConfig(RCC_LSE_ON); //開啟LSE時鐘while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET); //等待LSE準備就緒RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //選擇RTCCLK來源為LSERCC_RTCCLKCmd(ENABLE); //RTCCLK使能RTC_WaitForSynchro(); //等待同步RTC_WaitForLastTask(); //等待上一次操作完成RTC_SetPrescaler(32768 - 1); //設置RTC預分頻器,預分頻后的計數頻率為1HzRTC_WaitForLastTask(); //等待上一次操作完成MyRTC_SetTime(); //設置時間,調用此函數,全局數組里時間值刷新到RTC硬件電路BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); //在備份寄存器寫入自己規定的標志位,用于判斷RTC是不是第一次執行配置}else //RTC不是第一次配置{RTC_WaitForSynchro(); //等待同步RTC_WaitForLastTask(); //等待上一次操作完成}
}
?
這段代碼是用于配置和管理STM32微控制器中的實時時鐘(RTC)模塊的。代碼的目的是確保RTC只被配置一次,通過檢查備份寄存器(BKP)中的一個特定值來實現。以下是代碼的詳細解釋:
-
檢查備份寄存器:
if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
這行代碼讀取備份寄存器BKP_DR1的值,并檢查它是否不等于
0xA5A5
。如果不等于,說明RTC是第一次配置。 -
開啟LSE時鐘:
RCC_LSEConfig(RCC_LSE_ON);
開啟低速外部(LSE)時鐘,LSE通常用于為RTC提供精確的時鐘源。
-
等待LSE準備就緒:
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
等待LSE時鐘準備就緒,確保時鐘穩定。
-
配置RTC時鐘源:
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
將RTC的時鐘源配置為LSE。
-
使能RTC時鐘:
RCC_RTCCLKCmd(ENABLE);
使能RTC時鐘。
-
等待同步和上一次操作完成:
RTC_WaitForSynchro(); RTC_WaitForLastTask();
等待RTC同步和上一次操作完成,確保時鐘設置正確應用。
-
設置RTC預分頻器:
RTC_SetPrescaler(32768 - 1);
設置RTC預分頻器,使預分頻后的計數頻率為1Hz(每秒一個脈沖)。
-
再次等待上一次操作完成:
RTC_WaitForLastTask();
再次等待上一次操作完成。
-
設置時間:
MyRTC_SetTime();
調用自定義函數設置RTC的時間。
-
寫入備份寄存器:
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
在備份寄存器寫入標志位
0xA5A5
,用于判斷RTC是否已經配置過。 -
非首次配置:
else {RTC_WaitForSynchro();RTC_WaitForLastTask(); }
如果RTC不是第一次配置,只需等待同步和上一次操作完成。
這段代碼確保RTC模塊只被配置一次,通過在備份寄存器中寫入一個特定的標志位來實現。它首先檢查備份寄存器的值,如果未配置過,則進行一系列配置操作,包括開啟LSE時鐘、設置RTC時鐘源、設置預分頻器、設置時間,并在備份寄存器中寫入標志位。如果已經配置過,則只需等待同步和上一次操作完成。這種設計可以避免重復配置RTC,確保系統的穩定性和可靠性。
?