从不属于应用程序的闪存读取
Reading from flash that's not part of the application
我正在编程裸机嵌入式,所以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++)
使用指针强制转换定义
#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
常量变量
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
- phytec phyBOARD iMX-6在从闪存而不是SD卡运行qt5 opengles应用程序时表现不佳(FPS减半
- 与SPI NAND闪存(STM32L4,QSPI)的通信问题
- 从不属于应用程序的闪存读取
- LittleFS文件系统,NOR闪存的文件写入问题
- STM32使用HAL库的外部闪存读取设备ID
- STM32F4 - 在运行时将函数从闪存复制到RAM
- 如何将来自闪存中不同银行的两个内存区域交换为STM32L475
- 为什么在STM32上未清除闪存页面
- 如何在看门狗执行前将日志复制到闪存?
- 一种避免pgm_read访问闪存的方法(avr 微控制器)
- g++arm none eabi从4.9升级到gcc 8.2.生成的二进制文件不再适合闪存
- 如何检查USB插槽中是否存在空闪存卡读卡器?
- 获取闪存驱动器的读写速度
- c/c++:如何知道已用闪存的大小?
- 如何以编程方式使用 MBR 和 USB 闪存扇区
- C++/C 实时闪存流处理
- 在闪存驱动器上为 Windows C++ Code blocks设置 OpenGL
- 如何限制 MFC 应用程序仅从已知的 USB 闪存驱动器运行
- C++创建文件(紧凑型闪存卡写入器)
- 使用libusb从USB闪存驱动器读取数据