Persistent time counter using NVS without burning a hole in the FLASH?
Posted: Thu Nov 21, 2024 8:23 am
Once again I'm designing weird features into my clock OS/platform.
This time I want the settings menu to display a runtime counter on vacuum tube based displays, to be able to tell how many hours the VFD/plasma display has spent actively showing something and/or with the heater turned on.
Of course, just storing a uint64 in NVS will be wasteful and also slow, because I presume NVS erases the whole block of flash just to change a single setting.
So what I came up with is:
1. Allocate 512 bytes of NVS for each counter. Of which 8 bytes will be our hours count (uint64), 8 bytes can be the checksum (e.g. CRC), and the rest is filled with 1's (0xFF, a.k.a. erased).
2. Every power on, read the array and count the number of 0 bits. For ease of programming the number of 0 bits is kept in RAM, so we don't have to count them every time.
3. Every second, go to the array and flip one of the 1's into a 0. Since Flash does not need erasing to change 1 to 0, unlike the opposite, it can be done very fast (microseconds).
4. Once we have changed exactly 3600 of the 1's into 0's, that mean an hour has passed. We increment the hour counter allocated in the 8 bytes, and at the same time rewrite the remaining bytes back to 0xFF. This could also be a good place to relocate the data block for wear leveling.
5. When the user requests the total runtime of the display, we can calculate it as (8 byte value) hrs + (number of 0 bits / 60) minutes + (number of 0 bits % 60) seconds and show it in the service menu.
The questions that I have are:
1. Will this actually wear out the flash memory less than just updating an uint64?
2. How do I approximate the lifetime of the flash with this algorithm, if I'm not exactly doing a whole cell cycle each second? Can I just consider this as one cycle per hour? (thus 100k hrs without wear leveling?)
3. Does the NVS library allow to even do bit-level manipulation of the byte arrays, or does it yeet the whole block and rewrite it again no matter what? Can I perhaps mmap an NVS setting to do this myself, and only unmap and let the NVS library take care of it for wear leveling and all that on the hour mark?
This time I want the settings menu to display a runtime counter on vacuum tube based displays, to be able to tell how many hours the VFD/plasma display has spent actively showing something and/or with the heater turned on.
Of course, just storing a uint64 in NVS will be wasteful and also slow, because I presume NVS erases the whole block of flash just to change a single setting.
So what I came up with is:
1. Allocate 512 bytes of NVS for each counter. Of which 8 bytes will be our hours count (uint64), 8 bytes can be the checksum (e.g. CRC), and the rest is filled with 1's (0xFF, a.k.a. erased).
2. Every power on, read the array and count the number of 0 bits. For ease of programming the number of 0 bits is kept in RAM, so we don't have to count them every time.
3. Every second, go to the array and flip one of the 1's into a 0. Since Flash does not need erasing to change 1 to 0, unlike the opposite, it can be done very fast (microseconds).
4. Once we have changed exactly 3600 of the 1's into 0's, that mean an hour has passed. We increment the hour counter allocated in the 8 bytes, and at the same time rewrite the remaining bytes back to 0xFF. This could also be a good place to relocate the data block for wear leveling.
5. When the user requests the total runtime of the display, we can calculate it as (8 byte value) hrs + (number of 0 bits / 60) minutes + (number of 0 bits % 60) seconds and show it in the service menu.
The questions that I have are:
1. Will this actually wear out the flash memory less than just updating an uint64?
2. How do I approximate the lifetime of the flash with this algorithm, if I'm not exactly doing a whole cell cycle each second? Can I just consider this as one cycle per hour? (thus 100k hrs without wear leveling?)
3. Does the NVS library allow to even do bit-level manipulation of the byte arrays, or does it yeet the whole block and rewrite it again no matter what? Can I perhaps mmap an NVS setting to do this myself, and only unmap and let the NVS library take care of it for wear leveling and all that on the hour mark?