stm32之BKP备份寄存器和RTC时钟
目录
- 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时钟