1

(This question is specific for the Arduino Due, but maybe there's a general answer to the question)

I'm using DueFlashStorage to store application data in the flash memory of my Arduino Due. That works quite nicely. The Due has two flash blocks with 256k each. By default, the library writes data to the second flash block. The first flash block is used for the program. My program currently uses around 160k of flash (as the compiler tells me). That leaves about 96k of flash unused, which I could really use to store more application data. I managed to adjust DueFlashStorage to allow writing to the full memory range, but how do I detect where it is safe to write to? (Obviously, I must not overwrite any part of the program)

I have tried something like:

  // this returns the logical address of the first byte in flash (0x80000)
  byte* startAddr = dueFlashStorage.readAddress(0); 
  Serial.println((unsigned int)startAddr, HEX);
  int bytes = 0;
  while (bytes < 512 * 1024) // That's the size of the flash
  {
    int* data = (int*)(startAddr + bytes);
    if (*data == 0xFFFFFFFF) // empty flash is initialized with FF's
     {
       Serial.print("Free block at 0x");
       Serial.println((unsigned int)data, HEX);
     }
     else
     {
       Serial.print("Used block at 0x");
       Serial.println((unsigned int)data, HEX);
     }
    bytes+=512;
  }

This seems to work, as the first block detected as "free" is just above the size of the program. However, this approach has some major drawbacks:

  • It's slow. Running over the whole address space to find the end of the program uses time (and maybe I shouldn't just test the first word of each block)
  • It's not reproducible. After I have written data to the detected first free block, it's no longer free, so I need another approach (like some magic number) to detect the first data block after the CPU is reset.

Is there a better way for this (like, in the ideal world, a constant I could use)? I don't want to hardcode the data start, because as the program grows (and it's still in development) I would forget to adjust that constant and booooommmm....

2
  • exact location far up in flash? you want to have the data on the same location even if the sketch size changes or not?
    – Juraj
    Apr 30, 2021 at 18:45
  • I'm using some (simple) memory management for allocating flash space for the data. I'm filling it up from bottom to top, much like if it was a file. So the addresses I'm using are relative to the start, but the start can move.
    – PMF
    Apr 30, 2021 at 19:47

1 Answer 1

4

Just to warn you, I minimally tested this, but it seems to work.

In the Due's linker script there are these relevant lines, containing the named rom section.

MEMORY
{
    rom (rx)    : ORIGIN = 0x00080000, LENGTH = 0x00080000 /* Flash, 512K */

Below you'll see:

SECTIONS
{
    .text :
    {
        . = ALIGN(4);
        _sfixed = .;

_sfixed is a symbol they've created to mark the current location, which happens to be the start of the rom section in this case, 0x00080000. At the bettom of the brace matching the one following .text, you'll see:

} > rom

showing that section goes into ROM, which is why . at the beginning of the .text section refers to the rom section start address.

If you look near the last thing placed in rom, you'll see:

    . = ALIGN(4);
    _etext = .;

So, they've set another symbol there, the one of interest, _etext, the address of which should be the byte of ROM not used, or so I thought the first time through this; turns out it isn't. I gather you have no use for start of ROM symbol, since it's a static value and at the beginning, so I'll ignore that, but if you cared to make use of it it could be done the same way as follows for _etext.

These symbols don't have any type, but you can make them whatever you want. If you want to perform arithmetic on a pointer that results, it makes sense to use a char type, so:

extern "C" unsigned char _etext;

I thought this was the end of the story, could just take the address of this symbol, but there is more to it. Following the regular code is this section:

    .relocate : AT (_etext)
    {
        . = ALIGN(4);
        _srelocate = .;
        *(.ramfunc .ramfunc.*);
        *(.data .data.*);
        . = ALIGN(4);
        _erelocate = .;
    } > ram

They're placing code for the banzai (reset) function (and perhaps other things), which goes into ram ultimately, at this location in ROM. So, presumably in the start code this is copied from the end of the normal program code into some place in ram.

So, there are two more symbols of interest here, _srelocate and _erelocate. Declarations for them:

extern "C" unsigned char _srelocate;
extern "C" unsigned char _erelocate;

These symbols resolve to addresses in RAM, so they can't be used directly, but can have their addresses subtracted to get the size of the image in ROM that gets relocated to RAM, leading to:

const unsigned char *rom_end = &_etext + (&_erelocate - &_srelocate); 
Serial.print("The end of used ROM is : 0x");
Serial.println((uintptr_t)rom_end, HEX);

When I compile and run a simple test like this and check the serial monitor connection, I see output like:

The end of used ROM is : 0x82FF8

If look at the .bin file produced by the compiler:

$ wc --bytes  build/arduino.sam.arduino_due_x_dbg/efixed.ino.bin 
12280 build/arduino.sam.arduino_due_x_dbg/efixed.ino.bin

And 0x82FF8 - 0x80000 == 12280.

When I hex dump flash around this address I can see what looks like it padding out the flash sector with 0x00 bytes before the next unprogrammed flash sector shows as 0xFF. So you would need to round this up yourself, and if you want be somewhat safer, maybe add some margin.

I don't know how common that configuration in the linker script is. It's likely that most linker scripts place a symbol there, if they've had some forethought. But I wouldn't necessarily expect it have the same name. It's possible they modify it to add more stuff after that symbol. But for now, for the Due, that would seem to work fine. It is a bit messy though.

4
  • Wow, thanks for the concise analysis. Exactly what I was looking for. Seems to work nicely.
    – PMF
    May 1, 2021 at 8:02
  • This is specific to the Arduino Due anyway (and it doesn't look like it's toolchain is going to be updated any time soon), so that's fine. If I port this code to another CPU (i.e. ESP32), I need to replace the whole flash part anyway.
    – PMF
    May 1, 2021 at 8:04
  • Good deal. I had hoped to make the methodology fairly clear to allow future readers to react to changes in the linker script, or to apply the same thinking for a different Arduino, rather than offering a "just do this" type of answer. Then again, it may also get ignored until heat death, but I thought it was an interesting question to look at. =)
    – timemage
    May 1, 2021 at 17:48
  • Yea, I was just referring to the fact that github.com/arduino/ArduinoCore-sam is quite dead. Nobody seems to care about PRs or issues.
    – PMF
    May 1, 2021 at 18:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.