Learning Goals 5 min
- Understand the three memory areas on an Arduino UNO — flash, SRAM, EEPROM — and what survives a power-cycle.
- Use
EEPROM.write(addr, byte)andEEPROM.read(addr)to store and retrieve a value across resets. - Recognise EEPROM's limit: ~100 000 writes per address. Plan around it with
EEPROM.updateinstead ofEEPROM.write.
Warm-Up 10 min
Cluster G is about persistence. Up to now, every variable in every sketch has been a goldfish — it remembers things until the next power cycle, then forgets. EEPROM (Electrically Erasable Programmable Read-Only Memory) is the chip's tiny notepad that survives unplugging.
The three memory regions on an UNO
| Memory | Size on UNO | What it holds | Survives power-off? |
|---|---|---|---|
| Flash | 32 KB | Your sketch code | Yes |
| SRAM | 2 KB | Variables, arrays, the stack — everything during runtime | No |
| EEPROM | 1 KB | Long-term values you save explicitly | Yes |
1 KB is small — 1024 bytes. Just enough to remember a high score, a calibration value, a user-chosen mode. Not for logging hours of sensor data (that's tomorrow's SD card lesson).
New Concept · Read, write, update 20 min
The minimum-viable EEPROM sketch
#include <EEPROM.h>
void setup() {
Serial.begin(9600);
// Read what's stored at address 0 (might be garbage on first boot ever)
byte saved = EEPROM.read(0);
Serial.print("Stored value: ");
Serial.println(saved);
// Overwrite with the value 42
EEPROM.write(0, 42);
Serial.println("Wrote 42 to address 0.");
}
void loop() {}Upload once. The Serial Monitor shows whatever was at address 0 before (likely 255 on a brand-new UNO, since unwritten EEPROM defaults to 0xFF). The sketch then writes 42.
Unplug. Plug back in. Watch the Serial Monitor. The first line now shows Stored value: 42 — the value survived the power cycle. Memory!
The 100 000-write limit
Each EEPROM byte can be re-written about 100 000 times before it physically wears out. That sounds like a lot — but a sketch that writes the same byte once per loop iteration (at 10 000 iters/sec) would burn through the lifetime in 10 seconds.
The fix: EEPROM.update(addr, value) reads the current value first and only writes if it's different. Most of the time the value hasn't changed, so no write happens, so no wear. Use update instead of write by default; only fall back to write when you know you need to force the operation.
EEPROM.update(0, 42); // writes only if address 0 isn't already 42Reading and writing larger values
EEPROM is byte-addressed, but most useful values are bigger:
int= 2 bytes (on AVR).long,float= 4 bytes.
The EEPROM library provides templated helpers:
int score = 1234;
EEPROM.put(10, score); // writes 2 bytes starting at address 10
EEPROM.get(10, score); // reads 2 bytes starting at address 10EEPROM.put also uses the update-equivalent logic internally, so it's safe to call frequently. Use it for any non-byte value.
The "is it initialised?" problem
On a brand-new chip, unwritten EEPROM reads as 0xFF (255). On a chip you've previously used, EEPROM holds whatever the last sketch left there. Both cases mean: don't blindly trust an EEPROM read on first boot.
Two common patterns to detect "has this sketch ever saved anything?":
- Magic number: reserve a few bytes for a known constant. On boot, check it; if absent, initialise everything.
- Sentinel: use a value that's impossible for real data. E.g. 0xFFFF for an int score means "no score yet".
Worked Example · The boot counter 25 min
A classic first EEPROM project: count how many times this sketch has booted, ever. Each power-on increments a saved counter; the LCD or Serial shows the count.
Step 1 — wiring
No external wiring needed — EEPROM is on the same chip as your Arduino. Just the USB cable.
Step 2 — the sketch
Save as boot-counter.ino:
// L02-39: A boot counter saved in EEPROM
#include <EEPROM.h>
const int ADDR_MAGIC = 0; // 1 byte
const int ADDR_COUNT = 2; // 2 bytes (int)
const byte MAGIC = 0xA7; // arbitrary; means "this slot is ours"
void setup() {
Serial.begin(9600);
delay(500);
int count = 0;
// Has this sketch ever saved data before?
if (EEPROM.read(ADDR_MAGIC) != MAGIC) {
// First-ever boot for this sketch. Initialise.
Serial.println("First boot — initialising EEPROM.");
EEPROM.update(ADDR_MAGIC, MAGIC);
EEPROM.put(ADDR_COUNT, count); // 0
}
// Read, increment, save back.
EEPROM.get(ADDR_COUNT, count);
count++;
EEPROM.put(ADDR_COUNT, count);
Serial.print("This is boot number ");
Serial.println(count);
}
void loop() {}Step 3 — upload and observe
First upload: Serial shows First boot — initialising EEPROM. then This is boot number 1.
Press the reset button (or unplug/replug). Now Serial shows This is boot number 2. No "first boot" line — the magic number is already set.
Press reset again. This is boot number 3. The count survives every reset.
Step 4 — even when the sketch is re-uploaded
Compile and upload the same sketch again (don't change anything). The reset that happens on upload counts — you'll see a higher number. The EEPROM is not cleared on upload.
Step 5 — try the "wipe" trick
Add this line temporarily at the top of setup():
EEPROM.update(ADDR_MAGIC, 0xFF); // pretend we've never run beforeUpload. The count resets to 1 because the magic check fails. Remove the line again after one upload, or the count will always reset.
This is the classic "factory reset" pattern — invalidate the magic number, the sketch re-initialises on next boot.
Step 6 — verify update isn't wasting writes
Add a 100-iteration loop that calls EEPROM.update(ADDR_COUNT, count) with the same count. Watch the loop time — barely any delay, because update sees the value is unchanged and skips the actual write. Then change update to write. Re-run. The loop now takes ~330 ms (100 × 3.3 ms). That's the difference between burning EEPROM lifetime and preserving it.
Try It Yourself 15 min
Goal: Save a single byte representing your favourite LED brightness (0–255). On every boot, the on-board LED on D13 lights to that brightness (using analogWrite; D13 isn't PWM but you can pretend, or move to D9).
Hint
byte brightness = EEPROM.read(20);
analogWrite(9, brightness);On first boot it'll be 255 (or 0 if Arduino has been used). Use the Serial Monitor to type a new value, parse it with Serial.parseInt(), save with EEPROM.update(20, newValue).
Goal: Save the LDR's auto-calibration values (dark and bright raw) from L02-15. So you only have to do the cover-then-shine routine ONCE; subsequent boots use the saved values.
Hint
int darkRaw, brightRaw;
EEPROM.get(0, darkRaw);
EEPROM.get(2, brightRaw);
if (darkRaw == -1 || brightRaw == -1) {
// Never calibrated. Run the 5-s routine.
calibrate();
EEPROM.put(0, darkRaw);
EEPROM.put(2, brightRaw);
}Add a button to force re-calibration on demand. Now the project has persistent calibration with a manual override — like a real consumer product.
Goal: Save a four-character "user name" in EEPROM. Use Serial to receive a new name (type 4 chars, hit enter). Print the saved name on every boot.
Hint
char name[5] = "----"; // 4 chars + null terminator
// Read on boot:
for (int i = 0; i < 4; i++) name[i] = EEPROM.read(50 + i);
name[4] = '\0';
Serial.print("Hello, "); Serial.println(name);
// In loop, receive 4 chars from Serial:
if (Serial.available() >= 4) {
for (int i = 0; i < 4; i++) {
char c = Serial.read();
name[i] = c;
EEPROM.update(50 + i, c);
}
}A 4-char username uses 4 bytes — addresses 50, 51, 52, 53. The pattern scales: longer strings = more bytes. For arbitrary-length strings, save the length too (in 1 byte before the string).
Mini-Challenge · The mode-remembering thermometer 15 min
Take your L02-32 digital thermometer with LCD. Modify it so the screen mode (Now vs Stats) is saved to EEPROM on every change, and restored on boot. So if you turn the device off while looking at the Stats screen, it boots up showing Stats — preserving the user's context.
Spec:
- Reserve 1 byte at EEPROM address 0 for the current screen mode.
- On boot: read that byte, set
screento its value. - On every button press: after flipping
screen, callEEPROM.update(0, screen). - Use
update, notwrite, so each press is at most one EEPROM write — no wear if the user holds the button steady.
It's done when:
- Boot device. It shows Now (default first time).
- Press button. It shows Stats.
- Unplug and re-plug. It boots straight into Stats.
- Press button again. It shows Now. Unplug, re-plug → boots into Now.
- The screen-mode byte never gets stuck.
Recap 5 min
EEPROM is the on-chip 1 KB non-volatile memory. EEPROM.read(addr) and EEPROM.update(addr, byte) handle single bytes; EEPROM.get(addr, var) and EEPROM.put(addr, var) handle larger types. The 100 000-write lifetime per byte sounds generous but is easy to burn through; always use update over write to skip writes when nothing has changed. A magic-number pattern (a known byte at a known address) tells your sketch whether it has ever saved data before, distinguishing fresh chips from previously-initialised ones. Tomorrow we apply the pattern to a high-score for a tiny game.
- EEPROM
- Electrically Erasable Programmable Read-Only Memory. The 1 KB on-chip non-volatile storage on an ATmega328P (UNO). Survives power-off.
- Flash
- The 32 KB on-chip program memory. Holds your compiled sketch. Survives power-off but you don't write to it from your sketch at runtime.
- SRAM
- The 2 KB on-chip volatile memory. Holds variables, the stack, the heap during execution. Loses everything on power-off.
EEPROM.read / write- Single-byte read and unconditional write. Write takes ~3.3 ms, costs one of the byte's 100 000 lifetime writes regardless of whether the value changed.
EEPROM.update- Reads first, only writes if different. Same effect as
writewhen the value has changed, but free when it hasn't. Almost always preferred. EEPROM.get / put- Templated multi-byte read/write. Handles ints, longs, floats, structs — anything with a defined size in memory.
putinternally uses update-style logic. - Magic number
- A known byte placed at a known address to mark "this EEPROM region was initialised by my sketch". Distinguishes saved data from random garbage on first run.
- Wear-out
- The physical degradation of an EEPROM cell after ~100 000 writes. Manifests as "the write succeeded but reading back gives the wrong value".
- Persistent vs volatile
- Persistent = survives power loss (EEPROM, flash, SD card). Volatile = doesn't (SRAM, registers, all your normal variables).
Homework 5 min
Map your EEPROM. On paper, plan the EEPROM layout for one of your earlier projects. Don't implement it; just design it:
Project options:
- Personal Thermometer (L02-12): save COOL_MAX, NORM_MAX, WARM_MAX thresholds.
- Weather Station v1 (L02-20): save the headline thresholds (HOT, HUMID, BRIGHT).
- Smart Bin Lid (L02-26): save the open-trigger distance and hold time.
- Digital Thermometer with LCD (L02-32): save the current screen, the comfort thresholds, and the day's Lo/Hi/Avg/n.
For the chosen project, list:
- Each value to save.
- The address it lives at.
- The number of bytes it takes.
- The total EEPROM usage (must be under 1024).
Then implement the save + restore for ONE of the values from your plan.
Bring back next class:
- Your written EEPROM layout map.
- Your
hw-l02-39.inosketch with one persistent value working. - Tomorrow we use EEPROM to save a game's high score across resets — the classic application.