Wednesday, July 3, 2013

Accessing 24LC512 EEPROM: TWI/I2C emulation

Theory
Please refer to official 24LC512 specification and appropriate article at the Wikipedia for detailed information on 24LC512 EEPROM and theoretical vision of TWI/I2C protocol:






Practice
The following connection scheme and code is intended to be used with Atmel AT90USB162 MCU. First of all, we need to choose which MCU pins will be our SCL and SDA lines, I have chosen PB1 (pin 15) for SCL and PB2 (pin 16) for SDA. Refer to the Fritzing circuit in the previous post for connection scheme.

Define ports:
#define SCLPORT  PORTB
#define SCLDDR  DDRB

#define SDAPORT  PORTB
#define SDADDR  DDRB

#define SDAPIN  PINB
#define SCLPIN  PINB

#define SCL  PB1
#define SDA  PB2
SCL and SDA signal edges:
#define TWI_SDA_LOW SDADDR |= (1 << SDA)
#define TWI_SDA_HIGH SDADDR &= ~(1 << SDA)

#define TWI_SCL_LOW SCLDDR |= (1 << SCL)
#define TWI_SCL_HIGH SCLDDR &= ~(1 << SCL)
24LC512 commands, ACK/NACK (acknowledge) and delays definitions:
#define _24LC512_WRITE  0b10100000
#define _24LC512_READ   0b10100001

#define ACK_ACK         1
#define ACK_NACK        0

#define Q_DEL           _delay_loop_2(3);
#define H_DEL           _delay_loop_2(5);
TWI/I2C commands (Init/Start/Stop/Write/Read):
void TWI_Init (void) {
    SDAPORT &= ~(1 << SDA);
    SCLPORT &= ~(1 << SCL);
    TWI_SDA_HIGH;   
    TWI_SCL_HIGH;
}

void TWI_Start (void) {
    TWI_SCL_HIGH;
    H_DEL;
    TWI_SDA_LOW;
    H_DEL;
}

void TWI_Stop (void) {
    TWI_SDA_LOW;
    H_DEL;
    TWI_SCL_HIGH;
    Q_DEL;
    TWI_SDA_HIGH;
    H_DEL;
}

uint8_t TWI_Write (uint8_t Data) {
    uint8_t i;
    uint8_t ACK;
    for (i = 0; i < 8; i++) {
        TWI_SCL_LOW;
        Q_DEL;
        if (Data & 0x80) {
            TWI_SDA_HIGH;
        }
        else {
            TWI_SDA_LOW;
        }
        H_DEL;
        TWI_SCL_HIGH;
        H_DEL;
        while ((SCLPIN & (1 << SCL)) == 0);
        Data = Data << 1;
    }
    TWI_SCL_LOW;
    Q_DEL;
    TWI_SDA_HIGH;
    H_DEL;  
    TWI_SCL_HIGH;
    H_DEL;
    ACK = !(SDAPIN & (1 << SDA));
    TWI_SCL_LOW;
    H_DEL;
    return ACK;
}

uint8_t TWI_Read (uint8_t ACK) {
    uint8_t Data = 0x00;
    uint8_t i;
    for (i = 0; i < 8; i++) {
        TWI_SCL_LOW;
        H_DEL;
        TWI_SCL_HIGH;
        H_DEL;  
        while ((SCLPIN & (1 << SCL)) == 0);
        if (SDAPIN & (1 << SDA)) {
            Data |= (0x80 >> i);
        }
    }
    TWI_SCL_LOW;
    Q_DEL;
    if (ACK) {
        TWI_SDA_LOW;
    }
    else {
        TWI_SDA_HIGH;
    }
    H_DEL;
    TWI_SCL_HIGH;
    H_DEL;
    TWI_SCL_LOW;
    H_DEL;
    TWI_SDA_HIGH;
    return Data;
}
Finally, functions for accessing 24LC512 EEPROM:
uint8_t _24LC512_WriteByte (uint16_t Address, uint8_t Data) {
    TWI_Start();
    if (!TWI_Write(_24LC512_WRITE)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address >> 8)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Data)) {
        TWI_Stop();
        return FALSE;
    }
    TWI_Stop();
    _delay_ms(5);
    return TRUE;
}

uint8_t _24LC512_WritePage (uint16_t Address, uint8_t* Page, uint8_t Count) {
    TWI_Start();
    if (!TWI_Write(_24LC512_WRITE)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address >> 8)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address)) {
        TWI_Stop();
        return FALSE;
    }
    while (Count--) {
        if (!TWI_Write(*Page++)) {
            TWI_Stop();
            return FALSE;
        }
    }
    TWI_Stop();
    _delay_ms(5);
    return TRUE;
}

uint8_t _24LC512_ReadByte (uint16_t Address, uint8_t* Data) {
    TWI_Start();
    if (!TWI_Write(_24LC512_WRITE)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address >> 8)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address)) {
        TWI_Stop();
        return FALSE;
    }
    TWI_Start();
    if (!TWI_Write(_24LC512_READ)) {
        TWI_Stop();
        return FALSE;
    }
    *Data = TWI_Read(ACK_NACK);
    TWI_Stop();
    return TRUE;
}

uint8_t _24LC512_ReadPage (uint16_t Address, uint8_t* Page, uint8_t Count) {
    TWI_Start();
    if (!TWI_Write(_24LC512_WRITE)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address >> 8)) {
        TWI_Stop();
        return FALSE;
    }
    if (!TWI_Write(Address)) {
        TWI_Stop();
        return FALSE;
    }
    TWI_Start();
    if (!TWI_Write(_24LC512_READ)) {
        TWI_Stop();
        return FALSE;
    }
    Count--;
    while (Count--) {
        *Page++ = TWI_Read(ACK_ACK);
    }
    *Page++ = TWI_Read(ACK_NACK);
    TWI_Stop();
    return TRUE;
}
That's it, very simple.
Download full library sources: