Skip to content

flash.pl

Xaeian edited this page Oct 23, 2026 · 2 revisions

Sterownik pamięci flash

FLASH to najniższa warstwa przechowywania danych w Core: kasuje strony, zapisuje podwójne słowa i odczytuje je przez ECC. Na nim działają PDB i EEPROM, a FLASH_Save przechowuje jeden rekord dowolnego rozmiaru. Jak zeszyt pisany długopisem, pamięć flash przyjmuje podwójne słowo raz, a skasować można tylko całą stronę.

Strony i podwójne słowa

Strona i podwójne słowo

  • Pamięć flash dzieli się na strony: 2kB w STM32G0 i 4kB w STM32WB. FLASH_PAGES podaje ich liczbę, w WB tylko strony rdzenia aplikacji, poniżej obszaru stosu radiowego.
  • Skasowana strona ma same 0xFF. FLASH_Erase kasuje jedną stronę, a stronę, która już odczytuje się jako skasowana, pomija.
  • FLASH_Write zapisuje jedno podwójne słowo, 8 bajtów, czyli najmniejszą jednostkę zapisu, i odczytuje je z powrotem. Zapisane podwójne słowo zostaje do skasowania strony.
  • Obok każdego podwójnego słowa pamięć trzyma 8 bitów ECC: pojedynczy przestawiony bit jest poprawiany przy odczycie, a dwa są wykrywane.

Odczyt przez ECC

Odczyt przez ECC

Zapis przerwany resetem albo zanikiem zasilania może zostawić podwójne słowo, którego ECC nie odczyta, a w STM32 taki odczyt wywołuje NMI, które zatrzymuje układ.

  • FLASH_Copy kopiuje zakres do RAM i odczytuje każde podwójne słowo raz, w oknie rozpoznawanym przez obsługę NMI z Core: podwójne słowo, którego ECC nie odczyta, daje ERR, a układ pracuje dalej.
  • corrected informuje, że ECC poprawiło bit w zakresie. Przerwany zapis może też zostawić podwójne słowo, które ECC poprawia do złej wartości, dlatego magazyny nie ufają najnowszemu rekordowi odczytanemu z poprawką.
  • FLASH_Check odczytuje tak samo, ale bez kopiowania, i daje tylko werdykt: dane, które mógł rozerwać przerwany zapis, zawsze idą przez FLASH_Copy, bo podwójne słowo może przejść sprawdzenie, a przy późniejszym odczycie zawieść.
  • FLASH_Read i FLASH_Ref czytają poza tym oknem, dla kodu i stałych, których żaden zapis nie przerwie.

Rekord

Rekord na stronie

FLASH_Save przechowuje jeden rekord, od bajtu do kilku stron: kasuje strony, zapisuje treść, a nagłówek z rozmiarem i jego dopełnieniem zapisuje na końcu. FLASH_Load zwraca rozmiar rekordu albo 0, gdy strona go nie zawiera.

  • Zapis przerwany przed nagłówkiem zostawia nagłówek skasowany, a przerwany w jego trakcie zostawia rozmiar niezgodny z dopełnieniem, więc strona nie zawiera rekordu, nigdy mieszanki starego i nowego.
  • Nowy zapis najpierw kasuje stary rekord, więc przerwany w połowie traci oba. Dane, które muszą przetrwać zanik zasilania, należą do PDB albo EEPROM.
  • FLASH_Compare sprawdza, czy strona zawiera już dokładnie te bajty, więc niezmienione dane nie są zapisywane ponownie.
  • Rekord nie ma własnej sumy kontrolnej: treść chroni tylko ECC.

Zanik zasilania

Zanik zasilania

  • Przy kasowaniu: strona nie jest skasowana w całości. FLASH_Erase traktuje bit poprawiony przez ECC jako nieskasowany, więc kasuje stronę ponownie zamiast ją pominąć.
  • Przy zapisie: podwójne słowo jest rozerwane. Odczyt przez FLASH_Copy daje ERR albo wartość z poprawką, a decyzję podejmuje magazyn wyżej.
  • Przy FLASH_Save: nagłówka brakuje albo jest rozerwany, więc rekordu nie ma.

Testy na komputerze przerywają zasilanie w każdym cyklu pamięci flash podczas zapisu rekordu, przed cyklem i w jego połowie, a strona zawsze zawiera stary rekord, żaden albo nowy.

Konfiguracja

Jedna strona poza programem i innymi magazynami, taka sama w każdej wersji firmware, oraz rekord, którego rozmiar nie maleje między wersjami, bo FLASH_Load zapisuje tyle bajtów, ile zapisano na stronie.

// 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;

Użycie

Na starcie rekord jest wczytywany raz, a zapisywany tylko wtedy, gdy się zmienił.

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