Lasse
Lasse

Reputation: 1

Reading from flash that's not part of the application

I'm programming bare-metal embedded, so no OS etc. on a STM32L4 (ARM Cortex M4). I have a separate page in flash, which is written by a bootloader (it is not and should not be part of my application binary, this is a must). In this page, I store configuration parameters that will be used in my application. This configuration page may change, but not during runtime, after a change I reset the processor.

How can I access this data in flash most nicely?

My definition of nice is (in this order of priority): - support for (u)int32_t, (u)int8_t, bool, char[fixed-size] - little overhead when compared to #define PARAM (1) or constexpr - typesafe usage (i.e. uint8_t var = CONFIG_CHAR_ARRAY shall issue atleast a warning) - no RAM copy - readability of the configuration parameters while debugging (using STM32CubeIDE)

The solution shall scale for all possible 2048 bytes of the flashpage. Code generation is anyhow part of the process.

So far, I have tested two variants (I am coming from plain C but am using (potentially modern) C++ in this project). My current testcase is

    if (param) function_call();

but it should also work for other cases such as

    for(int i = 0; i < param2; i++)
  1. define with pointer cast

    #define CONF_PARAM1 (*(bool*)(CONFIG_ADDRESS + 0x0083))
    

    Which leads to (using -Os):

    8008872:    4b1b        ldr r3, [pc, #108]  ; (80088e0 <_Z16main_applicationv+0xac>)
    8008874:    781b        ldrb    r3, [r3, #0]
    8008876:    b10b        cbz r3, 800887c <_Z16main_applicationv+0x48>
    8008878:    f7ff ff56   bl  8008728 <_Z10function_callv>
    
    80088e0:    0801f883    .word   0x0801f883
    
  2. const variable

    const bool CONF_PARAM1 = *(bool*)(CONFIG_ADDRESS + 0x0083);
    

    leading to

    800887c:    4b19        ldr r3, [pc, #100]  ; (80088e4 <_Z16main_applicationv+0xb0>)
    800887e:    781b        ldrb    r3, [r3, #0]
    8008880:    b10b        cbz r3, 8008886 <_Z16main_applicationv+0x52>
    8008882:    f000 f899   bl  8008728 <_Z10function_callv>
    
    80088e4:    200000c0    .word   0x200000c0
    

I dislike option 2, as it adds a RAM copy (would not scale well for 2048 bytes of config), option 1 looks like very old c style and does not help while debugging. I struggle to find another option using the linker script, as I do not find a way to not end up with the variable being in the application's binary.

Is there any better way of doing this?

Upvotes: 0

Views: 508

Answers (4)

old_timer
old_timer

Reputation: 71536

You can isolate the variables in question their own section. There is more than one way to do that. The tools build normally and do all the addressing work. Like using structs across compile domains you need to be extremely careful and probably put checks into the code, but you can build the binary and only load it or all but the other flash contents, then at that time or later you can change the VALUES of the variables in the other section and build and isolate those into their own load.

Testing the theory

vectors.s

.globl _start
_start:
.word 0x20001000
.word reset


.thumb_func
reset:
    bl main
    b .

.globl dummy
.thumb_func
dummy:
    bx lr

so.c

extern volatile unsigned int x;
extern volatile unsigned short y;
extern volatile unsigned char z[7];
extern void dummy ( unsigned int );
int main ( void )
{
    dummy(x);
    dummy(y);
    dummy(z[0]<<z[1]);
    return(0);
}

flashvars.c

volatile unsigned int x=1;
volatile unsigned short y=3;
volatile unsigned char z[7]={1,2,3,4,5,6,7};

flash.ld

MEMORY
{
    rom0 : ORIGIN = 0x08000000, LENGTH = 0x1000
    rom1 : ORIGIN = 0x08002000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > rom0
    .vars : { flashvars.o } > rom1
}

build

arm-none-eabi-as --warn --fatal-warnings -mcpu=cortex-m0 vectors.s -o vectors.o
arm-none-eabi-ld -nostdlib -nostartfiles -T flash.ld vectors.o so.o flashvars.o -o so.elf
arm-none-eabi-objdump -D so.elf > so.list
arm-none-eabi-objcopy -R .vars -O binary so.elf so.bin

examine

Disassembly of section .text:

08000000 <_start>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000009    stmdaeq r0, {r0, r3}

08000008 <reset>:
 8000008:   f000 f802   bl  8000010 <main>
 800000c:   e7fe        b.n 800000c <reset+0x4>

0800000e <dummy>:
 800000e:   4770        bx  lr

08000010 <main>:
 8000010:   4b08        ldr r3, [pc, #32]   ; (8000034 <main+0x24>)
 8000012:   b510        push    {r4, lr}
 8000014:   6818        ldr r0, [r3, #0]
 8000016:   f7ff fffa   bl  800000e <dummy>
 800001a:   4b07        ldr r3, [pc, #28]   ; (8000038 <main+0x28>)
 800001c:   8818        ldrh    r0, [r3, #0]
 800001e:   b280        uxth    r0, r0
 8000020:   f7ff fff5   bl  800000e <dummy>
 8000024:   4b05        ldr r3, [pc, #20]   ; (800003c <main+0x2c>)
 8000026:   7818        ldrb    r0, [r3, #0]
 8000028:   785b        ldrb    r3, [r3, #1]
 800002a:   4098        lsls    r0, r3
 800002c:   f7ff ffef   bl  800000e <dummy>
 8000030:   2000        movs    r0, #0
 8000032:   bd10        pop {r4, pc}
 8000034:   0800200c    stmdaeq r0, {r2, r3, sp}
 8000038:   08002008    stmdaeq r0, {r3, sp}
 800003c:   08002000    stmdaeq r0, {sp}

Disassembly of section .vars:

08002000 <z>:
 8002000:   04030201    streq   r0, [r3], #-513 ; 0xfffffdff
 8002004:   00070605    andeq   r0, r7, r5, lsl #12

08002008 <y>:
 8002008:   00000003    andeq   r0, r0, r3

0800200c <x>:
 800200c:   00000001    andeq   r0, r0, r1

that looks good

hexdump -C so.bin
00000000  00 10 00 20 09 00 00 08  00 f0 02 f8 fe e7 70 47  |... ..........pG|
00000010  08 4b 10 b5 18 68 ff f7  fa ff 07 4b 18 88 80 b2  |.K...h.....K....|
00000020  ff f7 f5 ff 05 4b 18 78  5b 78 98 40 ff f7 ef ff  |.....K.x[x.@....|
00000030  00 20 10 bd 0c 20 00 08  08 20 00 08 00 20 00 08  |. ... ... ... ..|
00000040

as does that.

arm-none-eabi-objcopy -j .vars -O binary so.elf sovars.bin

hexdump -C sovars.bin 
00000000  01 02 03 04 05 06 07 00  03 00 00 00 01 00 00 00  |................|
00000010  47 43 43 3a 20 28 47 4e  55 29 20 39 2e 33 2e 30  |GCC: (GNU) 9.3.0|
00000020  00 41 30 00 00 00 61 65  61 62 69 00 01 26 00 00  |.A0...aeabi..&..|
00000030  00 05 43 6f 72 74 65 78  2d 4d 30 00 06 0c 07 4d  |..Cortex-M0....M|
00000040  09 01 12 04 14 01 15 01  17 03 18 01 19 01 1a 01  |................|
00000050  1e 02                                             |..|
00000052

hah, okay a little more work.

MEMORY
{
    rom0 : ORIGIN = 0x08000000, LENGTH = 0x1000
    rom1 : ORIGIN = 0x08002000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > rom0
    .vars : { flashvars.o(.data) } > rom1
}

hexdump -C sovars.bin 
00000000  01 02 03 04 05 06 07 00  03 00 00 00 01 00 00 00  |................|
00000010

much better.

I strongly recommend against structs across compile domains and this falls into that category as the build for the real data is separate and between the code build and the data build you could get data that doesn't land the same, when I do things like this I put in protections to catch the problem during execution before it goes off the rails (or better at build time). It is not a case of if it is a case of when. Implementation defined means implementation defined.

But thinking about your question this became an easy solution. And yes technically this data is read only, const this or that, but 1) does volatile and const go together? and 2) do you really want/need to do that?

Does it even need to be volatile? Probably not, just banged that out to start with. Switching it to const the tool puts them in .rodata. Well my tool does depends on how you write your linker script and I think the version of binutils.

so.c

extern const unsigned int x;
extern const unsigned short y;
extern const unsigned char z[7];
extern void dummy ( unsigned int );
int main ( void )
{
    dummy(x);
    dummy(y);
    dummy(z[0]<<z[1]);
    return(0);
}

flashvars.c

const unsigned int x=1;
const unsigned short y=3;
const unsigned char z[7]={1,2,3,4,5,6,7};

flash.ld

MEMORY
{
    rom0 : ORIGIN = 0x08000000, LENGTH = 0x1000
    rom1 : ORIGIN = 0x08002000, LENGTH = 0x1000
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
    .text : { *(.text*) } > rom0
    .vars : { flashvars.o(.rodata) } > rom1
}

output

Disassembly of section .text:

08000000 <_start>:
 8000000:   20001000    andcs   r1, r0, r0
 8000004:   08000009    stmdaeq r0, {r0, r3}

08000008 <reset>:
 8000008:   f000 f802   bl  8000010 <main>
 800000c:   e7fe        b.n 800000c <reset+0x4>

0800000e <dummy>:
 800000e:   4770        bx  lr

08000010 <main>:
 8000010:   4b08        ldr r3, [pc, #32]   ; (8000034 <main+0x24>)
 8000012:   b510        push    {r4, lr}
 8000014:   6818        ldr r0, [r3, #0]
 8000016:   f7ff fffa   bl  800000e <dummy>
 800001a:   4b07        ldr r3, [pc, #28]   ; (8000038 <main+0x28>)
 800001c:   8818        ldrh    r0, [r3, #0]
 800001e:   f7ff fff6   bl  800000e <dummy>
 8000022:   4b06        ldr r3, [pc, #24]   ; (800003c <main+0x2c>)
 8000024:   7818        ldrb    r0, [r3, #0]
 8000026:   785b        ldrb    r3, [r3, #1]
 8000028:   4098        lsls    r0, r3
 800002a:   f7ff fff0   bl  800000e <dummy>
 800002e:   2000        movs    r0, #0
 8000030:   bd10        pop {r4, pc}
 8000032:   46c0        nop         ; (mov r8, r8)
 8000034:   0800200c    stmdaeq r0, {r2, r3, sp}
 8000038:   08002008    stmdaeq r0, {r3, sp}
 800003c:   08002000    stmdaeq r0, {sp}

Disassembly of section .vars:

08002000 <z>:
 8002000:   04030201    streq   r0, [r3], #-513 ; 0xfffffdff
 8002004:   00070605    andeq   r0, r7, r5, lsl #12

08002008 <y>:
 8002008:   00000003    andeq   r0, r0, r3

0800200c <x>:
 800200c:   00000001    andeq   r0, r0, r1

hexdump -C so.bin
00000000  00 10 00 20 09 00 00 08  00 f0 02 f8 fe e7 70 47  |... ..........pG|
00000010  08 4b 10 b5 18 68 ff f7  fa ff 07 4b 18 88 ff f7  |.K...h.....K....|
00000020  f6 ff 06 4b 18 78 5b 78  98 40 ff f7 f0 ff 00 20  |...K.x[x.@..... |
00000030  10 bd c0 46 0c 20 00 08  08 20 00 08 00 20 00 08  |...F. ... ... ..|
00000040

hexdump -C sovars.bin 
00000000  01 02 03 04 05 06 07 00  03 00 00 00 01 00 00 00  |................|
00000010

Upvotes: 0

Lundin
Lundin

Reputation: 213960

No need to re-invent the wheel - placing data in flash is a fairly common use-case in embedded systems. When dealing with such data flash, there are some important considerations:

  • All data must sit at the very same address, with the same type, from case to case. This means that struct is problematic because of padding (and even more so class). If you align all data on 32 bit boundaries, this shouldn't be a problem, so I strongly recommend that you do so. Then the program becomes portable between compilers.
  • All these variables and pointers to them must be declared with volatile qualifier, otherwise the optimizer might go haywire. Things like (*(bool*)(CONFIG_ADDRESS + 0x0083)) are brittle and might break at any point, unless you add volatile.
  • You can place data at a fixed location in memory, but how to do so is compiler/linker-specific. And since it isn't standardized, it's always a pain to get right. With gcc-flavoured compilers it might be something like: __attribute__(section(".dataflash")) where .dataflash is your custom segment that you must reserve space for in the linker script. You'll need to take a closer look at how to do this with your specific toolchain (others use #pragmas etc instead), I'll use the __attribute__ here just to illustrate.

    If this section gets downloaded together with the executable binary, or only through bootloader, is up to you to define. Linker scripts typically come with a "no init" option.

So you could do something like:

// flashdata.h

typedef struct
{
  uint32_t stuff;
  uint32_t more_stuff;
  ...
} flashdata_t;

extern volatile const flashdata_t flash_data __attribute__(section(".dataflash"));

And then declare it as:

// flashdata.c

volatile const flashdata_t flash_data __attribute__(section(".dataflash"));

And now you can use it as any struct, flash_data.stuff.

If you are using C, you can even split up each uint32_t chunk with union, such as typedef union { uint32_t u32; uint8_t u8 [4]; } and similar, but that isn't possible in C++, because it doesn't allow union type punning.

Upvotes: 0

Lasse
Lasse

Reputation: 1

As proposed by @old_timer in a comment above, I favour this solution:

In the linker file, I put

    CONF_PARAM = _config_start + 0x0083;

In my config.hpp, I put

    extern const bool CONF_PARAM;

which then can easily be accessed in any source file

    if (CONF_PARAM)

This basically fulfills all "nice"-definitions of mine, as far as I can see.

Upvotes: 0

Alan Birtles
Alan Birtles

Reputation: 36399

If you make your constant a reference the compiler wont copy it into a variable, it will probably just load the address into a variable. You can then wrap the generation of the references into a templated function to make your code cleaner:

#include <cstdint>
#include <iostream>

template <typename T>
const T& configOption(uintptr_t offset)
{
    const uintptr_t CONFIG_ADDRESS = 0x1000;
    return *reinterpret_cast<T*>(CONFIG_ADDRESS + offset);
}

auto& CONF_PARAM1 = configOption< bool >(0x0083);
auto& CONF_PARAM2 = configOption< int >(0x0087);

int main()
{
    std::cout << CONF_PARAM1 << ", " << CONF_PARAM2 << "\n";
}

GCC optimises this fairly well: https://godbolt.org/z/r27o5Q

Upvotes: 1

Related Questions