Skip to content
Xaeian edited this page Oct 23, 2026 · 2 revisions

Flash Memory Driver

FLASH is the lowest storage layer of Core: it erases pages, writes doublewords and reads them back through ECC. PDB and EEPROM are built on it, and FLASH_Save keeps one record of any size. Like a notebook written in pen, flash takes a doubleword once, and only a whole page can be erased.

Pages and doublewords

Page and doubleword

  • Flash is divided into pages, 2kB on STM32G0 and 4kB on STM32WB. FLASH_PAGES gives their number, on WB only the pages of the application core, below the area of the radio stack.
  • An erased page reads all 0xFF. FLASH_Erase erases one page and skips a page that already reads erased.
  • FLASH_Write writes one doubleword, 8 bytes, the smallest unit flash takes, and reads it back. A written doubleword stays until its page is erased.
  • Beside every doubleword flash keeps 8 ECC bits: a single flipped bit is corrected on read, and two are detected.

Reading through ECC

Reading through ECC

A write cut by a reset or a power loss can leave a doubleword ECC cannot read, and on STM32 such a read raises an NMI that stops the chip.

  • FLASH_Copy copies a range into RAM and reads each doubleword once, in a window the NMI handler of Core recognizes: a doubleword ECC cannot read gives ERR, and the chip goes on.
  • corrected tells that ECC fixed a bit in the range. A cut can also leave a doubleword ECC corrects to a wrong value, so the stores do not trust the newest record that reads corrected.
  • FLASH_Check reads the same way without copying and gives a verdict only: data a cut may have torn always comes through FLASH_Copy, because a doubleword can pass a check and fail a later read.
  • FLASH_Read and FLASH_Ref read outside that window, for code and constants no write can cut.

Record

Record on a page

FLASH_Save keeps one record, from a byte up to several pages: it erases the pages, writes the body and writes the header last, the size and its complement. FLASH_Load returns the size of the record, or 0 when the page holds none.

  • A save cut before the header leaves it erased, and one cut inside it leaves a size its complement does not match, so the page holds no record, never a mix of the old one and the new one.
  • A new save erases the old record first, so a cut in the middle loses both. Data that must outlive a cut belongs in PDB or EEPROM.
  • FLASH_Compare tells whether the page already holds these very bytes, so unchanged data is not written again.
  • The record has no checksum of its own: the body is guarded by ECC only.

Power cuts

Power cuts

  • Cut while erasing: the page is not erased whole. FLASH_Erase counts a bit ECC corrected as not erased, so it erases the page again instead of skipping it.
  • Cut while writing: the doubleword is torn. A read through FLASH_Copy gives ERR or a corrected value, and the store above decides what to do with it.
  • Cut during FLASH_Save: the header is missing or torn, so there is no record.

Tests on the computer cut the power at every flash cycle of a save, before the cycle or halfway through it, and the page always holds the old record, no record or the new one.

Configuration

One page outside the program and the other stores, the same in every firmware version, and a record whose size never shrinks between versions, as FLASH_Load writes as many bytes as were saved.

// One page outside the program and the other stores
#define RECIPE_PAGE 46

typedef struct {
  uint16_t speed_rpm;
  uint16_t ramp_ms;
  int16_t offset;
  uint16_t mode;
} Recipe_t;

Usage

At start the record is loaded once, and it is saved only when it changed.

static Recipe_t recipe = { .speed_rpm = 1500, .ramp_ms = 200 };

void recipe_init(void)
{
  // A page without a record keeps the defaults above
  Recipe_t stored;
  if(FLASH_Load(RECIPE_PAGE, (uint8_t *)&stored) == sizeof(stored)) recipe = stored;
}

void recipe_save(void)
{
  // Unchanged data is not written again, which spares an erase
  if(FLASH_Compare(RECIPE_PAGE, (const uint8_t *)&recipe, sizeof(recipe))) return;
  FLASH_Save(RECIPE_PAGE, (const uint8_t *)&recipe, sizeof(recipe));
}

Clone this wiki locally