【问题标题】:Reading from flash that's not part of the application从不属于应用程序的闪存读取
【发布时间】:2020-05-30 08:29:28
【问题描述】:

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

我怎样才能最好地访问闪存中的这些数据?

我对 nice 的定义是(按优先顺序): - 支持 (u)int32_t、(u)int8_t、bool、char[固定大小] - 与#define PARAM (1) 或 constexpr 相比,开销很小 - 类型安全用法(即 uint8_t var = CONFIG_CHAR_ARRAY 应至少发出警告) - 没有 RAM 副本 - 调试时配置参数的可读性(使用 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 风格,并且在调试时没有帮助。我很难找到使用链接器脚本的另一个选项,因为我找不到不让变量最终出现在应用程序的二进制文件中的方法。

有没有更好的方法来做到这一点?

【问题讨论】:

  • 您有多少个“选项”或“配置”?它可以放入(打包)结构中吗?那么如何创建一个指向这样一个结构的指针,并让它指向存储配置的内存呢?
  • 配置页面中有 2048 个字节可用,目前几乎没有使用大约 20 个字节。不过,这将在项目生命周期内增加。我也在考虑结构,但害怕填充问题,可以通过打包来解决。不过,这将需要大量保留条目。不过,这可能是一个解决方案。
  • 你试过const bool&amp; CONF_PARAM1 = *(bool*)(CONFIG_ADDRESS + 0x0083);
  • 不,我直到现在才这样做。当我将定义直接放在 if 之上时,它就像一个魅力,与 #define 相同。如果我在我的 config.hpp 中 extern 并在我的 config.cpp 中定义它(test-if 在 main.cpp 中),它似乎仍然有效,但我很难理解为什么。它的汇编程序对于这个答案来说太大了:-(
  • 所以你知道这个闪存中的东西在哪里,或者你不知道链接时另一个闪存中的东西在哪里?只需像普通全局变量一样声明它们并将它们链接到此地址空间,但不要将其加载到闪存中(将它们全部放在它们自己的对象中并将该对象分配给该地址空间)。您只需将它们声明为 unsigned int x、unsigned int y。现在这与在编译域中使用结构有相同的问题,必须小心。但易于编码和阅读干净。

标签: c++ arm embedded cortex-m bare-metal


【解决方案1】:

如果您将常量设为引用,编译器不会将其复制到变量中,它可能只是将地址加载到变量中。然后,您可以将引用的生成包装到模板函数中,以使您的代码更清晰:

#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

【讨论】:

  • 我在auto&amp; 行中添加了static,这样我就可以在我的config.hpp 中有定义,而不会出现多个定义编译器错误。除此之外,这看起来很有希望,也非常好。谢谢!
  • 对不起,只有当定义是相同的范围时才会优化,只要我把 CONF_PARAM1 的定义放在 main() 之外(如你的例子),它的优化程度较低,变量是放入内存。有什么方法可以在单独的文件中对定义进行如此出色的优化,以便可以从我的所有源代码中访问此配置参数?
  • 您可以尝试将偏移量放入常量中,然后在需要时直接调用configOption,应该没有什么区别,取决于您的编译器和优化器设置
【解决方案2】:

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

在链接器文件中,我放了

    CONF_PARAM = _config_start + 0x0083;

在我的 config.hpp 中,我放了

    extern const bool CONF_PARAM;

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

    if (CONF_PARAM)

据我所知,这基本上满足了我的所有“好”定义。

【讨论】:

  • 你可以很肯定地得到一个错位的访问。不需要幻数和硬编码地址,而是让链接器来处理。
【解决方案3】:

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

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

    如果此部分与可执行二进制文件一起下载,或者仅通过引导加载程序下载,则由您来定义。链接描述文件通常带有“no init”选项。

所以你可以这样做:

// 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,你甚至可以用联合分割每个 uint32_t 块,例如 typedef union { uint32_t u32; uint8_t u8 [4]; } 和类似的,但这在 C++ 中是不可能的,因为它不允许联合类型双关语。

【讨论】:

    【解决方案4】:

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

    测试理论

    向量.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
    }
    

    构建

    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
    

    好多了。

    我强烈建议不要使用跨编译域的结构,这属于该类别,因为真实数据的构建是分开的,并且在代码构建和数据构建之间,当我这样做时,您可能会得到不同的数据像这样的事情,我在执行过程中采取了保护措施,以便在问题出轨之前(或者在构建时更好)。这不是如果它是何时的情况。实现定义意味着实现定义。

    但是考虑到您的问题,这成为一个简单的解决方案。是的,从技术上讲,这些数据是只读的,const 这个或那个,但是 1) volatile 和 const 是否在一起? 2) 你真的想要/需要这样做吗?

    它甚至需要是易失性的吗?可能不是,刚开始就敲了。将其切换为 const 工具会将它们放入 .rodata 中。好吧,我的工具确实取决于您如何编写链接器脚本,我认为是 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
    }
    

    输出

    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
    

    【讨论】:

      猜你喜欢
      • 2023-03-14
      • 1970-01-01
      • 2014-01-07
      • 1970-01-01
      • 2021-11-23
      • 1970-01-01
      • 1970-01-01
      • 2017-04-16
      • 1970-01-01
      相关资源
      最近更新 更多