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 10:11 AM
@PHolt.1, I put the proper code in the first response to this post. Did you miss that? Or do you not like it or something? Just want to make sure you saw it, don't mind if you use it or not.
"Eliminating HAL" by copy/pasting the HAL function code into your own misses the point.
2021-08-31 10:54 AM
Apologies TDK. I came to the same conclusion - that reading SSR twice at the beginning was the cause of the problem, but removing that didn't fix the problem.
This form of the loop works fine, but only with the extra DR read at the very end and dumping the result
uint32_t last_subsec;
uint32_t loopcnt=0;
uint32_t tmpreg = 0U;
uint32_t datetmpreg = 0U;
// Implement RTC errata sheet (SSR needs reading before and after date/time and compared, until same)
// There is a max loop count, which is simpler than a timeout.
// It was found experimentally that you get a dud SSR read every ~10k operations here.
do
{
// Get subseconds, and save for later comparison
last_subsec = (uint32_t)(hrtc.Instance->SSR);
// Always read both time and date, in that order
//HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
sTime.SubSeconds = last_subsec; // (uint32_t)(hrtc.Instance->SSR);
// Get SecondFraction structure field from the corresponding register field - not used
//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);
//HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
/* Get the DR register */
datetmpreg = (uint32_t)(hrtc.Instance->DR & RTC_DR_RESERVED_MASK);
loopcnt++;
}
while ( (last_subsec != (uint32_t)(hrtc.Instance->SSR)) && (loopcnt < MAX_SSR_READS) );
if (loopcnt >= MAX_SSR_READS) return 2;
// A dummy DR read to unlock the regs because last read above is the SSR, but the
// last read should be the DR
hrtc.Instance->DR;
2021-09-01 01:59 AM
Can anyone suggest the order of register accesses which deals with the RTC SSR errata issue i.e. reading SSR before and after, while at the same time reading DR as last?
2021-09-01 09:19 AM
EDIT: This example is updated according to the comments in further discussion!
I have an idea...
void RTC_DateTime(void)
{
uint32_t rDR1 = RTC->DR;
uint32_t rTR = RTC->TR;
uint32_t rDR2 = RTC->DR;
uint32_t rDR = (rTR >= 0x130000) ? rDR1 : rDR2;
// Use rDR, rTR variables.
}
#define PREDIV_S 0xFF
void RTC_DateTimeSubsecond(void)
{
uint32_t rTR1 = RTC->TR;
uint32_t rDR1 = RTC->DR;
uint32_t rSSR = RTC->SSR;
uint32_t rTR2 = RTC->TR;
uint32_t rDR2 = RTC->DR;
uint32_t rTR, rDR;
if (rSSR > (PREDIV_S / 2)) {
rTR = rTR2; rDR = rDR2;
} else {
rTR = rTR1; rDR = rDR1;
}
// Use rDR, rTR, rSSR variables.
}
Some comments on the code:
Honestly, I just figured this out in my head and haven't even tested yet. If I am not missing something, this code is simple, small, fast, deterministic and solves everything related to reading RTC date and time registers. Actually the general principle can also be used for reading the Ethernet PTP clock registers, which doesn't even have a lock mechanism, and can be used on reading CNT based timestamps from chained timers.
@Community member , @Community member , @TDK , everyone - can you review this and comment?
2021-09-01 11:18 AM
Nice.
The above method is OK, and as you've said, used for ages with non-atomically-changing counters (e.g. timer + overflow counter in ISR). Devil is in the details, as usually. This is hard to get right.
The time-only example is IMO OK with both settings of BYPSHD.
The real problem is with BYPSHD=0. When rTR1 = RTC->TR; DR is locked, so uint32_t rDR2 = RTC->DR; in fact reads the value locked *before* SSR is read, which is not what you want.
I believe the following modification should work also for BYPSHD=0:
void RTC_DateTimeSubsecond(void)
{
uint32_t rTR1 = RTC->TR;
uint32_t rDR1 = RTC->DR;
uint32_t rSSR = RTC->SSR;
uint32_t rTR2 = RTC->TR;
uint32_t rDR2 = RTC->DR;
uint32_t rTR, rDR;
if (rSSR > (PREDIV_S / 2)) { // SSR is downcounter
rTR = rTR2; rDR = rDR2;
} else {
rTR = rTR1; rDR = rDR1;
}
// Use rDR, rTR, rSSR variables.
}
I may be wrong of course.
JW
2021-09-01 11:39 AM
Interesting. I can sort of see how that might work.
However, what is actually wrong with my code?
The RTC is made up like this
[ async (ripple) counter ] -->> [ sync counter SSR ] -->> [ time counter ] -->> [ date counter ]
That is how all RTCs are designed. I did one in an ASIC. It is done to minimise how much logic gets clocked downstream. You prescale as much as you can with a ripple counter (down to 1Hz ideally, but if you do that, you don't get subseconds).
I read SSR first, store it
Read the time and date
Read SSR again and compare
If the SSR is the same, then the time and date could not have possibly been clocked, could it, because it is clocked from the output of the SSR synchronous counter, and if that produced a clock pulse then its value must have changed.
It is like reading any other chained counter arrangement. If you read the counter and "somehow" ensure that the input to the counter chain has not seen a clock since you started reading it, then the counter chain which you have just read must be self-consistent.
I've done tons of hardware design, including Xilinx FPGA stuff. Of course I may not be properly understanding how the 32F4 RTC works. STM do some funny stuff, with using PCLK1 or 2 to sync some things...
I need to add a timeout into that do/while loop otherwise the whole thing will hang if the 32k oscillator stops running (e.g. the xtal is duff, not soldered, etc). That's easy using CYCCNT.
2021-09-01 12:01 PM
> However, what is actually wrong with my code?
> I read SSR first, store it
> Read the time and date
> Read SSR again and compare
That's OK. if "read the time and date" means reading RTC_TR and RTC_DR in this order, but you need to read DR if BYPSHD=0. I may have mentioned it already.
And, as I've said, make sure there's no interrupt between first and second SSR read lasting exactly 1 second in the SSR granularity (or integer multiples). Using RTOS and Cube does not sound much reassuring in this regard, but it's your code.
> I need to add a timeout into that do/while loop otherwise the whole thing will hang if the 32k oscillator stops running
No, it won't. It will loop only if SSR changes. If XTAL stops XTALling, SSR wil stay the same.
JW
2021-09-01 12:11 PM
Oh yes I didn't think about it =) Thanks!
I do read DR - see code above.
I am very sure there won't be a 1 sec delay. The RTOS is pre-emptive and runs on a 1ms tick.
2021-09-01 12:37 PM
I am absolutely uninterested in RTOS, but last time I checked "pre-emptive" meant "it can interrupt and switch task whenever it wants" (i.e. also in between the two SSR reads). I am not sure what "runs on 1ms tick" means, but surely there are ways to tell the RTOS "this task is important, if you've taken control from it, switch back no later than after 𝗑𝗑�?�� ms", or at least have a promise it will do that.
Remember reading an article titled "Windows NT as a real-time operating system" or something like that, back then.
JW
2021-09-01 04:32 PM
Tried to write up this and related findings. Comments welcome.
JW