-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
Describe the bug
The standard EEPROM.h library fails to perform EEPROM emulation on the STM32G474RBT6 microcontroller. The behavior has two distinct symptoms:
- On a fully erased chip: The very first EEPROM.put() or EEPROM.write() call only succeeds in writing the first 4 bytes of data. All subsequent bytes in the data structure remain 0xFF.
- On subsequent resets: All EEPROM.put() calls fail silently. The data in flash is never updated, making it impossible to change a value after the initial partial write.
The root cause appears to be that the EEPROM.h library is passing incorrect parameters to the low-level HAL_FLASHEx_Erase() function for this specific MCU variant (when configured in single-bank mode). A direct call to the HAL function with the correct parameters works perfectly, proving the hardware and HAL drivers are functional.
To Reproduce
Complete source code which can be used to reproduce the issue:
This standard "boot counter" sketch demonstrates the failure. It uses the EEPROM.h library as intended.
#include <Arduino.h>
#include <EEPROM.h>
// A simple struct to test with
struct MySettings {
uint32_t bootCount;
uint32_t someOtherValue;
};
void setup() {
Serial.begin(115200);
while (!Serial);
delay(2000);
Serial.println("\n--- EEPROM.h Library Test on STM32G474RBT6 ---");
MySettings settings;
// Read the struct from emulated EEPROM
EEPROM.get(0, settings);
Serial.print("Data read from flash: bootCount = ");
// Check if the value is 0xFFFFFFFF (erased state)
if (settings.bootCount == 0xFFFFFFFF) {
Serial.println("[First Run / Erased]");
Serial.println("Initializing new settings...");
settings.bootCount = 1; // First boot
} else {
Serial.println(settings.bootCount);
Serial.println("Settings loaded. Incrementing for next boot...");
settings.bootCount++; // Increment the boot count
}
// Attempt to write the updated struct back. This is the operation that fails.
EEPROM.put(0, settings);
Serial.println("Write operation called.");
Serial.print("New value for bootCount should be: ");
Serial.println(settings.bootCount);
Serial.println("---------------------------------------------");
Serial.println("Press RESET to test the next cycle.");
}
void loop() {
// Do nothing.
}
Steps to reproduce the behavior:
- Use an STM32G474RBT6 board (custom or otherwise). Ensure it is configured for Single-Bank Mode via the DBANK option byte in STM32CubeProgrammer.
- Upload the sketch above.
- Open the Serial Monitor at 115200 baud.
- First Run: Observe the output. It will correctly identify the first run and report the new boot count as 1.
- Press the RESET button on the board.
- Second Run (See error): Observe the output again. Instead of reading 1 and incrementing to 2, it will read the old value from the first run (1) or a garbage value, because the EEPROM.put() call failed to erase the flash page. The boot counter will never increment past the first write.
Expected behavior
On every reset after the first run, the sketch should successfully read the previously saved value, increment it, and write the new value back to flash. The bootCount printed in the Serial Monitor should increase by one on every reset (1, 2, 3, ...).
Screenshots
If applicable, add screenshots to help explain your problem.
Desktop (please complete the following information):
- OS: Windows 11 Enterprise, version 24H2
- Arduino IDE version: 2.3.6
- STM32 core version: 2.11.0
- Tools menu settings if not the default:
- Board part number: "STM32G474RB"
- U(S)ART support: "Enabled"
- Upload method: SWD
Board (please complete the following information):
- Name: custom board with STM32G474RBT6 microcontroller
- Hardware Revision: n/a
- Extra hardware used if any: none
Additional context
This was a very difficult bug to isolate, and the following debugging steps have been performed to prove the issue is within the EEPROM.h library's implementation for this specific target.
- Hardware Ruled Out: The issue was reproduced on two separate, identical STM32G474RBT6 chips on the same custom board.
- PCB Ruled Out: A binary compiled for the RBT6 was accidentally flashed to an STM32G474CEU6, and the EEPROM emulation worked flawlessly. This indicates the compiled code is capable of performing a successful erase and that the custom PCB's power delivery is sufficient.
- Clock Instability Ruled Out: The issue persists even when the system clock is manually and correctly configured to a stable 168 MHz using the internal HSI, with appropriate voltage scaling and flash latency settings.
- The "Smoking Gun" - Direct HAL Call Works: The issue was definitively isolated by bypassing the EEPROM.h library and calling the ST HAL driver directly. The following code successfully erases the flash page every time, proving the hardware and HAL drivers are working correctly.
A Special Note on the Single-Bank Mode Configuration:
It is critically important to note that this bug was reproduced and isolated while the STM32G474RBT6 was configured in Single-Bank Mode.
By default, this microcontroller often ships in Dual-Bank Mode (the DBANK option byte is checked). In that mode, the 128 KB flash is split into two 64 KB banks with a large memory gap in between (0x08010000 to 0x0803FFFF), which causes its own set of failures with many libraries.
To create a clean and simple test environment, the DBANK option byte was unchecked using STM32CubeProgrammer. This correctly reconfigured the flash into a single, contiguous 128 KB bank with 32 pages of 4 KB each, running from 0x08000000 to 0x0801FFFF.
The fact that the EEPROM.h library still fails in this simplified, ideal memory layout is the strongest evidence that the bug is not related to the memory map itself, but rather to a hardcoded or incorrectly determined parameter within the library's device-specific logic for the STM32G474RBT6. The developers should ensure they are testing the fix in this single-bank configuration.
Working Code (Direct HAL Call):
#include <Arduino.h>
// --- START: CUSTOM EEPROM IMPLEMENTATION ---
// Define the location for our data.
// We'll use the last page (Page 31) of the 128KB flash.
#define EEPROM_PAGE_NUMBER 31
#define EEPROM_PAGE_SIZE 4096 // 4KB pages in single-bank mode
#define EEPROM_START_ADDRESS 0x0801F000
// Define the data structure you want to save
struct MySettings {
uint32_t bootCount;
float calibrationValue;
char deviceName[16];
// Add any other data you need here
};
// A simple error handler
void EEPROM_Error_Handler(void) {
pinMode(LED_BUILTIN, OUTPUT);
while (1) {
digitalWrite(LED_BUILTIN, HIGH); delay(100);
digitalWrite(LED_BUILTIN, LOW); delay(100);
}
}
// Function to read our settings from flash
void EEPROM_read(MySettings* settings) {
memcpy(settings, (const void*)EEPROM_START_ADDRESS, sizeof(MySettings));
}
// Function to write our settings to flash
void EEPROM_write(MySettings* settings) {
// The STM32G4 requires writing in 64-bit (8-byte) chunks.
// The easiest way to handle this is to create a RAM buffer of the entire page.
static uint8_t page_buffer[EEPROM_PAGE_SIZE];
// 1. Read the entire page from flash into our RAM buffer
memcpy(page_buffer, (const void*)EEPROM_START_ADDRESS, EEPROM_PAGE_SIZE);
// 2. Modify the start of the RAM buffer with our new settings data
memcpy(page_buffer, settings, sizeof(MySettings));
// 3. Erase the physical flash page
HAL_StatusTypeDef status;
FLASH_EraseInitTypeDef EraseInitStruct;
uint32_t page_error = 0;
HAL_FLASH_Unlock();
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
EraseInitStruct.Banks = FLASH_BANK_1;
EraseInitStruct.Page = EEPROM_PAGE_NUMBER;
EraseInitStruct.NbPages = 1;
if (HAL_FLASHEx_Erase(&EraseInitStruct, &page_error) != HAL_OK) {
HAL_FLASH_Lock();
EEPROM_Error_Handler(); // Erase failed
}
// 4. Write the entire modified RAM buffer back to the flash page
// We must write in 64-bit (8-byte) chunks.
for (uint32_t i = 0; i < EEPROM_PAGE_SIZE; i += 8) {
uint64_t data_chunk = *(uint64_t*)(page_buffer + i);
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, EEPROM_START_ADDRESS + i, data_chunk) != HAL_OK) {
HAL_FLASH_Lock();
EEPROM_Error_Handler(); // Write failed
}
}
HAL_FLASH_Lock();
}
// --- END: CUSTOM EEPROM IMPLEMENTATION ---
void setup() {
Serial.begin(115200);
while (!Serial);
delay(2000);
Serial.println("\n--- Custom EEPROM Test ---");
MySettings settings;
EEPROM_read(&settings); // Read using our custom function
Serial.print("Data read from flash: bootCount = ");
if (settings.bootCount == 0xFFFFFFFF) {
Serial.println("[First Run]");
settings.bootCount = 1;
settings.calibrationValue = 3.14f;
strcpy(settings.deviceName, "MyG4Device");
} else {
Serial.println(settings.bootCount);
settings.bootCount++;
}
Serial.print("New bootCount is: ");
Serial.println(settings.bootCount);
Serial.println("Writing back to flash...");
EEPROM_write(&settings); // Write using our custom function
Serial.println("Write complete. Please RESET to test again.");
}
void loop() {
delay(5000);
}
This strongly suggests the EEPROM.h library is constructing the FLASH_EraseInitTypeDef struct with incorrect parameters (most likely the .Banks field) when targeting the STM32G474RBT6.
Note: troubleshooted with help of Gemini 2.5 Pro