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
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 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.
The same setting in MicroPython. Type a number in Thonny's shell; update() reads the byte and writes only when it differs.
"""
EEPROM Memory - save a setting, MicroPython TK31 / /p/tk31
Wiring. Count from the square pad on the TinkerBlock board, parts
up, header at the bottom:
GND -> GND
VCC -> 3V3 on an ESP32 or ESP32-S3, 3V3(OUT) on a Pico.
Never 5V: the pull-ups would put 5 V on your pins.
SDA -> GPIO 21 on an ESP32, GPIO 8 on an ESP32-S3,
GP4 on a Raspberry Pi Pico
SCL -> GPIO 22 on an ESP32, GPIO 9 on an ESP32-S3,
GP5 on a Raspberry Pi Pico
Thonny
Run > Configure interpreter MicroPython (ESP32) or
MicroPython (Raspberry Pi Pico)
Type numbers into the shell at the bottom.
Nothing to install: machine and time are built in.
"""
import time
from machine import I2C, Pin
# SDA, SCL. ESP32: 21, 22. ESP32-S3: 8, 9. Pico: 4, 5.
i2c = I2C(0, sda=Pin(21), scl=Pin(22), freq=100_000)
EEPROM = 0x50
SETTING_AT = 16 # any address; move it if it wears
writes = 0
def read_byte(at):
return i2c.readfrom_mem(EEPROM, at, 1, addrsize=16)[0]
def update(at, value):
global writes
if read_byte(at) == value: # same: no write, no wear
return False
i2c.writeto_mem(EEPROM, at, bytes([value]), addrsize=16)
time.sleep_ms(5)
writes += 1
return True
print("saved setting:", read_byte(SETTING_AT))
while True:
v = int(input("a number from 0 to 255: "))
if 0 <= v <= 255:
wrote = update(SETTING_AT, v)
print(v, "saved" if wrote else "unchanged, not written",
"(writes this run:", writes, ")")input() waits for a line in Thonny's shell, so run this from Thonny rather than as main.py. update() is the part to keep: use it for every value you save.
When it does not work
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.
No. The rating is for erase and write cycles. Reads are free: read a setting as often as you like.
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.
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.
Edit this page — content/books/eeprom-memory/a-million-writes.mdx
Questions about this product
See what other owners have asked, and read their solutions.
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.