---
# System prepended metadata

title: How to use put and get for external EEPROM in Arduino?
tags: [Arduino]

---

On [Arduino](https://www.ampheo.com/c/development-board-arduino), EEPROM.put() / EEPROM.get() are designed for the internal EEPROM (on AVR boards like [Arduino Uno](https://www.ampheo.com/product/a000046-25542493)/Nano/Mega). For an external EEPROM (typically I²C parts like [24LC256](https://www.onzuu.com/search/24LC256)/[24LC512](https://www.onzuu.com/search/24LC512)/[AT24Cxx](https://www.onzuu.com/search/AT24C)), you have two good options:

1. Use an external-EEPROM library that already provides get/put-like helpers, or
2. Write your own “put/get” wrappers that read/write arbitrary types over I²C.

Below is a practical, copy-paste friendly approach (option 2), plus notes to avoid the classic EEPROM pitfalls.

![_GlVxsZ0wTh](https://hackmd.io/_uploads/SyxpL6z-4bl.jpg)

**Typical external EEPROM wiring (I²C)**

* EEPROM SDA → Arduino SDA
* EEPROM SCL → Arduino SCL
* Pull-ups on SDA/SCL (often 4.7k to 10k to VCC)
* EEPROM A0/A1/A2 set device address (commonly all GND → base address)
* EEPROM WP (write protect) to GND to allow writes (or VCC to lock)

Most 24xx EEPROMs show up at I²C address 0x50–0x57 depending on A0–A2.

**“put/get” for external I²C EEPROM (works with structs, floats, etc.)**

Example assumes a 16-bit address EEPROM like 24LC256 / 24LC512 (most common).

**Core byte read/write + ACK polling (recommended)**
```
#include <Wire.h>

static const uint8_t EEPROM_I2C_ADDR = 0x50;   // 0x50 + (A2 A1 A0)
static const uint16_t EEPROM_PAGE_SIZE = 64;   // 24LC256 often 64-byte pages

// Wait until EEPROM finishes its internal write cycle (ACK polling).
void eepromWaitReady() {
  while (true) {
    Wire.beginTransmission(EEPROM_I2C_ADDR);
    uint8_t err = Wire.endTransmission();
    if (err == 0) break; // device ACKed
    delay(1);
  }
}

void eepromWriteByte(uint16_t memAddr, uint8_t data) {
  Wire.beginTransmission(EEPROM_I2C_ADDR);
  Wire.write((uint8_t)(memAddr >> 8));     // high byte
  Wire.write((uint8_t)(memAddr & 0xFF));   // low byte
  Wire.write(data);
  Wire.endTransmission();
  eepromWaitReady();
}

uint8_t eepromReadByte(uint16_t memAddr) {
  Wire.beginTransmission(EEPROM_I2C_ADDR);
  Wire.write((uint8_t)(memAddr >> 8));
  Wire.write((uint8_t)(memAddr & 0xFF));
  Wire.endTransmission(false);             // repeated start

  Wire.requestFrom(EEPROM_I2C_ADDR, (uint8_t)1);
  return Wire.available() ? Wire.read() : 0xFF;
}
```

**Page-safe block write + sequential read**

External EEPROMs write in pages. If you cross a page boundary in a single write, data can wrap and corrupt.

```
void eepromWriteBlock(uint16_t memAddr, const uint8_t* data, size_t len) {
  while (len > 0) {
    uint16_t pageOffset = memAddr % EEPROM_PAGE_SIZE;
    uint16_t spaceInPage = EEPROM_PAGE_SIZE - pageOffset;
    uint16_t chunk = (len < spaceInPage) ? len : spaceInPage;

    Wire.beginTransmission(EEPROM_I2C_ADDR);
    Wire.write((uint8_t)(memAddr >> 8));
    Wire.write((uint8_t)(memAddr & 0xFF));

    for (uint16_t i = 0; i < chunk; i++) {
      Wire.write(data[i]);
    }
    Wire.endTransmission();
    eepromWaitReady();

    memAddr += chunk;
    data    += chunk;
    len     -= chunk;
  }
}

void eepromReadBlock(uint16_t memAddr, uint8_t* out, size_t len) {
  Wire.beginTransmission(EEPROM_I2C_ADDR);
  Wire.write((uint8_t)(memAddr >> 8));
  Wire.write((uint8_t)(memAddr & 0xFF));
  Wire.endTransmission(false);

  size_t idx = 0;
  while (idx < len) {
    uint8_t chunk = (len - idx > 32) ? 32 : (uint8_t)(len - idx); // Wire buffer
    Wire.requestFrom(EEPROM_I2C_ADDR, chunk);
    while (Wire.available() && idx < len) {
      out[idx++] = Wire.read();
    }
  }
}
```

**Now the “put/get” templates (drop-in replacements)**
```
template <typename T>
void eepromPut(uint16_t addr, const T& value) {
  const uint8_t* p = (const uint8_t*)&value;
  eepromWriteBlock(addr, p, sizeof(T));
}

template <typename T>
void eepromGet(uint16_t addr, T& value) {
  uint8_t* p = (uint8_t*)&value;
  eepromReadBlock(addr, p, sizeof(T));
}
```

**Example: store and load a struct**
```
struct Calibration {
  float offset;
  float scale;
  uint32_t magic;
};

static const uint16_t CAL_ADDR = 0x0000;

void setup() {
  Serial.begin(115200);
  Wire.begin();

  // Write
  Calibration calWrite { 1.25f, 0.98f, 0xC0A1B123 };
  eepromPut(CAL_ADDR, calWrite);

  // Read back
  Calibration calRead;
  eepromGet(CAL_ADDR, calRead);

  Serial.println(calRead.offset, 6);
  Serial.println(calRead.scale, 6);
  Serial.println(calRead.magic, HEX);
}

void loop() {}
```

**Important notes (so it works every time)**

* Write cycle time: external EEPROM needs a few ms to internally commit data. That’s why ACK polling (eepromWaitReady()) matters.
* Page boundaries: always write in page-safe chunks (done above).
* Wire buffer: many Arduino cores limit I²C TX/RX to ~32 bytes at a time.
* Wear: EEPROM has limited write endurance. Avoid writing the same address constantly; consider writing only when values change, or implement simple wear-leveling.

**If you prefer a library**

Search [Arduino](https://www.ampheoelec.de/c/development-board-arduino) Library Manager for:

* extEEPROM (popular for 24xx I²C EEPROM)
* I2C_eeprom

Many of these libraries provide .read()/.write() and sometimes .get()/.put() style helpers.