目錄
- 1.時間戳
- 1.1 Unix時間戳
- 1.2 UTC/GMT
- 1.3 時間戳轉換
- **1.** `time_t time(time_t*)`
- **2.** `struct tm* gmtime(const time_t*)`
- **3.** `struct tm* localtime(const time_t*)`
- **4.** `time_t mktime(struct tm*)`
- **5.** `char* ctime(const time_t*)`
- **6.** `char* asctime(const struct tm*)`
- **7.** `size_t strftime(char*, size_t, const char*, const struct tm*)`
- 2.BKP
- 2.1 簡介
- **寄存器資源**
- **PC13 引腳功能**
- 2.2 基本結構
- 3.RTC時鐘
- 3.1 簡介
- **3.1.1 RTC 的主要特性**
- **3.1.2 訪問 RTC 的流程**
- **3.1.3 RTC 的典型應用**
- **3.1.4 RTC 的優勢**
- 3.2 框圖(詳細結構)
- 3.3 RTC 的基本結構
- 3.4 硬件電路
- 3.5 使用注意事項
- **3.5.1 使能對 BKP 和 RTC 的訪問**
- **3.5.2 同步寄存器(RSF)狀態**
- **3.5.3 進入 RTC 配置模式**
- **3.5.4 寫操作完成前必須等待**
- **3.5.5 概括**
- **關鍵操作流程**
- **可能的錯誤操作**
- **優化建議**
- 4.實驗
- 4.1 BKP
- 4.2 RTC
- 5.擴展
1.時間戳
1.1 Unix時間戳
Unix 時間戳定義為從 UTC 時間的 1970 年 1 月 1 日 0 點 0 分 0 秒(稱為 Unix 紀元時間,Epoch Time)開始,所經過的總秒數,不考慮閏秒的影響。
時間戳是通過一個整數型變量存儲的,常見的位寬是 32 位 或 64 位:
- 32 位整數:能夠記錄約 136 年(正負 68 年)。
- 64 位整數:可記錄的時間范圍非常寬,基本可以覆蓋所有時間需求。
秒計數器記錄的是 UTC 時間,與不同的時區無關。
全世界所有地區的秒計數器值相同,但通過添加偏移量(時區)可以換算成當地時間。
- UTC 的 1970 年 1 月 1 日 0:0:0 對應秒計數器為 0。
- 北京時間(UTC+8)的 1970 年 1 月 1 日 8:0:0 對應的秒計數器值仍為 0。
-
計數器值為 0 時:
-
- UTC 時間:1970-1-1 0:0:0
- 北京時間:1970-1-1 8:0:0
-
秒計數器值為 10,000,000,000:
-
- UTC 時間:2001-9-9 1:46:40
- 北京時間:2001-9-9 9:46:40
-
秒計數器值為 1,672,588,795:
-
- UTC 時間:2023-1-1 15:59:55
- 北京時間:2023-1-1 23:59:55
1.2 UTC/GMT
- GMT(Greenwich Mean Time,格林尼治標準時間):
-
- GMT 是一種基于地球自轉的時間計量系統。
- 地球每自轉一周被劃分為 24 小時,時間間隔是均勻的,用來作為時間標準。
- GMT 主要用于歷史上的時間定義,現代已經逐漸被更精準的 UTC 替代。
- UTC(Universal Time Coordinated,協調世界時):
-
- UTC 是一種基于原子鐘的時間計量系統,是目前全球通用的時間標準。
- UTC 的定義:
-
-
- 1 秒被精確定義為:銫 133 原子基態的兩個超精細能級間在零磁場下輻射 9,192,631,770 周持續的時間。
-
-
- UTC 的調整機制:
-
-
- 地球自轉的時間并非完全均勻,地球自轉一天的時間與 UTC 之間的差異可能會超過 0.9 秒。
- 為了保持協調一致,UTC 會通過閏秒來調整時間,使其與地球自轉周期保持同步。
-
- 兩者的區別:
-
- GMT 基于地球自轉,時間間隔固定,但相對不夠精確。
- UTC 基于原子鐘,精準度更高,可以通過調整閏秒與地球自轉周期保持一致。
1.3 時間戳轉換
語言的time.h模塊提供了時間獲取和時間戳轉換的相關函數,可以方便地進行秒計數器、日期時間和字符串之間的轉換
函數 | 作用 |
---|---|
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. time_t time(time_t*)
獲取系統當前時間的秒計數器值(Unix 時間戳)。
#include <stdio.h>
#include <time.h>int main() {time_t current_time;current_time = time(NULL); // 獲取當前時間戳printf("當前時間的時間戳是:%ld\n", current_time);return 0;
}
輸出示例:
當前時間的時間戳是:1672588795
2. struct tm* gmtime(const time_t*)
將秒計數器轉換為 UTC 格式的日期時間。
#include <stdio.h>
#include <time.h>int main() {time_t current_time = time(NULL); // 獲取當前時間戳struct tm *utc_time = gmtime(¤t_time); // 轉換為 UTC 時間printf("UTC 時間是:%d-%02d-%02d %02d:%02d:%02d\n",utc_time->tm_year + 1900, // 年從 1900 開始utc_time->tm_mon + 1, // 月從 0 開始utc_time->tm_mday,utc_time->tm_hour,utc_time->tm_min,utc_time->tm_sec);return 0;
}
輸出示例:
UTC 時間是:2023-01-01 16:59:55
3. struct tm* localtime(const time_t*)
將秒計數器轉換為本地時間(根據時區調整)。
#include <stdio.h>
#include <time.h>int main() {time_t current_time = time(NULL); // 獲取當前時間戳struct tm *local_time = localtime(¤t_time); // 轉換為本地時間printf("本地時間是:%d-%02d-%02d %02d:%02d:%02d\n",local_time->tm_year + 1900,local_time->tm_mon + 1,local_time->tm_mday,local_time->tm_hour,local_time->tm_min,local_time->tm_sec);return 0;
}
輸出示例:
本地時間是:2023-01-01 23:59:55
4. time_t mktime(struct tm*)
將本地時間結構轉換為秒計數器。
#include <stdio.h>
#include <time.h>int main() {struct tm time_info = {0};// 設置一個本地時間:2023-01-01 12:00:00time_info.tm_year = 2023 - 1900; // 年份從 1900 開始time_info.tm_mon = 0; // 月份從 0 開始time_info.tm_mday = 1;time_info.tm_hour = 12;time_info.tm_min = 0;time_info.tm_sec = 0;time_t timestamp = mktime(&time_info); // 轉換為時間戳printf("2023-01-01 12:00:00 的時間戳是:%ld\n", timestamp);return 0;
}
輸出示例:
2023-01-01 12:00:00 的時間戳是:1672545600
5. char* ctime(const time_t*)
將時間戳轉換為默認格式的字符串(含換行符)。
#include <stdio.h>
#include <time.h>int main() {time_t current_time = time(NULL); // 獲取當前時間戳char *time_string = ctime(¤t_time); // 轉換為字符串printf("當前時間是:%s", time_string); // 輸出字符串(帶換行符)return 0;
}
輸出示例:
當前時間是:Sun Jan 1 23:59:55 2023
6. char* asctime(const struct tm*)
將日期時間結構轉換為默認格式的字符串(含換行符)。
#include <stdio.h>
#include <time.h>int main() {struct tm time_info = {0};// 設置一個時間結構time_info.tm_year = 2023 - 1900;time_info.tm_mon = 0;time_info.tm_mday = 1;time_info.tm_hour = 12;time_info.tm_min = 0;time_info.tm_sec = 0;char *time_string = asctime(&time_info); // 轉換為字符串printf("時間是:%s", time_string); // 輸出字符串(帶換行符)return 0;
}
輸出示例:
時間是:Sun Jan 1 12:00:00 2023
7. size_t strftime(char*, size_t, const char*, const struct tm*)
將日期時間結構轉換為自定義格式的字符串。
#include <stdio.h>
#include <time.h>int main() {time_t current_time = time(NULL); // 獲取當前時間戳struct tm *local_time = localtime(¤t_time); // 轉換為本地時間char buffer[100];// 自定義格式化:年-月-日 小時:分鐘:秒strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", local_time);printf("當前本地時間是:%s\n", buffer);return 0;
}
輸出示例:
當前本地時間是:2023-01-01 23:59:55
2.BKP
2.1 簡介
BKP 的基本功能:
- 存儲用戶數據:
-
- BKP 提供了一組獨立的寄存器,能夠存儲應用程序的重要數據,例如系統狀態、傳感器校準參數等。
- 在大容量和互聯型產品中,BKP 提供了 42 個 16 位寄存器,可存儲 84 字節數據。
- 在中容量和小容量產品中,BKP 僅提供 20 字節數據寄存器。
- 掉電保護:
-
- BKP 工作在備份域(Backup Domain)中,由 VBAT 供電。
- 即使主電源 VDD 被切斷,BKP 寄存器的數據仍然不會丟失。
- 這種特性非常適合存儲掉電后仍需保存的數據,例如:
-
-
- 實時時鐘(RTC)配置。
- 防篡改事件標志。
- 系統重要的用戶數據。
-
- 復位保護:
-
- 當系統處于待機模式被喚醒時,或者發生 系統復位 或 電源復位 時,備份寄存器的數據仍然保持不變。
- 只有特定的備份域復位操作(通過設置 RCC_BDCR 寄存器的 BDRST 位)才能清除 BKP 數據。
BKP 控制寄存器:
備份寄存器的控制寄存器(BKP_CR)用于管理以下功能:
- 防篡改檢測:
-
- BKP 控制寄存器支持 防篡改檢測(Tamper Detection) 功能。
- 當檢測到未授權的訪問或數據篡改時,可以觸發中斷以通知系統。
- 具體防篡改功能包括:
-
-
- 數據被篡改時產生中斷。
- 清除備份寄存器數據。
-
- RTC 校準功能:
-
- BKP 控制寄存器可以配合 RTC 校準功能使用。
- 用于存儲 RTC 校驗值、調節 RTC 校準值,并提供時鐘輸出到 PC13 引腳。
- 支持多種輸出功能:
-
- BKP 控制寄存器能夠控制在 PC13 引腳上輸出不同信號:
-
-
- RTC 校準時鐘。
- RTC 鬧鐘脈沖。
- RTC 秒脈沖。
-
如何訪問 BKP 寄存器:
默認情況下,備份域和備份寄存器被保護,無法直接訪問。以下是訪問流程:
- 使能時鐘:
-
- 設置 RCC_APB1ENR 寄存器的 PWREN 位 和 BKPEN 位,打開電源和后備接口的時鐘。
- 解除寫保護:
-
- 設置電源控制寄存器(PWR_CR)的 DBP 位(Disable Backup Domain Write Protection)。
- 只有在該位被置位后,才能對備份寄存器和 RTC 進行寫操作。
- 備份域復位(可選):
-
- 如果需要清空備份域的數據(包括 RTC 和 BKP 寄存器),可以設置 RCC_BDCR 寄存器的 BDRST 位。
BKP 的應用場景:
BKP 寄存器的主要應用場景如下:
- 存儲關鍵數據:
-
- 可以存儲掉電后需要保留的重要數據,例如:
-
-
- RTC 時間。
- 校準參數。
- 加密密鑰。
- 系統配置。
-
- 防篡改檢測:
-
- 用于檢測是否存在未授權的數據訪問或修改。
- 一旦檢測到篡改,可以通過中斷通知系統,同時清除備份寄存器中的數據。
- RTC 校準:
-
- 配合 RTC 使用,用于存儲 RTC 校驗值,并在 PC13 引腳輸出校準信號、鬧鐘脈沖或秒脈沖。
- 數據備份:
-
- 當系統進入低功耗或待機模式時,BKP 是一種可靠的數據存儲方案。
BKP 的硬件資源:
寄存器資源
在大容量和互聯型產品中,BKP 包括:
- 42 個 16 位的備份寄存器(BKP_DR1 到 BKP_DR42),每個寄存器可以存儲 2 字節,共計 84 字節。
- BKP 控制寄存器,用于配置防篡改檢測和 RTC 校準功能。
PC13 引腳功能
PC13 引腳在 BKP 配置中支持輸出以下信號:
- RTC 校準時鐘(RTC_CALIB_CLK)。
- RTC 鬧鐘脈沖(RTC_ALARM)。
- RTC 秒脈沖(RTC_SECOND)。
使用 BKP 的注意事項
- 寫保護機制:
-
- 默認情況下,備份域和 BKP 寄存器是受保護的,必須按照正確的流程解鎖才能進行寫操作。
- 避免意外寫入或修改。
- VBAT 電源支持:
-
- 確保在主電源斷電時,VBAT 能夠正常供電,以保證 BKP 數據不會丟失。
- 防篡改檢測的正確配置:
-
- 如果使用防篡改檢測功能,應正確設置中斷處理程序,以防止誤報或數據丟失。
- 低功耗模式的應用:
-
- 在進入待機或低功耗模式時,可以將重要數據寫入 BKP,以便喚醒后能夠快速恢復狀態。
2.2 基本結構
3.RTC時鐘
3.1 簡介
RTC(Real Time Clock,實時時鐘)是一種獨立的定時器模塊,用于提供時間和日歷功能。它包含一個連續計數的計數器,在適當的軟件配置下,能夠提供精確的時間跟蹤和日期維護功能。RTC 模塊特別適合需要低功耗、掉電保護的嵌入式應用場景。
RTC 是嵌入式系統(如 STM32 微控制器)中非常重要的功能模塊之一,其設計允許在系統復位或待機模式喚醒后保持時間和設置不變。它在后備域(Backup Domain)內工作,利用備用電池(VBAT)維持供電,即使主電源關閉,RTC 的計時功能仍然能正常運行。
3.1.1 RTC 的主要特性
\1. 靈活的計時功能
- RTC 模塊提供一個 32 位的連續計數器,用于測量較長的時間段。
- 通過軟件可以設置和修改計數器的值,以重新定義當前的時間和日期。
\2. 可編程的預分頻器
- RTC 允許配置一個可編程的預分頻系數,最高可以分頻到 2^20。
- 通過調整分頻系數,可以精確地設置 RTC 的時鐘頻率,以滿足不同的時間基準需求。
\3. 獨立的時鐘源
RTC 模塊支持以下三種時鐘源,具體選擇由硬件設計和應用需求決定:
- HSE(High-Speed External)時鐘:
-
- 高速外部振蕩器時鐘(HSE)可以通過除以 128 的方式作為 RTC 時鐘源。
- 高速時鐘一般供內部的應用程序和主要外設使用
- LSE(Low-Speed External)振蕩器:
-
- 低速外部振蕩器時鐘(通常為 32.768 kHz 晶體),提供高穩定性,非常適合 RTC。
- 低速時鐘主要給看門狗和RTC時鐘使用
- LSI(Low-Speed Internal)振蕩器:
-
- 低速內部振蕩器(內部 40 kHz 振蕩器),功耗較低,但精度略遜于 LSE。
注意:RTC 的時鐘頻率必須低于 APB1 接口時鐘(PCLK1)的四分之一。
\4. 多種復位類型
RTC 支持兩種不同類型的復位:
- 系統復位:
-
- APB1 接口由系統復位控制。
- 后備域復位:
-
- RTC 核心(包括預分頻器、鬧鐘、計數器和分頻器)僅受后備域復位的影響。通過設置 RCC_BDCR 寄存器的 BDRST 位,可以復位 RTC 核心。
\5. 多種中斷功能
RTC 提供 3 種專門的中斷功能,能夠實現豐富的時間事件管理:
- 鬧鐘中斷:
-
- RTC 可以配置一個軟件可編程的鬧鐘,觸發鬧鐘中斷,常用于定時喚醒或事件提醒。
- 秒中斷:
-
- 可產生一個周期性中斷信號,周期最長為 1 秒,適合用于定時任務。
- 溢出中斷:
-
- 當 RTC 的 32 位計數器溢出(從最大值回滾到 0)時觸發,指示計數器循環完成。
\6. 持久性
- RTC 位于備份域中,由備用電池(VBAT)供電。
- 即使主電源 VDD 斷開,RTC 的計數器和設置仍能正常運行。
- 系統復位或待機模式喚醒后,RTC 的配置和時間數據保持不變。
4-16 MHz HSE OSC:外部的4-16MHz高速石英晶體振蕩器,也就是晶振,一般都是接8MHz。
- 需要先進性128的分頻,后續的分頻器在進行適當的分頻,就可以輸出1Hz的頻率信號給計數器
LSE OSC 32.768 kHz:外部的 32.768 kHz(215)的低速晶振,一般是給RTC使用的(最常用)
LSI RC 40 KHz:內部的40 KHz低速RC晶振,可以提供給RTC,但一般是備用方案,給看門狗提供時鐘的情況比較多
3.1.2 訪問 RTC 的流程
默認情況下,RTC 和后備寄存器的訪問被禁止,這是為了防止意外的寫操作對關鍵數據造成破壞。以下步驟可以解鎖 RTC 和后備寄存器的訪問權限:
\1. 使能電源和后備接口時鐘
通過設置 RCC_APB1ENR 寄存器的以下兩位:
- PWREN 位:使能電源模塊。
- BKPEN 位:使能后備接口時鐘。
\2. 解除寫保護
設置 PWR_CR 寄存器的 DBP 位(Disable Backup Domain Write Protection)。
- 解除寫保護后,可以對 RTC 和后備寄存器進行讀寫操作。
\3. 配置 RTC 時鐘源
在 RCC_BDCR 寄存器中選擇 RTC 的時鐘源,常見配置為:
- 設置 RTCSEL 位 選擇時鐘源(LSE、LSI 或 HSE)。
- 通過設置 RTCEN 位 啟用 RTC。
\4. 后備域復位(可選)
如果需要重置 RTC 的配置,可以通過設置 RCC_BDCR 寄存器的 BDRST 位 觸發后備域復位。
3.1.3 RTC 的典型應用
\1. 時間和日歷功能
RTC 的主要應用是提供精確的時間和日期跟蹤功能。結合 32 位計數器和軟件支持,可以實現年、月、日、時、分、秒等信息的完整計算和管理。
\2. 鬧鐘功能
RTC 支持配置鬧鐘時間,當達到設定時間時,觸發鬧鐘中斷。常用于:
- 定時喚醒嵌入式系統。
- 提供定時提醒功能。
\3. 周期性事件管理
RTC 的秒中斷功能可以生成周期性信號,適合用于:
- 定時任務。
- 數據采集的時間基準。
\4. 超低功耗場景
RTC 模塊在主電源關閉或進入待機模式時,依然可以通過備用電池工作,適合需要長時間低功耗運行的應用場景。
\5. 數據采集和日志記錄
RTC 提供穩定的時間基準,可以用于時間戳記錄和數據采集。
3.1.4 RTC 的優勢
- 低功耗:RTC 工作在低功耗模式下,可以通過 VBAT 供電,適合掉電保護應用。
- 獨立性:RTC 獨立于主系統,即使 MCU 復位或掉電,時間信息也能保持不變。
- 多功能性:支持時間、日歷、鬧鐘、秒中斷和周期性事件管理。
- 高精度:結合 LSE 或 HSE 時鐘源,可以提供高精度的時間跟蹤。
3.2 框圖(詳細結構)
RTC(實時時鐘)模塊位于 后備區域(Backup Domain) 中,依賴于備用電池(VBAT)供電,即使主電源關閉也可以保持運行。框圖主要由以下幾個模塊組成:
- 時鐘輸入與分頻模塊
- RTC 可編程計數器
- 控制寄存器與中斷控制
- 待機喚醒功能
這些模塊協同工作以實現 RTC 的時間計數、鬧鐘、秒中斷和溢出中斷等功能。
(1) APB1 總線與接口
-
RTC 通過 APB1 總線(Advanced Peripheral Bus 1)連接到系統。
-
APB1 接口是 RTC 的通信接口,它允許 MCU(主控制器)通過寄存器對 RTC 進行配置和讀取。
-
注意:
-
- APB1 接口的時鐘頻率 必須高于 RTC 時鐘頻率的 4 倍,確保數據通信的穩定性。
(2) RTC 時鐘源(RTCCLK)
-
RTC 時鐘源 RTCCLK 是 RTC 模塊運行的核心時鐘信號。支持三種時鐘源選擇:
-
- HSE(高速外部時鐘)/128:高精度晶振時鐘。
- LSE(低速外部振蕩器):通常為 32.768 kHz 晶體,精度高、功耗低。
- LSI(低速內部振蕩器):40 kHz 內部振蕩器,功耗低,但精度稍差。
-
RTC 時鐘源通過 RCC 模塊配置。
(3) RTC 預分頻器
-
預分頻器(RTC Prescaler)是 RTC 模塊的重要組成部分,用于將高頻時鐘(RTCCLK)分頻為更低的頻率,適合 RTC 計數器使用。
-
功能:
-
- 通過寄存器 RTC_PRL(重載值寄存器) 和 RTC_DIV(分頻器寄存器) 設置分頻值。
- 比如,當輸入時鐘為 32.768 kHz,預分頻器設置為 32767 時,每秒觸發一次分頻輸出(1 Hz)。也就是PRL設置為32767(固定的),DIV初值可以設置為0,來一個輸入脈沖,DIV-1,溢出,輸出一個輸出脈沖,同時DIV被重載為32727;后面每來一個輸入脈沖,DIV的值減1,直到減到0時,再來一個輸入脈沖,DIV溢出,輸出一個脈沖信號,同時DIV繼續回到32767。實現每來一個32768脈沖的時鐘,輸出的是1脈沖的時鐘,也就是32.768 kHz被分頻為1Hz
- 輸出時鐘作為 RTC 計數器(RTC_CNT) 的輸入時鐘。
(4) RTC 可編程計數器
-
RTC 計數器(RTC_CNT) 是一個 32 位的遞增計數器,用于存儲當前的時間值。
-
工作原理:
-
- RTC_CNT 每秒遞增 1(當預分頻器設置為 1 秒時基)。
- 軟件可以讀取 RTC_CNT 的值來獲取當前的時間戳。
- 計數器可以被設置為任意值,以實現時間的初始化或校準。
(5) 鬧鐘功能
-
RTC 提供 RTC_ALR(鬧鐘寄存器),用戶可以通過設置鬧鐘時間與 RTC_CNT 的值進行比較。
-
當 RTC_CNT 的值與 RTC_ALR 相等時:
-
- 觸發 RTC_Alarm 中斷。
- 鬧鐘事件可以用于喚醒系統或執行定時任務。
(6) RTC 控制寄存器(RTC_CR)
-
RTC_CR 是 RTC 的核心配置寄存器,用于控制模塊功能和中斷管理。
-
主要功能:
-
- RTC_Second 中斷(SECF 和 SECIE):
-
-
- 每秒觸發一個中斷信號,用于實現周期性任務。
-
-
- RTC_Overflow 中斷(OWF 和 OWIE):
-
-
- 當 RTC_CNT 溢出(從最大值回到 0)時觸發。但一般是不會觸發的,因為這里的CNT定義的是一個32位的無符號數,到2106年的時候才會溢出(時間戳)
-
-
- RTC_Alarm 中斷(ALRF 和 ALRIE):
-
-
- 當 RTC_CNT 等于 RTC_ALR 的值時觸發。
-
-
- 通過 NVIC 中斷控制器 管理中斷優先級和響應。
(7) 中斷和待機模式喚醒
-
RTC 的中斷信號(RTC_Alarm、RTC_Second、RTC_Overflow)可以觸發系統中斷,通過 NVIC 中斷控制器 傳遞給 CPU 處理。
-
喚醒功能:
-
- RTC_Alarm 信號還可以通過 WKUP pin(喚醒引腳) 喚醒系統,從待機模式恢復到正常運行模式。
- 在低功耗應用中,RTC 是常用的喚醒觸發器。
(8) 后備區域與掉電保護
- RTC 位于后備區域(Backup Domain),由備用電池(VBAT)供電。
- 即使主電源關閉,RTC 的計數器和寄存器依然能正常運行。
- 在掉電或復位后,通過 VBAT 保持 RTC 的設置和當前時間不丟失。
3.3 RTC 的基本結構
(1) 初始化 RTC
- 選擇 RTC 時鐘源:
-
- 配置 RCC 模塊,選擇 RTCCLK 的來源(LSE、LSI 或 HSE/128)。
- 配置預分頻器:
-
- 設置 RTC_PRL 和 RTC_DIV,確保 RTC_CNT 每秒遞增 1(或其他所需頻率)。
- 解除寫保護:
-
- 通過 PWR_CR 的 DBP 位解除后備域寫保護,允許修改 RTC 的配置。
- 啟動 RTC:
-
- 啟用 RTC_CR 寄存器中的相關位,開始 RTC 計數。
(2) 讀取時間
- 通過 APB1 接口讀取 RTC_CNT 的值,獲取當前時間。
(3) 配置鬧鐘
- 設置 RTC_ALR 寄存器的值為目標時間。
- 啟用 RTC_ALR 中斷,等待鬧鐘事件觸發。
(4) 響應中斷
- 當 RTC_Alarm、RTC_Second 或 RTC_Overflow 中斷觸發時,系統可以通過 NVIC 響應中斷信號,執行相關操作。
(5) 喚醒系統
- 在低功耗模式下,RTC_Alarm 信號可以通過 WKUP pin 喚醒系統。
3.4 硬件電路
3.5 使用注意事項
3.5.1 使能對 BKP 和 RTC 的訪問
RTC 位于 后備區域(Backup Domain),默認情況下對其訪問是受限制的。這是為了保護后備區域(包括 RTC 和 BKP 寄存器)數據免于意外的寫操作。要使能對 RTC 和后備區域的訪問,需要執行以下步驟:
- 使能 PWR 和 BKP 的時鐘:
操作:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
-
- 設置 RCC_APB1ENR 寄存器的 PWREN 和 BKPEN 位。
-
-
- PWREN 位:使能電源模塊(PWR)的時鐘。
- BKPEN 位:使能后備區域(BKP)的時鐘。
-
- 解除后備區域的寫保護:
操作:
PWR_BackupAccessCmd(ENABLE);
-
- 設置 PWR_CR 寄存器的 DBP 位(Disable Backup Domain Write Protection)。
- 在解除寫保護后,允許對 RTC 和后備區域進行寫操作。
3.5.2 同步寄存器(RSF)狀態
在某些情況下,RTC 的 APB1 接口可能處于禁止狀態(如系統啟動后第一次訪問 RTC),此時需要等待 RTC 同步完成,才能正常讀取 RTC 寄存器的值。
- 檢查 RTC_CRL 的 RSF 位:
-
- RSF 位(寄存器同步標志位):指示 RTC 是否已經與 APB1 總線同步。
- 如果 RSF 位未被置位(同步未完成),則軟硬件操作必須等待 RSF 位被置位后才能繼續。
- 清除 RSF 標志:
操作:
while (RTC_GetFlagStatus(RTC_FLAG_RSF) == RESET); // 等待同步完成
-
- 軟件必須清除 RSF 位,等待同步完成。
- 確認同步完成后,才能讀取 RTC 的計數器值。
3.5.3 進入 RTC 配置模式
要對 RTC 進行配置(如設置計數器、預分頻器或鬧鐘寄存器的值),必須讓 RTC 進入 配置模式(Configuration Mode)。
- 設置 CNF 位:
-
- 通過設置 RTC_CRL 寄存器的 CNF 位,進入配置模式。
- 配置完成后,必須清除 CNF 位,退出配置模式。
- 可配置的寄存器:
-
- RTC_PRL(預分頻器寄存器):用于配置 RTC 的時鐘分頻值。
- RTC_CNT(計數器寄存器):設置 RTC 的當前計數值。
- RTC_ALR(鬧鐘寄存器):設置 RTC 的鬧鐘值。
- 操作流程:
示例代碼:
RTC_EnterConfigMode(); // 進入配置模式
RTC_SetPrescaler(32767); // 設置預分頻器值
RTC_SetCounter(0); // 設置計數器值
RTC_SetAlarm(3600); // 設置鬧鐘值
RTC_ExitConfigMode(); // 退出配置模式
-
- 進入配置模式,修改寄存器值,退出配置模式。
3.5.4 寫操作完成前必須等待
RTC 的所有寫操作都是異步完成的,寫操作可能需要一定時間。要確保寫入的值生效,必須等待上一次寫操作完成后,再進行下一次寫操作。
- 檢查 RTOFF 狀態:
-
- RTOFF 位(寫完成標志位):當 RTOFF 位為 1 時,表示 RTC 的寄存器寫操作已經完成,可以進行下一次寫操作。
- 在修改任何 RTC 寄存器前,必須先檢查 RTOFF 位為 1。
- 操作:
示例代碼:
while (RTC_GetFlagStatus(RTC_FLAG_RTOFF) == RESET); // 等待寫完成
-
- 每次寫操作完成后,檢查 RTOFF 位,確保寫入成功。
3.5.5 概括
關鍵操作流程
- 使能 PWR 和 BKP 的時鐘。
- 解除寫保護(設置PWR_CR的DBP,使能對BKP和RTC的訪問)。
- 檢查同步狀態(RSF 位)。若在讀取RTC寄存器時,RTC的APB1接口曾經處于禁止狀態,則軟件首先必須等待RTC_CRL寄存器中的RSF位(寄存器同步標志)被硬件置1 — 也就是
RTC_WaitForSynchro
函數,可以去看其定義就可以發現是對RSF標志位進行設置的 - 進入配置模式(設置 CNF 位)。
- 修改 RTC 寄存器的值:
-
- RTC_PRL(預分頻器寄存器)
- RTC_CNT(計數器寄存器)
- RTC_ALR(鬧鐘寄存器)
- 確保寫操作完成(檢查 RTOFF 位,僅當RTOFF狀態位是1時,才可以寫入RTC寄存器)。對RTC任何寄存器的寫操作,都必須在前一次寫操作結束后進行。可以通過查詢RTC_CR寄存器中的RTOFF狀態位,判斷RTC寄存器是否處于更新中。僅當RTOFF狀態位是1時,才可以寫入RTC寄存器 ---- 也就是
RTC_WaitForLastTask
函數,去看其函數定義可以發現其就是對RTOFF狀態進行循環查詢是否處于更新中 - 退出配置模式(清除 CNF 位)。
可能的錯誤操作
- 未使能時鐘: 如果 PWR 和 BKP 的時鐘未使能,將無法訪問 RTC 和后備寄存器。
- 未解除寫保護: 如果未設置 DBP 位,則無法對 RTC 和后備寄存器進行寫操作。
- 未等待同步完成: 如果 RSF 位未被置位而直接讀取寄存器,可能會導致讀取錯誤。
- 未等待寫完成: 如果在 RTOFF 位清零時修改寄存器,可能會覆蓋之前的寫操作。
優化建議
- 在代碼中加入足夠的錯誤檢查,確保每一步操作的狀態都滿足要求。
- RTC 的配置流程較為復雜,建議封裝成函數,減少出錯幾率。
#include "stm32f10x.h"void RTC_Init(void) {// 1. 使能 PWR 和 BKP 的時鐘RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);// 2. 解除寫保護PWR_BackupAccessCmd(ENABLE);// 3. 選擇 LSE 作為 RTC 時鐘源RCC_LSEConfig(RCC_LSE_ON);while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 等待 LSE 就緒// while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET); // 等待 LSE 就緒RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);// 4. 啟用 RTC 時鐘RCC_RTCCLKCmd(ENABLE);// 5. 等待 RTC 同步完成RTC_WaitForSynchro();RTC_WaitForLastTask(); //等待上一次操作完成,確保寫操作完成// 6. 配置 RTC// RTC_EnterConfigMode(); // 進入配置模式,其實也可以不用寫,下面的RTC_SetPrescaler函數其實內部就調用了RTC_SetPrescaler(32767); // 設置預分頻器(1 秒為基準)RTC_WaitForLastTask(); // 確保寫操作完成RTC_SetCounter(0); // 設置計數器初始值為 0,根據設置的分頻后的時鐘,會以1s的時間間隔開始自增RTC_WaitForLastTask(); // 確保寫操作完成// RTC_ExitConfigMode(); // 退出配置模式,和RTC_EnterConfigMode同理
}int main(void) {RTC_Init(); // 初始化 RTCwhile (1) {uint32_t time = RTC_GetCounter(); // 獲取當前時間printf("當前時間:%lu 秒\n", time);}
}
4.實驗
4.1 BKP
📎12-1 讀寫備份寄存器.zip
hardware:
- 📎Key.c📎Key.h📎OLED.c📎OLED.h📎OLED_Font.h
User
- 📎main.c ---- 主要看該文件中的函數使用
4.2 RTC
📎12-2 實時時鐘.zip
Hardware:
- 📎OLED.c📎OLED.h📎OLED_Font.h
User:
- 📎main.c
System:
- 📎MyRTC.c📎MyRTC.h
具體的函數去看函數手冊中的:
5.擴展
STM32 有5個時鐘源:HSI、HSE、LSI、LSE、PLL。
- HSI是高速內部時鐘,RC振蕩器,頻率為8MHz,精度不高。
- HSE是高速外部時鐘,可接石英/陶瓷諧振器,或者接外部時 鐘源,頻率范圍為4MHz~16MHz。
- LSI是低速內部時鐘,RC振蕩器,頻率為40kHz,提供低功耗時鐘。WDG
- LSE是低速外部時鐘,接頻率為32.768kHz的石英晶體。RTC
- PLL為鎖相環倍頻輸出,其時鐘輸入源可選擇為HSI/2、HSE或者HSE/2。倍頻可選擇為2~16倍,但是其輸出頻率最大不得超過72MHz。
系統時鐘SYSCLK可來源于三個時鐘源:
- HSI振蕩器時鐘
- HSE振蕩器時鐘
- PLL時鐘
重要的時鐘有:
- SYSCLK(系統時鐘)
- AHB總線時鐘
- APB1總線時鐘(低速): 速度最高36MHz
- APB2總線時鐘(高速): 速度最高72MHz
- PLL時鐘