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:
- Start condition.
- Send 7-bit slave address + Write bit (0).
- Wait for ACK.
- Send 8-bit memory address.
- Wait for ACK.
- Send 8-bit data byte.
- Wait for ACK.
- Stop condition.
Read Sequence:
- Start condition.
- Send 7-bit slave address + Write bit (0).
- Wait for ACK.
- Send 8-bit memory address.
- Wait for ACK.
- Repeated Start condition.
- Send 7-bit slave address + Read bit (1).
- Wait for ACK.
- Receive 8-bit data byte.
- Send NACK to signal end of read.
- 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.