STM32-BKP和RTC

Unix时间戳

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

GMT

格林尼治标准时间是一种以地球自转为基础的时间计量系统。它将地球自转一周的时间间隔等分为24小时,以此确定计时标准。

UTC

协调世界时是一种以原子钟为基础的时间计量系统。当原子钟计时一天的时间与地球自转一周的时间相差超过0.9秒时,UTC会执行闰秒来保证其计时时间与地球自转的协调一致。

时间戳转换

gmtime()是将秒计时器时间转换为伦敦日期时间,在操作系统上使用localtime()时,系统会直接匹配当地时区,将秒计数器时间转换为当地日期时间。(但是在STM32上,只会转换为伦敦时间)

BKP的简介

  • BKP(Backup Registers)备份寄存器
  • BKP可存储用户应用程序数据。当VDD(2~3.6V)被切断时,它仍然由VBAT(1.8~3.6V)维持供电。当系统在待机模式下被唤醒,或者系统复位、电源复位时,他们都不会倍复位;
  • TAMPER引脚产生的侵入事件将所有备份内容清除;
  • RTC引脚输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲;
  • 存储RTC时钟校准寄存器;
  • 用户数据存储容量:20字节(中、小容量)、84字节(大容量和互联型)

BKP的基本结构

RTC简介

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

RTC基本结构

RTC操作注意事项

  1. 执行以下操作将使能对BKP和RTC的访问:设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟;设置PWR_CR的DBP,使能对BKP和RTC的访问;
  2. 若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1;
  3. 必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC _CNT、RTC_ALR寄存器;
  4. 对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中,仅当RTOFF状态位为1时,才可以写入RTC寄存器。

一、BKP的读写

本实验主要完成对BKP的读写操作。

程序

由于本实验比较简单,直接在主函数中写相关功能,main.c文件:

复制代码
#include "stm32f10x.h"    // Device header
#include "Delay.h"
#include "OLED.h"
#include "key.h"

uint8_t num;//创建变量用于存储按键状态
uint16_t WriteArray[]={0x1234,0x2345};//创建数组用于存储要写入到BKP中的数据
uint16_t ReadArray[2];//创建数组用于存储要从BKP中读取到的数据

int main(void)
{	
	OLED_Init();
	key_init ();//各模块初始化
	
	OLED_ShowString (1,1,"W:");
	OLED_ShowString (2,1,"R:");//显示提示字符串
	
	//打开APB1总线上BKP的时钟
	RCC_APB1PeriphClockCmd (RCC_APB1Periph_BKP,ENABLE );
	//打开APB1总线上PWR的时钟
	RCC_APB1PeriphClockCmd (RCC_APB1Periph_PWR,ENABLE );
	
	//将备份寄存器的电源打开(允许BKP运作)
	PWR_BackupAccessCmd(ENABLE);
	
	while(1)
	{
		//接收按键状态
		num = Getnum ();
		//如果按键按下
		if(num == 1)
		{
			//写入BKP的值加一
			WriteArray[0]++;
			WriteArray[1]++;
			
			//将数值分别写入BKP中
			BKP_WriteBackupRegister(BKP_DR1,WriteArray[0]);
			BKP_WriteBackupRegister(BKP_DR2,WriteArray[1]);
			
			//分别显示写入BKP的数据
			OLED_ShowHexNum (1,3,WriteArray[0],4);
			OLED_ShowHexNum (1,8,WriteArray[1],4);
		}
		//读取BKP中的数据
		ReadArray[0] = BKP_ReadBackupRegister(BKP_DR1);
		ReadArray[1] = BKP_ReadBackupRegister(BKP_DR2);
		
		//显示从BKP中读取到的数据
		OLED_ShowHexNum (2,3,ReadArray[0],4);
		OLED_ShowHexNum (2,8,ReadArray[1],4);
	}
}

最终效果:按一下按键,写入BKP中的数据值加一,并在OLED上显示写入BKP的数据和从BKP中读取出来的数据。

注意事项:

  • STM32上的复位按键对写入BKP的数据无复位作用,想要清除BKP中的数据需要同时断掉VDD和VBAT;

二、实时时钟

本实验用STM32单片机实现实时时钟。

程序

先对RTC部分代码进行编写,MyRTC.h文件:

复制代码
#include "stm32f10x.h"                  // Device header
#include <Time.h>

uint16_t TimeArray[]={2026,9,23,20,50,30};//创建数据用于设置初始时间和存储后续读取的时间

//对函数提前进行声明
void RTC_SetTime(void);
void MyRTC_Init(void)
{
	//打开APB1总线上BKP的时钟
	RCC_APB1PeriphClockCmd (RCC_APB1Periph_BKP ,ENABLE );
	//打开APB1总线上PWR的时钟
	RCC_APB1PeriphClockCmd (RCC_APB1Periph_PWR ,ENABLE );
	//打开PWR对BKP的使能
	PWR_BackupAccessCmd(ENABLE);
	
	//用备份寄存器中的数据进行判断,使计时能够达到掉电不丢失,不重复初始化
	if(BKP_ReadBackupRegister(BKP_DR1) != 0x77)
	{
		//打开外部低速时钟
		RCC_LSEConfig(RCC_LSE_ON);
		//等待外部低速时钟起振
		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
		//配置RTC的时钟为外部低速时钟
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
		//使能RTC时钟
		RCC_RTCCLKCmd(ENABLE);
		
		//等待时钟对齐
		RTC_WaitForSynchro();
		//等待上一个任务完成
		RTC_WaitForLastTask();
		//对RTC频率进行分频,设置为秒计时器
		RTC_SetPrescaler(32768 - 1);
		//等待上一个任务完成
		RTC_WaitForLastTask();
		//设置初始时间
		RTC_SetTime();
		//向BKP中写入特定数据,用于判断,保证函数值初始化一次
		BKP_WriteBackupRegister(BKP_DR1,0x77);
	}
	//如果系统掉电,但是备用电源VBAT通电
	else
	{
		//等待时钟对齐
		RTC_WaitForSynchro();
		//等待上一个任务完成
		RTC_WaitForLastTask();
	}
}

//函数:对当前时间进行设置
void RTC_SetTime(void)
{
	//创建time_t类型变量,用于存储计时秒数
	time_t Cnt;
	//创建时间结构体,用于存储关于时间的结构化数据
	struct tm Time_Date;
	
	//将数组中的值对应传输给时间结构体的对应成员
	Time_Date.tm_year = TimeArray[0] - 1900;//C语言标准规定年份偏差为1900,需要手动校正
	Time_Date.tm_mon =TimeArray[1] - 1;//月份相差1个月,需要手动校正
	Time_Date.tm_mday =TimeArray[2];
	Time_Date.tm_hour =TimeArray[3];
	Time_Date.tm_min =TimeArray[4];
	Time_Date.tm_sec =TimeArray[5];
	
	//将结构化时间数据转化为时间戳的秒计时,这里需要进行时区的校正
	Cnt = mktime (&Time_Date) - 8 * 60 * 60;
	//对时间进行设置
	RTC_SetCounter(Cnt);
	//等待上一个任务完成
	RTC_WaitForLastTask();
}

//函数:读取时间
void RTC_ReadTime(void)
{
	//创建time_t类型变量,用于存储计时秒数
	time_t Cnt;
	//创建时间结构体,用于存储关于时间的结构化数据
	struct tm Time_Date;
	//将时间戳时间读取出来赋值给Cnt,这里需要进行时区的校正
	Cnt = RTC_GetCounter() + 8 * 60 * 60;
	//使用localtime()函数将秒计数转换为格式化时间数据
	Time_Date = *localtime(&Cnt);
	//用数组按顺序分别将年、月、日等时间数据存储起来
	TimeArray[0] = Time_Date.tm_year + 1900;
	TimeArray[1] = Time_Date.tm_mon + 1;
	TimeArray[2] = Time_Date.tm_mday;
	TimeArray[3] = Time_Date.tm_hour;
	TimeArray[4] = Time_Date.tm_min;
	TimeArray[5] = Time_Date.tm_sec;
}

MyRTC.h文件:

复制代码
#ifndef _MYRTC_H
#define _MYRTC_H
extern uint16_t TimeArray[6];

void MyRTC_Init(void);
void RTC_SetTime(void);
void RTC_ReadTime(void);

#endif

最后在主函数中对RTC部分函数进行调用,miain.c文件:

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

int main(void)
{	
	OLED_Init();
	MyRTC_Init();//各模块初始化
	
	OLED_ShowString (1,1,"Date:xxxx xx xx");
	OLED_ShowString (2,1,"Hour:xx xx xx");//显示提示字符串
	
	while(1)
	{
		//不断读取时间
		RTC_ReadTime ();
		//显示读取到的时间数据
		OLED_ShowNum (1,6,TimeArray[0],4);
		OLED_ShowNum (1,11,TimeArray[1],2);
		OLED_ShowNum (1,14,TimeArray[2],2);
		OLED_ShowNum (2,6,TimeArray[3],2);
		OLED_ShowNum (2,9,TimeArray[4],2);
		OLED_ShowNum (2,12,TimeArray[5],2);
	
	}
}

最终效果:在OLED上显示出实时时间,并且在不断更新,当VDD断开时,实时时钟还是会继续计时。

注意事项

  • 对时间的转换需要使用相关函数,必须声明头文件<Time.h>;
  • 对时间的校正,如果是单纯的Unix秒计数器时间校正,需要使用1970进行年份校正,但是C语言标准规定使用1900进行年份校正;
  • STM32标准库中 localtime() 函数转换出来的时间是伦敦时间,不是当地时间,需要自己计算时区来手动校正,最好在设置时间函数和读取时间函数中的秒计数器处进行校正,这样不用考虑进位的问题。
相关推荐
Yiran_G1 小时前
LoRa模块休眠后如何快速恢复通信?低功耗终端唤醒与数据交互实践
嵌入式硬件·物联网
意法半导体STM322 小时前
【官方原创】LAT1724 STM32CubeIDE for VSCode 修改链接文件中的起始地址无效?
vscode·stm32·单片机·嵌入式硬件·mcu·stm32cube
BSD_HY3 小时前
嵌入式薄膜开关中断去抖:中断优先级分层实战策略
单片机·嵌入式硬件·薄膜开关·源头工厂·薄膜按键
Zw-awa14 小时前
八路循迹模块的 DO:先让小车沿着线跑起来
stm32
黑妹天下第一乖15 小时前
第09讲 · 多媒体与音频 SDK:硬件编解码与端侧语音
人工智能·嵌入式硬件·深度学习·机器人·音视频·iot
comyccccc15 小时前
LM2596电源PCB布局布线全解析学习笔记
单片机·嵌入式硬件
恒锐丰科技林技术员17 小时前
EG2122 半桥栅极驱动芯片原理与工程应用解析
经验分享·嵌入式硬件·硬件工程
新晨单片机设计18 小时前
S017C-基于STM32汽车防酒驾系统(心率、血氧、短信)【Proteus仿真+Keil程序+原理图】
stm32·单片机·proteus·防酒驾报警系统
fly_sunnn19 小时前
STM32-GPIO输出以及按键控制LED&光敏传感器控制蜂鸣器
stm32·单片机·嵌入式硬件