There are times when you want to save the set values even after the power is turned off.
In this article, we will try to save the data in flash memory using HAL.
Since I have been working with Mbed, I wrote the code using C++ with Mbed classes and so on.
Here is the development environment at the time of submission.
PC: Windows 10 OS
IDE: STM32CubeIDE Version1.3.0
Board: STM32Nucleo-F401RE
Disclaimer
This is a rather tricky project, so please handle it at your own risk.
Checking the flash memory specifications
There is a reference manual available for download to check the specifications.
You can download it from here.
Flash memory is composed of units called sectors.
RAM (random access memory) can literally be accessed at random, but flash is not so easy.
Writing is done in sectors, and
Unlock
Erase
Write
Lock
The procedure is as follows.
A locking mechanism is provided to prevent easy rewriting.
The sector structure of the STM32F401RE is as follows

In which sectors to store
You can use sectors 0 to 7, but if you want to use it as a data area, it should be 0 or 7.
I thought so, because I wanted to keep the area for programs and other code contiguous.
However, since the interrupt vector is placed in sector 0, sector 7 is the best choice! That’s right.
But I’m sure you’ve all noticed by now. That’s right, sector 7 has a capacity of 128KB, which is equivalent to 1/4 of a flash memory.
That’s a waste of money, since the amount of data you want to store is negligible.
So, I tried a little ingenious method to save data in sector 0.
Summary of the specification
Normally, the STM32 interrupt vector table is located in flash.
I want to rewrite the vector table, so we decided to move the table to RAM.
To handle flash memory, I created a class called FlashMemoryAccesor.
When constructing FlashMemoryAccesor (constructor)
Save the vector table
Move the vector table to RAM
When writing
Unlock
Erase sector 0
First, write the saved vector table
Next, write the data to be saved
Locking
When reading
Read the saved data
It is important to note that the interrupt vector table is placed in RAM from the time the constructor of FlashMemoryAccesor is executed.
As mentioned in the article above, this increases the speed at which the interrupt process is executed.
Also, the reset process must work correctly when a reset occurs, so the vector table must be written back to the beginning of the area after the erase.
Source code
main.cpp
#include "mbed.h"
#include
#include "FlashMemoryAccesor.h"
// Pack the data into a structure.
struct Data
{
int x;
int y;
char str[10];
};
Using the Serial class and VCP, the result can be checked by a terminal application such as Tera Term.
int main(void)
{
/* USER CODE BEGIN 1 */
Serial s(SERIAL_TX, SERIAL_RX);
s.baud(9600);
s.format(8, SerialBase::None, 1);
FlashMemoryAccesor fa;
s.printf("Flash Memory Accesor Started.\r\n");
Data ds = {0};
Data dd = {0};
ds.x = 123;
ds.y = 456;
ds.str[0] = 'a';
ds.str[1] = 'b';
ds.str[2] = 'c';
ds.str[3] = '\0';
bool b = fa.write(SECTOR0_ADDRESS, (uint8_t*)&ds, (uint32_t)sizeof(ds));
if (b)
{
s.printf("Flash write.\r\n");
fa.read(SECTOR0_ADDRESS, (uint8_t*)&dd, (uint32_t)sizeof(dd));
if (0 == memcmp((const void*)&dd, (const void*)&ds, sizeof(dd)))
{
s.printf("x=%d, y=%d, str=%s\r\n", dd.x, dd.y, dd.str);
s.printf("Flash write & read success.\r\n");
}
} else
{
s.printf("Flash compare error.\r\n");
}
}
} else
{
s.printf("Flash write failure.\r\n");
}
/* USER CODE END 1 */ /* USER CODE END 2
(Abbreviated)
FlashMemoryAccesor.h
#ifndef __FLASHMEMORYACCESOR_H
#define __FLASHMEMORYACCESOR_H
#include "stdint.h"
#define NVIC_NUM_VECTORS 101
#define SECTOR0_SIZE 16384
#define SECTOR0_ADDRESS 0x8000000
class FlashMemoryAccesor
{
public:
FlashMemoryAccesor();
void NVIC_MoveAndSaveVectors();
void eraseSector0();
void read(uint32_t adrs, uint8_t *data, uint32_t size);
bool write(uint32_t adrs, uint8_t *data, uint32_t size);
private:
uint32_t save_vectors[NVIC_NUM_VECTORS * 4];
};
#endif
FlashMemoryAccesor.cpp
#include "FlashMemoryAccesor.h"
#include "cmsis_nvic.h"
#include "stm32f4xx_hal.h"
#include
#define NVIC_RAM_VECTOR_ADDRESS (0x20000000) // Vectors positioned at start of RAM
#define NVIC_FLASH_VECTOR_ADDRESS (0x08000000) // Initial vector position in flash
FlashMemoryAccesor::FlashMemoryAccesor()
{
NVIC_MoveAndSaveVectors();
}
void FlashMemoryAccesor::NVIC_MoveAndSaveVectors()
{
uint32_t *vectors = (uint32_t *)SCB->VTOR;
uint32_t i;
if (SCB->VTOR == NVIC_FLASH_VECTOR_ADDRESS) {
uint32_t *old_vectors = vectors;
vectors = (uint32_t*)NVIC_RAM_VECTOR_ADDRESS;
for (i = 0; i < NVIC_NUM_VECTORS; i++) {
vectors[i] = old_vectors[i];
save_vectors[i] = old_vectors[i];
}
SCB->VTOR = (uint32_t)NVIC_RAM_VECTOR_ADDRESS;
}
}
void FlashMemoryAccesor::eraseSector0()
{
FLASH_EraseInitTypeDef erase;
erase.TypeErase = FLASH_TYPEERASE_SECTORS;
erase.Sector = FLASH_SECTOR_0;
erase.NbSectors = 1;
erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;
uint32_t pageError = 0;
HAL_FLASHEx_Erase(&erase, &pageError);
}
void FlashMemoryAccesor::read(uint32_t adrs, uint8_t *data, uint32_t size)
{
memcpy(data, (uint8_t*)(adrs + NVIC_NUM_VECTORS * 4), size);
}
bool FlashMemoryAccesor::write(uint32_t adrs, uint8_t *data, uint32_t size)
{
if (SECTOR0_SIZE < size + NVIC_NUM_VECTORS * 4)
{
return false;
}
if (SECTOR0_ADDRESS != adrs)
{
return false;
}
HAL_FLASH_Unlock();
eraseSector0();
uint8_t *p = (uint8_t*)save_vectors;
for ( uint32_t add = adrs; add < (adrs + NVIC_NUM_VECTORS * 4); add++ )
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, add, *p);
p++;
}
for ( uint32_t add = (adrs + NVIC_NUM_VECTORS * 4); add < (adrs + NVIC_NUM_VECTORS * 4 + size); add++ )
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_BYTE, add, *data);
data++;
}
HAL_FLASH_Lock();
return true;
}
FlashMemoryAccesor.cpp, h should be created as a file and added to the IDE project.
Edit the linker script (STM32F401RETX_FLASH.ld) as follows.
Place only interrupt vectors in the FLASH area, and create an area called FLASH2 so that SECTOR1 and onward become the code area.
/* Memories definition */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000200, LENGTH = 96K-512
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 16K
FLASH2 (rx) : ORIGIN = 0x8004000, LENGTH = 512K-16K
}
/* Sections */
SECTIONS
{
/* The startup code into "FLASH" Rom type memory */ .
.isr_vector :
.
. . = ALIGN(4);
KEEP(*(.isr_vector)) /* Startup code */ .
. . = ALIGN(4);
} >FLASH
/* The program code and other data into "FLASH" Rom type memory */ .
.text :
{ .
. = ALIGN(4);
*(.text) /* .text sections (code) */ .
*(.text*) /* .text* sections (code) */ .
*(.glue_7) /* glue arm to thumb code */
*(.glue_7t) /* glue thumb to arm code */ *(.eh_frame)
*(.eh_frame)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(4);
_etext = . . /* define a global symbol at the end of code */ .
} >FLASH2
/* Constant data into "FLASH" Rom type memory */ .
.rodata :
{ .
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */ .
*(.rodata*) /* .rodata* sections (constants, strings, etc.) */ .
. . = ALIGN(4);
} >FLASH2
.ARM.extab : { .
. = ALIGN(4);
*(.ARM.extab* .gnu.linkonce.armextab.*)
. . = ALIGN(4);
} >FLASH2
.ARM : { .
. . = ALIGN(4);
__exidx_start = . ;
*(.ARM.exidx*)
. = ALIGN(4); __exidx_end = . ;
. = ALIGN(4);
} >FLASH2
.preinit_array :
{
. = ALIGN(4);
PROVIDE_HIDDEN (__preinit_array_start = .) ;
KEEP (*(.preinit_array*))
PROVIDE_HIDDEN (__preinit_array_end = .) ;
. = ALIGN(4);
} >FLASH2
.init_array :
{
. = ALIGN(4);
PROVIDE_HIDDEN (__init_array_start = .) ;
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array*))
PROVIDE_HIDDEN (__init_array_end = .) ;
. = ALIGN(4);
} >FLASH2
.fini_array :
{
. = ALIGN(4);
PROVIDE_HIDDEN (__fini_array_start = .) ;
KEEP (*(SORT(.fini_array.*)))
KEEP (*(.fini_array*))
PROVIDE_HIDDEN (__fini_array_end = .) ;
. = ALIGN(4);
} >FLASH2
/* Used by the startup to initialize data */ .
_sidata = LOADADDR(.data);
/* Initialized data sections into "RAM" Ram type memory */ .
.data :
.
. = ALIGN(4);
. = ALIGN(4); _sdata = . . = ALIGN(4); _sdata = . /* create a global symbol at data start */ .
*(.data) /* .data sections */
*(.data*) /* .data* sections */ .
. . = ALIGN(4);
. = ALIGN(4); _edata = . . = ALIGN(4); _edata = . /* define a global symbol at data end */ .
} >RAM AT> FLASH2
Build and write
When I finish building without error and select Debug to start writing, I get the following error once in two times.

It may be that you are verifying the flash memory.
This error can be avoided by the following settings
Run - Run Configrations - Debugger - Set Reset behaviour Type to None.
So far, I have not had any problems except for the error mentioned above, but if you use it, please verify it thoroughly and use it at your own risk.





