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
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?
The microcontroller on the Arduino boards have 512 bytes of EEPROM: memory whose values are kept when the board is turned off (like a tiny hard drive). This example illustrates how to store values read from analog input 0 into the EEPROM using the EEPROM. write() function.
Simply, the EEPROM is permanent storage similar to a hard drive in computers.
Reading from the EEPROM basically follows the same three step process as writing to the EEPROM: Send the Most Significant Byte of the memory address that you want to write to. Send the Least Significant Byte of the memory address that you want to write to. Ask for the data byte at that location.
All EEPROMs (Flash ROM), and EPROMs chips have a finite data retention time. Typically 10-15 years and after that they just start to forget their data. A device using that technology for firmware storage will just stop working when it is old enough even if all other circuits are still good.
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:
Byte 0: Table Length (in 16-bit words)
Byte 1: Table ID (used by firmware to determine what this data is)
Byte 2: Format Version (incremented every time the format of this table changes)
Byte 3: Checksum (simple sum-to-zero checksum)
Byte 4: Start of body
...
Byte N: End of body
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 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With