How to reserve a page of Flash memory? (STM32/arduino)

I am using STM32401CE with the platformio/arduino framework and would like to reserve a page of Flash memory for the application to store occasionally settings and calibration data. Something one would typically do with the builtin eeprom which this MCU doesn’t have.

What is a good way to reserve one of the 16K blocks such that the linker doesn’t place code in it? Preferably in a way that survives platformio updates or project replication on another machine.

This is the list of Flash blocks in that MCU:

image

1 Like

The pragmatic approach is to take the last page (starting at 0x08003c00) and be careful that the code size stays below 15K.

Whether more robust solutions are possible is difficult to say. It would probably involve using a custom loader file, So you will probably trade robustness during development vs robustness in case of Android updates.

Thanks @manuelbl.

I am not sure I understand. 0x08003c00 doesn’t seem to be a start of a Flash sector per the table in the original post.

(I presume that flash can be erased in ‘sector’ units and thus I need to allocate a whole sector, preferably a 16K one rather than 64K or 128K).

0x08003c00 doesn’t make sense indeed. I’ve mixed up the particular model.

In an STM32F4, erasing is sector units and is tricky because the small sectors are at the start and the big ones are at the end. So you probably have no other choice than to modify the loader file of your particular variant.

Do you know what modifications you need to make to the loader file?

@maxgerhardt Is it possible to specify an explicit loader file in platformio.ini when building for the Arduino framework in the STM32 platform?

@manuelbl, I don’t have much experience with linker scripts but searching my $HOME/.platformio revealed the file packages/framework-arduinoststm32/variants/Generic_F401Cx/ldscript.ld which seems to have such a script.

The flash space seems to be divided into named regions so possibly I can add my own region between the .isr_vector and .text, aligning it on 16K boundary and setting its size to 16K, or something like that.

Is this reasonable? Is there a way to make this surviving platformio updates?.

Or, since eeprom is so useful with other MCUs, maybe have it as a supported feature that can be enabled from platformio.ini?

/* Specify the memory areas */
MEMORY
{
FLASH (rx)      : ORIGIN = 0x8000000 + LD_FLASH_OFFSET, LENGTH = LD_MAX_SIZE - LD_FLASH_OFFSET
RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = LD_MAX_DATA_SIZE
}

/* Define output sections */
SECTIONS
{
  /* The startup code goes first into FLASH */
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector)) /* Startup code */
    . = ALIGN(4);
  } >FLASH

  /* The program code and other data goes into FLASH */
  .text ALIGN(4):
  {
    . = 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)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH

  ...
111

Interesting question.

First of all, if one would like to reserve memory at the end, it’s really easy with the already existing linker script, since,

The length of the FLASH memory is already controllable by the offset and LD_MAX_SIZE parameter, which is filled in by

which in turn takes it from the board. In your example, the STM32F401CE has 512Kbyte of flash. If we want to reserve the last 128kByte sector, then we would just say that the board has a flash size of 384kByte.

board_upload.maximum_size = 393216

When recompiling, you will also see this change

Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [          ]   0.9% (used 880 bytes from 98304 bytes)
Flash: [=         ]   7.5% (used 29444 bytes from 393216 bytes)
.pio\build\blackpill_f401ce\firmware.elf 

And thus if you, in code, directly access the last flash sector, compiling the firmware this way with a simulated lower memory size will always make sure that it’s available, since the linker script cannot place symbols beyond the end of memory that we have told about.

The STM32F4 series is kinda weird here. The last sector in flash is immensely huge. (The pages in e.g. an STM32F1 series chip are much smaller, all 1kByte each). From an erased flash state (0xff), one can program the flash in byte,half-word (2 bytes), word (4B) and double-word (8B) sizes, but in order to get to an erased state, one can only do whole sector erases or a mass erase. So if you would want to modify a word in memory that you’ve already written to once, you must erase it (to get a 0 to a 1 bit again).

Note however that there are special storage systems that do not actually require this. As a very simple example you may e.g. imagine that you have a “starting point of a list”, that is read from flash start to flash end. You store things as key,value pair, e.g. “(5, 1234)” with 5 meaning config-value-1 and 1234 being the value of it. When you want to write a (new) value for config-value-1, you go to the end of the list (that can be uniquely detected), and write your key-value pair with some metainfo there, while not erasing or modifying anything else in the sector. You always “just append”. Then to read the current value of a “key”, you need to search from beginning to end to get the most up-to-date value. (Implementations also use a RAM buffer / lookup table for this that is read at bootup and updated as new write-API-calls get in). Only when the sector is full, you need to save the last value of all key-value pairs (a key is unique) into RAM, erase the sector, then write the saved key-value pairs back, and you can start anew. This procedure, if done in the middle, is also called “garbage collection”, since it speeds up the time needed to get the current value of a K-V pair, but at the cost of flash erases.

An (more sophisticated) example of this is e.g. NVStore in the mbed-os framework.

I’ll come back to the topic of creating a “hole” in the middle of the flash region in a moment. We can define new memory regions and remap some sections to these rections or create new sections which we can place objects in with the __attribute__(section("abc")) attribute, and we can change the linker script to an arbitrary one using board_build.ldscript = myfile.ld in the platformio.ini. That way we can e.g. get the second 16KByte flash page. I’ll testing a bit…

Hah interesting, that is exactly what I thought about too just now.

I’m not an expert on loader scripts either. Your approach seems reasonable. Another approach is to split the memory into additional areas like ISRVECTORS, EEPROM and FLASH, each with fixed address and size.

Such a loader file can survive PlatformIO updates if it’s part of your project and not part of PlatformIO. That’s why it would be interesting to know of a custom loader script can be specified in platformio.ini. It might not survive Android framework updates if they modify the loader file.

Pseudo EEPROM is a useful feature. But such a feature belongs into the Arduino framework, not PlatformIO. PlatformIO at its core is a build system and doesn’t provide any such features.

Indeed, STM32Duino also has EEPROM emulation, and they also use as expected the last page (or here called sector) of flash.

I can also sanity check that it uses the correct last addresses by printing the FLASH_BASE_ADDRESS and FLASH_END macro values.

I’ve had a play with this and I was able to reserve the second 16Kbyte sector as you’ve described. Make multiple memory regions, only place the ISR vectors in the first memory region, then an empty / completely user-controlled section, then the rest of code and constants.

I’ve uploaded the project to GitHub - maxgerhardt/pio-stm32-reserved-flash-sector: An example for a modified linker script that relocates the code so that a 16kBytey page becomes free. and verified its workings with a Blackpill F401CC board I had laying around (you have the CE version).

The important magic is in the linker script

Firmware does some test output that gives for me

Address of reversed flash sector buffer object: 0x8004000
Length of reversed flash sector buffer object: 16384
_reserved_flash_start: 0x8004000
_reserved_flash_end: 0x8008000

So all is good :slight_smile:

That is a way more memory-saving method if the config data is small and you want to use up as little as flash sectors and space if possible and it has to be error checked against overflowing.

Otherwise, the more traditional “last flash sector” approach from the EEPROM emulation as linked above also works, but eats your last 128K in the 512K flash. But if your application never reaches that size, it’s okay too I guess.

2 Likes

Thanks @maxgerhardt! I will give it a try. It seems to be platformio safe in the sense that it doesn’t require modifying platformio managed files.

BTW, I found this thread, where the reserved section is reserved using C using pragmas. Any thoughts on this approach?

(See post #7 in the link below).

It seems that is for the IAR compiler (see here), not GCC. That would have been an extremely easy solution if that worked, just place a 16-kbyte variable / buffer at the start of a flash sector, then nothing else can overwrite it and you nicely reserve the memory.

I’ve tested that with the code

#pragma location=0x800C000
const uint8_t myTestBuffer[1024] = {0x00};

and then asking the nm tool for the location of the symbol

C:\Users\Max\Documents\PlatformIO\Projects\stm32_flash_alloc_test>arm-none-eabi-nm .pio\build\blackpill_f401ce\firmware.elf  | grep myTest
0800d89e r _ZL12myTestBuffer

and it did not get placed at that address. Further activating the warning -Wunknown-pragmas via build_flags gives

Compiling .pio\build\blackpill_f401ce\src\main.cpp.o
src\main.cpp:14: warning: ignoring #pragma location  [-Wunknown-pragmas]
   14 | #pragma location=0x800C000
      |

in the compile log.

The KEIL compiler can do this too (Documentation – Arm Developer), but GCC is lacking there.

Thanks @maxgerhardt. I will go with your custom linker script. I think it addresses all the requirements.

When I started working with STM32 I just assumed that it has EEPROM, so I guess I learn something new every day. The other option would be to add a small i2c EEPROM IC but that would be a defeat. :wink:

BTW: STM has published code for emulating EEPROMs. Their approach is:

  • Reserve two pages/sections and erase them
  • Whenever a value is set: append a key/value pair to the first page. Since the page was initially erased, it’s possible to append data by writing just a few bytes.
  • Whenever a value is queried, iterate through the entire page and take the last value for a given key.
  • When the first page is full, copy the last value of each key to the other page and erase the page. Then the two pages’ roles are swapped.

This is implemented such that it can recover without data loss even if power is lost during the copying.

I’ve converted the code to C++ and simplified it for a project using a STM32F0 MCU:

Here are some of the related application notes:

1 Like