当前位置: 首页 > news >正文

【STM32】RTC实时时钟

【STM32】RTC实时时钟

  • 一、Unix时间戳
    • 1.1 什么是时间戳
    • 1.2 时间戳转换
    • 1.3 C标准库<time.h>
      • (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*)
  • 二、BKP备份寄存器
    • 2.1 BKP简介
    • 2.2 BKP基本结构
  • 三、RTC实时时钟
    • 3.1 RTC简介
    • 3.2 RTC框图
    • 3.3 RTC基本结构
    • 3.4 硬件电路
    • 3.5 RTC 操作注意事项
  • 四、其他

一、Unix时间戳

1.1 什么是时间戳

  • Unix 时间戳(Unix Timestamp)定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒
  • 时间戳存储在一个秒计数器中秒计数器为32位/64位的整型变量
  • 世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间

时间戳是标识某一时刻的数值或字符串,通常以“从固定基准时间(如1970-01-01 00:00:00 UTC)起的秒数”或“人类可读的日期时间文本”形式存在,用于记录、同步或验证时间先后。

ps(了解):
GMT(Greenwich Mean Time)格林尼治标准时间是一种以地球自转为基础的时间计量系统。它将地球自转一周的时间间隔等分为24小时,以此确定计时标准
UTC(Universal Time Coordinated)协调世界时是一种以原子钟为基础的时间计量系统。它规定铯133原子基态的两个超精细能级间在零磁场下跃迁辐射9,192,631,770周所持续的时间为1秒。当原子钟计时一天的时间与地球自转一周的时间相差超过0.9秒时,UTC会执行闰秒来保证其计时与地球自转的协调一致

在这里插入图片描述

1.2 时间戳转换

C语言的time.h模块提供了时间获取和时间戳转换的相关函数,可以方便地进行秒计数器、日期时间和字符串之间的转换
在这里插入图片描述
在这里插入图片描述

1.3 C标准库<time.h>

以下是time.h头文件中各个时间戳转换相关函数的使用示例及详细解释:

(1) time_t time(time_t*)

该函数用于获取系统当前的时间,返回值是从1970年1月1日00:00:00 UTC 到当前时间所经过的秒数。

#include <stdio.h>
#include <time.h>int main() {time_t current_time;// 获取当前时间current_time = time(NULL); printf("从1970年1月1日00:00:00 UTC到现在经过的秒数为: %ld\n", (long)current_time);return 0;
}

解释

  • time(NULL)表示直接获取当前时间戳,参数传入NULL,表示不需要将时间戳存储到传入的指针指向的位置。函数返回值赋给current_time,然后通过printf打印出来。

(2) struct tm* gmtime(const time_t*)

time_t类型表示的秒计数器转换为格林尼治时间的struct tm结构体形式。

 struct tm {int tm_sec;         /* 秒,范围从 0 到 59 */int tm_min;         /* 分,范围从 0 到 59 */int tm_hour;        /* 小时,范围从 0 到 23 */int tm_mday;        /* 一月中的第几天,范围从 1 到 31 */int tm_mon;         /* 月份,范围从 0 到 11 */int tm_year;        /* 自 1900 起的年数 */int tm_wday;        /* 一周中的第几天,范围从 0 到 6 */int tm_yday;        /* 一年中的第几天,范围从 0 到 365 */int tm_isdst;       /* 夏令时 */    
};   
#include <stdio.h>
#include <time.h>int main() {time_t current_time = time(NULL);struct tm *gmt_time = gmtime(&current_time);printf("格林尼治时间:%d-%02d-%02d %02d:%02d:%02d\n", gmt_time->tm_year + 1900, gmt_time->tm_mon + 1, gmt_time->tm_mday,gmt_time->tm_hour, gmt_time->tm_min, gmt_time->tm_sec);return 0;
}

解释

  • 先通过time函数获取当前时间戳current_time
  • 然后将current_time的地址传入gmtime函数,gmtime函数会将秒数转换为格林尼治时间的struct tm结构体,并返回指向该结构体的指针,赋值给gmt_time
  • struct tm结构体中,tm_year是从1900年开始的年份偏移,tm_mon是从0开始的月份偏移,所以在打印时需要进行相应的处理。

(3) struct tm* localtime(const time_t*)

time_t类型表示的秒计数器转换为本地时间的struct tm结构体形式。

#include <stdio.h>
#include <time.h>int main() {time_t current_time = time(NULL);struct tm *local_time = localtime(&current_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;
}

解释
gmtime函数类似,不同之处在于localtime会根据系统设置的时区信息,将秒数转换为本地时间对应的struct tm结构体 。

(4) time_t mktime(struct tm*)

struct tm结构体表示的日期时间转换为time_t类型的秒计数器(以本地时间为准)。

#include <stdio.h>
#include <time.h>int main() {struct tm custom_time = {.tm_year = 2024 - 1900, .tm_mon = 10 - 1, .tm_mday = 1, .tm_hour = 12, .tm_min = 30, .tm_sec = 0};time_t seconds = mktime(&custom_time);printf("自定义时间对应的秒数为: %ld\n", (long)seconds);return 0;
}

解释

  • 先定义一个struct tm结构体custom_time,并设置好各个成员变量,注意年份和月份的偏移。
  • 然后将custom_time的地址传入mktime函数,mktime函数会根据本地时区,将结构体表示的时间转换为从1970年1月1日00:00:00 UTC 开始的秒数,返回值赋给seconds并打印。

(5) char* ctime(const time_t*)

time_t类型表示的秒计数器转换为默认格式的字符串。

#include <stdio.h>
#include <time.h>int main() {time_t current_time = time(NULL);char *time_str = ctime(&current_time);printf("当前时间字符串: %s", time_str);return 0;
}

解释

  • 先获取当前时间戳current_time
  • 再将current_time的地址传入ctime函数,ctime函数会将秒数转换为默认格式的字符串,例如Wed Oct 18 15:30:00 2023\n ,并返回指向该字符串的指针,最后打印输出。

(6) char* asctime(const struct tm*)

struct tm结构体表示的日期时间转换为默认格式的字符串。

#include <stdio.h>
#include <time.h>int main() {time_t current_time = time(NULL);struct tm *local_time = localtime(&current_time);char *time_str = asctime(local_time);printf("本地时间字符串: %s", time_str);return 0;
}

解释

  • 先获取当前时间戳并转换为本地时间的struct tm结构体。
  • 然后将local_time指针传入asctime函数,asctime函数会将struct tm结构体表示的时间转换为默认格式的字符串 ,并返回指向该字符串的指针,最后打印出来。

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

struct tm结构体表示的日期时间按照自定义格式转换为字符串。

#include <stdio.h>
#include <time.h>int main() {time_t current_time = time(NULL);struct tm *local_time = localtime(&current_time);char buffer[100];strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", local_time);printf("自定义格式的本地时间字符串: %s\n", buffer);return 0;
}

解释

  • 同样先获取当前时间戳并转换为本地时间的struct tm结构体。
  • 定义一个字符数组buffer用于存储转换后的字符串。
  • strftime函数的第一个参数是目标字符串缓冲区指针,第二个参数是缓冲区的大小,第三个参数是自定义的格式字符串,%Y表示四位数的年份,%m表示两位数的月份,%d表示两位数的日期,%H表示小时(24小时制),%M表示分钟,%S表示秒,第四个参数是指向struct tm结构体的指针。函数会按照指定格式将时间转换为字符串存入buffer,最后打印输出。

二、BKP备份寄存器

2.1 BKP简介

  • BKP(Backup Registers)备份寄存器
  • BKP 可用于存储用户应用程序数据。当 VDD(2.0~3.6V)电源被切断,他们仍然由 VBAT(1.8~3.6V)维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位
  • TAMPER引脚产生的侵入事件将所有备份寄存器内容清除
  • RTC引脚输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲
  • 存储RTC时钟校准寄存器
  • 用户数据存储容量:
    20字节(中容量和小容量)/ 84字节(大容量和互联型)

2.2 BKP基本结构

在这里插入图片描述

  • VBAT(备用电池输入):当主电源(VDD)掉电时,VBAT 为备份域供电,确保备份寄存器(DR)、RTC 等模块在主电源中断后仍能工作或保存数据(实现 “掉电不丢失”)。
  • TAMPER(入侵检测输入):外部入侵检测信号输入,可触发 “入侵事件”(如检测到外部引脚被非法触碰),进而触发备份域的中断或数据保护逻辑。
  • RTC(时钟输出 / 输入):与实时时钟(RTC)模块交互,既接收 RTC 的时钟信号用于时间同步,也可将 RTC 的时钟(如秒脉冲、闹钟脉冲)输出,为外部设备提供时间基准。
  • 控制寄存器:配置备份域的工作模式(如入侵检测的触发条件、时钟输出的使能等)。
  • 状态寄存器:反馈备份域的运行状态(如是否检测到入侵、VBAT 供电是否正常等)。
  • RTC 时钟校准寄存器:用于微调 RTC 的时钟精度,补偿晶体振荡的误差,让 RTC 计时更准确。

三、RTC实时时钟

3.1 RTC简介

  • RTC(Real Time Clock)实时时钟
  • RTC是一个独立的定时器,可为系统提供时钟和日历的功能
  • RTC 和时钟配置系统处于后备区域,系统复位时数据不清零VDD(2.0~3.6V)断电后可借助 VBAT(1.8~3.6V)供电继续走时
  • 32位的可编程计数器,可对应Unix时间戳的秒计数器
  • 20位的可编程预分频器,可适配不同频率的输入时钟
  • 可选择三种RTC时钟源
    HSE时钟除以128(通常为8MHz/128)
    LSE振荡器时钟(通常为32.768KHz)
    LSI振荡器时钟(40KHz)

3.2 RTC框图

在这里插入图片描述
1. RTCCLKRTC 的输入时钟源(通常来自外部 32.768kHz 晶振、内部低速时钟 LSI,或其他高速时钟分频后得到),是 RTC 计时的 “基准脉冲”。

2. RTC 预分频器(分频产生 “秒脉冲”)
预分频器由 RTC_PRL(预分频装载寄存器) 和 RTC_DIV(预分频余数寄存器) 组成,作用是将 RTCCLK 分频为1Hz 的秒脉冲(实现 “秒计数”):

  • RTC_PRL:存储预分频系数(比如 32.768kHz 时钟时,PRL 通常设为 32767,对应分频比 32768)。
  • RTC_DIV:实时递减计数器,从 RTC_PRL 的值开始自减,当减到 0 时,产生一个 “溢出脉冲”(即TR_CLK),同时自动重新加载 RTC_PRL 的值,循环往复。
  • 上升沿触发:RTC_DIV 的递减由 RTCCLK 的上升沿驱动,保证分频节奏与时钟源同步。

3. 32 位可编程计数器与闹钟(计时核心)

  • RTC_CNT(32 位计数器):
    接收预分频器产生的 TR_CLK(1Hz 脉冲),每收到一个脉冲就加 1,因此 RTC_CNT 的值代表 “从基准时间(如 1970-01-01 00:00:00)起经过的秒数”。
    标注 “待机时维持供电”:待机模式下,RTC_CNT 仍靠备份域供电,计时不中断(这是 RTC 掉电仍能计时的关键)。
  • RTC_ALR(闹钟寄存器):
    存储用户设置的 “闹钟时间”(对应 RTC_CNT 的某个计数值)。
    内部比较器 “=”:实时比较 RTC_CNT 与 RTC_ALR 的值,当两者相等时,产生 RTC_Alarm 信号(触发闹钟)。

4. 中断与待机唤醒控制

  • RTC_Second秒脉冲,每秒产生一次,用于秒级计时或同步。
  • RTC_Overflow计数器溢出,32位计数器计满后触发(因周期极长,实际少用)。
  • RTC_Alarm闹钟匹配,当RTC计数值与预设闹钟值一致时触发,用于定时唤醒或事件触发。
  • 待机唤醒(WKP_STDBY)
    当 MCU 处于待机模式时,RTC_Alarm 信号可通过 WKP_STDBY 逻辑,直接触发 “退出待机模式”(无需 NVIC 参与,因为待机时 NVIC 断电)。
    WKUP pin:外部唤醒引脚,也可与 RTC_Alarm 一起,通过或门 WKP_STDBY 触发待机唤醒。

3.3 RTC基本结构

在这里插入图片描述
整体逻辑总结:
RTC 工作流程:选择时钟源 → 预分频得到 1Hz 秒脉冲 → CNT 秒计数 → ALR 闹钟匹配 → 事件触发中断 / 唤醒。这套架构让 STM32 既能实现精准的长期计时,又能通过闹钟功能实现 “定时任务、低功耗唤醒”,是物联网、工业控制等场景中 “时间同步、定时触发” 的核心。

3.4 硬件电路

在这里插入图片描述

3.5 RTC 操作注意事项

要详细分析 STM32 RTC 操作及关联的标准库函数,需结合 STM32 标准外设库(StdPeriph) 的 API 与 RTC 硬件特性展开,以下分“使能访问、同步等待、配置模式、写操作原子性”四部分拆解:

一、使能 BKP 和 RTC 访问(时钟与权限解锁)

  • 硬件逻辑
    RTC 属于备份域(BKP),需先使能 PWR 时钟(电源管理)和 BKP 时钟,再解锁备份域访问权限。

标准库函数

// 1. 使能 PWR 和 BKP 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);// 2. 使能对 BKP 和 RTC 的访问(设置 PWR_CR 的 DBP 位)
PWR_BackupAccessCmd(ENABLE);
  • RCC_APB1PeriphClockCmd:属于 RCC(复位与时钟控制) 模块的函数,用于控制 APB1 总线上外设的时钟使能/禁用。这里同时使能 PWR(电源管理)和 BKP(备份域)的时钟。
  • PWR_BackupAccessCmd:属于 PWR(电源) 模块的函数,用于控制“备份域访问权限”。置位后,才能对 BKP 和 RTC 的寄存器进行读写。

二、读取 RTC 寄存器前的同步等待(RSF 标志)

  • 硬件逻辑
    RTC 内部时钟(RTCCLK)与 APB1 总线时钟不同步,若 APB1 接口曾被禁止,读取 RTC 寄存器前需等待 RTC_CRLRSF 位(寄存器同步标志)置 1,确保数据有效。

标准库函数

// 等待 RTC_CRL 的 RSF 位被硬件置 1(同步完成)
RTC_WaitForSynchro();								//等待同步

三、进入 RTC 配置模式(CNF 位控制)

  • 硬件逻辑
    要修改 RTC_PRL(预分频)、RTC_CNT(计数器)、RTC_ALR(闹钟)等核心寄存器,必须先将 RTC_CRLCNF 位置 1,使 RTC 进入配置模式

标准库函数

// 进入 RTC 配置模式(设置 RTC_CRL 的 CNF 位)
RTC_EnterConfigMode();
  • RTC_EnterConfigMode:属于 RTC 模块的函数,内部操作是置位 RTC_CRLCNF 位,使 RTC 进入配置模式,允许对核心寄存器写入。
  • 这一步通常不需要自己调用,在标准库函数中
    RTC_SetPrescaler RTC_SetCounter RTC_SetAlarm RTC_SetTime()这几个函数内部通过 RTC_EnterConfigMode() 和 RTC_ExitConfigMode(),间接完成了 “设置 CNF 位进入配置模式 → 写寄存器 → 清除 CNF 位退出配置模式” 的流程

四、RTC 写操作的原子性等待(RTOFF 标志)

  • 硬件逻辑
    RTC 写操作是“原子操作”(需完整执行),两次写操作之间必须等待前一次完成。通过查询 RTC_CRLRTOFF 位(寄存器操作完成标志),仅当 RTOFF=1 时,才能发起新的写操作。

标准库函数

// 等待 RTOFF 位为 1(前一次写操作完成)
RTC_WaitForLastTask();								//等待上一次操作完成

四、其他

BKP和RTC的关系

  • 供电方面:BKP和RTC都位于后备区域,当主电源VDD被切断后,它们都由VBAT(备用电池电源)维持供电,确保在主电源掉电时能继续工作或保存数据。
  • 功能协作方面
    数据存储:RTC在运行过程中需要存储一些数据,如校准值、时间计数等,这些数据可存储在BKP中。因为BKP具有在主电源断电、系统复位或电源复位时数据不丢失的特性,能为RTC数据提供稳定的存储环境。
    时钟输出:BKP可配置为输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲,为外部设备提供时间基准信号。
  • 配置方面:对BKP和RTC的访问使能需要先设置RCC_APB1ENR寄存器使能PWR和BKP时钟,再设置PWR_CR寄存器的DBP位,才能对它们进行访问和操作。

时钟输出解释:外部设备(比如另一个单片机、计数器、示波器等)就可以通过这个引脚,获取 RTC 的时间基准。举例来说,用示波器接这个引脚,就能看到每秒一次的脉冲,直观确认 RTC 是否在正常计时;或者让另一个设备通过这个脉冲同步自己的动作,实现多设备的时间对齐。
简单说,就是 BKP 帮 RTC 把内部的时间信号 “传出去”,给外部设备当参考。

写BPK_DR1时,写完一个8位,指针会自增到下一位吗?
在 STM32 中,BKP_DR(备份数据寄存器) 是 8 位寄存器,每个寄存器(如 BKP_DR1、BKP_DR2……)独立存储 8 位数据,且没有自动递增的指针。
当你写入 BKP_DR1 时,数据仅存储在 DR1 中;若要写入下一个寄存器(如 DR2),必须显式指定地址(如通过寄存器地址直接访问 DR2),不会因为写了 DR1 就自动切换到 DR2。

那如果我4位先写到DR1,再写一个4位到DR1,会覆盖吗?
会覆盖。
BKP_DR 寄存器是 8 位寄存器,每次写入操作都会覆盖整个寄存器的 8 位数据。即使你分两次写入 4 位数据(比如第一次写低 4 位,第二次写高 4 位),第二次写入时,新的 4 位数据会直接覆盖寄存器中原有的 8 位内容(包括之前的低 4 位),而不是 “拼接” 或 “补充”。

区分RCC与RTC
RCC 是复位与时钟控制,为 STM32 各模块提供时钟并管理复位 。它有 HSI、HSE、LSI、LSE 等多种时钟源可选,能灵活配置各外设时钟,关乎系统性能与功耗。
RTC 即实时时钟,用于提供精确时间日期 。靠 LSE 等时钟源驱动,掉电后靠 VBAT 维持计时,常用于定时任务、时间戳记录。
打个比方:如果把芯片比作一个工厂,RCC 就是 “电力和时间调度中心”—— 决定用哪个 “发电机”(时钟源)供电,怎么分配电力(时钟)给各个车间(外设),以及在机器出问题时如何重启(复位)。而 RTC 更像工厂里的 “精密闹钟”,专门负责时间计数,它的时钟通常是由 RCC 从低速时钟源(比如 LSE 晶振)分配过来的。


有关【STM32】RTC实时时钟 就到这,希望对你有所帮助,感谢观看!

码文不易,留个赞再走吧~

http://www.dtcms.com/a/502831.html

相关文章:

  • 【数据结构与算法基础】04. 线性表与链表详解(C++ 实战)
  • C程序中的预处理器
  • 长沙黄页全域seo
  • 负载均衡技术:Nginx/HAProxy/F5 等负载均衡配置与优化
  • 外国人做的关于中国的视频网站吗高师院校语言类课程体系改革与建设 教学成果奖申报网站
  • Linux 进阶指令实操指南:文件查看、时间管理、搜索压缩全场景覆盖(附高频案例)
  • K8S(十六)—— K8S集群apiserver证书有效期修改指南(适配v1.20.11版本)
  • Altium Designer(AD24)Reports报告功能总结
  • 第一章 绪论——课后习题解练【数据结构(c语言版 第2版)】
  • Ubuntu 系统 RabbitMQ 安装指南与使用(含 C++ 客户端与 SSL 错误解决)
  • 网站开发外包 价格阿里巴巴国际站入驻费用及条件
  • MVVM架构模式详解:从原理到Android实战
  • 【Pico企业版】Pico企业版的多种Wifi快速连接方式(Pico 4UE的快捷Wifi连接技巧)
  • Kafka服务端处理producer请求原理解析
  • 以电商系统为例,理解用户体验五层模型
  • 兰州网站开发企业学校门户网站建设
  • CreArt2.5.7 | 无限AI图片生成,将文字描述转化为艺术作品
  • Linux企业级解决方案架构:字节跳动短视频推荐系统全链路实践
  • Python编程之常用模块
  • ios面试底层题目
  • h5游戏免费下载:《高达战争》
  • 百度网盘不限速2025年最新方法
  • 网站维护大概要多久学校英文网站建设申请
  • 深入比较 Rust 与 Go:并发时代的两把利剑
  • 容器安全:gVisor系统调用过滤,攻击面缩小?
  • 前端基础二、CSS(二)、CSS基础选择器
  • 学做电影网站asp.net门户网站项目怎么做
  • 云主机搭建多个网站专业型网站网站
  • 【Linux学习笔记】线程同步与互斥之生产者消费者模型
  • C++函数使用