Testing a single byte write
I ran some test code overnight to try to get to the bottom of this. Somewhat surprisingly perhaps, I got up to over 11 million writes before a read-back failed:
Current count = 11514199
Writes per minute: 17550
....................
Current count = 11538199
Writes per minute: 17550
....................
Current count = 11562199
Writes per minute: 17549
...................
Failed readback!
Expected: FF
Got: 7F
Current count = 11585314
Halted.
So, 11.5 M writes. Still, as pointed out on the AVR Freaks thread, the guarantee of 100,000 writes to EEPROM gives you a data retention time of 20 years. Possibly if you exceed that limit, the data retention time will go down. It isn't particularly practical for me to test whether or not the EEPROM will still be valid after 20 years. ;)
Interestingly, the next day (as I write this) the EEPROM seems to have recovered and is now still writing and reading back OK. So it would appear that after 11 M writes you may sometimes get a bad read-back.
On the AVR Freaks thread they seemed to get somewhere between 5 M and 8 M writes before failure.
A number of hours later (2.4 hours), the same byte failed again, after a further 2,559,219 writes!
Current count = 14033314
Writes per minute: 17549
....................
Current count = 14057314
Writes per minute: 17549
....................
Current count = 14081314
Writes per minute: 17549
....................
Current count = 14105314
Writes per minute: 17549
....................
Current count = 14129314
Writes per minute: 17549
............
Failed readback!
Expected: FF
Got: 7F
Current count = 14144533
Halted.
Somewhat more quickly the third failure after a further 149,703 writes:
Current count = 14312533
Writes per minute: 17550
....
Failed readback!
Expected: FF
Got: 7F
Current count = 14318236
Halted.
And another failure (in 2.3 minutes) after a further 41757 writes:
Starting.
Current count = 14318236
Waiting for pin 2 to go LOW.
Started.
....................
Current count = 14342236
Writes per minute: 17551
..............
Failed readback!
Expected: FF
Got: 7F
Current count = 14359993
Halted.
Testing the adjacent byte
Now that I got a failure fairly quickly on the somewhat overworked byte, I adjusted the code to write to the next one up (1021) which would be in the same 4-byte "page".
Four hours later, the adjacent byte has been written to 4.36 million times without any errors, so this would appear to support the idea that each byte of EEPROM can be written individually without affecting its neighbours.
Next morning
The adjacent byte (address 1021) finally failed after 12,161,565 writes. So the proposition that each byte can be individually addressed, and has an individual lifetime, seems to be supported.
Current count = 12120000
Writes per minute: 17562
....................
Current count = 12144000
Writes per minute: 17562
..............
Failed readback!
Expected: FF
Got: 7F
Current count = 12161565
Halted.
Code
Main sketch
// EEPROM tester
// Author: Nick Gammon
// Date: 22 August 2015
// Connect a jumper from GND to pin D2 when ready to run.
// Remove the jumper for a clean wrap-up (saving the current count into EEPROM)
// Single flash, repeated every half second: ready to go
// Two flashes, stopped on request
// Three flashes, error
// Occasional flash: working
#include <EEPROMAnything.h>
#include <EEPROM.h>
const unsigned int WRITE_COUNT_ADDRESS = 1010;
const unsigned int TEST_ADDRESS = 1020;
const unsigned int ITERATIONS = 200;
const unsigned long MAX_DOTS = 20;
const byte TESTS [] = { 0x55, 0x00, 0xFF, 0x66, 0xAA, 0x11 };
const byte ACTIVATE_PIN = 2;
const byte LED = 13;
unsigned long dots = 0;
unsigned long writeCount;
unsigned long startTime;
unsigned long initialCount;
void showCount ()
{
Serial.print ("Current count = ");
Serial.println (writeCount);
} // end of showCount
// flash the LED the required number of times
void flashLED (const int times,
const unsigned long interval = 100,
const unsigned long delayTime = 500)
{
for (int i = 0; i < times; i++)
{
digitalWrite (LED, HIGH);
delay (interval);
digitalWrite (LED, LOW);
delay (interval);
} // end of for each iteration
delay (delayTime); // delay between iterations
} // end of flashLED
// check our memory agrees with what we expect
void checkMemory (const byte target)
{
byte found = EEPROM.read (TEST_ADDRESS);
if (found != target)
{
Serial.println ();
Serial.println ("Failed readback!");
Serial.print ("Expected: ");
Serial.println ((int) target, HEX);
Serial.print ("Got: ");
Serial.println ((int) found, HEX);
showCount ();
EEPROM_writeAnything (WRITE_COUNT_ADDRESS, writeCount);
Serial.print ("Halted.");
Serial.flush ();
while (true)
flashLED (3); // three flashes
}
} // end of checkMemory
void setup ()
{
Serial.begin (115200);
Serial.println ();
// get write count from previous run
EEPROM_readAnything (WRITE_COUNT_ADDRESS, writeCount);
initialCount = writeCount;
// if all 1 bits, variable was never initialized
if (writeCount == 0xFFFFFFFF)
writeCount = 0;
// tell them we are starting
Serial.println ("Starting.");
pinMode (2, INPUT_PULLUP);
pinMode (LED, OUTPUT);
showCount ();
// wait for them to unplug pin 2
Serial.println ("Waiting for pin 2 to go LOW.");
while (digitalRead (ACTIVATE_PIN) == HIGH)
flashLED (1); // one flash
Serial.println ("Started.");
startTime = millis ();
} // end of setup
void loop ()
{
// write to EEPROM, checking each write
for (unsigned int i = 0; i < ITERATIONS; i++)
{
for (unsigned int j = 0; j < sizeof (TESTS); j++)
{
EEPROM.write (TEST_ADDRESS, TESTS [j]);
writeCount++;
checkMemory (TESTS [j]);
} // end of doing each test pattern
} // end of loop of x ITERATIONS
// save the current write count so we can pick up the count next time
EEPROM_writeAnything (WRITE_COUNT_ADDRESS, writeCount);
Serial.print (".");
dots++;
// confirming we are working
flashLED (1, 10, 0); // flash once, for 10 ms, no extra delay
// newline, show counter
if (dots >= MAX_DOTS)
{
Serial.println ();
dots = 0;
showCount ();
unsigned long writes = writeCount - initialCount;
unsigned long timeTaken = millis () - startTime;
float writesPerMinute = float (writes) / float (timeTaken) * 60.0 * 1000.0;
Serial.print ("Writes per minute: ");
Serial.println ((int) writesPerMinute);
} // end of needing a newline and counter output
// check if user wants us to stop by disconnecting the jumper from Gnd to D2
if (digitalRead (ACTIVATE_PIN) == HIGH)
{
Serial.println ();
Serial.println ("Stop request detected.");
showCount ();
Serial.print ("Halted.");
Serial.flush ();
while (true)
flashLED (2); // two flashes
}
} // end of loop
EEPROMAnything.h
#include <Arduino.h> // for type definitions
#include <EEPROM.h>
template <typename T> unsigned int EEPROM_writeAnything (int ee, const T& value)
{
const byte* p = (const byte*)&value;
unsigned int i;
for (i = 0; i < sizeof(value); i++)
EEPROM.write(ee++, *p++);
return i;
}
template <typename T> unsigned int EEPROM_readAnything (int ee, T& value)
{
byte* p = (byte*)&value;
unsigned int i;
for (i = 0; i < sizeof(value); i++)
*p++ = EEPROM.read(ee++);
return i;
}
The code is designed to wait until you connect pin 2 (digital pin 2) to ground before starting. This is to stop it executing immediately after being uploaded, before you start the serial monitor. So, upload the code, open the serial monitor, and then connect Gnd to pin D2, and watch the results.
Error correction
If the concern is that the EEPROM reads may deteriorate, once could use Reed Solomon error correction to recover. That is the method used for correcting bad reads on compact disks, and also QR codes. It can recover from a number of errors (you choose the level of error correction you want - the more error correction, the more memory it takes).
My tests so far have shown that failure involves single bits (as did the AVR Freaks tests) so this may well be a way of extending EEPROM life and reliability.
Testing of some Reed Solomon code that I found, would appear to indicate that adding the encoding and decoding would take around 4800 bytes of program memory, plus around 600 bytes of RAM (512 bytes needed for the internal tables, plus some extra work space). In addition you need 256 bytes of RAM for the code block. Those figures could be reduced by using less bits per "symbol". For example, if you stored 4-bit symbols (such as BCD numbers) then the memory requirements would reduce somewhat.