Using I2C to read and write EEPROM

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Using I2C to read and write EEPROM

4,708 Views
aberger
Contributor V

I am trying to use the Kinetis SDK v2.3 with a TWR-K64F to read and write a 16kBit serial EEPROM (Microchip 24LC16B). Specifically, I am using the i2c_polling_b2b_transfer_master example and have simply changed the 7-bit slave address to that of the EEPROM (in this case, 0x50, which is just the control code (0b1010, as specified in the datasheet), plus a page address of 000).

I also specify

uint8_t deviceAddress = 0x00U;

Otherwise, I use the example code as-is.

The semihosting console output from running this code is:

I2C board2board polling example -- Master transfer. Master will send data : 0x 0 0x 1 0x 2 0x 3 0x 4 0x 5 0x 6 0x 7 0x 8 0x 9 0x a 0x b 0x c 0x d 0x e 0x f 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f

Receive sent data from slave : 0x1f 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff

Error occured in the transfer !

End of I2C example .

I've tried to step through the code in debug mode, but it's very hard to tell where things are going wrong. Is there any documentation of the I2C API that demonstrates the state machine that is implemented?

Labels (1)
Tags (4)
0 Kudos
6 Replies

2,699 Views
D4Worker
Contributor II

you don't read what you written onto the eeprom?

have you correctly manage the "page" on the eeprom? 
are you incorrectly read/write the same location but on a wrong page?

0 Kudos

3,803 Views
aberger
Contributor V

I was able to sort this out. There were two issues:

1. The EEPROM that I am using has only a 16-byte page memory. If you try to write more than 16 bytes in a row (without sending a STOP) condition, the data will wrap around and start overwriting at the start of the page. To fix this, I simply changed 
#define I2C_DATA_LENGTH 33U

to

#define I2C_DATA_LENGTH 10U

2. The example code from the SDK was also sending out the data length (originally, "32") as the first transmitted byte. But the comparison between master and slave data was ignoring this 0th element (I don't know why), so the data stored in the EEPROM were all shifted by one register. To fix this, I simply deleted the line

g_master_txBuff[0] = I2C_DATA_LENGTH - 1U;

and instead fill the txBuff using a FOR loop with index starting from element zero.

So in summary, there is no issue with the API provided by SDK 2.3.0.

0 Kudos

3,803 Views
mjbcswitzerland
Specialist V

Andy

I think your code writes a buffer of data and reads a buffer of data but doesn't control the EEPROM's internal address. That means that (assuming after a power cycle) it will write to the page address 0, 1, 2,3,..n and then read back from the address n+1, n+2, n+3 etc. which will not read back the data that has been written (and the compare will fail).

This means that you need to control the internal address pointer (the first byte of the data sent when writing and also write it just before reading back again, as described in the EEPROM's data sheet).

The uTasker project includes support for the EEPROM that you are using (it also simulates it) and below is the reference code to write and read to/from page 3 (for example).

#define ADD_EEPROM_READ           (0xa1 + (3 << 1))                     // read address of EEPROM (page 3)
#define ADD_EEPROM_WRITE          (0xa0 + (3 << 1))                     // write address of EEPROM (page 3)
#define OUR_I2C_CHANNEL           0                                     // use I2C 0

static QUEUE_HANDLE I2CPortID = NO_ID_ALLOCATED;                        // handle of I2C interface

// Open I2C interface to communicate with an EEPROM, RTC, etc.
//
static void fnConfigI2C_Interface(void)
{
    I2CTABLE tI2CParameters;

    tI2CParameters.Channel = OUR_I2C_CHANNEL;
    tI2CParameters.usSpeed = 100;                                        // 100k
    tI2CParameters.Rx_tx_sizes.TxQueueSize = 64;                         // transmit queue size
    tI2CParameters.Rx_tx_sizes.RxQueueSize = 64;                         // receive queue size
    tI2CParameters.Task_to_wake = 0;                                     // no wake on transmission

    if ((I2CPortID = fnOpen(TYPE_I2C, FOR_I_O, &tI2CParameters)) != NO_ID_ALLOCATED) { // open the channel with defined configurations
        static const unsigned char ucSetEEPROMAddress0[] = {ADD_EEPROM_WRITE, 0}; // command to set address to read to 0
        static const unsigned char ucReadEEPROM[] = {16, ADD_EEPROM_READ, OWN_TASK}; // command to start a read of 16 bytes with the task scheduled when the read has completed
        fnWrite(I2CPortID, (unsigned char *)ucSetEEPROMAddress0, sizeof(ucSetEEPROMAddress0)); // write the EEPROM address to read
        fnRead(I2CPortID, (unsigned char *)ucReadEEPROM, 0);             // start the read process of 16 bytes
    }
}

Reception handler

if (fnMsgs(I2CPortID) != 0) {                                        // if I2C message waiting
    while ((Length = fnRead(I2CPortID, ucInputMessage, MEDIUM_MESSAGE)) != 0) {
        static const unsigned char ucSetWriteEEPROM1[] = {ADD_EEPROM_WRITE, 3, 5}; // prepare write of one byte to address 3
        static const unsigned char ucSetWriteEEPROM2[] = {ADD_EEPROM_WRITE, 5, 3, 4, 5, 6, 7, 8, 9, 10}; // prepare write of multiple bytes to address 5
        int x = 0;
        while (x < Length) {                                         // display received bytes
            fnDebugHex(ucInputMessage[x++], (WITH_LEADIN | WITH_SPACE | 1));
        }
        fnDebugMsg("\r\n");
        // Now change the contents using two writes
        //
        fnWrite(I2CPortID, (unsigned char *)&ucSetWriteEEPROM1, sizeof(ucSetWriteEEPROM1)); // start single byte write
        fnWrite(I2CPortID, (unsigned char *)&ucSetWriteEEPROM2, sizeof(ucSetWriteEEPROM2)); // start page write
    }
}


On first use it reads and display the first 16 bytes in the page, then write 0x05 to address 3, followed by 3,4,5,..0xa to a addresses 5..12.

After a power cycle the values written (to verify they are non-volatile) are shown to be there as expected.

pastedImage_1.png

A binary file to do this on the FRDM-K64F is attached, whereby the I2C connections are on PTE24 and PTE25 and the debug interface on OpenSDA COM at 115200Baud.

pastedImage_2.png

Regards

Mark

Complete K64 solutions, training and support: http://www.utasker.com/kinetis.html
Kinetis K64:
- http://www.utasker.com/kinetis/FRDM-K64F.html
- http://www.utasker.com/kinetis/TWR-K64F120M.html
- http://www.utasker.com/kinetis/TEENSY_3.5.html
- http://www.utasker.com/kinetis/Hexiwear-K64F.html

I2C: http://www.utasker.com/docs/uTasker/uTasker_I2C.pdf

0 Kudos

3,803 Views
aberger
Contributor V

I don't think it's the case that I am not controlling the internal address pointer of the EEPROM. The API function I2C_MasterTransferBlocking takes a pointer to the data type i2c_master_transfer_t, which includes the slave address, subaddress, and subaddressSize as its members. (Annoyingly, the documentation for what subaddress and subaddressSize mean is completely missing, but I believe that subaddress is the first 8-bit register you wish to access and subaddressSize is the size in bytes of each register of the slave).

As far as I can tell, the I2C_MasterTransferBlocking proceeds as follows (for a read operation):

1. After some initial setup and status checks, I2C_MasterStart is called to initiate a transfer. This is responsible for sending the start signal, followed by the 7-bit slave address, plus the R/W bit. 

2. Following some error checking, the subaddress is sent. I believe this is what the EEPROM data sheet considers the "Word Address."

3. For a read operation, a repeated START signal is sent. This should initiate the "Sequential Read" mode starting from the Word Address of step (2).

4. I2C_MasterReadBlocking is then called, reading in each byte until the rxSize counter indicates that only one byte is remaining, after which the master sends a NACK to stop the read operation.

I altered the i2c_polling_b2b_transfer_master.c code to perform an initial read of the EEPROM, followed by a write, and finally a second read. This indicates that the internal address pointer for the EEPROM is being controlled, because the the initial and final read operations output the same data (i.e., the address pointer is not simply incrementing from 0,1,2,...n-1, then from n, n+1,...2*n-1, and finally from 2n, 2n+1,...3n-1, for the read, write, and read operations respectively). 

Thank you for the response, but I would prefer to avoid adding the complication of uTasker to my current project as it's already pretty far along. It would be much more helpful to understand where the I2C communication between the K64 and the EEPROM is going wrong.

0 Kudos

3,079 Views
RosimarSilva
Contributor I

I was also having this same problem, my solution was that when sending the memory address to be read to the slave, the length would be 2 bytes. I made this change and my problem was resolved.

follow the example ..
I just changed that data from:
masterXfer.subaddressSize = 1;

for:
masterXfer.subaddressSize = 2; // address in 2 bytes

0 Kudos

3,803 Views
mjbcswitzerland
Specialist V

Andy

I don't know the API that you are using but had the impression that it wasn't addressing correctly, but it may be a different problem (a driver bug or incorrect use of the API) so you will need to debug and best use a logic analyser to see what is on the bus (to avoid guess work).

Regards

Mark

0 Kudos