Flash memory is the non-volatile memory where your program code and persistent data are stored.ย It is a critical component of any microcontroller, including the STM32 family. STM32 microcontrollers, embed their own flash memory, tailored to meet deterministic execution, code retention, and in-application programming (IAP) needs.
Understanding how to properly program, erase, and manage flash memory is essential for developing robust STM32 applications, especially when you need to:
- Storing configuration data that must survive power cycles.
- Performing firmware updates in the field.
- Implementing bootloaders for application upgrades.
- Managing large datasets that do not fit in RAM.
In this blog post I will introduce the fundamentals of STM32 Flash memory programming โ from key concepts to hands-on implementation โ and is designed for beginners starting their journey with STM32 microcontrollers.
๐กย Note: I am taking STM32H573xx MCU for the reference of this blog post.
Flash Memory Architecture in STM32
Before diving into Flash programming, itโs essential to understand the underlying memory architecture. STM32 devices organize Flash memory into sectors or pages, depending on the family, with specific alignment, access size, and erase requirements. This structure directly impacts how code is stored, how updates are applied, and how memory operations are managed during runtime.
A solid grasp of this architecture ensures safe, efficient, and reliable use of Flash โ whether you’re updating firmware, storing configuration data, or implementing a bootloader.
Key Characteristics of STM32H573xx Flash Memory:
- Up to 2 MB Flash, split into two 1 MB banks
- Flexible read sizes: 128-bit down to 8-bit
- Flash Programming: 128-bit (main/user), 16-bit (OTP/high-cycle areas)
- Erase options (Page/Sector granularity): 8 KB sectors, full bank, or dual-bank mass erase
- Dual-bank support:
- Read-while-write (not write-while-write)
- Bank swapping with preserved security flags
- ECC protection: 1-bit correction, 2-bit detection per 128-bit word
- Configurable option bytes for Flash security and protection
- Flash security features:
- Debug access restriction
- Write protection per sector group
- Secure-only areas per bank
- HDP (Hide Protection) for secure startup code
- 2 KB OTP area + read-only area defined by ST
- Instruction prefetch for faster execution.
๐ Flash Organization:
The Flash is organized into 512 sector of 8 KB each (256 sector per bank). This means:
- Each sector is the minimum erasable unit.
- You cannot erase part of a sector.
- Programming is done in 128-bit aligned blocks โ anything else will fail.
Flash memory in STM32H573xx microcontrollers is typically organized as follows:
| Component | Description |
|---|---|
| Banks | Flash is divided into 1 or 2 banks (e.g., 1 MB each in dual-bank devices). |
| Sectors / Pages | Each bank is split into sectors or pages (granularity varies by series). |
| Erase Units | Smallest erasable unit โ typically a sector (e.g., 8 KB on STM32H5). |
| Program Units | Smallest programmable unit โ typically 128 bits (16 bytes) in user Flash. |
| Option Bytes | Dedicated memory for boot config, protection settings, and other flags. |
| OTP Area | One-Time Programmable region for storing permanent data like serial numbers. |
| Read-Only Area | Reserved region set by STMicroelectronics, not user-writable. |
Note: The granularity of memory access is the size of the data chunk (in bytes) that a processor or system accesses at once. Example, if a CPU has a 4-byte granularity, it reads or writes 4 bytes in one operationโeven if the program only needs 1 byte.
๐ง Basic Flash Operations:
Interacting with Flash memory in STM32 involves three key operations: reading, writing (programming), and erasing. While reading is simple and behaves like accessing normal memory, writing and erasing require specific sequences to ensure data integrity and prevent corruption.
โ๏ธ Writing to Flash Memory โ The Sequence
Unlike reading, writing to Flash memory requires a specific sequence to ensure data integrity and prevent corruption. The general steps are:
- Unlock the Flash control registers.
- Erase the target sector (Flash must be erased before programming).
- Program the data (in fixed-size units, depending on device).
- Lock the Flash to protect against unintended writes.
- Address must be aligned to 16 bytes,
- Data must be 16 bytes in size and aligned.
๐ STM32H5 flash requires 128-bit aligned writes because its internal program granularity is 16 bytes โ thatโs the smallest unit the flash hardware can safely and efficiently program.
๐น HAL-Based Flash Write Example (128-bit aligned)
Below function is a safe and optimized routine for programming the internal Flash of the STM32H5 series, which requires 128-bit (16-byte) aligned writes.
Before using this function:
- The target Flash region must be erased.
- The power supply must be stable, and
- Cache invalidation may be needed for fresh reads.
Following things happening in this function:
- Unlocks the Flash controller,
- Writes the buffer to Flash 16 bytes at a time using HAL_FLASH_Program(),
- Performs read-back verification after each write, and
- Locks the Flash when finished.
#include "stm32h5xx_hal.h"
#include <stdint.h>
#include <string.h>
#define FLASH_PROGRAM_UNIT 16 // 128 bits
#define IS_16BYTE_ALIGNED(x) (((x) & 0xF) == 0)
/**
* @brief Programs STM32H5 internal Flash using 128-bit (quad-word) aligned writes.
* @note The target Flash region must be erased before calling this.
* @param address: Starting Flash address (must be 16-byte aligned)
* @param buffer: Pointer to aligned data buffer
* @param length: Total bytes to write (must be multiple of 16)
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef flashProgram(uint32_t address, const uint8_t *buffer, uint32_t length)
{
HAL_StatusTypeDef status = HAL_OK;
// Validate alignment and size upfront
if (!IS_16BYTE_ALIGNED(address) ||
!IS_16BYTE_ALIGNED((uintptr_t)buffer) ||
(length == 0) ||
(length % FLASH_PROGRAM_UNIT) != 0)
{
return HAL_ERROR;
}
// Calculate programming loop bounds
const uint32_t startUnit = address;
const uint32_t endUnit = address + length;
// Unlock Flash memory
status = HAL_FLASH_Unlock();
if (status != HAL_OK)
return status;
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
// Program loop: 16 bytes at a time
for (uint32_t addr = startUnit; addr < endUnit; addr += FLASH_PROGRAM_UNIT)
{
const uint32_t *data = (const uint32_t *)(buffer + (addr - startUnit));
status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_QUADWORD, addr, (uint32_t *)data);
if (status != HAL_OK)
break;
// Optional: read-back verification
if (memcmp((void *)addr, data, FLASH_PROGRAM_UNIT) != 0)
{
status = HAL_ERROR;
break;
}
}
HAL_FLASH_Lock();
return status;
}
๐ Reading Flash Memory:
Reading from Flash memory is straightforward โ it’s similar to reading from any memory-mapped location. Flash is addressable like normal memory, so standard pointer dereferencing can be used:
// Read a 32-bit value from a specific Flash address uint32_t readValue = *(volatile uint32_t*)(FLASH_ADDRESS);
No special procedure is needed for reads, and there is no performance penalty under normal conditions, unless access latency is introduced due to wait states or if prefetch is disabled.
Note:ย A memory access boundary is the starting address of a memory chunk that aligns with the systemโs access granularity โ the smallest unit of memory the system can read or write efficiently. Example, with 4-byte granularity, valid boundaries are addresses like 0x00, 0x04, 0x08, etc.
๐งน Erasing Flash:
Before writing new data, Flash memory must be erased. You can erase:
- A single sector/page.
- A full bank.
- Or both banks (mass erase).
Typical erase procedure (using HAL):
FLASH_EraseInitTypeDef erase;
uint32_t pageError = 0;
// or FLASH_TYPEERASE_PAGES (on some families)
erase.TypeErase = FLASH_TYPEERASE_SECTORS;
// Make sure this matches your target address
erase.Banks = FLASH_BANK_1;
// Use sector number (STM32F4) or page number (STM32L4/H5)
erase.Sector = sector_number;
// Number of sectors/pages to erase
erase.NbSectors = 1;
// For 2.7Vโ3.6V supply range (check your hardware)
erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;
HAL_FLASH_Unlock();
if (HAL_FLASHEx_Erase(&erase, &pageError) != HAL_OK)
{
// Handle error โ pageError contains the failing sector/page number
}
HAL_FLASH_Lock();
๐ Note: Before performing any write or erase operations, you must unlock the flash control register:
This function offers a flexible way to erase Flash memory on STM32H5 devices. By dynamically computing sector indices and bank associations, it ensures proper erasure across one or more banks, making it ideal for firmware update or data storage scenarios.
#include "stm32h5xx_hal.h"
#define FLASH_BASE_ADDRESS 0x08000000U
#define FLASH_SECTOR_SIZE 0x2000U // 8 KB
// Adjust based on your MCU (e.g., 2MB)
#define FLASH_TOTAL_SIZE (2 * 1024 * 1024U)
#define FLASH_TOTAL_SECTORS (FLASH_TOTAL_SIZE / FLASH_SECTOR_SIZE)
#define SECTORS_PER_BANK (FLASH_TOTAL_SECTORS / 2)
/**
* @brief Calculate sector number from address.
*/
static uint32_t GetSector(uint32_t address)
{
return (address - FLASH_BASE_ADDRESS) / FLASH_SECTOR_SIZE;
}
/**
* @brief Calculate bank from sector number.
*/
static uint32_t GetBank(uint32_t sector)
{
return (sector < SECTORS_PER_BANK) ? FLASH_BANK_1 : FLASH_BANK_2;
}
/**
* @brief Erase flash sectors across banks if necessary.
* @param start_address: Starting address to erase from
* @param num_bytes: Number of bytes to erase
* @retval HAL status
*/
HAL_StatusTypeDef Flash_Erase(uint32_t start_address, uint32_t num_bytes)
{
if ((start_address < FLASH_BASE_ADDRESS) ||
(start_address + num_bytes > FLASH_BASE_ADDRESS + FLASH_TOTAL_SIZE))
{
return HAL_ERROR; // Invalid address range
}
HAL_FLASH_Unlock();
uint32_t start_sector = GetSector(start_address);
uint32_t end_sector = GetSector(start_address + num_bytes - 1);
HAL_StatusTypeDef status = HAL_OK;
FLASH_EraseInitTypeDef erase_init;
uint32_t page_error;
while (start_sector <= end_sector)
{
uint32_t current_bank = GetBank(start_sector);
uint32_t bank_limit = (current_bank == FLASH_BANK_1)
? SECTORS_PER_BANK
: FLASH_TOTAL_SECTORS;
uint32_t last_in_bank = (end_sector < bank_limit - 1) ? end_sector : (bank_limit - 1);
uint32_t sectors_to_erase = last_in_bank - start_sector + 1;
erase_init.TypeErase = FLASH_TYPEERASE_PAGES;
erase_init.Page = start_sector;
erase_init.NbPages = sectors_to_erase;
erase_init.Banks = current_bank;
status = HAL_FLASHEx_Erase(&erase_init, &page_error);
if (status != HAL_OK)
{
break;
}
start_sector = last_in_bank + 1;
}
HAL_FLASH_Lock();
return status;
}
๐ Option Bytes & EEPROM-Like eData in STM32H5:
When working with configuration storage or persistent counters in embedded systems, developers often rely on EEPROM. STM32H5 offers the eData area โ a section of Flash that behaves like EEPROM when properly configured.
๐ง What is the eData Area?
STM32H5 devices allow you to reserve a part of Flash memory for non-volatile data storage โ essentially emulating EEPROM. This feature is especially useful for:
- Device configuration parameters
- Calibration data
- Usage counters or logs
- Any small, persistent values that need to survive resets or power loss
The memory behaves like EEPROM: you can write to it without erasing the full Flash page manually and read it easily using dedicated HAL APIs.
๐ง How Do You Enable It?
To use this feature, you first need to configure Option Bytes โ special, non-volatile settings in STM32 that determine things like protection modes, boot configuration, and now, eData support.
Hereโs how to enable the eData area:
FLASH_OBProgramInitTypeDef ob; ob.OptionType = OPTIONBYTE_EDATA; ob.EDATAConfig = OB_EDATA_ENABLE; ob.EDATAStartPage = 240; // Reserve last 4 pages of Flash ob.EDATASize = 4;
This configuration tells the microcontroller:
โEnable the eData (EEPROM-like) area, starting from Flash page 240, and reserve 4 pages for it.โ
Now apply it with the following code:
HAL_FLASH_OB_Unlock(); // Unlock access to Option Bytes HAL_FLASHEx_OBProgram(&ob); // Program the eData settings HAL_FLASH_OB_Launch(); // Apply changes (this triggers a reset)
After the reset, your eData area is ready to use.
๐ก Reading and Writing to eData:
Once configured and enabled, you can use STM32โs HAL APIs to treat this eData area like EEPROM:
// Write a 64-bit value to eData HAL_FLASHEx_DATAEEPROM_Write(address, data); // Read the value back uint64_t data = HAL_FLASHEx_DATAEEPROM_Read(address);
โ ๏ธ Make sure the address falls within the eData region you reserved earlier. Otherwise, access might fail or corrupt memory.
๐ Things to Keep in Mind
- Flash space is sacrificed: eData takes over a part of your Flash. If you reserve 4 pages, your code/data area is reduced accordingly.
- Changes require a reset: When you launch the Option Bytes configuration (HAL_FLASH_OB_Launch()), the system resets to apply the new settings.
- No need to erase manually: HAL handles write operations internally, without forcing you to do page erases like normal Flash operations.
โ Best Practices and Considerations for Flash Programming (STM32):
Working directly with Flash โ especially for configuration or EEPROM-like emulation โ demands caution and precision. Below are key practices every experienced firmware engineer should follow when dealing with Flash on STM32, especially when using eData.
1. ๐งฎ Memory Alignment:
Flash programming typically requires aligned access, depending on the Flash word size for the target STM32 series:
32-bit alignment: Common across STM32F0/F1/F3/L0 families
64-bit alignment: Required in STM32F7, H7, and H5 (for HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD))
Failing to align properly can cause HAL_FLASH_Program() to return errors or silently corrupt data.
โ Tip: Always align your write addresses to the word size required by the FLASH_TYPEPROGRAM_* mode you’re using.
2. ๐ Power Supply Stability
Flash writes and erases are sensitive to voltage drops. Unstable power โ especially in battery-powered systems โ can corrupt Flash content or trigger incomplete operations.
Example safeguard:
if (HAL_PWREx_GetVoltageRange() == FLASH_VOLTAGE_RANGE_3)
{
// Safe to program
}
else
{
// Voltage too low โ delay, retry, or abort
}
๐ง Additional Considerations:
- Ensure brown-out reset (BOR) is properly configured.
- Use a low-dropout regulator (LDO) if supply margin is narrow.
- Avoid programming during sleep modes unless properly configured.
3. ๐งท Wear Leveling:
Flash endurance is limited (typically 10,000โ100,000 cycles) per sector or page. To prevent premature wear in frequently written areas:
๐ Simple Static Wear Leveling:
#define NUM_STORAGE_SLOTS 10
#define SLOT_SIZE 1024 // Must match Flash page size
#define CONFIG_FLASH_ADDR 0x080E0000 // Example start address
uint32_t FindNextFreeSlot(void)
{
for (int i = 0; i < NUM_STORAGE_SLOTS; i++)
{
uint32_t addr = CONFIG_FLASH_ADDR + (i * SLOT_SIZE);
if (*(uint32_t*)addr == 0xFFFFFFFF)
{
return addr; // Unused slot
}
}
return CONFIG_FLASH_ADDR; // All full, trigger erase
}
For dynamic data (e.g., counters, logs), implement circular buffer logic or use a file system layer (like LittleFS) on top of Flash for true wear leveling.
4. โ Robust Error Handling
Never assume Flash operations will succeed. Always check return values from HAL APIs:
HAL_StatusTypeDef status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, address, data);
if (status != HAL_OK)
{
switch (status)
{
case HAL_ERROR:
// Likely voltage issue, alignment, or protection error
break;
case HAL_BUSY:
// Possibly still erasing; retry after delay
break;
case HAL_TIMEOUT:
// Could be clock config issue or stuck peripheral
break;
default:
// Unknown error โ add logging/assert
break;
}
}
๐ก Pro Tip: Use __disable_irq() and __enable_irq() around write/erase operations to prevent ISR interruption, especially in latency-sensitive systems.
โ ๏ธ Other Considerations Seasoned Engineers Know
- Flash Read-While-Write (RWW) is not always supported. If your MCU executes from the same bank you’re writing to, you may stall the CPU. Consider dual-bank mode if available.
- Always unlock and lock Flash appropriately using HAL_FLASH_Unlock() and HAL_FLASH_Lock().
- After any Flash write/erase, verify the written value using a read-back check โ donโt trust blindly.
- Monitor temperature, as Flash behavior can degrade or fail outside safe thermal limits (especially in automotive or industrial environments).
Additional Resources and Exercises:
Resources:
- STM32 Reference Manuals: Check the specific manual for your STM32 device family
- AN3969: EEPROM emulation techniques for STM32 microcontrollers
Exercises:
- Basic Flash Writing: Create a program that stores a counter value in flash that increments each time the device boots up.
- Configuration Manager: Implement a complete configuration system that allows storing and retrieving multiple parameters with different data types.
- EEPROM Emulation: Use flash memory to emulate EEPROM behavior with proper wear leveling.
- Firmware Update: Develop a simple bootloader that can receive a new application image over UART and program it into flash.
- Data Logging: Create a circular buffer in flash memory that can store sensor readings even when power is lost.