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

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.dtcms.com/a/351900.html

相关文章:

  • Ubuntu 操作系统
  • 高速CANFD通讯接口芯片ASM1042性能分析与5Mbps多节点测验
  • 进程管理详解
  • 【ElasticSearch】客户端选择
  • Sigma规则集网络安全应用(Elasticsearch、es日志安全检查、SOC、自定义规则)
  • Linux修改服务器时区
  • S2B2B系统哪个好,商淘云、数商云、金蝶云苍穹供应链批发哪个比较靠谱
  • 模型微调训练中超长文本训练存在的问题
  • 机器视觉学习-day02-灰度化实验
  • 更新依赖失败,报错
  • 赋能增长:商城分销平台的五大核心模式与适用场景
  • 京东招java开发
  • 解决Ubuntu拉取Docker镜像失败问题。
  • 云计算学习笔记——Linux硬盘、硬盘划分、交换空间、自动挂载篇
  • 淤地坝安全在线监测系统
  • 如何用企业微信AI解决金融运维难题,让故障响应快、客服专业度高
  • Android 中使用开源库 ZXing 生成二维码图片
  • 实训日志day28
  • 人工智能-python-深度学习-参数初始化与损失函数
  • Redis核心机制解析:数据结构、线程模型与内存管理策略
  • Axios多实例封装
  • 产品运营必备职场通用能力及提升攻略,一文说明白
  • 人工智能之数学基础:离散型随机变量的概率分布有哪些?
  • windows下配置lua环境
  • KubeBlocks for Kafka 揭秘
  • 100种交易系统(6)均线MA识别信号与杂音
  • 部署本地模型,使用cherry-studio测试本地模型和云端模型
  • 【最短路问题转换/拓扑排序+dp】P1807 最长路
  • 广度优先遍历-BFS
  • 【跨国数仓迁移最佳实践7】基于MaxCompute多租的大数据平台架构