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 (BKPxR on F4, RTC_BKPxR on 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_ALARM output

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

CheckWhy
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 WUTRWUTR 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.h register definitions
  • STMicroelectronics — LSE crystal tuning and RTC calibration (AN2867)