BERGSONNE

Power

Battery life is about being asleep most of the time. core_power exposes the three low-power modes and the ways to wake from them — a timed RTC interval, a pin edge — plus backup registers that survive the deepest sleep. It sits on ll_pwr (there’s no HAL layer). Use the Core / HAL / LL toggle at the top of the sidebar to switch.

Overview

The modes trade wakeup latency and retained state against current draw. Picking one is mostly about what you need alive while asleep and how you’ll wake:

  • Sleep — CPU halted, peripherals and clocks running. Wakes on any interrupt, ~instantly. The everyday idle.
  • Stop — clocks halted, RAM retained. Wakes from EXTI or the RTC; the clock tree is rebuilt on wake.
  • Standby — almost everything off; RAM is lost and wakeup is a full reset. Lowest draw.

Sleep, stop & standby

The Core calls bundle entry, wakeup configuration, and (for stop) clock recovery into one call. core_sleep returns on the next interrupt; core_stop_for wakes itself after N seconds, restores the clocks, and advances core_millis() by the time it slept:

#include "core.h"

  core_sleep();                 // idle until any interrupt

  core_stop_for(30);            // Stop for 30 s (RTC), then resume with clocks restored

  if (core_standby_for(2) != HAL_OK) {
      // refused: the watchdog is running and 2 s is more than half its timeout
  }                             // on success: never returns, the Core resets on wake
Sleeping with the watchdog on
The watchdog keeps counting in Stop and Standby (on the L0 it can’t be paused at all), and Studio projects start it at 5 s. So core_stop_for sleeps in chunks of half the watchdog timeout and wakes briefly to feed it: any length is safe. Standby can’t be chunked, so with the watchdog running core_standby_for refuses anything longer than half the timeout (it returns HAL_ERROR without sleeping) and core_standby_until_on_change returns at once. Use core_stop_for for long sleeps. On Core.ST.H5 the chunked Stop is new (compile-checked, not yet re-benched), and core_standby_for doesn’t check the watchdog yet.
Sleeping on USB power
Stop turns the USB clock off. So while a USB host is awake on the bus, core_stop_for waits in Sleep mode instead on Core.ST.L4: USB keeps answering (Studio, flash-serial, prints), and the timing is the same. It uses real Stop on battery or a suspended bus. On Core.ST.H5 these sleeps always wait in Sleep mode once USB has started, which core_init() does on every H5 project.
Standby resets on wake
When it does sleep, core_standby_* never returns — the MCU resets and RAM is lost. Detect it at the top of main() with core_woke_from_standby().

Wakeup & the RTC

Beyond a timed interval, you can wake on a pin edge, and the RTC gives you a wall clock with periodic wakeups and a calendar alarm:

// Wake from Stop on a falling edge on pad 8, or after 10 s:
  int why = core_stop_until_on_change_timeout(8, EDGE_FALLING, 10000);
  // 1 = the pad (or it was already low), 0 = timed out, -1 = pad can't wake the Core

  // No timeout: wait for the edge however long it takes
  core_stop_until_on_change(8, EDGE_FALLING);

  // RTC wall clock + alarm:
  core_rtc_init();
  core_rtc_set_time(12, 0, 0);
  core_rtc_set_alarm(0xFF, 0xFF, 0);   // 0xFF = wildcard → fire every minute at :00
  if (core_rtc_alarm_fired()) { ... }

A pin wake returns at once if the pad is already at its wake level (a sensor holding its interrupt line low until serviced would otherwise never wake the Core); EDGE_BOTH always waits for a change. On the H5 the timeout isn’t implemented yet: a non-zero one returns -1.

Check core_woke_from_standby() first thing in main() to branch on a Standby return, then core_clear_standby_flag() to acknowledge it.

Backup registers

Backup registers are 32-bit slots that survive reset and Standby (and power loss, with VBAT). Use them for crash counters, boot flags, or carrying state across a Standby cycle:

core_backup_write(0, 0x12345678);
  uint32_t saved = core_backup_read(0);   // persists across reset / Standby
Count varies by Core
CORE_BACKUP_COUNT is 32 on most Cores, 5 on the M0+ (Core.ST.L0).

Cross-architecture support

Sleep is verified across the family. Stop passed a bench test of two back-to-back 10 s core_stop_for calls with a 5 s watchdog on the L4 and the W5 (25 September 2026). The L0’s sleep, RTC and backup code was rewritten against its reference manual the same day and passed the same bench test on 26 September (Stop, RTC wake-up and alarm, backup registers). The H5 ran Stop and RTC wake-up before; since 13 October it shares the chunked sleep code, which compiles there but hasn’t been re-benched. Hover a chip for each feature:

●L0M0+7/8●L4M48/8●W5M337/8●H5M337/8WCH (RISC-V) · Nordic (nRF54) — in development

See the implementation status for the full matrix.

Known gaps

What the SDK itself lists as missing here, straight from the headers:

Power
mediumDSLStandby family stays Tier 1 (noreturn)

core_standby_for / core_standby reset on wake — they never return on success and can't be safely exposed to the DSL, since the simulator would deadlock on a call that never returns. Recoverable variants (sleep, stop_for) are Tier 2; reaching Standby from a DSL program needs an "escape-to-C" fallthrough or a separate "would have entered Standby" event the simulator can model.

mediumDSLNo wake-on-pad Tier 2

core_stop_until_on_change blocks until a configured pad edge wakes the chip. The DSL surface is straightforward in spirit but needs the EXTI handler bridge to express "block until" cleanly across the host-call boundary. Tracked alongside the broader DSL event- model story.

highC APIStandby not bench-verified on Core.ST.L0 or Core.ST.W5; Core.ST.H5 sleep compile-only

The register maps behind stop_for / standby_for / RTC / backup were corrected against RM0377 and RM0493 on 2026-09-25. Stop with the watchdog running passed tests/hw-sleep-cycle on Core.ST.L4 and Core.ST.W5 (2026-09-25) and Core.ST.L0 (2026-09-26). Standby on the L0 and the W5 is compile-only. Core.ST.H5 moved to the same fed, chunked Stop code on 2026-10-13 and has not run it on a board yet; its core_standby_for() still has no watchdog check.

mediumC APIStandby can't outlast the watchdog

The IWDG keeps counting in Standby (always on the L0, by option byte on the L4/WBA) and nothing feeds it until main() runs again, so with the watchdog on (the Studio default, 5 s) core_standby_for refuses anything longer than half the timeout and returns HAL_ERROR, and core_standby_until_on_change returns without sleeping. Long sleeps use core_stop_for, which wakes to feed. Changing the IWDG_STDBY option byte would lift this on the L4/WBA; the SDK doesn't touch option bytes.

mediumC APIWKUP-pin Standby is L4-only

core_standby_until_on_change is implemented for STM32L422 only — Core.ST.L4.2's PA0/PA2 WKUP pins. Other Cores hit the (void) fallback and return without entering Standby.

mediumC APINo LPTIM (low-power timer) wakeup

The SDK roadmap flags LPTIM as Tier 1 high-impact: it runs in Stop without the RTC and gives finer wakeup granularity than seconds. Not wrapped here yet.

lowC APINo autonomous-mode peripherals (Core.ST.W5)

The WBA's Stop2 autonomous mode (ADC / SPI / I2C / UART operating while the CPU sleeps) is on the roadmap as Tier 3 — none of it is surfaced through core_power yet.

RTC
mediumDSLNo DSL get_time / get_date

The C surface has core_rtc_get_time / get_date taking out-pointers; they're not exposed because the host-call ABI doesn't map cleanly to multi-out values. The newer multi-scalar-out ABI (see DSL Capability Coverage close, 2026-05-03) could land them.

mediumC APILSE / external crystal not exposed

core_rtc_init always selects LSI (~32 kHz RC, ±5% typical drift on the L4/WBA). The L0's LSI is 26-56 kHz part to part; it is measured once against SYSCLK (ll_lsi_hz(), sdk/hal/hal_lsi.c) and the RTC prescaler is set from that, so the second is as good as SYSCLK at that moment (HSI16 at high / max, MSI at low / medium) and drifts with temperature. Cores with an LSE crystal need ll_rtc_init(1) — no Tier 2 wrapper that takes a clock-source argument.

lowC APINo subsecond / millisecond accuracy

The wrapper exposes whole-second resolution. STM32 RTCs have a subsecond register (1/PREDIV_S) that's reachable from ll_rtc but not from this header — wall-clock timestamps round to the second.

Backup
lowDSLTwin backup state wipes on Reset

read/write round-trip within a single run, but the per-slot store is cleared on every project reload — DSL programs that rely on backup state surviving a soft reset (e.g., boot counters across the user clicking Reset in the IDE) can't be exercised end-to-end.

lowC APINo tamper / anti-tamper integration

The TAMP block on WBA/H5 hosts the backup registers but also drives tamper detection (active edge, anti-tamper erase, time-stamping on tamper). None of that is wrapped — backup is read/write only.

From the @studio unsupported notes in core_power.h, core_rtc.h, core_backup.h — tiles@217a38f.

API reference

Power modes

Default-instance · Tier 2
void core_sleep(void);
Sleep until any interrupt (CPU stopped, peripherals running).
void core_stop_for(uint32_t seconds);
Enter Stop mode for a number of seconds, then wake and restore clocks. Uses the RTC wakeup timer (LSI). Returns after wake with PLL + SysTick restored; core_millis() advances by the time slept, as the RTC measured it. If the watchdog is running, sleeps in chunks of half its timeout and feeds it in between. Core.ST.L4 with a USB host awake on the bus: waits in Sleep mode so the host can still reach it (see the USB note at the top).
int core_woke_from_standby(void);
Returns 1 if the MCU woke from Standby. The flag survives later resets until core_clear_standby_flag() — call that once you've handled the wake.
void core_reset(void);
Reset the Core. Does not return. With a BLE central connected (Core.ST.W5), the link is dropped first so the central sees a clean disconnect (waits at most 200 ms). Pending USB serial output is flushed (Core.ST.L4 / H5: waits at most 50 ms for a terminal that stopped reading, longer only while a just-opened port settles). The watchdog is fed while it waits. Then a system reset (AIRCR SYSRESETREQ). Core.ST.L4: if the chip still believes its flash is empty (it was flashed blank by DFU or SWD and not power-cycled since), that stale verdict is corrected first, so the reset starts this program and not the ROM bootloader. Core.ST.H5 with BOOT0 held high resets into the ROM bootloader. Safe from an interrupt handler; the BLE disconnect is then left to the central's supervision timeout.
core_reset_reason_t core_reset_reason(void);
Why the Core last reset: power-on (or brownout), the reset pin, the watchdog, a software reset (core_reset(), a remote reset from Studio, an update), or other. Read once by core_init() and kept, so it answers the same for the whole run.
Returns: core_reset_reason_t: 0 power-on, 1 pin, 2 watchdog, 3 software, 4 other.
Lower-level · Tier 1
void core_clock_init(void);
Bring up the clock tree for the project's `clock` level (oscillator, PLL, flash wait states, bus prescalers) and start SysTick. Generated per project by coregen into core_init.c and called by core_init(); call it again only to restore the clocks by hand (core_stop_for already does after a wake).
int core_stop_until_on_change_timeout(uint8_t pad, uint32_t edge, uint32_t timeout_ms);
Enter Stop mode until a GPIO edge on the given pad, or until `timeout_ms` has passed (0 = no timeout). Configures the pad as input, arms its EXTI, enters Stop, and restores clocks on wake. If the watchdog is running, wakes every half timeout to feed it and goes back to sleep. Core.ST.L4 with a USB host awake on the bus: waits in Sleep mode (see the USB note at the top). Already at the wake level: EXTI latches only edges that happen after it is armed, so a line that is already at its wake level — a sensor INT held low until serviced, for EDGE_FALLING — would never wake the Core. After arming, this reads the pad and returns 1 at once if it is already low (EDGE_FALLING) or high (EDGE_RISING). EDGE_BOTH always waits for a change.
Returns: 1 = woken by the pad (or it was already at the wake level), 0 = timed out, -1 = the pad can't take an edge interrupt.
void core_stop_until_on_change(uint8_t pad, uint32_t edge);
Enter Stop mode until a GPIO edge occurs on the given pad (no timeout). Returns right away if the pad can't take an edge interrupt, or if it is already at the level the edge wakes on (see core_stop_until_on_change_timeout()). If the watchdog is running, wakes every half timeout to feed it and goes back to sleep.
hal_status_t core_standby_for(uint32_t seconds);
Enter Standby mode with RTC wakeup after the given seconds. On success it does not return — the MCU resets on wake. Check core_woke_from_standby() at the top of main() to detect a Standby wake. Watchdog: the IWDG keeps counting through Standby and nothing feeds it until main() runs again, and Standby can't be split into fed chunks. So while the watchdog is running this refuses — returns HAL_ERROR without sleeping — unless seconds is at most half the watchdog timeout (2 s with the default 5 s watchdog). Use core_stop_for() for longer sleeps. (Core.ST.H5: no check.)
Returns: HAL_ERROR if refused because of the watchdog; does not return otherwise.
void core_standby_until_on_change(uint8_t pad, uint32_t edge);
Enter Standby mode until a GPIO edge on the given pad (via WKUP pin). Does not return — MCU resets on wake. Not all pads support WKUP; returns without entering Standby if the pad has no WKUP capability, or if the watchdog is running (it would reset the Core long before most edges). On Core.ST.L4.2: pad 8 (PA0 = WKUP1) and pad 7 (PA2 = WKUP4).
void core_stop(void);
Enter Stop mode (caller manages wakeup source + clock recovery). A running watchdog keeps counting and is not fed: wake within its timeout. On the WBA the caller also owns the Stop 1 wait-state/HDIV5 rules (RM0493 §11.7.7).
void core_standby(void);
Enter Standby (caller manages wakeup source). Does not return. A running watchdog keeps counting through Standby and resets the Core at its timeout.
void core_clear_standby_flag(void);
Clear the Standby wake flag. Call after core_woke_from_standby() returns 1.
void core_watchdog_start_seconds(uint32_t seconds);
Start the independent watchdog with a timeout in seconds (convenience). For finer control use core_watchdog_start() from core_watchdog.h which accepts milliseconds.
uint32_t core_reset_flags(void);
The raw RCC reset flags read at boot (CORE_RESET_FLAG_*; bits 31..24). 0 if no flag was set.
void core_reset_capture(void);
Read and keep the reset flags, then clear them in RCC. core_init() calls this first thing; later calls do nothing. core_reset_reason() calls it too, for a program that never ran core_init().
Types
TypeKindDescription
core_reset_reason_tenumCause of the reset this program started from (core_reset_reason()).
  • CORE_RESET_POWER_ON = 0 · Power-on, or a brownout (one flag for both)
  • CORE_RESET_PIN = 1 · NRST pin: a reset button or a debug probe
  • CORE_RESET_WATCHDOG = 2 · Independent or window watchdog timeout
  • CORE_RESET_SOFTWARE = 3 · core_reset(), a remote reset, the DFU touch, an update's restart, SWD
  • CORE_RESET_OTHER = 4 · Illegal low-power entry, firewall, or no flag
Constants
NameValueDescription
CORE_RESET_FLAG_LPWR(1UL << 31)Illegal Stop/Standby entry
CORE_RESET_FLAG_WWDG(1UL << 30)Window watchdog
CORE_RESET_FLAG_IWDG(1UL << 29)Independent watchdog
CORE_RESET_FLAG_SFT(1UL << 28)Software (AIRCR SYSRESETREQ)
CORE_RESET_FLAG_POR(1UL << 27)POR/PDR (L0), BOR (L4, W5, H5)
CORE_RESET_FLAG_PIN(1UL << 26)NRST pin (set by every reset)
CORE_RESET_FLAG_OBL(1UL << 25)Option-byte reload (not on the H5)
CORE_RESET_FLAG_FW(1UL << 24)Firewall (L0, L4 only)

Generated from core_power.h, core_reset.h — tiles@217a38f.

RTC

Default-instance · Tier 2
void core_rtc_init(void);
Initialize the RTC using LSI (internal RC, ~32 kHz; ~37 kHz on Core.ST.L0, scaled so a second is still ~1 s). Call once from `on start` before setting time, date, or alarms. For LSE, call ll_rtc_init(1) directly in hand-written C (Core.ST.L0 has no LSE and stays on LSI).
void core_rtc_set_time(uint8_t h, uint8_t m, uint8_t s);
Set the time (24h format).
void core_rtc_set_date(uint8_t y, uint8_t mo, uint8_t d, uint8_t wd);
Set the date.
void core_rtc_wakeup(uint32_t seconds);
Configure a periodic wakeup timer.
void core_rtc_wakeup_stop(void);
Disable the periodic wakeup timer.
void core_rtc_set_alarm(uint8_t hours, uint8_t minutes, uint8_t seconds);
Set Alarm A to trigger at a specific time. Pass 0xFF for hours, minutes, or seconds to ignore that field. The alarm fires when all non-masked fields match the RTC time.
void core_rtc_clear_alarm(void);
Clear the alarm (disable Alarm A).
int core_rtc_alarm_fired(void);
Check if the alarm has fired (poll mode). Clears the flag on read.
Lower-level · Tier 1
void core_rtc_get_time(uint8_t * h, uint8_t * m, uint8_t * s);
Read the current time.
void core_rtc_get_date(uint8_t * y, uint8_t * mo, uint8_t * d, uint8_t * wd);
Read the current date.

Generated from core_rtc.h — tiles@8f1266b.

Backup registers

Default-instance · Tier 2
uint32_t core_backup_read(uint8_t index);
Read a backup register.
void core_backup_write(uint8_t index, uint32_t value);
Write a backup register. Backup domain write access is enabled automatically.
Lower-level · Tier 1
void _core_backup_ensure_clk(void);
Ensure the backup registers are reachable. L0: RTC_BKPxR live inside the RTC (RM0377 §22.7.20), so the RTC clock must be selected, enabled and running (LSI; never the LSE here). L4 (L422): TAMP_BKPxR (RM0394 §36.6.8) on the RTC APB clock; the RTC kernel clock is brought up too, as before, in case nothing else has. WBA: TAMP_BKPxR (RM0493 §37.6.18) need RCC_APB7ENR.RTCAPBEN, which is off at reset — without it TAMP read 0 and ignored writes unless the BLE stack happened to have set it. H5: TAMP on RCC_APB3ENR.RTCAPBEN. All of them need PWR + DBP for writes.

Generated from core_backup.h — tiles@4b95d38.