Storage & ID
Three ways a Core keeps something across resets: core_nvm for settings you rewrite, core_otp for identity burned once at production, and core_uid for the ID the chip was born with. How much of each a Core has depends on its MCU, so read the support notes before relying on one.
Overview
- NVM — byte-addressed storage that survives power loss. Only Core.ST.L0 has it today (512 bytes of true EEPROM). Flash emulation for the L4, W5 and H5 is planned, not built.
- OTP — write-once bytes outside the main flash, so even a full chip erase can’t clear them. Core.ST.W5 only, today.
- Unique ID — the 96-bit factory ID, read-only, on every Core.
For small state that only has to survive a reset or Standby (not power loss), the backup registers work on every Core.
NVM
core_nvm_read / core_nvm_write take a byte offset and a buffer, and return 0 on success or -1 if the range falls outside the region. core_nvm_size() says how big the region is on this Core:
#include "core_nvm.h"
struct { uint16_t boots; int16_t offset_mv; } cfg;
if (core_nvm_size() >= sizeof cfg &&
core_nvm_read(0, &cfg, sizeof cfg) == 0) {
cfg.boots++;
core_nvm_write(0, &cfg, sizeof cfg); // ~3.2 ms per byte on the L0
}core_nvm_size() is 0 and every read or write returns -1 without touching flash. Until flash emulation lands, keep settings in the backup registers or on an external storage tile.On the L0 the EEPROM handles erase-before-write itself. At the low clock level (voltage range 3, which can’t program it) a write steps up to range 2 for the program and back, automatically.
OTP identity
On Core.ST.W5 the OTP area is 512 bytes at 0x0BF90000, split into 32 slots of 16 bytes. Reads are a plain memory load. core_otp_program_slot() burns one slot, refuses a slot that isn’t blank, and checks the read-back — but there is no undo: bits only go from 1 to 0, and a programmed slot can never be rewritten. To amend a record, write a new one to the next blank slot and read the last valid one.
#include "core_otp.h"
uint8_t rec[16];
for (uint32_t s = 0; s < core_otp_slot_count(); s++) {
if (core_otp_slot_is_blank(s) == 1) break; // first unused slot
core_otp_read(s * 16, rec, sizeof rec); // a burned record
}The record format (magic, CRC, fields) is the application’s to define. Burning is deliberately C-only: the DSL can query OTP (size, slot count, blank check) but can’t program it, so a stray Studio block can’t consume a slot. Reads and the slot geometry were checked on a W5; the program path has never run on silicon, since every test burn is permanent.
core_otp_size() returns 0 and reads and programs return an error. The L0 has no OTP.Unique ID
Every STM32 carries a 96-bit ID set at the factory. It needs no init and never changes, so it makes a good serial number, a seed for a unique BLE name, or a basis for per-device keys:
#include "core_uid.h"
uint32_t w[3];
core_uid_read(w); // the three 32-bit words
uint32_t tok = core_uid_hash(); // stable 32-bit fold of the full ID
char serial[25];
core_uid_hex(serial, sizeof serial); // 24 hex characters + NULCross-architecture support
Storage, by Core (hover a chip for each feature):
The unique ID:
See the implementation status for the full matrix.
Known gaps
What the SDK itself lists as missing here, straight from the headers:
Tier 2 covers the byte-level pair (read_byte, write_byte) — enough for boot counters, single flags, small struct fields. Multi-byte read_buf / write_buf still need the array-IN / array-OUT host-call ABI prototyped on the tile-driver side. Tracked with the DSL Capability Coverage close.
read_byte / write_byte round-trip within a single run, but the per-slot store is cleared on every project reload — DSL programs that rely on NVM state surviving a soft reset (boot counters, crash flags) can't be exercised end-to-end in the IDE. Same issue the Backup register gap calls out; closing both needs persistent per-slot state across worker resets.
core_nvm_write returns -1 on Core.ST.H5 (CORE_NVM_SIZE is 0). The flash emulation behind Core.ST.L4 and Core.ST.W5 has not been ported to the H523's 8 KB sectors yet. Until it is, H5 programs that need persistent state are limited to backup registers.
core_nvm_erase_all clears the region, but nothing reports the page erase count or the remaining write budget. Long-running data loggers budget their write rate from the figures in core_nvm.h.
CORE_OTP_* is backed only on Core.ST.W5 (STM32WBA55, 512 B @ 0x0BF90000). L0 / L4 / H5 report size 0 and every read/program returns an error until their OTP geometry is added. WBA55 is the only Core that currently ships a product using OTP identity.
Reads (size, slot_count, slot_is_blank, read) are safe to surface, but the burn (core_otp_program_slot) is irreversible and write-once, so it is intentionally NOT exposed to the DSL — a stray palette call could permanently consume a slot. Provisioning burns happen from C / the production programmer, not from a Studio program.
From the @studio unsupported notes in core_nvm.h, core_otp.h — tiles@6af026f.
API reference
NVM
uint32_t core_nvm_size(void);int core_nvm_read_byte(uint32_t offset);int core_nvm_write_byte(uint32_t offset, uint8_t value);int core_nvm_read(uint32_t offset, void * buf, uint32_t len);int core_nvm_write(uint32_t offset, const void * data, uint32_t len);int core_nvm_erase_all(void);Generated from core_nvm.h — tiles@60254ad.
OTP
uint32_t core_otp_size(void);uint32_t core_otp_slot_count(void);int core_otp_slot_is_blank(uint32_t slot);uint32_t core_otp_slot_addr(uint32_t slot);int core_otp_read(uint32_t offset, void * buf, uint32_t len);int core_otp_program_slot(uint32_t slot, const void * rec);Generated from core_otp.h — tiles@f70bca2.
Unique ID
void core_uid_read(uint32_t out[3]);uint32_t core_uid_hash(void);int core_uid_hex(char * buf, uint16_t buflen);Generated from core_uid.h — tiles@f70bca2.

