2021-08-30 11:26 PM
I am referring to the bug whereby the shadowing of the date/time registers is very occassionally broken if the sync prescaler was read beforehand.
That reading order is exactly what the ST HAL functions do. See below.
HAL_RTC_GetTime reads the SSR and the time.
HAL_RTC_GetDate reads the date.
I have been doing tests and reading the RTC at 5Hz it fails once every few hours; then my code below reads the whole lot again.
int getrtc (struct tm * mytime)
{
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
int rtcret1=0;
int rtcret2=0;
RTC_Lock();
// Implement RTC errata sheet (SSR needs reading before and after date/time and compared, until same)
uint32_t last_subsec;
volatile uint32_t errorcnt=0; // for doing a breakpoint
do
{
last_subsec = (uint32_t)(hrtc.Instance->SSR);
// Always read both, even if 1st returns an error
rtcret1 = HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
rtcret2 = HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
errorcnt++;
}
while (last_subsec != sTime.SubSeconds);
RTC_Unlock();
if ( (rtcret1!=0) || (rtcret2!=0))
return 2;
// Convert the 32F4 RTC API to the K&R standard C clock structure "tm"
// Comments in setrtc above
// No error checking - would be meaningless
mytime->tm_year = sDate.Year+100;
mytime->tm_mon = sDate.Month-1;
mytime->tm_mday = sDate.Date;
mytime->tm_wday = sDate.WeekDay; // assume the RTC maintained the day of week correctly
if (sDate.WeekDay==7) mytime->tm_wday = 0;
mytime->tm_hour = sTime.Hours;
mytime->tm_min = sTime.Minutes;
mytime->tm_sec = sTime.Seconds;
// Load subseconds into a global variable, as microseconds
// There were problems with this when using the SHIFTR method to advance the RTC...
g_SubSeconds = ((RTC_SYNC_PREDIV-sTime.SubSeconds)*1000000L)/(RTC_SYNC_PREDIV+1);
// Calculate day of year from the date, because that doesn't come from the RTC.
mytime->tm_yday = day_of_year(mytime->tm_mday, mytime->tm_mon, mytime->tm_year);
// Return -1 for daylight saving, as per K&R
mytime->tm_isdst = -1;
return 0;
}
2021-08-31 01:30 AM
Looks like you're reading it twice before you read the other registers, then comparing. Note that SSR is the first register read in HAL_RTC_GetTime.
The errata says to read it before and after and compare:
do
{
rtcret1 = HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
rtcret2 = HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
}
while (hrtc.Instance->SSR != sTime.SubSeconds);
2021-08-31 02:01 AM
What is RTC_Lock();/RTC_Unlock?
For low-level work like this I would recommend to avoid Cube/HAL, as the implementation of functions there may change in time, with the potential for surprise. Or, if you want to stick to Cube/HAL, lobby ST to implement the erratum-workaround directly in Cube/HAL and then don't do it yourself.
Besides that, the idea of the workaround is, that most of the time you'll read the timestamp within one subsecond tick. By avoiding the Cube/HAL fluff you increase the chance for that, i.e. decrease probablility of the need to read twice (or more times). Reading only SSR the second time, as the errata suggests, brings also some improvement [EDIT] but if BYPSHD=0, you still need to read RTC->DR to unlock the shadow registers [/EDIT].
You also want to make sure there's no chance for an interrupt lasting 1 second, in which case you'd falsely see the same subsecond value twice.
JW
2021-08-31 02:10 AM
The RTC_Lock is a mutex, so I am not setting the RTC while reading it (in RTOS).
How should I implement this workaround?
Should I use
do
{
last_subsec = (uint32_t)(hrtc.Instance->SSR);
// Always read both, even if 1st returns an error
rtcret1 = HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
rtcret2 = HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
errorcnt++;
}
while (last_subsec != (uint32_t)(hrtc.Instance->SSR));
and then one is reading SSR at the very start, and at the very end, while the function HAL_RTC_GetTime fills in sTime.SubSeconds which "must" be the same value as SSR if the two SSR reads matched. Is that correct?
I have avoided HAL functions in many places, just extracting the code actually required...
There won't be any interrupts lasting 1 second. Well, I hope not!
2021-08-31 04:57 AM
I don't use Cube/HAL, but this should work, even if it contains redundancy. Note, that if you have BYPSHD=0, you'll still need to read RTC_DR as the last thing, to unlock the shadow registers, otherwise the next time you'll get to this routine, the first read to SSR/TR actually reads the old values (I edited my post above accordingly).
JW
2021-08-31 06:49 AM
OK; I have cut out the HAL read time and date, simplified them, and put them inline. Do you think this code is the correct implementation of the RTC SSR errata?
We do have shadowing enabled; that is how every RTC works, otherwise you would always have to read it twice (the whole lot) and compare, and repeat if changed.
Many thanks for any input.
int getrtc (struct tm * mytime)
{
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
// Mutex to prevent concurrent setrtc and getrtc, under RTOS
RTC_Lock();
// Implement RTC errata sheet (SSR needs reading before and after date/time and compared, until same)
// TODO implement a timeout here, otherwise it will hang if the xtal osc is not running, etc.
uint32_t last_subsec;
volatile uint32_t errorcnt=0; // can do a breakpoint on this
do
{
last_subsec = (uint32_t)(hrtc.Instance->SSR);
// Always read both time and date, in that order
//HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
uint32_t tmpreg = 0U;
/* 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)) >> 16U);
sTime.Minutes = (uint8_t)((tmpreg & (RTC_TR_MNT | RTC_TR_MNU)) >> 8U);
sTime.Seconds = (uint8_t)(tmpreg & (RTC_TR_ST | RTC_TR_SU));
sTime.TimeFormat = (uint8_t)((tmpreg & (RTC_TR_PM)) >> 16U);
/* 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);
//HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
uint32_t datetmpreg = 0U;
/* 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)) >> 16U);
sDate.Month = (uint8_t)((datetmpreg & (RTC_DR_MT | RTC_DR_MU)) >> 8U);
sDate.Date = (uint8_t)(datetmpreg & (RTC_DR_DT | RTC_DR_DU));
sDate.WeekDay = (uint8_t)((datetmpreg & (RTC_DR_WDU)) >> 13U);
/* 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);
errorcnt++;
}
while (last_subsec != (uint32_t)(hrtc.Instance->SSR));
RTC_Unlock();
// Convert the 32F4 RTC API to the K&R standard C clock structure "tm"
// Comments in setrtc above
// No error checking - would be meaningless
mytime->tm_year = sDate.Year+100;
mytime->tm_mon = sDate.Month-1;
mytime->tm_mday = sDate.Date;
mytime->tm_wday = sDate.WeekDay; // assume the RTC maintained the day of week correctly
if (sDate.WeekDay==7) mytime->tm_wday = 0;
mytime->tm_hour = sTime.Hours;
mytime->tm_min = sTime.Minutes;
mytime->tm_sec = sTime.Seconds;
// Load subseconds into a global variable, as microseconds
// There were problems with this when using the SHIFTR method to advance the RTC...
g_SubSeconds = ((RTC_SYNC_PREDIV-sTime.SubSeconds)*1000000L)/(RTC_SYNC_PREDIV+1);
// Calculate day of year from the date, because that doesn't come from the RTC.
mytime->tm_yday = day_of_year(mytime->tm_mday, mytime->tm_mon, mytime->tm_year);
// Return -1 for daylight saving, as per K&R
mytime->tm_isdst = -1;
return 0;
}
Unfortunately the above code no longer works. The date/time increment but the seconds are stuck for a few seconds at a time and then jump by a few seconds.
It looks like a timing dependence...
But if I take out that while loop which checks if SSR has changed, it returns the right time.
2021-08-31 07:42 AM
I'm not going to review your code.
Do you read RTC_DR as the last thing, as I've said above?
JW
2021-08-31 07:50 AM
Yes I do.
SSR
time
date
SSR
compare the two SSRs
But I have spotted something which I am about to test out. At the start I am reading SSR twice. 1st time to load last_subsec, and 2nd time soon afterwards.
EDIT: NO that wasn't it. Somehow the 2nd SSR read (in the while() statement) is screwing up the seconds, causing them to stick for 4-5 secs at a time.
2021-08-31 08:07 AM
In your sequence above, SSR is the last one, not DR.
JW
2021-08-31 08:12 AM
OK. But then how can one implement the errata, which asks for SSR to be read before and after?
I will try putting in a final
hrtc.Instance->DR
after the while() loop, just to do a dummy DR read.
My understanding is that one needs to read SSR before and after TR/DR and, if different, read the whole lot again, and if same, then everything should be mutually consistent.
EDIT: putting
hrtc.Instance->DR
at the end does fix the issue.
I wonder how many people have never realised that, using the ST libs, they get a duff subsecond value, roughly once an hour (reading the RTC at 5Hz in my tests).