提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
STM32 RTC时钟掉电日期不更新 & STM32 HAL库RTC时钟配置
- 一、STM32CubeMX RTC配置
- 二、RTC初始化
- 三、RTC日期掉电不更新(F1...这里暂时只考虑F103,其他系列未测试,对比的也只考虑F4)
- 四、解决办法
- 4.1、上电对时
- 4.2、将时间和日期都换算存入到CNT寄存器中(存入时间戳)
- 4.3、设置时间时将日期同时设置,且保存到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 1DateToUpdate.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();}#endifreturn;}/* 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 LeoXyear = HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR2);month = HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR3);day = HAL_RTCEx_BKUPRead(hrtc, RTC_BKP_DR4);
#endiffor (loop = 0U; loop < DayElapsed; loop++)
测试结果:
- 程序下载,初始化默认的时间
- 修改时间
- 带电时间跨天,日期更新
- 下电时间跨天
5) 设置时间后立即反复掉电重启
说明:
1) 以上的时间全部为测试时间,初始化默认时间和STM32CubeMx的对应不上是因为以及做过几轮测试;
2) 这个方法要改到库函数,修改库函数后要备份保存好,否则一旦重新获取HAL库,修改都会被覆盖掉。
3) 仅作参考。4.2的方法有空再实现。
4) 相对来说4.1的改动最小,不用自己去写日期判断函数,去处理时间戳转换。
5) 只有在上电和更新时间的时候会写RTC备份存储区。获取时间的时候只读,所以反复读取对寿命无影响。
6) 参考程序:STM32CubeMX RTC配置 STM32 RTC时钟掉电日期不更新