针对仅闪存器件的 EEPROM 仿真-解决方案
编辑:宝星微科技 | 发布时间:2020-06-11 10:15 | 浏览次数:250
tr}1{/attr}>简介
从 8 位 MCU 移植到 32 位 MCU 时,最大的障碍之一是大多数 32 位 MCU 中缺少真正的 EEPROM 存储器。一些软件 仿真层可解决该问题。不过,尽管这些仿真层可提供 EEPROM 接口的无缝仿真,但都存在明显的缺点(例如底层闪存 耗损过度),并且如果在写入过程中断电,可能会导致数据完全丢失。 本文档介绍了一种更好的方法。这种方法只需对应用流程进行少量更改,但却可以实现可靠的 EEPROM 仿真,并且 MCU 闪存的耗损最小。
问题 鉴于大多数现代 32 位 MCU 都缺少 EEPROM,因此惟一可用于存储变量数据的位置是闪存。 闪存的缺点是无法按照 EEPROM 存储器的通用方法以单个字节方式进行擦除或写入。闪存只能以较大的块方式进行擦 除和写入。典型的擦除块大小可以是 256 至 8192 字节,典型的写入块大小是 64 至 512 字节。一些闪存支持部分写 入,在这种模式下,只要有位从“1”(擦除状态)变为“0”(编程状态),擦除的块就可以多次写入。即使闪存支 持部分写入,通常也会有其他限制。在一些情况下,在必要的擦除之前可以执行的部分写入次数受到限制。在另一些 情况下,写入操作必须同时满足特定的对齐和大小要求。 此外,分配给数据存储的闪存通常与应用程序代码位于同一闪存阵列中。这导致在写入数据时需要暂停应用程序的执 行。借助本文建议的实现方式,用户可以定义将要出现该块的点。另一方面,MCU 制造商也意识到了这个问题,因此 许多现代 MCU 中都有一个单独的数据闪存段可用于实现该用途。在某些情况下,主闪存阵列拆分为多个独立的存储 区,可以对这些存储区执行写入操作,而其他存储区则可用于读取和代码执行。
2. 解决方案 建议的解决方案是将所有变量与几个服务成员一起长期存储在一个结构中。该结构可以映射到闪存中的地址,也可以 复制到 SRAM 中,以加快访问和支持累积更新。 以下代码给出了数据布局的典型结构。第一部分所示为原始结构,第二部分则是除原始版本外的两次版本更新后的布局。
---------------------------------------
typedef struct
{
uint32_t counter;
uint8_t version;
// 用户值
uint32_t crc;
} EepromLayout_v1;
typedef struct
{
uint32_t counter;
uint8_t version;
// 版本 1 用户值
uint32_t crc1;
// 版本 2 用户值
uint32_t crc2;
// 版本 3 用户值
uint32_t crc3;
} EepromLayout_v3;
---------------------------------------
版本字段用于标识结构的版本。对于没有固件更新、实现固定布局的应用,可忽略版本字段。
计数器字段是单调递增的计数器。最高计数器值与 CRC 字段一起标识结构的最新有效版本。 实际上,32 位值永远不
会溢出,因为计数器仅在写入时递增,并且闪存耐写入次数受到限制。
CRC 计算结构的大小可以根据版本字段推断出来,也可以在结构的特定部分中明确定义。
这种方法的稳健性得益于为结构的多个副本分配存储空间。如果闪存支持部分写入,则可以将多个副本分配到同一擦
除块中,但必须注意至少分配两个擦除块。同时必须至少分配两个副本。这将确保在必须擦除其中一个块时,电源中
断不会导致数据损坏。
分配多个副本的额外优势是无需增加成本即可实现耗损均衡。
为每个副本分配的数据大小必须足以覆盖当前结构大小。 同时,其大小还必须足以覆盖可能的未来扩展和新版本。可
以通过固件更新将分配大小更新到新版本,但过程可能很复杂。
在初始化时,应用程序必须迭代结构的所有可能副本。在每次迭代中,它必须计算出当前副本的 CRC 并将计数器字段
与目前为止的最佳候选项进行比较。具有较高计数器和版本值以及有效 CRC 值的副本是最新有效数据,必须装入
SRAM 中供应用程序使用。
如果找不到有效的副本,则应用程序必须将所有字段初始化为其默认值。这只会发生在新编程的器件上。
应用程序可能会检测到副本有效,但是结构的版本低于应用程序当前支持的版本。这将发生在固件更新(更改数据布
局)后的首次启动时。在这种情况下,旧版本的数据部分应按原样装入,新字段应初始化为默认值。版本字段应更新
为最新值,之后,结构将在下次写入时以新版本格式保存。
支持固件更新的应用程序必须支持结构的所有先前版本,这样才能以旧格式装入数据。如果支持固件降级,则应用程
序必须保留数据结构的布局以与旧版本兼容。这包括保留和更新所有先前版本的 CRC 值。如果无法进行固件降级,则
只能将一个 CRC 用于结构的最新版本。
如果支持固件降级,则版本字段用于指示最新版本。固件降级后,应用程序可能会在版本字段中看到不受支持的版本
集。在这种情况下,必须假定版本是由最新固件设置的,并且仅将 CRC 用作数据有效性的指标。
将数据保存到闪存时,下一个待写入条目的索引的计算公式为(C + 1) % N,其中 C 是当前有效条目的索引,N 是分配
为永久存储的条目总数。保存结构后,如果新索引指向另一个擦除块,则应用程序可以选择提前擦除下一个块。这会将擦除操作移至上一次写
入的时间点,从而加快下一次写入的速度。
通常,写操作的速度远快于擦除操作。提前擦除块可为应用程序留出足够的时间来检测电源故障并保存当前未保存的
数据。
初始化之后,应用程序不可假定下一个块已在上一次写入操作后正确擦除,因为擦除操作可能因电源故障而中断。在
这种情况下,即使闪存看似已被擦除,应用程序也必须始终执行擦除。
3. 实际应用注意事项 下图所示为典型的存储器布局。存储器布局中分配了八个副本,即每个擦除块四个副本。在第一种情况下,索引为 2 (计数器 = 10)的条目是最新副本。数据更新后,索引为 3(计数器 = 11)的条目将是最新副本。同时,擦除块“1” 会被擦除,因为它将包含下一个写入的条目。所有条目均以循环方式使用,以确保闪存耗损均匀。
为了限制闪存擦除周期数,实际写入操作必须由应用程序启动。可以在更新每个字段时启动,也可以在同时更新几个 字段后启动。此外,应用程序可以选择在设置的超时后定期保存更新的值。同一超时周期内对数据的任何更改都将同 时写入,从而节省闪存擦除周期。 在 EEPROM 访问很少且大多数为只读访问的情况下(例如,当 EEPROM 用于存储校准数据时),从闪存直接访问数 据而不在 SRAM 中保留副本可能更有意义。在这种情况下,每次需要保存数据时,都必须在 SRAM 中创建数据结构的 临时副本。 在器件的单独数据段中分配可进行写访问的永久存储器而主闪存可用于执行代码时,必须特别小心。在这种情况下, 代码执行与闪存的写入和擦除操作并行进行,从而可将 EEPROM 仿真对代码执行的影响降至最低。不过,EEPROM 仿真代码必须一直跟踪写入过程中发生更改的字段。数据保存代码可以创建正在保存的数据的副本,也可以在进行闪 存访问时阻止更新操作的执行。