Jump to content
Sign in to follow this  
ppulle

I2c Block Read/writes Across Device Page Boundaries

Recommended Posts

Hi All,

 

I've found an issue regarding talking to one or more I2C eeprom devices using multibyte read/writes.

 

I'm using SB6.60 with a 18F4550 and talking to four 24LC1025 EEPROM devices connected to the I2C bus.

 

According to the 24LC1025 datasheet you cannot write across page boundaries, nor more than 128 bytes in one block write. So if first you wrote 120 bytes starting at zero, fine, and then you wrote another 20 bytes, you would not perform a successful write because in the second operation you would be writing over a page boundary. You would need to make three operations. The first 120 bytes, then 8 bytes and then the remaining 12 bytes.

 

Additionally you cannot read over device block boundaries. For the 24LC1025 it is divided into two blocks 0x00000...0x0FFFF and 0x10000...0x1FFFF. Thus a read of 16 adresses from 0x0FFF8 to 0x10007 will fail unless again it is split across the boundary, ie one read of 8 bytes from 0x0FFF8 to 0x0FFFF and another from 0x10000 to 0x10007.

 

Note the sample i2C code that comes with Sourceboost doesn't take this into account, and in any case the block/page boundary problem will be different from different EEPROM devices.

 

This little issue might be of interest to those having troubles writing logging software to one or more EEPROM devices.....check your datasheets!

 

So some questions:

 

1) Has anyone written I2C EEPROM code that does take this page/block boundaries into account when doing block operations? I'll chugalug along and maybe post something....but would rather have some already tried and tested code if there is any about?

 

2) After a write operation, particularly a block write operation, the master needs to wait a time before performing another write. I'm currently adding delays (for 24LC1025 I'm using a delay of 5ms) worked out by trial an error. However an alternative faster method is to use Acknowledge Polling....where you send an additional identical write sequence (to the one used to start the block write) but check if the ACK comes back from the device. If the ACK doesn't come back then the device is busy. You keep repeating until you get an ACK then do the next write sequence. From what I can tell, this is different to checking if the i2C bus is idle.

 

From a quick read of the I2C code in sourceboost it looks like the I2C code sits in an infinite loop waiting for an ACK from the device during a write....any ideas on modifying the library to perform a wait till write complete sort of function? This can avoid the need for arbitary delays....simplifying code and increasing performance.

 

FYI info on Acknowledge Polling is on Section 7 of the 24LC1025 datasheet, page boundary stuff in Section 6 and Sequential Read limitations in Section 8.3.

 

Phil

Share this post


Link to post
Share on other sites

Hi all,

Just been doing some sleuthing about the net and reading up on the datasheets.

 

Here is some code fragments that may be suitable for solving the page write and acknowledge polling problem. Note that this is set for the 24LC1024....don't know what other chips page sizes are. There is more generic code out there....this is just to show a rough solution.

 

Firstly I looked at the i2c_driver.h code....I noticed that the hardware implementation for the i2c_write function returned the error condition of the i2c transmit buffer (ie any write collisions) and not the ACK status of the i2c device. The software implementation returned the ack status. This threw me for a bit. Rather than change the existing i2c_write code. I added a new function to the i2c_driver.h file.

 

Firstly the helper function definition

////////////////////////////////////////////////////////////////////////////

// Helpers that hide template arguments

////////////////////////////////////////////////////////////////////////////

....etc

#define i2c_check_ack i2c_CHECK_ACK<i2c_ARGS>

 

Then the new function

////////////////////////////////////////////////////////////////////////////

// Generates the I2C Bus Write Condition

////////////////////////////////////////////////////////////////////////////

_I2C_TEMPL

unsigned char i2c_CHECK_ACK(unsigned char i2c_data)

{

volatile unsigned char i2c_SSPBUF@T_i2c_SSPBUF;

volatile bit l_scl@T_SCL_PORT.T_SCL_BIT, l_sda@T_SDA_PORT.T_SDA_BIT;

volatile bit l_scl_tris@T_SCL_TRIS.T_SCL_BIT, l_sda_tris@T_SDA_TRIS.T_SDA_BIT;

volatile bit l_bf@T_i2c_SSPSTAT.i2c_BF, l_ackdt@T_i2c_SSPCON2.i2c_ACKDT;

volatile bit l_sspif@T_i2c_SSPIF_PIR.T_i2c_SSPIF_BIT, l_bclif@T_i2c_BCLIF_PIR.T_i2c_BCLIF_BIT;

volatile bit l_rw@T_i2c_SSPSTAT.i2c_RW,l_wcol@T_i2c_SSPCON1.i2c_WCOL;

volatile bit l_rcen@T_i2c_SSPCON2.i2c_RCEN, l_pen@T_i2c_SSPCON2.i2c_PEN, l_sen@T_i2c_SSPCON2.i2c_SEN;

volatile bit l_rsen@T_i2c_SSPCON2.i2c_RSEN, l_acken@T_i2c_SSPCON2.i2c_ACKEN;

volatile bit l_ackstat@T_i2c_SSPCON2.i2c_ACKSTAT; //NOTE! This is added for checking ackstat

 

char BitMask;

bit local_ack;

 

l_bclif = 0; // initialise the collision flag for this command

l_sspif = 0; // clear the operation completed

 

// Hardware I2C implementation

while (l_acken || l_rcen || l_pen || l_rsen || l_sen || l_rw)

if (T_MODE & i2c_reset_wdt)

clear_wdt();

 

l_wcol = 0; // clear write collision flag

i2c_SSPBUF = i2c_data;

 

// test if a write collision occurred....note not sure how to handle this as well as return ackstat

//if (l_wcol)

// return (1); // error exit

 

// wait until MSSP Tx register is empty

while (l_acken || l_rcen || l_pen || l_rsen || l_sen || l_rw || !l_sspif)

if (T_MODE & i2c_reset_wdt)

clear_wdt();

 

return (l_ackstat); //return the ackstat bit

 

//Software implementation omitted for clarity (also I didn't need it)

 

 

}

 

So calling i2c_check_ack will write the given data to the i2c device and return the ackstat bit.

 

To use the function I had the following code. Again this isn't generic, and is custom to my app. It basically uses a global long variable addCurrentLog to keep track of the current address in a big log of data. The code writes a given number of bytes to this log, and sets this address to the next available address.

 

void devWriteLogBytes(unsigned char *buf, unsigned char nBytes)

{

unsigned char nControlByte;

unsigned char Addressh;

unsigned int Addressl;

unsigned char nBytesWritten;

unsigned char nPagePosition;

nControlByte = xee_slave; //the old favourite for eeprom devices, xee_slave = 0xa0

 

while(nBytes>0)

{

Addressh = addLogCurrent >> 16; //The use of Addressh, Addressl, nPagePosition is a bit clumsy here. Addressh is byte 2 of the three byte address

Addressl = addLogCurrent & 0xffff; //addressl is bytes 1 and bytes 0 of the three byte address

nPagePosition = Addressl & 0xff; //this is the position in the low 256 bytes

if(test_bit(Addressh,0)) set_bit(nControlByte,3); //This is the block number

if(test_bit(Addressh,1)) set_bit(nControlByte,1); //This is the device number, I'm using 4 24LC1025 devices on the same I2C bus

if(test_bit(Addressh,2)) set_bit(nControlByte,2); //you put the 2 bits hardware address for the max 4 24LC1025 devices here

 

i2c_start();

i2c_write(nControlByte);

i2c_write(Addressl>>8);

i2c_write(nPagePosition); //this forms the low byte address of the address word

nBytesWritten = 0;

while(nBytes>0)

{

i2c_write(*buf);

buf++;

nBytes--;

nBytesWritten++;

nPagePosition++;

if ( (nPagePosition==0x80) || (nPagePosition==0x0) )

{

break; //Only do byte writes within a page boundary...which is 0x80 bytes for the 24LC1024

}

}

i2c_stop();

//ACKNOWLEDGE POLLING

//Now send the exact same control byte as the read operation and wait till the ack comes back

while(1)

{

i2c_restart(); //Don't use i2c_start here

if (!i2c_check_ack(nControlByte)) break; //stop polling when the ackstat bit goes low. Note you must use the same control byte as the write operation

};

i2c_stop();

//delay_ms(5); ------Don't need to do this, Acknowledge Polling will delay whatever the device needs

addLogCurrent = addLogCurrent + nBytesWritten;

addLogCurrent = addLogCurrent & 0x07ffff;

}

}

 

For some reason when I used i2c_start when doing the last bit there, it didn't work. Settling for i2c_restart made it all OK

 

Hope this helps, perhaps the Sourceboost team can incoorporate this into a more generic library for all EEPROM i2c device users, not just the 24LC1025.

 

I presume the single byte write operation could be re-written to use Acknowledge Polling rather than some arbitary delay.

 

Still to come, making sure block reads are not done over device block boundaries (as opposed to page write boundaries).

 

Phil

Share this post


Link to post
Share on other sites

Join the conversation

You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
Sign in to follow this  

×
×
  • Create New...