提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
STM32 RTC时钟掉电日期不更新 & STM32 HAL库RTC时钟配置
一、STM32CubeMX RTC配置
二、RTC初始化
由于自动生成的RTC程序会在初始化时将默认时间写入到RTC寄存器中,所以这里采用RTC备份存储区(掉电/重启数据不丢失)来做标志位来存储系统时间是否已经初始化过,已经初始化则不在重新写入时间,否则会将RTC时间复位。(其他系列的也可以按此操作)
程序代码加在 /* USER CODE BEGIN Check_RTC_BKUP */区域内,否则更新STM32CubeMx文件会将修改的代码删掉。
- 首先判断标志位 0x55AA,标志位自己定义数值,0x55AA没有特别含义;
- 如果是首次烧录运行,判断你是不,往下走写入默认时间和0x55AA标志位,且将日期备份到RTC备份存储区;
- 第二次运行,判断成功成功,将RTC备份存储区的日期设入RTC寄存器。日期就不会恢复到0-01-01。但是还存在一个日期掉电跨天的问题,在第4步解决。
/* 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 */
/* USER CODE END RTC_Init 1 */
/** Initialize RTC Only
*/
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN Check_RTC_BKUP */
if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1)== 0x55AA)//判断标志位
/*check rtc is ready running! If YES, return , otherwise set the default date and time */
{
#if 1
DateToUpdate.Year = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
DateToUpdate.Month = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR3);
DateToUpdate.Date = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR4);
DateToUpdate.WeekDay = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR5);
if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
#endif
return;
}
/* USER CODE END Check_RTC_BKUP */
/** Initialize RTC and set the Time and Date
*/
sTime.Hours = 16;
sTime.Minutes = 45;
sTime.Seconds = 0;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
DateToUpdate.WeekDay = RTC_WEEKDAY_THURSDAY;
DateToUpdate.Month = RTC_MONTH_MAY;
DateToUpdate.Date = 19;
DateToUpdate.Year = 22;
if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN RTC_Init 2 */
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x55AA);//写入标志位
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, (uint16_t)DateToUpdate.Year);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR3, (uint16_t)DateToUpdate.Month);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR4, (uint16_t)DateToUpdate.Date);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR5, (uint16_t)DateToUpdate.WeekDay);
/* USER CODE END RTC_Init 2 */
}
三、RTC日期掉电不更新(F1…这里暂时只考虑F103,其他系列未测试,对比的也只考虑F4)
STM32F103的寄存器和F4的不一样:
F4的RTC能够掉电自动更新日期,因为有TR寄存器更新时间,DR寄存器更新日期;
而F1的只能自动更新CNTH和CNTL寄存器。通过寄存器去换算成时间。但是HAL库使用寄存器的值会在跨天的时候自动清零。
F4
F1
跨天自减
四、解决办法
4.1、上电对时
如果对时间有非常明确的要求,有条件的采用上电对时,就不会有时间错乱。方法不多赘述。
4.2、将时间和日期都换算存入到CNT寄存器中(存入时间戳)
#include "time.h"
uint32_t stm32_rtc_timestamp(RTC_TimeTypeDef TimeStruct, RTC_DateTypeDef DateStruct)
{
struct tm tm_new;
tm_new.tm_sec = TimeStruct.Seconds;
tm_new.tm_min = TimeStruct.Minutes;
tm_new.tm_hour = TimeStruct.Hours;
tm_new.tm_mday = DateStruct.Date;
tm_new.tm_mon = DateStruct.Month;
tm_new.tm_year = DateStruct.Year;
return mktime(&tm_new);
}
(暂时没空实现,网上有一些类似方法)但是相对来说要处理的东西比较多,参考下面这个链接其实可以用时间戳转换函数去写入和转换时间。
STM32 RTC时钟掉电日期不更新
4.3、设置时间时将日期同时设置,且保存到RTC备份存储区
1) 设置的日期保存到RTC备份存储器,用作基准时间;自己实现的RTC设置日期函数。
void Bsp_RTC_SetDate(RTC_HandleTypeDef *pRtcHandle, RTC_DateTypeDef *pDateStruct)
{
HAL_RTC_SetDate(pRtcHandle, pDateStruct, FORMAT_BIN);
HAL_RTCEx_BKUPWrite(pRtcHandle, RTC_BKP_DR2, (uint16_t)pDateStruct->Year);
HAL_RTCEx_BKUPWrite(pRtcHandle, RTC_BKP_DR3, (uint16_t)pDateStruct->Month);
HAL_RTCEx_BKUPWrite(pRtcHandle, RTC_BKP_DR4, (uint16_t)pDateStruct->Date);
HAL_RTCEx_BKUPWrite(pRtcHandle, RTC_BKP_DR5, (uint16_t)pDateStruct->WeekDay);
}
void Bsp_RTC_SetTime(RTC_HandleTypeDef *pRtcHandle, RTC_TimeTypeDef *pTimeStruct)
{
HAL_RTC_SetTime(pRtcHandle, pTimeStruct, FORMAT_BIN);
}
2) 时间设置和日期设置必须保持同步;这一步是为了将基准时间(RTC备份区的日期)和偏移时间(CNTH和CNTL的自增寄存器数值)统一。stm32f1xx_hal_rtc.c文件
void Drv_RTC_CalendarSet(BSP_DateTypeDef Date, BSP_TimeTypeDef Time)
{
static RTC_DateTypeDef DateStruct;
static RTC_TimeTypeDef TimeStruct;
DateStruct.Year = Date.Year;
DateStruct.Month = Date.Month;
DateStruct.Date = Date.Date;
DateStruct.WeekDay = Date.WeekDay;
Bsp_RTC_SetDate(&RtcHandle,&DateStruct);
TimeStruct.Hours = Time.Hours;
TimeStruct.Minutes = Time.Minutes;
TimeStruct.Seconds = Time.Seconds;
Bsp_RTC_SetTime(&RtcHandle,&TimeStruct);
}
3) CNTL和CNTH寄存器跨天不更新;即,偏移时间counter_time必须一直自增。stm32f1xx_hal_rtc.c文件
HAL_RTC_GetTime
HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
{
/***省略*/
/* Read the time counter*/
counter_time = RTC_ReadTimeCounter(hrtc);
/* Fill the structure fields with the read parameters */
hours = counter_time / 3600U;
sTime->Minutes = (uint8_t)((counter_time % 3600U) / 60U);
sTime->Seconds = (uint8_t)((counter_time % 3600U) % 60U);
if (hours >= 24U)
{
/* Get number of days elapsed from last calculation */
days_elapsed = (hours / 24U);
/* Set Hours in RTC_TimeTypeDef structure*/
sTime->Hours = (hours % 24U);
/* Read Alarm counter in RTC registers */
counter_alarm = RTC_ReadAlarmCounter(hrtc);
/* Calculate remaining time to reach alarm (only if set and not yet expired)*/
if ((counter_alarm != RTC_ALARM_RESETVALUE) && (counter_alarm > counter_time))
{
counter_alarm -= counter_time;
}
else
{
/* In case of counter_alarm < counter_time */
/* Alarm expiration already occurred but alarm not deactivated */
counter_alarm = RTC_ALARM_RESETVALUE;
}
/* Set updated time in decreasing counter by number of days elapsed */
// counter_time -= (days_elapsed * 24U * 3600U); //modify by LeoX
/* Write time counter in RTC registers */
if (RTC_WriteTimeCounter(hrtc, counter_time) != HAL_OK)
{
return HAL_ERROR;
}
/***省略*/
}
HAL_RTC_SetDate
HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
{
/***省略*/
/* Reset time to be aligned on the same day */
/* Read the time counter*/
counter_time = RTC_ReadTimeCounter(hrtc);
/* Fill the structure fields with the read parameters */
hours = counter_time / 3600U;
if (hours > 24U)
{
/* Set updated time in decreasing counter by number of days elapsed */
// counter_time -= ((hours / 24U) * 24U * 3600U);
/* Write time counter in RTC registers */
if (RTC_WriteTimeCounter(hrtc, counter_time) != HAL_OK)
{
/* Set RTC state */
hrtc->State = HAL_RTC_STATE_ERROR;
/* Process Unlocked */
__HAL_UNLOCK(hrtc);
return HAL_ERROR;
}
/***省略*/
}
4) 日期更新的时候读取RTC备份区的日期来计算偏移;在for循环中计算年月日的偏移。
static void RTC_DateUpdate(RTC_HandleTypeDef *hrtc, uint32_t DayElapsed)
{
uint32_t year = 0U, month = 0U, day = 0U;
uint32_t loop = 0U;
#if 0 //modify by LeoX
/* Get the current year*/
year = hrtc->DateToUpdate.Year;
/* Get the current month and day */
month = hrtc->DateToUpdate.Month;
day = hrtc->DateToUpdate.Date;
#else //modify by LeoX
year = HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR2);
month = HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR3);
day = HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR4);
#endif
for (loop = 0U; loop < DayElapsed; loop++)
测试结果:
- 程序下载,初始化默认的时间
- 修改时间
- 带电时间跨天,日期更新
- 下电时间跨天
5) 设置时间后立即反复掉电重启
说明:
1) 以上的时间全部为测试时间,初始化默认时间和STM32CubeMx的对应不上是因为以及做过几轮测试;
2) 这个方法要改到库函数,修改库函数后要备份保存好,否则一旦重新获取HAL库,修改都会被覆盖掉。
3) 仅作参考。4.2的方法有空再实现。
4) 相对来说4.1的改动最小,不用自己去写日期判断函数,去处理时间戳转换。
5) 只有在上电和更新时间的时候会写RTC备份存储区。获取时间的时候只读,所以反复读取对寿命无影响。
6) 参考程序:STM32CubeMX RTC配置 STM32 RTC时钟掉电日期不更新