EEPROM memory/Making it last/09. A million writes
Making it last · 09 of 10

A million writes

Each byte is rated for a million writes. That sounds endless until a sketch writes in loop(): ten times a second uses it up in about 28 hours. Write a byte only when its value has changed and the same chip can outlast the 40 years it is rated to keep data. The sketch saves a setting you type, and skips the write when nothing changed.

The budget

A million writes
The sketch saves
and writes
Writes a day
864,000
One byte lasts
28 hours
Rated writes
1,000,000
Written 10 times a second, one byte reaches its 1,000,000 writes in about 28 hours. After that it may keep the wrong value, or forget it. This is how a sketch that saves a reading on every pass of loop() wears out a byte in days.

The datasheet rates each byte for 1,000,000 writes. Pick how often a sketch saves, and the figure divides that out.

Ten times a second, a byte lasts about 28 hours. Once a second, about 12 days. Once a minute, about two years. Once an hour, over a century, which is past the 40 years the chip is rated to keep data at all.

The numbers are worked out from the rating, not measured. The rating is at 25 °C and 5 V, and the datasheet does not say how a warm chip or a different voltage changes it. Treat them as a budget.

Write on change

Most values a sketch saves do not change most of the time: a brightness, a mode, a count of button presses. Reading costs nothing, so read the byte first and write only when it differs. Switch the figure to only on change and the same once-a-second loop lasts months or decades instead of days, depending on how often the value really moves.

That is update() in the sketch below, and it is the habit to keep. The Arduino EEPROM library has a function of the same name that does the same thing for the microcontroller's own memory.

What you should see

Type 42, then 42 again, then 7:

saved setting: 255
type a number from 0 to 255
42: saved   (writes this run: 1)
42: unchanged, not written   (writes this run: 1)
7: saved   (writes this run: 2)

Restart the board and the first line says 7. The first line of all says 255 on a byte never written; after that, it is what you saved.

Spreading the wear

When a value really does change often, such as a running total saved every few seconds, spread it: keep it in several places in turn, so each byte takes a share of the writes. That is what the ESP32's own settings storage does over its flash. For most projects writing on change is enough.

The code

Keeps one setting, a number from 0 to 255, at byte 16. Type a number in the serial monitor: update() reads the byte first and only writes when the value is different.

eeprom_save_setting.ino
/*
  EEPROM Memory - save a setting, only when it changes  TK31 / /p/tk31

  Wiring. Count from the square pad on the TinkerBlock board, parts
  up, header at the bottom:

    GND -> GND
    VCC -> your board's logic supply: 5V on an Uno, 3V3 on an
           ESP32 or ESP32-S3, 3V3(OUT) on a Pico. Never 5V beside
           a 3.3 V board: the pull-ups would put 5 V on its pins.
    SDA -> A4 on an Uno, GPIO 21 on an ESP32, GPIO 8 on an
           ESP32-S3, GP4 on a Raspberry Pi Pico
    SCL -> A5 on an Uno, GPIO 22 on an ESP32, GPIO 9 on an
           ESP32-S3, GP5 on a Raspberry Pi Pico

  Arduino IDE
    Tools > Board                 your board, e.g. Arduino Uno
    Tools > Port                  the one that appears when you plug in
    Tools > USB CDC On Boot       Enabled   (ESP32-S3 only)
    Serial monitor: 115200 baud, line ending Newline.
    No library to install: Wire comes with every board.
*/

#include <Wire.h>

const int EEPROM_ADDR = 0x50;
const uint16_t SETTING_AT = 16;   // any address; move it if it wears

unsigned long writes = 0;         // how many real writes, this run

void waitReady() {
  unsigned long t0 = millis();
  do {
    Wire.beginTransmission(EEPROM_ADDR);
  } while (Wire.endTransmission() != 0 && millis() - t0 < 20);
}

int readByte(uint16_t at) {
  Wire.beginTransmission(EEPROM_ADDR);
  Wire.write((uint8_t)(at >> 8));
  Wire.write((uint8_t)(at & 0xFF));
  if (Wire.endTransmission(false) != 0) return -1;
  Wire.requestFrom(EEPROM_ADDR, 1);
  return Wire.available() ? Wire.read() : -1;
}

// Write only when the value is different: a read costs no wear.
bool update(uint16_t at, uint8_t value) {
  if (readByte(at) == value) return false;
  Wire.beginTransmission(EEPROM_ADDR);
  Wire.write((uint8_t)(at >> 8));
  Wire.write((uint8_t)(at & 0xFF));
  Wire.write(value);
  Wire.endTransmission();
  waitReady();
  writes++;
  return true;
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Wire.begin();
  Serial.print("saved setting: ");
  Serial.println(readByte(SETTING_AT));
  Serial.println("type a number from 0 to 255");
}

void loop() {
  if (!Serial.available()) return;
  String line = Serial.readStringUntil('\n');
  line.trim();
  if (line.length() == 0) return;
  int v = line.toInt();
  if (v < 0 || v > 255) {
    Serial.println("0 to 255 only");
    return;
  }
  bool wrote = update(SETTING_AT, v);
  Serial.print(v);
  Serial.print(wrote ? ": saved" : ": unchanged, not written");
  Serial.print("   (writes this run: ");
  Serial.print(writes);
  Serial.println(")");
}

update() is the habit, not the setting: use it for every value you save. Restart the board and the setting is printed first, read back from the chip. Byte 16 is an arbitrary choice; any address works.

When it does not work

One saved setting reads back wrong, the rest are fine.

That byte has probably been written too often. Look for a write that runs on every pass of loop(). Change the code to write only on change, and move the setting to an address that has not been used.

Does reading wear it out?

No. The rating is for erase and write cycles. Reads are free: read a setting as often as you like.

What happens after the million?

The datasheet promises a million and says nothing about after. A worn byte may still work, or may keep its value for less time, or read back wrong. Treat a million as the budget, not as a cliff.

Nothing happens when I type a number.

Set the serial monitor's line ending to Newline. The sketch reads up to the end of the line, and without it waits a second for more and then takes what arrived.

Where this goes next

Six symptoms and the check for each.

When it does not answer

Edit this page — content/books/eeprom-memory/a-million-writes.mdx

Community

Questions about this product

See what other owners have asked, and read their solutions.

Ask a question ↗

EEPROM Memory

Loading discussions…

Discuss this article

Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.

Browse Modules and blocks on the forum