STM 32F4 subseconds - how can it work? - Page 1
Forum section: electronics / general

author: peter-h
author_rank: Super Contributor
timestamp: August 27, 2021, 08:39:23 pm
content: The HAL function HAL_RTC_GetTime returns a structure which includes the value of a prescaler, and a value of a reload constant. The default seems to be a prescaler which countsdownfrom 1023 to 0.I can see how this can be used, but it is obviously nonsense unless this prescaler is zeroed whenever the time is set! But I see no code attempting to do that. Only to read it. HAL_RTC_SetTime doesn't touch it.The only way I can see this scheme working is this:Set the clock with e.g. HAL_RTC_SetTime().Immediately read the prescaler and store the value.Then whenever HAL_RTC_GetTime() is called, you subtract (mod 1024) the stored value from whatever the subsecond figure you got from that.What am I missing?I see stuff about RTC calibration, but I am not using that (don't need to do xtal trimming; well not right now, and anyway there is the cheapo xtal tempco).I also see stuff about using the prescaler (they call it "subseconds") as an RTC calibration feature, but that will give you only +/- 512 i.e. roughly 500ms either way.Ifloadingthe seconds value zeroed the prescaler, that would also make sense, but the RM is silent on that.FYI I am setting the RTC from GPS time which arrives at 5Hz, with time delivered with 200ms increments, and I think (have no choice really) that if I get an NMEA packet, at 38400 baud, then the actual time will be the time delivered in the packet, delayed only by roughly the number of byte periods involved before the packet is decoded (which can't be done until the checksum has arrived, and you can't load the RTC speculatively like one does in some industrial protocols e.g. Modbus, plus the GPS receiver computation time... but that's not relevant to the Q, and is likely to be fairly constant anyway, plus or minus the NMEA packet length variation. I can see some people are trying to do clever stuff with extracting time from GPS, and a high baud rate, say 1mbps, would be the simplest thing to do.

author: thm_w
author_rank: Super Contributor
timestamp: August 27, 2021, 10:02:21 pm
content: Code:HAL_RTC_GetTimeFunction Name HAL_StatusTypeDef HAL_RTC_GetTime (RTC_HandleTypeDef * hrtc, RTC_TimeTypeDef * sTime, uint32_t Format)Function Description Gets RTC current time.Parametershrtc: pointer to a RTC_HandleTypeDef structure that contains the configuration information for RTC.sTime: Pointer to Time structureFormat: Specifies the format of the entered parameters. This parameter can be one of the following values:RTC_FORMAT_BIN: Binary dataformatRTC_FORMAT_BCD: BCD data formatReturn valuesHAL statusNotesYou must call HAL_RTC_GetDate() after HAL_RTC_GetTime() to unlock the values in the higher-order calendar shadow registers to ensure consistency between the time and date values. Reading RTC current time locks the values in calendar shadow registers until Current date is read.Code:typedef struct{uint8_t Hours; /*!< Specifies the RTC Time Hour.This parameter must be a number between Min_Data = 0 and Max_Data = 12 if the RTC_HourFormat_12 is selected.This parameter must be a number between Min_Data = 0 and Max_Data = 23 if the RTC_HourFormat_24 is selected */uint8_t Minutes; /*!< Specifies the RTC Time Minutes.This parameter must be a number between Min_Data = 0 and Max_Data = 59 */uint8_t Seconds; /*!< Specifies the RTC Time Seconds.This parameter must be a number between Min_Data = 0 and Max_Data = 59 */uint8_t TimeFormat; /*!< Specifies the RTC AM/PM Time.This parameter can be a value of @ref RTC_AM_PM_Definitions */uint32_t SubSeconds; /*!< Specifies the RTC_SSR RTC Sub Second register content.This parameter corresponds to a time unit range between [0-1] Secondwith [1 Sec / SecondFraction +1] granularity */uint32_t SecondFraction; /*!< Specifies the range or granularity of Sub Second register contentcorresponding to Synchronous pre-scaler factor value (PREDIV_S)This parameter corresponds to a time unit range between [0-1] Secondwith [1 Sec / SecondFraction +1] granularity.This field will be used only by HAL_RTC_GetTime function */uint32_t DayLightSaving; /*!< Specifies DayLight Save Operation.This parameter can be a value of @ref RTC_DayLightSaving_Definitions */uint32_t StoreOperation; /*!< Specifies RTC_StoreOperation value to be written in the BCK bitin CR register to store the operation.This parameter can be a value of @ref RTC_StoreOperation_Definitions */}RTC_TimeTypeDef;https://www.disca.upv.es/aperles/arm_cortex_m3/llibre/st/STM32F439xx_User_Manual/stm32f4xx__hal__rtc_8h_source.htmlhttps://www.st.com/resource/en/application_note/dm00226326-using-the-hardware-realtime-clock-rtc-and-the-tamper-management-unit-tamp-with-stm32-microcontrollers-stmicroelectronics.pdfQuote

author: peter-h
author_rank: Super Contributor
timestamp: August 28, 2021, 05:21:39 am
content: I read that material originally but I still don't get how one is expected to use the subseconds. It comes back to the prescaler not being programmable:Perhaps one could reset the RTC when setting the time? There are different ways of potentially doing that but it seems a right hack... it isn't intended to be easily reset (zeroed), for obvious reasons.I suspect that the intended way to implement the subseconds is like I said earlier: maintain a global value somewhere which is initialised by reading the RTC prescaler immediately whenever the RTC is set.In the init I haveCode:hrtc.Init.AsynchPrediv = RTC_ASYNC_PREDIV;hrtc.Init.SynchPrediv = RTC_SYNC_PREDIV;which are 31 and 1023 which gives 32768 which is right for a 32768Hz xtal.ThesyncPREDIV is what is read back in "subseconds".As to why PREDIV_S = 255 for 32.768kHz: you can use other values, if the PREDIV_A is set accordingly (the two must multiply to 32768). And the link you posted has 1023 in there too, as has "half the internet", and almost everything from ST, for 32F4However, it looks like PREDIV_A a) sets the power consumption, and b) determines the PREDIV_S value which is what "subseconds" come from, and this table implements thatso there is a tradeoff between having a low power consumption and having the prescaler delivering a higher resolution. An async (ripple) counter draws less power than a sync counter. Once upon a time I did an asic design which implemented an RTC using a long 64 bit ripple counter... However, I can't find any data on the extent to which this matters.FWIW, I changed it to 127 and 255 because 1/256 of a second (~4ms) is plenty good enough for subseconds.Still can't see how this subseconds thing is supposed to be used, if you can't directly set it.

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 28, 2021, 07:55:17 am
content: The reference manual describes the INIT bit of the RTC_ISR register asQuoteTo me, this means that whenever you program the RTC date or time (by switching to the initialization mode), the synchronous and asynchronous prescale counters start at the reset value. In other words, that whenever you switch from initialization mode to free running mode, the prescale counters restart from the maximum value.So, whenever you set the RTC date and/or time using the calendar initialization and configuration procedure described in both the reference manual (page 804 of 1751) and the RTC appnote, you always (but implicitly!) reset the subsecond field to zero.I know I really should verify this, considering I do have an STM32F4-Discovery board, but perhaps you could check this for us instead, considering you're working on it already?All you need to do is create a simple firmware that sets the RTC clock to some different values, clearing the RSF bit in the RTC_ISR register also; then waits until the RSF bit in the RTC_ISR register is set (and thus the RTC shadow registers updated – this takes a couple of RTC clock cycles, or about 61µs –, and then reads the RTC_SSR. It should be very close to the prescale init value (maximum); not necessarily the maximum value due to everything, but not much off.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: August 28, 2021, 08:07:47 am
content: Looks like you can adjust the SS by subtracting from it. Check RTC_SHIFTR register description (SUBFS field) and section "26.3.8 RTC synchronization" of the RM.

author: peter-h
author_rank: Super Contributor
timestamp: August 28, 2021, 08:03:31 pm
content: I am going to declare an array of say 2000 x uint32_t, and starting when the RTC is set from the GPS (once a minute, secs=0, ms=0) I will stuff this array at 1ms intervals, from reads of PREDIV_S.Then I can take a look at what was in it.A pity I can't easily measure the RTC current for different values of these divisors but based on experience from many years ago, most of the Icc is from the prescaler, to get 1Hz. I have a 0.22F supercap feeding it and could measure the voltage drop on that, every hour...

author: peter-h
author_rank: Super Contributor
timestamp: August 29, 2021, 09:40:05 am
content: OK you guys were right! The RTC resets the synchronous prescaler to its initialisation value, when you set the time.For a 32768Hz xtal, this will be typically 1023 or 255 according to which bit of the internet you lifted the code fromThese are the two configs I am testing withCode:// Sync and async pre-division for clock// These give subseconds resolution of 1/1024 sec//#define RTC_ASYNC_PREDIV 0x1F//#define RTC_SYNC_PREDIV 0x3FF// These give a lower RTC power consumption// These give subseconds resolution of 1/256 sec#define RTC_ASYNC_PREDIV 127#define RTC_SYNC_PREDIV 255My date/time func just returns the standard tm structure so I am loading the subseconds into a separate global variableCode:/** Read KDE real time clock.* Just call it. It fills in the structure 'mytime'. This is the standard C library structure tm*inttm_sec;//seconds after the minute (0..59)inttm_min;//minutes after the hour (0..59)inttm_hour;//hours since midnight (0..23)inttm_mday;//day of the month (1..31)inttm_mon;//months since January (0..11)inttm_year;//years since 1900 (100..199)inttm_wday;//days since Sunday (0..6)inttm_yday;//days since Jan 1 (0..365) calculated from above datainttm_isdst;//Daylight Saving Time flag (0..1) unimplemented, set to -1* Returns* 0 if no errors* 1 if RTC not present (not implemented; the RTC is on the CPU chip, but the battery/supercap may not be there)* 2 if the HAL code returns an error (should never happen)** TODO Code 1 is never going to happen because we have no way to detect the lack of the supercap.* Code 1 could be hard coded on systems where the RTC cannot be used for some reason (supercap not fitted AND* no provision to set the RTC).** Other comments as per setrtc above.* You must call HAL_RTC_GetDate() after HAL_RTC_GetTime() to unlock the values in the higher-order calendar* shadow registers to ensure consistency between the time and date values. Reading RTC current time locks* the values in calendar shadow registers until current date is read. So always read BOTH and in that order.** Also returns the subseconds counter in a global variable g_SubSeconds. This is related to the config* in KDE_rtc_init() and the two values RTC_ASYNC_PREDIV and RTC_SYNC_PREDIV which are #defined at the* top of this file.**/int getrtc (struct tm * mytime){RTC_TimeTypeDef sTime = {0};RTC_DateTypeDef sDate = {0};int rtcret1=0;int rtcret2=0;RTC_Lock();// Always read both, even if 1st returns an errorrtcret1 = HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);rtcret2 = HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);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 meaninglessmytime->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 correctlyif (sDate.WeekDay==7) mytime->tm_wday = 0;mytime->tm_hour = sTime.Hours;mytime->tm_min = sTime.Minutes;mytime->tm_sec = sTime.Seconds;g_SubSeconds = sTime.SubSeconds;// Load subseconds into a global variable// 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&Rmytime->tm_isdst = -1;return 0;}The synchronous prescaler decrements from the init value to zero. I suppose one could convert it to a float of microseconds or whatever:(if RTC_SYNC_PREDIV=1023)float us = (1023-g_SubSeconds)*1000000/1024;Note that on any real target these prescalers will not normally get initialised, because doing so will bugger up the time. They get set up on the raw RTC init only; typically one may do this by storing some magic number in the backup SRAM and if this is corrupted then you do a raw init. So to do these tests one has to force this raw init.I measured the supercap voltage drop rate (nominally 2.8V) over some hours, and the result is opposite of what the appnotes state:sync prescaler=255, current is 1.75uAsync prescaler=1023, current is 1.46uASo one may as well use 1023 and get subseconds to a higher resolution! But even if the numbers were swapped, there may be no point in changing to 255.On a bit of a tangent, the 32F4 RTC, like all integrated RTCs, is of limited value unless1) You are happy with "a few days" on any affordable supercap (one costing less than the CPU), OR2) You are happy with say a year on a primary 2032 lithium cell, which is a bastard for the customer to replace (and the rechargeable versions basically don't work)If you need a long life you need to use e.g. a DS1302 RTC which draws 200-300nA and then you get the full battery shelf life ( > 20 years). I have some products with that in.

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 29, 2021, 10:42:17 am
content: Quote from: peter-h on August 29, 2021, 09:40:05 amThanks for verifying it.Quote from: peter-h on August 29, 2021, 09:40:05 amIn your particular case, I do believe the STM32F407 has hardware floating point support (float, not double; so IEEE-754 Binary32), so usingfloatshould work just fine.In general, there is a "trick" of converting a fraction to any base you want, be it decimal or even 1000 (for milliseconds) or 1000000 (for microseconds).Whenever the divisor, RTC_SYNC_PREDIV+1, is a power of two –– in your case, I suppose 256, 512, or 1024 ––, this becomes a very simple loop.LetBbe the base,D = RTC_SYNC_PREDIV, andV = D - RTC_SSR. Then, for each fractional digit in decreasing order of value, loop overV = V × Bdigit= V / (D + 1)V = V % (D + 1)WhenD + 1is a power of two, the division becomes a right shift, and the modulo becomes a binary AND. You can repeat this for the number of fractional digits you want. That is, forD = 255, the loop iteration isV = V × Bdigit= V >> 8V = V & 255To round half upwards, after the final digit, if2×V >= D + 1, you need to round the previous digit up (and that can ripple all the way to the unit digits, leaving the fractional part zero).If baseB = 10, you can generate the decimal fractional digits (from decimal point rightwards). If you want some other base, say 60 (for video frames per second, perhaps), just useB = 60. Works fine with e.g.B = 1000, if you want milliseconds (in a single iteration!).With hardwarefloatsupport, to get the milliseconds (with RTC_SYNC_PREDIV = 255), you probably get better code if you usems= (255 - RTC_SSR)*3.90625instead. Here, 3.90625=1000/256, i.e. milliseconds per SSR period. For RTC_SYNC_PREDIV = 511, usems= (511 - RTC_SSR)×1.953125; and for RTC_SYNC_PREDIV = 1024, usems= (1023 - RTC_SSR)×0.9765625.

author: peter-h
author_rank: Super Contributor
timestamp: August 29, 2021, 03:33:35 pm
content: Ah yes.However the 32F4 does a single float mult in 1 clock and a single float div in 16 clocks, so this is not an issue.Of course I found I have to add the .0Code:float us = (1023-g_SubSeconds)*1000000.0/1024;otherwise I get a zero resultAlso if time was an issue one would never do a divide, but perhaps the compiler will pre-evaluate the 1M/1024 = 976.5625. Anyway, this is not a Z80 on which this would take 10msI am now working on an interesting challenge: GPS time is obviously going to be delayed by the time it takes to receive the relevant NMEA packet. This is trivial to quantify and comes to 12ms. But one can'twritethe subseconds (the sync prescaler) can one? Other hacks include a calculated delay on 1 second minus the packet time (etc) and setting the RTC to GPS time plus 1 second.

author: langwadt
author_rank: Super Contributor
timestamp: August 29, 2021, 03:44:50 pm
content: Quote from: peter-h on August 29, 2021, 03:33:35 pmyou needCode:float us = (1023-g_SubSeconds)*1000000.0f/1024;else 100000.0 is a double and the calculation done as doubles(or the flag -fsingle-precision-constant )

author: abyrvalg
author_rank: Frequent Contributor
timestamp: August 29, 2021, 04:02:34 pm
content: “But one can't write the subseconds”Check the RM section I’ve mentioned above: you can’t write a value directly, but you can command the RTC (via that SHIFTR reg) to subtract a given number from the SS counter. Their idea is you don’t need to write the SS at a precisely timed moment, instead of that you find out your clock offset somehow and pass it to the RTC to adjust the clock by that amount.

author: peter-h
author_rank: Super Contributor
timestamp: August 29, 2021, 04:11:07 pm
content: Thanks - another C lessonThe previously posted appnote states clearly the prescaler is not writeableso am looking at a hack where the delays are evaluated and the RTC is set to t + 1 sec, minus the delays which comes to about 40ms. Obviously a higher GPS baud rate would reduce this but actually evaluating the NMEA packet length i.e. time is easy (12ms). The other 28ms is my poor code structure, which can be fixed. With FreeRTOS I can do osDelay(960) but then nothing else in that thread runs for 960msInteresting stuff though. There is probably a way to schedule just the RTC setting to run at t plus 960ms, and with 1 added to seconds.Then I saw abyrvalg's post above and that must be the way to do itThere is a HAL function for this, which in a simplified version isCode:/*** Simplified version of HAL_RTCEx_SetSynchroShift().* @param ShiftAdd1S Select to add or not 1 second to the time calendar.* This parameter can be one of the following values :* @arg RTC_SHIFTADD1S_SET: Add one second to the clock calendar.* @arg RTC_SHIFTADD1S_RESET: No effect.* @param ShiftSubFS Select the number of Second Fractions to substitute.* This parameter can be one any value from 0 to 0x7FFF.**/void RTC_SetSynchroShift(RTC_HandleTypeDef* hrtc, uint32_t ShiftAdd1S, uint32_t ShiftSubFS){/* Disable the write protection for RTC registers */__HAL_RTC_WRITEPROTECTION_DISABLE(hrtc);/* Wait until the shift is completed*/while((hrtc->Instance->ISR & RTC_ISR_SHPF) != RESET) {}/* Configure the Shift settings */hrtc->Instance->SHIFTR = (uint32_t)(uint32_t)(ShiftSubFS) | (uint32_t)(ShiftAdd1S);/* Enable the write protection for RTC registers */__HAL_RTC_WRITEPROTECTION_ENABLE(hrtc);}I am struggling to make this work however. It is doing "something" for sure, but I think the range of the ShiftSubFS value is not 0 to 0x7xxx as the appnotes say but actually 0 to RTC_SYNC_PREDIV (0x3ff typically). And the value probably works backwards, just like the RTC_SYNC_PREDIV does (it decrements to zero and then reloads).And obviously the SHIFTR operation has to be doneafteryou set the RTC, because setting the RTC resets the prescaler

author: bson
author_rank: Unknown
timestamp: August 30, 2021, 06:10:40 am
content: Quote from: peter-h on August 29, 2021, 03:33:35 pmThis is receiver dependent. The PPS is pulsed, then within the next second the sentence is sent to indicate the time of the PPS. There's no particular guarantee it starts sending immediately (that the start bit correlates to the PPS or anything like that), only that it's sent before the next second.

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 30, 2021, 06:31:16 am
content: I just noticed that theerrata sheetmentions thatQuotewith the workarounds being either to disable the shadow registers, or to use a loop along the lines ofCode:ssr = RTC_SSR;do {old_ssr = ssr;tr = RTC_TR;dr = RTC_DR;ssr = RTC_SSR;} while (ssr != old_ssr);// Use dr, tr, ssrTheReference manualsection 26.6.12 (page 828/1751) describes the RTC_SHIFTR register. Because it describes it as an operation that completes, I believe it is an one-shot adjustment to the RTC by-PREDIV_S/(PREDIV_S+1)to+1.0seconds. Reading the section 26.3.8 (page 806/1751) very much implies the same.So, if I understood your situation correctly:You receiveYear,Month,Day,Hour,Minute,Seconds, andSubsecondsfrom a GPS sourceIt takes you a fixed amount of time,Delay, to process this messageYou wish to program the estimated time to the RTC registers, including the subseconds fieldThere are two options to achieve this: you can wait (either busy-wait or set a single-shot timer) to next full second using the estimated time and set the RTC at that point of time; or you can set the RTC time to the timestamp advanced by one second, subtracting the fractional seconds from the SSR register immediately after setting the time.I recommend the latter one:First, you addDelayto the receivedSubsecondsvalue. If this overflows, you incrementSeconds. If that overflows too, you incrementMinute. And so on. Again,Delayis the estimated delay from the point the GPS clock was sampled, to the point when the entire RTC clock is restarted after updating it.This gives you the estimated date and time at the time when the RTC is going to be set. We addedDelayto the timestamp, because this timestamp is in the future with respect to the timestamp we got from GPS.Next, we essentially set the RTCincluding the SSR partusing the following steps:Unlock RTC registers (by writing byte 0xCA, then byte 0x53, to RTC_WPR).Set RTC_ISR register INIT bit to 1. Wait until RTC_ISR register INITF is 1.Write the low 16 bits of RTC_PRER register to set the synchronous prescaler value, PREDIV_S.Write the high 16 bits of RTC_PRER register to set the asynchronous prescaler value, PREDIV_A.(The two must be separate write operations.)Ignoring the subseconds part at this point, set the date, time, and format by writing to the RTC_TR, RTC_DR, and RTC_CRT registers.Clear the RTC_ISR register RSF and INIT bits (to 0).Wait for RTC_ISR register RSF bit to become 1 and SHPF bit to be zero.If the fractional seconds is zero, skip to 10.Write the fractional subsecond part to RTC_SHIFTR, with the ADD1S bit set.If 0 <Subseconds< 1, then the register is set to(uint32_t)((PREDIV_S + 1) *Subseconds) | (((uint32_t)1)<<31).Wait for RTC_ISR register SHPF bit to become zero.Lock the RTC registers (by writing e.g. byte 0x35, then byte 0xAC, RTC_WPR).The wait in step 9 ensures that the RTC registers have been updated before the operation completes. You can omit that, but then you'll need to add a check (loop) in when you read the RTC, that RTC_ISR register RSF is 1 and SHPF is 0 before the shadow registers are read.Note that the value written to RTC_SHIFTR is logicallySubseconds= 1 - (1 -Subseconds). The value in the low bits counts down from PREDIV_S to zero, i.e. logically 1-Subseconds. However, the register value itself is subtracted from the synchronous counter; and the +1 comes from the ADD1S bit being set. We also kinda-sorta have to do this immediately after setting the clock, so that we can be sure that we can subtract from the synchronous counter without underflow and having to compensate for out-of-bound values when reading the RTC_SSR value soon after (if I understood the errata right).The reason STM does not describe this as a workaround or something for setting the RTC timeincluding the subseconds field, is that there is at minimum a 4 RTCCLK cycle delay between setting the RTC date and time, and updating the subseconds field. With a 32768 Hz RTCCLK, that's about 122 µs. Furthermore, the INIT bit keeps the RTC "stopped", while the SHIFTR register applies an one-time fractional-second shift to it. So, while the end result is indeed that you get the RTC clock set to exactly what you want, there is implementation jitter and the need to estimate how long it takes, to get it exactly right; and STM's wording about all this kinda makes sense.

author: peter-h
author_rank: Super Contributor
timestamp: August 30, 2021, 06:40:51 am
content: I don't think this SHIFTR solution is generally usable.The problem is this:This line increments the RTC by 1 second and at the same time adds the required delay value to the prescalerCode:// Load the prescaler// Bit 31 = 1 to add 1 second to RTC at the same timehrtc.Instance->SHIFTR = ShiftSubFS | ShiftAdd1S;So what happens is that you get animmediate1 second increment, but the delay to compensate for that 1 second (which will be less than 1 second, say 950ms if you want to advance the RTC by 50ms) doesn't wash out until the prescaler has decremented all the way to zero. So you get a duff RTC value (1 second too far, and decreasing over the next second) initially, which is obviously useless.This system is OK only for an initial RTC setting, or subsequently with a lot of precautions. I reckon one could make it work if one waited for the prescaler to be near zero, or actually underflowed, and then immediately load it (and do the +1 sec too).I've just read Nominal Animal's post and I think I will drop this whole thing and just store a global value of microseconds, to be added to the RTC timeThere are just too many complications. I've spent a couple of solid days on it, and even the simple case of adding the 1 sec and at the same time loading 1023 into SHIFTR doesn't work reliably. And whatever I do I am stuck with the issue I mention above, where you get the RTC being 1 second out after doing this operation. It indeed looks like there are subtle timing issues, so one needs wait loops, and I can't get my head around this.Yes; exactly, the SHIFTR operation must be done ASAP after the RTC is set."wait (either busy-wait or set a single-shot timer) to next full second using the estimated time and set the RTC at that point of time"That is likely the best way because it is done using some RTOS timed event (need to read the FreeRTOS docs) and doesn't involve weird stuff with the RTC hardware.Just limit the setting of the RTC to when secs=0 (which I am doing anyway) and if you need to advance it by say 50ms (the sort of value I am seeing) then schedule an event for t plus 950ms and load the time into it with secs=1.FWIW this was my last codeCode:/*** Simplified version of HAL_RTCEx_SetSynchroShift().* Advances the clock by the value in microseconds.** This function adds 1 second to the RTC and adds a value to the prescaler.* Since the prescaler decrements, adding a value to it delays the clock seconds increment.**The actual advance in ShiftSubFSis from 0 to RTC_SYNC_PREDIV, in units of RTC_SYNC_PREDIV*The clocking frequency which is 1024Hz if RTC_SYNC_PREDIV is 1023.**/void RTC_advance(uint32_t advance){if (advance<1000000L){uint32_t ShiftAdd1S = RTC_SHIFTADD1S_SET;uint32_t ShiftSubFS = (1024L*(advance+RTC_SYNC_PREDIV))/1000000L;// Disable RTC write protectionhrtc.Instance->WPR = 0xCAU;hrtc.Instance->WPR = 0x53U;// Wait until any shift is completed// This could hang up the thread if the 32k xtal is not connected! TODOwhile((hrtc.Instance->ISR & RTC_ISR_SHPF) != 0) {}// Load the prescaler// Bit 31 = 1 to add 1 second to RTC at the same timehrtc.Instance->SHIFTR = ShiftSubFS | ShiftAdd1S;// Re-enable RTC write protectionhrtc.Instance->WPR = 0xFFU;}}BTW onecanloadmorethan RTC_SYNC_PREDIV into the prescaler, using SHIFTR. I am not sure what the limit is; it may be 16 bits. In reality, if setting the clock at the instant when subseconds=RTC_SYNC_PREDIV (1023 in this case) you could end up loading in 2046.

author: AndrewBCN
author_rank: Frequent Contributor
timestamp: August 30, 2021, 07:12:42 am
content: Quote from: bson on August 30, 2021, 06:10:40 amExcellent point. I believe the correct method to synchronize an MCU RTC clock to GPS time with sub-millisecond accuracy is to use the 1PPS signal from the GPS to drive an interrupt pin on the MCU. Then the uncertainty in the RTC synchonization with GPS time is reduced to basically 1 period of the 32768Hz XO, or roughly around +/- 30 microseconds. If you need a lesser uncertainty you have to use a higher RTC clock frequency which drives current requirements up.

author: peter-h
author_rank: Super Contributor
timestamp: August 30, 2021, 10:11:13 am
content: That's with a 1Hz GPS.With a 5Hz GPS, one would expect the time to be not delayed by more than 200ms.

author: peter-h
author_rank: Super Contributor
timestamp: August 30, 2021, 02:21:41 pm
content: Nominal Animal that errata sheet is quite fascinatingI implemented that double SSR read.Code:/** Read KDE real time clock.* Just call it. It fills in the structure 'mytime'. This is the standard C library structure tm*inttm_sec;//seconds after the minute (0..59)inttm_min;//minutes after the hour (0..59)inttm_hour;//hours since midnight (0..23)inttm_mday;//day of the month (1..31)inttm_mon;//months since January (0..11)inttm_year;//years since 1900 (100..199)inttm_wday;//days since Sunday (0..6)inttm_yday;//days since Jan 1 (0..365) calculated from above datainttm_isdst;//Daylight Saving Time flag (0..1) unimplemented, set to -1* Returns* 0 if no errors* 1 if RTC not present (not implemented; the RTC is on the CPU chip, but the battery/supercap may not be there)* 2 if the HAL code returns an error (should never happen)** TODO Code 1 is never going to happen because we have no way to detect the lack of the supercap.* Code 1 could be hard coded on systems where the RTC cannot be used for some reason (supercap not fitted AND* no provision to set the RTC).** Other comments as per setrtc above.* You must call HAL_RTC_GetDate() after HAL_RTC_GetTime() to unlock the values in the higher-order calendar* shadow registers to ensure consistency between the time and date values. Reading RTC current time locks* the values in calendar shadow registers until current date is read. So always read BOTH and in that order.** Also returns the subseconds counter in a global variable g_SubSeconds in microseconds.**/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;do{last_subsec = (uint32_t)(hrtc.Instance->SSR);// Always read both, even if 1st returns an errorrtcret1 = 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 (errorcnt!=1)errorcnt=99;// for breakpointif ( (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 meaninglessmytime->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 correctlyif (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&Rmytime->tm_isdst = -1;return 0;}That condition obviously doesn't happen very often. Haven't seen it yet, after a few mins' running.However, the errata says"Similarly, RTC_DR register can be updated after reading the RTC_TR register instead of being locked."so doesn't one have to doeverythingtwice and compare? They seem to be saying the register locking mechanism (where you read the time, and then you read the date) doesn't work.EDIT: it did trap after a few hours, of five RTC reads per second. So this issue is real...However, given that the prescaler is what feeds the RTC counters, if you read the prescaler until you get two consecutive values then the values obtained for date/timein the middle of thatshould all be mutually consistent, because whatever is clocking the prescaler is also clocking the RTC date/time.The ST macros for write protection enable and disable are weird:Code:do{ \(hrtc)->Instance->WPR = 0xCAU; \(hrtc)->Instance->WPR = 0x53U; \} while(0U)What is the point of the loop?

author: Nominal Animal
author_rank: Super Contributor
timestamp: August 31, 2021, 02:55:29 am
content: Quote from: peter-h on August 30, 2021, 02:21:41 pmWell, the errata describes the workaround as only checking SSR, which indicates the locking mechanism is shared across the three registers. That is, if TR or DR is updated, then SSR is updated too. That makes logical sense to me.Quote from: peter-h on August 30, 2021, 02:21:41 pmNo, the#define SOME_MACRO do {...stuff...; } while (0)is a standard C pattern to ensureSOME_MACRObehaves like a single statement. It is not really a loop, because it is only executed once. The...stuff...statements and expressions are within the "loop" body, in their own scope (which is the purpose of the "loop"!), so even in C89 you can e.g. declare local variables, so that if the macro takes parameters, those parameters are only evaluated once:#define SOME_MACRO(a,b) do { consttypelocal_a = (a); consttypelocal_b = (b);...stuff...; } while (0)GCC and clang support an extension to C that behaves like an expression, yielding a value too. For example,#define FOO(a, b) ({ __auto_type local_a = (a); __auto_type local_b = (b); local_a*local_a*local_a - local_b*local_a + local_b*local_b; })evaluatesaandbonly once, and yields a³+b²-ab as the expected type (the type of a or b, whichever gets promoted to the other).The__auto_typeis a GCC/clang extension related to thetypeofextension; in C++, you need to useautoinstead. In mixed code, use a macro, sayAUTOTYPE:Code:#ifndef AUTOTYPE#ifdef __cplusplus#define AUTOTYPE auto#else#define AUTOTYPE __auto_type#endif#endifSince C99 and C++98, helper functions markedstatic inlinehave no overhead compared to macros, and provide type checking at call time, so I warmly recommend using such helper functions instead of statement or expression macros. The only exception is for GCC/clang-specific polymorphic type macros, like say#define SQUARE(x) ({ const AUTOTYPE local_x = (x); local_x*local_x; })which yields the value ofxsquared, using its own type. These are not useful for variables, but they are for math expressions, since the expression is only evaluated once, then squared, when using this macro. (For a type-agnostic ABS(x) macro, I'd use_Generic()with __builtin_abs(), __builtin_labs(), __builtin_llabs() (for integral types) and __builtin_fabsf(), __builtin_fabs(), and __builtin_fabsl() (for floating-point types), instead.)Only evaluating the macro parameter once is important, if it contains pre- or post-increments or decrements (++,--). As you know,(x++)*(x++)is not a valid expression in C; it violates the constraints on the increment operator. When the macro uses a local variable as above, it evaluates correctly (and usually the way a human programmer expects it to).

author: peter-h
author_rank: Super Contributor
timestamp: August 31, 2021, 06:20:05 am
content: ThanksSo my hypothesis that if the prescaler has been found to have changed, and the whole lot is read again until the prescaler matches, should work - because it is the prescaler that is clocking the rest of the RTC.It would actually be a totally terrible silicon bug if the shadow registers didn't work as published. That iscentralto any RTC device. It is merely the reading of the prescaler which exposes that problem, and I guess most people won't be doing that.That solution will incidentally hang up the product if the 32k xtal oscillator stops running. I should probably put a timeout around it.I did realise that loop doesn't actually loop and there must be some other reason.

author: peter-h
author_rank: Super Contributor
timestamp: August 31, 2021, 02:38:17 pm
content: It gets more complicated...I posted a Q on this on the ST forum (often, no replies there, due to volume of traffic) and one regular said he doesn't use HAL because it's crap and may not implement the ST errata (nothing new there) and I should not be using those functions.So I simplified them to what is actually needed and put them inline. And that pretty well broke it! The seconds would be stuck for a few seconds at a time. But OK if I removed that while loop to read the SSR twice if different. It looks like some timing dependence...Code:/** Read KDE real time clock.* Just call it. It fills in the structure 'mytime'. This is the standard C library structure tm*inttm_sec;//seconds after the minute (0..59)inttm_min;//minutes after the hour (0..59)inttm_hour;//hours since midnight (0..23)inttm_mday;//day of the month (1..31)inttm_mon;//months since January (0..11)inttm_year;//years since 1900 (100..199)inttm_wday;//days since Sunday (0..6)inttm_yday;//days since Jan 1 (0..365) calculated from above datainttm_isdst;//Daylight Saving Time flag (0..1) unimplemented, set to -1* Returns* 0 if no errors* 1 if RTC not present (not implemented; the RTC is on the CPU chip, but the battery/supercap may not be there)* 2 if SSR had to be read too many times before we got two same values** Code 1 is never going to happen because we have no way to detect the lack of the supercap.* Code 1 could be hard coded on systems where the RTC cannot be used for some reason (supercap not fitted AND* no provision to set the RTC).** Code 2 is related to the errata sheet which says that the SSR (the prescaler) may be incorrectly read* so one has to read it twice and compare. And the date/time needs to be read in between those two SSR reads.** Other comments as per setrtc above.** Also returns the subseconds counter in a global variable g_SubSeconds. This is related to the config* in KDE_rtc_init() and the two values RTC_ASYNC_PREDIV and RTC_SYNC_PREDIV which are #defined at the* top of this file.**/int getrtc (struct tm * mytime){#define MAX_SSR_READS 10volatile RTC_TimeTypeDef sTime = {0};// volatile to prevent compiler optimising these out (for debug)volatile RTC_DateTypeDef sDate = {0};// Mutex to prevent concurrent setrtc and getrtc, under RTOSRTC_Lock();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 comparisonlast_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 DRhrtc.Instance->DR;RTC_Unlock();// The following code could be simplified.../* 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);/* 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);// Convert the 32F4 RTC API to the K&R standard C clock structure "tm"// Comments in setrtc above// No error checking - would be meaninglessmytime->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 correctlyif (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&Rmytime->tm_isdst = -1;return 0;}EDIT: I think I fixed the last issue, with the line which just reads the DR and dumps the valuehrtc.Instance->DR;The thing is: the errata says you need to read SSR before and after, but the RTC requires the DR to be readlast, otherwise the RTC regs remain locked. How do you reconcile the two? The above hack seems to do it.The regs are not actually totally locked; they are locked for several seconds at a time. I reckon there is some timing issue here, and the HAL libs have got away with it because almost nobody is going to find out the occassional duff SSR value.My final working code posted above, in case somebody finds it useful.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 31, 2021, 05:42:26 pm
content: Quote from: Nominal Animal on August 31, 2021, 02:55:29 amYes. For those not accustomed to this pattern, here is the point. It has the exact same behavior as a block statement '{ }', and you can actually just use block statements instead, like:#define SOME_MACRO {...stuff...; }The reason for using the 'do {} while (0)' construct is that it can be used in all contexts when the macro call is terminated with a ';', which is a "natural" syntax when the macro looks like a function.One famous example of where the simple block statement would fail is this:Code:if ( ... )SOME_MACRO(a,b);else...because it will expand to this:Code:if ( ... ){ ... };else...and this is syntaxically wrong, as there can't be two statements (here, a block statement followed by an empty statement ';') between an 'if' and its 'else' clause. The 'do {} while (0)' construct solves this.I actually can't think of any other context where it would make a difference. Just chime in if you can think of one.Quote from: Nominal Animal on August 31, 2021, 02:55:29 amYes, this can be handy. Something I try avoiding though, as I try to avoid any compiler-specific extension which would make the code non-standard.I actually considered using this lately (for a message queue library), but I decided against it, and wrote macros that generate inline functions instead.

author: peter-h
author_rank: Super Contributor
timestamp: September 02, 2021, 04:23:12 am
content: More discussion herehttps://community.st.com/s/question/0D53W000013JV5nSAG/32f417-rtc-errata-sheet-if-reading-subseconds-is-this-code-okand a summary of the 32F4 RTC silicon bug herehttp://efton.sk/STM32/gotcha/g82.htmlThe funny thing is how many people must have this bug (because the HAL get time code triggers it) and have never found out. I suspect most embedded systems don't use the RTC for anything important, or at all.

author: SiliconWizard
author_rank: Super Contributor
timestamp: September 02, 2021, 06:02:40 pm
content: Quote from: peter-h on September 02, 2021, 04:23:12 amFrom the above, "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 looks like what you'd have to do for any set of registers that, as a set, can't be read atomically. Actually, I don't know of many MCUs that can allow reading *several* separate registers atomically. The same happens for, say, 64-bit counters on a 32-bit core. You need to read them in 2 separate register reads, and thus have to also read the MSW twice to ensure the pair will be consistent.I may have missed something additional though, as I admit I haven't followed the exact details of the whole issue you describe.Quote from: peter-h on September 02, 2021, 04:23:12 amI (and probably many) have used STM32s' RTC, but never used the subsecond register. It's probably rather rarely used. Does the RTC issue potentially affect RTC readings if you're not reading the subsecond register?

author: wek
author_rank: Frequent Contributor
timestamp: September 02, 2021, 10:55:18 pm
content: Quote from: SiliconWizard on September 02, 2021, 06:02:40 pmYes. The devil is, as usually, in the details. That's why that writeup is so long.Quote from: SiliconWizard on September 02, 2021, 06:02:40 pmYes, if you read both RTC_TR and RTC_DR. The problem will happen when you start reading RTC_TR one APB cycle before the date rollover (and you will get one second before midnight of the next day). What is the probability that this happens, and what are the odds you would notice that?JW

author: SiliconWizard
author_rank: Super Contributor
timestamp: September 02, 2021, 11:00:33 pm
content: Quote from: wek on September 02, 2021, 10:55:18 pmWell, there is a locking mechanism with shadow registers, as far as I've gotten.Here is what is noted in the HAL's source code:Quote

author: wek
author_rank: Frequent Contributor
timestamp: September 03, 2021, 06:05:42 am
content: QuoteThe entire discussion stems from the fact, that it's flawed (in some STM32 models, introduced before cca 2016).JW

author: peter-h
author_rank: Super Contributor
timestamp: September 03, 2021, 06:11:22 am
content: My understanding is that the RTC works ok if you read time and date,only, and the corruption can happen only if you read the SSR (subseconds). But maybe not, in which case the solution below is a good one even if you don't need subseconds. Or you could just read both time and date (which are just two uint32_ts) twice and compare them.The problem is that (probably) most users are starting with STM's HAL functions HAL_RTC_GetTime and HAL_RTC_GetDate, and the first of those always reads the SSR!!Code:/*** @brief Gets 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* @note You can use SubSeconds and SecondFraction (sTime structure fields returned) to convert SubSeconds* value in second fraction ratio with time unit following generic formula:* Second fraction ratio * time_unit= [(SecondFraction-SubSeconds)/(SecondFraction+1)] * time_unit* This conversion can be performed only if no shift operation is pending (ie. SHFP=0) when PREDIV_S >= SS* @note You must call HAL_RTC_GetDate() after HAL_RTC_GetTime() to unlock the values* in the higher-order calendar shadow registers to ensure consistency between the time and date values.* Reading RTC current time locks the values in calendar shadow registers until current date is read.* @retval HAL status*/HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format){uint32_t tmpreg = 0U;/* Check the parameters */assert_param(IS_RTC_FORMAT(Format));/* 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);/* 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);}return HAL_OK;}The code I now use is a fixed version which combines both functions. Can't say the conversion to tm afterwards is elegant! RTC_Lock is a mutex, btw, to make sure setrtc and getrtc cannot be active concurrently.Code:/** Read KDE real time clock.* Just call it. It fills in the structure 'mytime'. This is the standard C library structure tm*inttm_sec;//seconds after the minute (0..59)inttm_min;//minutes after the hour (0..59)inttm_hour;//hours since midnight (0..23)inttm_mday;//day of the month (1..31)inttm_mon;//months since January (0..11)inttm_year;//years since 1900 (100..199)inttm_wday;//days since Sunday (0..6)inttm_yday;//days since Jan 1 (0..365) calculated from above datainttm_isdst;//Daylight Saving Time flag (0..1) unimplemented, set to -1* Returns* 0 if no errors* 1 if RTC not present (not implemented; the RTC is on the CPU chip, but the battery/supercap may not be there)* 2 if SSR had to be read too many times before we got two same values** Code 1 is never going to happen because we have no way to detect the lack of the supercap.* Code 1 could be hard coded on systems where the RTC cannot be used for some reason (supercap not fitted AND* no provision to set the RTC).** Code 2 is related to the errata sheet which says that the SSR (the prescaler) may be incorrectly read* so one has to read it twice and compare. And the date/time needs to be read in between those two SSR reads.** Other comments as per setrtc above.** Also returns the subseconds counter in a global variable g_SubSeconds. This is related to the config* in KDE_rtc_init() and the two values RTC_ASYNC_PREDIV and RTC_SYNC_PREDIV which are #defined at the* top of this file.**/int getrtc (struct tm * mytime){#define MAX_SSR_READS 10volatile RTC_TimeTypeDef sTime = {0};// volatile to prevent compiler optimising these out (for debug)volatile RTC_DateTypeDef sDate = {0};// Mutex to prevent concurrent setrtc and getrtc, under RTOSRTC_Lock();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 comparisonlast_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 DRhrtc.Instance->DR;RTC_Unlock();// The following code could be simplified.../* 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);/* 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);// Convert the 32F4 RTC API to the K&R standard C clock structure "tm"// Comments in setrtc above// No error checking - would be meaninglessmytime->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 correctlyif (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&Rmytime->tm_isdst = -1;return 0;}

author: wek
author_rank: Frequent Contributor
timestamp: September 03, 2021, 09:51:11 am
content: Quote from: peter-h on September 03, 2021, 06:11:22 amNo. Quoting from the erratum in question:Similarly, RTC_DR register can be updated after reading the RTC_TR register instead of being locked.Thing is, that it's not likely you will ever observe the problem with reading only RTC_TR/RTC_DR, as it will happen very rarely. If you're not interested in subseconds, you're not likely to read out RTC more than once in a second. Assuming this readout is once in a second but entirely random and your APB clock is 42MHz, the likelyhood the critical read will fall onto the one single APB cycle which results in faulty readout (the one APB cycle before copying the timekeeping registers into the shadow just after the date rollover happened) is 1:42M, i.e. with ten thousand deployed devices it will happen roughly once in 10 years. And then the question is, would it be noticed.There may be synchronicities of course which would shift this estimate to either direction significantly.QuoteIn case the user is not interested in subseconds, this probably hides the problem, paradoxically. If my understanding of the nature of problem is correct, in the problematic instance SSR is read one APB cycle before the TR/DR lock occurs. But, quite likely, the TR/DR is locked at the same instant, so they are mutually correct.I am not ST insider and all I know is inferred (maybe incorrectly) from experiments and I did not perform an experiment specifically investigating mutual relationship of TR/DR when locked by SSR read, so I may be wrong.JW

author: Nominal Animal
author_rank: Super Contributor
timestamp: September 03, 2021, 11:08:55 am
content: (This is not really a reply, but a recap of the issues.)STM32F4 reference manualchapter 26.3.6 describes the shadow register operation asQuoteTheerratachapter 2.4.3 summarizes the issue asQuoteAfter thinking about this for a while, I would recommend the following:After initializing,modifying, or reading the RTC state, always clear RSF bit in RTC_ISR.When reading the RTC, first wait for RSF bit to become one.Use a loop to read the RTC registers, with RTC_DR read last; reading all three registers at least twice. Loop until the register values are stable.This is similar to, but not exactly the code in post #27. At the core, I would use a function that reads the three registers, and returns a status code whether the read was successful. For example,Code:enum {RTCREAD_OK = 0,RTCREAD_NOTREADY, /* Shadow registers not updated */RTCREAD_GLITCH, /* Shadow register contents glitched; recommend re-read */};typedef struct {uint32_t tr;uint32_t dr;uint32_t ssr;} rtc_registers;static volatile rtc_registers rtc_last;static inline int rtc_read(rtc_registers *to){rtc_registers prev, curr;RTC_Lock();/* Check if shadow registers have been updated */if (!(hrtc.Instance->ISR & RTC_ISR_RSF)) {/* No, return the last valid values. */if (to)*to = rtc_last;RTC_Unlock();return RTCREAD_NOTREADY;}prev.ssr = hrtc.Instance->SSR;prev.tr = hrtc.Instance->TR;prev.dr = hrtc.Instance->DR;next.ssr = hrtc.Instance->SSR;next.tr = hrtc.Instance->TR;next.dr = hrtc.Instance->DR;/* Clear shadow register update flag */hrtc.Instance->ISR &= ~(RTC_ISR_RSF);/* Verify the two readings are consistent,and we didn't get bitten by the RTC errata */if (prev.ssr == next.ssr && prev.tr == next.tr && prev.dr == next.dr) {rtc_last = prev;RTC_Unlock();if (to)*to = prev;return RTCREAD_OK;}/* Nope, glitched. */if (to)*to = rtc_last;RTC_Unlock();return RTCREAD_GLITCH;}Note howrtc_lastis only accessed while the lock is held. It therefore protects both against concurrent modification and reading, but also makes sure only one thread accessesrtc_lastat a time. Overall, the lock is only held for a very short, fixed duration: no looping while lock is held.If the registers were updated and read consistently,rtc_lastis updated, and RTCREAD_OK returned. Iftois non-NULL, the updated values if successful orrtc_lastif unsuccessful are stored there.Theget_rtc()function would internally callrtc_read(). Depending on your use cases, you can use the return value to determine whether you want to loop or fail. You could even make different versions of it, sayget_rtc_fast()that gives a best effort timestamp without looping, andget_rtc_slow()doing some heuristics and retries, perhaps yielding to other threads.For testing purposes, the abovertc_read()is simpler and faster than a fullget_rtc(), so it can be called more often and hopefully reproduce the glitches. It can even be instrumented to fail say one call in eight, to ensure the overall behaviour is correct even when the RTC does glitch. I wrote it in the above form, because I do believe that in the glitched cases, returning the same RTC value as the last successful call makes usually the most sense; but the return value is the important point that allows the caller to decide for themselves. I'm assuming rather generic use cases, so if yours are different, you may have a better pattern in mind. One could for example only update*toif the read was successful and consistent, so that each call site can use their own last valid values.I still haven't tested this on my STM32F4 Discovery board, so this is speculation on my part, and not verified in practice.

author: peter-h
author_rank: Super Contributor
timestamp: September 03, 2021, 03:22:07 pm
content: WEK - my reading of the errata is that the key phrase, in yellow belowis thatreading SSRis what triggers the unreliability of the shadowing.Well, I've lived in England for 50 years and that is the only proper interpretationNominal Animal - I suspect your code is similar to mine in the overall effect. I limit the hanging inside the mutex to 10 loops, which is statistically utterly impossible to be reached in the life of the universe. Well, unless there is synchronicity10 loops will be done in microseconds.I agree almost nobody will find this bug if they just read the RTC at 1Hz, but- a lot of people don't do that because it gives you obviously poor behaviour when say updating a display (really crappy display increment behaviour due to synchronicity between the two 1Hz frequencies); I reckon most read an RTC at say 10Hz and then a human cannot tell if 10:12:34 is displayed for 900ms while 10:12:35 is displayed for 1000ms- there may be systems where it could crash something downstream if the values are not mutually consistent; say something was checking that seconds increment monotonically, and if one value is skipped then this code (which was likely never tested) bombs- there may be some part of the RTC which is clocked not at Fcpu (say 168MHz) or APB (84MHz?) but at 32.768kHz and that gives you a much bigger window; look at that ethernet "APB2 clocking with DIV8/DIV16 bug"... logic designers, when they want a clock to sync something, pick whatever clock is around, and don't document itSod's law is that the simplest things are what bites you in the bum.I am still amazed ST didn't fix the siliconASAP.I am away currently but when I get back I will test this with shadowing disabled, because it really should not make any difference.

author: Nominal Animal
author_rank: Super Contributor
timestamp: September 03, 2021, 04:01:15 pm
content: Quote from: peter-h on September 03, 2021, 03:22:07 pmHey, don't ignore the "Similarly," part.Me fail English often, so my opinion of the meaning of the text is pretty much useless.Anyway, here is the way I understand the problem, and why.Essentially, both SSR and TR are supposed to "lock" the shadow registers until DR is read. It seems to me that the problem is in the hardware implementation ofobservingthat lock, and not intriggeringthat lock.That is, that it is not about failing totriggerthe lock, but a time-of-test-to-time-of-use bug in the implementation of the shadow registers: if the lock is triggered exactly one bus clock prior to the RTC subsystem trying to update the shadow registers, the RTC subsystem fails to observe the lock.Sounds very much like a classic TOTTOU bug to me. I see these way too often in all sorts of code. They do not occur because the code is complex, but because it is hard for many humans to correctly model these things in their mind, so I would not be surprised to see it occur in silicon also.I would even go as far as offering a theory why the silicon is not fixed: The way these bugs tend to need to be fixed, would here involve each shadow register to be duplicated, with separate index (bit) for writes (by the RTC subsystem) and another for reads (by user code), plus a separate "locked" bit. In a very real sense, this then acts like a latch, allowing writes and reads to occur at the same time, but not interfere with each other. Simply put, the fix may need more silicon area than is available, or worth fixing this particular bug.

author: SiliconWizard
author_rank: Super Contributor
timestamp: September 03, 2021, 06:26:11 pm
content: It's odd that the workaround is not implemented in the HAL, then. I looked in the latest version.

author: SiliconWizard
author_rank: Super Contributor
timestamp: September 03, 2021, 06:54:23 pm
content: Quote from: Nominal Animal on September 03, 2021, 04:01:15 pmYeah.The way I would personally implement a RTC peripheral would be the following:* A set of intermediate registers (call that "shadow" if you want);* For reading the current date/time: I would not implement a shaky locking mechanism relying on a specific read order of registers, that is easier to fuck-up on an implementation level. I would just add a special SET/GET date/time register, write-only, with two flags. One for getting the current date/time, one for setting it. Writing to this register with the GET flag would trigger the simultaneous latch of ALL RTC registers for date and time. Then reading the RTC registers would only read the "shadow" registers, in no particular order required - their value would hold until the next GET;* Likewise for setting the current date/time: you'd write the RTC registers, which would only write to the "shadow" registers, in no particular order, and then you'd write to the SET/GET register with the SET flag, which would latch all shadow registers simultaneously to the internal RTC registers.So yeah, that would require 1 additional instruction, but would be much easier to implement and much easier to use IMHO.

author: peter-h
author_rank: Super Contributor
timestamp: September 03, 2021, 07:49:59 pm
content: I am not native English either (former Soviet bloc) but the ST English here is really crap. I reckon they google translate, or use DEEPL, or most likely use somebody who knows nothing about the subject. There are a lot of "technical translators" around who get nice money but know little and use google to look up a lot of phrases.Originally, the whole "billion-transistor or whatever" chip will have been done in VHDL or whatever the current vogue is. The RTC may have been done manually to tweak the clocking chain for minimal power consumption, etc. The RAM and FLASH will be dropped in as pre laid out blocks. Etc. A good RTC will use special design (which is how the DS1302 gets ~10% of the current of the 32F4 RTC) so I don't think ST did that.Remember the STM code reads not just the SSR but alsoCode:sTime->SecondFraction = (uint32_t)(hrtc->Instance->PRER & RTC_PRER_PREDIV_S);so should either thatorthe SSRorthe TR commence the register locking, until DR is read?The above line of code is actually not needed because it can be compile-time evaluated. But that's true for so much ST HAL code! Their funcs are packed with code which reads register bits and executes tons of code to create different options (like checking if SPICRCis enabled), whereas anybody writing code would use #defines for options, not read the register bits they have just written and use convoluted macros to determine the configured mode

author: Nominal Animal
author_rank: Super Contributor
timestamp: September 04, 2021, 11:08:12 am
content: Quote from: SiliconWizard on September 03, 2021, 06:54:23 pmYup, sounds much better to me too.(I wasn't describing how I'd implement the RTC, but how I'd fix the silicon bug, without changing how the peripheral is used.)Quote from: peter-h on September 03, 2021, 07:49:59 pmWriting technical documentation well is a rare skill, though. Even those who are intimately familiar with the subject will struggle to convey that knowledge to others.This is particularly notable in physics, where capable popularizers like Richard Feynman are very, very rare.(It is also a good example how the intent of the text is much more important than the wording of the text, something I recently ranted about in conjunction with standards, especially the C programming standard.)One of the physics courses I enjoyed best at Uni, Introduction to the Structure of Matter, used the book by Brehm and Mullin having the same title. The prof who lectured the course early on described the book as pretty good introduction and reference to the subject, then proceeded to point out a dozen or so inaccuracies and frankly idiocies (mostly in the illustrations), emphasizing understanding and critical examination and making sure stuff makes sense before relying on it, rather than axiomatic memorization of facts and formulae.Quote from: peter-h on September 03, 2021, 07:49:59 pmWell, I've never found a vendor HAL that I'd consider "good", even in the Arduino environment. I've mentioned I find Teensyduino code rather good, but even its API is largely dictated by the Arduino API, and definitely less than "good". Maybe "acceptable"? Like ST HAL, Arduino seems to be an agglomeration of things, rather than anything properly designed to cater to actual existing needs and use cases.That said, I do understand why. Both library design and technical documentation take significant amount of time (and thus money), and they are non-distributable tasks: you cannot significantly speed the development of either by simply adding resources. Enhancing the quality (from the average) means hiring rare high-cost talent already in heavy demand, so even minor advances in quality increase the cost quite a bit, or being lucky from the get go. They're areas where the vendor reputation matters (wrt. talent), as high quality tends to attract high quality, but relaxing the quality control for even a little while is likely to lose the true top talent elsewhere first – the ones who don't just do what they're paid for, but care about the utility and quality of their work product, tend to leave first when the quality is let drop.All this means is that the crappiness is more or less expected, with better than crap an exception. Sure, there are programmers who simply use the tools they are given and say they never have any issues, but I suspect a lot of those programmers never check their own code for bugs or consider its behaviour in any unexpected situation either. It is what it is, as annoying as that is.

author: SiliconWizard
author_rank: Super Contributor
timestamp: September 04, 2021, 06:51:58 pm
content: Quote from: Nominal Animal on September 04, 2021, 11:08:12 amIndeed. It's pretty hard.One thing to consider too is that STM is a french/italian company. Many of their engineers are not english native speakers. But that's the case for many companies in the world, anyway. English has become the de facto "universal language", but these days, probably a majority of its users are not native speakers! It doesn't work too bad for this purpose, but you can't expect non-native speakers to write perfect English. Heck, even many native speakers do not...

author: peter-h
author_rank: Super Contributor
timestamp: September 05, 2021, 06:44:59 am
content: Indeed, but they could have written e.g."If you read the SSR, the shadowing is not 100% reliable"or"The shadowing is not 100% reliable".As a funny tangent on this: in France, English was historically poorly spoken even among graduate engineers. Since semiconductors are nearly all US-made, with English data sheets, companies sprung up in France buying US parts, removed the markings, and labelled them with made-up numbers (e.g. a 2N3055 would be called 2N4876 or some such previously unallocated number) and published French data sheets for these. I recently dismantled some 1970s autopilot from Badin-Crouzet and it had loads of these in there. A whole separate universe existed with these parts