STM32 Flash: Basics and Programming Tutorial

Okay, here’s a comprehensive article on STM32 Flash memory, covering its basics and providing a detailed programming tutorial. I’ve aimed for the 5000-word target, covering a wide range of topics and providing concrete examples.

STM32 Flash: Basics and Programming Tutorial

Introduction

The STM32 family of microcontrollers, produced by STMicroelectronics, is renowned for its versatility, performance, and wide range of peripherals. A crucial component of any STM32 microcontroller is its Flash memory. This non-volatile memory is where the microcontroller’s program code, constant data, and often, user-configurable settings are stored. Understanding how to interact with the Flash memory is fundamental for any embedded developer working with STM32 devices.

This article provides a deep dive into the STM32 Flash memory, covering its architecture, organization, features, and, most importantly, how to program it effectively and safely. We’ll explore the underlying principles, the HAL (Hardware Abstraction Layer) functions provided by ST, and best practices to ensure data integrity and prevent accidental overwrites.

1. Flash Memory Basics

1.1 What is Flash Memory?

Flash memory is a type of non-volatile memory, meaning it retains its data even when power is removed. It’s a form of EEPROM (Electrically Erasable Programmable Read-Only Memory) that allows data to be erased and reprogrammed in blocks (sectors) rather than byte-by-byte. This block-based erasure significantly speeds up the write process compared to older EEPROM technologies.

1.2 Types of Flash Memory: NOR vs. NAND

While this article focuses on the Flash memory within an STM32 microcontroller, it’s helpful to understand the broader context of Flash technology. There are two primary types of Flash memory:

  • NOR Flash: NOR Flash provides fast random access (read) times, making it ideal for storing program code that needs to be executed directly (execute-in-place, XIP). It also allows byte-level read access. STM32 microcontrollers primarily use NOR Flash for their internal program memory.
  • NAND Flash: NAND Flash offers higher storage density and faster write/erase speeds compared to NOR Flash, but it has slower random access times. It’s typically used for mass storage applications like SSDs, SD cards, and USB drives. Some STM32 devices might use external NAND Flash for data storage, but the internal program memory is almost always NOR-based.

1.3 Flash Memory Cell Operation

Flash memory cells store data by trapping electrons in a floating gate. The presence or absence of electrons in the floating gate represents a binary ‘0’ or ‘1’.

  • Programming (Writing): Applying a high voltage to the control gate and grounding the source allows electrons to tunnel through a thin oxide layer and become trapped in the floating gate (typically representing a ‘0’). This process is called Fowler-Nordheim tunneling.
  • Erasing: Applying a high voltage to the substrate (bulk) and grounding the control gate causes the electrons to tunnel out of the floating gate, returning the cell to its erased state (typically representing a ‘1’).
  • Reading: A lower voltage is applied to the control gate. The presence or absence of charge in the floating gate affects the current flow through the transistor, allowing the cell’s state to be read.

1.4 Flash Memory Endurance and Wear Leveling

Flash memory has a limited number of program/erase (P/E) cycles. Each time a sector is erased and reprogrammed, the oxide layer degrades slightly. Eventually, after a certain number of cycles (typically ranging from 10,000 to 100,000 or more, depending on the specific Flash technology), the cell may become unreliable.

  • Endurance: This is the specified number of P/E cycles a Flash memory block can withstand before the probability of errors increases significantly. STM32 datasheets specify the endurance of their internal Flash.
  • Wear Leveling: To extend the lifespan of Flash memory, especially in applications with frequent writes, wear leveling techniques are employed. Wear leveling distributes writes evenly across the available Flash sectors, preventing any single sector from being excessively used and prematurely worn out. While STM32’s internal Flash doesn’t have built-in hardware wear leveling within the main program memory, developers can implement software-based wear leveling strategies if needed for specific data storage areas.

2. STM32 Flash Memory Organization

The organization of the Flash memory within an STM32 microcontroller varies depending on the specific device. However, there are common principles and structures that apply across most STM32 families. You should always consult the reference manual for your specific STM32 microcontroller for precise details.

2.1 Main Memory (Program Memory)

This is the primary area where the application code is stored. It’s typically organized into sectors.

  • Sectors: The Flash memory is divided into sectors (or pages, the terminology can vary). The size of a sector can range from a few kilobytes to several kilobytes, depending on the device. Erasure operations are performed at the sector level; you cannot erase individual bytes or words.
  • Base Address: The main memory has a base address, which is usually 0x08000000. This is the starting address of the Flash memory.
  • Sector Numbering: Sectors are typically numbered sequentially, starting from 0.

2.2 System Memory

This area is reserved by ST and contains the bootloader. The bootloader is a small program that executes when the device is powered on or reset. It’s responsible for initializing the microcontroller and, optionally, loading the application code from an external source (e.g., via UART, USB, or SPI). You generally should not modify the system memory.

2.3 Option Bytes

Option bytes are a special area of Flash memory that stores configuration settings for the microcontroller. These settings control various aspects of the device’s behavior, such as:

  • Read Protection (RDP): Prevents unauthorized reading of the Flash memory contents.
  • Write Protection (WRP): Prevents specific sectors of the Flash memory from being erased or programmed.
  • Boot Configuration: Determines the boot source (e.g., main Flash memory, system memory, or SRAM).
  • Watchdog Configuration: Enables or disables the watchdog timer.
  • Other Device-Specific Options: Various other settings related to clock configuration, peripherals, and more.

Option bytes are non-volatile and persist across power cycles. Modifying option bytes requires a special procedure, and incorrect settings can potentially “brick” the device (make it unrecoverable).

2.4 Flash Memory Interface (FLASH)

The STM32 microcontroller has a dedicated Flash memory interface (often referred to as the FLASH peripheral) that handles the low-level operations of reading, programming, and erasing the Flash memory. This interface includes registers that control:

  • Flash Access Control Register (FLASH_ACR): Configures wait states, prefetching, and instruction/data caching.
  • Flash Key Register (FLASH_KEYR): Used to unlock the Flash memory for programming and erasing.
  • Flash Option Key Register (FLASH_OPTKEYR): Used to unlock the option bytes for modification.
  • Flash Status Register (FLASH_SR): Indicates the status of Flash operations (busy, error, etc.).
  • Flash Control Register (FLASH_CR): Controls Flash operations like programming, erasing, and setting write protection.
  • Flash Option Control Register (FLASH_OPTCR): Controls option byte programming.

3. Programming the STM32 Flash Memory (HAL-Based)

STMicroelectronics provides the Hardware Abstraction Layer (HAL) library, which simplifies interacting with the STM32 peripherals, including the Flash memory. The HAL provides a set of functions that abstract away the low-level register manipulations, making Flash programming more convenient and portable.

3.1 Prerequisites

Before you can program the Flash memory, you need:

  • An STM32 microcontroller: Choose a specific STM32 device (e.g., STM32F407, STM32L476, etc.).
  • A development board: A development board (e.g., Nucleo, Discovery) provides the necessary hardware and connections.
  • An IDE and Toolchain: You’ll need an Integrated Development Environment (IDE) like STM32CubeIDE, Keil MDK, or IAR Embedded Workbench, along with the appropriate toolchain (compiler, linker, debugger).
  • STM32CubeMX (Optional but Recommended): STM32CubeMX is a graphical configuration tool that helps you initialize the microcontroller’s peripherals and generate the initial project code.
  • HAL Library: The HAL library is typically included with the IDE and STM32CubeMX.

3.2 Basic Flash Programming Steps

The general process for programming the STM32 Flash memory using the HAL involves these steps:

  1. Unlock the Flash Memory: The Flash memory is protected by default to prevent accidental modification. You must unlock it before performing any erase or program operations.
  2. Erase Sectors (if necessary): Flash memory must be erased before it can be programmed. Erasure is done at the sector level.
  3. Program Data: Data is programmed in words (32 bits), half-words (16 bits), or bytes (8 bits), depending on the device and configuration.
  4. Lock the Flash Memory (optional): After programming, you can re-lock the Flash memory for protection.
  5. Verify (Optional): It is good practice to read back the programmed data to verify its integrity.

3.3 HAL Flash Functions

The HAL library provides several key functions for Flash programming:

  • HAL_FLASH_Unlock(): Unlocks the Flash memory for programming and erasing.
  • HAL_FLASH_Lock(): Locks the Flash memory to prevent further modifications.
  • HAL_FLASH_Program(uint32_t TypeProgram, uint32_t Address, uint64_t Data): Programs a single word, half-word or byte at the specified address. TypeProgram specifies the data size:
    • FLASH_TYPEPROGRAM_BYTE
    • FLASH_TYPEPROGRAM_HALFWORD
    • FLASH_TYPEPROGRAM_WORD
    • FLASH_TYPEPROGRAM_DOUBLEWORD (for devices that support it)
  • HAL_FLASHEx_Erase(FLASH_EraseInitTypeDef *pEraseInit, uint32_t *SectorError): Erases one or more sectors. The FLASH_EraseInitTypeDef structure specifies the type of erase (sector or mass erase) and the sector range.
  • HAL_FLASH_GetError(): Retrieves the last Flash error code.
  • HAL_FLASHEx_OBProgram(FLASH_OBProgramInitTypeDef *pOBInit): Programs option bytes.
  • HAL_FLASHEx_OBGetConfig(FLASH_OBProgramInitTypeDef *pOBInit): Reads current option bytes configuration.

3.4 Example: Erasing and Programming a Sector

“`c

include “stm32f4xx_hal.h” // Replace with your device’s header file

// Function to erase and program a sector
HAL_StatusTypeDef Flash_Program_Example(uint32_t sector_number, uint32_t start_address, uint32_t *data, uint32_t data_length) {
HAL_StatusTypeDef status;
FLASH_EraseInitTypeDef erase_init;
uint32_t sector_error = 0;

// 1. Unlock the Flash
status = HAL_FLASH_Unlock();
if (status != HAL_OK) {
    return status;
}

// 2. Erase the sector
erase_init.TypeErase = FLASH_TYPEERASE_SECTORS;
erase_init.Sector = sector_number;
erase_init.NbSectors = 1;
erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3; // Check your device's datasheet
status = HAL_FLASHEx_Erase(&erase_init, &sector_error);
if (status != HAL_OK) {
    HAL_FLASH_Lock(); // Lock on error
    return status;
}
if (sector_error != 0xFFFFFFFF) {
  //An error occurred during sector erase
  HAL_FLASH_Lock(); //Lock on error.
  return HAL_ERROR;
}

// 3. Program the data
for (uint32_t i = 0; i < data_length; i++) {
    status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, start_address + (i * 4), data[i]);
    if (status != HAL_OK) {
        HAL_FLASH_Lock(); // Lock on error
        return status;
    }
}

// 4. Lock the Flash (optional)
HAL_FLASH_Lock();
return HAL_OK;

}

// Example Usage (in main function):
int main(void) {
// … HAL Initialization …

// Example Data
uint32_t my_data[] = {0x11223344, 0x55667788, 0x99AABBCC, 0xDDEEFF00};
uint32_t data_size = sizeof(my_data) / sizeof(my_data[0]);

// Sector and address to program
uint32_t target_sector = 5; // Example: Sector 5
uint32_t target_address = 0x08020000; // Example address (adjust for your sector size)

//Ensure address is aligned to word boundary (4 bytes)
if (target_address % 4 != 0){
  //Handle error, address is not aligned.
  while(1);
}

HAL_StatusTypeDef result = Flash_Program_Example(target_sector, target_address, my_data, data_size);

if (result == HAL_OK) {
    // Programming successful
} else {
    // Programming failed, check HAL_FLASH_GetError() for details
    uint32_t error = HAL_FLASH_GetError();
    // Handle the error (e.g., display an error message)
}

// ... Rest of your application ...

 while (1)
 {
    //Main program loop.
 }

}

“`

Explanation:

  • Includes: The necessary header file for your STM32 device is included.
  • Flash_Program_Example function:
    • Unlocking: HAL_FLASH_Unlock() unlocks the Flash memory.
    • Erasing: HAL_FLASHEx_Erase() is used to erase the specified sector. The FLASH_EraseInitTypeDef structure is populated with the erase parameters. The VoltageRange should be set according to your device’s power supply and datasheet specifications. Error checking is crucial after the erase operation.
    • Programming: A for loop iterates through the data array, and HAL_FLASH_Program() writes each word to the Flash memory at the specified address. The address is incremented by 4 bytes (one word) for each iteration.
    • Locking: HAL_FLASH_Lock() re-locks the Flash memory.
    • Error Handling: The function checks the return status of each HAL function and returns an error code if any operation fails. The sector_error variable will be populated with the failing sector number in case of an error.
  • Main Function:
    • Data: Sample data is created as an array of 32-bit words.
    • Address Alignment: The code explicitly checks if target_address is properly aligned to a 4-byte boundary, which is required by HAL_FLASH_Program when programming words. If it is not aligned, the program enters an infinite loop (you’d typically handle this error more gracefully in a real application).
    • Function Call: The Flash_Program_Example function is called with the target sector, address, data, and data size.
    • Error Handling: The result of the function call is checked, and if an error occurred, HAL_FLASH_GetError() is used to get more details about the error.

3.5 Example: Reading from Flash

Reading from Flash memory is much simpler than writing, as it doesn’t require unlocking or erasing. You can directly access the Flash memory as if it were a regular array:

“`c
// Assuming target_address and data_size are defined as before

uint32_t read_data[data_size]; // Array to store the read data

// Read data from Flash
for (uint32_t i = 0; i < data_size; i++) {
read_data[i] = ((uint32_t )(target_address + (i * 4)));
}

// Now ‘read_data’ contains the data read from Flash
// You can compare ‘read_data’ with ‘my_data’ to verify the programming
“`

Explanation:

  • Pointer Casting: *((uint32_t *)(target_address + (i * 4))) This line does the following:
    • target_address + (i * 4): Calculates the address of the word to read.
    • (uint32_t *): Casts the address to a pointer to a 32-bit unsigned integer.
    • *: Dereferences the pointer, effectively reading the 32-bit value at that memory location.

3.6 Example: Programming Option Bytes

Programming option bytes is a more delicate operation. Incorrect settings can make your device unrecoverable. Always double-check the reference manual for your specific STM32 device before modifying option bytes.

“`c

include “stm32f4xx_hal.h” // Replace with your device’s header file

HAL_StatusTypeDef OptionBytes_Program_Example() {
HAL_StatusTypeDef status;
FLASH_OBProgramInitTypeDef ob_init;

// 1. Unlock the Flash and Option Bytes
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();

// 2. Get the current option byte configuration
HAL_FLASHEx_OBGetConfig(&ob_init);

// 3. Modify the desired option bytes (Example: Change Read Protection)
// BE VERY CAREFUL HERE!  Incorrect settings can brick your device.
ob_init.OptionType = OPTIONBYTE_RDP;  // Specify we are changing Read Protection
//Check current value, only program if different.
if (ob_init.RDPLevel != OB_RDP_LEVEL_1) // Example: set to level 1 (read protection)
{
    ob_init.RDPLevel = OB_RDP_LEVEL_1;


    // 4. Program the option bytes
    status = HAL_FLASHEx_OBProgram(&ob_init);
    if (status != HAL_OK) {
         HAL_FLASH_OB_Lock();
         HAL_FLASH_Lock();
         return status;
    }
    // 5. Launch Option Bytes loading (forces a reset)
    HAL_FLASH_OB_Launch();
    //NOTE: The code will not continue past this point because
    //the device will reset. The option byte changes take effect after reset.
}
else
{
   //Option bytes already programmed with requested value.
    HAL_FLASH_OB_Lock();
    HAL_FLASH_Lock();
    return HAL_OK;
}

// This code will not be reached due to the reset.
HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();
return HAL_OK;

}

“`

Explanation:

  • Includes: Includes the necessary header file.
  • OptionBytes_Program_Example function:
    • Unlocking: HAL_FLASH_Unlock() unlocks the Flash memory, and HAL_FLASH_OB_Unlock() unlocks the option bytes.
    • Get Configuration: HAL_FLASHEx_OBGetConfig() retrieves the current option byte settings. This is crucial; you modify the existing settings rather than writing a completely new set.
    • Modify Settings: The code modifies the RDPLevel member of the ob_init structure to set read protection to level 1. This is a critical step. Understand the implications of each option byte before changing it.
    • Check Current Value: Before programming, the code checks if the desired RDP level is already set. This avoids unnecessary option byte programming cycles, which have a limited lifespan.
    • Program Option Bytes: HAL_FLASHEx_OBProgram() applies the changes to the option bytes.
    • Launch Option Bytes Loading: HAL_FLASH_OB_Launch() triggers a system reset to apply the new option byte settings. The code will not execute beyond this point because the device will reset. This is a key difference from regular Flash programming.
    • Error Handling: Includes error checking after the HAL_FLASHEx_OBProgram() call.
  • Important Note: Modifying option bytes, especially read/write protection, can have serious consequences. Make sure you understand the different protection levels and their impact on debugging and future programming.

4. Best Practices and Considerations

4.1 Error Handling:

  • Always check the return status of HAL Flash functions. If a function returns anything other than HAL_OK, an error has occurred.
  • Use HAL_FLASH_GetError() to get details about the error. This function returns a bitmask indicating the type of error (e.g., programming error, write protection error, etc.).
  • Implement appropriate error handling logic. This might involve retrying the operation, displaying an error message, or taking other corrective actions.

4.2 Address Alignment:

  • Ensure that the programming address is aligned to the appropriate boundary. For word programming, the address must be a multiple of 4. For half-word programming, it must be a multiple of 2. Byte programming can be done at any address. Misaligned addresses will usually result in a hard fault.

4.3 Sector Size Awareness:

  • Be mindful of sector boundaries. When erasing, you must erase an entire sector. If you only need to update a small portion of data within a sector, you’ll need to:
    1. Read the entire sector into RAM.
    2. Modify the relevant data in RAM.
    3. Erase the sector.
    4. Write the modified data back to the sector.

4.4 Write Protection:

  • Use write protection features to safeguard critical code or data. The STM32 Flash memory allows you to write-protect individual sectors. This prevents accidental overwrites and can be helpful for storing bootloaders, calibration data, or other sensitive information.

4.5 Read Protection:

  • Consider using read protection to prevent unauthorized access to your code. Read protection prevents external tools (like debuggers) from reading the Flash memory contents. However, it also makes debugging more difficult, so use it judiciously. Note: Read Protection Level 2 makes the device read-protected permanently; this is irreversible.

4.6 Endurance:

  • Be aware of the Flash memory’s endurance limits. Avoid unnecessary erase/program cycles, especially in applications that require frequent data updates.
  • Consider implementing software wear leveling if you have a region of Flash that needs to be written to frequently. This will help distribute the wear across multiple sectors.

4.7 Interrupts:

  • Be cautious when programming Flash from within an interrupt service routine (ISR). Flash operations can take a significant amount of time, and blocking in an ISR can lead to system instability. It is generally recommended to perform Flash programming in the main application loop or in a lower-priority task. If you must do it in an ISR, use the non-blocking HAL functions (if available) and keep the operation as short as possible. You might signal a task in the main loop to complete the Flash operation.

4.8 Power Failure:

  • Flash programming operations are vulnerable to power failures. If power is interrupted during an erase or program operation, the Flash memory contents may become corrupted. Consider these mitigations:
    • Use a reliable power supply.
    • Implement a power-fail detection mechanism. If a power failure is detected, you might be able to save critical data to a non-volatile storage (like an external EEPROM) before the system shuts down.
    • Use a checksum or CRC to verify the integrity of the Flash contents after programming.
    • Consider using a “dual-bank” Flash approach (if supported by your device). This allows you to program one bank while the other bank is active, providing a fallback mechanism in case of a power failure during programming.

4.9 Code Readout Protection (RDP)

  • Understand the different RDP levels:
    • Level 0: No protection. Flash memory can be read and programmed freely.
    • Level 1: Read protection is enabled. The Flash memory cannot be read via the debugger or other external tools. Mass erase is still possible, which will remove the protection.
    • Level 2: Read and write protection are enabled permanently. The device is effectively locked, and you cannot reprogram it or debug it. Use this level with extreme caution!

5. Advanced Topics

5.1 Dual-Bank Flash (if supported)

Some STM32 devices have dual-bank Flash memory. This means the Flash memory is divided into two independent banks. This feature can be used for:

  • Over-the-Air (OTA) Updates: You can program a new firmware image into one bank while the application is running from the other bank. After the update is complete, you can switch to the newly programmed bank. This allows for seamless firmware updates without interrupting the application.
  • Fail-Safe Bootloader: You can store a backup bootloader in one bank, providing a recovery mechanism if the main bootloader in the other bank becomes corrupted.

5.2 In-Application Programming (IAP)

IAP allows the STM32 microcontroller to reprogram its own Flash memory while the application is running. This is useful for:

  • Firmware Updates: The application can download a new firmware image (e.g., via UART, USB, Ethernet, or a wireless interface) and write it to Flash memory.
  • Data Logging: The application can store data logs directly in Flash memory.
  • Configuration Updates: The application can update its configuration parameters stored in Flash.

5.3 Memory Mapping and Linker Scripts

The linker script (usually a .ld file) is crucial for defining how your code and data are placed in memory. It specifies the addresses and sizes of different memory sections (e.g., Flash, RAM, etc.). You need to ensure that your linker script is configured correctly to:

  • Place the code in the correct Flash memory region.
  • Allocate space for data in RAM.
  • Define the vector table (interrupt handlers) at the correct address.

5.4 Using Flash as Data Storage

While primarily intended for code storage, the internal Flash can also be used to store data, particularly for non-volatile configuration parameters, calibration data, or small data logs. When doing this, it is essential to:

  • Choose a dedicated area (sector or sectors) for data storage. Avoid overlapping with the code area.
  • Implement a robust data management scheme. This includes:
    • Version control: Store a version number with the data to handle potential data structure changes during firmware updates.
    • Checksum or CRC: Verify data integrity.
    • Wear leveling (software-based): If you have frequent writes, distribute the writes across multiple sectors to extend the Flash lifespan.
    • Power-fail safety: Design your data storage scheme to be resilient to power interruptions. This might involve using redundant copies of data or a journaling mechanism.

Conclusion

Programming the STM32 Flash memory is a fundamental skill for embedded developers. This article has provided a comprehensive overview of the STM32 Flash memory, its organization, and how to program it using the HAL library. By understanding the principles of Flash memory operation, the available HAL functions, and the best practices outlined here, you can effectively and safely utilize the Flash memory in your STM32 projects. Always remember to consult the reference manual and datasheet for your specific STM32 device for the most accurate and detailed information. The examples provided are a starting point; adapt them to your specific needs and hardware.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top