The internal flash memory integrated into STM32 microcontrollers provides non-volatile storage for both executable code and user data. While reading from this memory is as straightforward as dereferencing a pointer, modifying its contents requires strict adherence to hardware protection mechanisms. Write and erase cycles must be guarded by unlock and lock procedures to prevant accidental corruption of the running firmware.
Internal Flash Architecture
The flash space is typically segmented into three distinct regions:
- Main Memory: Serves as the primary storage for application binaries. If the allocated code footprint leaves unused space at the end of this region, developers can repurpose it for persistent parameter storage. The exact boundary of the loaded program can be determined by inspecting the linker map file generated by the IDE. The final loaded base address indicates the start of available flash space.
- System Memory: Contains factory-programmed data that is strictly read-only. This section hosts bootloaders and ISP routines required for serial firmware updates, functioning similarly to fixed memory-mapped regions in operating systems.
- Option Bytes: Houses configuration bits that control read protection levels, watchdog source selection, and power-mode reset behaviors. Modifying these settings alters the microcontroller's operational constraints and must be handled through dedicated flash control registers.
Operational Constraints and Design Considerations
Flash memory exhibits asymmetric read/write characteristics. The fundamental unit for erasure is a full page, whereas programming can occur at word (32-bit) or half-word (16-bit) granularity. This architecture introduces several engineering challenges:
- Page Erasure vs. Granular Writes: Since erasing affects an entire page, modifying a single value without a backup mechanism will overwrite adjacent data. Implementing a RAM-based page buffer allows modifications to be aggregated before a single erase-and-program cycle, minimizing data corruption risks.
- Alignment Requirements: Hardware enforces strict address alignment. Word writes must target addresses divisible by four, while half-word writes require even addresses. Misaligned access attempts will trigger hard faults or data corruption.
- Cross-Page Boundaries: Data structures spanning multiple flash pages are not natively supported for atomic operations. Logical boundaries must be manually managed by the application layer.
- Bit Transition Physics: Flash cells transition from
1to0during programming but require a full page erase to return to1. This physics-based limitation dictates why targeted deletion is impossible without page-level operations.
Implementation Strategy
Direct register manipulation is unnecessary when leveraging the Standard Peripheral Library. The following implementation abstracts address calculations and enforces alignment rules.
#include "stm32f10x_flash.h"
#include <stdint.h>
#define FLASH_PAGE_SIZE 2048U
#define FLASH_BASE_USER_AREA 0x08008000UL
static inline uint32_t get_absolute_address(uint32_t offset) {
return FLASH_BASE_USER_AREA + offset;
}
void persist_data(uint32_t offset, uint32_t payload) {
uint32_t target_addr = get_absolute_address(offset);
uint32_t page_start = FLASH_BASE_USER_AREA + ((offset / FLASH_PAGE_SIZE) * FLASH_PAGE_SIZE);
FLASH_Unlock();
FLASH_ErasePage(page_start);
FLASH_ProgramWord(target_addr, payload);
FLASH_Lock();
}
uint32_t retrieve_data(uint32_t offset) {
uint32_t target_addr = get_absolute_address(offset);
volatile const uint32_t *mem_ptr = (volatile const uint32_t *)target_addr;
return *mem_ptr;
}
Code Architecture Breakdown
- Address Mapping: The
get_absolute_addressutility translates a logical offset into a physical flash address. Using offsets improves portability and reduces hardcoding errors. - Page Boundary Calculation: Integer division
(offset / FLASH_PAGE_SIZE) * FLASH_PAGE_SIZEreliably computes the starting address of the relevant flash page without floating-point dependencies. - Protection Sequence:
FLASH_Unlock()disables write protection, allowing modifications.FLASH_ErasePage()resets the target page to all0xFF.FLASH_ProgramWord()then writes the 32-bit payload. The sequence concludes withFLASH_Lock()to re-enable protection. - Pointer Dereferencing: Reading utilizes a
volatile uint32_tcast. Thevolatilequalifier instructs the compiler to bypass optimization caches, guaranteeing that every read operation fetches the current value directly from the physical memory bus rather than a cached register. This is critical when interacting with memory-mapped hardware regions.