150 lines
3.3 KiB
C
150 lines
3.3 KiB
C
#ifndef __SIMPLE_EEPROM_H
|
|
#define __SIMPLE_EEPROM_H
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
|
|
#include "ch32v003fun.h"
|
|
|
|
// config
|
|
#define EEPROM_PAGE_SIZE 64 // size in bytes
|
|
#define EEPROM_HALFWORD_SIZE 2 // size of halfword in bytes
|
|
#define EEPROM_MAX_ENTRIES (EEPROM_PAGE_SIZE / EEPROM_HALFWORD_SIZE)
|
|
|
|
typedef enum {
|
|
EEPROM_OK = 0,
|
|
EEPROM_ERROR_WRITE,
|
|
EEPROM_ERROR_ERASE,
|
|
EEPROM_ERROR_VERIFY
|
|
} eeprom_status_t;
|
|
|
|
// ######## internal variables
|
|
// import from .ld, halal by
|
|
// https://sourceware.org/binutils/docs/ld/Source-Code-Reference.html
|
|
extern char _reserved_nv_start[];
|
|
extern char _reserved_nv_end[];
|
|
|
|
#define EEPROM_BASE (FLASH_BASE + (uint32_t)(uintptr_t)_reserved_nv_start)
|
|
|
|
static void flash_unlock(void) {
|
|
if (FLASH->CTLR & 0x80) {
|
|
FLASH->KEYR = FLASH_KEY1;
|
|
FLASH->KEYR = FLASH_KEY2;
|
|
}
|
|
}
|
|
|
|
static void flash_lock(void) { FLASH->CTLR = 0x80; }
|
|
|
|
static void flash_unlock_fast_mode(void) {
|
|
if (FLASH->CTLR & 0x8000) {
|
|
FLASH->MODEKEYR = FLASH_KEY1;
|
|
FLASH->MODEKEYR = FLASH_KEY2;
|
|
}
|
|
}
|
|
|
|
static void flash_wait_busy(void) { while (FLASH->STATR & FLASH_STATR_BSY); }
|
|
|
|
static eeprom_status_t flash_erase_page(void) {
|
|
uint16_t *page_addr = (uint16_t *)EEPROM_BASE;
|
|
|
|
flash_unlock_fast_mode();
|
|
flash_wait_busy();
|
|
|
|
// erase page
|
|
FLASH->CTLR = CR_PAGE_ER;
|
|
FLASH->ADDR = (uint32_t)page_addr;
|
|
FLASH->CTLR = CR_STRT_Set | CR_PAGE_ER;
|
|
flash_wait_busy();
|
|
|
|
// verify erase
|
|
for (int i = 0; i < EEPROM_MAX_ENTRIES; i++) {
|
|
if (page_addr[i] != 0xFFFF) {
|
|
return EEPROM_ERROR_ERASE;
|
|
}
|
|
}
|
|
|
|
// disable fast mode
|
|
FLASH->CTLR = 0x8000;
|
|
return EEPROM_OK;
|
|
}
|
|
|
|
static int find_next_free_slot(void) {
|
|
uint16_t *word = (uint16_t *)EEPROM_BASE;
|
|
for (int i = 0; i < EEPROM_MAX_ENTRIES; i++) {
|
|
if (word[i] == 0xFFFF) { // look for erased entries
|
|
return i;
|
|
}
|
|
}
|
|
return EEPROM_MAX_ENTRIES;
|
|
}
|
|
|
|
eeprom_status_t eeprom_write(uint16_t data) {
|
|
flash_unlock();
|
|
|
|
// find next available slot
|
|
int slot = find_next_free_slot();
|
|
uint16_t *word = (uint16_t *)EEPROM_BASE + slot;
|
|
|
|
// page is full, erase it
|
|
if (slot >= EEPROM_MAX_ENTRIES) {
|
|
eeprom_status_t status = flash_erase_page();
|
|
if (status != EEPROM_OK) {
|
|
flash_lock();
|
|
return status;
|
|
}
|
|
word = (uint16_t *)EEPROM_BASE; // reset to start of page
|
|
}
|
|
|
|
// write data
|
|
FLASH->CTLR = CR_PG_Set;
|
|
*word = data; // write full 16-bit value
|
|
flash_wait_busy();
|
|
|
|
// verify write
|
|
if (*word != data) {
|
|
flash_lock();
|
|
return EEPROM_ERROR_VERIFY;
|
|
}
|
|
|
|
flash_lock();
|
|
return EEPROM_OK;
|
|
}
|
|
|
|
uint16_t eeprom_read(void) {
|
|
uint16_t *word =
|
|
(uint16_t *)(EEPROM_BASE + EEPROM_PAGE_SIZE - EEPROM_HALFWORD_SIZE);
|
|
|
|
// scan back from the end until we find a non-erased value
|
|
for (int i = 0; i < EEPROM_MAX_ENTRIES; i++) {
|
|
uint16_t val = *word;
|
|
if (val != 0xFFFF) {
|
|
return val;
|
|
}
|
|
word--;
|
|
}
|
|
|
|
return 0xFFFF; // nothing found
|
|
}
|
|
|
|
eeprom_status_t eeprom_init(void) {
|
|
uint16_t *page = (uint16_t *)EEPROM_BASE;
|
|
|
|
// check empty/partial page
|
|
bool needs_erase = true;
|
|
for (int i = 0; i < EEPROM_MAX_ENTRIES; i++) {
|
|
if (page[i] != 0xFFFF) {
|
|
needs_erase = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (needs_erase) {
|
|
return flash_erase_page();
|
|
}
|
|
|
|
return EEPROM_OK;
|
|
}
|
|
|
|
eeprom_status_t eeprom_wipe(void) { return flash_erase_page(); }
|
|
|
|
#endif |