DEV Community

Ganesh Kumar
Ganesh Kumar

Posted on

EEPROM Data Storage Using I2C With Arduino UNO And AT24C256 Module

In the fast-evolving world of embedded systems, one of the most critical challenges is ensuring data persistence even when the power goes off.
During my recent college project I embarked on an in-depth exploration of EEPROM (Electrically Erasable Programmable Read-Only Memory) interfaced via the I2C protocol. This journey not only deepened my technical expertise but also demonstrated the fascinating interplay between hardware and software—a true bridge between theory and practice.

Understanding the Fundamentals

What is EEPROM?

EEPROM is a non-volatile memory technology that retains data even when power is removed. Its key features include the ability to rewrite data at the byte level and its durability, albeit with a limited number of write cycles. This makes it ideal for storing configuration settings, calibration data, and small chunks of user data in everyday electronic devices.

The I2C Protocol

I2C, or Inter-Integrated Circuit communication, is a widely used two-wire serial interface that connects microcontrollers with peripheral devices like EEPROMs. By managing start/stop conditions, addressing, and acknowledgments, I2C ensures smooth data transfer while using minimal wiring. This protocol was central to my project, enabling the Arduino to effectively communicate with the AT24C256 EEPROM.

EEPROM Data Storage Using I2C

Internal Structure of AT24C256 EEPROM Module

The primary objective of the project was to design an embedded system that leverages an external AT24C256 EEPROM for non-volatile storage via I2C. The project was divided into two main phases:

Basic Functionality

Block diagram of Basic Functionality

The first phase focused on creating a reliable interface to write data into and read data from the EEPROM. This involved:

  • Implementing I2C communication routines.
  • Writing data in 64-byte page chunks to respect the memory’s page boundaries.
  • Incorporating appropriate delays to accommodate the EEPROM’s write cycle.

Extended Application: Recording and Playback Demo

Block diagram of Extended Application: Recording and Playback Demo

To add a practical twist, the project was extended to record analog sensor values from a potentiometer. These values were mapped to servo motor positions, stored in the EEPROM, and later retrieved to control the servo. This demonstration showcased the dynamic capabilities of EEPROM-based data logging and actuator control, proving the design’s real-world applicability.

Design and Implementation

At the heart of the project was a modular design that ensured reliability and ease of troubleshooting. The system architecture consisted of:

  • Arduino Microcontroller: Serving as the master, it initiated and managed all I2C communications.
  • AT24C256 EEPROM: The external memory chip responsible for persistent data storage.
  • I2C Bus: The two-wire interface (SDA and SCL) that connected the microcontroller and EEPROM.
  • Additional Components: A potentiometer to simulate sensor input and a servo motor to demonstrate playback functionality.

Key Modules and Functions

The software was carefully structured into distinct modules:

  • Data Writing: A dedicated function handled writing data in manageable 64-byte blocks. This ensured that the write operations respected page boundaries and included necessary delays to allow for complete write cycles.
  • Data Reading: Another function was responsible for retrieving data from the EEPROM, formatted and displayed in a hexadecimal format for verification.
  • Extended Demo Functions: Special functions were implemented to record analog values and control a servo motor based on the stored positions. This not only reinforced the core EEPROM interfacing techniques but also brought the system to life with tangible motion.

Design and Implementation: Basic EEPROM Read/Write Operations

In the initial phase of the project, the focus was on establishing a robust and reliable method to store and retrieve data from the external AT24C256 EEPROM. The key design choices and implementations include:

Circuit diagram of Basic EEPROM Read/Write Operations

  • Modular Functions:

    Two dedicated functions—writeEEPROM() and readEEPROM()—form the backbone of the system.

    • writeEEPROM(): This function writes data in 64-byte page blocks, ensuring that each operation respects the EEPROM’s page boundaries. It incorporates deliberate delays after writing each page to accommodate the EEPROM's slower write cycle.
    • readEEPROM(): To efficiently retrieve data, this function reads in chunks (up to 32 bytes per transaction), taking into account the limitations of the I2C library buffer. This segmented reading process ensures that data is accurately captured without overrunning the buffer.
  • I2C Communication Management:

    The project leverages the I2C protocol to facilitate communication between the Arduino and the EEPROM. Special attention was paid to managing start/stop conditions, device addressing, and acknowledgments. These steps are critical for maintaining reliable data transfers over the two-wire interface (SDA and SCL).

  • Data Verification:

    A helper function, disp_buf(), was implemented to format and display the read data in hexadecimal format. This not only helps in verifying the correctness of the operations but also assists in debugging during development.

#include <Arduino.h>
#include <Wire.h>

#define EEPROM_I2C_ADDRESS 0x50   // I2C address of EEPROM
#define EEPROM_PAGE_SIZE   64     // EEPROM page size in bytes
#define FLAG_ADDR          0x0000 // EEPROM address reserved for our flag
#define FLAG_VALUE         0xAA   // Value used to mark that data was written
#define BLOCK_ADDR         0x0010 // EEPROM memory start address for our data
#define LENGTH             48     // Number of bytes to write/read

// Print a buffer in hex format (16 bytes per line)
void disp_buf(uint8_t *buf, int len) {
  for (int i = 0; i < len; i++) {
    if (buf[i] < 0x10) Serial.print("0");
    Serial.print(buf[i], HEX);
    Serial.print(" ");
    if ((i + 1) % 16 == 0) {
      Serial.println();
    }
  }
  Serial.println();
}

// Write data to EEPROM with page writes
void writeEEPROM(uint16_t eeAddress, uint8_t *data, uint16_t length) {
  uint16_t bytesWritten = 0;
  while (bytesWritten < length) {
    // Begin transmission and send the 16-bit address (high byte first)
    Wire.beginTransmission(EEPROM_I2C_ADDRESS);
    Wire.write((uint8_t)(eeAddress >> 8));   // High address byte
    Wire.write((uint8_t)(eeAddress & 0xFF));   // Low address byte

    // Write data until page boundary or end of data
    uint8_t pageBytes = 0;
    while (pageBytes < EEPROM_PAGE_SIZE && bytesWritten < length) {
      Wire.write(data[bytesWritten]);
      pageBytes++;
      bytesWritten++;
      eeAddress++;
    }
    Wire.endTransmission();

    // Wait for EEPROM to complete the write cycle.
    delay(10);
  }
}

// Read data from EEPROM
void readEEPROM(uint16_t eeAddress, uint8_t *buffer, uint16_t length) {
  uint16_t bytesRead = 0;
  while (bytesRead < length) {
    // Set EEPROM internal address pointer
    Wire.beginTransmission(EEPROM_I2C_ADDRESS);
    Wire.write((uint8_t)(eeAddress >> 8));   // High address byte
    Wire.write((uint8_t)(eeAddress & 0xFF));   // Low address byte
    Wire.endTransmission();

    // Request up to 32 bytes at a time (due to Wire library buffer limit)
    uint8_t bytesToRead = (length - bytesRead) > 32 ? 32 : (length - bytesRead);
    Wire.requestFrom((uint8_t)EEPROM_I2C_ADDRESS, (uint8_t)bytesToRead);

    for (uint8_t i = 0; i < bytesToRead && Wire.available(); i++) {
      buffer[bytesRead++] = Wire.read();
      eeAddress++;
    }
  }
}

void setup() {
  Serial.begin(115200);
  while (!Serial); // Wait for the serial monitor to open
  Wire.begin();
  Serial.println("EEPROM test started");

  // Read the flag byte from address 0
  uint8_t flag;
  readEEPROM(FLAG_ADDR, &flag, 1);

  // If the flag is not set, then write our data and mark the flag
  if (flag != FLAG_VALUE) {
    // Prepare the data to write (if the string is shorter than LENGTH, the remaining bytes will be '\0')
    uint8_t writeBuffer[LENGTH] = "HELLO WORLD Charevtore storing ";
    writeEEPROM(BLOCK_ADDR, writeBuffer, LENGTH);
    Serial.println("Data written to EEPROM.");

    // Write the flag value (0xAA) at address 0 to indicate the data has been written
    uint8_t flagValue = FLAG_VALUE;
    writeEEPROM(FLAG_ADDR, &flagValue, 1);
  } else {
    Serial.println("EEPROM already written; skipping write.");
  }

  // Read back the data for verification
  uint8_t readBuffer[LENGTH];
  readEEPROM(BLOCK_ADDR, readBuffer, LENGTH);
  Serial.println("Data read from EEPROM:");
  disp_buf(readBuffer, LENGTH);
}

void loop() {
  // Nothing to do in loop
}
Enter fullscreen mode Exit fullscreen mode

Design and Implementation: Extended Functionality – Recording Sensor Data and Controlling a Servo Motor via Playback

Circuit diagram of Extended Functionality – Recording Sensor Data and Controlling a Servo Motor via Playback

Building upon the basic EEPROM operations, the project was extended to demonstrate a practical application involving real-time data logging and actuator control. The extended functionality centers on recording sensor data and later using that data to control a servo motor.

  • Recording Mode – Capturing Sensor Data:

    In this mode, the system reads analog input values from a potentiometer. These readings are:

    • Mapped to Servo Angles: The analog values (ranging from 0 to 1023) are converted into corresponding servo angles (0° to 180°).
    • Sequentially Stored: Each mapped value is written into the EEPROM in a sequential manner. This process leverages the same page-based write strategy to ensure data integrity while accommodating the EEPROM's write cycle delays.
    • Real-Time Feedback: As data is recorded, the servo motor moves to reflect the current angle, offering a visual and practical demonstration of the sensor input.
  • Playback Mode – Reproducing Recorded Movements:

    Once the sensor data has been recorded, the system can switch to playback mode. In this mode:

    • Data Retrieval: The stored servo angles are read back from the EEPROM in sequential order.
    • Servo Control: The servo motor is driven by the retrieved values, effectively replaying the recorded sequence of movements. This dynamic control loop demonstrates how data logging and retrieval can be harnessed to automate physical actions.
    • User Interaction: A simple user interface via the Serial Monitor allows users to switch between record (R) and playback (P) modes, emphasizing the system’s interactive capabilities.
  • Integration and Consistency:

    The extended functionality maintains a high level of modularity and leverages the same core principles as the basic operations. This ensures that the additional features do not compromise the reliability of the system but rather build upon its robust foundation.

Together, these enhanced features showcase the flexibility of EEPROM interfacing via I2C. They not only prove the concept of dynamic data logging but also illustrate a real-world application where sensor inputs directly drive actuator behavior—highlighting the practical significance of the project in embedded system design.

#include <Wire.h>
#include <Servo.h>
#include <Arduino.h>

#define EEPROM_I2C_ADDRESS 0x50

int analogPin = 0;
int val = 0;
int readVal = 0;
int maxaddress = 1500;
Servo myservo;

// Function to write to EEPROM
void writeEEPROM(int address, byte val, int i2c_address) {
  Wire.beginTransmission(i2c_address);
  Wire.write((int)(address >> 8));   // MSB
  Wire.write((int)(address & 0xFF));   // LSB
  Wire.write(val);
  Wire.endTransmission();
  delay(5);
}

// Function to read from EEPROM
byte readEEPROM(int address, int i2c_address) {
  byte rcvData = 0xFF;
  Wire.beginTransmission(i2c_address);
  Wire.write((int)(address >> 8));   // MSB
  Wire.write((int)(address & 0xFF));   // LSB
  Wire.endTransmission();
  Wire.requestFrom(i2c_address, 1);
  rcvData = Wire.read();
  return rcvData;
}

// Function to record data (write mode)
void recordData() {
  Serial.println("Start Recording...");
  for (int address = 0; address <= maxaddress; address++) {
    // Read potentiometer value and map to servo angle
    val = map(analogRead(analogPin), 0, 1023, 0, 180);
    myservo.write(val);
    delay(15);

    // Write the value to EEPROM
    writeEEPROM(address, val, EEPROM_I2C_ADDRESS);

    // Print address and value to Serial Monitor
    Serial.print("ADDR = ");
    Serial.print(address);
    Serial.print("\t");
    Serial.println(val);
  }
  Serial.println("Recording Finished!");
  delay(1000); // Short pause before prompting again
}

// Function to playback data (read mode)
void playbackData() {
  Serial.println("Begin Playback...");
  for (int address = 0; address <= maxaddress; address++) {
    readVal = readEEPROM(address, EEPROM_I2C_ADDRESS);
    myservo.write(readVal);
    delay(15);

    // Print address and value to Serial Monitor
    Serial.print("ADDR = ");
    Serial.print(address);
    Serial.print("\t");
    Serial.println(readVal);
  }
  Serial.println("Playback Finished!");
  delay(1000); // Short pause before prompting again
}

// Function to prompt user for mode selection and return the selected mode
char getModeInput() {
  Serial.println("\nSelect Mode:");
  Serial.println("(R) for Record/Write mode");
  Serial.println("(P) for Playback/Read mode");

  // Wait indefinitely until input is available
  while (!Serial.available()) {
    delay(10); // Prevents a busy loop
  }

  // Read the user's input
  char mode = Serial.read();

  // Clear any extra characters from the Serial buffer
  while (Serial.available()) {
    Serial.read();
  }

  // Convert lowercase input to uppercase
  if (mode == 'r') {
    mode = 'R';
  }
  if (mode == 'p') {
    mode = 'P';
  }

  return mode;
}

void setup() {
  Wire.begin();
  Serial.begin(9600);
  myservo.attach(9);

  // Wait for the Serial Monitor to open (important for some boards)
  while (!Serial) { ; }

  Serial.println("Arduino EEPROM and Servo Controller");
}

void loop() {
  // Get the user's mode selection and perform the corresponding operation
  char mode = getModeInput();

  if (mode == 'R') {
    recordData();
  } else if (mode == 'P') {
    playbackData();
  } else {
    Serial.println("Invalid input. Please enter 'R' or 'P'.");
  }

  // After completing the selected operation, loop() will prompt again.
}
Enter fullscreen mode Exit fullscreen mode

Real-World Applications and Practical Insights

The skills and techniques developed during this project have broad applications. EEPROMs are found in many consumer electronics for storing configuration settings and sensor logs. The project’s recording and playback demo, in which sensor inputs dynamically controlled a servo motor, provided a vivid demonstration of how embedded systems can seamlessly integrate data storage with real-time control functions.

This hands-on experience not only sharpened my technical abilities but also taught valuable lessons in modular programming, debugging, and iterative design. It highlighted the importance of planning, precise timing, and resource management—skills that are indispensable in any embedded systems project.

Project Outcome and Learnings

The successful implementation of the EEPROM data storage system was more than just a technical milestone; it was a journey of personal and collaborative growth. The project:

  • Enhanced Technical Skills: Through designing, coding, and debugging, I developed a robust understanding of both EEPROM operations and I2C communication.
  • Fostered Team Collaboration: Working closely with peers, we divided tasks based on our strengths, from code development to documentation and presentation.
  • Improved Problem-Solving Abilities: Every challenge, whether related to I2C timing or memory management, was a learning opportunity that honed my analytical skills.

Conclusion

In wrapping up this project, the experience was a powerful reminder of the synergy between theoretical knowledge and practical application. By successfully interfacing an AT24C256 EEPROM with an Arduino via I2C, the project not only met its technical objectives but also paved the way for future explorations in embedded system design. Whether you’re a student delving into electronics or a professional looking to brush up on memory interfacing techniques, this project serves as an inspiring example of innovation and perseverance in the field of embedded systems.

For those interested in further details or seeking inspiration for their own projects, the complete project documentation offers an in-depth look at the design process, code implementation, and technical challenges encountered along the way.

For the full repository of this project, check it out here:
https://github.com/Amazing-Stardom/arduino-uno-i2c-eeprom

I am also working on another project which might interest you. LiveAPI is a product I've been passionately working on for quite a while.

With LiveAPI, you can quickly generate interactive API documentation that allows users to execute APIs directly from the browser.

Image description

If you’re tired of manually creating docs for your APIs, this tool might just make your life easier.

Top comments (0)