从不属于应用程序的闪存读取

Reading from flash that's not part of the application

本文关键字:闪存 读取 应用程序 不属于      更新时间:2023-10-16

我正在编程裸机嵌入式,所以STM32L4(ARM Cortex M4)上没有操作系统等。我在 flash 中有一个单独的页面,它是由引导加载程序编写的(它不是也不应该是我的应用程序二进制文件的一部分,这是必须的)。在此页中,我存储将在应用程序中使用的配置参数。此配置页面可能会更改,但不会在运行时更改,更改后我重置处理器。

如何在闪存中最好地访问这些数据?

我对nice的定义是(按优先级顺序): - 支持(U)int32_t,(U)int8_t,布尔值,字符[固定大小] - 与 #define PARAM (1) 或 constexpr 相比开销很小 - 类型安全用法(即uint8_t var = CONFIG_CHAR_ARRAY应至少发出警告) - 没有内存副本 - 调试时配置参数的可读性(使用STM32CubeIDE)

该解决方案应针对所有可能的 2048 字节的闪页进行扩展。无论如何,代码生成是该过程的一部分。

到目前为止,我已经测试了两个变体(我来自普通 C,但在此项目中使用(可能是现代的)C++)。我目前的测试用例是

if (param) function_call();

但它也应该适用于其他情况,例如

for(int i = 0; i < param2; i++)
  1. 使用指针强制转换定义

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

    这导致(使用 -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 bool CONF_PARAM1 = *(bool*)(CONFIG_ADDRESS + 0x0083);
    

    导致

    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
    

我不喜欢选项 2,因为它添加了一个 RAM 副本(对于 2048 字节的配置来说不能很好地扩展),选项 1 看起来像非常旧的 c 样式,在调试时没有帮助。我很难使用链接器脚本找到另一个选项,因为我找不到一种方法来不最终使变量位于应用程序的二进制文件中。

有没有更好的方法?

如果你把你的常量作为一个引用,编译器不会把它复制到变量中,它可能只是将地址加载到变量中。然后,可以将引用的生成包装到模板化函数中,以使代码更简洁:

#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 对此进行了很好的优化:https://godbolt.org/z/r27o5Q

正如@old_timer在上面的评论中提出的那样,我赞成这个解决方案:

在链接器文件中,我把

CONF_PARAM = _config_start + 0x0083;

在我的配置.hpp中,我把

extern const bool CONF_PARAM;

然后可以在任何源文件中轻松访问

if (CONF_PARAM)

据我所知,这基本上满足了我所有"不错"的定义。

无需重新发明轮子 - 将数据放入闪存是嵌入式系统中相当常见的用例。在处理此类数据闪存时,有一些重要的考虑因素:

  • 所有数据必须位于同一地址,具有相同的类型,从案例到案例。这意味着struct由于填充而存在问题(class更是如此)。如果你在 32 位边界上对齐所有数据,这应该不是问题,所以我强烈建议你这样做。然后程序在编译器之间变得可移植。
  • 所有这些变量和指向它们的指针都必须使用限定符声明volatile否则优化器可能会失控。像(*(bool*)(CONFIG_ADDRESS + 0x0083))这样的东西很脆,随时可能破裂,除非你添加volatile
  • 您可以将数据放置在内存中的固定位置,但如何执行此操作取决于编译器/链接器。而且由于它没有标准化,所以做对总是很痛苦。对于 gcc 风格的编译器,它可能是这样的:__attribute__(section(".dataflash"))其中.dataflash是必须在链接器脚本中为其保留空间的自定义段。您需要仔细研究如何使用您的特定工具链(其他人使用 #pragmas 等)来执行此操作,我将在这里使用__attribute__来说明。

    如果此部分与可执行二进制文件一起下载,或者仅通过引导加载程序下载,则由您定义。链接器脚本通常带有"无初始化"选项。

因此,您可以执行以下操作:

// flashdata.h
typedef struct
{
uint32_t stuff;
uint32_t more_stuff;
...
} flashdata_t;
extern volatile const flashdata_t flash_data __attribute__(section(".dataflash"));

然后将其声明为:

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

现在您可以将其用作任何结构,flash_data.stuff.

如果您使用的是 C,您甚至可以使用联合(例如typedef union { uint32_t u32; uint8_t u8 [4]; }和类似)拆分每个uint32_t块,但这在C++中是不可能的,因为它不允许联合类型双关语。

您可以将有问题的变量隔离在自己的部分。 有不止一种方法可以做到这一点。 这些工具正常构建并执行所有寻址工作。 就像跨编译域使用结构一样,您需要非常小心,并且可能会在代码中进行检查,但是您可以构建二进制文件并仅加载它或除其他闪存内容之外的所有内容,然后在那时或以后,您可以更改其他部分中变量的 VALUES 并构建并隔离它们到它们自己的加载中。

检验理论

向量.s

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

.thumb_func
reset:
bl main
b .
.globl dummy
.thumb_func
dummy:
bx lr

所以.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};

闪存

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

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

检查

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

看起来不错

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

也是如此。

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

呵呵,好吧,再做一点工作。

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

好多了。

我强烈建议不要跨编译域的结构,这属于该类别,因为真实数据的构建是独立的,并且在代码构建和数据构建之间,您可能会获得不同的数据,当我做这样的事情时,我会在执行过程中采取保护措施以在问题脱轨之前捕获问题(或在构建时更好)。 这不是一个是否是什么时候的情况。 实现定义意味着实现定义。

但是考虑到你的问题,这变成了一个简单的解决方案。 是的,从技术上讲,这些数据是只读的,常量这个或那个,但是 1) 易失性和常量会一起吗? 2)你真的想要/需要这样做吗?

它甚至需要不稳定吗? 可能不是,只是一开始就把它敲出来了。将其切换为const工具将它们放入.rodata中。 好吧,我的工具取决于您如何编写链接器脚本,我认为 binutils 的版本。

所以.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};

闪存

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
}

输出

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