Backup Domain Architecture
The backup domain consists of:
- RTC — calendar (hours/minutes/seconds + date/weekday/month/year) running from LSE (32.768 kHz) or LSI (~32 kHz)
- 20×32-bit backup registers (
BKPxRon F4,RTC_BKPxRon newer series) — retain data during VBAT-only power - Tamper detection — up to 3 tamper pins with configurable edge/level sensitivity, optional timestamp capture
- Wake-up timer — a standalone down-counter that can wake the system from STOP 0/1/2 and STANDBY
- RTC alarms — two configurable alarms (A and B) that assert
RTC_ALARMoutput
The power supply switch between VDD and VBAT is internal: when VDD drops below the VBAT threshold, the backup domain transparently switches to VBAT. On STM32L4/U5, the backup domain also includes the LPTIM and the PC13–PC15 GPIOs configured as RTC outputs.
Access and Write Protection
The backup domain registers live on the APB1 bus but are write-protected by default. Writing to RTC registers, backup registers, or RTC control registers requires unlocking the Power Control and Backup Domain:
// Step 1: Enable the PWR and RTC clock
RCC->APB1ENR |= RCC_APB1ENR_PWREN; // enable PWR interface
PWR->CR1 |= PWR_CR1_DBP; // enable backup domain write access
// Wait for DBP to be set (usually immediate, but check)
while (!(PWR->CR1 & PWR_CR1_DBP));
// Step 2: Enable RTC clock, select LSE as source
RCC->BDCR |= RCC_BDCR_RTCEN; // enable RTC clock
RCC->BDCR |= RCC_BDCR_RTCSEL; // select LSE as RTC clock source
The order matters. Writing to RTC registers before DBP is set will be silently ignored — no error, no effect. This is one of the most common RTC debugging traps.
RTC Calendar Initialisation
On STM32F4 and newer, the RTC peripheral uses a unified register set. The calendar is configured through the initialisation mode, a protected state that can only be entered when the RTC is not initialised or after a system reset.
// Enter init mode
RTC->ISR |= RTC_ISR_INIT; // request init mode
while (!(RTC->ISR & RTC_ISR_INITF)); // wait until RTC acknowledges init mode
// Configure prescaler (LSE = 32768 Hz)
RTC->PRER = (0x7F << 16) // async prescaler = 128 → 256 Hz
| (0xFF << 0); // sync prescaler = 256 → 1 Hz
// Set initial time: 14:30:00
RTC->TR = (0 << RTC_TR_PM_Pos) // AM/PM, active if FMT=12h
| (0x14 << RTC_TR_HT_Pos) // hours tens = 1
| (4 << RTC_TR_HU_Pos) // hours units = 4 (so 14)
| (3 << RTC_TR_MNT_Pos) // minutes tens = 3
| (0 << RTC_TR_MNU_Pos) // minutes units = 0
| (0 << RTC_TR_ST_Pos) // seconds tens = 0
| (0 << RTC_TR_SU_Pos); // seconds units = 0
// Set initial date: 2026-06-20
RTC->DR = (0 << RTC_DR_YT_Pos) // year tens = 0
| (6 << RTC_DR_YU_Pos) // year units = 6 → 2026
| (0 << RTC_DR_WDU_Pos) // weekday
| (6 << RTC_DR_MU_Pos) // month units = 6 → June
| (2 << RTC_DR_DT_Pos) // date tens = 2
| (0 << RTC_DR_DU_Pos); // date units = 0 → 20th
// Format is 24-hour by default
RTC->CR &= ~RTC_CR_FMT; // 24-hour format
// Exit init mode
RTC->ISR &= ~RTC_ISR_INIT; // leave init mode
// Wait for register synchronisation (~2 RTCCLK cycles)
RTC->ISR &= ~RTC_ISR_RSF; // clear the sync flag
while (!(RTC->ISR & RTC_ISR_RSF)); // wait for shadow registers to sync
The shadow registers (RTC_TR, RTC_DR) are updated asynchronously from the actual RTC counter. Reading back immediately after write may return stale data. The RSF flag guarantees the shadow registers are synchronised.
Backup Registers: Persisting State Across Resets
The 20 backup registers (RTC_BKP0R through RTC_BKP19R on L4/G4/U5, or BKPxR on F4 with a separate BKP peripheral) survive system reset, wake-up from STANDBY, and VBAT-only power. They are the simplest way to detect if software has booted for the first time after a complete power loss:
// On boot, check a magic value in backup register 0
if (RTC->BKP0R != 0xDEADBEEF) {
// First boot after complete power loss (or reset of backup domain)
init_rtc_calendar(); // set date/time from external source
RTC->BKP0R = 0xDEADBEEF; // mark as initialised
RTC->BKP1R = current_boot_count; // track number of power-on events
} else {
// RTC is running from VBAT — no need to reinitialise
read_backup_counter();
}
// Store a time-stamped event before shutdown
RTC->BKP2R = event_code;
RTC->BKP3R = RTC->TR; // capture current time
RTC->BKP4R = RTC->DR; // capture current date
Backup registers are also the simplest way to communicate between the main CPU and a firmware updater running from system memory, since they survive a software reset.
Tamper Detection with Timestamp
The RTC tamper peripheral monitors up to 3 external pins (TAMP1–TAMP3). On detecting a tamper event (rising edge, falling edge, or low level), it can:
- Clear the backup registers (anti-forensic feature)
- Capture the exact timestamp in
RTC_TSTR/RTC_TSDR - Generate an interrupt
- Assert the RTC_ALARM output on PC13
// Configure TAMP1 on PC13
RTC->TAMPCR |= RTC_TAMPCR_TAMP1E; // enable tamper 1
RTC->TAMPCR |= RTC_TAMPCR_TAMP1TRG; // active edge: falling (high→low)
RTC->TAMPCR |= RTC_TAMPCR_TAMPIE; // interrupt enable
RTC->TAMPCR |= RTC_TAMPCR_TSIE; // timestamp interrupt enable
// Optional: validate with 1-2 RTC clock samples (debounce)
RTC->TAMPCR |= RTC_TAMPCR_TAMPFLT_0; // 2 consecutive samples
// Enable timestamp
RTC->CR |= RTC_CR_TSE; // timestamp enable
RTC->CR |= RTC_CR_TSIE; // timestamp interrupt
// In the RTC interrupt handler:
void RTC_IRQHandler(void) {
if (RTC->ISR & RTC_ISR_TAMP1F) {
uint32_t tamper_time = RTC->TSTR; // read timestamp hours/min/sec
uint32_t tamper_date = RTC->TSDR; // read timestamp date
RTC->ISR &= ~RTC_ISR_TAMP1F; // clear flag
}
if (RTC->ISR & RTC_ISR_TSF) {
RTC->ISR &= ~RTC_ISR_TSF; // clear timestamp flag
}
}
On STM32L4+/U5, tamper is enhanced with monotonic counter and active tamper — the RTC actively drives a pattern on the tamper pin and checks for the expected response, detecting short circuits and trace cuts. This is available on U575 and L4P5/L4Q5 series.
Wake-Up from STOP/STANDBY Using the RTC
The RTC wake-up timer is a 16-bit down-counter that counts the RTC clock cycles (or its prescaled derivatives) and generates a wake-up event when it reaches zero. This is the standard way to achieve periodic wake-up in battery-powered products.
// Configure wake-up timer for a 10-second interval
// WUCKSEL = 3 → CK_SPRE (1 Hz, the sync prescaler output)
// Wake-up time = (WUT + 1) * 1 second
// Enter init mode (required for WUT and ALARM config)
RTC->ISR |= RTC_ISR_INIT;
while (!(RTC->ISR & RTC_ISR_INITF));
RTC->WUTR = 9999; // wake-up auto-reload value
RTC->CR = (RTC->CR & ~RTC_CR_WUCKSEL_Msk) // clear WUCKSEL field
| (3 << RTC_CR_WUCKSEL_Pos) // CK_SPRE = 1 Hz
| RTC_CR_WUTE; // enable wake-up timer
RTC->CR |= RTC_CR_WUTIE; // enable wake-up interrupt
RTC->ISR &= ~RTC_ISR_INIT; // exit init mode
// Now enter STOP mode on the main CPU
PWR->CR1 |= PWR_CR1_LPDS; // low-power regulator in STOP
PWR->CR1 |= PWR_CR1_PDDS; // enter STANDBY on deepsleep
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; // select deepsleep
__WFI(); // enter STOP or STANDBY
The wake-up timer operates from the LSE or LSI regardless of the system clock state. This means the RTC wake-up works even when the main HSI/HSE/PLL is stopped during STOP mode. For STANDBY wake-up, the wake-up timer is the only RTC-based option (alarms are not available in STANDBY on F4 — check the reference manual for your family).
Practical Example: IoT Sensor Node 15-Minute Wake Cycle
A common pattern for battery-powered IoT nodes: sleep for 15 minutes, read sensor, transmit data, sleep again. The RTC backup domain is the key enabler.
#define WAKEUP_INTERVAL_S 900 // 15 minutes
void enter_low_power_cycle(void) {
// Save time-stamped data before sleeping
RTC->BKP2R = last_sensor_value;
RTC->BKP3R = RTC->TR; // save time of last reading
// Set wake-up for next interval
RTC->ISR |= RTC_ISR_INIT;
while (!(RTC->ISR & RTC_ISR_INITF));
RTC->WUTR = WAKEUP_INTERVAL_S - 1;
RTC->CR = (RTC->CR & ~RTC_CR_WUCKSEL_Msk)
| (3 << RTC_CR_WUCKSEL_Pos)
| RTC_CR_WUTE
| RTC_CR_WUTIE;
RTC->ISR &= ~RTC_ISR_INIT;
// Configure tamper to detect enclosure opening
RTC->TAMPCR |= RTC_TAMPCR_TAMP1E
| RTC_TAMPCR_TAMP1TRG
| RTC_TAMPCR_TAMPIE;
// Enter STOP 2 mode (L4 series)
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
__WFI();
}
void RTC_WKUP_IRQHandler(void) {
if (RTC->ISR & RTC_ISR_WUTF) {
RTC->ISR &= ~RTC_ISR_WUTF; // clear wake-up flag
EXTI->PR1 |= EXTI_PR1_PR20; // clear EXTI line 20
}
}
Practical Checklist
| Check | Why |
|---|---|
| DBP bit set before any backup register write? | All writes silently ignored without backup domain write access |
| LSE stable before selecting RTC clock? | RTC may run at wrong frequency or not at all |
| RSF flag checked before reading calendar? | Shadow registers may return stale data |
| Backup register magic value on boot? | Prevents reinitialising RTC on every reset |
| Tamper pin pull configured? | Floating tamper pin causes false events |
| WUCKSEL matches your expected interval? | Wrong prescaler = seconds or minutes off by factor of 256 |
| Wake-up EXTI line enabled in SYSCFG? | RTC wake-up interrupt must be routed through EXTI to wake from STOP |
| STANDBY vs STOP wake-up sources? | Alarms don't wake from STANDBY on F4; only WUT does |
| VBAT pin connected to battery or supercap? | Without VBAT, backup domain loses state when VDD is removed |
How I Would Approach This on a Client Project
On a recent STM32L4-based environmental sensor product, we needed to maintain accurate UTC time across months of battery operation with the main CPU in STOP 2 mode 99.8% of the time. The RTC backup domain was the backbone:
- The backup registers held a monotonic boot counter, last calibration offset, and an AES-GCM nonce seed — all surviving system resets.
- The wake-up timer cycled the system every 15 minutes for sensor reading + LoRa transmission, using the LSE-calibrated 1 Hz output as WUCKSEL.
- The tamper detection on TAMP1 (connected to a magnetic reed switch on the enclosure) cleared the backup registers on enclosure opening — satisfying the anti-tamper requirement for a metering application.
The one issue that cost us a day: the wake-up timer would occasionally fire 1–2 seconds early. Root cause: the sync prescaler output (CK_SPRE) is derived from the asynchronous prescaler cascade, and the RTC_ISR_INIT entry briefly stopped the clock dividers, resetting the prescaler phase. The fix was to configure the wake-up timer without entering init mode when only changing WUTR — WUTR can be written with WUTE = 1 using RTC_ISR_WUTWF. The reference manual notes this under "Updating WUTR when WUTE = 1".
On STM32U5, the backup domain gains three important enhancements: active tamper (pattern-based), monotonic counter (tamper-proof rollback detection), and a dedicated VBAT supply for the SRAM2 retention. If your client requires security certification, the U5 backup domain features are worth the premium over L4.
Sources consulted:
- STM32F4 Reference Manual (RM0090) — Chapter 14: RTC, Chapter 5: Reset and clock control (RCC)
- STM32L4 Reference Manual (RM0351) — Chapter 14: RTC, Chapter 6.4: Backup domain
- STM32G4 Reference Manual (RM0440) — Chapter 16: RTC
- STM32U5 Reference Manual (RM0456) — Chapter 19: RTC
- ST Application Note AN4759 — RTC tamper and timestamp configuration
- ST Application Note AN3371 — Using the STM32 real-time clock
- CMSIS-Core 5.6.0 —
stm32l4xx.hregister definitions - STMicroelectronics — LSE crystal tuning and RTC calibration (AN2867)

💬 Comments
Have questions or experience with STM32 RTC backup domain, tamper detection, or low-power wake-up? Drop me an email. Include the article slug in the subject line.