2025-12-07 21:39:26 +01:00
|
|
|
#include "eeprom.h"
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Record format (4 bytes):
|
|
|
|
|
* [0] VirtAddress (uint16_t)
|
|
|
|
|
* [2] Data (uint16_t)
|
|
|
|
|
*
|
|
|
|
|
* Page layout:
|
|
|
|
|
* [0] PageStatus (uint16_t)
|
|
|
|
|
* [2..] Records...
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
|
{
|
|
|
|
|
uint16_t VirtAddress;
|
|
|
|
|
uint16_t Data;
|
|
|
|
|
} EE_Record_t;
|
|
|
|
|
|
|
|
|
|
/* Active page base address (runtime selected in EE_Init) */
|
|
|
|
|
static uint32_t EE_ActivePageBase = EE_PAGE0_BASE;
|
|
|
|
|
|
|
|
|
|
/* 32 sequential virtual addresses */
|
|
|
|
|
const uint16_t EE_VirtAddrs[EE_NUM_VIRTUAL_ADDR] =
|
|
|
|
|
{
|
|
|
|
|
0x0001, 0x0002, 0x0003, 0x0004,
|
|
|
|
|
0x0005, 0x0006, 0x0007, 0x0008,
|
|
|
|
|
0x0009, 0x000A, 0x000B, 0x000C,
|
|
|
|
|
0x000D, 0x000E, 0x000F, 0x0010,
|
|
|
|
|
0x0011, 0x0012, 0x0013, 0x0014,
|
|
|
|
|
0x0015, 0x0016, 0x0017, 0x0018,
|
|
|
|
|
0x0019, 0x001A, 0x001B, 0x001C,
|
|
|
|
|
0x001D, 0x001E, 0x001F, 0x0020
|
|
|
|
|
};
|
|
|
|
|
/* ========================================================================= */
|
|
|
|
|
|
|
|
|
|
/* --- Internal helpers ---------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
static uint16_t EE_GetPageStatus(uint32_t pageBase)
|
|
|
|
|
{
|
|
|
|
|
return *(__IO uint16_t *)pageBase;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static HAL_StatusTypeDef EE_FlashProgramHalfWord(uint32_t Address, uint16_t Data)
|
|
|
|
|
{
|
|
|
|
|
HAL_StatusTypeDef status;
|
|
|
|
|
|
|
|
|
|
HAL_FLASH_Unlock();
|
|
|
|
|
status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, Address, Data);
|
|
|
|
|
HAL_FLASH_Lock();
|
|
|
|
|
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static HAL_StatusTypeDef EE_FlashErasePage(uint32_t PageAddress)
|
|
|
|
|
{
|
|
|
|
|
HAL_StatusTypeDef status;
|
|
|
|
|
FLASH_EraseInitTypeDef EraseInit;
|
|
|
|
|
uint32_t PageError = 0;
|
|
|
|
|
|
|
|
|
|
HAL_FLASH_Unlock();
|
|
|
|
|
|
|
|
|
|
EraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
|
|
|
|
|
EraseInit.PageAddress = PageAddress;
|
|
|
|
|
EraseInit.NbPages = 1;
|
|
|
|
|
|
|
|
|
|
status = HAL_FLASHEx_Erase(&EraseInit, &PageError);
|
|
|
|
|
|
|
|
|
|
HAL_FLASH_Lock();
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Find first free record address in given page (returns 0 if full) */
|
|
|
|
|
static uint32_t EE_FindFreeAddress(uint32_t pageBase)
|
|
|
|
|
{
|
|
|
|
|
uint32_t addr = pageBase + 2U; /* Skip status word */
|
|
|
|
|
uint32_t pageEnd = pageBase + EE_PAGE_SIZE;
|
|
|
|
|
|
|
|
|
|
while (addr < (pageEnd - sizeof(EE_Record_t) + 1U))
|
|
|
|
|
{
|
|
|
|
|
uint16_t vaddr = *(__IO uint16_t *)addr;
|
|
|
|
|
|
|
|
|
|
if (vaddr == 0xFFFFU)
|
|
|
|
|
{
|
|
|
|
|
/* Empty slot */
|
|
|
|
|
return addr;
|
|
|
|
|
}
|
|
|
|
|
addr += sizeof(EE_Record_t);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0U; /* No space */
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-08 13:48:52 +01:00
|
|
|
/* Find latest value of VirtAddress in a specific page (internal, uses EE_Status) */
|
|
|
|
|
/* Find latest value of VirtAddress in a specific page (scan forward) */
|
2025-12-07 21:39:26 +01:00
|
|
|
static EE_Status EE_FindInPage(uint32_t pageBase, uint16_t VirtAddress, uint16_t *Data)
|
|
|
|
|
{
|
2025-12-08 13:48:52 +01:00
|
|
|
uint32_t addr = pageBase + 2U; // skip status halfword
|
|
|
|
|
uint32_t pageEnd = pageBase + EE_PAGE_SIZE;
|
|
|
|
|
EE_Status result = EE_NOT_FOUND;
|
|
|
|
|
uint16_t lastVal = 0;
|
2025-12-07 21:39:26 +01:00
|
|
|
|
|
|
|
|
if (Data == NULL)
|
|
|
|
|
return EE_ERROR;
|
|
|
|
|
|
2025-12-08 13:48:52 +01:00
|
|
|
while (addr <= (pageEnd - sizeof(EE_Record_t)))
|
2025-12-07 21:39:26 +01:00
|
|
|
{
|
|
|
|
|
uint16_t vaddr = *(__IO uint16_t *)addr;
|
|
|
|
|
|
|
|
|
|
if (vaddr == 0xFFFFU)
|
|
|
|
|
{
|
2025-12-08 13:48:52 +01:00
|
|
|
// First empty slot => no more records in this page
|
|
|
|
|
break;
|
2025-12-07 21:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-08 13:48:52 +01:00
|
|
|
uint16_t value = *(__IO uint16_t *)(addr + 2U);
|
|
|
|
|
|
2025-12-07 21:39:26 +01:00
|
|
|
if (vaddr == VirtAddress)
|
|
|
|
|
{
|
2025-12-08 13:48:52 +01:00
|
|
|
lastVal = value; // keep most recent
|
|
|
|
|
result = EE_OK;
|
2025-12-07 21:39:26 +01:00
|
|
|
}
|
2025-12-08 13:48:52 +01:00
|
|
|
|
|
|
|
|
addr += sizeof(EE_Record_t); // move 4 bytes forward
|
2025-12-07 21:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-08 13:48:52 +01:00
|
|
|
if (result == EE_OK)
|
|
|
|
|
*Data = lastVal;
|
|
|
|
|
|
|
|
|
|
return result;
|
2025-12-07 21:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
2025-12-08 13:48:52 +01:00
|
|
|
|
|
|
|
|
|
2025-12-07 21:39:26 +01:00
|
|
|
/* Format both pages: erase and set PAGE0 as VALID */
|
|
|
|
|
static EE_Status EE_Format(void)
|
|
|
|
|
{
|
|
|
|
|
if (EE_FlashErasePage(EE_PAGE0_BASE) != HAL_OK)
|
2025-12-08 13:48:52 +01:00
|
|
|
return EE_STATUS_ERROR;
|
2025-12-07 21:39:26 +01:00
|
|
|
|
|
|
|
|
if (EE_FlashErasePage(EE_PAGE1_BASE) != HAL_OK)
|
2025-12-08 13:48:52 +01:00
|
|
|
return EE_STATUS_ERROR;
|
2025-12-07 21:39:26 +01:00
|
|
|
|
|
|
|
|
if (EE_FlashProgramHalfWord(EE_PAGE0_BASE, EE_PAGE_STATUS_VALID) != HAL_OK)
|
2025-12-08 13:48:52 +01:00
|
|
|
return EE_STATUS_ERROR;
|
2025-12-07 21:39:26 +01:00
|
|
|
|
|
|
|
|
/* PAGE1 will remain erased (status = 0xFFFF) */
|
|
|
|
|
EE_ActivePageBase = EE_PAGE0_BASE;
|
|
|
|
|
|
2025-12-08 13:48:52 +01:00
|
|
|
return EE_STATUS_OK;
|
2025-12-07 21:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Get the base of the other page */
|
|
|
|
|
static uint32_t EE_GetOtherPageBase(uint32_t pageBase)
|
|
|
|
|
{
|
|
|
|
|
return (pageBase == EE_PAGE0_BASE) ? EE_PAGE1_BASE : EE_PAGE0_BASE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Page transfer (garbage collection + new write) */
|
|
|
|
|
static EE_Status EE_PageTransfer(uint16_t VirtAddress, uint16_t Data)
|
|
|
|
|
{
|
|
|
|
|
uint32_t oldBase = EE_ActivePageBase;
|
|
|
|
|
uint32_t newBase = EE_GetOtherPageBase(oldBase);
|
|
|
|
|
uint32_t addr;
|
|
|
|
|
uint16_t value;
|
|
|
|
|
EE_Status st;
|
|
|
|
|
|
|
|
|
|
/* Erase new page */
|
|
|
|
|
if (EE_FlashErasePage(newBase) != HAL_OK)
|
2025-12-08 13:48:52 +01:00
|
|
|
return EE_STATUS_ERROR;
|
2025-12-07 21:39:26 +01:00
|
|
|
|
|
|
|
|
/* Mark new page as RECEIVE */
|
|
|
|
|
if (EE_FlashProgramHalfWord(newBase, EE_PAGE_STATUS_RECEIVE) != HAL_OK)
|
2025-12-08 13:48:52 +01:00
|
|
|
return EE_STATUS_ERROR;
|
2025-12-07 21:39:26 +01:00
|
|
|
|
|
|
|
|
/* Start writing records just after status */
|
|
|
|
|
addr = newBase + 2U;
|
|
|
|
|
|
|
|
|
|
/* For each known virtual variable */
|
|
|
|
|
for (uint16_t i = 0; i < EE_NUM_VIRTUAL_ADDR; i++)
|
|
|
|
|
{
|
|
|
|
|
uint16_t vaddr = EE_VirtAddrs[i];
|
|
|
|
|
|
|
|
|
|
if (vaddr == VirtAddress)
|
|
|
|
|
{
|
|
|
|
|
/* Use the new data passed into PageTransfer */
|
|
|
|
|
value = Data;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Read latest value from old active page */
|
|
|
|
|
st = EE_FindInPage(oldBase, vaddr, &value);
|
2025-12-08 13:48:52 +01:00
|
|
|
if (st != EE_STATUS_OK)
|
2025-12-07 21:39:26 +01:00
|
|
|
{
|
|
|
|
|
/* Variable never written -> skip */
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Write record to new page */
|
|
|
|
|
if (EE_FlashProgramHalfWord(addr, vaddr) != HAL_OK)
|
2025-12-08 13:48:52 +01:00
|
|
|
return EE_STATUS_ERROR;
|
2025-12-07 21:39:26 +01:00
|
|
|
if (EE_FlashProgramHalfWord(addr + 2U, value) != HAL_OK)
|
2025-12-08 13:48:52 +01:00
|
|
|
return EE_STATUS_ERROR;
|
2025-12-07 21:39:26 +01:00
|
|
|
|
|
|
|
|
addr += sizeof(EE_Record_t);
|
|
|
|
|
if (addr >= (newBase + EE_PAGE_SIZE))
|
2025-12-08 13:48:52 +01:00
|
|
|
return EE_STATUS_NO_SPACE;
|
2025-12-07 21:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Erase old page */
|
|
|
|
|
if (EE_FlashErasePage(oldBase) != HAL_OK)
|
2025-12-08 13:48:52 +01:00
|
|
|
return EE_STATUS_ERROR;
|
2025-12-07 21:39:26 +01:00
|
|
|
|
|
|
|
|
/* Mark new page as VALID */
|
|
|
|
|
if (EE_FlashProgramHalfWord(newBase, EE_PAGE_STATUS_VALID) != HAL_OK)
|
2025-12-08 13:48:52 +01:00
|
|
|
return EE_STATUS_ERROR;
|
2025-12-07 21:39:26 +01:00
|
|
|
|
|
|
|
|
/* Update active page */
|
|
|
|
|
EE_ActivePageBase = newBase;
|
|
|
|
|
|
2025-12-08 13:48:52 +01:00
|
|
|
return EE_STATUS_OK;
|
2025-12-07 21:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* --- Public API ----------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Initialize the EEPROM emulation.
|
|
|
|
|
* - Checks page statuses and chooses the active page.
|
|
|
|
|
* - If inconsistent or blank, formats pages.
|
2025-12-08 13:48:52 +01:00
|
|
|
* PUBLIC RETURN TYPE: uint16_t (EE_OK / EE_ERROR / ...)
|
2025-12-07 21:39:26 +01:00
|
|
|
*/
|
2025-12-08 13:48:52 +01:00
|
|
|
uint16_t EE_Init(void)
|
2025-12-07 21:39:26 +01:00
|
|
|
{
|
|
|
|
|
uint16_t status0 = EE_GetPageStatus(EE_PAGE0_BASE);
|
|
|
|
|
uint16_t status1 = EE_GetPageStatus(EE_PAGE1_BASE);
|
|
|
|
|
|
|
|
|
|
if ((status0 == EE_PAGE_STATUS_ERASED) && (status1 == EE_PAGE_STATUS_ERASED))
|
|
|
|
|
{
|
|
|
|
|
/* Fresh device -> format */
|
2025-12-08 13:48:52 +01:00
|
|
|
return (uint16_t)EE_Format();
|
2025-12-07 21:39:26 +01:00
|
|
|
}
|
|
|
|
|
else if ((status0 == EE_PAGE_STATUS_VALID) && (status1 == EE_PAGE_STATUS_ERASED))
|
|
|
|
|
{
|
|
|
|
|
EE_ActivePageBase = EE_PAGE0_BASE;
|
|
|
|
|
return EE_OK;
|
|
|
|
|
}
|
|
|
|
|
else if ((status1 == EE_PAGE_STATUS_VALID) && (status0 == EE_PAGE_STATUS_ERASED))
|
|
|
|
|
{
|
|
|
|
|
EE_ActivePageBase = EE_PAGE1_BASE;
|
|
|
|
|
return EE_OK;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Any weird or inconsistent state -> reformat */
|
2025-12-08 13:48:52 +01:00
|
|
|
return (uint16_t)EE_Format();
|
2025-12-07 21:39:26 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Read a 16-bit variable by its virtual address.
|
2025-12-08 13:48:52 +01:00
|
|
|
* PUBLIC RETURN: EE_OK / EE_NOT_FOUND / EE_ERROR (as uint16_t)
|
2025-12-07 21:39:26 +01:00
|
|
|
*/
|
2025-12-08 13:48:52 +01:00
|
|
|
uint16_t EE_ReadVariable(uint16_t VirtAddress, uint16_t *Data)
|
2025-12-07 21:39:26 +01:00
|
|
|
{
|
2025-12-08 13:48:52 +01:00
|
|
|
EE_Status st;
|
|
|
|
|
|
2025-12-07 21:39:26 +01:00
|
|
|
if (Data == NULL)
|
|
|
|
|
return EE_ERROR;
|
|
|
|
|
|
2025-12-08 13:48:52 +01:00
|
|
|
st = EE_FindInPage(EE_ActivePageBase, VirtAddress, Data);
|
|
|
|
|
return (uint16_t)st;
|
2025-12-07 21:39:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Write (append) a 16-bit variable.
|
|
|
|
|
* - Writes a new record in the active page.
|
|
|
|
|
* - If the page is full, triggers a page transfer (GC).
|
2025-12-08 13:48:52 +01:00
|
|
|
* PUBLIC RETURN: EE_OK / EE_ERROR / EE_NO_SPACE (as uint16_t)
|
2025-12-07 21:39:26 +01:00
|
|
|
*/
|
2025-12-08 13:48:52 +01:00
|
|
|
uint16_t EE_WriteVariable(uint16_t VirtAddress, uint16_t Data)
|
2025-12-07 21:39:26 +01:00
|
|
|
{
|
|
|
|
|
uint32_t freeAddr;
|
|
|
|
|
EE_Status st;
|
|
|
|
|
|
|
|
|
|
/* Find free space in active page */
|
|
|
|
|
freeAddr = EE_FindFreeAddress(EE_ActivePageBase);
|
|
|
|
|
if (freeAddr != 0U)
|
|
|
|
|
{
|
|
|
|
|
/* Write new record */
|
|
|
|
|
if (EE_FlashProgramHalfWord(freeAddr, VirtAddress) != HAL_OK)
|
|
|
|
|
return EE_ERROR;
|
|
|
|
|
if (EE_FlashProgramHalfWord(freeAddr + 2U, Data) != HAL_OK)
|
|
|
|
|
return EE_ERROR;
|
|
|
|
|
|
|
|
|
|
return EE_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* No space -> page transfer */
|
|
|
|
|
st = EE_PageTransfer(VirtAddress, Data);
|
2025-12-08 13:48:52 +01:00
|
|
|
return (uint16_t)st;
|
2025-12-07 21:39:26 +01:00
|
|
|
}
|