STM32H7 Real Time Clock: Set Date and Time with HAL "Bypasses" RTC_EnterInitMode function
Hi,
I have a project that automatically sets the MCU's RTC from the network time on a routine basis using HAL_RTC_SetDate(...)
and
HAL_RTC_SetTime(...)
I realised this was potentially hazardous as rollover of the time or the data could occur between these two function as a consequence of high priority interrupts and their high priority tasks interrupting the time and data setting functions which Mr Murphy ensured they did. In fact because these two functions take many usecs to complete, desynch of the time and date could still occur without external influences.
Setting the time and data using two separate HAL functions is flawed as each function restarts the RTC internal timer at the end of each function. There is no HAL function that sets both the time and date in a single step with the RTC internal counter stopped. Stopping the counter prevents the data part and calendar part from getting out of step. The same problem can occur with reading as there is no HAL function that gets both the time and date within a short time with interrupts turned off to prevent corruption of the time/data sync.
Using HAL to set the time and date takes around 112 us when using both the data and time functions which provides plenty of opportunity for loss of time/data sync especially if there are other processes that can extend this out.
To overcome the problem I have written two new functions one for setting and one for getting the time and date. Each has a critical section to prevent interrupts raining on the parade.
Here they are:
HAL_StatusTypeDef SetDateAndTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime,
RTC_DateTypeDef *sDate, uint32_t Format)
{
volatile uint32_t tmpreg;
volatile uint32_t datetmpreg;
volatile HAL_StatusTypeDef status;
/* Process Locked */
__HAL_LOCK(hrtc);
hrtc->State = HAL_RTC_STATE_BUSY;
/* Disable the write protection for RTC registers */
__HAL_RTC_WRITEPROTECTION_DISABLE(hrtc);
/* Enter Initialization mode */
status = RTC_EnterInitMode(hrtc);
if (status == HAL_OK)
{
if(Format == RTC_FORMAT_BIN)
{
if((hrtc->Instance->CR & RTC_CR_FMT) != 0U)
{
assert_param(IS_RTC_HOUR12(sTime->Hours));
assert_param(IS_RTC_HOURFORMAT12(sTime->TimeFormat));
}
else
{
sTime->TimeFormat = 0x00U;
assert_param(IS_RTC_HOUR24(sTime->Hours));
}
assert_param(IS_RTC_MINUTES(sTime->Minutes));
assert_param(IS_RTC_SECONDS(sTime->Seconds));
tmpreg = (uint32_t)(((uint32_t)RTC_ByteToBcd2(sTime->Hours) << RTC_TR_HU_Pos) | \
((uint32_t)RTC_ByteToBcd2(sTime->Minutes) << RTC_TR_MNU_Pos) | \
((uint32_t)RTC_ByteToBcd2(sTime->Seconds) << RTC_TR_SU_Pos) | \
(((uint32_t)sTime->TimeFormat) << RTC_TR_PM_Pos));
}
else
{
if((hrtc->Instance->CR & RTC_CR_FMT) != 0U)
{
assert_param(IS_RTC_HOUR12(RTC_Bcd2ToByte(sTime->Hours)));
assert_param(IS_RTC_HOURFORMAT12(sTime->TimeFormat));
}
else
{
sTime->TimeFormat = 0x00U;
assert_param(IS_RTC_HOUR24(RTC_Bcd2ToByte(sTime->Hours)));
}
assert_param(IS_RTC_MINUTES(RTC_Bcd2ToByte(sTime->Minutes)));
assert_param(IS_RTC_SECONDS(RTC_Bcd2ToByte(sTime->Seconds)));
tmpreg = (((uint32_t)(sTime->Hours) << RTC_TR_HU_Pos) | \
((uint32_t)(sTime->Minutes) << RTC_TR_MNU_Pos) | \
((uint32_t)(sTime->Seconds) << RTC_TR_SU_Pos) | \
((uint32_t)(sTime->TimeFormat) << RTC_TR_PM_Pos));
}
/* Construct the Date */
if((Format == RTC_FORMAT_BIN) && ((sDate->Month & 0x10U) == 0x10U))
{
sDate->Month = (uint8_t)((sDate->Month & (uint8_t)~(0x10U)) + (uint8_t)0x0AU);
}
assert_param(IS_RTC_WEEKDAY(sDate->WeekDay));
if(Format == RTC_FORMAT_BIN)
{
assert_param(IS_RTC_YEAR(sDate->Year));
assert_param(IS_RTC_MONTH(sDate->Month));
assert_param(IS_RTC_DATE(sDate->Date));
datetmpreg = (((uint32_t)RTC_ByteToBcd2(sDate->Year) << RTC_DR_YU_Pos) | \
((uint32_t)RTC_ByteToBcd2(sDate->Month) << RTC_DR_MU_Pos) | \
((uint32_t)RTC_ByteToBcd2(sDate->Date) << RTC_DR_DU_Pos) | \
((uint32_t)sDate->WeekDay << RTC_DR_WDU_Pos));
}
else
{
assert_param(IS_RTC_YEAR(RTC_Bcd2ToByte(sDate->Year)));
assert_param(IS_RTC_MONTH(RTC_Bcd2ToByte(sDate->Month)));
assert_param(IS_RTC_DATE(RTC_Bcd2ToByte(sDate->Date)));
datetmpreg = ((((uint32_t)sDate->Year) << RTC_DR_YU_Pos) | \
(((uint32_t)sDate->Month) << RTC_DR_MU_Pos) | \
(((uint32_t)sDate->Date) << RTC_DR_DU_Pos) | \
(((uint32_t)sDate->WeekDay) << RTC_DR_WDU_Pos));
}
taskENTER_CRITICAL();
/* Set the RTC_TR register */
hrtc->Instance->TR = (uint32_t)(tmpreg & RTC_TR_RESERVED_MASK);
/* Clear the bits to be configured */
hrtc->Instance->CR &= ((uint32_t)~RTC_CR_BKP);
/* Configure the RTC_CR register */
hrtc->Instance->CR |= (uint32_t)(sTime->DayLightSaving | sTime->StoreOperation);
/* Set Date */
/* Set the RTC_DR register */
hrtc->Instance->DR = (uint32_t)(datetmpreg & RTC_DR_RESERVED_MASK);
}
taskEXIT_CRITICAL();
/* Exit Initialization mode */
status = RTC_ExitInitMode(hrtc);
/* Enable the write protection for RTC registers */
__HAL_RTC_WRITEPROTECTION_ENABLE(hrtc);
if (status == HAL_OK)
{
hrtc->State = HAL_RTC_STATE_READY;
}
/* Process Unlocked */
__HAL_UNLOCK(hrtc);
return status;
}
/******************************************************************************************************/
HAL_StatusTypeDef GetDateAndTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime,
RTC_DateTypeDef *sDate, uint32_t Format)
{
uint32_t tmpreg;
uint32_t datetmpreg;
/* Check the parameters */
assert_param(IS_RTC_FORMAT(Format));
taskENTER_CRITICAL();
/* Get subseconds structure field from the corresponding register*/
sTime->SubSeconds = (uint32_t)(hrtc->Instance->SSR);
/* Get SecondFraction structure field from the corresponding register field*/
sTime->SecondFraction = (uint32_t)(hrtc->Instance->PRER & RTC_PRER_PREDIV_S);
/* Get the TR register */
tmpreg = (uint32_t)(hrtc->Instance->TR & RTC_TR_RESERVED_MASK);
/* Fill the structure fields with the read parameters */
sTime->Hours = (uint8_t)((tmpreg & (RTC_TR_HT | RTC_TR_HU)) >> RTC_TR_HU_Pos);
sTime->Minutes = (uint8_t)((tmpreg & (RTC_TR_MNT | RTC_TR_MNU)) >> RTC_TR_MNU_Pos);
sTime->Seconds = (uint8_t)((tmpreg & (RTC_TR_ST | RTC_TR_SU)) >> RTC_TR_SU_Pos);
sTime->TimeFormat = (uint8_t)((tmpreg & (RTC_TR_PM)) >> RTC_TR_PM_Pos);
/* Get the DR register */
datetmpreg = (uint32_t)(hrtc->Instance->DR & RTC_DR_RESERVED_MASK);
/* Fill the structure fields with the read parameters */
sDate->Year = (uint8_t)((datetmpreg & (RTC_DR_YT | RTC_DR_YU)) >> RTC_DR_YU_Pos);
sDate->Month = (uint8_t)((datetmpreg & (RTC_DR_MT | RTC_DR_MU)) >> RTC_DR_MU_Pos);
sDate->Date = (uint8_t)((datetmpreg & (RTC_DR_DT | RTC_DR_DU)) >> RTC_DR_DU_Pos);
sDate->WeekDay = (uint8_t)((datetmpreg & (RTC_DR_WDU)) >> RTC_DR_WDU_Pos);
taskEXIT_CRITICAL();
/* Check the input parameters format */
if(Format == RTC_FORMAT_BIN)
{
/* Convert the time structure parameters to Binary format */
sTime->Hours = (uint8_t)RTC_Bcd2ToByte(sTime->Hours);
sTime->Minutes = (uint8_t)RTC_Bcd2ToByte(sTime->Minutes);
sTime->Seconds = (uint8_t)RTC_Bcd2ToByte(sTime->Seconds);
/* Convert the date structure parameters to Binary format */
sDate->Year = (uint8_t)RTC_Bcd2ToByte(sDate->Year);
sDate->Month = (uint8_t)RTC_Bcd2ToByte(sDate->Month);
sDate->Date = (uint8_t)RTC_Bcd2ToByte(sDate->Date);
}
return HAL_OK;
}
Both of these functions were lifted from the HAL Library so there is plenty of room for optimization for those so inclined.
The critical sections turn off interrupts for less than a micro-second for a H7 running at 240 MHz which is better than using two HALs and bracketing them with an interrupt disable period of 132 micro-seconds.
I have used freeRTOS critical macros as I am a bit if a freeRTOS fiend, but you could use asm disable interrupts or if you don't have troublesome interrupts you could leave them out.
I hope someone finds them useful.
I do understand that it is only us masochists who insist on using HAL drivers that will have this problem, but like freeRTOS I like the HAL drivers because register bashing and bit shifting is something I had to do forty five years ago and somehow the novelty wore off.
Lets hope that the next edition of the HAL drivers provides for robust time and date setting & getting in single unified functions. We aren't talking neuroscience here.
Best regards
Rob
