The RTC in STM32 (except the 'F1 family) maintains time in three separate registers - date in RTC_DR, time in RTC_TR and subseconds (i.e. the current state of synchronous prescaler) in RTC_SSR. These registers are in the RTC's clock domain, which is typically asynchronous to clock of APB bus through which processor reads these registers, so reading them involves internal synchronization, which implies 2 APB-cycle wait states when reading them1.
Other problem with separate timekeeping registers is, that reading them one by one may result in inconsistent set of data (as an incrementing pulse may arrive out of the asynchronous prescaler during that reading sequence).
In the STM32 RTC, both problems are addressed using a set of shadow registers in the APB clock domain (i.e. reading them does not impose the waitstates), into which the timekeeping registers' content is automatically copied by hardware every 2 RTC clocks2. Reading RTC_SSR, RTC_TR and RTC_DR then reads these shadow registers.
To provide consistent set of timekeeping registers, this copy process is stopped (shadow registers are "locked") as soon as RTC_SSR or RTC_TR is read; and the copy process is resumed (shadow registers are "unlocked") when RTC_DR is read. But this description is not entirely true: instead of stopping the copy process altogether, it is stopped selectively for individual registers - when RTC_SSR is read, RTC_TR and RTC_DR are locked (but RTC_SSR is never locked itself); when RTC_TR is read, only RTC_DR is locked. Or, as the RM puts it:
To ensure consistency between the 3 values, reading either RTC_SSR or RTC_TR locks the values in the higher-order calendar shadow registers until RTC_DR is read.
This behaviour can be easily checked in debugger (such that enables reading individual registers, without reading other RTC registers, e.g. gdb): reading RTC_SSR always returns a changing value. After RTC_SSR was read, reading RTC_TR returns the same value until RTC_DR is read, After that, repeated reading RTC_TR will return a changing value. After reading RTC_SSR or RTC_TR, and waiting long enough, first read of RTC_DR will return the old value which has been locked at the reading of RTC_SSR or RTC_TR, subsequent read will return changing (current) value 3.
Unfortunately, this locking mechanism is faulty, as witnessed by the erratum RTC calendar registers are not locked properly (for parts introduced before cca 2016). According to this erratum:
When reading the calendar registers with BYPSHAD = 0, the RTC_TR and RTC_DR registers may not be locked after reading the RTC_SSR register. This happens if the read operation is initiated one APB clock period before the shadow registers are updated.
As workaround4, erratum recommends to read RTC_SSR second time after reading out all three registers, and repeat if the two RTC_SSR reads don't match. This is basically the same procedure as it would be for BYPSHAD=1 (i.e. as if there would be no shadow registers). The basic idea is, that if the two RTC_SSR reads return identical value, there was no decrement5 to it, thus RTC_DR and RTC_TR could not have changed either. This of course assumes the two RTC_SSR reads are not more than one second (taking into account RTC_SSR decrement period) apart, i.e. there must be no lengthy interrupt in between, but that's a reasonable requirement for normal microcontroller applications.
However, the erratum does not mention that it is necessary to read RTC_DR after the second RTC_SSR read, otherwise RTC_TR and RTC_DR would remain locked, and upon the next read of the timekeeping registers, old values would be read from these registers.
The following snippet for STM32F091 tests the erratum's assertion, that RTC_TR may remain not locked after reading RTC_SSR. After RTC_SSR read, RTC_TR is read, then follows a delay long enough to ensure the "true" RTC_TR to be incremented, and then RTC_TR is read again to find out, whether it has changed due to failure of the lock:
A "random" wait (together with mutually asynchronous system/RTC clock sources) should ensure that the "one APB clock period before the shadow registers are updated" condition of erratum is fulfilled. One iteration lasts roughly 500us, and assuming enough "randomness" the probability of fulfilling above condition is given by the ratio of shadow registers update (8MHz/32/2) and APB clock (8MHz) i.e. 1:64, so we should see the program to end up in the breakpoint pretty soon.
However, the program did not stop at the breakpoint even when run for several minutes. So what gives?
I believe that the description in the erratum is not precise. In the above program, the main loop has been changed for a simple:
and when breakpoint was reached (i.e. RTC_TR changed), ssr2 was observed. It was found, that while in most cases ssr2 = 7 - which is the expected value, equal to the synchronous prescaler, given RTC_TR has just changed and the program is simple enough to avoid any delays to be introduced - sometimes ssr = 0 was seen.
This means, that under the specific timing condition, content of RTC_SSR was read before decrement (and rollover), while RTC_TR was locked after RTC_SSR decrement => RTC_TR increment, in the updated state.
The same program (only modified for register name changes) was run also on a STM32G0B1, with breakpoint subject to ssr2 != 7, and there was no occurence of this during several minutes of run, confirming that the RTC in 'G0 has this particular problem fixed.
Regardless of the exact mechanism of the lock problem, the workaround described in the erratum is valid.
1. Accessing all RTC registers except the three shadow registers impose the same 2 APB-cycle wait states, see RTC register access subchapter in RTC chapter of RM. In other words, all RTC registers are located at the backup clock domain. In some applications, this waitstate can have unwanted consequences in blocking the APB bus with this access, potentially resulting in late handling (and thus over/underflow) of other peripherals on the same APB bus.
2. For this copy process to work properly, APB bus frequency has to be 7x the RTC clock frequency. There is a method to monitor this copying process: the RTC_ISR.RSF bit is set by hardware when the copy is performed, so this bit can be cleared by software and then polled. There is also a method to avoid using the shadow registers altogether, by setting RTC_CR.BYPSHAD.
3. As RTC_DR changes only "once a day", this is normally cumbersome to observe. For experiments, RTC can be clocked from a high-speed oscillator (LSE in bypass, or using the HSE/32 option) and the prescaler can be set to a minimal value. The experimental setup described in this article results in RTC_DR being incremented roughly once in 12 seconds.
4. By the end of this discussion (which inspired this article), user with nick Piranha proposes an alternative method for consistent reading of the timekeeping registers, which should work consistently, regardless of the erratum or BYPSHAD setting.
5. RTC_SSR is downcounter.