STM32单片机-BKP和RTC

STM32单片机-BKP和RTC

一、Unix时间戳

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

1.1 时间戳转换

  • C语言的time.h模块提供了时间获取时间戳转换的相关函数,可以方便地进行秒计数器、时器时间和字符串之间的转换

  下图为时间戳转换图

  time_t是int32 或者int64 数据类型,存储时间戳中自增的秒数

  struct tm 是一个封装的结构体 类型,结构体成员是秒,分,时等

  char*指向一个表示时间的字符串

二、BKP(备份寄存器)

  • BKP用于存储用户应用程序数据 ,当VDD主电源 被切断,它们仍然由VBRT备用电源 维持供电,当系统在待机模式下被唤醒,或系统复位或电源复位时,它们也不会被复位
  • TAMPER引脚 产生的侵入事件将所有备份寄存器内容清除
  • RTC引脚 输出RTC校准时钟RTC闹钟脉冲 或者秒脉冲
  • 存储RTC时钟校准寄存器
  • 用户数据存储容量20字节 /84字节

  下图为BKP基本结构

  BKP由数据寄存器-存储数据、控制寄存器、状态寄存器和RTC时钟校准寄存器组成,TAMPER有上升沿或者下降沿出现时,清除寄存器内容,保证安全

三、RTC(实时时钟)

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

3.1 RTC工作原理

  下图为RTC框图

  灰色填充部分属于后备区域,在主电源掉电后,可以使用备用电源维持工作。RTCCLK是时钟源 ,一般选择LSE振荡器时钟 ,经过分频器分频,重装载的值加1 就是分频系数。32位可编程计数器RTC_CNT 存放时间数据,RTC_ALR 是闹钟寄存器

  进入中断有3个中断源,RTC_Second秒中断 ,每秒进一次中断。RTC_Overflow溢出中断 ,当32位计数器溢出进入中断。RTC_Alarm闹钟中断

  下图为RTC基本结构

  选择时钟,RTCCKL先通过预分频器 对时钟进行预分频,重装寄存器 是计数目标,决定分频值余数寄存器 是一个自减计数器,存储当前的计数值 ,最终得到1Hz的秒计数信号通向32位计数器,1秒自增一次,同时可以设定闹钟,最终也可以进入中断

四、代码部分

  注意事项

  • RTC的时钟开启没有单独选项,需要开启PWR和BKP时钟 ,设置PWR_CR 的DBP,使能对BKP和RTC的访问
  • 若在读取 RTC寄存器时,RTC的APB1接口曾经处在禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位 (寄存器同步标志)被硬件置1
  • 必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL(重装值)、RTC_CNT(计数器)、RTC_ALR(闹钟值)寄存器
  • 对RTC任何寄存器的写操作 ,都必须在前一次写操作结束后进行 。可以通过查询RTC_CR寄存器中的RTOFF状态位 ,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器

4.1 BKP备份寄存器

  STM32的VBRT引脚接在ST-Link的3.3V,PB1接一个按键

  步骤:开启RCC (PWR和BKP) --- 使能BKP -PWR_BackupAccessCmd(ENABLE) --- 写入和读取

  下面为main.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#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();
	//开启RCC-PWR和BKP
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
	//使能BKP
	
	OLED_ShowString(1,1,"W:");
	OLED_ShowString(2,1,"R:");
	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);
	}
}

4.2 RTC实时时钟

  下面是RTC相关的库函数

c 复制代码
void RCC_LSEConfig(uint8_t RCC_LSE);//启动LSE时钟
void RCC_LSICmd(FunctionalState NewState);//配置LSI时钟
void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource);//选择RTCCLK的时钟源
void RCC_RTCCLKCmd(FunctionalState NewState);//RTCCLK使能
FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);//等待RCC标志位,时钟启动完成

void RTC_EnterConfigMode(void);//设置CNF位,使RTC进入配置模式
void RTC_ExitConfigMode(void);//退出配置模式
uint32_t  RTC_GetCounter(void);//获取CNT计数器的值
void RTC_SetCounter(uint32_t CounterValue);//写入计数器的值
void RTC_SetPrescaler(uint32_t PrescalerValue);//写入预分频器
void RTC_SetAlarm(uint32_t AlarmValue);//写入闹钟值
uint32_t  RTC_GetDivider(void);//读取余数寄存器值
void RTC_WaitForLastTask(void);//等待上一次操作完成
void RTC_WaitForSynchro(void);//等待同步
FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);//等待标志位

  步骤:开启PWRBKP 时钟,使能BKP和RTC的访问 --- 开启LSE时钟 并等待LSE时钟启动完成 --- 选择RCCCLK时钟源 -LSE --- 等待同步 和等待上一次写入操作 完成 --- 设置预分频器初始时间 --- 写入数据 --- 读取数据

  其中想要实现复位不重置时间数据和主电源断开时不切断时间,需要使用BKP判断

  利用c语言库time.h中的函数,实现写入和读取时间,即计数器值和时间数据的相互转换

  下面为MyRTC.c和MyRTC.h

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "MyRTC.h"                  // Device header
#include <time.h>

uint16_t MyRTC_Time[] = {2024,1,1,1,1,1};//时间数据

void MyRTC_Init()
{
	//开启RCC-BKP、PWR和使能访问
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
	PWR_BackupAccessCmd(ENABLE);
	//第一次上电或者完全断电,BKPDR1默认为0时,才会初始化时间
	if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
	{
		//开启LSE时钟,等待LSE时钟启动完成(标志位)
		RCC_LSEConfig(RCC_LSE_ON);
		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
		
		//选择RCCCLK时钟源-LSE
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
		RCC_RTCCLKCmd(ENABLE);
		
		//等待同步和等待上一次写入操作完成
		RTC_WaitForSynchro();
		RTC_WaitForLastTask();
		
		//配置预分频器 =  32768MHz /32768 = 1Hz
		RTC_SetPrescaler(32768-1);
		RTC_WaitForLastTask();//等待写入操作
		
		//设置初始时间
		MyRTC_SetTime();
		
		BKP_WriteBackupRegister(BKP_DR1,0xA5A5);//DR1写入A5A5
	}
	else
	{
		//等待同步和等待上一次写入操作完成
		RTC_WaitForSynchro();
		RTC_WaitForLastTask();
	}
}
/*
@brief:写入时间数据
*/
void MyRTC_SetTime()
{
	time_t time_cnt;//计数器值
	struct tm time_data;//时间数据
	
	time_data.tm_year = MyRTC_Time[0] - 1900;
	time_data.tm_mon = MyRTC_Time[1] - 1;
	time_data.tm_mday = MyRTC_Time[2];
	time_data.tm_hour = MyRTC_Time[3];
	time_data.tm_min = MyRTC_Time[4];
	time_data.tm_sec = MyRTC_Time[5];//写入数据
	
	time_cnt = mktime(&time_data)- 8*60*60;//时间数据--计数器(北京时间)
	RTC_SetCounter(time_cnt);
	RTC_WaitForLastTask();//等待写入操作
}
/*
@brief:读取时间数据
*/
void MyRTC_ReadTime()
{
	time_t time_cnt;//计数器值
	struct tm time_data;//时间数据
	
	time_cnt = RTC_GetCounter()+ 8*60*60;//读取时间存到计数器(北京时间)
	time_data = *localtime(&time_cnt);//将时间数据转到结构体
	
	MyRTC_Time[0] = time_data.tm_year + 1900;
	MyRTC_Time[1] = time_data.tm_mon + 1;
	MyRTC_Time[2] = time_data.tm_mday;
	MyRTC_Time[3] = time_data.tm_hour;
	MyRTC_Time[4] = time_data.tm_min;
	MyRTC_Time[5] = time_data.tm_sec;//读取数据
}
c 复制代码
#ifndef __MYRTC_H
#define __MYRTC_H

extern uint16_t MyRTC_Time[];


void MyRTC_Init();
void MyRTC_SetTime();
void MyRTC_ReadTime();


#endif

  下面为main.c

c 复制代码
#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "MyRTC.h"

int main(void)
{
	MyRTC_Init();
	OLED_Init();
	
	OLED_ShowString(1,1,"Data: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);
	}
}
相关推荐
十月的皮皮4 小时前
stm20260720-从新手 C 到量产 STM32 工程:程序设计推导指南
c语言·开发语言·stm32·stm32cubemx·hal库
开发笔记-阿牛5 小时前
解析一款有意思的产品||CK6159A语音芯片设计的儿童专注力训练机-舒尔特玩具
stm32·单片机·嵌入式硬件·音频
搞全栈小苏7 小时前
SIMetrix_simplis8.4安装教程——附安装包
stm32·单片机·嵌入式硬件·电力电子
qq_573967828 小时前
STM32控制器
stm32·单片机·嵌入式硬件
某林2129 小时前
履带小车底盘stm32F103RCT6开发
人工智能·stm32·单片机·嵌入式硬件·架构·人机交互·ros2
做萤石二次开发的哈哈10 小时前
萤石 ERTC Windows SDK 包含哪些类型定义?
stm32·单片机·嵌入式硬件
亿道电子Emdoor11 小时前
【Aitium】PCB导出DXF文件出现eDuplicateRecordName报错
stm32·单片机·嵌入式硬件·altium
巴巴媛6661 天前
STM32学习笔记【38.DHT11实验】
笔记·stm32·学习
深圳市泰凌微电子1 天前
HK航顺 HK32F407RGT7替STM32F405RGT6 微控制器32位MCU
stm32·单片机·嵌入式硬件
小心亦新1 天前
STM32学习16--定时器输出比较2直流电机
stm32·嵌入式硬件·学习