嵌入式小白跟随江科大学习笔记 -- 2024.4.7

一、Unix时间戳

简介

Unix 时间戳( Unix Timestamp )定义为从 UTC/GMT 1970 1 1 0 0 0 秒开始所经过的秒数,不考虑闰秒
时间戳存储在一个秒计数器中,秒计数器为 32 /64 位的整型变量
世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间
我们使用时间戳就是调用c语言官方的计算函数 进行使用即可使用

UCT/GMT 

GMT Greenwich Mean Time )格林尼治(伦敦)标准时间是一种以地球自转为基础的时间计量系统。它将地球自转一周的时间间隔等分为 24 小时,以此确定计时标准,过去都是使用GMT,但是因为地球自转一周的时间不是固定的,因此是有缺陷的所以被舍弃了
UTC Universal Time Coordinated )协调世界时是一种以原子钟为基础的时间计量系统。它规定铯 133 原子基态的两个超精细能级间在零磁场下跃迁辐射 9,192,631,770 周所持续的时间为 1 秒。当原子钟计时一天的时间与地球自转一周的时间相差超过 0.9 秒时, UTC 会执行闰秒来保证其计时与地球自转的协调一致

时间戳转换 

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

函数

作用

time_t time(time_t*);

获取系统时钟

struct tm* gmtime(const time_t*);

秒计数器转换为日期时间(格林尼治时间

struct tm* localtime(const time_t*);

秒计数器转换为日期时间(当地时间)

time_t mktime(struct tm*);

日期时间转换为秒计数器(当地时间

char* ctime(const time_t*);

秒计数器转换为字符串(默认格式)

char* asctime(const struct tm*);

日期时间转换为字符串(默认格式)

size_t strftime(char*, size_t, const char*, const struct tm*);

日期时间转换为字符串(自定义格式)

二、BKP/RTC

BKP简介

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

  20字节(中容量和小容量)/ 84字节(大容量和互联型)

BKP基本结构

STM32为中容量设备,因此数据寄存器有20个字节,也就是图中数据寄存器部分只有DR1~DR10,

RTC简介

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

  HSE时钟除以128(通常为8MHz/128

  LSE振荡器时钟(通常为32.768KHz

  LSI振荡器时钟(40KHz

RTC结构

RTC_ALR闹钟计数器,用于设置闹钟,在寄存器中设置一个数字,当CNT的值跟ALR中的值一致时,就发送一个闹钟信号,这个闹钟信号可以用于触发中断或者使STM32退出待机模式

中断部分可以由三种方式触发:RTC_Second在CNT左侧每秒触发一次中断,RTC_Overflow是在CNT溢出后产生中断,RTC_Alarm是由闹钟信号触发的中断

基本结构图:

RTC注意事项

执行以下操作将使能对 BKP RTC 的访问:

  1.开启PWR和BKP的时钟  2.使用PWR使能BKP和RTC的访问

  设置PWR_CRDBP,使能对BKPRTC的访问

若在读取 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 寄存器

三、读写备份寄存器&实时时钟

读写BKP

部分库函数解释: 

//复位BKP里的数据
void BKP_DeInit(void);
 
//用于配置侵入Tamp检测的有效电平,即高/低电平触发
void BKP_TamperPinLevelConfig(uint16_t BKP_TamperPinLevel);

//是否开启侵入检测
void BKP_TamperPinCmd(FunctionalState NewState);

//时钟输出功能的配置,可以选择在BKP引脚上输出RTC校准时钟、闹钟脉冲和秒脉冲
void BKP_RTCOutputConfig(uint16_t BKP_RTCOutputSource);

//设置RTC校准值
void BKP_SetRTCCalibrationValue(uint8_t CalibrationValue);

//写备份寄存器
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);

//读备份寄存器
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);

//备份寄存器访问使能
void PWR_BackupAccessCmd(FunctionalState NewState);

BKP配置步骤:

1.开启PWR、BKP时钟

2.  设置PWR_CRDBP,使能对BKPRTC的访问

3.写入读取

代码部分:

因为需要执行的操作很少所以本节代码不进行封装

#include "Key.h"
#include "OLED.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_BKP|RCC_APB1Periph_PWR,ENABLE);
	PWR_BackupAccessCmd(ENABLE);

	while(1)
	{
		KeyNum=KEY_TAP();
		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);//显示写入DR1的值
			OLED_ShowHexNum(1,8,ArrayWrite[1],4);//显示写入DR2的值
			
		}
		ArrayRead[0]=BKP_ReadBackupRegister(BKP_DR1);//读出DR1
		ArrayRead[1]=BKP_ReadBackupRegister(BKP_DR2);//读出DR2
		
		OLED_ShowHexNum(2,3,ArrayRead[0],4);
		OLED_ShowHexNum(2,8,ArrayRead[1],4);
	}
	
}

实时时钟

封装MyRTC.c函数,参考RTC基本结构图进行配置:

1.开启PWR、BKP时钟

2.  设置PWR_CRDBP,使能对BKPRTC的访问

3.开启RTC时钟,此处我们计划使用LSE外部低速时钟作为系统时钟,因此开启LSE时钟

4.配置RTCCLK指定LSE作为RTCCLK

5.等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1,等待上一次操作完成

6.配置预分频器,给PRL一个合适的输出值确保输出给计数器的时钟是1Hz

7.配置CNT的值给一个初始时间

8.根据需要开启闹钟和中断

部分库函数解释:

//配置LSE,开启LSE
void RCC_LSEConfig(uint8_t RCC_LSE);

//配置LSI,开启LSI,在外部时钟不起振的情况下可以使用这个时钟
void RCC_LSICmd(FunctionalState NewState);

//配置RTCCLK时钟源
void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource);

//启动RTCCLK
void RCC_RTCCLKCmd(FunctionalState NewState);

//获取标志位
FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);

//进入RTC配置模式,配置模式中配置PRL/CNT/ARL
void RTC_EnterConfigMode(void);

//退出配置模式
void RTC_ExitConfigMode(void);

//获取cnt计数器的值
uint32_t  RTC_GetCounter(void);

//写入预分频器
void RTC_SetPrescaler(uint32_t PrescalerValue);

//写入闹钟值
void RTC_SetAlarm(uint32_t AlarmValue);

//读取预分频器中的DIV余数寄存器,为了获取更细致的时间
uint32_t  RTC_GetDivider(void);

//等待上一次操作完成
void RTC_WaitForLastTask(void);

//等待同步
void RTC_WaitForSynchro(void);

配置部分代码:

#include "stm32f10x.h"                  // Device header
#include <time.h>

uint16_t MyRTC_Time[]={2023,1,1,23,59,55};//定义全局变量存放年月日时分秒

void MyRTC_SetTime(void);

void MyRTC_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP|RCC_APB1Periph_PWR,ENABLE);
	PWR_BackupAccessCmd(ENABLE);
	
	if(BKP_ReadBackupRegister(BKP_DR1)!= 0xA5A5)//利用BKP判断是否完全断电,不完全断电就不进行初始化防止时间重置
	{
		RCC_LSEConfig(RCC_LSE_ON);//LSE开启
	
		while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)!=SET);//等待LSE开启完毕
	
		RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);//时钟选择
		RCC_RTCCLKCmd(ENABLE);
	
		//等待同步
		RTC_WaitForSynchro();
		//等待上一次操作完成
		RTC_WaitForLastTask();
	
		RTC_SetPrescaler(32768-1);//库函数自动进入配置模式,LSE是32.768kHz,要分成1Hz
		RTC_WaitForLastTask();
	
		MyRTC_SetTime();//写入时间
		
		BKP_WriteBackupRegister(BKP_DR1,0xA5A5);//将BKP_DR1写为0xA5A5用于下次检测
	}
	else//防止意外进行等待
	{
		//等待同步
		RTC_WaitForSynchro();
		//等待上一次操作完成
		RTC_WaitForLastTask();
	}
}

//写入时间
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;//日期时间到秒数的转换,同时进行时区偏移
	
	RTC_SetCounter(time_cnt);
	RTC_WaitForLastTask();
}

//读取时间
void MyRTC_ReadTime(void)
{
	time_t time_cnt;
	struct tm time_date;
	
	time_cnt=RTC_GetCounter()+8*60*60;//进行时区偏移
	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;
}

主函数:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"   //使用延时函数时提前引入
#include "LED.h"
#include "Key.h"
#include "OLED.h"
#include "MyRTC.h"

uint8_t KEY_num;

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:");
	OLED_ShowString(4,1,"Div:");
	
	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);
		OLED_ShowNum(4,6,(32767-RTC_GetDivider())/32767.0*999,10);//将DIV转换为0~999毫秒
	}
	
}

  • 8
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值