STM32——Uinx時間戳+BKP+RTC實時時鐘

目錄

一、Uinx時間戳

1.1Uinx簡介

1.2UTC/GMT

1.3時間戳轉換

1.3.1主要數據類型

1.3.2主要函數

1.3.3C語言時間戳轉換示例

1.3.4時間格式化說明符

1.3.5注意事項

二、BKP

2.1BKP簡介

2.2BKP基本結構

三、RTC

3.1RTC簡介

3.2RTC框圖

3.3RTC基本結構

3.4RTC硬件電路

3.5RTC操作注意事項

3.6學習中的疑惑與解答:PWR和RTC、BKP之間有什么關系?

3.6.1核心關系一句話總結

3.6.2. 三個模塊各自的角色

3.6.3.它們為什么會聯系在一起?

3.6.4總結與類比

四、BKP讀寫代碼編寫

4.1讀寫BKP步驟

4.2BKP相關庫函數與PWR與之對應的庫函數

4.3讀寫BKP代碼編寫

4.3.1讀寫數組BKP

五、RTC代碼編寫

5.1RTC初始化

5.2RTC相關庫函數

5.3RTC初始化代碼編寫

5.4顯示時間戳代碼

5.5讀寫設置的時間

5.6初始化RTC優化

5.7顯示余數寄存器的數值


一、Uinx時間戳

1.1Uinx簡介

?Unix 時間戳(Unix Timestamp)定義為從UTC/GMT(倫敦時間)197011000秒開始所經過的秒數,不考慮閏秒?

?時間戳存儲在一個秒計數器中,秒計數器為32/64位的整型變量

?世界上所有時區的秒計數器相同,不同時區通過添加偏移來得到當地時間

1.2UTC/GMT

?GMTGreenwich Mean Time)格林尼治標準時間是一種以地球自轉為基礎的時間計量系統。它將地球自轉一周的時間間隔等分為24小時,以此確定計時標準(前)

?UTCUniversal Time Coordinated)協調世界時是一種以原子鐘為基礎的時間計量系統。它規定銫133原子基態的兩個超精細能級間在零磁場下躍遷輻射9,192,631,770周所持續的時間為1秒。當原子鐘計時一天的時間與地球自轉一周的時間相差超過0.9秒時,UTC會執行閏秒(多加秒數)來保證其計時與地球自轉的協調一致(后)

1.3時間戳轉換

C語言的time.h模塊提供了時間獲取和時間戳轉換的相關函數,可以方便地進行秒計數器、日期時間和字符串之間的轉換

1.3.1主要數據類型

  • time_t:通常是一個整數類型,用于存儲時間戳

  • struct tm:用于存儲日期和時間成分的結構體

  • clock_t用于測量處理器時間的類型

1.3.2主要函數

函數

作用

time_t time(time_t*);

獲取系統時鐘(自己設備上的時間)

struct tm* gmtime(const time_t*);

秒計數器轉換為日期時間(格林尼治時間)

struct tm* localtime(const time_t*);

秒計數器轉換為日期時間(當地時間)

time_t mktime(struct tm*);

日期時間轉換為秒計數器(當地時間)

char* ctime(const time_t*);

秒計數器轉換為字符串(默認格式)

char* asctime(const struct tm*);

日期時間轉換為字符串(默認格式)

size_t strftime(char*, size_t, const char*, const struct tm*);

日期時間轉換為字符串(自定義格式)

1.3.3C語言時間戳轉換示例

#include <stdio.h>
#include <time.h>
#include <stdlib.h>int main() 
{printf("========== 時間戳轉換示例 ==========\n\n");// 獲取當前時間戳time_t current_time = time(NULL);printf("1. 當前時間戳: %ld\n", current_time);printf("   (從1970年1月1日00:00:00 UTC開始的秒數)\n\n");// 將時間戳轉換為本地時間結構體struct tm* local_time = localtime(&current_time);printf("2. 本地時間分解:\n");printf("   年份: %d (從1900年開始計算,實際年份=%d)\n",local_time->tm_year, local_time->tm_year + 1900);printf("   月份: %d (0-11, 0=1月,實際月份=%d)\n",local_time->tm_mon, local_time->tm_mon + 1);printf("   日: %d\n", local_time->tm_mday);printf("   時: %d\n", local_time->tm_hour);printf("   分: %d\n", local_time->tm_min);printf("   秒: %d\n", local_time->tm_sec);printf("   星期: %d (0-6, 0=周日)\n", local_time->tm_wday);printf("   年中的第幾天: %d\n\n", local_time->tm_yday);// 將時間戳轉換為UTC時間結構體struct tm* utc_time = gmtime(&current_time);printf("3. UTC時間分解:\n");printf("   年份: %d (實際年份=%d)\n",utc_time->tm_year, utc_time->tm_year + 1900);printf("   月份: %d (實際月份=%d)\n",utc_time->tm_mon, utc_time->tm_mon + 1);printf("   日: %d\n", utc_time->tm_mday);printf("   時: %d\n", utc_time->tm_hour);printf("   分: %d\n", utc_time->tm_min);printf("   秒: %d\n\n", utc_time->tm_sec);// 使用strftime格式化時間輸出char formatted_time[100];strftime(formatted_time, sizeof(formatted_time),"4. 格式化時間: %Y年%m月%d日 %H時%M分%S秒", local_time);printf("%s\n", formatted_time);strftime(formatted_time, sizeof(formatted_time),"5. 另一種格式: %A, %B %d, %Y %I:%M:%S %p", local_time);printf("%s\n\n", formatted_time);// 將struct tm轉換回時間戳time_t converted_time = mktime(local_time);printf("6. 轉換回的時間戳: %ld\n", converted_time);printf("========== 程序結束 ==========\n");return 0;
}

#char* ctime(const time_t*);這個函數VS顯示函數不安全,我就沒展示#

// 將時間戳轉換為可讀字符串printf("可讀時間字符串: %s\n", ctime(&current_time));

1.3.4時間格式化說明符

strftime函數中常用的格式說明符

  • %Y:4位數的年份

  • %y:2位數的年份

  • %m:月份(01-12)

  • %d:日(01-31)

  • %H:24小時制的小時(00-23)

  • %I:12小時制的小時(01-12)

  • %M:分鐘(00-59)

  • %S:秒(00-59)

  • %p:AM/PM指示

  • %A:完整的星期名稱

  • %B:完整的月份名稱

1.3.5注意事項

  1. struct tm中的年份是從1900年開始計算的,所以需要加1900

  2. struct tm中的月份是從0開始計數的(0=1月,11=12月),所以要加1,顯示目前月份

  3. mktime()函數會自動調整struct tm中的超出范圍的值(如將60秒調整為下一分鐘)

  4. 時區轉換需要注意使用localtime()(本地時間)或gmtime()(UTC時間)

二、BKP

2.1BKP簡介

?BKPBackup Registers)備份寄存器

?BKP可用于存儲用戶應用程序數據。當VDD2.0~3.6V)主電源被切斷,他們仍然由VBAT(備用電池,引腳1)1.8~3.6V)維持供電。當系統在待機模式下被喚醒,或系統復位或電源復位時,他們也不會被復位

RTC和BKP共享同一個備用電源域。這意味著,只要你的板子上有后備電池,無論是RTC的計時,還是BKP中存儲的數據,在主板斷電后都不會丟失。

若VDD和VBAT都斷電,則BKP內數據丟失(BKP本質是RAM存儲器)

?TAMPER引腳(引腳2 默認復用功能 安全保障設計)產生的侵入事件將所有備份寄存器內容清除

?RTC引腳輸出RTC校準時鐘、RTC鬧鐘脈沖或者秒脈沖

?存儲RTC時鐘校準寄存器

?用戶數據存儲容量:20字節(中容量和小容量)/ 84字節(大容量和互聯型)

2.2BKP基本結構

圖中橙色部分為后備區域,BKP和RTC處于后備區域,其特性是:VDD主電源掉電時,后備區域仍可以由VBAT的備用電池供電;VDD主電源上電時,后備區域供電會由VBAT切換到VDD(節省電池電量)

BKP含有:數據寄存器、控制寄存器、狀態寄存器、RTC校準寄存器

a) 數據寄存器(BKP_DRx - Data Register)

這是BKP的核心,用于存儲用戶數據。不同型號的STM32,數據寄存器的數量不同

  • 命名BKP_DR1,?BKP_DR2,?BKP_DR3, ...?BKP_DRn

  • 寬度:每個寄存器都是?16位?寬,可以存儲2個字節,小/中容量有10個寄存器,大容量/互聯型設備有42個數據寄存器。

  • 功能:你可以用它們存儲任何你想在斷電后保留的數據,例如:

    • RTC配置狀態標志

    • 系統配置參數

    • 運行日志或錯誤代碼

    • 校準值


b) 控制與狀態寄存器(BKP_CR / BKP_CSR)

這些寄存器用于管理BKP模塊本身的功能,例如:

  • TAMPER引腳檢測:許多STM32有一個防篡改(Tamper)引腳。當這個引腳上有指定電平跳變時,它可以自動擦除整個BKP區域的數據(出于安全考慮)。相關寄存器用于配置檢測邊沿和使能該功能。

  • RTC校準:有些寄存器位用于對RTC時鐘進行精細的軟件校準。

三、RTC

3.1RTC簡介

?RTCReal Time Clock)實時時鐘

?RTC是一個獨立的定時器,可為系統提供時鐘和日歷的功能

?RTC和時鐘配置系統處于后備區域,系統復位時數據不清零,VDD2.0~3.6V)斷電后可借助VBAT1.8~3.6V)供電繼續走時

?32位的可編程計數器,可對應Unix時間戳的秒計數器

?20位的可編程預分頻器,可適配不同頻率的輸入時鐘

?可選擇三種RTC時鐘源:

? HSE時鐘除以128(通常為8MHz/128)

? LSE振蕩器時鐘(通常為32.768KHz)

? LSI振蕩器時鐘(40KHz)

3.2RTC框圖

3.3RTC基本結構

①RTCCLK時鐘來源,RCC內配置(數據選擇器黃色部分,選擇時鐘來源)

時鐘源選擇器 (RTCSEL)

  • 框圖入口。RTC可以選擇三個時鐘源之一:

    • LSE首選。外部低速晶振,精度高。

    • LSI:內部低速RC振蕩器,成本低但精度較差,受溫度影響大。

    • HSE/128:高速外部時鐘分頻后的信號,精度高但耗電,主電源掉電后無法使用。

  • 通過?RCC_BDCR?寄存器中的?RTCSEL?位進行選擇。這個選擇是一次性的,只有在備份域復位后才能重新設置。

②預分頻器:余數寄存器——自減計數器,存儲當前計數值;重裝寄存器——計數目標,決定分頻值,之后得到1Hz秒計數信號(配置重裝寄存器選擇分頻系數)

預分頻器 (Prescaler)

  • 作用:將輸入的高速時鐘(如32.768kHz)分頻,得到精確的1Hz(每秒一次)信號。

  • 組成:通常由一個20位的異步預分頻器和一個7位的同步預分頻器組成。異步分頻器降低時鐘頻率以降低功耗,同步分頻器保證與APB1時鐘域的無風險交互。

  • 計算:例如,使用LSE(32768 Hz)時,通常設置異步分頻器為127,同步分頻器為255,則最終頻率為:?32768 / (127 + 1) / (255 + 1) = 1 Hz

③計數器:32位計數器——1s自增一次;32位鬧鐘值——設定鬧鐘(配置32位寄存器,進行日期時間的讀寫)

32位可編程計數器 (RTC_CNT)

  • 這是RTC的核心。它是一個由預分頻器輸出的1Hz信號驅動的32位向上計數器。

  • 它存儲的值就是從參考起點開始所經過的秒數。如果我們把參考起點設置為Unix紀元(1970-01-01 00:00:00),那么這個計數器的值就是當前的Unix時間戳。

  • 軟件可以直接讀寫這個計數器來設置或獲取時間。

鬧鐘寄存器 (RTC_ALR)

  • 一個32位的比較寄存器。你可以設置一個想要的秒數目標值。

  • RTC_CNT的值與RTC_ALR的值匹配時,就會產生一個鬧鐘中斷。你可以利用這個中斷讓單片機在特定時間執行某些任務(比如喚醒停止模式)。

④三個信號觸發中斷:秒信號、計數器溢出信號、鬧鐘信號,通過中斷輸出控制,進行中斷使能

⑤使能的中斷,通向NVIC,向CPU申請中斷(需要中斷,先允許中斷,再配置NVIC,再寫對應的中斷函數)

控制與狀態寄存器

  • 用于管理RTC的各種功能,如:

    • 使能寄存器 (RTC_CRH/CRL):允許配置和產生秒中斷、鬧鐘中斷等。

    • 狀態寄存器:用于查看各種中斷標志位。

3.4RTC硬件電路

a) 主電源 (VDD)

  • 作用:為STM32芯片的主核心(包括RTC模塊的邏輯部分)供電。

  • 要求:當主電源存在時,RTC由VDD供電。

b) 備用電池 (VBAT)

  • 作用:這是RTC的“生命線”。當主電源VDD斷開時,自動切換由VBAT引腳上的電池為整個備份域(包括RTC計數器和BKP寄存器)供電,保持計時和數據不丟失。

  • 典型選擇:一顆3V的紐扣電池(如CR2032)。其續航能力可達數年。

  • 電路設計:通常會在VBAT路徑上串聯一個肖特基二極管,并在VDD路徑上也串聯一個二極管。這樣可以實現電源自動切換:當VDD電壓高于VBAT時,由VDD供電;當VDD掉電時,由VBAT供電,防止電流倒灌。或者直接連接一個1.8~3.6V的電池到VBAT,內有一個供電開關,在不同情況選擇合適供電。

c) 低速外部晶振 (LSE)

  • 作用:為RTC提供高精度的時鐘源。RTC的本質是一個計數器,它需要一個“心跳”來遞增,LSE就是這個“心跳”。

  • 頻率32.768 kHz。這是一個標準頻率,因為?32768 = 2^15,經過一個15位的分頻器(2^15分頻)后,正好得到?1Hz(每秒一次)的信號,非常適合驅動秒計數器。

  • 電路設計:需要連接一個32.768kHz的晶振(Xtal),并搭配兩個負載電容(通常為5~15pF)。

3.5RTC操作注意事項

?執行以下操作將使能對BKPRTC的訪問:

? 設置RCC_APB1ENR(開啟APB1外設的時鐘)PWRENBKPEN,使能PWRBKP時鐘

? 設置PWR_CRDBP,使能對BKPRTC的訪問(調用PWR庫函數)

?若在讀取RTC寄存器時,RTCAPB1接口曾經處于禁止狀態,則軟件首先必須等待RTC_CRL寄存器中的RSF位(寄存器同步標志)被硬件置1。(調用等待同步函數)

  • 根本原因:時鐘域不同。RTC核心由低速的LSE(32.768kHz)驅動,而CPU和其寄存器接口由高速的APB1總線時鐘驅動。這兩個時鐘域是異步的。

  • 問題所在:當APB1接口被禁用(例如系統剛復位、或從低功耗模式喚醒)后重新啟用時,RTC的日歷寄存器組(RTC_CNT,?RTC_ALR,?RTC_PRL)需要從RTC的時鐘域同步到APB1的時鐘域。在這個同步過程完成之前,軟件讀取到的寄存器值可能是陳舊、不可靠的。

  • RSF位的作用RSF?(Register Synchronized Flag) 位由硬件自動置1,標志著同步過程已完成。軟件必須等待此位被置1后,才能安全地讀取日歷寄存器,以確保數據的正確性。

?必須設置RTC_CRL寄存器中的CNF,使RTC進入配置模式后,才能寫入RTC_PRLRTC_CNTRTC_ALR寄存器

  • 保證寫的原子性RTC_PRL(預分頻器)、RTC_CNT(計數器)和RTC_ALR(鬧鐘值)這三個寄存器是密切相關的。為了防止在軟件修改其中一個寄存器時,RTC核心正在更新另一個寄存器而導致數據不一致(例如,在修改秒計數器的高16位時,低16位可能因為秒信號到來而遞增),STM32引入了“配置模式”的概念。

  • CNF位的作用:將?CNF?(Configuration Flag) 位置1,會使RTC核心暫停對日歷寄存器的更新,并允許軟件一次性、安全地寫入多個相關寄存器。退出配置模式后,RTC核心會使用新值繼續運行。

?RTC任何寄存器的寫操作,都必須在前一次寫操作結束后進行。可以通過查詢RTC_CR寄存器中的RTOFF狀態位,判斷RTC寄存器是否處于更新中。僅當RTOFF狀態位是1時,才可以寫入RTC寄存器。(調用等待函數)

  • 根本原因:慢速外設。RTC的時鐘源非常慢(RTCCLK~32kHz),而CPU的指令執行速度極快(PCLK1~72MHz)。向RTC寄存器寫入一個數據需要數個RTC時鐘周期才能完成。

  • 問題所在:如果軟件在前一次寫操作尚未被RTC硬件處理完成時,就立刻發起下一次寫操作,會導致后一次寫操作被忽略,或者造成寄存器內容錯誤。

  • RTOFF位的作用RTOFF?(RTC Operation OFF) 位由硬件控制。當它為?0?時,表示RTC正在處理上一次的寫操作,此時總線會被鎖定,禁止新的寫入。當它為?1?時,表示RTC空閑,允許新的寫操作。

3.6學習中的疑惑與解答:PWR和RTC、BKP之間有什么關系?

3.6.1核心關系一句話總結

PWR(電源控制)是基礎和保障,而RTC(實時時鐘)和BKP(備份寄存器)是需要被保護的核心數據。PWR確保了即使在主電源(VDD)斷開的情況下,也能通過備用電池(VBAT)為RTC和BKP所在的“備份域”持續供電,從而保住這些關鍵數據不丟失。


3.6.2. 三個模塊各自的角色

a) PWR - 電源控制

  • 職責:管理整個微控制器的電源,包括供電、功耗模式(如睡眠、停止、待機模式)以及電源域的劃分。

  • 關鍵概念:STM32內部有兩個主要的電源域

    • VDD域:由主電源(VDD)供電。絕大部分外設(如GPIO、USART、SPI)和內核(CPU)、SRAM都在這個域。當主電源斷開時,這個域的內容會丟失。

    • 備份域:由一個單獨的引腳VBAT供電。即使主電源VDD斷開,只要VBAT引腳上接有電池(如3V紐扣電池),這個域就能繼續工作。

b) BKP - 備份寄存器

  • 職責:提供一小塊在備份域中的特殊內存(通常是16-20個16位的寄存器)。

  • 關鍵特性只要備份域有電(無論是來自VDD還是VBAT),這些寄存器里的數據就不會丟失。因此,它們常用來存儲系統配置、密碼、運行次數等需要掉電保存的關鍵數據。

  • 訪問控制:為了防止意外寫入,對BKP寄存器的訪問受到PWR_CR寄存器中的DBP位控制。只有先使能DBP位,才能讀寫BKP寄存器。

c) RTC - 實時時鐘

  • 職責:提供一個獨立的計時器/日歷功能。

  • 關鍵特性:RTC的核心也是一個需要持續運行的部件。因此,它也被放在了備份域中。這樣,無論主系統是否上電,只要VBAT有電,RTC就能一直“滴答滴答”地走時。

3.6.3.它們為什么會聯系在一起?

因為RTC和BKP同屬于“備份域”

PWR模塊,是進入和管理這個“備份域”的“看門人”和“電源開關”

當你學習RTC和BKP時,你必然要操作備份域里的資源。而要安全地操作這些資源,就必須通過PWR模塊來進行配置。這就是為什么在講BKP/RTC的章節,會突然引入PWR。

它們的關系結構圖如下:

從這個圖可以看出:

  1. 物理供電:PWR模塊負責選擇是使用VDD還是VBAT為備份域供電。

  2. 邏輯訪問:PWR模塊中的DBP位是讀寫BKP和RTC部分寄存器的“鑰匙”。

3.6.4總結與類比

你可以把一個帶STM32的設備想象成一個公司

  • BKP:是公司的保險柜,里面放著最重要的現金和合同(關鍵數據)。

  • RTC:是公司墻上那個永遠不停的電子鐘,即使公司下班斷電了,它靠自己的電池也能走(備份域供電)。

  • PWR:是公司的物業和電力部門。它管理著整個大樓的供電(主電源和備用發電機),并且掌握著打開保險柜房間的鑰匙(DBP位)。

  • Tamper引腳:是保險柜上的震動警報器。一旦有人動保險柜(觸發入侵),警報器就會通知電力部門(PWR),電力部門會啟動應急 protocol(喚醒系統),并且警報器會指令保險柜自毀(擦除BKP數據)。

四、BKP讀寫代碼編寫

4.1讀寫BKP步驟

①BKP初始化:開啟PWREN和BKPEN的時鐘;使用PWR_CR的一個函數,使能BKP和RTC的訪問
②寫DR:BKP寫函數
③讀DR:BKP讀函數

4.2BKP相關庫函數與PWR與之對應的庫函數

1.恢復缺省配置(手動清空BKP所有的數據寄存器)
void BKP_DeInit(void);


下面2個TAMPER函數是配置TAMPER侵入檢測功能
2.配置TAMPER引腳的有效電平(高/低電平觸發)
void BKP_TamperPinLevelConfig(uint16_t BKP_TamperPinLevel);
3.TAMPER使能(是否開啟侵入檢測功能:若需要此項功能,需要先配置有效電平在配置使能)
void BKP_TamperPinCmd(FunctionalState NewState);


4.中斷配置:是否開啟中斷
void BKP_ITConfig(FunctionalState NewState);
5.時鐘輸出功能配置(可選擇在RTC引腳輸出時鐘信號,輸出RTC校準時鐘、RTC鬧鐘脈沖、秒脈沖)
void BKP_RTCOutputConfig(uint16_t BKP_RTCOutputSource);
6.設置RTC校準值(寫入RTC校準寄存器)
void BKP_SetRTCCalibrationValue(uint8_t CalibrationValue);


?下面2個是BKP的重點函數
7.寫備份寄存器
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
8.讀備份寄存器
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);

下面4個是獲取標志和清除標志的函數
FlagStatus BKP_GetFlagStatus(void);
void BKP_ClearFlag(void);
ITStatus BKP_GetITStatus(void);
void BKP_ClearITPendingBit(void);


PWR與BKP相關的庫函數:
?1.備份寄存器訪問使能,設置PWR_CR寄存器內的DBP位,使能對BKP和RTC的訪問
?? ?void PWR_BackupAccessCmd(FunctionalState NewState);

4.3讀寫BKP代碼編寫

	//①使能BKP和PWRRCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);//②使能對BKP和RTC的訪問PWR_BackupAccessCmd(ENABLE);BKP_WriteBackupRegister(BKP_DR1,0x1234);OLED_ShowHexNum(1,1,BKP_ReadBackupRegister(BKP_DR1),4);while(1){}

4.3.1讀寫數組BKP

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "key.h"
uint16_t WriteARR[]={0x1234,0x5678};
uint16_t ReadARR[2];
uint8_t key;
void key_pro(void)
{key=key_init();if(key==1){WriteARR[0]++;WriteARR[1]++;BKP_WriteBackupRegister(BKP_DR1,WriteARR[0]);BKP_WriteBackupRegister(BKP_DR2,WriteARR[1]);OLED_ShowHexNum(1,3,WriteARR[0],4);OLED_ShowHexNum(1,8,WriteARR[1],4);}}int main(void)
{	OLED_Init();key_scan();//初始化//①使能BKP和PWRRCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);//②使能對BKP和RTC的訪問PWR_BackupAccessCmd(ENABLE);while(1){OLED_ShowString(1,1,"W:");OLED_ShowString(2,1,"R:");key_pro();ReadARR[0]=BKP_ReadBackupRegister(BKP_DR1);ReadARR[1]=BKP_ReadBackupRegister(BKP_DR2);OLED_ShowHexNum(2,3,ReadARR[0],4);OLED_ShowHexNum(2,8,ReadARR[1],4);}
}

五、RTC代碼編寫

5.1RTC初始化

RTC初始化流程:
①開啟PWR和BKP的時鐘
②使能BKP和RTC的訪問
③啟動RTC時鐘,選擇LSE作為系統時鐘(LES需要手動開啟,平時為了省電是關閉狀態)(RCC內部)
④配置RTCCLK數據選擇器,指定LSE作為RTCCLK(RCC模塊內)
⑤調用等待函數:等待同步和等待上一次操作完成
⑥配置預分頻器,給PRC重裝寄存器一個合適的預分頻值,以確保輸出給寄存器的頻率是1Hz
⑦配置CNT的值,給RTC初始時間
⑧(若需要配置鬧鐘值)
⑨(若需要中斷,配置中斷:NVIC)

?

5.2RTC相關庫函數

RCC模塊內有關RTC庫函數:
?1.配置LSE外部低速時鐘,啟動LSE時鐘
void RCC_LSEConfig(uint8_t RCC_LSE);
2.配置LSI內部低速時鐘(若外部時鐘不起振,可使用內部時鐘進行驗證)
void RCC_LSICmd(FunctionalState NewState);
?3.RTCCLK配置(選擇RTCCLK的時鐘源)
void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource);
?4.啟動RTCCLK(調用配置RTCCLK函數之后,還需使能RTCCLK)
void RCC_RTCCLKCmd(FunctionalState NewState);
?5.獲取標志位(調用啟動時鐘后,需等待標志位LSERDY=1,時鐘才算啟動完成并且工作穩定)
FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);

RTC時鐘源初始化庫函數使用:1→5→3→4

RTC庫函數
1.配置中斷輸出
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);
2.進入配置模式(置CRL的CNF=1,進入配置模式后,才可讀寫一些寄存器)
void RTC_EnterConfigMode(void);
3.退出配置模式(CNF=0)
void RTC_ExitConfigMode(void);


4.獲取CNT計數器的值(讀取時鐘)
uint32_t ?RTC_GetCounter(void);
5.寫入CNT計數器的值(設置時間)
void RTC_SetCounter(uint32_t CounterValue);
?6.寫入預分頻器(值寫入到預分頻器的PRL重裝寄存器,配置預分頻器的預分頻系數)
void RTC_SetPrescaler(uint32_t PrescalerValue);
7.寫入鬧鐘值(配置鬧鐘)
void RTC_SetAlarm(uint32_t AlarmValue);
8.讀取預分頻器中的DIV余數寄存器(自減計數器——為了獲得更細致的時間)
uint32_t ?RTC_GetDivider(void);


?9.等待上次操作完成(循環,等待RTOFF=1)
void RTC_WaitForLastTask(void);
?10.等待同步(清除RSF標志位,循環直到RSF=1)
void RTC_WaitForSynchro(void);

(讀取RTC寄存器之前,時鐘源選擇完畢后,先等待同步10和等待上次操作完成9)

(對RTC任何寄存器的寫操作,都必須在前一次寫操作結束后進行,即每次進行寫操作后都加一個等待上次操作完成函數)


11.獲取標志位,清楚標志位的函數
FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);
void RTC_ClearFlag(uint16_t RTC_FLAG);
ITStatus RTC_GetITStatus(uint16_t RTC_IT);
void RTC_ClearITPendingBit(uint16_t RTC_IT);
?

5.3RTC初始化代碼編寫

#include "stm32f10x.h"                  // Device headervoid MyRTC_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);PWR_BackupAccessCmd(ENABLE);RCC_LSEConfig(RCC_LSE_ON);//啟動LSE振蕩器/*  *     @arg RCC_LSE_OFF: LSE oscillator OFF         LSE振蕩器關閉*     @arg RCC_LSE_ON: LSE oscillator ON           LSE振蕩器開啟*     @arg RCC_LSE_Bypass: LSE oscillator bypassed with external clock  LSE時鐘旁路——不接晶振,直接從OSE32——IN引腳輸入一個指定頻率的信號*/while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)!=SET);//等待LSE啟動完成RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//選擇RTCCLK時鐘源RCC_RTCCLKCmd(ENABLE);RTC_WaitForSynchro();RTC_WaitForLastTask();//⑤調用等待函數:等待同步和等待上一次操作完成RTC_SetPrescaler(32768-1);//⑥配置預分頻器,給PRC重裝寄存器一個合適的預分頻值,以確保輸出給寄存器的頻率是1HzRTC_WaitForLastTask();//對RTC的任何一個寫操作,必須在前一次寫操作結束后進行
//必須設置RTC_CRL寄存器中的CNF位,使RTC進入配置模式后,才能寫入RTC_PRL、RTC_CNT、RTC_ALR寄存器
//寫入預分頻器也需要進入配置模式,但是代碼不需要寫,內部自帶進入退出配置代碼RTC_SetCounter(1672588795);//⑦配置CNT的值,給RTC初始時間,此為2023年,不初始也可以,默認從0開始,一開始是1970年RTC_WaitForLastTask();}

5.4顯示時間戳代碼

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "My_RTC.h"int main(void)
{		OLED_Init();MyRTC_Init();while(1){OLED_ShowNum(1, 1, RTC_GetCounter(), 10);	//顯示32位的秒計數器}
}

5.5讀寫設置的時間

#include "stm32f10x.h"                  // Device header
#include "Time.h"//存放年月日時分秒
uint16_t MyRTC_Time[]={2024,8,26,10,9,30};//設置時間
void MyRTC_SetTime(void)
{/*①將數組時間填充到struct tm 結構體內;②使用mktime函數得到秒數③將秒數寫入RTC的CNT中*/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;//讀取的是倫敦時間的時間戳,-8個小時,就是北京時間的時間戳RTC_SetCounter(time_cnt);RTC_WaitForLastTask();
}//讀取時間
void MyRTC_ReadTime(void)
{time_t time_cnt;struct tm time_date;time_cnt=RTC_GetCounter()+8*60*60;//此時計數是倫敦時間,+8個小時,就是北京時間的時間戳time_date=*localtime(&time_cnt);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;
}void MyRTC_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);PWR_BackupAccessCmd(ENABLE);RCC_LSEConfig(RCC_LSE_ON);//啟動LSE振蕩器while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)!=SET);//等待LSE啟動完成RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//選擇RTCCLK時鐘源RCC_RTCCLKCmd(ENABLE);RTC_WaitForSynchro();RTC_WaitForLastTask();//⑤調用等待函數:等待同步和等待上一次操作完成RTC_SetPrescaler(32768-1);//⑥配置預分頻器,給PRC重裝寄存器一個合適的預分頻值,以確保輸出給寄存器的頻率是1HzRTC_WaitForLastTask();//對RTC的任何一個寫操作,必須在前一次寫操作結束后進行//必須設置RTC_CRL寄存器中的CNF位,使RTC進入配置模式后,才能寫入RTC_PRL、RTC_CNT、RTC_ALR寄存器//寫入預分頻器也需要進入配置模式,但是代碼不需要寫,內部自帶進入退出配置代碼RTC_SetCounter(1672588795);//⑦配置CNT的值,給RTC初始時間,此為2023年,不初始也可以,默認從0開始,一開始是1970年RTC_WaitForLastTask();	MyRTC_SetTime();}
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "My_RTC.h"int main(void)
{	OLED_Init();MyRTC_Init();OLED_ShowString(1, 1, "Date:XXXX-XX-XX");OLED_ShowString(2, 1, "Time:XX:XX:XX");OLED_ShowString(3, 1, "CNT :");while(1){MyRTC_ReadTime();//顯示MyRTC_Time數組中的時間值OLED_ShowNum(1, 6, MyRTC_Time[0], 4);//年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位的秒計數器}
}

5.6初始化RTC優化

防止電源斷電備用電源不斷電或者按復位鍵時,時間重置

簡單來說,就是在BKP設置一個標志位

void MyRTC_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);PWR_BackupAccessCmd(ENABLE);RCC_LSEConfig(RCC_LSE_ON);//啟動LSE振蕩器while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)!=SET);//等待LSE啟動完成RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//選擇RTCCLK時鐘源RCC_RTCCLKCmd(ENABLE);//第一次上電或者系統完全斷電之后,BKP_DR1默認是0,if成立,執行初始化if(BKP_ReadBackupRegister(BKP_DR1)!=0xFFFF){RTC_WaitForSynchro();RTC_WaitForLastTask();//⑤調用等待函數:等待同步和等待上一次操作完成RTC_SetPrescaler(32768-1);//⑥配置預分頻器,給PRC重裝寄存器一個合適的預分頻值,以確保輸出給寄存器的頻率是1HzRTC_WaitForLastTask();//對RTC的任何一個寫操作,必須在前一次寫操作結束后進行//必須設置RTC_CRL寄存器中的CNF位,使RTC進入配置模式后,才能寫入RTC_PRL、RTC_CNT、RTC_ALR寄存器//寫入預分頻器也需要進入配置模式,但是代碼不需要寫,內部自帶進入退出配置代碼RTC_SetCounter(1672588795);//⑦配置CNT的值,給RTC初始時間,此為2023年,不初始也可以,默認從0開始,一開始是1970年RTC_WaitForLastTask();	MyRTC_SetTime();BKP_WriteBackupRegister(BKP_DR1,0xFFFF);}else{RTC_WaitForSynchro();RTC_WaitForLastTask();//⑤調用等待函數:等待同步和等待上一次操作完成}}

5.7顯示余數寄存器的數值

#之前一直CNT數值不變,但是加上DIV后,數值就自增了,好奇怪,沒理解#

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "My_RTC.h"int main(void)
{	OLED_Init();MyRTC_Init();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();//顯示MyRTC_Time數組中的時間值OLED_ShowNum(1, 6, MyRTC_Time[0], 4);//年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);	//顯示余數寄存器的數值}
}

?DIV數值一直自減,數值范圍時32767~0
?DIV每自減一輪,CNT+1
?因此可以對秒數進行更細致的劃分,秒-分秒-厘秒-毫秒

?? ??? ?若轉換為毫秒,1秒=1000毫秒
?? ??? ?將數值范圍32767——0線性變換為0——999

OLED_ShowNum(4, 6, (32767-RTC_GetDivider())/32767.0*999, 10);?? ?//顯示余數寄存器的數值

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/920870.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/920870.shtml
英文地址,請注明出處:http://en.pswp.cn/news/920870.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java設計模式是什么?核心設計原則有哪些?

文章目錄什么是設計模式&#xff1f;為什么使用設計模式&#xff1f;設計模式的核心設計原則是什么&#xff1f;1. 開閉原則&#xff08;Open-Closed Principle, OCP&#xff09;2. 里氏替換原則&#xff08;Liskov Substitution Principle, LSP&#xff09;3. 依賴倒置原則&am…

網絡層和數據鏈路層

目錄 1.網絡層 2.數據鏈路層 1.網絡層 我們知道&#xff0c;我們的消息為了從A端發送到B端&#xff0c;達成遠距離傳輸&#xff0c;我們為此設計了很多協議層&#xff0c;分別是應用層&#xff0c;傳輸層&#xff0c;網絡層&#xff0c;數據鏈路層&#xff0c;網卡&#xff0c…

Redis 的字典:像智能文件柜一樣高效的哈希表實現

目錄 一、從傳統查找的痛點到哈希表的優勢? 二、哈希表的核心結構&#xff1a;文件柜的構成? 2.1、 dictht 結構體&#xff1a;文件柜本體? 2.2、dictEntry 結構體&#xff1a;帶鏈條的文件夾? 2.2.1、 哈希沖突的解決&#xff1a;抽屜里的鏈條? 2.3、字典的高層封裝…

FAST API部署和使用

第一部分&#xff1a;FastAPI 的使用&#xff08;開發環境&#xff09; 1. 安裝 首先&#xff0c;你需要安裝 FastAPI 和一個 ASGI 服務器&#xff0c;最常用的是 Uvicorn。 pip install "fastapi[standard]"這個命令會安裝 FastAPI 以及所有推薦的依賴&#xff0c;包…

【JavaWeb】之HTML(對HTML細節的一些總結)

大家天天開心&#xff01; 文章目錄 前言一、HTML的簡介二、HTML運行方式三、html 的標簽/元素-說明四、表單注意事項總結 前言 首先我們在把Java基礎學習完之后&#xff0c;我們就要進行網站方面的開發了&#xff0c;我們要了解網頁的組成&#xff0c;而網頁的組成有HTML,CSS,…

互聯網醫院品牌IP的用戶體驗和生態構建

一、患者體驗與信任構建互聯網醫院品牌IP的價值核心在于獲得患者的深度信任&#xff0c;而卓越的用戶體驗是實現這一目標的關鍵路徑。在醫療服務同質化嚴重的當下&#xff0c;患者體驗已成為醫療機構差異化競爭的重要維度。研究表明&#xff0c;良好的用戶體驗能夠提高用戶滿意…

【Node.js教程】Express框架入門:從搭建到動態渲染商品列表

前言 Visual Studio Code(簡稱VSCode)是微軟開發的一款免費開源跨平臺代碼編輯器,憑借其免費、開源、跨平臺的特性,以及豐富的插件生態和美觀的界面,成為前端開發者的首選工具。 本文將帶你從零開始學習Express框架,包括搭建項目、配置路由、使用中間件以及實現動態渲染…

眾擎機器人開源代碼解讀

一&#xff0c;綜述 EngineAI ROS 包&#xff1a; 高層開發模式&#xff1a;用戶可通過發布身體速度指令&#xff0c;直接調用 EngineAI 機器人的行走控制器。底層開發模式&#xff1a;用戶可通過發布關節指令&#xff0c;自主開發專屬的控制器。 ROS2 package&#xff1a;全…

Windows系統安裝Git詳細教程

文章目錄步驟 1&#xff1a;下載 Git 安裝包步驟 2&#xff1a;運行安裝程序步驟 3&#xff1a;選擇安裝路徑步驟 4&#xff1a;選擇組件步驟 5&#xff1a;選擇默認編輯器步驟 6&#xff1a;選擇路徑環境變量步驟 7&#xff1a;選擇 HTTPS 協議的傳輸方式步驟 8&#xff1a;配…

leetcode 3446. 按對角線進行矩陣排序 中等

給你一個大小為 n x n 的整數方陣 grid。返回一個經過如下調整的矩陣&#xff1a;左下角三角形&#xff08;包括中間對角線&#xff09;的對角線按 非遞增順序 排序。右上角三角形 的對角線按 非遞減順序 排序。示例 1&#xff1a;輸入&#xff1a; grid [[1,7,3],[9,8,2],[4,…

攜程旅行 web 驗證碼 分析

聲明 本文章中所有內容僅供學習交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包內容、敏感網址、數據接口等均已做脫敏處理&#xff0c;嚴禁用于商業用途和非法用途&#xff0c;否則由此產生的一切后果均與作者無關&#xff01; 逆向分析 部分python代碼 result cp…

JavaEE 進階第一期:開啟前端入門之旅(上)

專欄&#xff1a;JavaEE 進階躍遷營 個人主頁&#xff1a;手握風云 一、HTML基礎 1.1. 什么是HTML HTML(Hyper Text Markup Language)&#xff0c;超文本標記語言。 超文本&#xff1a;比文本要強大&#xff0c;通過鏈接和交互式方式來組織和呈現信息的文本形式。不僅僅有文本…

4.5 PBR

1.PBR簡介 2.高光工作流 3.金屬工作流1.PBR簡介 PBR(Physically Based Rendering, 基于物理的渲染)的工作流分為金屬工作流和高光工作流2.高光工作流 高光工作流是一種傳統的工作流, 現在用的相對較少, 但是在某些特定情況下能提供更精細的控制a.核心思想它不區分金屬和非金屬,…

09.《路由基礎知識解析和實踐》

09.路由基礎 文章目錄09.路由基礎核心概念路由關鍵組成部分三層轉發原理介紹(通信流程)路由類型及配置直連路由&#xff08;direct&#xff09;實驗示例**靜態路由&#xff08;Static&#xff09;****實驗示例****動態路由****RIP&#xff08;routing information protocol---路…

websocket建立連接過程

1. 客戶端發送一個GET的http請求&#xff0c;請求頭要包含connection: upgradehost&#xff1a;localhost:8000。表明地址upgrade: websocket。指明升級的協議sec-websocket-key 。 安全驗證密鑰sec-websocket-version。 協議版本sec-websocket-accept 。對傳過來的key進行加密…

Simulink庫文件-一種低通濾波模塊搭建方法

在汽車電控系統應用層開發中&#xff0c;經常會用到低通濾波模塊&#xff0c;其主要作用是去除輸入信號中的高頻干擾&#xff0c;防止由于輸入信號的干擾引起后續執行系統的非預期頻繁波動。本文介紹簡要介紹低通濾波的定義及作用&#xff0c;并介紹一種低通濾波模塊simulink搭…

【C++游記】AVL樹

楓の個人主頁 你不能改變過去&#xff0c;但你可以改變未來 算法/C/數據結構/C Hello&#xff0c;這里是小楓。C語言與數據結構和算法初階兩個板塊都更新完畢&#xff0c;我們繼續來學習C的內容呀。C是接近底層有比較經典的語言&#xff0c;因此學習起來注定枯燥無味&#xf…

音視頻學習(六十二):H264中的SEI

什么是SEI? 在 H.264 視頻編碼標準中&#xff0c;補充增強信息&#xff08;Supplemental Enhancement Information&#xff0c;SEI&#xff09; 是一種特殊的 NAL&#xff08;網絡抽象層&#xff09;單元。它不像序列參數集&#xff08;SPS&#xff09;或圖像參數集&#xff0…

docker run 后報錯/bin/bash: /bin/bash: cannot execute binary file總結

以下方法來源于AI&#xff0c;個人僅驗證了第三條便成功執行 1. 鏡像與宿主機架構不匹配 比如&#xff1a; 你是 x86_64 的機器&#xff0c;但鏡像是 ARM64 的&#xff08;或反之&#xff09;。在 PC 上拉了樹莓派用的鏡像。查看鏡像架構 docker inspect <image_name> | …

【Redisson 加鎖源碼解析】

Redisson 源碼解析 —— 分布式鎖實現過程 在分布式系統中&#xff0c;分布式鎖 是非常常見的需求&#xff0c;用來保證多個節點之間的互斥操作。Redisson 是 Redis 的一個 Java 客戶端&#xff0c;它提供了對分布式鎖的良好封裝。本文將從源碼角度剖析 Redisson 的分布式鎖實現…