diff --git a/build-number.txt b/build-number.txt index 18e1dd6..e5a135a 100644 --- a/build-number.txt +++ b/build-number.txt @@ -1 +1 @@ -442 +445 diff --git a/ld/common.ld b/ld/common_nvs.ld similarity index 87% rename from ld/common.ld rename to ld/common_nvs.ld index 9d9835a..91ea21c 100644 --- a/ld/common.ld +++ b/ld/common_nvs.ld @@ -12,6 +12,15 @@ SECTIONS _end_text = .; } >flash + /* Non-volatile storage area in flash */ + .nvstore (NOLOAD) : + { + . = ALIGN(1K); + *(.nvstore*) + . = ALIGN(1K); + _end_nvstore = .; + } > flash_nvstore + /* C++ initialiser code segments */ .preinit_array : { diff --git a/ld/stm32f030f4_flash.ld b/ld/stm32f030f4_15k_flash_1k_nvs.ld similarity index 59% rename from ld/stm32f030f4_flash.ld rename to ld/stm32f030f4_15k_flash_1k_nvs.ld index 9fecb90..93724af 100644 --- a/ld/stm32f030f4_flash.ld +++ b/ld/stm32f030f4_15k_flash_1k_nvs.ld @@ -1,7 +1,8 @@ MEMORY { - flash (rx) : ORIGIN = 0x08000000, LENGTH = 16K + flash (rx) : ORIGIN = 0x08000000, LENGTH = 15K + flash_nvstore (rw) : ORIGIN = 0x08003c00, LENGTH = 1K sram (xrw) : ORIGIN = 0x20000000, LENGTH = 4K } -INCLUDE ld/common.ld +INCLUDE ld/common_nvs.ld diff --git a/makefile b/makefile index f0849c5..6583cf1 100644 --- a/makefile +++ b/makefile @@ -10,7 +10,7 @@ DEBUG := yes H_DEVICE = STM32F030x6 STARTUP_SOURCE_DIR = src STARTUP_SOURCES = $(STARTUP_SOURCE_DIR)/startup.S -LD_SCRIPT = ld/stm32f030f4_flash.ld +LD_SCRIPT = ld/stm32f030f4_15k_flash_1k_nvs.ld ifeq ($(DEBUG),yes) DEBUG_FLAGS = -DDEBUG -g3 diff --git a/src/animation.c b/src/animation.c index 3da8329..6c50ff3 100644 --- a/src/animation.c +++ b/src/animation.c @@ -1,6 +1,7 @@ #include "animation.h" #include "animation_lut.h" #include "light_sensor.h" +#include "nvs.h" volatile bool Animation_FrameFlag = false; @@ -75,18 +76,19 @@ void Animation_Poll(void) } Animation_FrameFlag = false; - static unsigned int bottom = 0; - static unsigned int top = 0; - - bottom += ANIMATION_STEPS / ANIMATION_CYCLE_TIME_BOTTOM + NVS_Data->animation_step_bottom += ANIMATION_STEPS + / ANIMATION_CYCLE_TIME_BOTTOM / ANIMATION_REFRESH_RATE; - top += ANIMATION_STEPS / ANIMATION_CYCLE_TIME_TOP + NVS_Data->animation_step_top += ANIMATION_STEPS + / ANIMATION_CYCLE_TIME_TOP / ANIMATION_REFRESH_RATE; - bottom %= 2 * ANIMATION_STEPS; - top %= 2 * ANIMATION_STEPS; + NVS_Data->animation_step_bottom %= 2 * ANIMATION_STEPS; + NVS_Data->animation_step_top %= 2 * ANIMATION_STEPS; - Animation_DrawGradient(bottom, top, LightSensor_RelativeBrightness); + Animation_DrawGradient(NVS_Data->animation_step_bottom, + NVS_Data->animation_step_top, + LightSensor_RelativeBrightness); LED_Commit(); } diff --git a/src/nvs.c b/src/nvs.c new file mode 100644 index 0000000..1cb4466 --- /dev/null +++ b/src/nvs.c @@ -0,0 +1,183 @@ +#include +#include + +#include "nvs.h" + +// Note that the area used for storage must be aligned to the flash's pages. If +// you want to use a larger area, be sure to also change the size and location +// in the linker script. +#define NVS_AREA_SIZE 1024 +#define NVS_BLOCK_COUNT (NVS_AREA_SIZE / sizeof(NVS_Block_t)) +#define NVS_VALID_BLOCK_MARKER 0xab34 + +typedef struct +__attribute__((packed)) +{ + // Actual block data + NVS_Data_t data; + + // Ensure struct size is divisible by two + uint8_t padding[sizeof(NVS_Data_t) % 2]; + + // Block marker for detecting if the block is the current or an old block + uint16_t marker; + + uint32_t crc; +} NVS_Block_t; + +__attribute__((used, section(".nvstore"))) +volatile NVS_Block_t NVS_Area[NVS_BLOCK_COUNT]; + +volatile NVS_Block_t *NVS_FlashData; +__attribute__((used)) +NVS_Block_t NVS_RAMData; + +NVS_Data_t *const NVS_Data = &NVS_RAMData.data; + +static uint32_t NVS_CalculateCRC(NVS_Data_t *data) +{ + CRC->CR = CRC_CR_RESET; + for(int i = 0; i < sizeof(NVS_Data_t); i++) + { + CRC->DR = ((uint8_t*)(data))[i]; + } + return CRC->DR; +} + +static void NVS_ProgramHalfWord(uint16_t *dest, uint16_t value) +{ + FLASH->CR |= FLASH_CR_PG; + *(volatile uint16_t*)dest = value; + while(FLASH->SR & FLASH_SR_BSY); + if(*dest != value) + { + // Write failed + __asm__("bkpt"); + } +} + +static void NVS_UnlockFlash(void) +{ + while(FLASH->SR & FLASH_SR_BSY); + if(FLASH->CR & FLASH_CR_LOCK) + { + FLASH->KEYR = 0x45670123; + FLASH->KEYR = 0xcdef89ab; + } +} + +static void NVS_EraseArea(void) +{ + for(int i = 0; i < NVS_AREA_SIZE; i += 1024) + { + while(FLASH->SR & FLASH_SR_BSY); + FLASH->CR |= FLASH_CR_PER; + FLASH->AR = (uint32_t)NVS_Area + i; + FLASH->CR |= FLASH_CR_STRT; + while(FLASH->SR & FLASH_SR_BSY); + if(FLASH->SR & FLASH_SR_EOP) + { + // Clear EOP flag + FLASH->SR = FLASH_SR_EOP; + } + else + { + // Erase failed + __asm__("bkpt"); + } + FLASH->CR &= ~FLASH_CR_PER; + } +} + +static bool NVS_BlockEmpty(NVS_Block_t *block) +{ + for(int i = 0; i < sizeof(NVS_Block_t) / 2; i++) + { + if(*((uint16_t*)block + i) != 0xffff) + { + return false; + } + } + return true; +} + +static void NVS_LoadDefaults(void) +{ + NVS_Data->animation_step_bottom = 0; + NVS_Data->animation_step_top = 0; +} + +bool NVS_Load(void) +{ + RCC->AHBENR |= RCC_AHBENR_CRCEN; + + volatile NVS_Block_t *block = NULL; + + // Find valid block + for(int i = 0; i < NVS_BLOCK_COUNT; i++) + { + block = &NVS_Area[i]; + if(block->marker == NVS_VALID_BLOCK_MARKER) + { + // Valid block found, next up is the CRC check + break; + } + else if(block->marker == 0xffff) + { + // This block was erased and no block before was valid, so we can + // assume there is no valid block + block = NULL; + break; + } + } + + if(block == NULL || block->marker != NVS_VALID_BLOCK_MARKER + || block->crc != NVS_CalculateCRC(&block->data)) + { + // No valid block found + NVS_LoadDefaults(); + return false; + } + else + { + // Valid block and CRC check successful + NVS_FlashData = block; + memcpy(&NVS_RAMData, (void*)block, sizeof(NVS_Block_t)); + return true; + } +} + +void NVS_Save(void) +{ + NVS_UnlockFlash(); + + // Currently loaded block. Is NULL if the defaults were loaded in NVS_Load() + // instead of some flash contents. + NVS_Block_t *current_block = NVS_FlashData; + NVS_Block_t *next_block = NVS_FlashData + 1; + + if(current_block == NULL || next_block > NVS_Area + NVS_BLOCK_COUNT + || !NVS_BlockEmpty(next_block)) + { + NVS_EraseArea(); + next_block = &NVS_Area[0]; + current_block = NULL; + } + + NVS_RAMData.crc = NVS_CalculateCRC(NVS_Data); + NVS_RAMData.marker = NVS_VALID_BLOCK_MARKER; + + // The block length is guaranteed to be divisible by 2 + for(int i = 0; i < sizeof(NVS_Block_t) / 2; i++) + { + NVS_ProgramHalfWord((uint16_t*)next_block + i, + *((uint16_t*)&NVS_RAMData + i)); + } + + if(current_block != NULL) + { + NVS_ProgramHalfWord((uint16_t*)¤t_block->marker, 0x0000); + } + + NVS_FlashData = next_block; +} diff --git a/src/nvs.h b/src/nvs.h new file mode 100644 index 0000000..0ef9aab --- /dev/null +++ b/src/nvs.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include "stm32f030x6.h" + +typedef struct +__attribute__((packed)) +{ + unsigned int animation_step_bottom; + unsigned int animation_step_top; +} NVS_Data_t; + +extern NVS_Data_t *const NVS_Data; + +// Returns true if the data was successfully loaded from flash and false if the +// defaults were restored instead +bool NVS_Load(void); + +// Stores the current contents of NVS_Data to flash +void NVS_Save(void); + +