1

I would like to estimate the effective (minimum) endurance of the EEPROM of the Arduino in terms of single-byte writes.

The datasheet of the ATmega used on many Arduino boards says (page 20):

The ATmega48A/PA/88A/PA/168A/PA/328/P contains 256/512/512/1Kbytes of data EEPROM memory. It is organized as a separate data space, in which single bytes can be read and written. The EEPROM has an endurance of at least 100,000 write/erase cycles.

Consider the ATmega328P with 1 kB of EEPROM. As I can write single bytes, if I loop through the EEPROM, writing every byte position the same number of times, that would make 1024 * 100,000 writes (assuming the nominal endurance).

But as far as I know not single bytes are erased before a write, but pages / cells. On page 285 / table 28.12 of the datasheet, the page size is specified to be 4 bytes. Does that mean the specified minimum endurance amounts to 1024 * 100,000 / 4 single-byte writes?

  • That story goes around, but it appears not to be true. – Gerben Mar 15 '15 at 12:57
  • I'm doing my own test on this right now. So far I am up to a million writes on a single byte, with it reading back correctly. Once I get it to fail (which might be in 7 hours judging by the AVR Freaks posting, I'll see if an adjacent byte fails, or not. Both my own test, and the AVR Freaks one, seem to confirm that you can probably get somewhat more than 100,000 writes from a single address. – Nick Gammon Aug 22 '15 at 4:47
4

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.

  • See edited reply. The second byte failed after 12.16 million writes, so it appears that even bytes in the same 4-byte page have individual lifetimes. – Nick Gammon Aug 23 '15 at 20:30
0

In the arduino forum http://forum.arduino.cc/index.php?topic=293472.0 someone states that is 1 byte size (each "page") - each write writes and spends a cycle for a single byte. There it also says that SPI access to eeprom uses a 4 bytes page size. I did not went further reading since my doubt was same as here, writing a byte to arduino eeprom.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.