Listing 4: Setting and checking the CRC
void setCalibCRC(void)
{
persistent->calibCRC = calcCRC(&persistent->calib, CALIB_SIZE);
}
int checkCalibCRC(void)
{
if (persistent->calibCRC==calcCRC(&persistent->calib, CALIB_SIZE))
{
return true;
}
else
{
return false;
}
}
Now each time we add a field, the padding will shrink, but the CRC and any following structures remain in the same place. The main problem with this scheme is that you have to guess how many extra bytes you are going to need. If we have broken up the persistent store into many separate blocks, for the reasons discussed last month, then we have to guess the amount of padding for each block. In the example shown here, if the padding for the calibration section were completely used up, it wouldn't be possible to borrow any space from the settings section, because that would cause the CRC and other fields to move. This can lead to cases where fields are put into the wrong block because there was no room in the block where it really belonged. This issue should be considered when you are deciding how many divisions you want to create in your persistent store.
Versioning
We now have a structure that will pass its CRC test after the new software has been installed. However, we still want to know that the upgrade happened. We need this information because we have fields living in locations that used to be part of the padding. The padding was probably all zeros at the beginning of the product's life so we may need to put some sensible initial values in those fields as soon as we start running a version of software that uses that area.
In some cases there might be an ad-hoc way of detecting the change. For example, if the value of a new field is zero, when zero is not a legal value for that field, we can assume that this is the first time running with the updated software. Such schemes quickly turn ugly when a number of upgrade paths are possible-you have to assume that some customers out there will have all the previous versions and they might upgrade to any version newer than the one they have.
A better option is to put a version number in the persistent store. There is a bit of a dilemma about where to place this version number. In some cases, in spite of the method just discussed, the CRCs might move. Reading the version number might tell us where they are for this particular version. If the version number is inside a block that has a CRC, then you do not know how to CRC the block before reading the version number, because we do not know the size of the block. Because of this possible catch-22, a reasonable approach is to store the version number in the first byte and the inverse of it in the second byte, as a check. Changes to the structures that follow will never reposition the version number, and the version number will never depend on the size of the blocks that we are checking against a CRC.
The version number of persistent store is different from the version number of software. It is possible that many releases of the software will be made that do not change the layout of the persistent store and, so, the version number stored there does not need to be upgraded. Any time that the layout of our structure is changed, we must update the constant in software that says which version of persistent storage is in use. We will call this constant the persistent store version number (PSVN). It is important to be aware that any version of software will have a PSVN defined as a compile time constant. The PSVN stored in persistent storage is the same as the PSVN of the program that wrote the data to that persistent store.
When the program boots, it needs to check the PSVN in persistent storage. If it doesn't match the compile time constant, we know that this version of software is being run for the first time on this device. It will be necessary to set any new fields to sensible defaults. The number of new values in the structure depends on how many versions there are between the current PSVN and the PSVN found in the persistent store.
Once you have set up reasonable values in all fields, update the PSVN stored in the persistent store to match the number defined in the current revision of software. In addition to setting the new values, the conversion process may involve massaging some of the old ones. On one project, the programmers changed the range of values that the user was allowed to enter. The old range was 0 to 60. The new range was 0 to 50. If the setting was 55 when you did the upgrade, then the first time the new version of software is run it will have an illegal setting, which, on this system, would cause an assert to fire, since there was no legal way for the user to set a value of 55 in the new version. The solution was to check the setting, and if it was above the new maximum, 50, then set it to be equal to 50. This meant that we were not preserving all of the settings exactly, but it was a reasonable compromise.
Other problems are caused by values that change their meaning. For example, if an enumerated type is reordered. For these types of changes, whcih require the persistent store to be updated, it is necessary to increase the PSVN for the new version of the program, even if the actual layout has not changed.
Pointers
In most cases you would not store pointers in the structures that are persistently stored. Pointers into RAM lose all meaning after a reset. Occasionally, pointers to ROM can be usefully stored. They may point to constant strings. In other cases there may be a set of structures in ROM. Each structure represents a mode of operation. Storing a pointer to the structure represents the current mode of operation. You can avoid pointers by storing the structures in an array and simply storing the index into the array. In that case you are paying the price of an array access every time the structure is used.
If you are one of the brave souls who decides to store pointers in persistent store, you should take note of the following warnings:
- Any recompilation may change the location of pointers in ROM, so the PSVN needs to be increased for every compile.
- The first time a new version of software is run, all of the pointer fields need to be set to defaults.
With this scheme, I have sometimes found it useful to set a flag during debugging to indicate that all persistent data needs to be set to defaults during start-up. This avoids the need to change the PSVN after every recompile. Such a flag may also be useful if your layout is constantly changing during development. Once things settle down, it is obviously important to test the system without this flag so you can observe proper persistence of the data through a reset or power cycle.
Dropping fields
So far we have considered what happens when a field is added. If a field is dropped, the layout will also change. My usual policy is to rename them as dummy1, dummy2, and so on. By renaming them, you can be fairly sure that no other part of the program will use them, believing them to be valid. Since they have not been removed, the layout does not change. If space gets tight later on, there is no harm in reusing one of them, so long as it is exactly the same size and does not change the layout in any other way, for example, by causing padding to be inserted for alignment reasons.
Padding
While on the subject of padding, it is worth considering that there may be padding between any of the fields that you insert into the structures. The compiler does this to satisfy the requirements of some types to only exist on word boundaries, for some processors. For example a char may follow another char with no unused bytes in between. However, a char followed by an int may have an unused byte inserted in between which ensures that the int starts on a word boundary. For some preocessors, the software can work with any alignment, but uneven alignment will have a performance penalty. How much padding is required is a function of the processor and of the compiler used. In some cases, compiler flags can change the amount of padding.
Compiler flags may also change the size of certain types. Enumerated types can often fit in a single byte. Some compilers have a flag that can force all enumerated types to be 32 bits. Changing such a compile-time switch between releases of your software can change the layout. If you are forced to make such a change you may be able to compensate by adjusting the size of your padding array, or inserting a dummy field to consume space where the compiler used to insert padding. Ideally, such byte shuffling should be avoided.
Storing C++ objects in NVRAM
On a number of projects, I've considered the alluring proposition of placing C++ objects in persistent store. The placement new operator allows you to choose the location of an object, and to run a constructor which will use the exact location you specify.[3] This allows you to run a constructor on the space the first time it is used, or when reinitializing after a failed CRC.
My view of this technique is that you get very little return on investment. The objects in persistent store do not behave like ordinary objects. If you are using inheritance, the size of the objects changes, and that affects the layout of the objects. If you use virtual functions, the virtual function pointer is stored with the object, and may therefore become invalid each time you recompile your software (just like the pointers discussed previously). Unlike the pointers discussed earlier, you may not be able to fix the virtual function pointer, since the compiler knows the value of that pointer, but your program does not have direct access to it.
While it is no doubt possible to get your objects to live in persistent memory with some restrictions, I prefer the approach of placing simple structures in persistent store and then using an object living in normal RAM to wrap the persistent data. If the object in RAM contains a pointer to the structure within the persistent store, the user of that object will get all of the benefits of the data being preserved across power cycles, without the hassle of worrying about some of the more esoteric pieces of C++. If any readers have experimented with this, and found some real advantages to placing objects in persistent store on an embedded system, let me know.
Don't forget the user
It is usually a good idea to indicate to the user that an upgrade of the persistent data has been performed with a message on the display. If the user performed the software upgrade, this will reassure him that it went smoothly. It will also make it more likely that problems will be spotted, for example, if a corrupted persistent store is falsely interpreted as being a valid, but older, set of data.
Saving some persistent data can hugely enhance the user experience, especially in underdeveloped locations such as California, where power outages can happen at any time. Just remember that persistent storage is the one place where bad data will not get fixed by cycling the power, so you have to take more care with persistent data than you do with any other.
Niall Murphy has been writing software for user interfaces and medical systems for ten years. He is the author of Front Panel: Designing Software for Embedded User Interfaces. Murphy's training and consulting business is based in Galway, Ireland. He welcomes feedback and can be reached at nmurphy@panelsoft.com. Reader feedback to this column can be found at www.panelsoft.com/murphyslaw.
References
references1. Murphy, Niall. "Forget Me Not," Embedded Systems Programming, June 2001, p. 39.
Back
2. Feedback to column of April 2001, available at www.panelsoft.com/murphyslaw/apr01.htm.
Back
3. C++ FAQ, Question 11.10, available at www.faqs.org/faqs/C++-faq/part4/.
Back
Return to July 2001 Table of Contents