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.
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.
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
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…
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.
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.
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
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.
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.
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.
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: