FlashBuddy — Crash‑Safe Tiny Config Store with Wear‑Leveling
The problem
You have a 32KB or 256KB microcontroller flash and a few bytes of configuration that must survive resets, power glitches and firmware updates. The naive approach—erase a sector and rewrite a struct—works until a brownout hits mid‑write and you end up with corrupted settings or, worse, a sector worn out by repeated erases.
FlashBuddy is a tiny, practical layout and algorithm to store key configuration blobs safely and evenly across flash. It’s intentionally minimal: no filesystem, no journaling library, and it fits in a few dozen lines of code usable on AVRs, Cortex‑M0, ESP8266 and similar targets.
Key ideas
- Append‑only records: write new versions sequentially so an interrupted write leaves previous good data intact.
- Compactable ring: use a set of flash pages as a circular log, trimming old entries to reclaim space and distribute wear.
- Header checks: each record carries a small header with length, sequence number and CRC to detect partial writes and corruption.
- Single‑record atomicity: a record is either valid (header+payload+CRC matches), or ignored; no two‑phase commit needed.
Layout
Pick N flash pages (or a dedicated area) to hold the config log. Each record looks like this:
[MAGIC(2)][SEQ(4)][LEN(2)][PAYLOAD(LEN)][CRC(2)]
Notes:
- MAGIC marks the start and helps skip garbage. Use 0xAA55 or similar.
- SEQ is a monotonically increasing counter. Comparing SEQ finds the newest valid record.
- LEN lets you support variable payload sizes up to a practical maximum (e.g. 256–1024 bytes).
- CRC covers header+payload (not the trailing CRC field). A 16‑bit CRC is usually enough for small blobs.
Read algorithm
- Scan the ring from start to end (or remember last position in RAM during runtime).
- For each candidate MAGIC, read SEQ and LEN, then attempt to read payload and CRC.
- Verify CRC. If valid, keep the record; if not, stop scanning forward (you likely hit a partial write) and use the highest SEQ collected so far.
- If multiple valid records exist, the one with the largest SEQ is the latest configuration.
Write algorithm (safe and simple)
- Build a record in RAM: MAGIC, SEQ = last_seq + 1, LEN, PAYLOAD, CRC.
- Find the next free space in the ring (first erased area large enough). If none, trigger compaction.
- Program the record sequentially. If power fails mid‑write, future scans will reject the incomplete CRC and fall back to the previous record.
- Optionally verify by rereading the last bytes after write to ensure NAND-style partial programming issues aren’t hiding.
Compaction
Compaction reclaims space and spreads erases across pages. When free space is low:
- Find the newest valid record and copy it to the start of the ring (or the start of a fresh erased page).
- Erase the pages following the copy point to reclaim them.
- Continue normal append at the newly freed area (SEQ should continue increasing).
This approach keeps at most one erase per compaction cycle and ensures you always have at least one valid copy of your config during the operation.
Failure modes & testing
- Partial writes: detected via CRC. Always fall back to last good SEQ.
- Corrupted headers: skip and continue scan; if many corruptions occur, you may need a factory default routine.
- Wraparound of SEQ: use 64‑bit or check for low/high scans to detect wrap and treat smaller numbers as newer only after a full ring reset—practically use 32‑bit and ignore wrap for lifetime devices.
Test with a kill‑switch: repeatedly power‑cycle while writing in a tight loop. Verify no invalid config is ever selected and that compaction doesn't lose data.
Implementation notes
- Keep RAM small: you only need a buffer the size of your largest payload plus a page or two for copying during compaction.
- Flash alignment: ensure writes are aligned to the device's minimum program unit (some flash programs 16 or 32‑byte chunks).
- Erase size: choose the ring to span multiple erase sectors to get basic wear leveling.
- Power economics: if possible, detect low voltage and delay writes; if not available, rely on append semantics to survive glitches.
Practical tweaks
- Keep small 'delta' records rather than full copies when only a few fields change: store key/value diffs and occasionally compact to a full snapshot.
- Store two independent config slots (A/B) inside the ring and alternate when compaction happens—helpful for very tiny rings.
- Use an extra byte in header to mark a record as 'committed' after CRC is written; this can speed scans, but be mindful of extra program cycles.
Why this pattern works
FlashBuddy leans on append‑only safety: writes extend state, never overwrite. Combined with a simple CRC and compact/erase routine, you get crash safety, simple repair strategies and distributed wear without a heavyweight filesystem. It’s easy to audit, test and port to the smallest MCUs.
Next steps
Prototype it: pick a 4–8 page region on your board, implement the scan/write/compact loop and run a stress test with a power‑cycle jig. Expect to iterate on alignment and CRC checks for your specific flash chip. If you want, I can sketch a tiny C reference implementation tuned for AVR or for ARM Cortex‑M—say which target and I’ll produce code you can drop into a bootloader or main app.