STM32 Flash memory to store data permanently

STM32 Flash memory to store data permanently


6 minute read

Imagine, you are recording a sensor and storing it using some variables in your code. At some point, BOOM, the microcontroller is disconnected from a power supply and you lose the recorded data. Once you power on the MCU, the variables hold default values, not sensor readings. Is there any remedy to this problem, so you can store data permanently within the MCU to retrieve it after the power outage? The answer to this question lies in using STM32 Flash Memory which allows us to keep the data after power outage. So, this tutorial is about explaining general concepts of flash memory, writing to flash memory, and reading data from flash memory. I will use the Nucleo-l4768rg board, but you can use any other type of STM32 microcontroller. Also, I will use STM32 HAL API develop the code. 

First, let's look at the table below which shows the structure of flash memory. You can find it within a reference manual of the STM32 MCU you are using. If you do not know how to work with the reference manuals, please refer to my STM32 Intro Course: STM32 Intro Course

As you see, the memory unit might contain several banks and it is divided into pages. In my case, I have 512 pages and each page can contain 2048 Bytes. These numbers might vary from one microcontroller to another. The primary purpose of flash memory is to keep the execution instructions. When you write code and compile it, your code will be translated into a set of instructions that your microcontroller can understand. When you press 'run', these instructions will be sent to the microcontroller and stored inside of flash memory. The microcontroller stores the instructions from the top starting from the 0x8000000 memory address. Therefore, it is super critical not to use flash memory from the top. Instead, we have to use the last pages, which are not allocated by the instructions. Another important thing is we have to erase the memory space before storing data. Otherwise, it is not going to work.

STM32 Flash memory

Header File

To work with flash memory, I created source and header files. The code below belongs to the header file 'flash_store_data.h' and it has two function prototypes. 

#ifndef INC_FLASH_STORE_DATA_H_
#define INC_FLASH_STORE_DATA_H_

#include "stdio.h"
#include "main.h"

void store_flash_memory(uint32_t memory_address, uint8_t *data, uint16_t data_length);
void read_flash_memory(uint32_t memory_address, uint8_t *data, uint16_t data_length);


#endif /* INC_FLASH_STORE_DATA_H_ */

Source File

The source file contains the implementation of the functions. First, let me show a function to store data within flash memory.

Initially, as I said, we have to erase memory before writing. For that purpose we have FLASH_EraseInitTypeDef. We need to define all the members of the struct before erasing the memory:

  • TypeErase: equals FLASH_TYPEERASE_PAGES, meaning that we will erase specific pages, not the whole flash memory
  • Page: Onset page.
  • NbPages: number of pages we want to erase
  • Flash Bank

Once we define them, we can erase the memory by calling HAL_FLASHEx_Erase function. After erasing, we can start sending data to the flash memory. To do that, we will use HAL_FLASH_Program function. The first argument is the size of a data chunk that we can convey per iteration. It can be either Single Word (4 bytes) or Double Word (8 bytes). In my microcontroller, this argument has to be FLASH_TYPEPROGRAM_DOUBLEWORD. It means that I can send a data piece with uint64_t data type that can contain 8 bytes of data. Using a while loop, I iterate through data, sending it to the flash memory. Then I have to Lock the flash memory by calling HAL_FLASH_Lock function.

#include "flash_store_data.h"
#include "stdint.h"
#include "stm32l4xx_it.h"


typedef uint64_t flash_datatype;
#define DATA_SIZE sizeof(flash_datatype)


void store_flash_memory(uint32_t memory_address, uint8_t *data, uint16_t data_length)
{
   uint8_t double_word_data[DATA_SIZE];
   FLASH_EraseInitTypeDef flash_erase_struct = {0};
   HAL_FLASH_Unlock();
   // defining the members of a struct
   flash_erase_struct.TypeErase = FLASH_TYPEERASE_PAGES;
   // defining an onset number page to be erased
   flash_erase_struct.Page = (memory_address - FLASH_BASE) / FLASH_PAGE_SIZE;
   // number of pages to remove
   flash_erase_struct.NbPages = 1 + data_length / FLASH_PAGE_SIZE;
   // identify the flash bank
   if(memory_address >  FLASH_BANK1_END && memory_address < FLASH_BANK2_END )
   {
	flash_erase_struct.Banks = FLASH_BANK_2;
   }
   else if(memory_address >  FLASH_BASE && memory_address < FLASH_BANK1_END)
   {
	flash_erase_struct.Banks = FLASH_BANK_1;
   }
   else
   {
	printf("illegal memory address \n");
	UsageFault_Handler();
   }
   uint32_t  error_status = 0;

   // erase the pages, this step is mandatory
   HAL_FLASHEx_Erase(&flash_erase_struct, &error_status);
   int i = 0;
   // using while loop, convey all data to the flash memory
   while ( i <= data_length)
   {
	double_word_data[i % DATA_SIZE] = data[i];
	i++;
	if (i % DATA_SIZE == 0)
	{
	  HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, memory_address + i - 
		DATA_SIZE, *((uint64_t *)double_word_data));

	}
   }
   // convey data if something left
   if (i % DATA_SIZE != 0)
   {
	HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, memory_address + i 
		- i % DATA_SIZE, *((flash_datatype *)double_word_data));
   }
	// lock the memory
   HAL_FLASH_Lock();
}

It is worth mentioning that if your MCU supports FLASH_TYPEPROGRAM_SINGLEWORD instead Double Word, define flash_datatype as uint32_t:

typedef uint32_t flash_datatype;

Finally, we have a function to get data from the flash memory. Using for loop, I read all the necessary bytes from the memory unit:

void read_flash_memory(uint32_t memory_address, uint8_t *data, uint16_t data_length)
{
    for(int i = 0; i < data_length; i++)
    {
	*(data + i) = (*(uint8_t *)(memory_address + i));
    }
}

To test the library, we can use the code below. In this code, a counter is incremented every second within a while loop and stored within the flash memory. If you power off the microcontroller and start the code again, the program will restore the counter value from the Flash memory. TO store data, I used the last page of flash memory which has an address of 0x080FF800

To display the counter value, I use printf function based on SWV. You can get more information about SWV by referring to this article: printf using SWV. Or, you can use live expressions on debug mode. 

#include "main.h"
#include "flash_store_data.h"

#define FLASH_ADDRESS_COUNTER 0x080FF800
uint32_t counter;
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM4_Init();
    read_flash_memory(FLASH_ADDRESS_COUNTER, (uint8_t *)&counter, sizeof(counter));
    if(counter == 0xffffffff)
    {
	counter = 0;
    }
    else
    {
	printf("counter value is %d \n", (int)counter);
    }
    while (1)
    {
	counter++;
	HAL_Delay(1000);
	printf("counter is incremented: %d \n", (int)counter);
        store_flash_memory(FLASH_ADDRESS_COUNTER, (uint8_t *)&counter, sizeof(counter));
    }
}

The source code is available on GitHub. You can get access by joining the steppeschool community:

Steppeschool Community

Patreon Steppeschool COmmunity

Video version of the tutorial:

« Back to Blog