cancel
Showing results for 
Search instead for 
Did you mean: 

STM32H7 Real Time Clock: Set Date and Time with HAL "Bypasses" RTC_EnterInitMode function

Garnett.Robert
Senior III

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

3 REPLIES 3
TDK
Guru

> 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.

The RTC is stopped during initialization. Initialization is started in HAL_RTC_SetDate and exited in HAL_RTC_SetTime. As long as you're calling them with a consistent date/time, there's no chance for desync.

The documentation should be improved to note that they must be called in the correct order (first HAL_RTC_SetDate then HAL_RTC_SetTime).

Look at the RTC_ISR_INIT bit to see how this is handled.

 

TDK_0-1704548911248.png

 

If you feel a post has answered your question, please click "Accept as Solution".

Hi TDK,

Thank you for your prompt reply.

I don't understand your following sentence:

"The RTC is stopped during initialization. Initialization is started in HAL_RTC_SetDate and exited in HAL_RTC_SetTime."

Looking at HAL, as far as I can see the clock counter is started and stopped in each of the HAL RTC functions; setData..,

setTime...,

See code below.

If this doesn't happen than setting the time and not setting the calendar when the calendar isn't used will stop the RTC without restarting it.

HAL RTC Set Date

  status = RTC_EnterInitMode(hrtc);  /* Clock stopped here */
  if (status == HAL_OK)
  {
    /* Set the RTC_DR register */
    hrtc->Instance->DR = (uint32_t)(datetmpreg & RTC_DR_RESERVED_MASK);


    /* Exit Initialization mode */
    status = RTC_ExitInitMode(hrtc); /* Clock restarted here */
  }


HAL RTC Set Time:

HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
{
  uint32_t tmpreg;...

 /* Enter Initialization mode */
  status = RTC_EnterInitMode(hrtc);  /* Clock stopped here */
  if (status == HAL_OK)...

    /* 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);

    /* Exit Initialization mode */
    status = RTC_ExitInitMode(hrtc);  /* Clock restarted here */
  }
HAL_StatusTypeDef RTC_EnterInitMode(RTC_HandleTypeDef *hrtc)
{
  uint32_t tickstart;
  HAL_StatusTypeDef status = HAL_OK;
  /* Check if the Initialization mode is set */
#if defined(RTC_ICSR_INITF)
  if((hrtc->Instance->ICSR & RTC_ICSR_INITF) == 0U)
  {
    /* Set the Initialization mode */
    SET_BIT(hrtc->Instance->ICSR, RTC_ICSR_INIT); /* Clock stopped here */ ...


HAL_StatusTypeDef RTC_ExitInitMode(RTC_HandleTypeDef *hrtc)
{
  HAL_StatusTypeDef status = HAL_OK;

  /* Check if the Initialization mode is set */
#if defined(RTC_ICSR_INITF)

  /* Exit Initialization mode */
  CLEAR_BIT(RTC->ICSR, RTC_ICSR_INIT);...  /* Clock started here */

 

I know that the books say (somewhere) that you should set the time THEN the data, but I cannot see why.

The HAL_RTC idea that you should call the functions in the "correct order" not good programming practice when dealing with hardware drivers, especially when there is no mention of this in the HAL driver documentation. (I remember reading it somewhere, but I can't find it now).  I found the problem when I noticed the date time got out of sync on the annual rollover from 2023 to 2024.  I then went back through our databases and found similar desynchs on the 24 hour boundary. Luckily the de-synchs were corrected by routine RTC clock setting, so they didn't accumulate. I worked out what the problem was immediately and did my fix in under an hour. Works fine now.  I was setting the date before the time.  It was easy to fix the date & times in the database as they are in unix epoch milliseconds.

What happens if you are trying to set the clock and a high priority task delays the implementation of the second function.   That could cause stopping the RTC for a long time. milliseconds or more. The sensible way of doing it which doesn't waste programmers time with obtuse programming requirements is to have functions which are optimised to set and get the time and data as a single package. This does two things, it forces the correct order (if required) and reduces housekeeping overheads, function parameter checking etc. All of the standard date formats such as microsoft, Julian data and unix epochs all combine the data and time in a single data entity and that is how HAL should handle them.  Of course not all apps require both data and time the existing functions are fine for those cases. In my case I am taking a a unix time from the network converting it into the date and time fields of the RTC registers and then setting the registers using the HAL functions. 

This is what RM0455 Rev 10 says about clock and calendar initialisation:

"To program the initial time and date calendar values, including the time format and the
prescaler configuration, the following sequence is required:
1. Set INIT bit to 1 in the RTC_ICSR register to enter initialization mode. In this mode, the
calendar counter is stopped and its value can be updated.
2. Poll INITF bit of in the RTC_ICSR register. The initialization phase mode is entered
when INITF is set to 1. It takes around 2 RTCCLK clock cycles (due to clock
synchronization).
3. To generate a 1 Hz clock for the calendar counter, program both the prescaler factors
in RTC_PRER register.
4. Load the initial time and date values in the shadow registers (RTC_TR and RTC_DR
and configure the time format (12 or 24 hours) through the FMT bit in the RTC_CR
register.
5. Exit the initialization mode by clearing the INIT bit. The actual calendar counter value is
then automatically loaded and the counting restarts after 4 RTCCLK clock cycles."

This is very good advice, it's a pity the HAL programmers didn't take it.

I can understand the need for use of the calendar and time on their own, but I would have thought most non-trivial apps would use both, so why not have functions that handle both at the same time. I don't think I have ever used the time or date of the RTC individually.

It took me about thirty minutes to combine the functions into one and I'm not the brightest star in the constellation of embedded programmers.

 

Best regards

Rob

 

 


@TDK wrote:

> 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.

The RTC is stopped during initialization. Initialization is started in HAL_RTC_SetDate and exited in HAL_RTC_SetTime. As long as you're calling them with a consistent date/time, there's no chance for desync.

The documentation should be improved to note that they must be called in the correct order (first HAL_RTC_SetDate then HAL_RTC_SetTime).

Look at the RTC_ISR_INIT bit to see how this is handled.

 

TDK_0-1704548911248.png

 


 

TDK
Guru

Hmm, interesting.

It looks like there has been a change to HAL in regard to this behavior. Here's the function from the STM32H7 HAL version I'm using:

/**
  * @brief  Sets RTC current time.
  * @PAram  hrtc pointer to a RTC_HandleTypeDef structure that contains
  *                the configuration information for RTC.
  * @PAram  sTime Pointer to Time structure
  * @PAram  Format Specifies the format of the entered parameters.
  *          This parameter can be one of the following values:
  *            @arg RTC_FORMAT_BIN: Binary data format
  *            @arg RTC_FORMAT_BCD: BCD data format
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
{
  uint32_t tmpreg = 0U;

 /* Check the parameters */
  assert_param(IS_RTC_FORMAT(Format));
  assert_param(IS_RTC_DAYLIGHT_SAVING(sTime->DayLightSaving));
  assert_param(IS_RTC_STORE_OPERATION(sTime->StoreOperation));

  /* Process Locked */
  __HAL_LOCK(hrtc);

  hrtc->State = HAL_RTC_STATE_BUSY;

  if(Format == RTC_FORMAT_BIN)
  {
    if((hrtc->Instance->CR & RTC_CR_FMT) != (uint32_t)RESET)
    {
      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) << 16U) | \
                        ((uint32_t)RTC_ByteToBcd2(sTime->Minutes) << 8U) | \
                        ((uint32_t)RTC_ByteToBcd2(sTime->Seconds)) | \
                        (((uint32_t)sTime->TimeFormat) << 16U));
  }
  else
  {
    if((hrtc->Instance->CR & RTC_CR_FMT) != (uint32_t)RESET)
    {
      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) << 16U) | \
              ((uint32_t)(sTime->Minutes) << 8U) | \
              ((uint32_t)sTime->Seconds) | \
              ((uint32_t)(sTime->TimeFormat) << 16U));
  }

  /* Disable the write protection for RTC registers */
  __HAL_RTC_WRITEPROTECTION_DISABLE(hrtc);

  /* Set Initialization mode */
  if(RTC_EnterInitMode(hrtc) != HAL_OK)
  {
    /* Enable the write protection for RTC registers */
    __HAL_RTC_WRITEPROTECTION_ENABLE(hrtc);

    /* Set RTC state */
    hrtc->State = HAL_RTC_STATE_ERROR;

    /* Process Unlocked */
    __HAL_UNLOCK(hrtc);

    return HAL_ERROR;
  }
  else
  {
    /* 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_BCK;

    /* Configure the RTC_CR register */
    hrtc->Instance->CR |= (uint32_t)(sTime->DayLightSaving | sTime->StoreOperation);

    /* Exit Initialization mode */
    hrtc->Instance->ISR &= (uint32_t)~RTC_ISR_INIT;

    /* If  CR_BYPSHAD bit = 0, wait for synchro else this check is not needed */
    if((hrtc->Instance->CR & RTC_CR_BYPSHAD) == RESET)
    {
      if(HAL_RTC_WaitForSynchro(hrtc) != HAL_OK)
      {
        /* Enable the write protection for RTC registers */
        __HAL_RTC_WRITEPROTECTION_ENABLE(hrtc);

        hrtc->State = HAL_RTC_STATE_ERROR;

        /* Process Unlocked */
        __HAL_UNLOCK(hrtc);

        return HAL_ERROR;
      }
    }

    /* Enable the write protection for RTC registers */
    __HAL_RTC_WRITEPROTECTION_ENABLE(hrtc);

   hrtc->State = HAL_RTC_STATE_READY;

   __HAL_UNLOCK(hrtc);

   return HAL_OK;
  }
}

 As you can see, it calls RTC_EnterInitMode without calling RTC_ExitInitMode (which is called in HAL_RTC_SetDate).

Looks like it was changed in v1.9.0 (four years ago), since it is now as you described:

https://github.com/STMicroelectronics/stm32h7xx_hal_driver/blob/35582f63f1ab507e9d3e44e8e0db074fdb67752f/Src/stm32h7xx_hal_rtc.c#L885

Definitely a regression IMO. Now you have an unavoidable race condition. Here's the v1.7.0 version without the issue:

https://github.com/STMicroelectronics/stm32h7xx_hal_driver/blob/7d7c53a49dbf527a331697d8833dd8dc0862556c/Src/stm32h7xx_hal_rtc.c

 

Things like this are why I don't update the repo unless there's a specific reason.

If you feel a post has answered your question, please click "Accept as Solution".