EEPROM Counter with Wear Leveling

I have an application where I need to store an incrementing number in a non-volatile memory. Basically an odometer that keeps track of usage, and persists through power cycles.

I know I can write bytes to EEPROM and later read them. To increment the counter, I could simply read the value from EEPROM, add one to it, and write it back to EEPROM. This would all be fine, except I want to be able to exceed the rated 100,000 erase cycles of the EEPROM. So effectively I'm looking to implement a type of wear-leveling. To be most effective (least chance of any bits failing), I need to understand the deeper functionality of how Arduino writes to the EEPROM.

If I write a single byte to EEPROM, will it only erase and write that specific byte, or does it erase and rewrite a larger section? I assume only one byte.

Does reading from EEPROM have any effect on the longevity of the EEPROM? I assume not.

As I understand, an erased EEPROM byte will have a value of 0xFF (all bits set). Starting with a cleared byte, if I write the value 0xFE to the byte, will it simply write 0 to the least significant bit? Or will it erase the byte (set to 0xFF) and then write the bit to 0? Now what if I want to change the value from 0xFE to 0xF0? Will it erase (0xFF) and then set the four bits 0 (0xF0)? Or will it simply set the three other bits to 0? What if I then want to set the value to 0xF3? Will it erase the whole byte (setting it all back to 0xFF) and then set bits 2 and 3 to 0 (0xF3)?

I'm trying to determine how to achieve maximum lifespan (highest reliability) of my counter. Assuming the byte is only being erased when it needs to set a bit back to 1, I think I could have a series of 32 bytes in EEPROM, with each bit being set to 0 sequentially until all 256 bits are set 0, at which point it could overflow (probably into a 24-bit counter stored in three bytes) and erase all 32 bytes back to 0xFF. This would give me a 32 bit counter, that should be good for at least 25 million increments (256 x 100,000 cycles = 25.4M). The 32 bytes representing the 8 least significant bits of the counter would look something like this (abbreviated to show only 2 bytes for the sake of space):

1111 1111 1111 1111
1111 1111 1111 1110
1111 1111 1111 1100
1111 1111 1111 1000
1111 1111 1111 0000
1111 1111 1110 0000
1111 1111 1100 0000
1111 1111 1000 0000
1111 1111 0000 0000
1111 1110 0000 0000
1111 1100 0000 0000
1111 1000 0000 0000
1111 0000 0000 0000
1110 0000 0000 0000
1100 0000 0000 0000
1000 0000 0000 0000
0000 0000 0000 0000

Basically with each increment a single bit would be written (set to 0). Across 32 bytes (256 bits), that would represent an 8 bit value (actually 8-bit plus 1 I think, so 0-256 theoretically, or even slightly more by erasing 1 byte at a time; maybe about 0-287).

Side question: At the gate level, is there an advantage/disadvantage to bits being in a 0 vs. 1 state when issuing an erase to the byte? Would it be less wearing on the gate to first write all 0 before erasing all the bits to 1? To prevent "over-exposure" as it were, of clearing a bit already set to 1? Or would it be less wearing on the gate to leave any 1s in their state, rather than writing to 0 and then erasing back to 1?

I can't answer all the questions but can help with some of them, and mention you can reference the EEPROM description and registers in your mcu's datasheet for more detail. If you're using an ATmega328, it's here https://www.sparkfun.com/datasheets/Components/SMD/ATMega328.pdf

"If I write a single byte to EEPROM, will it only erase and write that specific byte, or does it erase and rewrite a larger section?" It only erases and writes 1 byte, specifically the byte specified by the EEARH and EEARL address registers and it writes the data you place in the EEDR register to that memory location. There is no mechanism to only write specific bits, you must write the entire contents of that data register.

"Does reading from EEPROM have any effect on the longevity of the EEPROM?" Not to a significant degree, they only tested and guarantee the erase/write cycles since it's that process which performs significant amounts of wear.

"Now what if I want to change the value from 0xFE to 0xF0? Will it erase (0xFF) and then set the four bits 0 (0xF0)?" You can control if a read, erase, or write is performed on the EEPROM address using the EEPM1 and EEPM0 registers; so you could do some testing to see if an intelligent approach to avoiding erases when not needed is possible. This would be easy to test and you could confirm if erasing is even a necessary step. I guess it's possible that erasing does more than simply writing a 0 or 1 and instead it prepares parts of the EEPROM circuitry to allow the assignment of the 0 or 1 during the write operation, but again that can all be figured out with straightforward testing.

"This would give me a 32 bit counter, that should be good for at least 25 million increments (256 x 100,000 cycles = 25.4M)" One thought about this, after X amount of increments you could also move the address at which you store the 32 bit counter, allowing you to wear a different location within the EEPROM.

This video might interest you, it shows how the 100,000 guarantee can be quite low.

Maybe you could contribute to existing wear level libraries as well.

Hope this and that datasheet help, good luck!

BTW I found this great explanation for the purpose of the erase vs the write.

  1. EEPROM cells are usually written (by the hardware) in a two step operation: first, the cell is erased, that is, set to all ones (0b11111111 = 0xff), then the bits to be written (effectively only those that are 0) are actually written. Bits can only be set to 0 by an actual write operation. Changing a bit from 0 to 1 requires the whole cell [byte in EEPROM] to be erased and then re-writing the new value.

From

I'm sure there are better methods, but here's a simple one I'd think about.
Use a 16 bit int as a rollover counter. Use a series of ints as your counter; when each int gets to 65535, increment the rollover counter AND move to the next int in the EEPROM. This is fairly wasteful of EEPROM, but, for example, if you needed a counter that runs to 16,777,215 that's only 256 * 65536, using 513 bytes of the EEPROM.
The value in the rollover int effectively becomes your pointer to the eeprom int currently being used, plus one of course if you put your rollover int at the start.
It's a whole lot of waste compared to storing a 32 bit int, but you can't do that, so improvise.

What is the likely maximum number of changes to the odometer value during a session during which the Arduino is powered up?

One possibility is hardware based. The Arduino is latched on at the start of a session and unlatches itself after detecting the end of a session. Before the unlatch operation, it performs the EEPROM update operation. If this method is employed, it would be good also to periodically update the EEPROM so that , in the case of an uncontrolled shutdown, less data is lost.

As for a wear level strategy for a single counter, another option is to have say 100 copies of the counter (4 byte integer). At the start of a session, scan for the highest value and the lowest value. The highest value is used as the starting value for the session. The position of the lowest value is written to during the session (and will become the new highest value at the beginning of the next session).

1 Like

Hi,
Instead of adding 1 then writing.
Add 1, then add the next 1
ONLY write when you conclude the session.

There are ways to keep your controller ON after a power switch has been turned off to the equipment but keep the controller ON until it has done the totalised input write to the EEPROM.

Tom... :smiley: :+1: :coffee: :australia:

Yes - that’s a common implementation strategy that works well.adding a small cap and diode to keep cranking and save to eeprom when power is cut is usually not difficult.

Alternatively you can use the cool external FRAM ( Adafruit offers SPI and I²C variants for FRAM boards.)

Unlike Flash or EEPROM there's no pages to worry about. Each byte can be read/written 10,000,000,000,000 times so you don't have to worry too much about wear leveling.

I really appreciate all the information, suggestions, and ideas presented.

Where can I find the code (register manipulations) behind eeprom_write_byte? I've searched seemingly everywhere and can't seem to find where it is.

For the time being, I'm going to attempt an approach where it will store the incrementing value in RAM, and then write it to EEPROM when it detects a shutdown. Part of this project already is to detect when it loses power input (but it's still running), so this should be a straight-forward approach. I'll make sure I have the BOD enabled and I'll ensure that there is enough time to write the data before power loss.

I am using an AVR, but it's actually an AtTiny44 in this case. With just 256 bytes of EEPROM, there is only one EEPROM address register, EEAR, rather than EEARH and EEARL. Otherwise I believe it's pretty similar architecture to the AtMega328.

I might do some destructive testing later, but for now I'll try the approach above.

Something I noticed in the datasheet is this sentence "If the location to be written has not been erased before write, the data that is stored must be considered as lost.". But there's really not an explanation that I see. Is that because a write operation can only set bits 0, so if you try to write 0xF3 to a byte containing 0xF9, the result will be 0xF1? Or does it really mess up the data in an unpredictable way?

That is how I would understand it.
You may get away without doing a clear operation (setting all to 1) if the new value you are writing to the cell does not require any new bits to be set from 0 to 1.

say setting 0b1100'0000 to 0b1000'0000 should not require a clear.

What I don't fully understand why the user has to explicitly clear a cell before writing to it. I'd have expected that to have been handled deep in the EEPROMs firmware, at least for single cell write operations, and any optimisation such as omitting unnecessary clear operations done there.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.