Skip to main content

Arduino EEPROM

Introduction​

When working with Arduino projects, you'll often need to save data that persists even when the device loses power. The Arduino's built-in EEPROM (Electrically Erasable Programmable Read-Only Memory) provides a simple solution for this need. Unlike RAM, which loses its contents when power is removed, EEPROM retains data even when the Arduino is powered off.

In this tutorial, you'll learn:

  • What EEPROM is and why it's useful
  • How to read from and write to EEPROM
  • How to work with different data types in EEPROM
  • Best practices for using EEPROM efficiently
  • Real-world applications of EEPROM in Arduino projects

What is EEPROM?​

EEPROM is a type of non-volatile memory built into the microcontroller on your Arduino board. Here are the key characteristics:

FeatureDescription
Size512 bytes to 4KB (depending on Arduino model)
Lifespan~100,000 write cycles per memory location
Data retention~10-100 years
Access speedSlower than RAM, but persistent

Different Arduino boards have different EEPROM capacities:

  • Arduino Uno, Nano, Mini: 1KB (1024 bytes)
  • Arduino Mega: 4KB (4096 bytes)
  • Arduino Leonardo: 1KB (1024 bytes)

The EEPROM Library​

Arduino provides a built-in library for EEPROM operations. Let's start by including it:

cpp
#include <EEPROM.h>

Basic EEPROM Operations​

Reading from EEPROM​

To read a byte from EEPROM, use the EEPROM.read() function:

cpp
#include <EEPROM.h>

void setup() {
Serial.begin(9600);

// Read a byte from address 0
byte value = EEPROM.read(0);

Serial.print("Value at address 0: ");
Serial.println(value);
}

void loop() {
// Empty
}

Writing to EEPROM​

To write a byte to EEPROM, use the EEPROM.write() function:

cpp
#include <EEPROM.h>

void setup() {
Serial.begin(9600);

// Current value
byte oldValue = EEPROM.read(0);
Serial.print("Old value at address 0: ");
Serial.println(oldValue);

// Write new value
byte newValue = 42;
EEPROM.write(0, newValue);
Serial.print("Wrote value to address 0: ");
Serial.println(newValue);

// Verify the write
byte verifiedValue = EEPROM.read(0);
Serial.print("New value at address 0: ");
Serial.println(verifiedValue);
}

void loop() {
// Empty
}

Output:

Old value at address 0: 255 (or whatever was previously stored)
Wrote value to address 0: 42
New value at address 0: 42

Updating EEPROM​

The EEPROM.update() function is more efficient than write() because it only writes to EEPROM if the value is different from what's already stored. This helps extend the EEPROM's lifespan:

cpp
#include <EEPROM.h>

void setup() {
Serial.begin(9600);

byte currentValue = EEPROM.read(0);
Serial.print("Current value: ");
Serial.println(currentValue);

// Update with the same value
EEPROM.update(0, currentValue);
Serial.println("Updated with same value (no actual write occurred)");

// Update with a different value
EEPROM.update(0, currentValue + 1);
Serial.print("Updated with new value: ");
Serial.println(currentValue + 1);
}

void loop() {
// Empty
}

Working with Different Data Types​

Storing Integers (2 bytes)​

EEPROM.read() and EEPROM.write() only work with single bytes (0-255). To store larger values, you need to split them into bytes:

cpp
#include <EEPROM.h>

void setup() {
Serial.begin(9600);

// Store an integer (16-bit value)
int valueToStore = 12345;

// Split the int into two bytes
byte lowByte = valueToStore & 0xFF;
byte highByte = (valueToStore >> 8) & 0xFF;

// Write the bytes to consecutive EEPROM addresses
EEPROM.write(0, lowByte);
EEPROM.write(1, highByte);

Serial.println("Integer stored in EEPROM");

// Read the integer back from EEPROM
byte readLowByte = EEPROM.read(0);
byte readHighByte = EEPROM.read(1);

// Combine the bytes back into an integer
int readValue = (readHighByte << 8) | readLowByte;

Serial.print("Read integer from EEPROM: ");
Serial.println(readValue);
}

void loop() {
// Empty
}

Output:

Integer stored in EEPROM
Read integer from EEPROM: 12345

Using EEPROM.put() and EEPROM.get()​

For Arduino boards with more recent versions of the EEPROM library, you can use .put() and .get() to store and retrieve variables of any type:

cpp
#include <EEPROM.h>

struct Settings {
int threshold;
float calibration;
bool enableFeature;
};

void setup() {
Serial.begin(9600);

// Create a settings object
Settings mySettings = {
threshold: 500,
calibration: 1.23,
enableFeature: true
};

// Store the entire structure
EEPROM.put(0, mySettings);
Serial.println("Settings stored in EEPROM");

// Create an empty object to read into
Settings retrievedSettings;

// Read the structure from EEPROM
EEPROM.get(0, retrievedSettings);

// Print the retrieved values
Serial.println("Retrieved settings:");
Serial.print("Threshold: ");
Serial.println(retrievedSettings.threshold);
Serial.print("Calibration: ");
Serial.println(retrievedSettings.calibration, 2);
Serial.print("Feature enabled: ");
Serial.println(retrievedSettings.enableFeature ? "Yes" : "No");
}

void loop() {
// Empty
}

Output:

Settings stored in EEPROM
Retrieved settings:
Threshold: 500
Calibration: 1.23
Feature enabled: Yes

Best Practices for EEPROM Usage​

1. Address Management​

Keep track of which EEPROM addresses are used for what:

cpp
// Constants for EEPROM addresses
const int ADDR_SETTINGS_VERSION = 0;
const int ADDR_BRIGHTNESS = 1;
const int ADDR_THRESHOLD = 2;
const int ADDR_TEMPERATURE_OFFSET = 4; // 2 bytes for a float

2. EEPROM Wear Leveling​

Remember that EEPROM has a limited number of write cycles. For values that change frequently, consider implementing wear leveling by rotating through multiple addresses:

cpp
#include <EEPROM.h>

const int NUM_SLOTS = 5;
const int START_ADDR = 0;

int findLatestSlot() {
byte maxCounter = 0;
int latestSlot = 0;

for (int i = 0; i < NUM_SLOTS; i++) {
byte counter = EEPROM.read(START_ADDR + i);
if (counter > maxCounter) {
maxCounter = counter;
latestSlot = i;
}
}

return latestSlot;
}

void writeValue(byte value) {
int latestSlot = findLatestSlot();
int nextSlot = (latestSlot + 1) % NUM_SLOTS;
byte counter = EEPROM.read(START_ADDR + latestSlot) + 1;

EEPROM.write(START_ADDR + nextSlot, counter);
EEPROM.write(START_ADDR + NUM_SLOTS + nextSlot, value);

Serial.print("Wrote value to slot ");
Serial.println(nextSlot);
}

byte readValue() {
int latestSlot = findLatestSlot();
return EEPROM.read(START_ADDR + NUM_SLOTS + latestSlot);
}

void setup() {
Serial.begin(9600);

// Example usage
writeValue(42);
byte value = readValue();

Serial.print("Read value: ");
Serial.println(value);
}

void loop() {
// Empty
}

3. Version Control​

Include a version number at the beginning of your EEPROM data to handle format changes:

cpp
#include <EEPROM.h>

const byte CURRENT_VERSION = 2;
const int VERSION_ADDR = 0;

void setup() {
Serial.begin(9600);

// Check if stored data is compatible
byte storedVersion = EEPROM.read(VERSION_ADDR);

if (storedVersion != CURRENT_VERSION) {
Serial.println("EEPROM data format has changed. Initializing with defaults.");

// Initialize with defaults for version 2
EEPROM.write(VERSION_ADDR, CURRENT_VERSION);
EEPROM.write(1, 128); // Default brightness
EEPROM.put(2, 25.5f); // Default temperature

Serial.println("Defaults stored.");
} else {
Serial.println("EEPROM data format is current.");
}

// Read stored values
byte brightness = EEPROM.read(1);
float temperature;
EEPROM.get(2, temperature);

Serial.print("Brightness: ");
Serial.println(brightness);
Serial.print("Temperature: ");
Serial.println(temperature);
}

void loop() {
// Empty
}

Practical Applications​

1. Saving Configuration Settings​

One of the most common uses of EEPROM is storing user configuration:

cpp
#include <EEPROM.h>

// EEPROM addresses
const int ADDR_BRIGHTNESS = 0;
const int ADDR_MODE = 1;

void saveSettings(byte brightness, byte mode) {
EEPROM.update(ADDR_BRIGHTNESS, brightness);
EEPROM.update(ADDR_MODE, mode);
Serial.println("Settings saved!");
}

void loadSettings(byte &brightness, byte &mode) {
brightness = EEPROM.read(ADDR_BRIGHTNESS);
mode = EEPROM.read(ADDR_MODE);
}

void setup() {
Serial.begin(9600);

byte brightness = 128; // Default
byte mode = 1; // Default

// Load saved settings
loadSettings(brightness, mode);

Serial.print("Loaded brightness: ");
Serial.println(brightness);
Serial.print("Loaded mode: ");
Serial.println(mode);

// Simulate changing settings
brightness = 200;
mode = 2;

// Save new settings
saveSettings(brightness, mode);
}

void loop() {
// Empty
}

2. Usage Counter​

Track how many times a device has been powered on:

cpp
#include <EEPROM.h>

const int BOOT_COUNT_ADDR = 0;

void setup() {
Serial.begin(9600);

// Read the current boot count
unsigned int bootCount;
EEPROM.get(BOOT_COUNT_ADDR, bootCount);

// Increment and store the new count
bootCount++;
EEPROM.put(BOOT_COUNT_ADDR, bootCount);

Serial.print("Device has been powered on ");
Serial.print(bootCount);
Serial.println(" times");
}

void loop() {
// Empty
}

3. Data Logger with Power Failure Recovery​

Use EEPROM to remember the last logged position:

cpp
#include <EEPROM.h>

const int LAST_RECORD_ADDR = 0;
const int DATA_START_ADDR = 2;
const int MAX_RECORDS = 50;

void setup() {
Serial.begin(9600);

// Read the last saved record index
int lastRecord;
EEPROM.get(LAST_RECORD_ADDR, lastRecord);

Serial.print("Last saved record position: ");
Serial.println(lastRecord);

if (lastRecord >= MAX_RECORDS) {
// Reset if we've reached the maximum
lastRecord = 0;
EEPROM.put(LAST_RECORD_ADDR, lastRecord);
Serial.println("Maximum records reached, resetting to 0");
}

// Simulate logging a new sensor reading
int sensorValue = analogRead(A0);
int recordAddr = DATA_START_ADDR + (lastRecord * sizeof(int));

EEPROM.put(recordAddr, sensorValue);

// Update the last record position
lastRecord++;
EEPROM.put(LAST_RECORD_ADDR, lastRecord);

Serial.print("Saved sensor value: ");
Serial.println(sensorValue);
Serial.print("New record position: ");
Serial.println(lastRecord);

// Print all stored records
Serial.println("All stored records:");
for (int i = 0; i < lastRecord; i++) {
int value;
EEPROM.get(DATA_START_ADDR + (i * sizeof(int)), value);
Serial.print("Record ");
Serial.print(i);
Serial.print(": ");
Serial.println(value);
}
}

void loop() {
// Empty
}

EEPROM Clear and Management​

Sometimes you need to clear or initialize the EEPROM:

cpp
#include <EEPROM.h>

void clearEEPROM() {
Serial.println("Clearing EEPROM...");

for (int i = 0; i < EEPROM.length(); i++) {
EEPROM.write(i, 0);
}

Serial.println("EEPROM cleared!");
}

void setup() {
Serial.begin(9600);

// Only call this when you want to reset all EEPROM data
// clearEEPROM();

Serial.println("EEPROM content (first 16 bytes):");
for (int i = 0; i < 16; i++) {
byte value = EEPROM.read(i);
Serial.print(i);
Serial.print(": ");
Serial.print(value);
Serial.print(" (0x");
if (value < 16) Serial.print("0");
Serial.print(value, HEX);
Serial.println(")");
}
}

void loop() {
// Empty
}

Summary​

EEPROM is a valuable resource in Arduino projects that allows you to:

  1. Store configuration settings that persist when power is lost
  2. Track usage statistics across power cycles
  3. Create data loggers with power failure recovery
  4. Build devices with user-customizable preferences

Remember these key points:

  • EEPROM has limited write cycles (about 100,000 per address)
  • Use update() instead of write() when possible
  • Implement wear leveling for frequently changed values
  • Include version control for your EEPROM data format
  • Keep track of which addresses store what data

Exercises​

  1. Create a program that uses EEPROM to remember the highest value read from an analog sensor.
  2. Build a simple "favorites" system that stores three user-favorite modes in EEPROM.
  3. Make a program that uses EEPROM to implement a "secret knock" detector that remembers a sequence of knocks.
  4. Create a data logger that stores temperature readings in EEPROM and can be accessed via the Serial Monitor.
  5. Implement a wear-leveled counter that can survive trillions of updates by rotating through the entire EEPROM space.

Additional Resources​



If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)