The internal EEPROM on a standard Arduino Uno or Nano (ATmega328P) holds exactly 1,024 bytes of non-volatile memory. For logging sensor data over time, that fills up in minutes. To scale up, you need an external I2C EEPROM like the Microchip 24LC256, which provides 32,768 bytes (32KB) of storage. This guide walks through wiring the 24LC256 to an Arduino Nano v3, writing robust code that handles the notorious 64-byte page boundary bug, and debugging the exact I2C NACK errors that silently corrupt your data.
Hardware Specs and Parts List
Before wiring, it is critical to understand the physical limits of the silicon you are using. Internal EEPROM and external I2C EEPROM behave differently regarding write cycles, page sizes, and bus capacitance.
| Feature | Internal (ATmega328P) | External (Microchip 24LC256) |
|---|---|---|
| Capacity | 1,024 bytes (1 KB) | 32,768 bytes (32 KB) |
| Interface | Direct memory addressing | I2C (up to 400 kHz) |
| Page Write Size | 1 byte (no page boundary limits) | 64 bytes (strict boundary limits) |
| Write Cycle Time | ~3.4 ms per byte | ~5 ms per page (up to 64 bytes) |
| Endurance | 100,000 cycles | 1,000,000 cycles |
Required Parts
- Microcontroller: Arduino Nano v3 (ATmega328P variant, 5V/16MHz)
- EEPROM Chip: Microchip 24LC256-I/P (DIP-8 package) or a pre-wired 24LC256 I2C module
- Resistors: Two 4.7kΩ pull-up resistors (required if using a bare DIP chip; modules usually include them)
- Capacitor: One 0.1µF (100nF) ceramic decoupling capacitor
- Wiring: 22 AWG solid core jumper wires
Wiring the 24LC256 to the Arduino Nano
The 24LC256 uses the I2C protocol. On the Arduino Nano v3, the I2C pins are shared with the analog inputs. The chip requires three address pins (A0, A1, A2) to be tied to either VCC or GND to set its I2C address.
| 24LC256 Pin | Pin Name | Arduino Nano v3 Pin | Notes |
|---|---|---|---|
| 1 | A0 | GND | Sets I2C address to 0x50 |
| 2 | A1 | GND | Sets I2C address to 0x50 |
| 3 | A2 | GND | Sets I2C address to 0x50 |
| 4 | VSS (GND) | GND | Common ground |
| 5 | SDA | A4 | Requires 4.7kΩ pull-up to 5V |
| 6 | SCL | A5 | Requires 4.7kΩ pull-up to 5V |
| 7 | WP | GND | Write Protect OFF (tied to GND) |
| 8 | VCC | 5V | Place 0.1µF cap between VCC and GND |
The Code: Handling Page Boundaries and Write Cycles
The most common reason external EEPROM writes fail silently is the page boundary bug. The 24LC256 writes data in 64-byte pages. If you attempt to write 65 bytes starting at memory address 0, the 65th byte does not roll over to address 64; it wraps around to address 0 and overwrites your first byte. The code below targets the Arduino Nano v3 and includes a custom page-chunking function to prevent this, alongside strict I2C error handling.
#include <Wire.h>
// Target Board: Arduino Nano v3 (ATmega328P)
// Chip: Microchip 24LC256 (32KB I2C EEPROM)
#define EEPROM_I2C_ADDR 0x50
#define PAGE_SIZE 64
#define WRITE_DELAY_MS 6 // Datasheet says 5ms max, we use 6ms for safety
void setup() {
Serial.begin(115200);
Wire.begin();
Wire.setClock(400000); // Set I2C to 400kHz
Serial.println("Arduino EEPROM I2C Logger Initialized.");
// Example: Write a 100-byte payload across page boundaries
uint8_t testData[100];
for (int i = 0; i < 100; i++) {
testData[i] = i + 10;
}
writeEEPROMChunked(0, testData, 100);
// Read it back to verify
Serial.println("Reading back data...");
for (int i = 0; i < 100; i++) {
uint8_t val = readEEPROMByte(i);
if (val != testData[i]) {
Serial.print("Mismatch at address ");
Serial.print(i);
Serial.print(": Expected ");
Serial.print(testData[i]);
Serial.print(", Got ");
Serial.println(val);
}
}
Serial.println("Verification complete.");
}
void loop() {
// Data logging loop would go here
}
// Handles 64-byte page boundaries automatically
void writeEEPROMChunked(uint16_t memAddr, const uint8_t* data, uint16_t length) {
uint16_t bytesWritten = 0;
while (bytesWritten < length) {
uint16_t currentPage = memAddr / PAGE_SIZE;
uint16_t pageOffset = memAddr % PAGE_SIZE;
uint16_t bytesToWrite = PAGE_SIZE - pageOffset;
if (bytesToWrite > (length - bytesWritten)) {
bytesToWrite = length - bytesWritten;
}
Wire.beginTransmission(EEPROM_I2C_ADDR);
Wire.write((uint8_t)(memAddr >> 8)); // MSB of memory address
Wire.write((uint8_t)(memAddr & 0xFF)); // LSB of memory address
for (uint16_t i = 0; i < bytesToWrite; i++) {
Wire.write(data[bytesWritten + i]);
}
uint8_t i2cError = Wire.endTransmission();
if (i2cError != 0) {
Serial.print("I2C Write Error Code: ");
Serial.println(i2cError);
}
delay(WRITE_DELAY_MS); // Mandatory wait for internal write cycle
memAddr += bytesToWrite;
bytesWritten += bytesToWrite;
}
}
uint8_t readEEPROMByte(uint16_t memAddr) {
Wire.beginTransmission(EEPROM_I2C_ADDR);
Wire.write((uint8_t)(memAddr >> 8));
Wire.write((uint8_t)(memAddr & 0xFF));
Wire.endTransmission();
Wire.requestFrom(EEPROM_I2C_ADDR, (uint8_t)1);
if (Wire.available()) {
return Wire.read();
}
return 0xFF; // Return 0xFF on read failure
}
Debugging: First Three Things to Check When It Fails
When your serial monitor spits out I2C Write Error Code: 2 or your data reads back as 0xFF, do not immediately assume the chip is dead. I2C EEPROM failures are almost always timing or electrical issues. Here are the first three things to check, ranked by likelihood.
1. Check the Exact Error String from Wire.endTransmission()
The Arduino Wire library returns specific integer codes when Wire.endTransmission() fails. If you see these exact values in your serial output:
- Returns
2(Received NACK on transmit of address): The Arduino cannot see the chip. Check your wiring, ensure A0/A1/A2 are tied to GND (yielding address 0x50), and run an I2C Scanner sketch to verify the bus. If your module has a different base address, it might be 0x51 or 0x54. - Returns
3(Received NACK on transmit of data): The chip acknowledged its address but rejected the data. This almost always means you exceeded the chip's internal I2C buffer (usually 32 bytes on some variants, 64 on the 24LC256) in a singleWire.write()burst, or the bus is noisy.
2. Verify the 5ms Write Cycle Delay
According to the Microchip 24LC256 datasheet, after you send a stop condition, the chip disconnects from the I2C bus internally for up to 5ms to burn the data into the floating gate cells. If your code loops back and tries to write or read before this delay expires, the chip will NACK the bus. Ensure delay(6); is present after every page write.
3. Measure I2C Bus Capacitance and Pull-Up Voltage
If you are using long jumper wires (over 12 inches) or have multiple devices on the bus, parasitic capacitance increases. This rounds off the rising edges of the SCL/SDA signals. Use an oscilloscope to check the I2C lines. If the rise time is sluggish, drop your pull-up resistors from 4.7kΩ to 2.2kΩ to charge the capacitance faster.
Extending and Simplifying the Build
Depending on your project requirements, you may not need a full external I2C setup, or you may need vastly more storage.
<EEPROM.h> library. It requires zero wiring, no pull-ups, and no page boundary math. Just remember to implement software wear-leveling if you update the value frequently.
How to Extend:
- Scale Addressing: The 24LC256 has three address pins (A0, A1, A2). By wiring these to VCC or GND in different combinations, you can put up to eight 24LC256 chips on the same I2C bus, yielding 256KB of total storage.
- Upgrade to FRAM: If your application requires high-speed logging (e.g., writing every 10ms) and you are burning through the 1,000,000 write cycle limit, swap the EEPROM for a Fujitsu/Ramtron FM24C256 FRAM chip. It uses the exact same I2C commands and pinout, but has zero write delay, no page boundaries, and a 10^14 write cycle endurance.
Frequently Asked Questions
How many write cycles does the Arduino EEPROM actually last?
The internal EEPROM on the ATmega328P is rated for 100,000 write cycles per memory cell. However, this is a conservative baseline tested at room temperature. In real-world bench tests at 25°C, internal AVR EEPROM cells often survive well over 1,000,000 cycles before bit-flipping occurs. External chips like the 24LC256 are officially rated for 1,000,000 cycles. To maximize lifespan, never write to the EEPROM in the main loop() without a state-change check or a time-based interval.
Why is my external EEPROM only saving the first 16 or 64 bytes?
This is the classic page boundary overwrite issue. The 24LC256 has a 64-byte page buffer. If you send 100 bytes in a single I2C transmission starting at address 0, the chip writes the first 64 bytes to addresses 0-63. The remaining 36 bytes do not spill over to address 64; the internal address counter rolls over to 0, and bytes 65-100 overwrite the data you just wrote at addresses 0-35. You must chunk your writes to align with the 64-byte boundaries, exactly as demonstrated in the writeEEPROMChunked() function above.
What is the difference between EEPROM and Flash memory on the Arduino?
Flash memory (32KB on the Nano) is where your compiled sketch code lives. It is optimized for reading and is written in large blocks (pages) by the bootloader. You cannot easily write to Flash memory from within your running sketch without complex bootloader modifications. EEPROM (1KB internal, or 32KB external) is optimized for byte-level reading and writing at runtime. Use Flash for static lookup tables and code; use EEPROM for user settings, sensor logs, and runtime state preservation across power cycles.






