System
The services every Core gives you for free — blocking delays and a millisecond clock, the independent watchdog for recovering from hangs, and fault handlers that turn a crash into a readable register dump. None of them need a pad or any configuration. Use the Core / HAL / LL toggle at the top of the sidebar to see each at the layer you work in.
Overview
System features handle timing and fault recovery. They’re available on every Core tile and require no pad assignments or peripheral configuration — they’re part of the Core itself, not something you wire up.
Delays, timekeeping, and the fault handlers come in automatically through core.h — no extra includes. The watchdog is opt-in: include core_watchdog.h when you want it. Most calls are Tier 2 — you call them directly, no handle. A few watchdog helpers are Tier 1, meant for a one-time check at boot.
Delays & timing
Blocking delays and a free-running millisecond counter come from core.h. The counter is handy for timeouts and general timekeeping without tying up a hardware timer.
core_delay_ms(500); // block for 500 ms
core_delay_us(100); // block for 100 us
uint32_t start = core_millis();
// ... your application code ...
if (core_timeout(start, 1000)) {
// 1 second has elapsed since 'start'
}core_delay_usis for short waits — for anything over a millisecond prefercore_delay_ms, which won’t starve the rest of the system as long.core_millis()counts up from boot and wraps after ~49 days. Compare withcore_timeout()rather than subtracting raw values, so a wrap doesn’t bite you.
Watchdog
The independent watchdog (IWDG) runs on its own low-speed internal oscillator (about 32 kHz; 37 kHz nominal on the L0, where it varies more from part to part), completely separate from the system clock. Once started, it cannot be stopped — only a full MCU reset disables it. If your code doesn’t refresh it before the timeout, the MCU resets itself. That’s exactly what you want in a deployed system: a hang, an infinite loop, or a deadlock all recover automatically.
#include "core.h"
#include "core_watchdog.h"
int main(void)
{
core_init();
if (core_watchdog_caused_reset()) {
// We rebooted from a watchdog reset — handle recovery
core_watchdog_clear_flags();
}
core_watchdog_start(2000); // 2-second timeout
while (1) {
// ... your application code ...
core_watchdog_feed(); // must call within 2 seconds
}
}Pass the timeout in milliseconds; the prescaler and reload are chosen for you. The usable range is roughly 100 ms to 28 s. start and feed are the everyday Tier 2 calls; core_watchdog_caused_reset() and core_watchdog_clear_flags() are Tier 1 helpers you typically call once, at boot, to detect and acknowledge a watchdog-induced reset.
stop. Start the watchdog only after init is complete and your loop is actually feeding it, or the first slow path will reset you mid-bring-up.Fault handlers
The SDK installs handlers for the four CPU faults — HardFault, MemManage, BusFault, and UsageFault. No setup required: they’re compiled into every project and override the default infinite-loop handlers from the startup code. When one fires, the handler captures the stacked register frame (PC, LR, R0–R3, R12, PSR), runs your callback if you registered one, and then does what the Core can:
| Core | Register dump | Then |
|---|---|---|
| L4 | Over USB CDC, if USB enumerated | One SOS, then the ROM bootloader (ROM-DFU builds); otherwise SOS forever |
| H5 | None | One SOS, then the ROM bootloader (ROM-DFU builds); otherwise SOS forever |
| L0, W5 | None (no USB) | SOS on the LED forever |
Rebooting into the ROM bootloader means a crashing app can always be reflashed over USB without a debugger. On the W5 and the H5 the cause of a fault is lost today (tracked in the known gaps below); a debugger or your own callback is how you catch it there.
Example output (Core.ST.L4)
The dump uses polled transmit that works with interrupts disabled, so it runs from fault context. If USB CDC never enumerated it is skipped and you still get the SOS.
*** HardFault ***
PC = 0xDEADDEAC
LR = 0x08002345
R0 = 0x00000000
R1 = 0x20001234
R2 = 0x00000000
R3 = 0x00000001
R12 = 0x00000000
PSR = 0x61000000
CFSR = 0x00000001
SOS...The PC value is the instruction that faulted — look it up in your .map file or a disassembly to find the source line.
Optional callback
Register a callback to run first, on every Core — e.g. to stash the PC in a backup register for the next boot. It runs in fault context, so keep it minimal: no heap, no interrupts.
#include "core_fault.h"
void my_fault_handler(hal_fault_type_t type, const hal_fault_frame_t *frame)
{
// Log fault PC to NVM, set a flag, etc. Keep it tiny.
(void)type;
(void)frame;
}
// In main, before your loop:
core_fault_set_callback(my_fault_handler);Brick recovery
On the USB Cores (L4 and H5, ROM-DFU builds) a bad app that hangs before USB comes up can’t brick the board. core_init() counts consecutive watchdog resets; after three with no healthy run in between, it stops launching the app, blinks SOS and parks in the ST ROM bootloader (USB 0483:DF11), so it can always be reflashed. A power cycle starts the count afresh.
Nothing to call. core_watchdog_feed() clears the count once the app has run for max(10 s, twice the watchdog timeout), so occasional resets in a healthy app never add up to a false park. core_recovery_clear() is there if you want to clear it sooner. Bench-verified on a Core.ST.L4; compile-only on the H5. The L0 and W5 have no USB, so no recovery path.
SWO debug output
core_debug_print() and core_debug_printf() send text over the SWD trace pin (ITM port 0) to a probe that reads SWO, with no UART or USB needed. Call core_debug_init() once. L4, W5 and H5 only: the L0’s Cortex-M0+ has no ITM, so the calls do nothing there. Compile-only so far; no DSL surface.
Known gaps
What the SDK itself lists as missing here, straight from the headers:
millis wraps at ~49.7 days. Long-running systems (industrial / datalogger) need either a 64-bit upcounter or a wrap-aware helper for diff-since-start.
These return / clear hardware flags that only matter on the very first boot iteration. Exposing them needs a story for "before studio_start runs" — Studio doesn't currently model that phase.
STM32 also has a windowed watchdog (must feed within a window, not just before the deadline). Useful for catching feed-too-fast bugs. Not wrapped here.
The default handler dumps to USB CDC at runtime on the L4 only (the W5, H5 and L0 lose the cause), and there's no crash log that survives reset for post-mortem analysis. A small ring buffer in backup or NVM (with the captured PC / LR / xPSR / fault status registers) would make field debugging tractable.
Debug output is intentionally Tier 1 — the DSL doesn't have a notion of "debug print to a hardware probe." DSL programs use Core.USB.print for visible output; SWO is reserved for users who are already in C and have a probe attached.
All output goes to ITM stimulus port 0 with no timestamp packets. Multi-channel routing (e.g., separate streams for log vs. data) and ETM/CYCCNT correlation aren't wrapped.
From the @studio unsupported notes in core_timing.h, core_watchdog.h, core_fault.h, core_debug.h — tiles@6af026f.
API reference
Delays & timing
void core_delay_ms(uint32_t ms);void core_delay_us(uint32_t us);uint32_t core_millis(void);int core_timeout(uint32_t start, uint32_t ms);void core_cycle_init(void);void core_delay_cycles(uint32_t cycles);void core_delay_ns(uint32_t ns);Generated from core_timing.h — tiles@777be99.
Watchdog
void core_watchdog_start(uint32_t timeout_ms);void core_watchdog_feed(void);int core_watchdog_running(void);uint32_t core_watchdog_sleep_chunk_ms(void);int core_watchdog_caused_reset(void);void core_watchdog_clear_flags(void);void core_watchdog_debug_freeze(void);Generated from core_watchdog.h — tiles@4b95d38.
Fault handlers
void core_fault_set_callback(hal_fault_callback_t cb);Generated from core_fault.h — tiles@f70bca2.
Brick recovery
uint32_t core_recovery_note_boot(void);int core_recovery_over_limit(uint32_t strikes);void core_recovery_clear(void);Generated from core_recovery.h — tiles@6af026f.
SWO debug output
void core_debug_init(void);void core_debug_print(const char * str);Generated from core_debug.h — tiles@f70bca2.
core_debug_printf(fmt, ...) is a macro for hal_debug_printf.

