Implement flash config storage

Calibration needs to be saved for each badge in continuity mode. Added a flash-backed user config storing option with basic wear leveling. Have NOT yet tested the write and erase functions.
This commit is contained in:
true 2023-11-01 07:45:44 -07:00
parent ad383a76ac
commit 6394c3aaff
8 changed files with 267 additions and 24 deletions

View File

@ -16,9 +16,39 @@
#define FLASH_HSI_24MHz (uint32_t *)0x1FFF0F6C #define FLASH_HSI_24MHz (uint32_t *)0x1FFF0F6C
#define UCONF_FLASH_SIZE 512
#define UCONF_FLASH_START (FLASH_END + 1) - UCONF_FLASH_SIZE
#define UCONF_SIZE 64 // size in bytes
#define UCONF_COUNT (UCONF_FLASH_SIZE / UCONF_SIZE)
#define UCONF_KEY 0x3713
typedef struct UConf {
uint16_t conf_key; // should read 0x3713
uint8_t cont_buzzer; // continuity buzzer on/off
uint8_t cont_sensitivity; // continuity sensitivity high/low
uint16_t cont_shorted; // ADC counts when probes shorted in continuity mode
uint16_t diode_shorted; // unused, unneeded
uint32_t rsvd_1[4];
uint32_t rsvd_2[8];
uint32_t vctr; // increment every update of config
uint32_t crc32; // CRC of preceding 60 bytes
} UConf; // 64 bytes
extern struct UConf conf;
void flash_init(); void flash_init();
void flash_load_conf();
void flash_commit_conf();
#endif /* _INC_FLASH_H */ #endif /* _INC_FLASH_H */

View File

@ -14,7 +14,7 @@
void rgbprog_run(); void rgbprog_run();
void rgbprog_error_flasher(uint8_t r, uint8_t g, uint8_t b); void rgbprog_error_flasher(uint8_t scale, uint8_t r, uint8_t g, uint8_t b);

View File

@ -56,14 +56,14 @@ debug_build_flags =
[env:sc7-testobot_REV1-py32f-dbg_pyocd] [env:sc7-testobot_REV1-py32f-dbg_pyocd]
build_type = debug build_type = debug
board_upload.maximum_size = 32256 ; 512 bytes for config
debug_build_flags = ${common.debug_build_flags} debug_build_flags = ${common.debug_build_flags}
-DTESTO_REV1 -DTESTO_REV1
[env:sc7-testobot_REV2-py32f-dbg_pyocd] [env:sc7-testobot_REV2-py32f-dbg_pyocd]
build_type = debug build_type = debug
board_upload.maximum_size = 32256 ; 512 bytes for config
debug_build_flags = ${common.debug_build_flags} debug_build_flags = ${common.debug_build_flags}
-DTESTO_REV2 -DTESTO_REV2
@ -71,8 +71,7 @@ debug_build_flags = ${common.debug_build_flags}
[env:sc7-testobot_REV1-py32f-rel_bl] [env:sc7-testobot_REV1-py32f-rel_bl]
build_type = release build_type = release
board_build.ldscript = py32f030x6_bl.ld board_build.ldscript = py32f030x6_bl.ld
board_upload.maximum_size = 30720 ; 2K reserved for bootloader board_upload.maximum_size = 30208 ; 2K reserved for bootloader, 512 bytes for config
build_flags = ${common.build_flags} build_flags = ${common.build_flags}
-DBOOTLOADED -DBOOTLOADED
-DTESTO_REV1 -DTESTO_REV1
@ -81,8 +80,7 @@ build_flags = ${common.build_flags}
[env:sc7-testobot_REV2-py32f-rel_bl] [env:sc7-testobot_REV2-py32f-rel_bl]
build_type = release build_type = release
board_build.ldscript = py32f030x6_bl.ld board_build.ldscript = py32f030x6_bl.ld
board_upload.maximum_size = 30720 ; 2K reserved for bootloader board_upload.maximum_size = 30208 ; 2K reserved for bootloader, 512 bytes for config
build_flags = ${common.build_flags} build_flags = ${common.build_flags}
-DBOOTLOADED -DBOOTLOADED
-DTESTO_REV2 -DTESTO_REV2

View File

@ -30,7 +30,7 @@ _Min_Stack_Size = 0x100; /* required amount of stack: 256 bytes */
MEMORY MEMORY
{ {
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K - 512
} }
/* Define output sections */ /* Define output sections */

View File

@ -30,7 +30,7 @@ _Min_Stack_Size = 0x100; /* required amount of stack: 256 bytes */
MEMORY MEMORY
{ {
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4K RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 4K
FLASH (rx) : ORIGIN = 0x08000800, LENGTH = 32K - 2K FLASH (rx) : ORIGIN = 0x08000800, LENGTH = 32K - 2K - 512
} }
/* Define output sections */ /* Define output sections */

View File

@ -15,6 +15,11 @@
#include "flash.h" #include "flash.h"
struct UConf conf = {0};
/* /*
* loads flash timing values. * loads flash timing values.
* ensure flash is unlocked before calling this function, * ensure flash is unlocked before calling this function,
@ -34,6 +39,7 @@ void flash_set_timing(uint32_t *timing_table)
// the values listed in the reference manual. this is true of // the values listed in the reference manual. this is true of
// all versions I have access to - English v1.0 and Chinese v1.2. // all versions I have access to - English v1.0 and Chinese v1.2.
// for example, @8MHz, RM says 0x5dc0 for PERTPE but ROM holds 0x6b60 // for example, @8MHz, RM says 0x5dc0 for PERTPE but ROM holds 0x6b60
// in testing, either ROM or RM value seems to work.
// note: the datasheet doesn't say it anywhere, but if flash is not unlocked, // note: the datasheet doesn't say it anywhere, but if flash is not unlocked,
// then flash timing values aren't writeable // then flash timing values aren't writeable
@ -84,8 +90,8 @@ __attribute__ ((long_call, section(".ramfunc"))) void flash_init()
// enable EOPIE (per DS, EOP isn't set unless EOPIE is set) // enable EOPIE (per DS, EOP isn't set unless EOPIE is set)
// and signal that we want to write option bits, // and signal that we want to write option bits,
// then trigger the write by writing to a random address shown in the DS
FLASH->CR |= FLASH_CR_EOPIE | FLASH_CR_OPTSTRT; FLASH->CR |= FLASH_CR_EOPIE | FLASH_CR_OPTSTRT;
// then trigger the write by writing to a random address shown in the DS
*((__IO uint32_t *)(0x40022080)) = 0x55aa55aa; *((__IO uint32_t *)(0x40022080)) = 0x55aa55aa;
// wait for BSY to go low, then EOP to go high // wait for BSY to go low, then EOP to go high
@ -103,3 +109,211 @@ __attribute__ ((long_call, section(".ramfunc"))) void flash_init()
// re-lock flash when done // re-lock flash when done
FLASH->CR = FLASH_CR_LOCK; FLASH->CR = FLASH_CR_LOCK;
} }
/*
* loads latest configuration from flash to conf variable in RAM.
*/
void flash_load_conf()
{
int8_t i, j;
uint8_t loaded = 0;
struct UConf *fconf;
uint32_t *f32;
uint32_t *u32;
for (i = (UCONF_COUNT - 1); i >= 0; i--) {
fconf = (struct UConf *)(UCONF_FLASH_START + (UCONF_SIZE * i));
f32 = (uint32_t *)fconf;
// empty flash = nothing here
if (fconf->crc32 == 0xffffffff) continue;
// invalid header = skip
if (fconf->conf_key != UCONF_KEY) continue;
// calculate crc32
CRC->CR = CRC_CR_RESET;
while (CRC->CR & CRC_CR_RESET);
for (j = 0; j < ((UCONF_SIZE / 4) - 1); j++) {
CRC->DR = f32[j];
}
// check crc32
if (CRC->DR == f32[(UCONF_SIZE / 4) - 1]) {
// it passes. copy to RAM and break out
u32 = (uint32_t *)&conf;
for (j = 0; j < (UCONF_SIZE / 4); j++) {
*u32++ = *f32++;
}
loaded = 1;
break;
}
}
if (!loaded) {
// default config of zeroes works
}
}
/*
* erases a flash page at the specified address.
*/
__attribute__ ((long_call, section(".ramfunc"))) void flash_erase_page(uint32_t addr)
{
uint32_t primask;
// ensure flash is not busy
while (FLASH->SR & FLASH_SR_BSY);
// unlock flash
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
while (FLASH->CR & FLASH_CR_LOCK); // still locked? then stall forever
// enable EOPIE (per DS, EOP isn't set unless EOPIE is set)
// and signal that we want to erase a page
FLASH->CR |= FLASH_CR_EOPIE | FLASH_CR_PER;
// disable interrupts
primask = __get_PRIMASK();
__disable_irq();
// trigger the erase by writing to a random address shown in the DS
*((__IO uint32_t *)(addr)) = 0x55aa55aa;
// ensure flash is not busy, then wait for end of programming
while (FLASH->SR & FLASH_SR_BSY);
while (!(FLASH->SR & FLASH_SR_EOP));
// re-enable interrupts
__set_PRIMASK(primask);
// clear EOP
FLASH->SR |= FLASH_SR_EOP;
// and re-lock flash
FLASH->CR = FLASH_CR_LOCK;
}
/*
* erases a flash page at the specified address.
* data must be a pointer to a full page and
* must contain 128 bytes (32 words) of data to write.
*/
__attribute__ ((long_call, section(".ramfunc"))) void flash_write_page(uint32_t addr, uint32_t *data)
{
uint32_t primask;
uint8_t i;
uint32_t *src;
volatile uint32_t *dst;
// ensure flash is not busy
while (FLASH->SR & FLASH_SR_BSY);
// unlock flash
FLASH->KEYR = FLASH_KEY1;
FLASH->KEYR = FLASH_KEY2;
while (FLASH->CR & FLASH_CR_LOCK); // still locked? then stall forever
// enable EOPIE (per DS, EOP isn't set unless EOPIE is set)
// and signal that we want to write a page
FLASH->CR |= FLASH_CR_EOPIE | FLASH_CR_PG;
// disable interrupts
primask = __get_PRIMASK();
__disable_irq();
// write data to flash
src = data;
dst = (volatile uint32_t *)addr;
for (i = 0; i < (FLASH_PAGE_SIZE / 4); i++) {
// on the last page, enable the page write start bit to commit to flash
if (i == (FLASH_PAGE_SIZE / 4) - 1) {
FLASH->CR |= FLASH_CR_PGSTRT;
}
*dst++ = *src++;
}
// ensure flash is not busy, then wait for end of programming
while (FLASH->SR & FLASH_SR_BSY);
while (!(FLASH->SR & FLASH_SR_EOP));
// re-enable interrupts
__set_PRIMASK(primask);
// clear EOP
FLASH->SR |= FLASH_SR_EOP;
// and re-lock flash
FLASH->CR = FLASH_CR_LOCK;
}
/*
* stores configuration from conf variable in RAM to flash.
* performs very basic wear leveling.
*/
void flash_commit_conf()
{
uint32_t i;
uint8_t flash_idx = 0;
uint8_t flash_full;
struct UConf *fconf;
uint32_t *f32, *u32;
uint32_t write[(FLASH_PAGE_SIZE / 4)];
// find first free page index
for (i = 0; i < UCONF_COUNT; i++) {
fconf = (struct UConf *)(UCONF_FLASH_START + (UCONF_SIZE * i));
if ((fconf->crc32 == 0xffffffff) || (fconf->conf_key != UCONF_KEY)) {
flash_idx = i;
break;
}
}
// if all pages are occupied, erase the first page
if (i == UCONF_COUNT) {
flash_full = 1;
flash_erase_page(UCONF_FLASH_START);
}
// copy flash page from ROM to the write buffer,
// as our data (64 bytes) is smaller than the page write size (128 bytes)
f32 = (uint32_t *)fconf;
u32 = (uint32_t *)write;
for (i = 0; i < (FLASH_PAGE_SIZE / 4); i++) {
*u32++ = *f32++;
}
// ensure config header is correct, and calculate CRC
conf.conf_key = UCONF_KEY;
u32 = (uint32_t *)&conf;
CRC->CR = CRC_CR_RESET;
while (CRC->CR & CRC_CR_RESET);
for (i = 0; i < (UCONF_SIZE / 4) - 1; i++) {
CRC->DR = u32[i];
}
u32[(UCONF_SIZE / 4) - 1] = CRC->DR;
// copy user config from RAM to write buffer at the appropriate offset
f32 = (uint32_t *)&conf;
u32 = (uint32_t *)write;
for (i = (flash_idx ? UCONF_SIZE : 0); i < (UCONF_SIZE / 4); i++) {
*u32++ = *f32++;
}
// if flash was full, erase other pages
if (flash_full) {
for (i = (UCONF_FLASH_START + FLASH_PAGE_SIZE); i < FLASH_END; i += FLASH_PAGE_SIZE) {
flash_erase_page(i);
}
}
}

View File

@ -104,6 +104,9 @@ int main()
flash_init(); // also configures option bytes to enable PF2, if necessary flash_init(); // also configures option bytes to enable PF2, if necessary
gpio_init(); gpio_init();
// load configuration from flash
flash_load_conf();
// peripheral initialization // peripheral initialization
led_init(); led_init();
adc_init(); adc_init();

View File

@ -117,7 +117,7 @@ void rgbprog_run()
// if SET1 is out of range, indicate that instead of // if SET1 is out of range, indicate that instead of
// running the normal program // running the normal program
if (userio_get_set1_limit()) { if (userio_get_set1_limit()) {
rgbprog_error_flasher(120, 0, 0); rgbprog_error_flasher(0, 120, 0, 0);
return; return;
} }
@ -405,22 +405,20 @@ void rgbprog_prog7(uint8_t k0)
* *
* flashes LEDs, indicating a SET1 knob error. * flashes LEDs, indicating a SET1 knob error.
*/ */
static uint8_t err_ctr; static uint8_t err_ctr = 0;
static uint8_t err_tog = 0; static uint8_t err_tog = 0;
void rgbprog_error_flasher(uint8_t r, uint8_t g, uint8_t b) void rgbprog_error_flasher(uint8_t scale, uint8_t r, uint8_t g, uint8_t b)
{ {
err_ctr++; uint8_t x;
x = (1 << scale);
err_ctr += x;
err_ctr &= ~(x - 1);
if (!err_ctr) { if (!err_ctr) {
err_tog ^= 1; err_tog ^= 1;
} }
rgb[0].r = r; led_setrgb(err_tog, r, g, b);
rgb[0].g = g; led_setrgb(err_tog ^ 1, 0, 0, 0);
rgb[0].b = b;
rgb_setled(err_tog, &rgb[0]);
rgb[0].r = 0;
rgb[0].g = 0;
rgb[0].b = 0;
rgb_setled(err_tog ^ 1, &rgb[0]);
} }