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_DR1BKP_DR2BKP_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_CNTRTC_ALRRTC_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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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 的分布式锁实现…