1、HAL库的RTC单纯的跑时间还是挺简单的,但是日期确实保存在RTC句柄下的一个结构体变量中,当掉电后日期便丢失了,重新上电初始化的话会在RTC初始化中强制赋值为0年1月1日,这就不爽了,于是通过查询资料终于解决了
2、先说硬件32内部RTC时钟可以选择LSI,但是LSI不属于备份域的范围,当掉电后,电池连接到VBAT引脚不能保证LSI工作,这样的话时钟就停止运行了,重新上电后还是掉电的时间,需要重新设置时间了,而且LSI的时钟频率30到60KHz,对应时钟来说也太不准了,而且对F103C8T6来说也不支持校准。如果不用于时间,而是短暂不精确的延时还是可以考虑的。因此还是要选LSE,这样的话就需要外部32.768晶振了,然后经典电路跨接两个电容中间接地。电容大小根据手册晶振阻抗30Ω,选择15pF就可以了。
3、说完硬件该软件了,先是CubeMX配置
注意一下时钟选择LSE,数据格式跟你情况,我这里直接选择二进制了,也可以选择BCD格式,HAL自动转换也挺方便。配置好直接生成工程就行了。
4、因为main函数默认选择调用初始化函数,想自己写的话可以改一下CubeMx的配置
把这个√选上生成的程序就不会调用RTC初始化了 函数了,因为一旦调用RTC初始化函数就会重新配置RTC的备份域,设置时间日期,这对应掉电后重新上电会导致时间恢复为配置值,我看过一下这个博客采用了注释RTC初始化函数的方式,博主也说明了,没次重新生成工程时都需要重新注释掉初始化程序,我觉得很好解决啊
解决CUBEMX生成stm32f103xx RTC时钟掉电日期不保存的问题_C的世界的博客-CSDN博客_stm32rtc掉电不保存看
/* USER CODE BEGIN 0 */
extern RTC_TimeTypeDef Time ;
extern RTC_DateTypeDef Date;
/* USER CODE END 0 */
我先定义了两个变量用于保存时间和日期,其实可以不用定义的,因为在hrtc的句柄里都有这两个结构体。首先判断是否初始化还是利用电池供电时备份域的掉电保存功能,在初始化后写入一个非复位值这里我写成0x55,然后在初始化前读取备份域判断是否已经写入过0x55,表示已经初始化过,这个判断一定要放在 /* USER CODE BEGIN RTC_Init 1 */这个注释之后,不然CubeMx重新生成程序的话会覆盖到自己的辛苦成果。
/* USER CODE BEGIN RTC_Init 1 */
//第一次初始化写入0X55,下次上电通过读取该值判断是否是重新已经初始化时间否则时间进行默认设置
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0x55)
{
/* USER CODE END RTC_Init 1 */
当然CubeMX必须勾选这里,有点啰嗦了
如果没有初始化则调用CubeMx生成的初始化函数,并在初始化后然后在
/* USER CODE BEGIN RTC_Init 2 */
这里写入BKP区域0x55,记录已初始化。
/* USER CODE BEGIN RTC_Init 2 */
//初始化结束后写入0X55 下次判断不再初始化
HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0X55);
Date_write_BKP(&hrtc,&DateToUpdate);//将日期保存在BKP区域防止掉电后丢失
}
else
{
/*因为RTC是根据按照一天24*60*365秒进行计算时间,也就是24小时制
hrtc.DateToUpdate的用于保存日期且初始化为0年1月1日
当调用HAL_RTC_GetTime根据秒数,判断是否超过一天,更新日期保存并在hrtc.DateToUpdate中,
然后计数值会减去一天的秒数,这样就不能通过读计数值算出来过了多少天
当掉电时变量也就丢失了,也就是日期就丢失了只要定时将日期保存在后备区域,
上电后先读取后备区域重新初始化hrtc.DateToUpdate变量,
当调用HAL_RTC_GetTime就可以根据hrtc.DateToUpdate变量更新日期,这样就不会丢失日期了
*/
//已经初始化过因为不调用初始化函数但是变量该赋值的还是要赋值
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
Date_read_BKP(&hrtc);//读取BKP区域的掉电前的日期,然后根据计数值计算掉电期间过了几天
//32位每秒计数1的话可以计数136年,不用担心溢出
}
HAL_RTC_MspInit(&hrtc);//因为不调用了RTC初始化避免重置日期 但是BKP的时钟中断等配置还是要有的
HAL_RTCEx_SetSecond_IT(&hrtc);//开启1秒中断//这个函数CubeMx不会自动生成需要手动调用
HAL_RTC_GetTime(&hrtc,&Time,RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc,&Date,RTC_FORMAT_BIN);
/* USER CODE END RTC_Init 2 */
由于生成的初始化代码在if判断中,如果已经初始化过,就不会调用生成的代码,避免时间被重置为初始值,但同事也产生了一个问题,就是RTC句柄和备份域或者中断什么的都不会初始化,这就要自己来进行赋值了。RTC的时间是保存在RTC句柄hrtc中的DateToUpdate中 包括星期 月 日 年,重新上电后要从备份域中读取出来掉电前的日期恢复到DateToUpdate中
void Date_read_BKP(RTC_HandleTypeDef * hrtc)
{//将日期从备份域还原到hrtc->DateToUpdate用于HAL_RTC_GetDate更新日期
Date.WeekDay=HAL_RTCEx_BKUPRead(hrtc,RTC_BKP_DR2);
Date.Year=HAL_RTCEx_BKUPRead(hrtc,RTC_BKP_DR3);
Date.Month=HAL_RTCEx_BKUPRead(hrtc,RTC_BKP_DR4);
Date.Date=HAL_RTCEx_BKUPRead(hrtc,RTC_BKP_DR5);
HAL_RTC_SetDate(hrtc, &Date, RTC_FORMAT_BIN);
//设置到hrtc.DateToUpdate中 因为HAL_RTC_GetTime只更新DateToUpdate 其实我是多定义了一个Date可以直接使用hrtc的DateToUpdate
}
这样再获取时间时,日期就能正常连接上了
HAL_RTC_GetTime(&hrtc,&Time,RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc,&Date,RTC_FORMAT_BIN);
5、但是正常运行时要将最新的日期保存在备份域中,我这里时开启了秒中断,在中断里判断是否有日期更新,如果更新了就保存最新的到备份域
void HAL_RTCEx_RTCEventCallback ( RTC_HandleTypeDef * hrtc )
{//秒中断更新时间
static uint8_t Time_Date=0xFF;
HAL_RTC_GetTime(hrtc,&Time,RTC_FORMAT_BIN);
HAL_RTC_GetDate(hrtc,&Date,RTC_FORMAT_BIN);
//如果日期有变化就保存当前日期到备份域BKP为下次掉电上电后计算日期提供初始化数据
if(Time_Date!=Date.Date)
{
Time_Date=Date.Date;
Date_write_BKP(hrtc,&Date);
}
}
/* USER CODE END 1 */
void Date_write_BKP(RTC_HandleTypeDef * hrtc,RTC_DateTypeDef * Date)
{//将日期保存在备份域
HAL_RTCEx_BKUPWrite(hrtc,RTC_BKP_DR2,Date->WeekDay);
HAL_RTCEx_BKUPWrite(hrtc,RTC_BKP_DR3,Date->Year);
HAL_RTCEx_BKUPWrite(hrtc,RTC_BKP_DR4,Date->Month);
HAL_RTCEx_BKUPWrite(hrtc,RTC_BKP_DR5,Date->Date);
}
我使用的是备份域的2到5,1用于记录是否已经初始化。这样就可以保证掉电后时间正常走,上电后日期也不丢失了。
6、最后将rtc.h代码整体附上
/**
******************************************************************************
* @file rtc.c
* @brief This file provides code for the configuration
* of the RTC instances.
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2022 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "rtc.h"
/* USER CODE BEGIN 0 */
extern RTC_TimeTypeDef Time ;
extern RTC_DateTypeDef Date;
/* USER CODE END 0 */
RTC_HandleTypeDef hrtc;
/* RTC init function */
void MX_RTC_Init(void)
{
/* USER CODE BEGIN RTC_Init 0 */
/* USER CODE END RTC_Init 0 */
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef DateToUpdate = {0};
/* USER CODE BEGIN RTC_Init 1 */
//第一次初始化写入0X55,下次上电通过读取该值判断是否是重新已经初始化时间否则时间进行默认设置
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!=0x55)
{
/* USER CODE END RTC_Init 1 */
/** Initialize RTC Only
*/
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN Check_RTC_BKUP */
/* USER CODE END Check_RTC_BKUP */
/** Initialize RTC and set the Time and Date
*/
sTime.Hours = 23;
sTime.Minutes = 59;
sTime.Seconds = 55;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
DateToUpdate.WeekDay = RTC_WEEKDAY_SUNDAY;
DateToUpdate.Month = RTC_MONTH_APRIL;
DateToUpdate.Date = 3;
DateToUpdate.Year = 22;
if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN RTC_Init 2 */
//初始化结束后写入0X55 下次判断不再初始化
HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0X55);
Date_write_BKP(&hrtc,&DateToUpdate);//将日期保存在BKP区域防止掉电后丢失
}
else
{
/*因为RTC是根据按照一天24*60*365秒进行计算时间,也就是24小时制
hrtc.DateToUpdate的用于保存日期且初始化为0年1月1日
当调用HAL_RTC_GetTime根据秒数,判断是否超过一天,更新日期保存并在hrtc.DateToUpdate中,
然后计数值会减去一天的秒数,这样就不能通过读计数值算出来过了多少天
当掉电时变量也就丢失了,也就是日期就丢失了只要定时将日期保存在后备区域,
上电后先读取后备区域重新初始化hrtc.DateToUpdate变量,
当调用HAL_RTC_GetTime就可以根据hrtc.DateToUpdate变量更新日期,这样就不会丢失日期了
*/
//已经初始化过因为不调用初始化函数但是变量该赋值的还是要赋值
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
Date_read_BKP(&hrtc);//读取BKP区域的掉电前的日期,然后根据计数值计算掉电期间过了几天
//32位每秒计数1的话可以计数136年,不用担心溢出
}
HAL_RTC_MspInit(&hrtc);//因为不调用了RTC初始化避免重置日期 但是BKP的时钟中断等配置还是要有的
HAL_RTCEx_SetSecond_IT(&hrtc);//开启1秒中断//这个函数CubeMx不会自动生成需要手动调用
HAL_RTC_GetTime(&hrtc,&Time,RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc,&Date,RTC_FORMAT_BIN);
/* USER CODE END RTC_Init 2 */
}
void HAL_RTC_MspInit(RTC_HandleTypeDef* rtcHandle)
{
if(rtcHandle->Instance==RTC)
{
/* USER CODE BEGIN RTC_MspInit 0 */
/* USER CODE END RTC_MspInit 0 */
HAL_PWR_EnableBkUpAccess();
/* Enable BKP CLK enable for backup registers */
__HAL_RCC_BKP_CLK_ENABLE();
/* RTC clock enable */
__HAL_RCC_RTC_ENABLE();
/* RTC interrupt Init */
HAL_NVIC_SetPriority(RTC_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(RTC_IRQn);
/* USER CODE BEGIN RTC_MspInit 1 */
/* USER CODE END RTC_MspInit 1 */
}
}
void HAL_RTC_MspDeInit(RTC_HandleTypeDef* rtcHandle)
{
if(rtcHandle->Instance==RTC)
{
/* USER CODE BEGIN RTC_MspDeInit 0 */
/* USER CODE END RTC_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_RTC_DISABLE();
/* RTC interrupt Deinit */
HAL_NVIC_DisableIRQ(RTC_IRQn);
/* USER CODE BEGIN RTC_MspDeInit 1 */
/* USER CODE END RTC_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
void Date_write_BKP(RTC_HandleTypeDef * hrtc,RTC_DateTypeDef * Date)
{//将日期保存在备份域
HAL_RTCEx_BKUPWrite(hrtc,RTC_BKP_DR2,Date->WeekDay);
HAL_RTCEx_BKUPWrite(hrtc,RTC_BKP_DR3,Date->Year);
HAL_RTCEx_BKUPWrite(hrtc,RTC_BKP_DR4,Date->Month);
HAL_RTCEx_BKUPWrite(hrtc,RTC_BKP_DR5,Date->Date);
}
void Date_read_BKP(RTC_HandleTypeDef * hrtc)
{//将日期从备份域还原到hrtc->DateToUpdate用于HAL_RTC_GetDate更新日期
Date.WeekDay=HAL_RTCEx_BKUPRead(hrtc,RTC_BKP_DR2);
Date.Year=HAL_RTCEx_BKUPRead(hrtc,RTC_BKP_DR3);
Date.Month=HAL_RTCEx_BKUPRead(hrtc,RTC_BKP_DR4);
Date.Date=HAL_RTCEx_BKUPRead(hrtc,RTC_BKP_DR5);
HAL_RTC_SetDate(hrtc, &Date, RTC_FORMAT_BIN);
//设置到hrtc.DateToUpdate中 因为HAL_RTC_GetTime只更新DateToUpdate 其实我是多定义了一个Date可以直接使用hrtc的DateToUpdate
}
void HAL_RTCEx_RTCEventCallback ( RTC_HandleTypeDef * hrtc )
{//秒中断更新时间
static uint8_t Time_Date=0xFF;
HAL_RTC_GetTime(hrtc,&Time,RTC_FORMAT_BIN);
HAL_RTC_GetDate(hrtc,&Date,RTC_FORMAT_BIN);
//如果日期有变化就保存当前日期到备份域BKP为下次掉电上电后计算日期提供初始化数据
if(Time_Date!=Date.Date)
{
Time_Date=Date.Date;
Date_write_BKP(hrtc,&Date);
}
}
/* USER CODE END 1 */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/