Repository navigation
pdb.pl
PDB (pico database) przechowuje w pamięci flash mikrokontrolera małe rekordy, na przykład mailbox bootloadera, dziennik zdarzeń albo dane fabryczne. Pamięć flash działa jak zeszyt pisany długopisem: zapisanej linii nie da się poprawić, a skasować można tylko całą stronę. Dlatego PDB niczego nie zmienia, tylko dopisuje kolejne rekordy.
- Każda strona zaczyna się nagłówkiem zapisywanym zaraz po jej skasowaniu, więc strona bez nagłówka nie zawiera danych.
- Dalej są rekordy o jednakowym rozmiarze, a numery nadaje im sam PDB: 1, 2, 3.
- Na końcu rekordu jest suma kontrolna CRC, zapisywana jako ostatnia. Rekord bez poprawnego CRC nie został dokończony i przy odczycie jest pomijany.
Gdy nowsza strona się zapełni, PDB kasuje starszą i zapisuje dalej na niej, więc zawsze zostaje jedna pełna strona historii. Dane, których nie wolno stracić, na przykład dane fabryczne, zajmują jedną stronę bez pierścienia: po jej zapełnieniu zapis jest odrzucany, a obowiązuje najnowszy poprawny rekord.
Zasilanie może zaniknąć w dowolnej chwili, a PDB obsługuje każdy z trzech przypadków:
- Przy zapisie rekordu: rekord nie ma CRC i przy odczycie jest pomijany.
- Przy kasowaniu strony: strona nie ma nagłówka i przed zapisem jest kasowana od nowa.
- Na samym początku zapisu: miejsce wygląda na puste, choć nie jest, więc pierwszy zapis po każdym starcie zeruje pierwsze wolne miejsce, a rekord trafia do następnego.
Odczyt niczego nie zapisuje, a PDB nigdy nie kasuje całej bazy z powodu błędu.
Każde wykorzystanie to zwykła baza PDB z własnym rekordem i trybem:
- Mailbox: bootloader i aplikacja zapisują historię slotów na dwóch stronach w pierścieniu.
- Dziennik zdarzeń: aplikacja dopisuje zdarzenia na dwóch stronach w pierścieniu.
- Dane fabryczne: jedna strona bez pierścienia.
Znaczenie rekordu określa jego użytkownik, a PDB obsługuje każdy rekord tak samo.
Rekord o stałym rozmiarze: pierwsze 4 bajty to numer, a ostatnie 4 bajty to CRC, oba uzupełnia PDB. Baza zajmuje dwie strony w pierścieniu, a jej rodzaj odróżnia ją od innej bazy na tych stronach.
typedef struct {
uint32_t number; // first 4 bytes: the number PDB gives the record
uint32_t time;
uint16_t code;
uint16_t value;
uint32_t crc; // last 4 bytes: the CRC PDB writes
} Event_t;
// Two pages outside the program, a ring; `.no_wrap = true` stops when they are full
static PDB_t events = { .page_start = 48, .page_count = 2,
.record_size = sizeof(Event_t), .kind = 0x5645 };Po każdym starcie najpierw PDB_Init; rekord dopisuje się w całości, a odczyt przechodzi po rekordach pasujących do zapytania.
void events_init(void)
{
PDB_Init(&events); // reads the headers and finds where to write, writes nothing
}
void event_add(uint16_t code, uint16_t value)
{
Event_t event = { .time = (uint32_t)RTC_Timestamp(), .code = code, .value = value };
PDB_Insert(&events, &event); // PDB fills in the number and the CRC
}
void events_print(void)
{
PDB_Query_t query = { .limit = 10, .dir = PDB_Desc }; // the newest ten
PDB_Iter_t iter;
Event_t event;
PDB_IterInit(&events, &iter, &query);
while(PDB_IterNext(&iter, &event) == OK) {
LOG_INF("%u time:%u code:%u value:%u", event.number, event.time, event.code, event.value);
}
}