I2C EEPROM Access

From Nutwiki
Revision as of 13:53, 11 September 2009 by AdrianPyka (Talk) (Authors Remarks)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Description

This text explains how to access an I2C EEPROM. Even the example in nut/app/eeprom-test relates to an AT24C EEPROM, there is no difference in using any other I2C eeprom besides some modifications of the hardware description struct.
Even better, this works also with non-EEPROM memories like FRAM and others too.


Prerequisites

This text relates to the AT91 implementation of NutO/S in the latest trunk version. It is not working with AVR as some functions are not implemented until now. They will be implemented in my next project.

Source Code

<source lang="c">

/*

* Register the UART device, open it, assign stdout to it and set
* the baudrate.
*/

NutRegisterDevice(&DEV_DEBUG, 0, 0); freopen(DEV_DEBUG_NAME, "w", stdout); _ioctl(_fileno(stdout), UART_SETSPEED, &baud);

printf( "\n*** EEPROM Test ***\n");

baud = 100000; TwInit( 0 ); /* par = slave address but we are master */ TwIOCtl( TWI_SETSPEED, &baud); EEInit(); /* Initialize EEPROM access </source>

This part initalizes the controller like anyone would do it. I2C related is the part starting with baud = 100000; In this case we reuse the int for baudrate programming of the USART to setup the speed of the I2C communication. After that we call TwInit( 0); 0 means that we are the master. See http://www.ethernut.de/nutwiki/I2C for details of I2C communication.

Like USART or SPI, the TwIOCtl() call sets up special parameters, in this case the speed.

EEInit() is a simple function, that initializes the EEPROM for our usage. As there is no real bus action to be don for initializing the EEPROM, this function only copies over some parameters to a struc, describing the EEPROM: <source lang="c"> static struct at24c at24c32s;

/****************************************************************************/ void EEInit( void ) /****************************************************************************/ {

   at24c32s.PageSize = 32;               /* Size of one page */
   at24c32s.NumOfPage = 128;             /* Number of pages ==obsolete== */
   at24c32s.EepromSize = 32*128;         /* Size of the chip */
   at24c32s.SlaveAddress = I2C_SLA_AT24C;/* Slave address of the chip on the bus */
   at24c32s.IAddrW = 2;                  /* Width of the IADDR register */
   at24c32s.Timeout = 20;                /* Failure timeout for chip access */

} </source> This function is only implemented for backward compatibility.
For future projects I reccomment to simply define a <source lang="c"> const struct at24c myeeprom = { 32, 128, 23*128, I2C_SLA_AT24C, 2, 20 }; </source> This results in better overview and less code. As we will see later, it is allowed to define as many of these structs as I2C memories are implemented.

Chip specification


at24c32s.PageSize
EEPROMs are page oriented. So one has to keep an eye on those page boundaries. Writing over a page results in a wraparound, overwriting data at the beginning of the page.
EEPROMs usually start programming after they received a STOP condition. With this parameter the driver can calculate where to write data and keep track of the page boundaries

at24c32s.NumOfPage
In the past several different implementations of 'how to track chipsize' where found. I hop no one is using this parameter today, but I kept it for backwards comptibility. Me and my driver don't need it, so I declare it obsolete.

at24c32s.EepromSize
The EEPROM size is used for programmers security. The EEPROM driver sitting right on top of the I2C driver is checking the values for writing and reading and he is returning an error if the address is out of range.

at24c32s.SlaveAddress
The slave address is the address the EEPROM is set to. Most EEPROMs have 1 to 3 address-lines that can be soldered to high or low. If your EEPROM does not work, check this first.

at24c32s.IAddrW
Now, EEPROMs are available in different sizes. While a 128Byte EEPROM can be addressed with only one address byte, a bigger one with 64kByte needs two of them. To let the driver know, how many bytes are needed, IAddrW is implemented.

at24c32s.Timeout
Most EEPROM driver applications I saw make havy use of the next value, Timeout.
Most people wait for a fixed and most times far to long time to be shure that the EEPROM has written its new data. The new implementation uses ACK-Polling and the Timeout is a security value that only gets the driver out of an endless loop, if the EEPROM or the bus is dead for any unknown reason.

Ack-Polling

EEPROMs, busy with writing data, are internally detaching from the I2C bus. So if we now send the EEPROMs address out, no matter for reading or writing, it will not react and we get a NACK condition (9th bit is high). As soon as the EEPROM is ready again, it will reply with ACK to the next address cycle. We use this to find out, when the EEPROM is ready for access again. If we use this mechanism, the delay between two write cycles is only about 2.5ms instead of fixed 10 to 20ms.

As a big advantage, the bus is not blocked for a fixed ammount of time and the driver is only blocking the caller task of the eeprom access. Other tasks get their time slots until we start a new polling cacle. Even better, other tasks can access i2c in the meantime. For an example I use an at24c eeprom an a PCA9555 IO-port expander. The leds connected to the expander still blink with only small timing delays even I write my 4k EEPROM completely with different patterns.

Reading and Writing

<source lang="c">

/****************************** 1st part *******************************/ /* Before starting the test, fill the EEPROM with 0xFF */ /***********************************************************************/

/* Clear the Receive buffer */

   memset( rxBuffer, 0x00, sizeof( rxBuffer));
   memset( txBuffer, 0xff, sizeof( txBuffer));
   printf( "Read\n" );
   EEReadData( 2, rxBuffer, 64);
   HexDump( rxBuffer, 64 );
   if( !strncmp( rxBuffer, teststr, strlen( teststr)))
       printf( "Test successfull, data is equal!\n");
   else
       printf( "Test failed, data not equal!\n");
   printf( "\nInit: Fill 0xFF\n" );
   EEWriteData( 0, txBuffer, 64);
   EEReadData( 0, rxBuffer, 64);
   HexDump( rxBuffer, 64 );

</source>

This excerpt from the eeprom_test.c shows how to access the EEPROM.
EEReadData() and EEWriteData() are compatibility functions to old EEPROM access routines. Careful with using them, as if the real access functions are portable to AVR, these functions might be declared obsolete.
The eeprom_test.c simply reads the EEPROM and compares the content to a fixed constant. The result is then stated through the debug interface.
After that the programm writes some different test patterns over two pages and over a page border for testing the EEPROM access. After all tests it writes the initial test string to the EEPROM. For an easy test of your hardware just run the program, reset and run again. If in the second run you see a "Test successfull, data is equal!" you won.
The real access functions for the EEPROM are these, you'll find them in nut/dev/eeprom.c: <source lang="c"> /****************************************************************************/ int EEWriteData( uint16_t addr, void *data, uint16_t len ) /****************************************************************************/ {

   return At24cWrite( &at24c32s, (uint8_t *)data, len, addr );

}

/****************************************************************************/ int EEReadData( uint16_t addr, void *data, uint16_t len ) /****************************************************************************/ {

   return At24cRead( &at24c32s, (uint8_t *)data, len, addr );

} </source>

Calling these functions by handling over the correct descriptor struct for the EEPROM enables access to more than only one of them. The only thing you need to add are the databuffer to write from or to read to, the length of the data to access and the address where in the EEPROM the data should go or come from.

T.B.C.

Authors Remarks

I'd like to extend this EEPROM access functions a bit more into general EEPROM access and include them into the memory access functions NutO/S already has. There is no difference in accessing serial memory chips, whether they are connected by I2C or SPI, besides that one has a bus address and the other has a chipselect. A unified eeprom-driver should take care of that without having to much overhead for adressing the chips.

See also