Interfacing an Arduino with EEPROM memory is the standard solution when your project outgrows the microcontroller's internal storage. The ATmega328P on an Arduino Uno has exactly 1,024 bytes of internal EEPROM. If you are logging sensor data, storing calibration tables, or saving user configurations, you will exhaust that space rapidly. By adding an external I2C EEPROM like the Microchip 24LC256, you instantly expand your non-volatile storage to 32,768 bytes (32KB) while only using two GPIO pins (SDA and SCL).
This guide provides the exact wiring, a fully compilable C++ sketch with robust I2C error handling, and a bench-tested debugging framework for when the I2C bus inevitably throws a NACK error.
Parts List and Specification Sheet
The code and wiring below specifically target the Arduino Uno R3 (or any ATmega328P-based board like the Nano v3). The external memory module is the Microchip 24LC256-I/P, a 32KB serial EEPROM in a DIP-8 package. Do not substitute the 24LC256 with the smaller 24LC02 (256 bytes) without modifying the memory addressing logic in the code, as smaller EEPROMs use 8-bit addressing rather than 16-bit.
| Parameter | Value | Design Note |
|---|---|---|
| Memory Capacity | 32 Kbytes (256 Kbits) | Address range: 0x0000 to 0x7FFF |
| Interface | I2C (Up to 400 kHz) | Requires 4.7kΩ pull-up resistors on SDA/SCL |
| Page Write Buffer | 64 bytes | Writes wrap around if exceeding 64 bytes in one transaction |
| Write Cycle Time | 5 ms (max) | Microcontroller must delay after write commands |
| Endurance | 1,000,000 erase/write cycles | Data retention: >200 years at 25°C |
Pin Mapping and I2C Wiring Steps
Reliable I2C communication requires strict attention to address pins and bus pull-up resistors. While the Arduino's internal pull-ups (approx. 30kΩ) are sometimes enough for a single device on a short wire, a dedicated 4.7kΩ external pull-up is mandatory for stable operation, especially if you plan to run the bus at 400kHz Fast Mode.
| 24LC256 Pin | Pin Name | Arduino Uno R3 Connection |
|---|---|---|
| 1 | A0 | GND (Sets I2C address bit 0) |
| 2 | A1 | GND (Sets I2C address bit 1) |
| 3 | A2 | GND (Sets I2C address bit 2) |
| 4 | VSS | GND |
| 5 | SDA | A4 (SDA) via 4.7kΩ pull-up to 5V |
| 6 | SCL | A5 (SCL) via 4.7kΩ pull-up to 5V |
| 7 | WP | GND (Disables write protection) |
| 8 | VCC | 5V |
0x50.
- Power the Bus: Connect the 24LC256 VCC (Pin 8) to the Arduino 5V pin, and VSS (Pin 4) to GND.
- Set the Address: Wire A0, A1, and A2 (Pins 1-3) directly to GND. This configures the chip to respond to the I2C address
0x50. - Disable Write Protect: Connect the WP pin (Pin 7) to GND. If tied to 5V, the memory becomes read-only.
- Wire I2C Lines: Connect SDA (Pin 5) to Arduino A4, and SCL (Pin 6) to Arduino A5.
- Install Pull-ups: Insert a 4.7kΩ resistor between the SDA line and 5V. Insert a second 4.7kΩ resistor between the SCL line and 5V.
Complete Compilable Code with Error Handling
The following sketch targets the Arduino Uno R3. It uses the native Arduino Wire library to write a string to the EEPROM, read it back, and verify the I2C bus state. Crucially, it includes error handling for Wire.endTransmission() return codes and respects the 5ms write-cycle delay required by the Microchip 24LC256 datasheet.
#include <Wire.h>
// Target Board: Arduino Uno R3 (ATmega328P)
// Device: Microchip 24LC256 (32KB I2C EEPROM)
#define EEPROM_I2C_ADDRESS 0x50 // A0, A1, A2 tied to GND
#define WRITE_CYCLE_TIME 5 // Max write cycle time in ms
void setup() {
Serial.begin(115200);
while (!Serial); // Wait for serial port (Leonardo/Micro only, harmless on Uno)
Wire.begin();
Wire.setClock(400000); // Enable 400kHz Fast Mode
Serial.println("--- Arduino with EEPROM (24LC256) Test ---");
// Test Data
char testData[] = "ElectricalFlux 2026 Sensor Log";
unsigned int memAddress = 0x0000; // Start at memory address 0
// Write to EEPROM
Serial.print("Writing data to address 0x0000... ");
writeEEPROM(memAddress, testData, sizeof(testData));
Serial.println("Done.");
// Read back from EEPROM
Serial.print("Reading data from address 0x0000... ");
char readData[sizeof(testData)];
readEEPROM(memAddress, readData, sizeof(testData));
Serial.print("Result: ");
Serial.println(readData);
}
void loop() {
// Nothing to do in loop
}
// Function to write a byte array to 24LC256
void writeEEPROM(unsigned int eeAddress, const char* data, unsigned int length) {
Wire.beginTransmission(EEPROM_I2C_ADDRESS);
// 24LC256 uses 16-bit memory addressing
Wire.write((int)(eeAddress >> 8)); // MSB
Wire.write((int)(eeAddress & 0xFF)); // LSB
for (unsigned int i = 0; i < length; i++) {
Wire.write(data[i]);
// 24LC256 has a 64-byte page buffer.
// If we cross a page boundary, we must stop, wait, and start a new transmission.
if ((i + 1) % 64 == 0) {
uint8_t error = Wire.endTransmission();
checkI2CError(error, "Page boundary write");
delay(WRITE_CYCLE_TIME); // Wait for physical write cycle
Wire.beginTransmission(EEPROM_I2C_ADDRESS);
Wire.write((int)((eeAddress + i + 1) >> 8));
Wire.write((int)((eeAddress + i + 1) & 0xFF));
}
}
uint8_t error = Wire.endTransmission();
checkI2CError(error, "Final write");
delay(WRITE_CYCLE_TIME); // Mandatory delay for final write cycle
}
// Function to read a byte array from 24LC256
void readEEPROM(unsigned int eeAddress, char* buffer, unsigned int length) {
Wire.beginTransmission(EEPROM_I2C_ADDRESS);
Wire.write((int)(eeAddress >> 8)); // MSB
Wire.write((int)(eeAddress & 0xFF)); // LSB
uint8_t error = Wire.endTransmission();
checkI2CError(error, "Read address set");
Wire.requestFrom(EEPROM_I2C_ADDRESS, (uint8_t)length);
for (unsigned int i = 0; i < length; i++) {
if (Wire.available()) {
buffer[i] = Wire.read();
} else {
Serial.println("Error: I2C buffer underflow during read.");
buffer[i] = '\0';
return;
}
}
buffer[length] = '\0'; // Null-terminate string
}
// Error handling function for Wire.endTransmission()
void checkI2CError(uint8_t error, const char* context) {
if (error == 0) return; // Success
Serial.print("I2C Fault during ");
Serial.print(context);
Serial.print(" - Error Code: ");
switch (error) {
case 1:
Serial.println("1 (Data too long for transmit buffer)");
break;
case 2:
Serial.println("2 (Received NACK on transmit of address)");
break;
case 3:
Serial.println("3 (Received NACK on transmit of data)");
break;
case 4:
Serial.println("4 (Unknown bus error)");
break;
default:
Serial.println("Unknown");
}
while(1); // Halt execution on critical I2C failure
}
Debugging: First Three Things to Check When It Fails
When working with an Arduino with EEPROM over I2C, bus failures are common on solderless breadboards. If your Serial monitor outputs the exact error string I2C Fault during Final write - Error Code: 2 (Received NACK on transmit of address), do not immediately assume the chip is dead. Follow these first three diagnostic steps:
- Verify the Pull-Up Resistors: The most common cause of Error Code 2 is missing or incorrect pull-up resistors. Measure the voltage on the SDA and SCL pins with a multimeter. They should read a steady 5V (or 3.3V if using a 3.3V board) when idle. If they read 0V or fluctuate wildly, your 4.7kΩ pull-ups are missing, miswired, or blown. The internal Arduino pull-ups are ~30kΩ, which results in slow rise times that the 400kHz I2C clock misinterprets as data errors.
- Check Address Pin Voltages: Use your multimeter to probe pins 1, 2, and 3 (A0, A1, A2) on the 24LC256 DIP chip. They must read exactly 0.0V (GND). If a breadboard contact is bent and the pin is floating, the chip's internal logic will interpret the noise as a HIGH state, changing the I2C address from
0x50to something like0x54. The Arduino will transmit to0x50, receive no acknowledgment, and throw Error Code 2. - Run an I2C Bus Scanner: If the hardware checks out, upload the standard Arduino I2C Scanner sketch (available in the IDE examples). If the scanner outputs
No I2C devices found, you have a physical break in the SDA/SCL lines. If it outputsI2C device found at address 0x57(or another unexpected hex value), your A0/A1/A2 pins are wired incorrectly, and you must update the#define EEPROM_I2C_ADDRESSin your code to match the scanner's output.
Extending and Simplifying the Build
How to Extend: To turn this into a robust data logger, add a DS3231 Real-Time Clock (RTC) module to the same I2C bus. The DS3231 uses address 0x68, which will not conflict with the 24LC256's 0x50. Wire the DS3231 SDA/SCL in parallel with the EEPROM, add a separate 4.7kΩ pull-up pair if the wire run exceeds 12 inches, and prepend a 4-byte Unix timestamp to every EEPROM write. This creates a timestamped, non-volatile black box for environmental monitoring.
How to Simplify: If you realize your project only requires storing a few calibration variables (under 1,000 bytes), abandon the external DIP chip entirely. Use the microcontroller's internal EEPROM via the native #include <EEPROM.h> library. It requires zero external wiring, no pull-ups, and no I2C address management. Conversely, if you need high-speed logging and are frustrated by the 5ms write delay, swap the 24LC256 for an MB85RC256V FRAM module. FRAM uses the exact same I2C wiring and addressing, but writes occur at bus speed with zero delay and virtually unlimited write cycles.
Frequently Asked Questions
How many write cycles can an Arduino with EEPROM handle before wearing out?
The Microchip 24LC256 external EEPROM is rated for 1,000,000 erase/write cycles per memory cell. If your Arduino sketch writes to the exact same memory address once every second, the chip will wear out in approximately 11.5 days. To prevent this, implement 'wear leveling' in your code by cycling through different memory addresses, or only trigger a write when the sensor value actually changes by a significant threshold.
Can I use an Arduino with EEPROM to log data at high speeds?
Standard I2C EEPROMs like the 24LC256 are poor choices for high-speed, continuous data logging. Every write operation requires a mandatory 5ms internal programming delay. At 400kHz I2C speeds, your maximum sustained write throughput is roughly 12,000 bytes per second, and that is only if you utilize the 64-byte page write buffer efficiently. For logging high-frequency audio or vibration data, you must use an SPI SD card module or an I2C FRAM chip instead.
Why does my Arduino with EEPROM forget data when power is removed?
EEPROM (Electrically Erasable Programmable Read-Only Memory) is inherently non-volatile; it retains data without power for over 200 years. If your data is vanishing upon power loss, one of two things is happening: First, your code might be executing an initialization routine in setup() that blindly overwrites the memory space with zeros on every boot. Second, the Write Protect (WP) pin on the EEPROM might be floating or accidentally tied HIGH, preventing the physical write from occurring in the first place, meaning the data never actually made it into the silicon.






