【江协科技STM32】读写备份寄存器RTC实时时钟(学习笔记)

news/2025/4/26 11:44:49/

 参考相关文章理解:

【江协科技STM32】Unix时间戳(学习笔记)-CSDN博客

【江协科技STM32】BKP备寄存器&RTC实时时钟(学习笔记)_stm32断电保存时钟-CSDN博客 

读写备份寄存器

接线图:VBAT是从STLINK的3.3V引出来的,注意不要接到5V的

 

 BKP初始化:

①设置RCC_APB1ENR的PWREN和BKPEN,开启PWR和BKP时钟     

②设置PWR_CR的DBP,使能对BKP和RTC的访问

 代码较少,就没有对BKP单独进行封装,下面直接写

uint8_t KeyNum;					//定义用于接收按键键码的变量uint16_t ArrayWrite[] = {0x1234, 0x5678};	//定义要写入数据的测试数组
uint16_t ArrayRead[2];						//定义要读取数据的测试数组int main(void)
{OLED_Init();				//OLED初始化Key_Init();					//按键初始化OLED_ShowString(1, 1, "W:");OLED_ShowString(2, 1, "R:");RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);		//开启PWR的时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);		//开启BKP的时钟/*备份寄存器访问使能*/PWR_BackupAccessCmd(ENABLE);							//使用PWR开启对备份寄存器的访问while (1){KeyNum = Key_GetNum();		//获取按键键码if (KeyNum == 1)			//按键1按下{ArrayWrite[0] ++;		//测试数据自增,先加加在写入ArrayWrite[1] ++;BKP_WriteBackupRegister(BKP_DR1, ArrayWrite[0]);	//写入测试数据到备份寄存器BKP_WriteBackupRegister(BKP_DR2, ArrayWrite[1]);//ArrayWrite[0] ++;		//测试数据自增,或者写入后再加加//ArrayWrite[1] ++;OLED_ShowHexNum(1, 3, ArrayWrite[0], 4);		//显示写入的测试数据OLED_ShowHexNum(1, 8, ArrayWrite[1], 4);}ArrayRead[0] = BKP_ReadBackupRegister(BKP_DR1);		//读取备份寄存器的数据ArrayRead[1] = BKP_ReadBackupRegister(BKP_DR2);OLED_ShowHexNum(2, 3, ArrayRead[0], 4);				//显示读取的备份寄存器数据OLED_ShowHexNum(2, 8, ArrayRead[1], 4);}
}

 相关函数:

void PWR_BackupAccessCmd(FunctionalState NewState)//启用或禁用对RTC和备份寄存器的访问

参数说明
NewState访问RTC和备份寄存器的新状态;取值包括:ENABLE或DISABLE

 void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data)//将用户数据写入指定的数据备份寄存器

参数说明
BKP_DR指定数据备份寄存器,该参数可以是BKP_DRx,其中x:[1,42],STM32是中容量型芯片,DR是范围1~10;大容量和互联型才有42各DR
Data

写入的数据

 uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR)//从指定的数据备份寄存器读取数据

参数说明
BKP_DR指定数据备份寄存器,该参数可以是BKP_DRx,其中x:[1,42],STM32是中容量型芯片,DR是范围1~10;大容量和互联型才有42各DR

 返回值:指定的数据备份寄存器的内容。

实验结果:

 

RTC实时时钟 

接线图:

 

 RTC初始化

具体步骤: 

①和BKP一样,开启PWR和BKP时钟,使能对BKP和RTC的访问

②启动RTC时钟,计划使用LSE作为系统时钟,所以要使用RCC模块里的函数,开启LSE时钟,注意LSE时钟为了省电,默认是关闭的,所以需要我们自己手动开启

③ 配置RTCCLK这个数据选择器,指定LSE为RTCCLK,函数也是在RCC模块

④ 等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1和查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器

⑤配置预分频器,给PRL预重装载器一个合适的分频值,确保输出频率为1Hz

⑥配置CNT的值,给RTC一个合适的初始时间,之后需要闹钟或者中断就配置就可以 

void HerRTC_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);		//开启PWR的时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);		//开启BKP的时钟/*备份寄存器访问使能*/PWR_BackupAccessCmd(ENABLE);							//使用PWR开启对备份寄存器的访问if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)			//通过写入备份寄存器的标志位,判断RTC是否是第一次配置//if成立则执行第一次的RTC配置{RCC_LSEConfig(RCC_LSE_ON);							//开启LSE时钟while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);	//等待LSE准备就绪RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);				//选择RTCCLK来源为LSERCC_RTCCLKCmd(ENABLE);								//RTCCLK使能RTC_WaitForSynchro();								//等待同步RTC_WaitForLastTask();								//等待上一次操作完成RTC_SetPrescaler(32768 - 1);						//设置RTC预分频器,预分频后的计数频率为1HzRTC_WaitForLastTask();								//等待上一次操作完成HerRTC_SetTime();									//设置时间,调用此函数,全局数组里时间值刷新到RTC硬件电路BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);			//在备份寄存器写入自己规定的标志位,用于判断RTC是不是第一次执行配置}else													//RTC不是第一次配置{RTC_WaitForSynchro();								//等待同步RTC_WaitForLastTask();								//等待上一次操作完成}
}

void RCC_LSEConfig(uint8_t RCC_LSE) //配置外部低速振荡器(LSE)

参数说明
RCC_LSE指定LSE的新状态

 

 FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG)//检查是否设置了指定的RCC标志

参数说明
RCC_FLAG指定要检查的标志

返回值: RCC_FLAG的新状态(SET或RESET)

void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource)//配置RTC时钟(RTCCLK)就是配置数据选择器 

注意:一旦选择了RTC时钟,就不能更改它,除非重置备份域 。

参数说明
RCC_RTCCLKSourceRTC时钟源

void RCC_RTCCLKCmd(FunctionalState NewState)//开启或关闭RTC时钟 

注意:只有通过RCC_RTCCLKConfig函数选择了RTC时钟后,才能使用该功能。  

参数说明
NewStateRTC时钟的新状态取值为:ENABLE或DISABLE

 void RTC_WaitForSynchro(void)//等待RTC寄存器(RTC_CNT, RTC_ALR和RTC_PRL),设置RSF标志1

void RTC_WaitForLastTask(void)//等待RTC寄存器上的最后一次写操作完成,循环直到设置RTOFF标志

void RTC_SetPrescaler(uint32_t PrescalerValue)//设置RTC预分频器值 

参数说明
PrescalerValueRTC预caler的新值

 小细节:

对应上节RTC操作注意事项的第三点:

  • 必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器

 进入和退出RTC的配置模式的代码不用我们自己额外调用,相关函数已经帮我们写好,我们知道有这个东西就可以

如果RTC晶振不起振的解决方法:戳看解决方法

 接下来还要写两个函数,一个是设置时间,一个是读取时间。计划是,读取时间,我们就把读取到的秒数转换为年月日时分秒放在一个全局数组里,设置时间,我们再把年月日时分秒转换为秒数,再写入RTC的CNT。

 RTC设置时间

定义数组注意:小细节  

uint16_t MyRTC_Time[] = {2023, 1, 1, 23, 59, 55};	//定义全局的时间数组,数组内容分别为年、月、日、时、分、秒void MyRTC_SetTime(void);				//函数声明,调用函数不能在函数定义前,所以要声明/*** 函    数:RTC设置时间* 参    数:无* 返 回 值:无* 说    明:调用此函数后,全局数组里时间值将刷新到RTC硬件电路*/
void MyRTC_SetTime(void)
{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;	//调用mktime函数,将日期时间转换为秒计数器格式//- 8 * 60 * 60为东八区的时区调整RTC_SetCounter(time_cnt);						//将秒计数器写入到RTC的CNT中RTC_WaitForLastTask();							//等待上一次操作完成
}

void RTC_SetCounter(uint32_t CounterValue)//设置RTC计数器值 

参数说明
CounterValueRTC计数器新值

time_t mktime(struct tm*)函数文章开头unix时间戳篇有介绍

struct tm* localtime(const time_t*); 函数文章开头unix时间戳篇有介绍

time.h头文件在keil的小区别:time.h头文件

 RTC读取时间

/*** 函    数:RTC读取时间* 参    数:无* 返 回 值:无* 说    明:调用此函数后,RTC硬件电路里时间值将刷新到全局数组*/
void MyRTC_ReadTime(void)
{time_t time_cnt;		//定义秒计数器数据类型struct tm time_date;	//定义日期时间数据类型time_cnt = RTC_GetCounter() + 8 * 60 * 60;		//读取RTC的CNT,获取当前的秒计数器//+ 8 * 60 * 60为东八区的时区调整time_date = *localtime(&time_cnt);				//使用localtime函数,将秒计数器转换为日期时间格式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;
}

外部声明全局数组

main函数 

int main(void)
{OLED_Init();		//OLED初始化MyRTC_Init();		//RTC初始化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();							//RTC读取时间,最新的时间存储到MyRTC_Time数组中OLED_ShowNum(1, 6, MyRTC_Time[0], 4);		//显示MyRTC_Time数组中的时间值,年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);	//显示余数寄存器}
}

uint32_t RTC_GetDivider(void)//获取RTC余数寄存器值 

程序现象:


http://www.ppmy.cn/news/1583489.html

相关文章

三十而“砺”:百威“第一”工厂以创新驱动高质量增长

(2025年3月24日,中国武汉)在中国外商投资企业协会双碳与可持续发展工作委员会、武汉外商投资企业协会、上海市外商投资协会的指导下,“三十而砺,源启未来”百威武汉工厂三十周年庆典暨世界水日活动在百威武汉工厂——百…

Linux的进程信号 -- 信号产生,信号保存,信号捕捉,硬件中断,内核态和用户态,可重入函数,volatile,SIGCHLD

目录 1. 认识信号 1.1 信号的定义和基本结论 1.1.1 查看信号 1.2 技术应用角度的信号 1.2.1 一个样例 1.2.2 系统调用 signal 函数 1.3 信号的处理 2. 信号的产生 2.1 通过终端按键产生信号 2.1.1 基本操作 2.1.2 理解操作系统如何得知键盘信号 2.1.3 初步理解信号…

linux命令行工具进阶

文章目录 前言ssh免密登录,免密码登录,公私钥查看与修改IP地址临时修改永久修改 mount临时切换根文件系统永久切换根文件系统loop文件partedinitramfsuboot command line 前言 本文记录了一些不经常用到,但在某个时刻需要用到的一些指令。 免…

单表、多表查询练习

课堂练习代码: 一、单表查询 1、显示所有职工的基本信息。 mysql> select * from worker; --------------------------------------------------------------------------------- | 部门号 | 职工号 | 工作时间 | 工资 | 政治面貌 | 姓名 | …

Python+Pytorch掌纹训练识别

程序示例精选 PythonPytorch掌纹训练识别 如需安装运行环境或远程调试,见文章底部个人QQ名片,由专业技术人员远程协助! 前言 这篇博客针对《PythonPytorch掌纹训练识别》编写代码,代码整洁,规则,易读。 学…

数据结构——顺序栈seq_stack

前言:大家好😍,本文主要介绍了数据结构——顺序栈 目录 一、概念 1.1 顺序栈的基本概念 1.2 顺序栈的存储结构 二、基本操作 2.1 结构体定义 2.2 初始化 2.3 判空 2.4 判满 2.5 扩容 2.6 插入 入栈 2.7 删除 出栈 2.8 获取栈顶元…

算法 | 小龙虾优化算法原理,引言,公式,算法改进综述,应用场景及matlab完整代码

小龙虾优化算法(Crayfish Optimization Algorithm, COA)详解 一、引言 背景与意义 小* 龙虾优化算法(COA)是一种受小龙虾自然行为启发的元启发式算法,模拟其温度适应、洞穴选择、觅食竞争等机制,用于解决复杂优化问题。相比传统算法(如遗传算法、粒子群优化),COA通过…

Rust从入门到精通之进阶篇:14.并发编程

并发编程 并发编程允许程序同时执行多个独立的任务,充分利用现代多核处理器的性能。Rust 提供了强大的并发原语,同时通过类型系统和所有权规则在编译时防止数据竞争和其他常见的并发错误。在本章中,我们将探索 Rust 的并发编程模型。 线程基…