安全存储和访问 EEPROM
我最近发现需要将不经常更新的配置变量存储在微控制器的 EEPROM 中。向程序添加状态会立即迫使人们担心
- EEPROM 中未初始化数据的检测(即第一次启动)、
- 旧固件版本中的数据转换或无效以及
- 多个结构的寻址,其中每个结构都可能在固件更新中增长。
广泛的谷歌搜索只找到一篇文章,解决通过固件更新保持 EEPROM 数据有效。有人使用过那篇文章中讨论的方法吗?有更好的替代方法吗?
I've recently established the need to store infrequently-updated configuration variables in the EEPROM of a microcontroller. Adding state to the program immediately forces one to worry about
- detection of uninitialized data in EEPROM (i.e. first boot),
- converting or invalidating data from old firmware versions, and
- addressing of multiple structures, each of which which may grow in firmware updates.
Extensive Googling has only turned up one article that addresses keeping your EEPROM data valid through firmware updates. Has anyone used the approach discussed in that article? Is there a better alternative approach?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
就我个人而言,我更喜欢“标记表”格式。
在这种格式中,您的数据被分成一系列“表”。每个表格都有一个遵循可预测格式的标题和一个可以根据需要更改的正文。
下面是其中一个表的示例:
我没有存储大量数据,因此我为标头中的每个字段使用了一个字节。您可以使用任何您需要的尺寸,只要您不改变它即可。数据表被依次写入EEPROM中。
当您的固件需要从 EEPROM 中读取数据时,它会从第一个表开始读取。如果固件识别表 ID 并支持列出的表版本,则会从表主体中加载数据(当然,在验证校验和之后)。如果 ID、版本或校验和未签出,则直接跳过该表。长度字段用于定位链中的下一个表。当固件看到长度为零的表时,它知道它已到达数据末尾,并且没有更多的表需要处理。
我发现这种格式灵活(我可以将任何类型的数据添加到表主体中)并且强大(保持标头格式不变,并且数据表将向前和向后兼容)。
有一些警告,尽管它们并不是太繁重。首先,您需要确保您的固件可以处理重要数据不在表中或使用不受支持的格式版本的情况。您还需要将 EEPROM 存储区域的第一个字节初始化为零(这样在第一次启动时,您就不会开始加载垃圾并认为它是数据)。由于每个表都知道其长度,因此可以扩展或缩小表;但是,您必须移动表存储区域的其余部分,以确保没有“漏洞”(如果整个表链无法放入您的设备内存中,那么此过程可能会很烦人)。就我个人而言,我认为这些都不是大问题,而且使用其他一些数据存储方法省去的麻烦是值得的。
Personally, I prefer a "tagged table" format.
In this format, your data is split up into a series of "tables". Each table has a header that follows a predictable format and a body that can change as you need it to.
Here's an example of what one of the tables would look like:
I wasn't storing a lot of data, so I used a single byte for each field in the header. You can use whatever size you need, so long as you never change it. The data tables are written one after another into the EEPROM.
When your firmware needs to read the data out of the EEPROM, it starts reading at the first table. If the firmware recognizes the table ID and supports the listed table version, it loads the data out of the body of the table (after validating the checksum, of course). If the ID, version, or checksum don't check out, the table is simply skipped. The length field is used to locate the next table in the chain. When firmware sees a table with a length of zero, it knows that it has reached the end of the data and that there are no more tables to process.
I find this format flexible (I can add any type of data into the body of a table) and robust (keep the header format constant and the data tables will be both forward- and backwards-compatible).
There are a couple of caveats, though they are not too burdensome. First, you need to ensure that your firmware can handle the case where important data either isn't in the table or is using an unsupported format version. You will also need to initialize the first byte of the EEPROM storage area to zero (so that on the first boot, you don't start loading in garbage thinking that it's data). Since each table knows its length it is possible to expand or shrink a table; however, you have to move the rest of the table storage area around in order to ensure that there are no "holes" (if the entire chain of tables can't fit in your device's memory, then this process can be annoying). Personally, I don't find any of these to be that big of a problem, and it is well worth the trouble I save over using some other methods of data storage.
Nigel Jones 在您的参考资料中介绍了一些基础知识。有很多替代方案。
如果您有足够的空间,另一种选择是存储键值对而不是结构。然后,您可以更新一个值(通过附加它),而无需删除所有内容。这对于擦除周期数有限的设备最有用。您的读取例程需要从头开始扫描,每次遇到密钥时更新值。当然,您的更新例程需要有一个“垃圾收集器”,该收集器会在内存已满时启动。
为了处理更新过程中的设备错误和断电,我们通常存储数据的多个副本。最简单的方法是使用序列号在设备的两半之间进行“乒乓”操作,以确定哪一个较新。每个部分上的 CRC 用于验证它。这也解决了未初始化数据的问题。
对于键值版本,您需要在每次写入后附加新的 CRC。
Nigel Jones has covered some of the basics in your reference. There are plenty of alternatives.
One alternative, of you have lots of room, is storing key-value pairs instead of structures. Then you can update one value (by appending it) without erasing everything. This is most useful in devices that have a limited number of erase cycles. Your read routine will need to scan from the beginning, updating values each time the key is encountered. Of course your update routine will need to have a "garbage collector" that kicks in when the memory is full.
To handle device errors and power-downs in the middle of updates, we usually store multiple copies of the data. The simplest approach is to pingpong between to halves of the device using sequence number to determine which is newer. A CRC on each section is used to validate it. This also addresses the uninitialized data issue.
For the key-value version you'd need to append the new CRC after each write.