eeprom_stm32: implement wear leveling

This commit is contained in:
Ilya Zhuravlev 2020-12-26 06:18:20 -05:00
parent 80e488bfae
commit f8638a95e7
2 changed files with 105 additions and 85 deletions

View File

@ -14,102 +14,119 @@
* Artur F.
*
* Modifications for QMK and STM32F303 by Yiancar
* Modifications to add flash wear leveling by Ilya Zhuravlev
*/
#include <stdio.h>
#include <string.h>
#include "eeprom_stm32.h"
/*****************************************************************************
* Allows to use the internal flash to store non volatile data. To initialize
* the functionality use the EEPROM_Init() function. Be sure that by reprogramming
* of the controller just affected pages will be deleted. In other case the non
* volatile data will be lost.
******************************************************************************/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Functions -----------------------------------------------------------------*/
/* In-memory contents of emulated eeprom for faster access */
static uint8_t DataBuf[FEE_DENSITY_BYTES];
uint8_t DataBuf[FEE_PAGE_SIZE];
/*****************************************************************************
* Delete Flash Space used for user Data, deletes the whole space between
* RW_PAGE_BASE_ADDRESS and the last uC Flash Page
******************************************************************************/
uint16_t EEPROM_Init(void) {
// unlock flash
/* Pointer to the first available slot within flash area */
static uint8_t *empty_slot;
void EEPROM_Init(void) {
memset(DataBuf, 0, sizeof(DataBuf));
/* Load emulated eeprom contents from flash into memory */
uint8_t *addr;
for (addr = (uint8_t*)FEE_PAGE_BASE_ADDRESS; addr < (uint8_t*)FEE_LAST_PAGE_ADDRESS; addr += 4) {
uint16_t address;
uint8_t value;
memcpy(&address, addr, sizeof(address));
memcpy(&value, addr+2, sizeof(value));
if (address == FEE_EMPTY_WORD)
break;
if (address < FEE_DENSITY_BYTES)
DataBuf[address] = value;
}
empty_slot = addr;
}
/* Clear flash contents (doesn't touch in-memory DataBuf) */
static void eeprom_clear(void) {
FLASH_Unlock();
// Clear Flags
// FLASH_ClearFlag(FLASH_SR_EOP|FLASH_SR_PGERR|FLASH_SR_WRPERR);
return FEE_DENSITY_BYTES;
}
/*****************************************************************************
* Erase the whole reserved Flash Space used for user Data
******************************************************************************/
void EEPROM_Erase(void) {
int page_num = 0;
// delete all pages from specified start page to the last page
do {
for (uint32_t page_num = 0; page_num < FEE_DENSITY_PAGES; ++page_num)
FLASH_ErasePage(FEE_PAGE_BASE_ADDRESS + (page_num * FEE_PAGE_SIZE));
page_num++;
} while (page_num < FEE_DENSITY_PAGES);
FLASH_Lock();
empty_slot = (void*)FEE_PAGE_BASE_ADDRESS;
}
/*****************************************************************************
* Writes once data byte to flash on specified address. If a byte is already
* written, the whole page must be copied to a buffer, the byte changed and
* the manipulated buffer written after PageErase.
*******************************************************************************/
uint16_t EEPROM_WriteDataByte(uint16_t Address, uint8_t DataByte) {
FLASH_Status FlashStatus = FLASH_COMPLETE;
uint32_t page;
int i;
/* Erase emulated eeprom */
void EEPROM_Erase(void) {
eeprom_clear();
/* re-initialize to clear DataBuf */
EEPROM_Init();
}
// exit if desired address is above the limit (e.G. under 2048 Bytes for 4 pages)
if (Address > FEE_DENSITY_BYTES) {
return 0;
}
static void eeprom_writedatabyte(uint16_t Address, uint8_t DataByte);
// calculate which page is affected (Pagenum1/Pagenum2...PagenumN)
page = FEE_ADDR_OFFSET(Address) / FEE_PAGE_SIZE;
// if current data is 0xFF, the byte is empty, just overwrite with the new one
if ((*(__IO uint16_t *)(FEE_PAGE_BASE_ADDRESS + FEE_ADDR_OFFSET(Address))) == FEE_EMPTY_WORD) {
FlashStatus = FLASH_ProgramHalfWord(FEE_PAGE_BASE_ADDRESS + FEE_ADDR_OFFSET(Address), (uint16_t)(0x00FF & DataByte));
} else {
// Copy Page to a buffer
memcpy(DataBuf, (uint8_t *)FEE_PAGE_BASE_ADDRESS + (page * FEE_PAGE_SIZE), FEE_PAGE_SIZE); // !!! Calculate base address for the desired page
// check if new data is differ to current data, return if not, proceed if yes
if (DataByte == *(__IO uint8_t *)(FEE_PAGE_BASE_ADDRESS + FEE_ADDR_OFFSET(Address))) {
return 0;
}
// manipulate desired data byte in temp data array if new byte is differ to the current
DataBuf[FEE_ADDR_OFFSET(Address) % FEE_PAGE_SIZE] = DataByte;
// Erase Page
FlashStatus = FLASH_ErasePage(FEE_PAGE_BASE_ADDRESS + (page * FEE_PAGE_SIZE));
// Write new data (whole page) to flash if data has been changed
for (i = 0; i < (FEE_PAGE_SIZE / 2); i++) {
if ((__IO uint16_t)(0xFF00 | DataBuf[FEE_ADDR_OFFSET(i)]) != 0xFFFF) {
FlashStatus = FLASH_ProgramHalfWord((FEE_PAGE_BASE_ADDRESS + (page * FEE_PAGE_SIZE)) + (i * 2), (uint16_t)(0xFF00 | DataBuf[FEE_ADDR_OFFSET(i)]));
}
/* Dump in-memory contents into flash */
static void eeprom_restore(void) {
for (uint32_t i = 0; i < FEE_DENSITY_BYTES; ++i) {
/* don't bother writing zeroes */
if (DataBuf[i]) {
eeprom_writedatabyte(i, DataBuf[i]);
}
}
return FlashStatus;
}
/*****************************************************************************
* Read once data byte from a specified address.
*******************************************************************************/
static void eeprom_writedatabyte(uint16_t Address, uint8_t DataByte) {
/* if couldn't find an empty spot, we must re-initialize emulated eeprom */
if (empty_slot >= (uint8_t*)FEE_LAST_PAGE_ADDRESS) {
/* ensure that the following call to eeprom_restore will write our desired byte value */
DataBuf[Address] = DataByte;
/* fully erase emulated eeprom */
eeprom_clear();
/* and then write DataBuf contents back into flash */
eeprom_restore();
/* don't need to do anything else as eeprom_restore already wrote our value */
return;
}
/* ok we found a place let's write our data */
FLASH_Unlock();
/* address */
FLASH_ProgramHalfWord((uint32_t)empty_slot, Address);
/* value */
FLASH_ProgramHalfWord((uint32_t)empty_slot + 2, DataByte | 0xFF00);
FLASH_Lock();
empty_slot += 4;
}
void EEPROM_WriteDataByte(uint16_t Address, uint8_t DataByte) {
/* if the address is out-of-bounds, do nothing */
if (Address >= FEE_DENSITY_BYTES)
return;
/* if the value is the same, don't bother writing it */
if (DataBuf[Address] == DataByte)
return;
/* perform the write into flash memory */
eeprom_writedatabyte(Address, DataByte);
/* keep DataBuf cache in sync */
DataBuf[Address] = DataByte;
}
uint8_t EEPROM_ReadDataByte(uint16_t Address) {
uint8_t DataByte = 0xFF;
uint8_t DataByte = 0x00;
// Get Byte from specified address
DataByte = (*(__IO uint8_t *)(FEE_PAGE_BASE_ADDRESS + FEE_ADDR_OFFSET(Address)));
if (Address < FEE_DENSITY_BYTES)
DataByte = DataBuf[Address];
return DataByte;
}

View File

@ -43,7 +43,7 @@
#ifndef EEPROM_PAGE_SIZE
# if defined(MCU_STM32F103RB)
# define FEE_PAGE_SIZE (uint16_t)0x400 // Page size = 1KByte
# define FEE_DENSITY_PAGES 2 // How many pages are used
# define FEE_DENSITY_PAGES 8 // How many pages are used
# elif defined(MCU_STM32F103ZE) || defined(MCU_STM32F103RE) || defined(MCU_STM32F103RD) || defined(MCU_STM32F303CC) || defined(MCU_STM32F072CB)
# define FEE_PAGE_SIZE (uint16_t)0x800 // Page size = 2KByte
# define FEE_DENSITY_PAGES 4 // How many pages are used
@ -66,18 +66,21 @@
# endif
#endif
// DONT CHANGE
// Choose location for the first EEPROM Page address on the top of flash
/* Start of the emulated eeprom flash area */
#define FEE_PAGE_BASE_ADDRESS ((uint32_t)(0x8000000 + FEE_MCU_FLASH_SIZE * 1024 - FEE_DENSITY_PAGES * FEE_PAGE_SIZE))
#define FEE_DENSITY_BYTES ((FEE_PAGE_SIZE / 2) * FEE_DENSITY_PAGES - 1)
/* End of the emulated eeprom flash area */
#define FEE_LAST_PAGE_ADDRESS (FEE_PAGE_BASE_ADDRESS + (FEE_PAGE_SIZE * FEE_DENSITY_PAGES))
/* Size of emulated eeprom */
#define FEE_DENSITY_BYTES 1024
/* Flash word value after erase */
#define FEE_EMPTY_WORD ((uint16_t)0xFFFF)
#define FEE_ADDR_OFFSET(Address) (Address * 2) // 1Byte per Word will be saved to preserve Flash
// Use this function to initialize the functionality
uint16_t EEPROM_Init(void);
_Static_assert(FEE_DENSITY_PAGES * FEE_PAGE_SIZE >= FEE_DENSITY_BYTES * 8,
"flash memory for emulated eeprom is too small; for correct functionality ensure it is at least 8x FEE_DENSITY_BYTES");
void EEPROM_Init(void);
void EEPROM_Erase(void);
uint16_t EEPROM_WriteDataByte(uint16_t Address, uint8_t DataByte);
void EEPROM_WriteDataByte(uint16_t Address, uint8_t DataByte);
uint8_t EEPROM_ReadDataByte(uint16_t Address);
#endif /* __EEPROM_H */