Reputation: 1
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++)
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
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
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
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:
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.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
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
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