STM32(二十三)——读写备份寄存器&实时时钟

一、读写备份寄存器

接线图
初始化

使能 PWR 和 BKP 时钟

RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);

RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);

使能对备份寄存器的写访问

PWR_BackupAccessCmd(ENABLE);

写入备份寄存器

BKP_WriteBackupRegister(BKP_DR1, ArrayWrite[0]);

读取备份寄存器

BKP_ReadBackupRegister(BKP_DR1);

完整代码

程序最开始,是初始化和两个用于指示的字符串显示。之后三句是BKP的初始化,然后主循环里循环执行的是,先获取键码,如果按键按下,就变换一下测试数据,把数据分别写入到BKP的DR1和DR2中,再在OLED显示写入的数据。在按键没有按下时,也不断读取BKP的DR1和DR2,刷新显示到OLED上。

复制代码
#include "stm32f10x.h"                  
#include "OLED.h" 
#include "Key.h"

uint8_t KeyNum;

uint16_t ArrayWrite[] = {0x1234, 0x5678};
uint16_t ArrayRead[2];

int main(void)
{
	OLED_Init();
	Key_Init();
	
	OLED_ShowString(1,1,"W:");
	OLED_ShowString(2,1,"R:");
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
	
	PWR_BackupAccessCmd(ENABLE);
	
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			ArrayWrite[0] ++;
			ArrayWrite[1] ++;
			
			BKP_WriteBackupRegister(BKP_DR1, ArrayWrite[0]);
			BKP_WriteBackupRegister(BKP_DR2, 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);
	}
	
}

二、RTC实时时钟

复制代码
uint16_t MyRTC_Time[] = {2026,3,15,13,46,00};
写入RTC

把自定义的时间数组转换成标准时间戳,写入 RTC 硬件计数器,完成 RTC 时间配置。

struct tm 标准时间结构体
time_t / mktime()
  • time_t:存储Unix 时间戳(从 1970 年 1 月 1 日 0 点开始的总秒数)

  • mktime(&time_date):把tm结构体转换成时间戳,是标准库函数

硬件操作逻辑
  1. RTC_SetCounter(time_cnt):STM32 的 RTC 本质是一个秒计数器,写入时间戳后,RTC 会每秒自动加 1,实现计时

  2. RTC_WaitForLastTask():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);
     
     RTC_SetCounter(time_cnt);
     RTC_WaitForLastTask();

    }

初始化

上电后只初始化一次 RTC,断电后时间不丢失,下次上电直接读取已保存的时间

1. 为什么要这么初始化

STM32 的RTC + BKP 备份区域 有电池供电(VBAT),主电源断电后,RTC 时间和备份寄存器数据不会丢失

所以代码做了关键判断

  • 第一次上电:初始化 RTC、设置时间、写入标志0xA5A5
  • 后续重启:检测到0xA5A5,直接用之前的时间,不重新初始化
2. 关键硬件知识点
  1. LSE 32.768kHz 晶振RTC 专用低速晶振,功耗极低,电池供电也能运行
  2. 分频值 32768-1 32768Hz ÷ 32768 = 1Hz → 每秒 RTC 计数器 + 1,精准计时
  3. BKP 备份寄存器 10 个独立寄存器,断电不丢失,用来存0xA5A5标志位
  4. PWR 电源控制负责开启备份区域写权限,不开启无法配置 RTC
3. 必须等待的函数(防止配置失败)
  • RTC_WaitForSynchro():等待 RTC 时钟同步
  • RTC_WaitForLastTask():等待寄存器写入完成RTC 是低速外设,操作后必须等待,否则会配置失效

4.完整运行流程
  1. 开启 PWR、BKP 时钟,解除备份区写保护
  2. 读取 BKP_DR1 判断是否第一次初始化
  3. 第一次初始化
    • 开启 32.768kHz 晶振
    • 选择 RTC 时钟源
    • 设置分频实现 1 秒计时
    • 调用MyRTC_SetTime()设置初始时间
    • 写入0xA5A5标志
  4. 非第一次初始化

只做等待同步,直接使用之前保存的时间

复制代码
void MyRTC_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
	
	PWR_BackupAccessCmd(ENABLE);
	
	if(BKP_ReadBackupRegister(BKP_DR1)!= 0xA5A5)
	{
		RCC_LSEConfig(RCC_LSE_ON);
		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
		
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
		RCC_RTCCLKCmd(ENABLE);
		
		RTC_WaitForSynchro();
		RTC_WaitForLastTask();
		
		RTC_SetPrescaler(32768-1);
		RTC_WaitForLastTask();
		MyRTC_SetTime();
		
		BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
	}
	else													//RTC不是第一次配置
	{
		RTC_WaitForSynchro();								//等待同步
		RTC_WaitForLastTask();								//等待上一次操作完成
	}
}
读取RTC

从硬件 RTC 中读取时间戳,转换成我们能看懂的年月日时分秒,存到数组里

1. 执行流程
  1. 读取硬件RTC_GetCounter() → 获取 RTC 里的时间戳秒数
  2. 时间转换localtime() → 把秒数转成 tm 结构体时间
  3. 格式还原 :把 tm 结构体 → 还原成你的 MyRTC_Time 数组
2. 关键函数:localtime()
  • 作用:Unix 时间戳 → 年月日时分秒
  • 输入:time_t 类型的秒数指针
  • 输出:struct tm 结构体指针
  • 必须包含头文件:<time.h>
3. 为什么要 +1900、+1?

和写时间时刚好相反

  • 设置时:实际年 - 1900 → 存入 tm

  • 读取时:tm年 + 1900 → 变回实际年

  • 设置时:实际月 - 1 → 存入 tm

  • 读取时:tm月 + 1 → 变回实际月

    void MyRTC_ReadTime(void)
    {
    time_t time_cnt;
    struct tm time_date;

    复制代码
      time_cnt = RTC_GetCounter();
      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;

    }

注:

这里如果使用gmtime()会导致数据出错,可能是库函数或者这个编译器有问题。使用localtime()时偶尔出现不加8的现象,解决方法是固定东八区时区.

复制代码
// 固定东八区时区,让 localtime 永远稳定 +8小时
extern char **environ;
char *__env[] = {"TZ=UTC-8", NULL};
char **environ = __env;
调用
复制代码
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();
		
		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);
		
	}
	
}
相关推荐
国科安芯2 小时前
抗辐照ASP4644四通道降压稳压器在商业卫星通信处理模块的应用研究
单片机·嵌入式硬件·安全·fpga开发·架构·安全性测试
爱倒腾的老唐2 小时前
06、STM32 的 I/O 应用
stm32·单片机·嵌入式硬件
承前智2 小时前
基于任意单片机的PWM调速电机
单片机·嵌入式硬件
干啥都是小小白2 小时前
如何移植FreerRTOS到STM32F103(2026.3.13)
stm32·单片机·嵌入式硬件
爱上珍珠的贝壳2 小时前
ESP32-S3-CAM:认识引脚
单片机·嵌入式硬件·智能硬件·esp32-s3
wregjru3 小时前
【网络】6.UDP和TCP原理
单片机·嵌入式硬件
干啥都是小小白3 小时前
2.创建你的第一个FreeRTOS任务(动态与静态)
stm32·单片机
芯联智造3 小时前
【stm32简单外设篇】- 震动传感器
c语言·stm32·单片机·嵌入式硬件
Hello_Embed3 小时前
LVGL 入门(八):标签控件 lv_label
前端·笔记·stm32·单片机·嵌入式