Skip to content

Instantly share code, notes, and snippets.

@rikka0w0
Last active June 11, 2021 15:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rikka0w0/99278e44eb0244c354fc97f408ae605e to your computer and use it in GitHub Desktop.
Save rikka0w0/99278e44eb0244c354fc97f408ae605e to your computer and use it in GitHub Desktop.
Reliable config data storage on the built-in flash of STM32. Tolerate to power loss and flash damage.
/**
* Resource used:
* Flash, CRC
*/
/**
* A utility which reliably stores config data on the built-in flash of the MCU.
* Data integrity is guaranteed by CRC32. If a power loss happens during a flash
* write operation, upon next start-up, the last valid configuration will be used.
*/
#include "config.h"
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "crc.h"
#include "stm32f0xx_ll_bus.h"
#define CONFIG_ENTER_CRITICAL() (__disable_irq())
#define CONFIG_EXIT_CRITICAL() (__enable_irq())
#if ((SNAPSHOT_PAYLOAD_SIZE >> 2) << 2) != SNAPSHOT_PAYLOAD_SIZE
#error "SNAPSHOT_PAYLOAD_SIZE is not a multiple of 4!"
#endif
#if ((FLASH_PAGE_SIZE >> 4) << 4) != FLASH_PAGE_SIZE
#error "FLASH_PAGE_SIZE is not a multiple of 16!"
#endif
/**
* These are defined in the linker script (xxxxxx_FLASH.ld).
*/
extern uint32_t _user_config_start;
extern uint32_t _user_config_break;
#define FLASH_PAGE_A (&_user_config_start)
#define FLASH_PAGE_B (&_user_config_break)
#define INDEX_IS_NEWER(input, ref) ((uint32_t)((int8_t)input) - (uint32_t)((int8_t)ref) < 0x7FFFFFFF)
typedef struct config_snapshot_t {
uint32_t crc32;
uint8_t index;
uint8_t reserved1;
uint16_t reserved2;
uint8_t payload[SNAPSHOT_PAYLOAD_SIZE];
} cs;
enum config_active_page {
PAGE_NONE,
PAGE_A,
PAGE_B
};
/**
* Indicates the flash page of the current valid snapshot.
*/
static enum config_active_page config_page;
/**
* The address for the next possible snapshot.
* Set to NULL if the current flash page don't have sufficient space.
*/
static void* config_next_snapshot;
static cs config_wip_snapshot;
static uint32_t config_snapshot_crc32(const cs* snapshot_ptr) {
return crc32_calc(&snapshot_ptr->index, sizeof(cs) - sizeof(uint32_t));
}
static int config_snapshot_validate(const cs* snapshot_ptr) {
return snapshot_ptr->crc32 == config_snapshot_crc32(snapshot_ptr);
}
/**
* Search for the latest snapshot in the current page
*/
static cs* config_locate_latest_snapshot(void* page_addr) {
cs* cur_addr = (cs*) page_addr;
void* end_addr = (void*) ((char*)page_addr + FLASH_PAGE_SIZE);
while ((void*)(cur_addr + 1) <= end_addr) {
cur_addr++;
}
cur_addr = cur_addr - 1;
// Now cur_addr contains the address of the latest possible snapshot in this page.
while ((void*)cur_addr >= page_addr) {
if (config_snapshot_validate(cur_addr)) {
return cur_addr;
}
cur_addr--;
}
// Unable to find a valid snapshot in this page.
return NULL;
}
/**
* Return the pointer to next vacant space if a new snapshot can be inserted into the page,
* otherwise return NULL.
*/
static cs* config_fit_next_snapshot(void* page_addr, cs* latest_snapshot) {
cs* ret = latest_snapshot + 1;
void* end_addr = (void*) ((char*)page_addr + FLASH_PAGE_SIZE);
return (void*)(ret+1) > end_addr ? NULL : ret;
}
static void config_load_defaults() {
config_page = PAGE_NONE;
config_next_snapshot = NULL;
memset(&config_wip_snapshot, 0, sizeof(cs));
config_wip_snapshot.index = 254; // Test the index roll-over
config_cb_load_default();
}
void config_init(void) {
// Look for the latest snapshot
cs* latest_snapshot_a = config_locate_latest_snapshot(FLASH_PAGE_A);
cs* latest_snapshot_b = config_locate_latest_snapshot(FLASH_PAGE_B);
if (latest_snapshot_a == NULL) {
if (latest_snapshot_b == NULL) {
// Unable to find valid snapshot in both flash page
config_page = PAGE_NONE;
} else {
config_page = PAGE_B;
}
} else {
if (latest_snapshot_b == NULL) {
config_page = PAGE_A;
} else {
config_page = INDEX_IS_NEWER(latest_snapshot_a->index, latest_snapshot_b->index) ? PAGE_A : PAGE_B;
}
}
// Copy the active snapshot into memory
switch (config_page) {
case PAGE_A:
memcpy(&config_wip_snapshot, latest_snapshot_a, sizeof(cs));
config_next_snapshot = config_fit_next_snapshot(FLASH_PAGE_A, latest_snapshot_a);
break;
case PAGE_B:
memcpy(&config_wip_snapshot, latest_snapshot_b, sizeof(cs));
config_next_snapshot = config_fit_next_snapshot(FLASH_PAGE_B, latest_snapshot_b);
break;
default:
config_load_defaults();
}
config_cb_on_load();
}
static void flash_erase_page(void* page_addr) {
CONFIG_ENTER_CRITICAL();
// Wait until flash is not in use
while (FLASH->SR & FLASH_SR_BSY);
// Unlock
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
// Erase page
FLASH->CR |= FLASH_CR_PER; // (1) Set the PER bit in the FLASH_CR register to enable page erasing
FLASH->AR = (uint32_t) page_addr; // (2) Program the FLASH_AR register to select a page to erase
FLASH->CR |= FLASH_CR_STRT; // (3) Set the STRT bit in the FLASH_CR register to start the erasing
// (4) Wait until the BSY bit is reset in the FLASH_SR register
while ((FLASH->SR & FLASH_SR_BSY) != 0);
if (FLASH->SR & FLASH_SR_EOP) { // (5) Check the EOP flag in the FLASH_SR register
FLASH->SR = FLASH_SR_EOP; // (6) Clear EOP flag by software by writing EOP at 1
} else {
// Error, trigger a hard fault by writing to an invalid address
*((uint32_t*) 0xDEADBEEF) = 0xDEADBEEF;
}
FLASH->CR &= ~FLASH_CR_PER; // (7) Reset the PER Bit to disable the page erase
// Flash RW operation done, re-lock the flash
FLASH->CR = FLASH_CR_LOCK;
}
static void flash_program_page(void* dst_addr) {
uint16_t* src_addr = (uint16_t*) &config_wip_snapshot;
void* end_addr = (void*) ((char*)dst_addr + sizeof(cs));
CONFIG_ENTER_CRITICAL();
// Wait until flash is not in use
while (FLASH->SR & FLASH_SR_BSY);
// Unlock
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
// Program page
while (FLASH->SR & FLASH_SR_BSY); // Wait until flash is not in use
FLASH->CR |= FLASH_CR_PG;
while (dst_addr < end_addr) {
*((__IO uint16_t*) dst_addr) = *src_addr;
while (FLASH->SR & FLASH_SR_BSY); // Wait until flash is not in use
src_addr++;
dst_addr = (void*) ((uint16_t*)dst_addr + 1);
}
if (FLASH->SR & FLASH_SR_EOP) {
FLASH->SR = FLASH_SR_EOP;
} else {
// Error, trigger a hard fault by writing to an invalid address
*((uint32_t*) 0xDEADBEEF) = 0xDEADBEEF;
}
FLASH->CR &= ~FLASH_CR_PG;
// Flash RW operation done, re-lock the flash
FLASH->CR = FLASH_CR_LOCK;
CONFIG_EXIT_CRITICAL();
}
void config_wipe() {
config_load_defaults();
flash_erase_page(FLASH_PAGE_A);
flash_erase_page(FLASH_PAGE_B);
config_cb_on_load();
}
void config_commit() {
// Increase the index
config_wip_snapshot.index++;
// Recalc. the CRC32 checksum
config_wip_snapshot.crc32 = config_snapshot_crc32(&config_wip_snapshot);
if (config_next_snapshot) {
// Current flash page still have space.
flash_program_page(config_next_snapshot);
// Update global state
config_next_snapshot = config_fit_next_snapshot(
config_page == PAGE_B ? FLASH_PAGE_B : FLASH_PAGE_A,
config_next_snapshot);
} else {
// Current flash page is full or uninitialized.
// If both pages are uninitialized or invalid, default to PAGE_A.
if (config_page == PAGE_A) {
config_next_snapshot = FLASH_PAGE_B;
config_page = PAGE_B;
} else {
config_next_snapshot = FLASH_PAGE_A;
config_page = PAGE_A;
}
// Commit the current snapshot to the beginning of the other flash page
flash_erase_page(config_next_snapshot);
flash_program_page(config_next_snapshot);
if (config_snapshot_crc32((cs*)config_next_snapshot) != config_wip_snapshot.crc32) {
// Error, trigger a hard fault by writing to an invalid address
*((uint32_t*) 0xDEADBEEF) = 0xDEADBEEF;
}
// Update global state
config_next_snapshot = config_fit_next_snapshot(config_next_snapshot, config_next_snapshot);
}
}
void* config_get_data_ptr() {
return (void*) &config_wip_snapshot.payload;
}
#ifndef CONFIG_H_
#define CONFIG_H_
/**
* The maximum size of the config data.
* This size should be multiple of 4.
*/
#define SNAPSHOT_PAYLOAD_SIZE 492
/**
* See datasheet.
*/
#define FLASH_PAGE_SIZE 1024
/**
* Read the stored config data from flash
*/
void config_init(void);
/**
* Returns a pointer to the config data in the memory,
* 4-byte aligned. Write changes to this memory area,
* then commit the changes by invoking config_commit().
* If config_commit() is not called, any changes will
* be discarded after power lost.
*/
void* config_get_data_ptr(void);
/**
* Erase the config flash, and then load the default content.
*/
void config_wipe(void);
/**
* Write the changes into flash.
*/
void config_commit(void);
/**
* Callback, load the default content.
*/
void config_cb_load_default(void);
/**
* Callback, when the config is loaded.
*/
void config_cb_on_load(void);
#endif /* CONFIG_H_ */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment