on 2026-03-25 5:28 AM
This article explains how to use the STM32 EEPROM emulation driver on STM32C5 devices by leveraging a ready-to-build Visual Studio Code, CMake, and GCC example project. It introduces the STM32C5 EDATA FLASH sector and the main concepts behind EEPROM emulation. The article describes the structure and configuration of the provided example, EDATA mapping, EEPROM emulation configuration, glue layer, and application code. Finally, the article demonstrates by example how the scenario writes and reads back 1000 virtual variables with cleanup handling, and details how to build and run this example on STM32C5. This example can be used as a reference for implementing robust nonvolatile storage.
STM32C5 devices do not include a dedicated hardware EEPROM block. Instead, nonvolatile data such as configuration parameters, calibration values, and usage counters must be stored in on-chip flash memory. While this approach is common across STM32 families, it requires careful handling of flash constraints such as page-based erase operations, limited endurance, and strict alignment and access granularity rules.
To support this use case, the STM32C5 family introduces the EDATA FLASH sector: a dedicated 64 Kbyte region organized in two banks, each with 16 pages of 2 Kbytes. This smaller page size and specific layout are designed to improve endurance and flexibility when implementing EEPROM emulation algorithms, especially those using wear leveling techniques, and fine write granularity.
STMicroelectronics provides an EEPROM emulation driver that uses EDATA and exposes a simple API based on virtual addresses. The driver internally manages page usage, wear leveling, and data integrity, so applications can perform EEPROM-like read and write operations instead of raw FLASH accesses.
The objectives of this document are to:
The sections below will help you understand the structure of the example. You will learn how to configure and initialize EEPROM emulation. The application manages 1,000 virtual variables by writing and reading them back. You will also see how the driver functions in an STM32C5 project.
Hardware
Software
This section summarizes the key concepts used by the STM32C5 EEPROM emulation driver to help interpret the behavior of the Visual Studio Code example.
Virtual variables and logical addressing
The EEPROM emulation driver exposes virtual variables identified by virtual addresses, for example, from 1 to EE_NB_OF_VARIABLES:
This indirection allows the driver to move data between pages for wear leveling and cleanup, without the application being aware of the underlying Flash layout.
Wear leveling and page usage
Each EDATA page supports a limited number of erase cycles. To extend effective endurance:
Parameters such as:
#define EE_CYCLES_NUMBER (1U)
#define EE_GUARD_PAGES_NUMBER (1U)
#define EE_NB_OF_VARIABLES (1000U)
In eeprom_emul_conf.h control how many pages are used and how many 8-bit variables the emulation can handle. For the provided example, 1000 variables (approximately 7 Kbytes) are used, leading to an effective write endurance significantly higher than the raw page erase limit.
Cleanup and maintenance operations
During normal operation:
When the driver detects that a page is reaching capacity or that memory needs to be compacted, write operations may return:
In this case, the application must call:
to allow the driver to:
The Visual Studio Code example demonstrates this pattern by checking for EE_INFO_CLEANUP_REQUIRED after each write and calling EE_CleanUp() when needed.
Data integrity and CRC
To ensure the integrity of emulated EEPROM data:
If integrity checks fail, the driver may return error codes, and the application can react accordingly, for example, signaling an error and avoiding the use of invalid data.
Status and error codes
The main status codes relevant to the example are:
The example code checks these return values and:
With these concepts in mind, the next sections describe how the STM32C5 EDATA sector is configured. Additionally, how Visual Studio Code example maps and configures the EEPROM emulation driver on top of it.
Before running the EEPROM emulation example, the STM32C5 EDATA sector must be understood and configured correctly.
EDATA sector overview
On STM32C5, the EDATA region is a dedicated 64 Kbyte FLASH sector intended for EEPROM emulation:
This organization provides:
Data granularity: 16-bit vs 128-bit mode
The EDATA region supports two write granularities, configured through an option byte:
The mode impacts how data is written and read: the access width must match the configured mode, and it also determines how ECC is managed for erased and programmed locations.
If the EDATA region is configured in 128-bit mode and is accessed as 16-bit or 32-bit, this can generate ECC errors and trigger a non-maskable interrupt (NMI).
In 16-bit mode, erased 16-bit words cannot have ECC bits all set to 1. Such a pattern also leads to ECC errors and NMI on read access to erased data or OTP.
The EEPROM emulation driver and configuration must be aligned with the chosen mode. The Visual Studio Code example is configured accordingly in its FLASH driver and should be used with a matching EDATA configuration.
Configuring EDATA via STM32CubeProgrammer
The EDATA region is enabled and configured outside the Visual Studio Code project, using STM32CubeProgrammer:
With EDATA correctly enabled and configured, proceed to the next section to observe the structure of the Visual Studio Code and CMake example and how it maps the EEPROM emulation driver onto this dedicated FLASH region.
The example is provided as a ready-to-build Visual Studio Code, CMake, and GCC project. The layout is:
The file eeprom_emul_conf.h is the central configuration point for the emulation. It controls which algorithm is used, where in EDATA the emulation resides, and how many variables and endurance you target.
Algorithm and FLASH interface selection
For the STM32C5 EDATA example, the FLITF + EDATA algorithm is selected:
#define EE_ALGO_FLITF (1)
This makes eeprom_emul_core.c call the FLITF algorithm (EE_FLITF_Init, EE_FLITF_ReadVariableXbits, EE_FLITF_WriteVariableXbits) and use the FLITF + EDATA FLASH interface (eeprom_itfflash_flitf_edata.c).
Memory mapping to EDATA
The configuration binds the algorithm to the STM32C5 EDATA region:
#define EE_FRAME_LINE_SIZE (8)
#define EE_START_PAGE_ADDRESS (FLASH_EDATA_BASE + FLASH_EDATA_BANK_SIZE)
#define EE_FLASH_BASE_ADDRESS (FLASH_EDATA_BASE)
#define EE_FLASH_PAGE_SIZE (FLASH_EDATA_PAGE_SIZE)
#define EE_FLASH_BANK_SIZE (FLASH_EDATA_BANK_SIZE * 2)
With this setup:
The FLITF algorithm then computes:
The application never manipulates these addresses; it works purely with virtual variable IDs. The mapping to physical EDATA pages and frames is fully handled by the driver.
Endurance, guard pages, and number of variables
Endurance and capacity are controlled by:
#define EE_CYCLES_NUMBER (1U)
#define EE_GUARD_PAGES_NUMBER (1U)
#define EE_NB_OF_VARIABLES (1000U)
For this example:
This configuration can be tuned to your own needs.
The core driver needs two hardware objects:
These are grouped in ee_object_t (from eeprom_emul_core.h)
typedef struct
{
void *f_object; /* pointer to flash handle */
void *crc_object; /* pointer to crc handle */
} ee_object_t;
The glue layer mx_eeprom_emul.c fills this object using target‑specific accessors and then calls EE_Init:
static ee_object_t mx_ee_object_example;
system_status_t mx_eeprom_emulation_init(void)
{
system_status_t return_status = SYSTEM_OK;
/* get interface handle */
mx_ee_object_example.f_object = MX_EEPROM_FLASH_HANDLE();
mx_ee_object_example.crc_object = MX_EEPROM_CRC_HANDLE();
/* Init of eeprom emulation */
if (EE_Init(&mx_ee_object_example, EE_CONDITIONAL_ERASE) != EE_OK)
{
return_status = SYSTEM_POSTSYSTEM_ERROR;
}
return return_status;
}
Key points:
From the application point of view, you only need to call:
The application layer shows how to use the EEPROM emulation API in a real sequence. It is structured into four steps, exposed via three functions:
and controlled by main.c.
Overall flow
Data structures and virtual addresses
Two key objects are defined:
uint32_t dataCounter = 0;
uint8_t dataWriteValue[EE_NB_OF_VARIABLES + 1] = {0};
Virtual addresses range from 1 to EE_NB_OF_VARIABLES and are passed to EE_WriteVariable8bits() and EE_ReadVariable8bits(). The driver maps these IDs to frames inside EDATA; the application never uses raw addresses.
Write process (Step 2)
PRINTF("[INFO] Step 2: START THE WRITE PROCESS\n");
for (uint16_t var_id = 1; var_id <= EE_NB_OF_VARIABLES; var_id++)
{
dataCounter++;
dataWriteValue[var_id] = (uint8_t)var_id;
ee_status ret_state = EE_WriteVariable8bits(var_id, dataWriteValue[var_id]);
switch (ret_state)
{
case EE_INFO_CLEANUP_REQUIRED:
{
PRINTF("[INFO] Step 2: CLEAN UP REQUIRED\n");
EE_CleanUp();
break;
}
case EE_OK :
{
break;
}
default:
{
PRINTF("[ERROR] Step 2: ERROR RETURNED BY EE_WRITE FUNCTION\n");
goto error;
}
}
}
Read and verification process (Step 3)
PRINTF("[INFO] Step 3: START READ PROCESS\n");
for (uint16_t var_id = 1; var_id <= EE_NB_OF_VARIABLES; var_id++)
{
uint8_t data_read = 0;
/* Read the variable value from the virtual address var_id */
if (EE_ReadVariable8bits(var_id, &data_read) != EE_OK)
{
PRINTF("[ERROR] Step 3: ERROR RETURNED BY EE_READ FUNCTION\n");
goto error;
}
if (data_read != dataWriteValue[var_id])
{
PRINTF("[ERROR] Step 3: ERROR, READ and WRITTEN VARIABLES ARE DIFFERENT\n");
goto error;
}
}
return_status = EXEC_STATUS_OK;
PRINTF("[INFO] Step 3: READ and WRITTEN VARIABLES ARE EQUAL\n");
De‑initialization (Step 4)
app_status_t app_deinit(void)
{
PRINTF("[INFO] Step 4: DE-INIT\n");
/** No action required
*/
return EXEC_STATUS_OK;
} /* end app_deinit */
In a real application, you could add any extra cleanup here, but the EEPROM emulation driver itself does not require a special deinit phase.
main.c ties everything together and provides a simple visual status indication on the NUCLEO‑C562RE user LED.
System initialization
if (mx_system_init() != SYSTEM_OK)
{
ExecStatus = EXEC_STATUS_ERROR; /* Record the error */
}
else
{
#if defined(USE_TRACE) && USE_TRACE != 0
/* Initialize basic_stdio separately, after system initialization. */
UTIL_BASIC_STDIO_Init(mx_basic_stdio_gethandle());
#endif /* defined(USE_TRACE) && USE_TRACE != 0 */
ExecStatus = app_init();
/* ... loop ... */
} /* end main */
mx_system_init() (generated/board‑specific code) is responsible for:
Example loop
#define EXAMPLE_LOOP_COUNT 20U
#define EXAMPLE_LOOP_DELAY_MS 2U
app_status_t ExecStatus = EXEC_STATUS_UNKNOWN; /* Application status */
uint32_t ProcessLoops = EXAMPLE_LOOP_COUNT; /* Execution loop control, stops if 0 */
while ((ExecStatus != EXEC_STATUS_ERROR) && (ProcessLoops != 0U))
{
ExecStatus = app_process();
/* Control and report */
ProcessLoops--;
HAL_Delay(EXAMPLE_LOOP_DELAY_MS);
} /* end while */
if (ExecStatus == EXEC_STATUS_OK)
{
ExecStatus = app_deinit();
}
Status reporting and error handling
At the end of main():
if (ExecStatus == EXEC_STATUS_OK)
{
success_handler();
}
else
{
error_handler();
}
The application also redefines HardFault_Handler():
void HardFault_Handler(void)
{
/* The example encountered an unrecoverable error */
ExecStatus = EXEC_STATUS_ERROR;
/* Attempt to turn the status LED off (this might fail) */
HAL_GPIO_WritePin(PA5_PORT, PA5_PIN, HAL_GPIO_PIN_RESET);
/* Unrecoverable error: infinite loop */
while (1);
}
This allows:
This overview describes how the provided example works out of the box: how the driver is organized, how the configuration binds it to STM32C5 EDATA, and how the application exercises the API and reports success or failure.
Importing the EEPROM Emulation in VS Code
The build output appears in the terminal panel, showing compilation progress and any errors or warnings
Using the existing STM32C5_EEPROM_Emulation configuration
The project provides a launch.json file with a debug configuration named STM32C5_EEPROM_Emulation, based on the STM32Cube ST-LINK GDB server. You do not need to create or modify it; just select and use it.
This configuration is already set up to:
Stepping through the EEPROM Emulation example
To understand and validate the EEPROM emulation flow, set breakpoints in the key functions:
Run the example, single‑step or continue, and observe:
Validating behavior (logs and LED)
With the debugger running:
In the serial/ITM output, you should see:
Any [ERROR] message indicates where the scenario failed (init, write, read, or comparison).
At the end of main():
Typical issues and what to check
If the example does not behave as expected:
Using the existing STM32C5_EEPROM_Emulation debug configuration helps in understanding the EEPROM emulation example. It works with breakpoints, logs, and LED indications. This setup provides a complete view of the behavior on the STM32C5. It also helps validate that the driver and EDATA setup are correct.
The STM32C5 EEPROM emulation Visual Studio Code example provides a complete, ready-to-run implementation of flash-based EEPROM emulation on the dedicated EDATA sector, without requiring STM32CubeMX2 project generation. It demonstrates how the core driver, flash memory, CRC, ECC interfaces, configuration, glue layer, and application code work together to offer an EEPROM-like application programming interface based on virtual variable IDs.
By enabling EDATA in the option bytes, configuring eeprom_emul_conf.h for STM32C5, and running the scenario on NUCLEO-C562RE, you can observe a full write, cleanup, read, and verify cycle over 1000 virtual variables. The driver transparently manages endurance and data integrity. This example serves as a reference implementation of the STM32 EEPROM emulation stack. Additionally, it is a practical starting point for implementing robust nonvolatile storage in STM32C5 projects using Visual Studio Code, CMake, and GCC.