Implementing I2C Communication with AT24C02 EEPROM on 8051 Microcontrollers

Overview of Memory Types

Memory is broadly categorized into RAM (Random Access Memory) and ROM (Read-Only Memory). RAM offers fast access but loses data when power is removed. ROM is slower but retains data without power.

RAM Variants

  • SRAM (Static RAM): Utilizes flip-flops for data storage, providing very high speed. Commonly used in CPU caches.
  • DRAM (Dynamic RAM): Relies on capacitors to store data, requiring periodic refresh cycles to maintain the charge. It is more cost-effective than SRAM.

ROM Variants

  • Mask ROM: Data is permanently written during manufacturing using a photomask. It is inexpensive but immutable.
  • PROM (Programmable ROM): Can be programmed once by the user, typically by fusing internal links.
  • EPROM (Erasable Programmable ROM): Can be erased via ultraviolet light exposure and reprogrammed.
  • EEPROM (Electrically Erasable PROM): Allows individual bytes to be erased and rewritten electrically. AT24C02 is a EEPROM.
  • Flash Memory: A type of EEPROM that allows block-level erasure. Widely used in modern storage.

Address Bus Fundamentals

The address bus is used to select a specific memory location or peripheral device. A decoder often interprets the address lines to enable only one device at a time, preventing bus contention.

AT24C02 Chip Specifications

The AT24C02 is a 256-byte EEPROM that communicates via the I2C serial bus.

Key Pins:

  • VCC, GND: Power supply (1.8V to 5.5V).
  • WP: Write Protect. When held high, writes are disabled.
  • SCL, SDA: I2C clock and data lines.
  • A0, A1, A2: Hardware address pins for setting the device's I2C address.

On a typical development board, the WP pin is often grounded, allowing writes at all times.

I2C Bus Protocol

I2C (Inter-Integrated Circuit) is a synchronous, half-duplex serial communication protocol using two wires: SCL (Serial Clock) and SDA (Serial Data).

Key Characteristics:

  • All devices share the same SCL and SDA lines.
  • Requires open-drain output configuration for the I/O pins with external pull-up resistors (typical 4.7 kΩ).
  • Supports multiple masters and slaves.
  • Standard mode operates at 100 kbps; fast mode at 400 kbps.

Protocol Signals:

  • Start Condition: SDA transitions from high to low while SCL is high.
  • Stop Condition: SDA transitions from low to high while SCL is high.
  • Data Validity: Data on SDA must be stable when SCL is high. Changes can only ocur when SCL is low.
  • Acknowledge (ACK): After each byte, the receiving device pulls SDA low.
  • Not Acknowledge (NACK): The receiving device leaves SDA high.

Data Frame for AT24C02:

Write Sequence:

  1. Start condition.
  2. Send 7-bit slave address + Write bit (0).
  3. Wait for ACK.
  4. Send 8-bit memory address.
  5. Wait for ACK.
  6. Send 8-bit data byte.
  7. Wait for ACK.
  8. Stop condition.

Read Sequence:

  1. Start condition.
  2. Send 7-bit slave address + Write bit (0).
  3. Wait for ACK.
  4. Send 8-bit memory address.
  5. Wait for ACK.
  6. Repeated Start condition.
  7. Send 7-bit slave address + Read bit (1).
  8. Wait for ACK.
  9. Receive 8-bit data byte.
  10. Send NACK to signal end of read.
  11. Stop condition.

The AT24C02's 7-bit I2C address is 1010XXX, where XXX are set by the A2, A1, A0 pins.

Write Cycle Time: After a write operation, the AT24C02 requires up to 5 ms to internally complete the data storage process. A read immediately after a write may return old data.

Code Implementation

I2C Low-Level Driver (i2c_driver.c)

#include <REGX52.H>
#include <INTRINS.H>

#define I2C_SCL_PIN P2_1
#define I2C_SDA_PIN P2_0

void I2C_Delay() {
    _nop_(); _nop_(); _nop_(); _nop_(); _nop_();
}

void I2C_Initiate() {
    I2C_SDA_PIN = 1;
    I2C_SCL_PIN = 1;
    I2C_SDA_PIN = 0;
    I2C_Delay();
    I2C_SCL_PIN = 0;
}

void I2C_Terminate() {
    I2C_SDA_PIN = 0;
    I2C_SCL_PIN = 1;
    I2C_SDA_PIN = 1;
}

void I2C_Transmit(unsigned char dat) {
    unsigned char i;
    for(i=0; i<8; i++) {
        I2C_SDA_PIN = dat & (0x80 >> i);
        I2C_Delay();
        I2C_SCL_PIN = 1;
        I2C_Delay();
        I2C_SCL_PIN = 0;
    }
}

unsigned char I2C_Receive() {
    unsigned char i, dat = 0;
    I2C_SDA_PIN = 1;
    for(i=0; i<8; i++) {
        I2C_SCL_PIN = 1;
        if(I2C_SDA_PIN) dat |= (0x80 >> i);
        I2C_Delay();
        I2C_SCL_PIN = 0;
    }
    return dat;
}

void I2C_Acknowledge() {
    I2C_SDA_PIN = 0;
    I2C_SCL_PIN = 1;
    I2C_Delay();
    I2C_SCL_PIN = 0;
}

void I2C_NotAcknowledge() {
    I2C_SDA_PIN = 1;
    I2C_SCL_PIN = 1;
    I2C_Delay();
    I2C_SCL_PIN = 0;
}

unsigned char I2C_WaitForAck() {
    unsigned char ackBit;
    I2C_SDA_PIN = 1;
    I2C_SCL_PIN = 1;
    ackBit = I2C_SDA_PIN;
    I2C_Delay();
    I2C_SCL_PIN = 0;
    return ackBit;
}

AT24C02 Application Layer (eeprom_handler.c)

#include "i2c_driver.h"
#define EEPROM_DEVICE_CODE 0xA0

void EEPROM_ByteWrite(unsigned char memLoc, unsigned char datVal) {
    I2C_Initiate();
    I2C_Transmit(EEPROM_DEVICE_CODE);
    I2C_WaitForAck();
    I2C_Transmit(memLoc);
    I2C_WaitForAck();
    I2C_Transmit(datVal);
    I2C_WaitForAck();
    I2C_Terminate();
}

unsigned char EEPROM_ByteRead(unsigned char memLoc) {
    unsigned char readData;
    I2C_Initiate();
    I2C_Transmit(EEPROM_DEVICE_CODE);
    I2C_WaitForAck();
    I2C_Transmit(memLoc);
    I2C_WaitForAck();
    I2C_Initiate();
    I2C_Transmit(EEPROM_DEVICE_CODE | 0x01);
    I2C_WaitForAck();
    readData = I2C_Receive();
    I2C_NotAcknowledge();
    I2C_Terminate();
    return readData;
}

Main Application Example (main.c)

#include <REGX52.H>
#include "eeprom_handler.h"
#include "delay.h"

unsigned char counterMin, counterSec, counterHun;

void main() {
    // Read saved values from EEPROM
    counterMin = EEPROM_ByteRead(0x00);
    counterSec = EEPROM_ByteRead(0x01);
    counterHun = EEPROM_ByteRead(0x02);

    while(1) {
        // Application logic to update counterMin, counterSec, counterHun...
        // ...

        // Example: Save values to EEPROM on a button press (simulated)
        if(/* save condition */) {
            EEPROM_ByteWrite(0x00, counterMin);
            DelayMs(5); // Wait for write cycle
            EEPROM_ByteWrite(0x01, counterSec);
            EEPROM_ByteWrite(0x02, counterHun);
        }
    }
}

Debugging and Timing Considerations

Using a logic analyzer to capture SCL and SDA waveforms is invaluable for verifying I2C timing and debugging communication errors. Ensure the 5 ms write cycle delay is respected after any write operation before initiating a read. Avoid placing lengthy display update routines inside timer interrupt service routines, as this can cause missed button presses by delaying the execution of key scanning code.

Tags: 8051 I2C eeprom AT24C02 microcontroller

Posted on Thu, 10 Sep 2026 16:34:58 +0000 by spode