Interfacing an AVR to a 24C16 TWI EEPROM

(mega8 Example Code in C!)

[ Hardware ] [ Write Operations ] [ Write Code Example ] [ Read Operations (with code) ] [ Complete File ]

Hardware

This TWI code example for communicating with a 24C16 EEPROM also uses a 16*4 LCD for debugging. Please have a look at the LCD C Code in the LCD section for a short description on the functions used (they're all pretty simple). The LCD is connected as explained there. Everything needed for the TWI connection is the TWI bus itself, plus ground and supply voltage for the EEPROM and pull-up resistors on the TWI lines. On the mega8, SCL is at PortC.5 and SDA is at PortC.4. The EEPROM pinout is shown below:

<- Pinout of the 24C16 DIP package

The EEPROM has more pins than just SCL, SDA and power. First of all, there's three address pins (A0..A2), which have no effect for this type of EEPROM (just don't connect them). Smaller EEPROMs (24C08) use them. The memory size of the 24C16 is 16k bits (!) which makes 2k bytes. They are organised in 128 pages with 16 bytes each. The upper three page address bits are transferred together with the slave address (which is 0xA0 for writing and 0xA1 for reading).

The WP pin can be used to protect certain memory areas from being overwritten (calibration values for example). If tied to Vcc, the upper half memory array is protected. When WP is tied to ground, the whole EEPROM can be written to. We'll tie it to ground for now.

Write Operations

You can write single bytes or whole 16 byte pages to the EEPROM. Both write operations have the same structure. Here's the byte-write figure:

First of all, a start condition is generated. Then the slave address byte is transferred. It consists of the bare slave address (upper nibble: 0xA0 = 0b10100000 for better viewing), the upper three page select bits (bits 1..3) and the read/write bit (LSB) which is zero for writing. This address block is ACKed by the EEPROM if the EEPROM is present and NOT BUSY (!). If the EEPROM is busy, it will not respond with an ACK. Then the word address is transferred. Don't mind the "*" in the figure, it's for the 1K version of the EEPROM. In our case, the word address has 8 bits. This address is also ACKed by the EEPROM.

The difference between byte-write and page-write is just the number of data bytes that are transferred now. For a byte write just transfer one data byte (which is again ACKed by the EEPROM if everything's alright). For a page write, transfer up to 16 bytes. If more than 16 bytes are transferred, the page address counter will roll over to the first address of the current page.

When everything is done, the master generates a Stop condition. The EEPROM should disconnect itself from the bus and enter some kind of power-save mode.

After every TWI operation, the TWI will set the TWINT flag and return a status code in TWSR. TWINT is NOT set after the TWI generated a Stop condition (why should it?). Our code will tell the TWI what to do, then wait for TWINT being set and then check the status code to see if everything is right. Depending on the status of the operation that was completed, it will print success/error messages on the LCD.

Before we can do any printing, we'll have to run through some init code though:

LCD_init();
TWBR = 32;

LCD_init() will initialize the LCD (8 bit interface, 2 lines, 5*7 font, auto-inc cursor, cursor on and blinking). TWBR is the TWI bit rate register. At 8 MHz, a value of 32 will result in a SCL frequency of 100kHz.

Write Code Example

The first thing we'll need is some function that initiates TWI operations, such as generating Start and address transfer. As the TWI won't do anything while TWINT is set, our function will also make sure that TWINT is cleared when writing TWCR. TWINT is cleared by writing a 1 to it. Then we'll wait for the TWI hardware to set TWINT again and return the status code from TWSR:

char TWI_action(char command)
{
//write command to TWCR and make sure TWINT is set
TWCR = (command | (1<<TWINT);
//now wait for TWINT to be set again (when the operation is completed)
while (!(TWCR & (1<<TWINT)));
return TWSR;
}

The status codes are a good and rich source for errors. If the application checks for errors by looking at the status codes, it can happen that the *wrong* status code is expected (especially when reading from the EEPROM). This is not too dangerous now, but I thought it might be worth mentioning. The status codes are divided into four groups: Master Transmitter Mode (MT), Master Receiver Mode (MR), Slave Transmitter Mode (ST) and Slave Receiver Mode (SR). The slave modes are not interesting now. The tables are in the mega8 datasheet (print them out). When switching between these modes, it can happen that status codes get mixed up. For writing, only MT mode is used. Reading from the EEPROM also uses MR mode.

Here's the code for sending a Start condition and the slave address. We'll write to page 0, byte 0. The word address and data transfer is described seperately (but it's similar).

//send start. the expected status code is 0x08
if(TWI_action((1<<TWINT)|(1<<TWEN)|(1<<TWSTA)) == 0x08)
//if that worked, print 'S' on the LCD
LCD_putchar('S');
else
//if something went wrong, print 'E'
LCD_putchar('E');
 
//wait for the LCD to finish the character write (just for safety...)
LCD_wait();
//now send slave address, expected status code 0x18 (ACK received)
TWDR = 0xA0;
if(TWI_action((1<<TWINT)|(1<<TWEN)) == 0x18)
LCD_putchar('A');
else
LCD_putchar('N');

That's all you need for addressing a slave. The TWI hardware will return different status codes for data sent AFTER the slave address. On the bus side, these transfers are equal, but the status codes are different. That's why I've divided the code into two parts. The word address and the data byte are true data transfers:

LCD_wait();
//send word address 0x00, expected status code is 0x28 (ACK)
TWDR = 0;
if(TWI_action((1<<TWINT)|(1<<TWEN)) == 0x28)
//if word address ACKed, print 'W', else print 'N' on the LCD
LCD_putchar('W');
else
LCD_putchar('N');
 
LCD_wait();
//now send the data byte. We'll use 0x55. Again, the expected status code is 0x28.
TWDR = 0x55;
if(TWI_action((1<<TWINT)|(1<<TWEN)) == 0x28)
//if data ACKed, print 'D', else print 'N' on the LCD
LCD_putchar('D');
else
LCD_putchar('N');

The very first 24C16 memory location should now be ready to be verified as 0x55.

If we wanted to write the whole page (or parts of it), we would just write more data bytes now (up to 16 byte in total plus the word address first):

Both byte write and page write are terminated with a stop condition:

LCD_wait();
TWCR = ((1<<TWINT)|(1<<TWEN)|(1<<TWSTO));
LCD_putchar('P');

The LCD should now show "SAWDP" for Start - Slave Address ACK - Word Address ACK - Data ACK - Stop. The TWI hardware does not leave any specific status code after generating the Stop condition. TWINT will also not be set. If the EEPROM is not connected to the bus, the LCD will show "SNNNP".

Read Operations

Read operations will put the TWI in two different states: MT mode and MR mode. There are three different read operations: The current address read, the random read (from a specific address) and the sequential read. The current address read just consists of a single read transfer without word address:

The master generates a Start condition, then sends the slave address (now with the R/W bit set for reading). The slave responds with an ACK. Then the master reads the data from the slave (SCL driven by the master, SDA driven by the EEPROM) and sends a NACK afterwards indicating that it does not want to read any more data. Then a Stop is generated by the master.

When reading from a specific address, the word address is transferred first (as in a data write operation). Then a repeated start is generated and the data is read:

Now it's important to understand which transfer modes the master is in: The first time the device address is sent, the master is in MT mode for both the address and the word address transfer. Then, after the repeated start condition, the master is is MR mode. This important, because the status codes come from different tables. Here's the complete random read code (read from address 0):

LCD_wait();

//send start
if(TWI_action((1<<TWINT)|(1<<TWSTA)|(1<<TWEN)) == 0x08)
LCD_putchar('S');
else
LCD_putchar('E');
LCD_wait();

//send slave address
TWDR = 0xA0;
if(TWI_action((1<<TWINT)|(1<<TWEN)) == 0x18)
LCD_putchar('A');
else
LCD_putchar('N');
LCD_wait();

//send word address
TWDR = 0x00;
if(TWI_action((1<<TWINT)|(1<<TWEN)) == 0x28)
LCD_putchar('W');
else
LCD_putchar('N');
LCD_wait();

//repeated start
if(TWI_action((1<<TWINT)|(1<<TWSTA)|(1<<TWEN)) == 0x10)
LCD_putchar('S');
else
LCD_putchar('E');
LCD_wait();

//send slave address, read bit = 1; MR mode!
TWDR = 0xA1;
if(TWI_action((1<<TWINT)|(1<<TWEN)) == 0x40)
LCD_putchar('A');
else
LCD_putchar('N');
LCD_wait();

//now, in MR mode, get data byte. We don't set TWEA, so no ACK is sent afterwards:
TWI_action((1<<TWINT)|(1<<TWEN));
if (TWDR == 0x55)
LCD_write("read OK");
else
LCD_write("read error");

//send stop
TWCR = ((1<<TWINT)|(1<<TWSTO)|(1<<TWEN));

For this to work as expected, the first memory location should hold 0x55. If everything is right, the LCD should show "SAWSAread OK" after such a read operation.

As you can see in this example, it's very important to look at the right status code!

It is also possible to do a sequential read (of the whole EEPROM if required). To do that, just write the desired start address after sending the slave address, and read as man bytes as you wish, each time sending an ACK. After the last byte, a NACK has to be sent:

The address pointer will roll over to address 0 after the last byte in memory has been read (it does NOT roll over page-wise like in a write page operation!). You can read out all 2k bytes in one go if you want.

I've put these examples in a file, with read and write functions for single bytes and pages. Though not all functions are used by the main code, they have all been tested.

24C16.c
lcd.h and the makefile (atmega8, avrdude programmer: stk500) are also required. The makefile has been generated with mfile. mfile can be downloaded from the http://winavr.sourceforge.net/ news page.

The files contain connecting information.