STM32-BKP和RTC实时时钟

在学习实时时钟之前先要学习一下时间戳(Unix Timestamp)。

一、时间戳

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

格林尼治标准时间(Greenwich Mean Time)是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间。伦敦采取格林尼治国际标准时间作为伦敦的区时,但是地方时间有不同。

从下图可以看出,伦敦与北京时间上相差8个小时。

具体了解下原理:

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

UTC(Universal Time Coordinated)协调世界时是一种以原子钟为基础的时间计量系统。它规定铯133原子基态的两个超精细能级间在零磁场下跃迁辐射9,192,631,770周所持续的时间为1秒。当原子钟计时一天的时间与地球自转一周的时间相差超过0.9秒时,UTC会执行闰秒来保证其计时与地球自转的协调一致。

1、时间戳的转换

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

 下面用DevC++软件编写程序来实践以下。

#include <stdio.h>
#include <time.h>

time_t time_cnt; //64位

struct tm  *time_date;

char *time_str;


int main(void)
{
	//time_cnt = time(NULL);  //返回的是秒的总数
	time(&time_cnt);
	time_date=localtime(&time_cnt);
	printf("%d\n",time_cnt);
	
	printf("%d %d %d\n", time_date->tm_year+1900, time_date->tm_mon+1, time_date->tm_mday);
	printf("%d %d %d %d\n", time_date->tm_hour, time_date->tm_min,time_date->tm_sec,time_date->tm_wday);
	
	time_cnt=mktime(time_date);
	printf("%d\n",time_cnt);
	
	time_str=ctime(&time_cnt); //字符串格式
	printf(time_str);
	
	time_str=asctime(time_date);
	printf(time_str);
	
	char t[50];
	strftime(t,50,"%H-%M-%S",time_date);
	printf(t);
	return 0;

}

二、BKP(Backup Registers,备份寄存器)

1、概述

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

2、基本结构

每个DR寄存器存储数据2个字节,所以DR1~DR10是20个字节即小容量存储。

3、代码

下面简单编程实现哈:

main.c

#include  "stm32f10x.h"                  // Device header
#include  "OLED.h"
#include  "delay.h"

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


int main(void)
{
	
   OLED_Init();
	
	//设置RCC_APB1的PWREN和BKPEN,使能PWR和BKP时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
	
	//设置PWR_CR的DBP,使能对BKP和RTC的访问
	PWR_BackupAccessCmd(ENABLE);
	

	OLED_ShowString(1,1,"W:");
	OLED_ShowString(2,1,"R:");
	while(1) 
	{
	  
	  Delay_ms(1000);
      //往DR1寄存器写数据
	  BKP_WriteBackupRegister(BKP_DR1,ArrayWrite[0]++);
	  OLED_ShowHexNum(1,3,ArrayWrite[0],4);
	  Delay_ms(1000);
      //从DR1寄存器读取数据,并显示
	  OLED_ShowHexNum(2,3,BKP_ReadBackupRegister(BKP_DR1),4);
	} 
	
}

三、RTC(Real Time Clock,实时时钟)

1、概述

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

2、RTC框图

32位可编程计数器:用来计数并存储秒的总数,每过1s进1位。

RTC_DIV手册里叫余数寄存器,其实也是个20位计数器,和定时器里时基单元中计数器CNT一样,RTC_PRL重装载与时基单元中ARR一样。由RTCCLK输入时钟,经过分频器,进行分频得到1Hz,也就是1s,输出TRCLK时钟使RTC_CNT计数。

RTC_DIV初始值为0,在第一上升沿到来时,DIV立即溢出,产生溢出信号给后续电路,同时,DIV变为重装值32767,然后来一个时钟减1次,一直减为0,正好计数32768次,再来一个输入时钟就会产生一个溢出信号,同时RTC_DIV回到32767。RTCCLK输入时钟为LSE 32.768KHz。

RTC_ALR:用来设置闹钟,我们可以在ALR写一个秒数,设定闹钟,当CNT的值和ALR的值一样时,这里画着一个等号,说明闹钟响了,这时会产生一个中断RTC_Alarm。下面,闹钟还兼具一个功能,就是可以退出待机模式(唤醒芯片)。待机模式就是芯片不工作的状态。这里闹钟只能响一次,如果想要再响,需手动再设置

RTC_Second:秒钟中断,TR_CLK每来一次发生一次中断。

RTC_Overflow:CNT计数器溢出会发生中断,这个需要到2106年才能溢出哈。

3、RTC基本结构

4、备用电池供电

下面的红色圆圈为外部低速晶振32.768KHz

 5、RTC操作注意事项

  • 执行以下操作将使能对BKP和RTC的访问:设置RCC_APB1的PWREN和BKPEN,使能PWR和BKP时钟。设置PWR_CR的DBP,使能对BKP和RTC的访问。
  • 若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1。PCLK1是36kHZ,而RTCCLK是32.768KHz,两个时钟不同步,所以先同步,在RTCCLK上升沿写入和读取,RTCCLK上升沿作为同步的标志。两个不同频率的同步,肯定是以慢的为标准。
  • 必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器
  • RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器。

6、代码

利用BKP进行存储数据,通过读取BKP的数据,来进行判断时钟是否复位。

MyRTC.c:

#include "stm32f10x.h" // Device header
#include "time.h"
#include "MyRTC.h"

void MyRTC_SetTime(Time_Value *Time_Data);

void MyRTC_Init(Time_Value *Time_Data)
{
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
	
	
   // 对BKP和RTC使能
	PWR_BackupAccessCmd(ENABLE);
	
	if(BKP_ReadBackupRegister(BKP_DR1)!=0xA5A5)
	{
	//LSE配置,打开
	RCC_LSEConfig(RCC_LSE_ON);
	
	//判断外部时钟是否打开
	while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)!=SET);
	
	//RTC时钟配置
	RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
	
	//RTC时钟使能
	RCC_RTCCLKCmd(ENABLE);
	
	//Synchro  同步
	RTC_WaitForSynchro();
	//等待上一次的写入操作完成
	RTC_WaitForLastTask();
	
	//设置分频器
	RTC_SetPrescaler(32768-1);
	RTC_WaitForLastTask();
	
	MyRTC_SetTime(Time_Data);
   BKP_WriteBackupRegister(BKP_DR1,0xA5A5);
	}
	else{
		//Synchro  同步
	RTC_WaitForSynchro();
	//等待上一次的写入操作完成
	RTC_WaitForLastTask();
	}
}

void MyRTC_SetTime(Time_Value *Time_Data)
{
	time_t time_cnt;
	struct tm tm_date;
	
   tm_date.tm_year=Time_Data->year-1900;
	tm_date.tm_mon=Time_Data->month-1;
	tm_date.tm_mday=Time_Data->day;
	tm_date.tm_hour=Time_Data->hour;
	tm_date.tm_min=Time_Data->minute;
	tm_date.tm_sec=Time_Data->second;
	
	time_cnt=mktime(&tm_date)-8*60*60;
	RTC_SetCounter(time_cnt);
   RTC_WaitForLastTask();
}


void MyRTC_ReadTime(Time_Value *Time_Data)
{
	
	time_t time_cnt;
	struct tm *tm_date;
	
	time_cnt=RTC_GetCounter()+8*60*60;
	
	tm_date=localtime(&time_cnt);
	
	Time_Data->year=tm_date->tm_year+1900;
	Time_Data->month=tm_date->tm_mon+1;
	Time_Data->day=tm_date->tm_mday;
	Time_Data->hour=tm_date->tm_hour;
	Time_Data->minute=tm_date->tm_min;
   Time_Data->second=tm_date->tm_sec;
	
}

MyRTC.h:

#ifndef _MYRTC_H
#define _MYRTC_H

typedef struct
{
	int year;
	int month;
	int day;
	//int week;
	
	int hour;
	int minute;
	int second;
	
}Time_Value;

void MyRTC_Init(Time_Value *Time_Data);

void MyRTC_SetTime(Time_Value *Time_Data);

void MyRTC_ReadTime(Time_Value *Time_Data);

#endif

main.c:

#include  "stm32f10x.h"                  // Device header
#include  "OLED.h"
#include  "delay.h"
#include  "MyRTC.h"


Time_Value Time_Data={2024,11,1,19,30,0};

//在编程中数字以0开头往往表示八进制

int main(void)
{
	
   OLED_Init();
	MyRTC_Init(&Time_Data);
	
	OLED_ShowString(1,1,"Date:");
	OLED_ShowString(2,1,"Time:");
	OLED_ShowString(3,1,"Cnt:");
	
	while(1) 
	{
	  MyRTC_ReadTime(&Time_Data);
	  OLED_ShowNum(1,6,Time_Data.year,4);
	  OLED_ShowChar(1,10,'-');
	  OLED_ShowNum(1,11,Time_Data.month,2);
	  OLED_ShowChar(1,13,'-');
	  OLED_ShowNum(1,14,Time_Data.day,2);
		
	  OLED_ShowNum(2,6,Time_Data.hour,2);
	  OLED_ShowChar(2,8,'-');
	  OLED_ShowNum(2,9,Time_Data.minute,2);
	  OLED_ShowChar(2,11,'-');
	  OLED_ShowNum(2,12,Time_Data.second,2);
	  OLED_ShowNum(3,5,RTC_GetCounter(),10);
	} 
	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值