Skip to main content
HTD
Senior II
October 17, 2022
Question

The time is: 33:55!

  • October 17, 2022
  • 8 replies
  • 4820 views

I think it's related to my previous question:

https://community.st.com/s/question/0D53W00001sGksoSAC/is-it-possible-rtc-on-stm32h7-loses-1-second-on-each-power-cycle

I applied a workaround by practically removing all MX_RTC_Init().

It seems to work, however today my device showed 33:55 at 10:55. Why? It seems like after 23:59 the clock set to 24:00. To 25:00 one hour later.

Is this behavior related to initialization? I'm tempted to just check the TR for values over 23 and subtract 23 when initializing. It would obviously fix the issue, but it seems a little silly ;)

This topic has been closed for replies.

8 replies

waclawek.jan
Super User
October 17, 2022

Read out and check content of RTC registers.

This is usually symptom of errorneously set 12/24 flag, usually due to using uninitialzed initialization struct in Cube. It's a recurring theme here.

JW

HTD
HTDAuthor
Senior II
October 20, 2022

Yes, it started to occur since I disabled RTC_Init(). But now I have problem - when the RTC_Init() sets the control register (CR), I lose up to one second on each restart.

It's weird, since RTC_Init() doesn't touch the DR register.

Anyway, what I still need now is RTC that keeps time without ANY extra changes like resetting neither the seconds nor hours. Is it possible and how to enable this advanced feature? ;)

Which part of the RTC_Init() resets the fractions of seconds? I looked at the code and I can't figure it out. I see where the time format is set, along with some other bits. It seems like I could just leave the code as it is, but then the fractions of seconds keep being reset.

waclawek.jan
Super User
October 20, 2022

> Yes, it started to occur since I disabled RTC_Init().

That's not "yes", it has nothing to do with RTC_Init(). You are using an unitialized struct (okay maybe I should not call it init struct, but generally any struct used as parameter when calling cube/hal functions) when calling set time function. I gave above a link to text explining that.

> Which part of the RTC_Init() resets the fractions of seconds?

https://community.st.com/s/question/0D53W00001sGksoSAC/is-it-possible-rtc-on-stm32h7-loses-1-second-on-each-power-cycle and the link I gave you there explains the mechanism (using INIT clears the subseconds).

Read the RTC chapter in manual.

JW

HTD
HTDAuthor
Senior II
October 20, 2022

I read it again several times, but I'm not sure if I understood this right this time:

According to the linked article, entering the INIT mode of RTC, that MX_RTC_Init() does just resets the fraction of the seconds indirectly, by resetting the clock divider, right?

The partial solution is just not call MX_RTC_Init() when the time was already set. Right?

However, when I power cycle the device, I get random 23h error. Random means sometimes I power cycle the device and it's OK, sometimes it's not OK. It's always 23h either added or subtracted from the correct time, or zero, sometimes nothing happens with the time.

As you've said: it has nothing to do RTC_Init(), so - a set time function.

The problem with this is HAL_RTC_SetTime() is not called at all on power cycle or reset.

I only call HAL_RTC_GetTime(). I'd suspect it requires the initialized RTC_InitTypeDef struct to work properly. Am I heading in the right direction with this? Or... I reset the time manually from the device's GUI after disabling the init function, so it stored the time in wrong format. It could happen.

If so, the full working solution would be to properly initialize the structure without touching the Instance property of htrc, right?

But the HAL_RTC_Init() doesn't do anything else. It takes init struct already initialized.

It just writes RTC registers, that leads to that fraction of seconds loss.

Here's the relevant main() fragment:

 /** Initialize RTC Only
 */
 hrtc.Instance = RTC;
 hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
 hrtc.Init.AsynchPrediv = 127;
 hrtc.Init.SynchPrediv = 255;
 hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
 hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
 hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
 hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
 if (HAL_RTC_Init(&hrtc) != HAL_OK)
 {
 Error_Handler();
 }

It is executed on every start, so there's no chance the Init structure is uninitialized. All my code that uses the RTC uses HAL functions that refer to the initialized hrtc structure.

The hrtc.Init.HourFormat is always set to RTC_HOURFORMAT_24.

waclawek.jan
Super User
October 20, 2022

> According to the linked article, entering the INIT mode of RTC, that MX_RTC_Init() does just resets the fraction of the seconds indirectly, by resetting the clock divider, right?

Not by resetting the clock divider, but the RTC_SSR register is zeroed when RTC_ISR.INIT is set i.e. the initialization mode is entered (or maybe when it's exit - the effect is the same). I wanted to RTFM you on this, but can't find this information in RM and don't remember where do I get it from - maybe some other RM, maybe some appnote, maybe just experimentation.

> However, when I power cycle the device, I get random 23h error.

Read out and check/post RTC registers content, before and after the power cycle.

> The problem with this is HAL_RTC_SetTime() is not called at all on power cycle or reset.

Whenever you called it, by failing to fill in all fields in struct used to call it you inadvertently set the 12/24 bit in TR. Or maybe it's something else. Read out and check RTC registers content.

In the long run, the best way is to avoid using Cube/HAL entirely, and write your own functions. In that way you don't have to deal with whatever idiosyncracies Cube/HAL throws at you.

JW

HTD
HTDAuthor
Senior II
October 21, 2022

Thanks, I'll make my own RTC_SetTime() (-like) function, setting all registers manually. I assumed HAL function does that, but well, if it did, it would probably work properly then.

waclawek.jan
Super User
October 21, 2022

> I'll make my own RTC_SetTime() (-like) function

I probably messed it up and it's not the "set time" function but maybe the "initialize rtc" function. I don't know and I don't care. This is because I deeply don't care about Cube and its users, just have seen this problem many times here and it was usually consequence of not setting some field in the struct - whether at initialization or time setting.

Cube is not broken per se (mostly, sometimes it is but it's relatively rare), but its usage has certain rules and you have to obey them. And these rules are not written down, they are mostly "enforced" by generating code after clicking in CubeMX and by relying on "intuitive naming" and such.

One such rule in particular is, that structs in Cube calls have to be filled entirely or zeroed explicitly. The fields of structs used in Cube calls are often indiscriminately ORed together and result stored to registers with multiple bits and fields. A field in the struct you don't even have heard of (unless looked up the definition of said struct and studied it meticulously), if uninitialized, may contain a random (not always truly random, but depending on previous run of the program, and this is part of the gotcha) value, which when ORed together with other fields, may result in changing surprising bits/bitfields in the target register.

Or maybe I'm wrong (I often am) and the reason for problems you see is something else. 

This is why the first thing to do, always, when the hardware behaves weirdly, to read out and check/post content of the said hardware registers. That usually reflects the truth, because the hardware does not care about you using Cube, any IDE, compiler, library or whatever, hardware works out of the registers. And yes, that means that you MUST read the manual Cube promised you won't need to, originally.

But again, Cube is not dumb (i.e. its authors are not dumb, even if that's the common assertion when one stumbles upon problems like these). Those functions are written so, that the fields are (mostly) checked at the entry for sanity, but as those checks are runtime and relatively expensive, they are conditional. FULL ASSERT or something, again, I don't use don't care. Would you use that, you would catch your error when it happens.

By writing your own code you are going to fight the intricacies of the hardware, but you can always refer directly to the RM and ask meaningful questions (i.e. other than "slapped Cube on it and it ain't work) here, and once you manage that, you will be freed from the - quite different - intricacies of the intermediate layer Cube presents.

I believe, I've written all this in other words in the texts I linked to. I write those texts exactly in order to spare time not needing to rewrite it again and again, when users here report similar problems again and again. Well, this time this did not work, for whatever reason.

JW

waclawek.jan
Super User
October 21, 2022

https://github.com/STMicroelectronics/STM32CubeH7/blob/4cb7c570fa5f2a362193d713a70b43ab02c68197/Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_rtc.c#L907

 hrtc->Instance->CR |= (uint32_t)(sTime->DayLightSaving | sTime->StoreOperation);

 and the preceding

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

 is what I meant by "uninitialized struct field may cause havoc", and "FULL ASSERT something something".

JW

HTD
HTDAuthor
Senior II
October 21, 2022

Here's my hacked MX_RTC_Init():

static void MX_RTC_Init(void)
{
 /* USER CODE BEGIN RTC_Init 0 */
 
 // THE WORKAROUND FOR A 1 SECOND LOSS ON RESTART STARTS HERE:
 
 // The Init structure initialization code is copied from the generated part, it can't be moved since the Cube can't be made to do so.
 hrtc.Instance = RTC;
 hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
 hrtc.Init.AsynchPrediv = 127;
 hrtc.Init.SynchPrediv = 255;
 hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
 hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
 hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
 hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
 // The copied code ends here. The proper initialization ahead:
 if (hrtc.Instance->DR)
 { // The clock has been set, we skip broken initialization here to avoid resetting fraction of seconds bits.
 if (hrtc.State == HAL_RTC_STATE_RESET) hrtc.Lock = HAL_UNLOCKED;
 hrtc.State = HAL_RTC_STATE_BUSY;
 __HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc);
 hrtc.Instance->CR &= ~(RTC_CR_FMT | RTC_CR_OSEL | RTC_CR_POL); // Clear RTC_CR FMT, OSEL and POL Bits.
 hrtc.Instance->CR |= (hrtc.Init.HourFormat | hrtc.Init.OutPut | hrtc.Init.OutPutPolarity); // Set RTC_CR register.
 HAL_RTC_WaitForSynchro(&hrtc);
 hrtc.State = HAL_RTC_STATE_READY;
 __HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc);
 }
 else
 { // Original initialization otherwise.
 if (HAL_RTC_Init(&hrtc) != HAL_OK)
 {
 Error_Handler();
 }
 }
 return;
 
 // THE WORKAROUND FOR A 1 SECOND LOSS ON RESTART ENDS HERE.
 
 /* USER CODE END RTC_Init 0 */
 
 /* USER CODE BEGIN RTC_Init 1 */
 
 /* USER CODE END RTC_Init 1 */
 
 /** Initialize RTC Only
 */
 hrtc.Instance = RTC;
 hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
 hrtc.Init.AsynchPrediv = 127;
 hrtc.Init.SynchPrediv = 255;
 hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
 hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
 hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
 hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
 if (HAL_RTC_Init(&hrtc) != HAL_OK)
 {
 Error_Handler();
 }
 /* USER CODE BEGIN RTC_Init 2 */
 
 /* USER CODE END RTC_Init 2 */
 
}

It solves the problem entirely. I pinpointed the exact place when the clock broke completely, and after 23:59:59 I got 24:00:00 and still the same date.

The clock behaved that way when I skipped the call to HAL_RTC_WaitForSynchro(). That was it.

However, entering the init mode in line 313 of the file you linked caused the exact problem with losing seconds on restart. When I removed that call but left the HAL_RTC_WaitForSynchro() - the clock works properly after 23:59:59 and doesn't lose seconds anymore. PROBLEM SOLVED.

waclawek.jan
Super User
October 22, 2022

Show me, how do you set time.

JW

HTD
HTDAuthor
Senior II
October 22, 2022
/**
 * @typedef DateTypeDef
 * @struct
 * @brief A simple unpacked universal date structure.
 */
typedef struct
{
 int16_t y; ///< Year number.
 uint8_t m; ///< Month number. 1..12.
 uint8_t d; ///< Day number. 1..31.
} DateTypeDef;
 
/**
 * @typedef TimeTypeDef
 * @struct
 * @brief A precise unpacked local time structure.
 */
typedef struct
{
 uint8_t h; ///< Hour number. 0..23.
 uint8_t m; ///< Minute number. 0..59.
 uint8_t s; ///< Second number. 0..59.
 uint16_t ms; ///< Milliseconds since full second. 0..999.
 uint16_t us; ///< Microseconds since full millisecond. 0..999.
 uint16_t ns; ///< Nanoseconds since full microsecond. 0..999.
} TimeTypeDef;
 
/**
 * @typedef DateTimeTypeDef
 * @struct
 * @brief A simple 10 byte date/time structure containing full date and local time with 1ns precision.
 */
typedef struct
{
 DateTypeDef date;
 TimeTypeDef time;
} DateTimeTypeDef;
 
/**
 * @fn void DateTime2RTC(DateTimeTypeDef*, RTC_DateTypeDef*, RTC_TimeTypeDef)
 * @brief Converts the ISO date/time to RTC date and RTC time.
 * @param dateTime ISO date/time pointer.
 * @param rtcDate RTC date pointer.
 * @param rtcTime RTC time pointer.
 */
void DateTime2RTC(DateTimeTypeDef* dt, RTC_DateTypeDef* rd, RTC_TimeTypeDef* rt)
{
 rd->Year = dt->date.y - 2000;
 rd->Month = dt->date.m;
 rd->Date = dt->date.d;
 rd->WeekDay = dayOfWeek(dt->date.y, dt->date.m, dt->date.d);
 rt->TimeFormat = 0x00u; // the RTC hour format must be set to 24H!
 rt->Hours = dt->time.h;
 rt->Minutes = dt->time.m;
 rt->Seconds = dt->time.s;
 if (rt->SecondFraction != 0)
 {
 if (dt->time.ms != 0)
 rt->SubSeconds = dt->time.ms / (1000.0 / rt->SecondFraction);
 if (dt->time.us != 0)
 rt->SubSeconds += dt->time.us / (1000000.0 / rt->SecondFraction);
 if (dt->time.ns != 0)
 rt->SubSeconds += dt->time.ns / (1000000000.0 / rt->SecondFraction);
 }
}
 
/**
 * @fn HAL_StatusTypeDef RTC_SetDateTime(DateTimeTypeDef*)
 * @brief Sets the current RTC date and time from ISO time.
 * @param dateTime ISO date/time pointer.
 */
HAL_StatusTypeDef RTC_SetDateTime(DateTimeTypeDef* dt)
{
 if (!isValidDateTime(dt)) return HAL_ERROR;
 RTC_DateTypeDef rd;
 RTC_TimeTypeDef rt;
 HAL_StatusTypeDef status;
 HAL_RTC_Init(&hrtc); // Just in case.
 status = HAL_RTC_GetTime(&hrtc, &rt, RTC_FORMAT_BIN);
 if (status != HAL_OK) return status;
 status = HAL_RTC_GetDate(&hrtc, &rd, RTC_FORMAT_BIN);
 if (status != HAL_OK) return status;
 DateTime2RTC(dt, &rd, &rt);
 status = HAL_RTC_SetDate(&hrtc, &rd, RTC_FORMAT_BIN);
 if (status != HAL_OK) return status;
 HAL_RTC_SetTime(&hrtc, &rt, RTC_FORMAT_BIN);
 return status;
}

Here. Without UI, naturally.

waclawek.jan
Super User
October 22, 2022

In RTC_SetDateTime() you define rt as a local variable of RTC_TimeTypeDef type, thus its fields are uninitialized and may contain "random" values (may be depending on previous run of the program).

HAL_RTC_GetTime() sets the SubSeconds, SecondFraction, Hours, Minutes, Seconds and TimeFormat fields, but not DayLightSaving and StoreOperation fields.

Your DateTime2RTC() function changes only Hours, Minutes, Seconds and SubSeconds in rt.

So, at this point, DayLightSaving and StoreOperation fields are uninitialized.

Then you call HAL_RTC_SetTime() which contains this line:

hrtc->Instance->CR |= (uint32_t)(sTime->DayLightSaving | sTime->StoreOperation);

JW

HTD
HTDAuthor
Senior II
November 18, 2022

I revisited the problem. I set all uninitialized fields, however, it didn't solve the problem.

Here's what did work:

HAL_StatusTypeDef RTC_SetDateTime(DateTimeTypeDef* dt)
{
 if (!isValidDateTime(dt)) return HAL_ERROR;
 RTC_DateTypeDef rd;
 RTC_TimeTypeDef rt;
 HAL_StatusTypeDef status;
 DateTime2RTC(dt, &rd, &rt);
 status = HAL_RTC_SetDate(&hrtc, &rd, RTC_FORMAT_BIN);
 if (status != HAL_OK) return status;
 rt.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
 rt.StoreOperation = RTC_STOREOPERATION_SET;
 status = HAL_RTC_Init(&hrtc);
 if (status != HAL_OK) return status;
 status = HAL_RTC_SetTime(&hrtc, &rt, RTC_FORMAT_BIN);
 return status;
}

So - calling full HAL_RTC_Init(). As you remember - I don't call full HAL_RTC_Init() on start, to avoid shaving fractions of seconds. But calling it just before resetting the time is a good idea, because I reset seconds anyway. I tested it several times for cases of time 23:59:59:

  • just waiting a minute observing time,
  • resetting the device,
  • powering the device off

The valid outcome is time after 00:00:00, invalid is greater than 24:00:00.

Everything passed.

Piranha
Principal III
November 18, 2022

With normal code one can just enter initialization mode, set the date and time, exit initialization mode and you are done. With the "helpful" HAL, of course, one has to at least several times more code and setting a date and time as a single operation is not even possible. An example from my code for L43x:

RTC->WPR = 0xCA;
RTC->WPR = 0x53;
 
RTC->ISR = RTC_ISR_FLAGS_ | RTC_ISR_INIT;
while (!(RTC->ISR & RTC_ISR_INITF));
RTC->TR = rTR;
RTC->DR = rDR;
RTC->ISR = RTC_ISR_FLAGS_; // Clear RTC_ISR_INIT
 
RTC->WPR = 0;

Just prepare the data in rTR and rDR variables correctly.