Embedded HandbookApps
All sections › II. Core and system mechanisms
STM32G474, STM32G431 · level intermediate · 25 min read
Open in the interactive handbook

12а. The new-style RTC: subseconds, calibration, wakeup, TAMP

Section 12.6 covered the F1 clock — a seconds counter — and the F4 calendar with two alarms. But the clock of the F4 and newer series also has what was left off-screen there: subseconds, shifting the clock by a fraction of a second, trimming the crystal frequency in parts per million (ppm) and the wakeup timer. The G4 adds a separate TAMP block: tamper inputs, internal alarm sources and 32 backup registers. Here is all of that on the G474: formulas, calculations with them and register-level code.

Prerequisites: reference manual RM0440 (G4), chapters 35 "RTC" and 36 "TAMP", the RCC_BDCR register and the PWR section on wakeup; the headers stm32g474xx.h, stm32g431xx.h and stm32f407xx.h and the HAL library for G4 from the Keil packs. The calculations were done with a PC model and checked against the RM0440 formulas; the code was built with armclang for the G474 and G431. QEMU does not model the G4 clock — not checked on hardware.

Terms used in the documentation

Term Meaning
sub second register (SSR) the subsecond register: the synchronous prescaler counter
asynchronous, synchronous prescaler (PREDIV_A, PREDIV_S) the two dividers in series
shift control (SHIFTR), ADD1S, SUBFS shifting the clock back or forward by a fraction of a second
smooth digital calibration (CALR), CALP, CALM inserting or removing pulses to trim the rate
wakeup timer (WUT), WUCKSEL the wakeup timer and the choice of its clock
alarm sub second mask (MASKSS) how many low bits of SS the alarm compares
shadow registers, BYPSHAD shadow copies of the calendar; BYPSHAD reads past them
timestamp (TSE, ITSE, TAMPTS) the calendar captured at the moment of an event
tamper, internal tamper intrusion detection: an external input or an internal source
backup registers (TAMP_BKPxR) registers kept in the backup domain
no erase (TAMPxNOER) do not erase the backup registers on tamper

12а.1 What is new compared with the F4

Subseconds, shift, smooth calibration and the wakeup timer already exist on the F407: its header has the registers SSR, SHIFTR, CALR and WUTR. The G474 has the same set, but the block has been rearranged:

F407 G474
configuration mode and flags in one ISR (0x0C) ICSR (0x0C) — configuration, SR (0x50) — flags
clearing a flag zero in ISR, preserving INIT (section 12.6) a one in SCR (0x5C)
calibration coarse CALIBR (0x18) and smooth CALR (0x3C) smooth CALR (0x28) only
tamper TAFCR (0x40) inside the RTC a separate TAMP block: 3 inputs and 4 internal sources
backup registers RTC_BKP0R…BKP19R: 20 of them, 80 bytes TAMP_BKP0R…BKP31R: 32 of them, 128 bytes

The G431 has 16 backup registers — 64 bytes: RM0440 gives 32 to devices of categories 3 and 4 and 16 to category 2, and the pack headers confirm this. F4 code for the backup registers will not build on the G4: there is no RTC->BKP0R there, there is TAMP->BKP0R.

RTC on the G4: the 32 768 Hz watch crystal goes through smooth calibration and the PREDIV_A divider, giving 256 Hz; the SS counter counts down from PREDIV_S to zero and supplies 1 Hz to the calendar; the fraction of a second is PREDIV_S minus SS divided by PREDIV_S plus 1; SHIFTR adds SUBFS to SS; the alarms also compare SS bits; the WUT wakeup timer takes its clock from the crystal through a divider of 2–16 or 1 Hz and wakes through EXTI line 20; the TAMP block with 32 backup registers writes a timestamp and erases the registers on tamper

Enabling at register level. On the G4 the RTC has its own bus clock — the RTCAPBEN bit, and the clock source is selected in RCC_BDCR once: RTCSEL can later be changed only by a backup domain reset or after an LSE failure:

void rtc_lse_on(void)
{
    RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN | RCC_APB1ENR1_RTCAPBEN;   /* у G4 у RTC свой такт шины */
    PWR->CR1 |= PWR_CR1_DBP;                /* доступ к резервному домену */
    RCC->BDCR |= RCC_BDCR_LSEON;
    while (!(RCC->BDCR & RCC_BDCR_LSERDY)) { }
    RCC->BDCR |= RCC_BDCR_RTCSEL_0 | RCC_BDCR_RTCEN;   /* 01 — LSE; сменить потом — только сбросом домена */
}

The write protection keys in RTC_WPR are the same as on the F4: 0xCA and 0x53. In the examples below they are written by rtc_unlock, and rtc_lock restores protection by writing 0xFF.


12а.2 Two prescalers and subseconds

A second is made from 32 768 Hz by two prescalers in series (RM0440, 35.3.4):

ck_apre = 32 768 / (PREDIV_A + 1), 1 Hz = ck_apre / (PREDIV_S + 1).

The asynchronous PREDIV_A is 7 bits, the synchronous PREDIV_S is 15 bits; the total division ratio is from 1 to 2²² = 4 194 304. The synchronous one is the SS counter itself in the RTC_SSR register: it counts down from PREDIV_S to zero at the ck_apre clock. Hence the fraction of a second:

fraction = (PREDIV_S − SS) / (PREDIV_S + 1).

With the default prescalers (127 and 255) SS = 191 is 0.25 s, SS = 127 is 0.5 s, SS = 0 is 0.99609 s. The step is 1 / (PREDIV_S + 1), and it can be made finer by moving the division into the synchronous prescaler:

PREDIV_A PREDIV_S ck_apre Subsecond step CALP
127 255 256 Hz 3906.25 µs allowed
31 1023 1024 Hz 976.56 µs allowed
7 4095 4096 Hz 244.14 µs allowed
3 8191 8192 Hz 122.07 µs allowed
0 32767 32768 Hz 30.52 µs not allowed

The price of a fine step is current: the faster ck_apre, the higher the consumption of the clock may be, and RM0440 advises keeping PREDIV_A large (the manual gives no current figures). The second limitation is calibration: with PREDIV_A less than 3 CALP has no effect (12а.3). So 3 and 8191 give the finest step, 122 µs, with the full calibration range. Two more conditions from RM0440: an alarm on seconds requires PREDIV_S of at least 3, and trimming to the 50 or 60 Hz mains (REFCKON) requires the default prescalers.

The prescalers are written in configuration mode, and RM0440 requires PRER to be written in two separate accesses (HAL for G4 writes it in one):

void rtc_prescalers(uint32_t a, uint32_t s)
{
    rtc_unlock();
    RTC->ICSR |= RTC_ICSR_INIT;             /* календарь стоит */
    while (!(RTC->ICSR & RTC_ICSR_INITF)) { }
    RTC->PRER = s;                          /* RM0440: две раздельные записи */
    RTC->PRER = s | (a << RTC_PRER_PREDIV_A_Pos);
    RTC->ICSR &= ~RTC_ICSR_INIT;            /* пошёл через 4 такта RTCCLK */
    rtc_lock();
}

INITF rises after about 2 RTCCLK cycles — 61.0 µs, and after INIT is cleared the calendar starts after 4 cycles — 122.1 µs.

Shifting by a fraction of a second

If the clock has to be brought into line with exact time — from GPS, a time server, another device — there is no need to stop the calendar. A write to RTC_SHIFTR adds SUBFS to the SS counter, and since it counts down, the clock falls behind by SUBFS / (PREDIV_S + 1) s. With the ADD1S bit a whole second is added to this, and the clock moves forward by 1 − SUBFS / (PREDIV_S + 1) s:

Needed PREDIV_S SHIFTR Result
back by 0.1 s 255 0x0000001A −0.101562 s
back by 0.1 s 8191 0x00000333 −0.099976 s
forward by 0.25 s 255 0x800000C0 +0.250000 s
forward by 0.25 s 8191 0x80001800 +0.250000 s

A model of the counter on the PC — SS goes down, the shift adds SUBFS to it — after three seconds of running gives the same difference as the formula. The accuracy of the shift is the subsecond step: with 255 a shift back by 0.1 s is not exact, the error is 1.56 ms.

int rtc_shift(uint32_t add1s, uint32_t subfs)
{
    if (RTC->SSR & 0x8000u)                 /* SS[15] = 1 — новый сдвиг переполнит SS */
        return -1;
    rtc_unlock();
    while (RTC->ICSR & RTC_ICSR_SHPF) { }
    RTC->SHIFTR = (add1s ? RTC_SHIFTR_ADD1S : 0u) | subfs;
    rtc_lock();
    return 0;
}

After a shift SS may be greater than PREDIV_S, and then, according to RM0440, the correct time is one second less than in TR. The fraction formula then simply gives a negative number — compute it as signed:

int32_t rtc_ms(uint32_t ss, uint32_t prediv_s)
{
    return ((int32_t)prediv_s - (int32_t)ss) * 1000 / ((int32_t)prediv_s + 1);
}

12а.3 Smooth calibration: ppm instead of a trimmer capacitor

A watch crystal rarely gives exactly 32 768 Hz. An error of +20 ppm is 1.728 s a day and 51.8 s in 30 days, of −35 ppm it is 3.024 s a day and 90.7 s a month. The RTC_CALR register corrects this without trimming the crystal: in a cycle of 2²⁰ pulses — 32 s — it removes CALM pulses (0…511), and the CALP bit adds 512 (RM0440, 35.3.13):

FCAL = F × [1 + (CALP × 512 − CALM) / (2²⁰ + CALM − CALP × 512)].

The range is from −487.1 to +488.5 ppm, the step is 0.9537 ppm. Selection: CALM − 512 × CALP ≈ 2²⁰ × the error. For +20 ppm this is 20.97 — CALM = 21; for −35 ppm it is −36.70 — set CALP and CALM = 512 − 37 = 475. A PC model tries all 1024 combinations and chooses the best:

Crystal Cycle CALR Residual Drift per day
+20 ppm 32 s 0x0015 −0.027 ppm 0.002 s
+20 ppm 16 s 0x2014 +0.926 ppm 0.080 s
+20 ppm 8 s 0x4014 +0.926 ppm 0.080 s
−35 ppm 32 s 0x81DB +0.286 ppm 0.025 s
−35 ppm 16 s 0xA1DC −0.668 ppm 0.058 s
−35 ppm 8 s 0xC1DC −0.668 ppm 0.058 s

A short cycle (CALW16, CALW8) is needed to check the result faster, but it coarsens the step: the low bit of CALM sticks at zero with 16 s, the two low bits with 8 s. The rate is checked on the CALIB output on RTC_OUT1 (PC13): with the default prescalers this is 512 Hz with COSEL = 0 or 1 Hz with COSEL = 1. If you measure exactly one cycle, the measurement error is half a clock per window: 0.477 ppm over 32 s, 0.954 ppm over 16 s, 1.907 ppm over 8 s.

The removed pulses are spread over the cycle: according to RM0440, the CALM[0] bit removes a pulse at cal_cnt = 0x80000, CALM[1] at 0x40000 and 0xC0000 and so on up to CALM[8] — 256 pulses of the form 0xXX800. A PC model lays them out by these rules: for any CALM and any cycle start, the lag behind uniform is no more than 1.89 pulses. So the clock is calibrated over short intervals too, not only once every 32 s. A simple count of pulses per cycle differs from the RM formula by no more than 0.24 ppm — less than one step.

CALR can be changed on the fly, but only when RECALPF = 0: until the previous value has been accepted, the register is locked. The new value takes effect within three ck_apre cycles — 11.72 ms at 256 Hz:

void rtc_calibrate(uint32_t calr)
{
    rtc_unlock();
    while (RTC->ICSR & RTC_ICSR_RECALPF) { }    /* прошлая запись ещё не принята */
    RTC->CALR = calr;
    rtc_lock();
}

12а.4 The wakeup timer and an alarm with subseconds

The alarm from section 12.6 is tied to the calendar, and for "wake up in N" there is a separate 16-bit timer WUT: it counts down from the value of RTC_WUTR, every WUT + 1 cycles it raises WUTF and reloads itself. The clock is chosen by WUCKSEL (RM0440, 35.3.7):

WUCKSEL Clock Step Period
000 RTCCLK / 16 488.28 µs from 0.488 ms to 32 s
001 RTCCLK / 8 244.14 µs from 0.244 ms to 16 s
010 RTCCLK / 4 122.07 µs from 0.122 ms to 8 s
011 RTCCLK / 2 61.04 µs from 0.122 ms to 4 s
10x ck_spre, 1 Hz 1 s from 1 to 65 536 s — 18.2 h
11x ck_spre plus 2¹⁶ 1 s from 65 537 to 131 072 s — 36.4 h

With RTCCLK / 2 the value WUT = 0 is forbidden, so the minimum is two cycles. With 11x 2¹⁶ is added to the counter: the timer becomes 17-bit. The choice by period is the finest step into which the period fits:

Period WUCKSEL WUT Result
1 ms 011 15 (0x000F) 0.977 ms, −2.344 %
100 ms 011 1637 (0x0665) 99.976 ms, −0.024 %
1 s 011 16383 (0x3FFF) exactly 1 s
10 min 100 599 (0x0257) exactly 600 s
24 h 110 20863 (0x517F) exactly 86 400 s

A second can also be taken from ck_spre with WUT = 0 — the same clock that drives the calendar. A millisecond cannot be exact: a 32 768 Hz crystal has no whole number of cycles in 1 ms.

void rtc_wakeup(uint32_t wucksel, uint32_t wut)
{
    rtc_unlock();
    RTC->CR &= ~(RTC_CR_WUTE | RTC_CR_WUTIE);
    while (!(RTC->ICSR & RTC_ICSR_WUTWF)) { }   /* WUTR и WUCKSEL можно менять */
    RTC->WUTR = wut;
    RTC->CR = (RTC->CR & ~RTC_CR_WUCKSEL) | wucksel;
    RTC->SCR = RTC_SCR_CWUTF;
    RTC->CR |= RTC_CR_WUTIE | RTC_CR_WUTE;
    rtc_lock();
    EXTI->IMR1 |= EXTI_IMR1_IM20;           /* из Stop будит линия EXTI 20 */
    EXTI->RTSR1 |= EXTI_RTSR1_RT20;
    NVIC_EnableIRQ(RTC_WKUP_IRQn);
}

void RTC_WKUP_IRQHandler(void)
{
    if (RTC->SR & RTC_SR_WUTF) {
        RTC->SCR = RTC_SCR_CWUTF;           /* единица в SCR — без ключей и без INIT */
        wakeups++;
    }
    EXTI->PR1 = EXTI_PR1_PIF20;
}

The timer wakes from Stop through EXTI line 20 on the rising edge (RM0440, 6.3.10), the alarm through line 17. The first WUTF comes not after WUT + 1 cycles, but between WUT and WUT + 1 after WUTE: for 10 minutes between 599 and 600 s, and with ck_spre and WUT = 0 at any moment of the first second.

Alarm with subseconds

Next to RTC_ALRMAR from section 12.6 the G4 has RTC_ALRMASSR: the SS value and MASKSS — how many of its low bits to compare. If all fields of ALRMAR are masked, the mask alone gives a periodic alarm. A PC model with PREDIV_S = 255:

MASKSS Compared Fires
0 nothing, fires on the change of second once every 1 s
1 SS[0] once every 7.812 ms
6 SS[5:0] once every 0.25 s
7 SS[6:0] once every 0.5 s
8 SS[7:0] once every 1 s
15 all 15 bits once every 1 s

An alarm "every day at 07:15:00.500" is ALRMAR = 0x80071500 from section 12.6 and ALRMASSR = 0x0F00007F: SS = 127 is the middle of the second. Writing is as on the F4, after ALRAWF, only the flag is cleared by a one in SCR:

void rtc_alarm_a_ss(uint32_t alrmar, uint32_t maskss, uint32_t ss)
{
    rtc_unlock();
    RTC->CR &= ~(RTC_CR_ALRAE | RTC_CR_ALRAIE);
    while (!(RTC->ICSR & RTC_ICSR_ALRAWF)) { }
    RTC->ALRMAR = alrmar;
    RTC->ALRMASSR = (maskss << RTC_ALRMASSR_MASKSS_Pos) | ss;
    RTC->SCR = RTC_SCR_CALRAF;
    RTC->CR |= RTC_CR_ALRAE | RTC_CR_ALRAIE;
    rtc_lock();
}

12а.5 Reading the calendar: shadow registers and BYPSHAD

RTC_SSR, RTC_TR and RTC_DR are by default read from shadow copies: the calendar is copied there all the time and raises RSF. The rules of RM0440 (35.3.9):

void rtc_resync(void)
{
    rtc_unlock();
    RTC->ICSR &= ~RTC_ICSR_RSF;
    rtc_lock();
    while (!(RTC->ICSR & RTC_ICSR_RSF)) { }
}

With BYPSHAD = 1 there is no need to wait for RSF — the values come straight from the counters, but nobody promises consistency either: a clock tick may come between the reads. A PC model sets the clock to 12:00:00 with SS = 0 — the last cycle of the second — and lets it tick right after the first read:

How it was read Shown Actually Error
TR, then SSR 12:00:00.000 12:00:01.000 −1 s
SSR, then TR 12:00:01.996 12:00:01.000 +0.996 s
SSR, TR, DR and SSR again 12:00:01.000 12:00:01.000 none

The cure is the same as for the F1 counter in section 12.6: re-read the lowest register and repeat if it has changed. RM0440 advises exactly this — compare two reads of the lowest calendar register. In the rare case of a tick between the reads this is eight accesses instead of four:

void rtc_read(rtc_now_t *t)
{
    do {
        t->ss = RTC->SSR;
        t->tr = RTC->TR;
        t->dr = RTC->DR;
    } while (t->ss != RTC->SSR);
}

12а.6 TAMP: tamper, timestamp, backup registers

The G474 has 32 backup registers TAMP_BKP0R…BKP31R — from 0x4000 2500 to 0x4000 257C. Neither a system reset nor exit from Standby resets them, and they are powered from VBAT. Writing requires DBP, like everything in the backup domain (section 10.5):

void bkp_write(uint32_t i, uint32_t v)
{
    (&TAMP->BKP0R)[i] = v;                  /* нужен DBP; пока стоит флаг вмешательства — не запишется */
}

armclang turns it into a single write to address 0x4000 2500 + 4 × i:

bkp_write: MOVW  r2,#0x2500
           MOVT  r2,#0x4000
           STR   r1,[r2,r0,LSL #2]
           BX    lr

Tamper erases them. By default any TAMP event clears all backup registers, and they are held at zero while at least one tamper flag is set. Not erasing can be chosen per input: TAMPxNOER or TAMPxMSK in TAMP_CR2 — in the header these bits are called TAMP1NOERASE and TAMP1MF. The registers are also erased when Flash read protection is lowered from level 1 to 0.

There are seven tamper sources. Three external inputs — TAMP_IN1…TAMP_IN3; four internal: LSE failure, HSE failure, calendar overflow (31 December of year 99, 23:59:59 — after that the calendar stops) and ST factory read-out. After a backup domain reset TAMP_CR1 = 0xFFFF 0000 — the bits ITAMP3E…ITAMP6E are already set.

An external input works in two ways:

The detector works even with VDD removed, so the input must be reliably pulled to the inactive level — otherwise noise will erase the registers. Example: when opened, the case lid shorts TAMP_IN1 (PC13) to ground, 4 samples at 16 Hz — 0.25 s:

void tamp_in1_setup(void)
{
    TAMP->FLTCR = (2u << TAMP_FLTCR_TAMPFLT_Pos) | (4u << TAMP_FLTCR_TAMPFREQ_Pos);
    TAMP->CR2 &= ~(TAMP_CR2_TAMP1NOERASE | TAMP_CR2_TAMP1MF | TAMP_CR2_TAMP1TRG);  /* низкий уровень — вскрытие */
    TAMP->IER |= TAMP_IER_TAMP1IE;
    TAMP->CR1 |= TAMP_CR1_TAMP1E;
    rtc_unlock();
    RTC->CR |= RTC_CR_TAMPTS;               /* метка времени на вмешательство, даже при TSE = 0 */
    rtc_lock();
    EXTI->IMR1 |= EXTI_IMR1_IM19;           /* вмешательство и метка — линия EXTI 19 */
    EXTI->RTSR1 |= EXTI_RTSR1_RT19;
    NVIC_EnableIRQ(RTC_TAMP_LSECSS_IRQn);
}

A timestamp is the calendar written into RTC_TSSSR, RTC_TSTR and RTC_TSDR at the moment of an event. There are three events: an edge on RTC_TS (bit TSE), switching to the battery (ITSE) and tamper (TAMPTS). TSF rises after 2 ck_apre cycles — 7.81 ms at 256 Hz; a second event while TSF is set raises TSOVF, and the registers keep the first. There is no year in the stamp: RTC_TSDR has only the weekday, month and day. And most importantly — clearing TSF clears the stamp, so read first, then clear:

void RTC_TAMP_LSECSS_IRQHandler(void)
{
    if (TAMP->SR & TAMP_SR_TAMP1F) {
        if (RTC->SR & RTC_SR_TSF) {         /* сначала прочитать: сброс TSF обнуляет метку */
            ts_ss = RTC->TSSSR;
            ts_time = RTC->TSTR;
            ts_date = RTC->TSDR;
            RTC->SCR = RTC_SCR_CTSF;
        }
        TAMP->SCR = TAMP_SCR_CTAMP1F;       /* пока флаг стоит, резервные регистры держатся в нуле */
        tampers++;
    }
    EXTI->PR1 = EXTI_PR1_PIF19;
}

PC13 carries RTC_OUT1, RTC_TS and TAMP_IN1 at once, and these functions work in all sleep modes and from the battery.


12а.7 Pitfalls

The RTC registers do not respond. On the G4 the clock has its own bus clock: RTCAPBEN in RCC_APB1ENR1 is not set.

The clock source will not change. RTCSEL is selected once; changing it is possible only by a backup domain reset (BDRST), and that erases the clock too.

CALP has no effect. PREDIV_A is less than 3 — then RM0440 treats CALP as zero. The finest step with full calibration is prescalers 3 and 8191.

The write to CALR was lost. RECALPF was set: the register is locked until the previous value is accepted.

An odd CALM does not work. CALW16 (or CALW8) is on: the low bits stick at zero, the step is 1.907 ppm (or 3.815 ppm).

The seconds alarm is silent. PREDIV_S is less than 3, and MSK1 = 0.

After wakeup the time is old. In Stop the shadow copies are not updated: clear RSF and wait for it.

The time occasionally jumps by a second. BYPSHAD = 1, and SSR and TR were read without a repeat — a tick fell between the reads.

The first wakeup is early. The first WUTF comes between WUT and WUT + 1 cycles; with ck_spre and WUT = 0 at any moment of the first second.

WUTR will not write. WUTE was not cleared or WUTWF was not waited for.

The shift did not work. SHPF was set (the previous shift is in progress), SS[15] = 1 or REFCKON is on — the shift is incompatible with mains trimming.

The flag is cleared as on the F4. A zero in RTC_SR does nothing — on the G4 the flags are cleared by a one in RTC_SCR.

A backup register will not write. No DBP — or a tamper flag is set: until it is cleared, the registers are held at zero.

The backup registers erased themselves. A tamper input fired (it is floating, or edge mode was enabled while the active level was present), an internal source, or read protection was lowered from level 1 to 0.

The timestamp is empty. TSF was cleared before TSTR and TSDR were read — clearing it zeroes them.

F4 backup register code does not build. On the G4 they are in TAMP, not in RTC: TAMP->BKP0R.


12а.8 Summary

Full sources

rtc12a.c — RTC and TAMP of the STM32G474 at register level.

/* 12а: RTC и TAMP у STM32G474 по регистрам — LSE, делители, гладкая калибровка, сдвиг на доли
 * секунды, таймер пробуждения, будильник с субсекундами, чтение календаря, резервные регистры
 * TAMP, вход вмешательства с меткой времени. Собирается armclang: -DSTM32G474xx (и -DSTM32G431xx —
 * у него резервных регистров 16). Числа — RM0440, главы 35 и 36; проверка — _verify/rtc12a_check.py. */
#include <stdint.h>
#include <stddef.h>

#define WUCKSEL_DIV2    3u                  /* 011: RTCCLK / 2, шаг 61 мкс */
#define WUCKSEL_1HZ     4u                  /* 10x: ck_spre, обычно 1 Гц */
#define WUCKSEL_1HZ_17  6u                  /* 11x: ck_spre, к WUT добавляется 2^16 */
#define CALR_FAST_20PPM 21u                 /* кварц спешит на 20 ppm: замаскировать 21 импульс из 2^20 */
#define CALR_SLOW_35PPM (RTC_CALR_CALP | 475u)  /* отстаёт на 35 ppm: +512 − 475 = +37 импульсов */

_Static_assert(RTC_BASE == 0x40002800u && TAMP_BASE == 0x40002400u, "RTC и TAMP — соседние блоки на APB1");
_Static_assert(offsetof(RTC_TypeDef, SSR) == 0x08u && offsetof(RTC_TypeDef, ICSR) == 0x0Cu
               && offsetof(RTC_TypeDef, PRER) == 0x10u && offsetof(RTC_TypeDef, WUTR) == 0x14u
               && offsetof(RTC_TypeDef, CR) == 0x18u && offsetof(RTC_TypeDef, WPR) == 0x24u
               && offsetof(RTC_TypeDef, CALR) == 0x28u && offsetof(RTC_TypeDef, SHIFTR) == 0x2Cu
               && offsetof(RTC_TypeDef, TSSSR) == 0x38u && offsetof(RTC_TypeDef, ALRMASSR) == 0x44u
               && offsetof(RTC_TypeDef, SR) == 0x50u && offsetof(RTC_TypeDef, SCR) == 0x5Cu,
               "смещения регистров RTC — по таблице 336 RM0440");
_Static_assert(offsetof(TAMP_TypeDef, BKP0R) == 0x100u && RTC_TAMP_NB == 3u && RTC_TAMP_INT_NB == 4u,
               "резервные регистры с 0x100; три внешних входа и четыре внутренних источника");
#if defined(STM32G474xx)
_Static_assert(RTC_BACKUP_NB == 32u && offsetof(TAMP_TypeDef, BKP31R) == 0x17Cu, "у G474 — 32 регистра");
#elif defined(STM32G431xx)
_Static_assert(RTC_BACKUP_NB == 16u && offsetof(TAMP_TypeDef, BKP15R) == 0x13Cu, "у G431 — 16 регистров");
#endif
_Static_assert(RTC_PRER_PREDIV_A_Pos == 16u && RTC_PRER_PREDIV_A_Msk == (0x7Fu << 16)
               && RTC_PRER_PREDIV_S_Msk == 0x7FFFu, "PREDIV_A — 7 бит с 16-го, PREDIV_S — 15 бит");
_Static_assert(RTC_SSR_SS_Msk == 0xFFFFu && RTC_ALRMASSR_SS_Msk == 0x7FFFu && RTC_ALRMASSR_MASKSS_Pos == 24u,
               "SS — 16 бит (15-й — переполнение после сдвига), в будильнике — 15, MASKSS с 24-го бита");
_Static_assert(RTC_CALR_CALP == (1u << 15) && RTC_CALR_CALW8 == (1u << 14) && RTC_CALR_CALW16 == (1u << 13)
               && RTC_CALR_CALM_Msk == 0x1FFu, "CALP, CALW8, CALW16 — биты 15, 14, 13; CALM — 9 бит");
_Static_assert(CALR_FAST_20PPM == 0x0015u && CALR_SLOW_35PPM == 0x81DBu, "CALR для +20 и −35 ppm");
_Static_assert(RTC_SHIFTR_ADD1S == (1u << 31) && RTC_SHIFTR_SUBFS_Msk == 0x7FFFu, "ADD1S — бит 31, SUBFS — 15 бит");
_Static_assert(RTC_CR_WUCKSEL_Msk == 7u && RTC_WUTR_WUT_Msk == 0xFFFFu && RTC_CR_WUTE == (1u << 10)
               && RTC_CR_WUTIE == (1u << 14) && RTC_CR_BYPSHAD == (1u << 5),
               "WUCKSEL — 3 бита, WUT — 16, WUTE — бит 10, WUTIE — 14, BYPSHAD — 5");
_Static_assert(RTC_ICSR_WUTWF == (1u << 2) && RTC_ICSR_SHPF == (1u << 3) && RTC_ICSR_RSF == (1u << 5)
               && RTC_ICSR_INITF == (1u << 6) && RTC_ICSR_INIT == (1u << 7) && RTC_ICSR_RECALPF == (1u << 16),
               "флаги ICSR");
_Static_assert(RTC_SR_WUTF == (1u << 2) && RTC_SCR_CWUTF == RTC_SR_WUTF && RTC_SCR_CTSF == RTC_SR_TSF,
               "флаг в SR и бит его сброса в SCR — на одном месте");
_Static_assert(TAMP_FLTCR_TAMPFREQ_Pos == 0u && TAMP_FLTCR_TAMPFLT_Pos == 3u && TAMP_FLTCR_TAMPPRCH_Pos == 5u
               && TAMP_FLTCR_TAMPPUDIS == (1u << 7), "поля TAMP_FLTCR");
_Static_assert(TAMP_CR2_TAMP1NOERASE == 1u && TAMP_CR2_TAMP1MF == (1u << 16) && TAMP_CR2_TAMP1TRG == (1u << 24),
               "TAMP1NOER и TAMP1MSK из RM в заголовке называются TAMP1NOERASE и TAMP1MF");
_Static_assert(RCC_BDCR_RTCSEL_0 == (1u << 8) && RCC_BDCR_RTCEN == (1u << 15) && RCC_APB1ENR1_RTCAPBEN == (1u << 10)
               && PWR_CR1_DBP == (1u << 8), "RTCSEL = 01 — LSE; RTCEN; такт шины RTC; DBP");
_Static_assert(EXTI_IMR1_IM20 == (1u << 20) && RTC_WKUP_IRQn == 3 && RTC_TAMP_LSECSS_IRQn == 2,
               "таймер пробуждения — линия EXTI 20");

volatile uint32_t wakeups, tampers;
volatile uint32_t ts_time, ts_date, ts_ss;

static void rtc_unlock(void)
{
    RTC->WPR = 0xCAu;                       /* ключи те же, что у F4 */
    RTC->WPR = 0x53u;
}

static void rtc_lock(void)
{
    RTC->WPR = 0xFFu;
}

void rtc_lse_on(void)
{
    RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN | RCC_APB1ENR1_RTCAPBEN;   /* у G4 у RTC свой такт шины */
    PWR->CR1 |= PWR_CR1_DBP;                /* доступ к резервному домену */
    RCC->BDCR |= RCC_BDCR_LSEON;
    while (!(RCC->BDCR & RCC_BDCR_LSERDY)) { }
    RCC->BDCR |= RCC_BDCR_RTCSEL_0 | RCC_BDCR_RTCEN;   /* 01 — LSE; сменить потом — только сбросом домена */
}

/* 1 Гц = 32768 / ((a + 1) × (s + 1)); шаг долей секунды — 1 / (s + 1) */
void rtc_prescalers(uint32_t a, uint32_t s)
{
    rtc_unlock();
    RTC->ICSR |= RTC_ICSR_INIT;             /* календарь стоит */
    while (!(RTC->ICSR & RTC_ICSR_INITF)) { }
    RTC->PRER = s;                          /* RM0440: две раздельные записи */
    RTC->PRER = s | (a << RTC_PRER_PREDIV_A_Pos);
    RTC->ICSR &= ~RTC_ICSR_INIT;            /* пошёл через 4 такта RTCCLK */
    rtc_lock();
}

void rtc_calibrate(uint32_t calr)
{
    rtc_unlock();
    while (RTC->ICSR & RTC_ICSR_RECALPF) { }    /* прошлая запись ещё не принята */
    RTC->CALR = calr;
    rtc_lock();
}

/* назад на subfs / (PREDIV_S + 1) с; с add1s — вперёд на 1 − subfs / (PREDIV_S + 1) */
int rtc_shift(uint32_t add1s, uint32_t subfs)
{
    if (RTC->SSR & 0x8000u)                 /* SS[15] = 1 — новый сдвиг переполнит SS */
        return -1;
    rtc_unlock();
    while (RTC->ICSR & RTC_ICSR_SHPF) { }
    RTC->SHIFTR = (add1s ? RTC_SHIFTR_ADD1S : 0u) | subfs;
    rtc_lock();
    return 0;
}

void rtc_wakeup(uint32_t wucksel, uint32_t wut)
{
    rtc_unlock();
    RTC->CR &= ~(RTC_CR_WUTE | RTC_CR_WUTIE);
    while (!(RTC->ICSR & RTC_ICSR_WUTWF)) { }   /* WUTR и WUCKSEL можно менять */
    RTC->WUTR = wut;
    RTC->CR = (RTC->CR & ~RTC_CR_WUCKSEL) | wucksel;
    RTC->SCR = RTC_SCR_CWUTF;
    RTC->CR |= RTC_CR_WUTIE | RTC_CR_WUTE;
    rtc_lock();
    EXTI->IMR1 |= EXTI_IMR1_IM20;           /* из Stop будит линия EXTI 20 */
    EXTI->RTSR1 |= EXTI_RTSR1_RT20;
    NVIC_EnableIRQ(RTC_WKUP_IRQn);
}

void RTC_WKUP_IRQHandler(void)
{
    if (RTC->SR & RTC_SR_WUTF) {
        RTC->SCR = RTC_SCR_CWUTF;           /* единица в SCR — без ключей и без INIT */
        wakeups++;
    }
    EXTI->PR1 = EXTI_PR1_PIF20;
}

/* будильник A: календарь как в разделе 12 плюс доли секунды — сравниваются SS[maskss−1:0] */
void rtc_alarm_a_ss(uint32_t alrmar, uint32_t maskss, uint32_t ss)
{
    rtc_unlock();
    RTC->CR &= ~(RTC_CR_ALRAE | RTC_CR_ALRAIE);
    while (!(RTC->ICSR & RTC_ICSR_ALRAWF)) { }
    RTC->ALRMAR = alrmar;
    RTC->ALRMASSR = (maskss << RTC_ALRMASSR_MASKSS_Pos) | ss;
    RTC->SCR = RTC_SCR_CALRAF;
    RTC->CR |= RTC_CR_ALRAE | RTC_CR_ALRAIE;
    rtc_lock();
}

typedef struct {
    uint32_t ss, tr, dr;
} rtc_now_t;

/* BYPSHAD = 1: SSR прочитан дважды одинаковым — между чтениями календарь не менялся */
void rtc_read(rtc_now_t *t)
{
    do {
        t->ss = RTC->SSR;
        t->tr = RTC->TR;
        t->dr = RTC->DR;
    } while (t->ss != RTC->SSR);
}

/* BYPSHAD = 0, после выхода из Stop: теневые регистры ещё старые */
void rtc_resync(void)
{
    rtc_unlock();
    RTC->ICSR &= ~RTC_ICSR_RSF;
    rtc_lock();
    while (!(RTC->ICSR & RTC_ICSR_RSF)) { }
}

/* доля секунды в миллисекундах; после сдвига SS бывает больше PREDIV_S — доля отрицательная */
int32_t rtc_ms(uint32_t ss, uint32_t prediv_s)
{
    return ((int32_t)prediv_s - (int32_t)ss) * 1000 / ((int32_t)prediv_s + 1);
}

void bkp_write(uint32_t i, uint32_t v)
{
    (&TAMP->BKP0R)[i] = v;                  /* нужен DBP; пока стоит флаг вмешательства — не запишется */
}

uint32_t bkp_read(uint32_t i)
{
    return (&TAMP->BKP0R)[i];
}

/* TAMP_IN1 (PC13): крышка замыкает вход на землю; 4 выборки подряд при 16 Гц — 0,25 с;
 * резервные регистры стираются, метка времени пишется */
void tamp_in1_setup(void)
{
    TAMP->FLTCR = (2u << TAMP_FLTCR_TAMPFLT_Pos) | (4u << TAMP_FLTCR_TAMPFREQ_Pos);
    TAMP->CR2 &= ~(TAMP_CR2_TAMP1NOERASE | TAMP_CR2_TAMP1MF | TAMP_CR2_TAMP1TRG);  /* низкий уровень — вскрытие */
    TAMP->IER |= TAMP_IER_TAMP1IE;
    TAMP->CR1 |= TAMP_CR1_TAMP1E;
    rtc_unlock();
    RTC->CR |= RTC_CR_TAMPTS;               /* метка времени на вмешательство, даже при TSE = 0 */
    rtc_lock();
    EXTI->IMR1 |= EXTI_IMR1_IM19;           /* вмешательство и метка — линия EXTI 19 */
    EXTI->RTSR1 |= EXTI_RTSR1_RT19;
    NVIC_EnableIRQ(RTC_TAMP_LSECSS_IRQn);
}

void RTC_TAMP_LSECSS_IRQHandler(void)
{
    if (TAMP->SR & TAMP_SR_TAMP1F) {
        if (RTC->SR & RTC_SR_TSF) {         /* сначала прочитать: сброс TSF обнуляет метку */
            ts_ss = RTC->TSSSR;
            ts_time = RTC->TSTR;
            ts_date = RTC->TSDR;
            RTC->SCR = RTC_SCR_CTSF;
        }
        TAMP->SCR = TAMP_SCR_CTAMP1F;       /* пока флаг стоит, резервные регистры держатся в нуле */
        tampers++;
    }
    EXTI->PR1 = EXTI_PR1_PIF19;
}

Next: section 12б "Firmware self-test: RAM, Flash, clocks and the core during operation" — a RAM march, image CRC against a reference, checking the frequency, the core registers and the stack.

Open in the interactive handbook