Repository navigation
pdb
PDB (pico database) keeps small records in MCU flash, for example a bootloader mailbox, an event journal or factory data. Flash works like a notebook written in pen: a line cannot be changed, only a whole page can be erased. So PDB never changes anything, it only adds new lines.
- Each page starts with a header, written right after the page is erased, so a page without a header holds nothing.
- Then come records, all of one size, and PDB numbers them itself: 1, 2, 3.
- A record ends with a CRC, written last. A record without a valid CRC was never finished, and reading skips it.
When the newer page is full, PDB erases the older one and writes there, so one full page of history always stays. Data that must never be lost, factory data for example, sits on one page without the ring: when it is full, writing stops, and the newest valid record counts.
Power can go out at any moment, and every case has its answer:
- Cut while writing: the record has no CRC, and reading skips it.
- Cut while erasing: the page has no header, and PDB erases it again before writing.
- Cut at the very start of a write: the place looks empty but is not, so the first write after every start zeroes the first free place and writes to the next one.
Reading never writes, and PDB never erases a whole database because of an error.
Every use is a plain PDB with its own record and mode:
- Mailbox: the bootloader and the application keep the slot history on two pages in a ring.
- Event journal: the application adds events on two pages in a ring.
- Factory data: one page without the ring.
What a record means is up to its user, and PDB treats every record the same way.
A record of fixed size: its first 4 bytes the number and its last 4 bytes the CRC, both filled in by PDB. The database sits on two pages in a ring, and its kind tells it from another database on those pages.
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 };After every start PDB_Init goes first; a record is added whole, and reading walks the records a query matches.
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);
}
}