STM32实战总结:HAL之RTC

news2024/12/24 8:29:49

RTC基础知识参考:

51单片机内部外设:实时时钟(SPI)_路溪非溪的博客-CSDN博客

STM32中的RTC

51单片机通常是外置的RTC芯片如DS1302,那么STM32的RTC是什么情况呢?

STM32芯片自带RTC,因此不须像其他MCU需外接RTC模块。

先读一读单片机的数据手册。

实时时钟是一个独立的定时器。RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。

RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后,RTC的设置和时间维持不变。 系统复位后,对后备寄存器和RTC的访问被禁止,这是为了防止对后备区域(BKP)的意外写操 作。执行以下操作将使能对后备寄存器和RTC的访问:

● 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟

● 设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。

主要特性:

● 可编程的预分频系数:分频系数高为220。
● 32位的可编程计数器,可用于较长时间段的测量。
● 2个分离的时钟:用于APB1接口的PCLK1和RTC时钟(RTC时钟的频率必须小于PCLK1时钟 频率的四分之一以上)。
● 可以选择以下三种RTC的时钟源:
     ● HSE时钟除以128;
     ● LSE振荡器时钟;
     ● LSI振荡器时钟

● 2个独立的复位类型:
     ● APB1接口由系统复位;
     ● RTC核心(预分频器、闹钟、计数器和分频器)只能由后备域复位

● 3个专门的可屏蔽中断:
     ● 1.闹钟中断,用来产生一个软件可编程的闹钟中断。

     ● 2.秒中断,用来产生一个可编程的周期性中断信号(长可达1秒)。

     ● 3.溢出中断,指示内部可编程计数器溢出并回转为0的状态。

RTC时钟源:
三种不同的时钟源可被用来驱动系统时钟(SYSCLK):

● HSI振荡器时钟
● HSE振荡器时钟
● PLL时钟

这些设备有以下2种二级时钟源:

● 40kHz低速内部RC,可以用于驱动独立看门狗和通过程序选择驱动RTC。 RTC用于从停机/待机模式下自动唤醒系统。
● 32.768kHz低速外部晶体也可用来通过程序选择驱动RTC(RTCCLK)。

RTC原理框图

灰色区域为待机时维持供电区域。

APB1 接口:用来和 APB1 总线相连。

此单元还包含一组 16 位寄存器,可通过 APB1 总线对其进行读写操作。APB1 接口由 APB1 总线时钟驱动,用来与 APB1 总线连接。通过APB1接口可以访问RTC的相关寄存器(预分频值,计数器值,闹钟值)。

RTC 核心接口:由一组可编程计数器组成,分成两个主要模块 。

第一个模块是个RTC预分频器。

在这里插入图片描述

第二个模块是一个 32 位的可编程计数器 (RTC_CNT)可被初始化为当前的系统时间。

一个 32 位的时钟计数器,按秒钟计算,可以记录4294967296秒,约合136年左右,作为一般应用,这已经是足够了的。

RTC具体流程:
RTCCLK经过RTC_DIV预分频,RTC_PRL设置预分频系数,然后得到TR_CLK时钟信号,我们一般设置其周期为1s,RTC_CNT计数器计数,假如1970设置为时间起点为0s,通过当前时间的秒数计算得到当前的时间。RTC_ALR是设置闹钟时间,RTC_CNT计数到RTC_ALR就会产生计数中断。

RTC_Second为秒中断,用于刷新时间。
RTC_Overflow是溢出中断。
RTC Alarm 控制开关机。

断电后这三个中断不起作用。


RTC时钟选择
使用HSE分频时钟或者LSI的时候,在主电源VDD掉电的情况下,这两个时钟来源都会受到影响,因此没法保证RTC正常工作。所以RTC一般都时钟低速外部时钟LSE,频率为实时时钟模块中常用的32.768KHz,因为32768 = 2^15,分频容易实现,所以被广泛应用到RTC模块。(在主电源VDD有效的情况下(待机),RTC还可以配置闹钟事件使STM32退出待机模式)

RTC设备因为其独特的运行方式(即掉电依旧运行)使用HSE分频时钟或者LSI的时候,在主电源VDD掉电的情况下,这两个时钟来源都会受到影响,资源消耗太大,小小的纽扣电池根本吃不消。没法保证RTC正常工作.所以RTC一般都时钟低速外部时钟LSE。

RTC复位过程
除了RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器外,所有的系统寄存器都由系统复位或电源复位进行异步复位。
RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器仅能通过备份域复位信号复位。系统复位后,禁止访问后备寄存器和RCT,防止对后卫区域(BKP)的意外写操作。

RTC中断
秒中断:
这里时钟自带一个秒中断,每当计数加一的时候就会触发一次秒中断。注意,这里所说的秒中断并非一定是一秒的时间,它是由RTC时钟源和分频值决定的“秒”的时间,当然也是可以做到1秒钟中断一次。我们通过往秒中断里写更新时间的函数来达到时间同步的效果。

闹钟中断:
闹钟中断就是设置一个预设定的值,计数每自加多少次触发一次闹钟中断。

另外要注意的是,RTC的后备区域是没有日期寄存器的。所以,在断电重启后,日期不会更新,只有时间是最新的。那么,怎么能获取到最新的日期呢?

第一种办法是使用更高级的单片机,比如F4系列,就自带了日期寄存器。

另外一种办法就是通过计数器来硬算。计算量很大。

实际中可根据情况来选择最适合的方式。

硬件电路

RTC时钟:

后备电池模块

MX配置

开启RTC

开启LSE

生成代码即可。

关键代码

/* Includes ------------------------------------------------------------------*/
#include "MyApplication.h"

/* Private define-------------------------------------------------------------*/

/* Private variables----------------------------------------------------------*/
RTC_TimeTypeDef RTC_TimeStruct_CurrentValue; //RTC当前时间
RTC_DateTypeDef RTC_DateStruct_CurrentValue; //RTC当前日期

uint8_t *Week_Str[7] = 
{
	(uint8_t*)"日",
	(uint8_t*)"一", 
	(uint8_t*)"二", 
	(uint8_t*)"三", 
	(uint8_t*)"四", 
	(uint8_t*)"五", 
	(uint8_t*)"六"
};
	
/* Private function prototypes------------------------------------------------*/  
static void Calendar_Set(void);  //设置日历
static void Calendar_Get(void);  //获取日历
static void Calendar_Show(void); //显示日历

static uint8_t Input_RTC_SetValue(uint8_t); //输入RTC设置值
static void    RTC_Time_Set(void);          //RTC时间设置
static void    RTC_Date_Set(void);          //RTC日期设置

/* Public variables-----------------------------------------------------------*/
MyRTC_t MyRTC = 
{
	TRUE,
	
	&RTC_TimeStruct_CurrentValue,
	&RTC_DateStruct_CurrentValue,
	
	Calendar_Set,
	Calendar_Get,
	Calendar_Show
};

/*
	* @name   Calendar_Set
	* @brief  设置日历
	* @param  None
	* @retval None      
*/
static void Calendar_Set()
{	
	//上电复位时,读取RTC备份寄存器1的数据,如果为0x1688,则不需要通过串口重新设置日期与时间
	if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1) != 0x1688)
	{
		printf("^_^^_^开始设置RTC的日期与时间^_^^_^\r\n\r\n");
		
		RTC_Date_Set(); //设置日期
		RTC_Time_Set(); //设置时间
		
		HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0x1688);	
	}
	else
	{
		printf("^_^^_^RTC的日期与时间已设置!^_^^_^\r\n\r\n");
		printf("重新设置的方法如下:\r\n");
		printf("方法一:长按触摸按键2s以上;\r\n");
		printf("方法二:系统断电,同时拔掉RTC电池。\r\n\r\n");
	}
}

/*
	* @name   Calendar_Get
	* @brief  显示日历
	* @param  None
	* @retval None      
*/
static void Calendar_Get()
{
	//获取当前时间
	HAL_RTC_GetTime(&hrtc,MyRTC.pRTC_TimeStruct,RTC_FORMAT_BIN);
	//获取当前日期
	HAL_RTC_GetDate(&hrtc,MyRTC.pRTC_DateStruct,RTC_FORMAT_BIN);
}

/*
	* @name   Calendar_Show
	* @brief  显示日历
	* @param  None
	* @retval None      
*/
static void Calendar_Show()
{
	//串口打印日期
	printf("当前时间为: %02u年%02d月%02d日(星期%s)  ", 2000+MyRTC.pRTC_DateStruct->Year,MyRTC.pRTC_DateStruct->Month,MyRTC.pRTC_DateStruct->Date,Week_Str[MyRTC.pRTC_DateStruct->WeekDay]);
	//串口打印时间
	printf("%02u:%02u:%02u\r\n",MyRTC.pRTC_TimeStruct->Hours,MyRTC.pRTC_TimeStruct->Minutes,MyRTC.pRTC_TimeStruct->Seconds);
	
	//数码管显示时间
	Display.Disp_HEX(Disp_NUM_6,MyRTC.pRTC_TimeStruct->Hours/10,Disp_DP_OFF);
	Display.Disp_HEX(Disp_NUM_5,MyRTC.pRTC_TimeStruct->Hours%10,Disp_DP_ON);
	
	Display.Disp_HEX(Disp_NUM_4,MyRTC.pRTC_TimeStruct->Minutes/10,Disp_DP_OFF);
	Display.Disp_HEX(Disp_NUM_3,MyRTC.pRTC_TimeStruct->Minutes%10,Disp_DP_ON);
	
	Display.Disp_HEX(Disp_NUM_2,MyRTC.pRTC_TimeStruct->Seconds/10,Disp_DP_OFF);
	Display.Disp_HEX(Disp_NUM_1,MyRTC.pRTC_TimeStruct->Seconds%10,Disp_DP_OFF);
}

/*
	* @name   Input_RTC_SetValue
	* @brief  输入RTC设置值
	* @param  MAX_Value -> 输入最大值
	* @retval SetValue  -> 返回输入字符对应的数值     
*/
static uint8_t Input_RTC_SetValue(uint8_t MAX_Value)
{
	uint8_t SetValue = 0;  //返回值
	uint8_t Value_Arr[2] = {0};
	uint8_t Index = 0;
	
	//以等待方式从串口接收2个有效字符
	while(Index < 2)
	{
		//等待接收串口数据
		Value_Arr[Index++] = getchar();
		//校验字符有效性
		if((Value_Arr[Index -1] < '0') || (Value_Arr[Index -1] > '9'))
		{
			printf("请输入 0 到 9 之间的数字 -->:\n");
			Index--;
		}
	}
	
	//接收到的2个字符转化为数值
	SetValue = (Value_Arr[0] - '0')*10 + (Value_Arr[1] - '0');
	
	//校验数值有效行
	if(SetValue > MAX_Value)
	{
		printf("请输入 0 到 %d 之间的数字\n", MAX_Value);
		SetValue = 0xFF; //SetValue设置为无效数据
	}
	
	//返回数据
	return SetValue;
}

/*
	* @name   RTC_Date_Set
	* @brief  RTC日期设置
	* @param  None
	* @retval None  
*/
static void RTC_Date_Set()
{
  RTC_DateTypeDef RTC_DateStruct_SetValue;
	uint8_t SetValue;
	
	printf("=========================日期设置==================\n");
	printf("请输入年份(00-99): 20\n");
	SetValue = 0xFF;
	while (SetValue == 0xFF)
	{
		SetValue = Input_RTC_SetValue(99);
	}
	printf("年份被设置为:  20%02u\n", SetValue);
	RTC_DateStruct_SetValue.Year = SetValue;
	
	printf("请输入月份(01-12):  \n");
	SetValue = 0xFF;
	while (SetValue == 0xFF)
	{
		SetValue = Input_RTC_SetValue(12);
		
		if(SetValue == 0x00)
		{
			printf("月份不能设置为0,请重新输入月份:\r\n");
			SetValue = 0xFF;
		}
	}
	printf("月份被设置为:  %02u\n", SetValue);
	RTC_DateStruct_SetValue.Month = SetValue;
	
	printf("请输入日期(01-31):  \n");
	SetValue = 0xFF;
	while (SetValue == 0xFF)
	{
		SetValue = Input_RTC_SetValue(31);
		
		if(SetValue == 0x00)
		{
			printf("日期不能设置为0,请重新输入日期:\r\n");	
			SetValue = 0xFF;
		}
	}
	printf("日期被设置为:  %02u\r\n", SetValue);
	RTC_DateStruct_SetValue.Date = SetValue;
	//设置日期
	HAL_RTC_SetDate(&hrtc,&RTC_DateStruct_SetValue,RTC_FORMAT_BIN);
}

/*
	* @name   RTC_Time_Set
	* @brief  RTC时间设置
	* @param  None
	* @retval None  
*/
static void RTC_Time_Set()
{
	RTC_TimeTypeDef RTC_TimeStruct_SetValue;
	uint8_t SetValue;
	
	printf("=========================时间设置==================\n");
	printf("请输入时钟(00-23):  \n");
	SetValue = 0xFF;
	while (SetValue == 0xFF)
	{
		SetValue = Input_RTC_SetValue(23);
	}
	printf("时钟被设置为:  %02u\n", SetValue);
	RTC_TimeStruct_SetValue.Hours = SetValue;
	
	printf("请输入分钟(00-59):  \n");
	SetValue = 0xFF;
	while (SetValue == 0xFF)
	{
		SetValue = Input_RTC_SetValue(59);
	}
	printf("分钟被设置为:  %02u\n", SetValue);
	RTC_TimeStruct_SetValue.Minutes = SetValue;
	
	printf("请输入秒钟(00-59):  \n");
	SetValue = 0xFF;
	while (SetValue == 0xFF)
	{
		SetValue = Input_RTC_SetValue(59);
	}
	printf("秒钟被设置为:  %02u\n", SetValue);
	RTC_TimeStruct_SetValue.Seconds = SetValue;
	//设置时间
	HAL_RTC_SetTime(&hrtc,&RTC_TimeStruct_SetValue,RTC_FORMAT_BIN);
}
/********************************************************
  End Of File
********************************************************/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/43488.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

年末盘点Android 过去一年与未来的一个走势~

随着Android的发展&#xff0c;有些人对Android未来感到茫然&#xff0c;不少人可能会产生这样的疑惑&#xff1a;“从事Android是不是没有前途&#xff0c;Android开发还有什么值得学&#xff1f;“这类话题一直让大家争论不休&#xff0c;它并没有一个确切、唯一的标准答案&a…

中介者模式

思考中介者模式 当多个类&#xff08;对象&#xff09;耦合严重时&#xff0c;通过中介者模式创建一个中介者&#xff0c;多个类不直接交互了&#xff0c;变成和中介者进行交互&#xff0c;松散耦合 1.中介者模式的本质 中介者模式的本质:封装交互。 中介者模式的目的&#xff…

关于无感刷新Token,我是这样子做的

本文正在参加「金石计划 . 瓜分6万现金大奖」 什么是JWT JWT是全称是JSON WEB TOKEN&#xff0c;是一个开放标准&#xff0c;用于将各方数据信息作为JSON格式进行对象传递&#xff0c;可以对数据进行可选的数字加密&#xff0c;可使用RSA或ECDSA进行公钥/私钥签名。 使用场景…

WPSpell将拼写检查添加到VCL应用程序

WPSpell将拼写检查添加到VCL应用程序 WPSpell包括键入功能时的拼写。拼写错误的单词带有下划线&#xff0c;可以使用上下文菜单进行更正。它还包括一个传统的拼写检查对话框&#xff0c;并支持多个词典。WPSpell特别适合与WPTools一起使用。 WPSpell功能 键入时进行拼写检查。 …

1-FreeRTOS入门指南

本专栏是根据官方提供的文档进行FreeRTOS的各个功能函数的说明&#xff0c;以及函数的使用 本专栏不涉及动手操作&#xff0c;只是对原理进行说明&#xff0c;FreeRTOS基础知识篇更新完成会对如何在开发板上进行上手实战操作。 这里不会对比其他RTOS的优缺点&#xff0c;因为每…

2、Redis中简单动态字符串的简介,也就是Redis中的键和值的字符串底层表达

简介 首先在Redis中&#xff0c;没有直接使用C语言传统字符串表示(以空字符结尾的字符数组,以下简称C字符串)&#xff0c;而是自己构建了一种名为简单动态字符串(simple dynamic string,SDS)的抽象类型(可以简单的理解为Java中的String 类)&#xff0c;并且将SDS用作Redis的默…

动态规划算法(1)

认识动态规划 动态规划的求解思路&#xff1a; 1. 把一个问题分解成若干个子问题 2. 将中间结果保存以避免重复计算 基本步骤&#xff1a; 1. 找出最优解的性质&#xff0c;然后刻画结构特征 &#xff08;找规律&#xff09; 2. 最优解(最好的解决方案 定义) 循环(递归) 3. 以…

我与梅西粉丝们的世界杯观球日常

世界杯 ⚽️ 期间&#xff0c;我与其他的梅西粉丝在某 APP 里建了个梅粉聊天群&#xff0c;群内人数上万人&#xff0c;大家一起讨论赛事热点&#xff0c;可谓热火朝天&#xff0c;此起彼伏&#xff0c;这是四年一度的狂欢&#xff0c;虽值冬季&#xff0c;但热情不减。 “阿根…

配置设备远程管理—eNSP

案例&#xff1a;给路由器配置远程管理&#xff0c;使一台路由器远程管理另一台。 所需设备&#xff1a;两台路由器&#xff0c;一根网线 图示 一、给两台设备配置IP地址 AR1&#xff08;以下命令&#xff09; a. sy b. int g0/0/0 c. ip add 1.1.1.1 24AR2 a. sy b. int g0/0…

十分钟学完简单工厂,普通工厂,抽象工厂

快速学习简单工厂&#xff0c;普通工厂&#xff0c;抽象工厂前言&#xff1a;产品等级和产品族工厂模式作用简单工厂模式uml代码优缺点普通工厂模式uml代码优缺点抽象工厂模式uml代码优缺点前言&#xff1a;产品等级和产品族 在学习工厂模式之前&#xff0c;先得了解一下产品等…

Redis实践

一、持久化 Redis 的数据 全部存储 在 内存 中&#xff0c;如果 突然宕机&#xff0c;数据就会全部丢失&#xff0c;因此必须有一套机制来保证 Redis 的数据不会因为故障而丢失&#xff0c;这种机制就是 Redis 的 持久化机制&#xff0c;它会将内存中的数据库状态 保存到磁盘 …

Spring——AOP原理及流程详解

AOP原理及流程详解一、AOP结构介绍Pointcut通知原理连接点拦截器二、Bean介入点EnableAspectJAutoProxyAspectJAutoProxyRegistrarAnnotationAwareAspectJAutoProxyCreatorAbstractAutoProxyCreator实例前执行初始化后执行循环依赖会调用总结三、处理切面获取所有切面其下通知方…

国内饮料行业数据浅析

大家好&#xff0c;这里是小安说网控。 饮料一直深得年轻人的宠爱&#xff0c;主要消费品类为饮用水、碳酸饮料、奶制品、气泡水等。刚刚过去的十月份&#xff0c;我国饮料产量当期值1199.6万吨&#xff0c;同比下降6.1%&#xff1b;今年1-10月份&#xff0c;饮料产量累计值157…

这几个点让我买了Watch Ultra

01.凑够Apple 全家桶 MacBook ProiPhoneAirPodsiPad 02.可以解锁iPhone手机&#xff0c;MacBook,iPad 03.当iPhone 来电话&#xff0c;不方便接听&#xff0c;可以使用Watch接听(虽然这种情况挺少) 04.可以连接AirPods 听音乐 05.花10元钱开卡&#xff0c;iPhone和Watch 可以…

前端ES6-ES11新特性

ES6新特性 变量声明 let a; let b,c,d; let e 100; let f 521, g iloveyou, h [];块级作用域 {let girl 周扬青; }console.log(girl); //这里会报错&#xff0c;变量不在作用域内,用var声明就可以常量声明 const NAME tom; //必须赋予初始值&#xff0c;变量名大写&am…

3-7数据链路层-设备

文章目录一.网桥1.基本原理2.透明网桥&#xff08;1&#xff09;工作原理&#xff08;2&#xff09;自学习算法3.源路由网桥二.局域网交换机1.局域网交换机2.原理3.特点4.两种交换模式5.交换机的自学习算法一.网桥 网桥根据MAC帧的目的地址对帧进行转发和过滤。当网桥收到一个…

Java中数组、集合初始化及遍历方式

一、数组 1. 一维数组 一维数组两种初始化方式 静态初始化 int[] array {1,2,3};int[] array new int[]{1,2,3};动态初始化 int[] array new int[3]; array[0]1; array[1]2; array[2]3;一维数组两种遍历方式 普通for循环for (int i 0; i < array.length; i) {System.ou…

全家桶Spring、HikariCP、Mybatis和Oracle配置,你想要的都在这里

目录1、HikariCP配置说明2、spring配置文件里&#xff0c;配置HikariCP数据库连接池3、注意连接池大小设置&#xff0c;重点推荐官方说明文档4、HikariCP配置5、数据库配置文件1、HikariCP配置说明 HikariCP: https://github.com/brettwooldridge/HikariCP 2、spring配置文件…

毕业设计-机器学习人眼检测活体检测-opencv

目录 前言 课题背景和意义 实现技术思路 实现效果图样例 前言 &#x1f4c5;大四是整个大学期间最忙碌的时光,一边要忙着备考或实习为毕业后面临的就业升学做准备,一边要为毕业设计耗费大量精力。近几年各个学校要求的毕设项目越来越难,有不少课题是研究生级别难度的,对本科…

视效剧情口碑双爆棚!Netflix 现象级剧集《怪奇物语》第四季神级视效专访大揭秘!

刷新 Netflix 收视记录的超火剧集《怪奇物语》&#xff08;Stranger Things&#xff09;第四季视效剧情口碑双爆棚&#xff0c;无疑是2022年最值得一看的现象级剧集之一。第四季共九集&#xff0c;分上下两部&#xff0c;分别在今年5月和7月上线&#xff0c;目前豆瓣评分已经稳…