STM32 RTC时钟掉电日期不更新 & STM32 HAL库RTC时钟配置

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

一、STM32CubeMX RTC配置

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、RTC初始化

由于自动生成的RTC程序会在初始化时将默认时间写入到RTC寄存器中,所以这里采用RTC备份存储区(掉电/重启数据不丢失)来做标志位来存储系统时间是否已经初始化过,已经初始化则不在重新写入时间,否则会将RTC时间复位。(其他系列的也可以按此操作)
程序代码加在 /* USER CODE BEGIN Check_RTC_BKUP */区域内,否则更新STM32CubeMx文件会将修改的代码删掉。

  1. 首先判断标志位 0x55AA,标志位自己定义数值,0x55AA没有特别含义;
  2. 如果是首次烧录运行,判断你是不,往下走写入默认时间和0x55AA标志位,且将日期备份到RTC备份存储区;
  3. 第二次运行,判断成功成功,将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++)

测试结果:

  1. 程序下载,初始化默认的时间
    在这里插入图片描述
  2. 修改时间
    在这里插入图片描述
  3. 带电时间跨天,日期更新
    在这里插入图片描述
  4. 下电时间跨天
    在这里插入图片描述
    5) 设置时间后立即反复掉电重启
    在这里插入图片描述
    在这里插入图片描述
    说明:
    1) 以上的时间全部为测试时间,初始化默认时间和STM32CubeMx的对应不上是因为以及做过几轮测试;
    2) 这个方法要改到库函数,修改库函数后要备份保存好,否则一旦重新获取HAL库,修改都会被覆盖掉。
    3) 仅作参考。4.2的方法有空再实现。
    4) 相对来说4.1的改动最小,不用自己去写日期判断函数,去处理时间戳转换。
    5) 只有在上电和更新时间的时候会写RTC备份存储区。获取时间的时候只读,所以反复读取对寿命无影响。
    6) 参考程序:STM32CubeMX RTC配置 STM32 RTC时钟掉电日期不更新
  • 17
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Geek__1992

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值