23 February 2013

Tutorial 20c: The 24xx08

While setting up the USI for I2C is more complicated, the value of that complexity becomes apparent when you look at a real implementation of the I2C protocol. The Microchip 24AA08 EEPROM is the I2C equivalent to the 25AA080 we used as an example for SPI. The I2C version of this EEPROM is substantially simpler.

Instructions

This chip, unlike its SPI counterpart, has no status register. Write protection is handled strictly by hardware through a single pin connected to either ground (write enable) or Vcc (write protect). There are really only two instructions, and the read and write instructions can be done by single bytes or by up to 16 bytes (a page). The seek instruction is really nothing more than a write instruction without sending any data to write.

  • Seek: The 24xx08 has a register that points to the current address in its memory for the next read/write command. We can change the current address by sending the slave address + write bit (a 0 in the 8th bit sent) followed by the address to which the slave should point. Each of these is, of course, followed by an acknowledge bit. The master issues a stop condition immediately after the address.
  • Write: We write a value to the 24xx08 by sending the slave address + write bit followed by the memory address to which the value should be written. This is followed by the 8-bit value to write and a stop condition. We can write up to 16 bytes in contiguous memory space by sending subsequent values before the stop condition.
  • Read: We read a value from the 24xx08 by sending the slave address + read bit (a 1 in the 8th bit sent). After acknowledging the command, the slave sends the 8 bit value at the current address on the next 8 clock cycles and increments the address pointer to the next space. If the master acknowledges, the slave will repeat by sending the next value. When all values required are read, the master sends a nack, indicating no more data is to be sent, and then issues a stop condition. If a value is required from a different address than the one to which the 24xx08's register currently points, a seek instruction should be sent before the read instruction.

Design Considerations

A quick look at the datasheet for the 24xx08 is worthwhile. There are a few things to note about this device.
  • Address: The 7-bit binary address for this device is 0b1010XBB. The bit labeled X is unused in this device, and can take either value. The last two bits BB refer to the block within the chip. The memory space is divided into four, 256-byte blocks. (Each byte within a block has an 8-bit address between 0 and 255. The X bit here would be used in a larger device with 8 blocks in memory.) Note that this means that only one of this particular device can be used on a given I2C bus; adding a second 24xx08 device, or any Microchip I2C EEPROM in fact, would create an address conflict on the bus. Adding the 8th R/W bit, this corresponds to slave addresses from 0xA0 to 0xA7 in hex. (That uses 0 as the X bit. Technically a 1 can be used here as well, so the addresses 0xA8 through 0xAF are respectively equivalent in this device.)
  • Most packages of this chip have three pins labeled A0, A1, and A2. In this particular device, these pins are unused and unconnected internally. Larger devices would use these in place of the block bits to determine the chip's address, allowing for up to 8 of the same chip on the bus. For this chip, the pins can be grounded, tied to Vcc, or even left floating.
  • Most of the timing requirements specified in the datasheet are good to see, but not an issue at the transmission rates we'll be using. (The device is rated for up to 400 kHz, and even at that rate the minimum requirements are well below what is actually used.) The important one for our purposes, however, is the maximum rise and fall times, TR and TF. These define the maximum RC time constant for the SDA and SCL lines. This device is rated with a 10 pF capacitance on the lines. The GPIO pins of the MSP430 are rated at 5 pF. The lines connecting the two chips will add a little more capacitance. Using a worst-case estimate of 30 pF and the maximum rise/fall times of 300 ns, we get a maximum resistance value of 10 kΩ.
  • Faster rates, as you recall, will benefit from lower resistances, at the expense of power consumption. The internal resistors available in the MSP430 are between 20 and 50 kΩ. For slow data rates, these are probably alright to use. But faster rates may require an external resistor in the 1 to 10 kΩ range.
  • Like the SPI version of this chip, the write process is not initiated until a stop condition is seen, and a 5 ms settling period is required for write operations whether writing a single byte or a full 16-byte page. Using a similar setup to the previous tutorial, we'll have to keep that in mind when using a 9600 baud UART to send the data being written. If any instruction is issued to the device during this settling time, the chip will not respond. The master can identify the nack by polling, in this case, and wait until an ack bit is sent before sending the next instruction.
And that's really about all you need to know to use this EEPROM successfully with the MSP430! The wire connections are just as straight forward, as only 4 wires are needed: Vcc, ground, SDA, and SCL. The WP pin of the 24xx08 should be tied to ground for write operations.

Examples

This example starts off simply, writing a single value to every memory space one by one. Since we need 5 ms between write commands, a simple delay is used in the code. Ideally, you would allow the MSP430 to continue with other tasks, and have a timer to indicate when it is ready to write again, but for this example a simple method works fine. The code provided uses a default fill value of 0xff, to act as an eraser of the flash memory. Feel free to change the fill_char value to whatever you wish; hex, decimal, and C character values are all acceptable here.
This example reads the entire contents of the EEPROM and dumps the data via UART. If you ran the i2cerase_G2231.c code previously, you should see 1024 instances of 0xff appear in your terminal (assuming it has the capability of displaying this code; you might change to a visible character if not). In this example, I've used a low power mode to hold the main function until a value has been read by I2C  Uncommenting the _low_power_mode_off_on_exit() command at the end of USI_ISR signals the data is ready to be transmitted by UART. A delay loop is used again to delay the next read, though ideally a low power mode could be used again here.
The final example demonstrates writing in full pages of 16 bytes. The first 1024 characters received by UART are written to the EEPROM. Like the SPI example, you can use a text file to send particular ASCII data to the EEPROM. After sending the data, use i2cread_G2231.c again to display the message on your terminal. The page write itself is used to accommodate the EEPROM settling time: the amount of time it takes to send 16 bytes at 9600 baud is longer than the required 5 ms.

Race Conditions

When designing these examples, I ran into a bug that took me a long while to solve, so I thought I'd mention it here. What I finally figured out was that I was hitting what's called a race condition. Originally, my main function in i2cwrite_G2231.c looked like this:
    for(i=0; i<64; i++) {
     slave_address = SAdrs + i/16; // Current Block Address
     pageBuffer[0] = (i%16)*16; // EEPROM Write Address
     for(j=1; j<17; j++) {
     _BIS_SR(LPM0_bits + GIE);   // Standby for UART RX
    pageBuffer[j] = RXBuffer;   // Extract RXBuffer
    }
    while(SCFG & BIT4) // Ensure previous transmission is complete
    _nop();
     SCFG |= BIT3; // Mark transmission/write instruction
    USICTL1 |= USIIFG; // Set USI Interrupt to start
    // I2C communication
    }
The problem was that the first element of pageBuffer would be overwritten shortly after the USI interrupt was started-- before the USI could transmit the original value! Once you start mixing peripherals and making extensive use of interrupts, you need to be very careful of race conditions: when values you want to use are changed before you actually use them. The solution here, as seen in the linked code example, was to not change the slave and memory addresses until a new page of 16 bytes was ready to be sent. That prevented the race condition by ensuring the value was not changed until the USI was clear.

Note that there is still a potential race condition due to the UART: if you speed up the UART (likely you'd need to slow down the I2C bus too), you could receive new data before the old data is transmitted to the EEPROM. Of course, increasing the UART speed could cause problems with the settling time as well.

That concludes this particular tutorial; I'll move on to some other topics now, and bring a new experiment to the blog that will illustrate a fun use of I2C.

1 comment:

Yann said...

Just to tell you I am literally drinking each line of this blog since weeks and I have to say it is just amazing.

I really would like to congratulate you for this great (and huge) work that is probably helpfull for so much people (including me !).

Thank you again !