嵌入式 C++11 代码 — 我需要易失性代码吗?

Embedded C++11 code — do I need volatile?

本文关键字:代码 易失性 C++11 嵌入式      更新时间:2023-10-16

内置Cortex M3 MCU(STM32F1)。它有嵌入式闪存(64K)。MCU固件可以在运行时重新编程闪存扇区;这是由闪存控制器(FMC)寄存器完成的(所以它不像a=b那么容易)。FMC获取缓冲区指针并将数据刻录到某个闪存扇区。

我想使用最后一个闪存扇区作为设备配置参数。形参存储在带有数组的打包结构体中,并包含一些自定义类。

参数可以在运行时更改(复制到RAM,使用FMC更改并刻录回闪存)。

这里有一些问题:

  1. 参数结构的状态(按位)由FMC硬件改变。c++编译器不知道它是否被修改过。这是否意味着我应该将所有结构体成员声明为volatile?

  2. Struct应该在编译时静态初始化(默认参数)。结构应该是POD (TriviallyCopyable并具有标准布局)。记住,这里有一些自定义类,所以我要记住这些类也应该是POD。但也存在一些问题:cppreference.com

    唯一普通可复制的类型是标量类型,普通可复制类,以及此类类型/类的数组(可能是const限定的,但不包含易失性限定)。

这意味着我不能保持我的类POD和挥发性?那么我该如何解决这个问题呢?

可以在参数结构中只使用标量类型,但这可能会导致配置处理周围的代码不那么干净…

注:即使没有volatile,它也可以工作,但我担心有一天,一些智能LTO编译器会看到静态初始化,不改变(通过c++)结构,并优化出对底层内存地址的一些访问。这意味着新的编程参数将不会被应用,因为它们是由编译器内联的。

EDIT:不使用volatile也可以解决这个问题。而且它似乎更正确。

您需要在单独的翻译单元(.cpp文件)中定义配置结构变量,并且不初始化变量以避免在LTO期间进行值替换。如果不使用LTO -一切都可以,因为优化是一次在一个翻译单元中完成的,因此在专用翻译单元中定义的具有静态存储持续时间和外部链接的变量不应该被优化出来。只有LTO可以丢弃它或在不发出内存提取的情况下进行值替换。特别是在将变量定义为const时。我认为如果不使用LTO,可以初始化变量

你有一些选择取决于你的编译器:

    你可以声明一个指向结构体的指针,并初始化该指针
  • 告诉编译器变量应该驻留在哪里
指向Flash的指针

声明结构体的指针。
在Flash中为指针指定正确的地址。
通过对指针解引用来访问变量。
该指针应声明并赋值为指向常量数据的常量指针。

告诉编译器变量的地址。

一些编译器允许您将变量放在特定的内存区域中。第一步是在链接器命令文件中创建一个区域。下一步是告诉编译器该变量在该区域中。

同样,变量应该声明为"static const"。之所以说"静态",是因为只有一个实例。之所以使用"const"是因为闪存在大部分时间都是只读的。

Flash Memory: Volatile vs. Const

在大多数情况下,闪存,无论如何编程,是只读的。实际上,在Flash中读取数据的唯一方法是将其锁定,也就是使其只读。一般情况下,没有程序的同意是不会改变的。

大多数闪存是由软件编程的。通常,这是您的程序。如果你的程序要重新编程Flash,它知道值已经改变了。这类似于写入RAM。程序更改了值,而不是硬件。因此Flash是而不是易失性的。

我的经验是,Flash可以通过另一种方式编程,通常当你的程序没有运行。在这种情况下,它仍然不是易失性的,因为您的程序没有运行。Flash仍然是只读的。

Flash将是易失性的,当且仅当,另一个任务或执行线程程序的Flash,而你的执行线程是活动的。我仍然不认为这种情况是易失性。这是同步性中的一种情况——如果flash被修改,那么应该通知一些侦听器。

总结

Flash最好作为只读存储器处理。虽然一些编译器和链接器允许你在特定的硬编码地址声明变量,但是Flash中的变量是通过指针访问的,这是为了获得最佳的可移植性。变量应该声明为const static,这样编译器就可以发出代码直接访问变量,而不是在堆栈上复制。如果Flash是由另一个任务或执行线程编程的,这是一个同步性问题,而不是一个volatile。在极少数情况下,Flash是由外部源编程的,而您的程序正在执行。

您的程序应该提供校验和或其他方法来确定自上次检查以来内容是否发生了更改。

不允许编译器从FLASH中初始化变量
这不是便携式的。更好的方法是让初始化代码从flash中加载变量。让编译器从不同的段加载变量需要大量的编译器和链接器的内部工作;比在Flash中初始化指向地址的指针要复杂得多。

通过重新编程flash,您正在改变底层对象的表示。volatile限定符是适合的解决方案情况,以确保数据的更改不会被优化掉。

您希望声明为:const volatile Settings settings;

缺点是volatile阻止对象的静态初始化。这将阻止您使用链接器将初始化的对象放入其适当的内存地址。

您希望定义为:const Settings settings = { ... };

幸运的是,您可以初始化const对象并将其作为const volatile访问。


// Header file
struct Settings { ... };
extern const volatile Settings& settings;

// Source file
static const Settings init_settings = { ... };
const volatile Settings& settings = init_settings;

init_settings对象是静态初始化的,但所有通过settings引用的访问都被视为易失性。

请注意,修改定义为const的对象是未定义的行为。