# Emulacja EEPROM w pamięci flash EEPROM przechowuje w pamięci flash mikrokontrolera ustawienia pod 32-bitowymi kluczami, na przykład wartość zadaną, kalibrację albo licznik. Mikrokontroler nie ma własnej pamięci EEPROM, dlatego biblioteka emuluje ją w pamięci flash. Podobnie jak [PDB](pdb.pl), niczego nie zmienia w zapisanych danych: nowa wartość trafia do nowej komórki, a odczyt bierze najnowszą. ## Komórki i połowy ![Połowy EEPROM](.img/eeprom-halves-PL.svg) - Magazyn zajmuje parzystą liczbę stron pamięci flash i dzieli je na dwie połowy, A i B. Do jednej trafiają zapisy, druga pozostaje skasowana. - Każdy zapis dodaje jedną 8-bajtową komórkę: klucz i wartość, po 4 bajty. Ponowny zapis klucza zajmuje nową komórkę, a obowiązuje najnowsza. - Przepisana połowa zaczyna się znacznikiem BEGIN, a kończy znacznikiem generacji, czyli licznika rosnącego przy każdym przepisaniu. - Cztery najwyższe klucze, od `0xFFFFFFFC` w górę, należą do samego EEPROM, a zapis z którymś z nich jest odrzucany. ## Przepisanie ![Przepisanie pełnej połowy](.img/eeprom-rewrite-PL.svg) Gdy aktywna połowa się zapełni, EEPROM przepisuje ją do drugiej w tej kolejności: 1. Znacznik BEGIN. 2. Najnowsza wartość każdego klucza, po jednej komórce. 3. Znacznik z kolejnym numerem generacji. 4. Dopiero wtedy pełna połowa jest kasowana. Historia klucza skraca się do jednej komórki, a zapis trwa dalej w nowej połowie. Klucz, który nie mieści się w połowie, przepada przy przepisaniu, dlatego zbiór kluczy musi być znacznie mniejszy niż połowa. ## Zanik zasilania ![Zanik zasilania](.img/eeprom-power-cut-PL.svg) Zasilanie może zaniknąć w dowolnej chwili. Przy następnym starcie `EEPROM_Init` odczytuje obie połowy i rozpoznaje, co było w toku: - Przy zapisie wartości: komórka nie jest dokończona, więc się nie liczy, a jej klucz zachowuje starą wartość. - Przy przepisaniu, przed znacznikiem generacji: nowa połowa ma znacznik BEGIN, ale nie ma znacznika generacji, więc jest kasowana, a przepisanie rusza od nowa. - Po znaczniku generacji, przed skasowaniem pełnej połowy: obie połowy są kompletne, obowiązuje wyższa generacja, a druga połowa jest kasowana. - Pod koniec kasowania: strona może odczytywać się jako skasowana, choć nie została skasowana do końca, więc EEPROM kasuje połowę w całości przed zapisem do niej, także taką, która odczytuje się jako skasowana. Zanik zasilania w trakcie tego odtwarzania jest obsługiwany tak samo przy kolejnym starcie. Po zaniku zasilania w dowolnej chwili każdy klucz ma ostatnią zapisaną wartość, a klucz zapisywany w tej chwili starą albo nową. Testy na komputerze przerywają zasilanie w każdym cyklu pamięci flash długiej serii zapisów i przepisań, a także w trakcie samego odtwarzania. Poza tą gwarancją pozostają trzy przypadki: - Wartość zapisana pod dwoma kluczami, na przykład 64-bitowa, to dwa zapisy, więc zanik zasilania między nimi zostawia jej części z dwóch różnych zapisów. - Przerwane `EEPROM_Clear` może zostawić stare wartości do odczytu. - Najnowsza komórka liczy się tylko wtedy, gdy odczytuje się bez błędu, więc klucz zapisany raz traci wartość, jeśli w tej komórce z wiekiem przestawi się bit. ## Konfiguracja Własny magazyn: dwie strony i klucz dla każdego ustawienia, takie same w każdej wersji firmware. ```c enum { KEY_Setpoint = 1, KEY_Hysteresis = 2 }; // Two pages outside the program and the board stores static EEPROM_t settings = { .page_start = 50, .page_count = 2 }; ``` ## Użycie Po każdym starcie najpierw `EEPROM_Init`, a każdy odczyt podaje wartość domyślną dla klucza, którego jeszcze nie zapisano. ```c static uint32_t setpoint, hysteresis; void settings_init(void) { EEPROM_Init(&settings); // recovers whatever a power cut left // A key not written yet reads as its default setpoint = EEPROM_Read(&settings, KEY_Setpoint, 1500); hysteresis = EEPROM_Read(&settings, KEY_Hysteresis, 50); } void setpoint_set(uint32_t value) { if(EEPROM_Write(&settings, KEY_Setpoint, value) == OK) setpoint = value; } ```