Bare Metal STM32: the Various Real Time Clock Flavors
Keeping track of time is essential, even for microcontrollers, which is why a real-time clock (RTC) peripheral is a common feature in MCUs. In the case of the STM32 family there are three varieties of RTC peripherals, with the newest two creatively called ‘RTC2′ and RTC3’, to contrast them from the very basic and barebones RTC that debuted with the STM32F1 series.
Commonly experienced in the ubiquitous and often cloned STM32F103 MCU, this ‘RTC1’ features little more than a basic 32-bit counter alongside an alarm feature and a collection of battery-backed registers that requires you to do all of the heavy lifting of time and date keeping yourself. This is quite a contrast with the two rather similar successor RTC peripherals, which seem to insist on doing everything possible themselves – except offer you that basic counter – including giving you a full-blown calendar and today’s time with consideration for 12/24 hour format, DST and much more.
With such a wide gulf between RTC1 and its successors, this raises the question of how to best approach these from a low-level perspective.
You Can Count On Me
If it was just about counting seconds, then any of the timer peripherals in an MCU would be more than up to the task, limited only by the precision of the used system clock. The RTC requirements are a bit more extensive, however, as indicated by what is called the backup domain in F1 and the backup registers in the RTC2 and RTC3 peripherals. Powered by an external power source, this clock and register data are expected to survive any power event, the CPU being reset, halted or powered off, while happily continuing to count the progress of time until the rest of the MCU and its firmware returns to check up on its progress.
Naturally, this continuation requires two things: the first is a power source to the special power pin on the MCU (VBAT), often provided from a ubiquitous 3 V lithium cell, along with a clock source that remains powered when the rest of the MCU isn’t. This provides the first gotcha as the RTC clock can be configured to be one of these three:
- Low Speed External (LSE
usually an external 32,768 Hz oscillator which is powered via VBAT.
- Low Speed Internal (LSI
a simple internal ~40-ish kHz oscillator that is only powered by VDD.
- High Speed External (HSE
the external clock signal that’s generally used to clock the MCU’s CPU and many of its peripherals. Also not available in all low-power modes.
Thus, the logical RTCCLK
choice for an RTC that has to survive any and all adverse power events is the LSE as it feeds into the RTC. Take for example the STM32F103 RTC block diagram:
Simplified RTC diagram of the STM32F103. (Source: RM0008)
Here we can see the elements of the very basic RTC1 peripheral, with the sections that are powered by VBAT marked in grey. The incoming RTCCLK
is used to generate the RTC time base TR_CLK
in the RTC prescaler, which increases the value in the RTC_CNT
register. It being a 32-bit register and TR_CLK
usually being 1 Hz means that this counter can be run for approximately 136 years if we ignore details like leap years, without overflowing.
For initializing and using the RTC1 peripheral, we can consult application note AN2821 alongside reference manual RM0008, which covers a clock and calendar implementation, specifically on the STM3210B-EVAL board, but applicable to all STM32F10x MCUs. If you want to keep a running calendar going, it’s possible to use the backup registers for this whenever the counter reaches a certain number of seconds.
That said, where having just this counter is rather pleasant is when using the C <time.h>
functions with Newlib, such as time()
. As Newlib on STM32 requires you to implement at least [url=https://www.man7.org/linux/man-pages/man2/gettimeofday.2.html]_gettimeofday()[/url]
, this means that you can just let RTC_CNT
do its thing and copy it into the seconds member of a timeval
struct – after converting from BCD to binary – before returning it. This is significantly easier than with RTC2 and 3, with my own implementation in Nodate’s RTC code currently fudging things with mktime()
to get a basic seconds counter again from the clock and calendar register values.
All The Bells And Whistles
If the RTC1 peripheral was rather basic with just a counter, an alarm and some backup registers, its successor and the rather similar RTC3 peripheral are basically the exact opposite. A good, quick comparison is provided here, with AN4759 providing a detailed overview, initialization and usage of these newest RTCs. One nice thing about RTC3 is that it adds back an optional counter much like the – BCD-based – RTC1 counter by extending the RTC_SSR
register to 32-bit and using it as a binary counter. However as the summary by Efton notes, this counter and some other features are not present on every MCU, so beware.
Correspondingly, the block diagram for the RTC2 peripheral is rather more complicated:
Block diagram of the RTC 2 peripheral in the STM32F401 MCU. (Source: ST, RM0368)
Although we can still see the prescaler and backup/tamper registers, the prescaler is significantly more complex with added calibration options, the alarms span more registers and there are now three shadow registers for the time, date and sub-seconds in RTC_TR
, RTC_DR
and RTC_SSR
respectively. This is practically identical to the RTC3 block diagram.
These shadow registers lay out the individual values as for example in the RTC_TR
register:
The RTC_TR
register in the STM32F401. (Source: ST, RM0368)
Taking the seconds as an example, we got the tens (ST
) and units (SU
), both in BCD format which together form the current number of seconds. For the minutes and hours the same pattern is used, with PM
keeping track of whether it’s AM or PM if 12 hour format is used. Effectively this makes these shadow registers a direct source of time and calendar information, albeit generally in BCD format and unlike with the basic RTC1 peripheral, using it as the source for C-style functions via Newlib has become rather tricky.
Unix Time Things
In the world of computing the ‘seconds since the Unix Epoch’ thing has become rather defining as the starting point for many timing-related functions. One consequence of this is that indicating a point in time often involves listing the number of seconds since said epoch on January 1st of 1970, at 00:00:00 UTC. This includes the time-related functions in the standard C libraries, such as Newlib, as discussed earlier.
This is perhaps the most frustrating point with these three-ish different STM32 RTC peripherals, as although the RTC1 is barebones, making it work with Newlib is a snap, while RTC2 and RTC3 are for the most part a nightmare, except for the RTC3 implementations that support the binary mode, although even that is a down-counter instead of an up-counter. This leaves one with the dreadful task of turning those shadow register values back into a Unix timestamp.
One way to do this is by using the mktime()
function as mentioned earlier. This takes a tm
struct whose fields define the elements of a date, e.g. for seconds:
tm tt;
tt.tm_sec = (uint8_t) bcd2dec32(RTC_TR & (RTC_TR_ST | RTC_TR_SU));
By repeating this for each part of RTC_TR
and RTC_DR
, we end up with a filled in struct that we can pass to mktime
which will then spit out our coveted Unix timestamp in the form of a time_t
integer. Of course, that would be far too easy, and thus we run head-first into the problem that mktime
is incredibly picky about what it likes, and makes this implementation-dependent.
For example, despite the claims made about ranges for the tm
struct, running a simple local test case in an MSYS2 environment indicated that negative years since 1970 wasn’t allowed, so that not having the RTC set to a current-ish date will always error out when the year is less than 71. It’s quite possible that a custom alternative to mktime
will be less headache-inducing here.
Of course, ST could just have been nice and offered the basic counter of RTC1 along with all of the good stuff added with RTC2 and RTC3, but maybe for that we’ll have to count the seconds until the release of RTC4.
hackaday.com/2025/09/10/bare-m…






