I2C EEPROM code, not using interrupts

Started by Mike Harrison January 30, 2009
(LPC2103 and 2136, but I think I2C is the same on most parts)

I only want to use I2C to access an EEPROM, and to keep it simple I'm using the I2C hardware in
polled mode instead of under interrupts - I always know what the EEPROM will do next so this should
simplify things a lot.

However there's an issue I can't quite get my head round - I've read the UM several times & still
can't figure out the answer...

When doing an eeprom read, after sending the last byte of the address, I need to do a repeated start
condition, but am having a hard time figuring out how this can work reliably when the code might get
interrupted. (all eeprom datasheets show using a repeated start not a stop+start)

If I set STA, clear SI, then wait for SI to check when the start condition has happened, and an
interrupt occurs between setting STA and clearing SI, the clear of SI could be delayed until after
the start condition has completed (due to the time taken for the interrupt service), and it could
then loop forever waiting for the start condition to finish.

I can't clear SI before setting STA, as if an interrupt occurs between these operations, the cleared
SI could cause another byte to be written as the II2C hardware is still in master write mode.

As I need to clear one bit & set another, it can't be done as an atomic operation on I2CCON.
OK I could disable ints but this feels like a messy solution....

I also can't see why this issue wouldn't also affect interrupt-driven I2C, e.g. if a FIQ interrupts
the I2C IRQ service between STA getting set and SI being cleared, unless the FIQ service time was
always short enough to never be longer then the time between STA being set and SI going high.
Or maybe there is there some internal latching that prevents this?

If setting STA also clears SI, then this shouldn't be a problem, but as far as far as I can tell it
doesn't.

Below is the current code - any comments welcome!

void sendiic(char d)
{ I2C0DAT=d; I2C0CONCLR=8; while((I2C0CONSET & 0x08)==0) ; }

char iistart(char adr) // returns true if no ack
{
I2C0CONSET=0x20; // startcond
// an int here could cause enough delay that SI gets set by the hardware
I2C0CONCLR=8; // clear previous int from ee address write
while((I2C0CONSET & 0x08)==0); // this loop could hang if SI got cleared after hardware set it
I2C0DATr;
I2C0CONCLR=0x28; // clear start+int
while((I2C0CONSET & 0x08)==0); // ii address done
return(I2C0STAT!=0x18);
}

void iistop(void)
{ I2C0CONSET=0x10; // stop
I2C0CONCLR=0x08; // clear int
while(I2C0CONSET & 0x10); // wait til stop done
}

void doreadeeprom(Int32U adr,Int32U nbytes,char *dest) // b27..4 = i2c address
{
while(iistart(((adr>>24) & 0x0e) | 0xa0)) I2C0CONCLR=8;

sendiic(adr>>8);
sendiic(adr);

iistart(((adr>>24) & 0x0e) | 0xa1);// repeated start
I2C0CONSET=4; // ACK

do {
I2C0CONCLR=--nbytes?0x08:0x0c; // clear AA on last
while((I2C0CONSET & 0x08)==0); // wait 1st byte
*dest++=I2C0DAT;

} while (nbytes);
iistop();

}

An Engineer's Guide to the LPC2100 Series

Hello,

I spent few days when I first used I2C with LPC. Finally with a help from an
oscilloscope I did it, and now I uses them with many projects without
problem. You may download the code at
http://www.thaieasyelec.com/index.php?lay=show&ac_show_pro_detail&cid@2&pidE899.
And see the i2c_eeprom.h, i2c_eeprom.c, i2c_lpc23xx_v1_00.h and
i2c_lpc23xx_v1_00.c. Hope this will help.

Best Regards,
Chutiman Yongprapat

--- In l..., Mike Harrison wrote:
>
> (LPC2103 and 2136, but I think I2C is the same on most parts)
>
> I only want to use I2C to access an EEPROM, and to keep it simple
I'm using the I2C hardware in
> polled mode instead of under interrupts - I always know what the
EEPROM will do next so this should
> simplify things a lot.
>
> However there's an issue I can't quite get my head round - I've read
the UM several times & still
> can't figure out the answer...
>
> When doing an eeprom read, after sending the last byte of the
address, I need to do a repeated start
> condition, but am having a hard time figuring out how this can work
reliably when the code might get
> interrupted. (all eeprom datasheets show using a repeated start not
a stop+start)
>
> If I set STA, clear SI, then wait for SI to check when the start
condition has happened, and an
> interrupt occurs between setting STA and clearing SI, the clear of
SI could be delayed until after
> the start condition has completed (due to the time taken for the
interrupt service), and it could
> then loop forever waiting for the start condition to finish.
>
> I can't clear SI before setting STA, as if an interrupt occurs
between these operations, the cleared
> SI could cause another byte to be written as the II2C hardware is
still in master write mode.
>
> As I need to clear one bit & set another, it can't be done as an
atomic operation on I2CCON.
> OK I could disable ints but this feels like a messy solution....
>
> I also can't see why this issue wouldn't also affect interrupt-
driven I2C, e.g. if a FIQ interrupts
> the I2C IRQ service between STA getting set and SI being cleared,
unless the FIQ service time was
> always short enough to never be longer then the time between STA
being set and SI going high.
> Or maybe there is there some internal latching that prevents this?
>
> If setting STA also clears SI, then this shouldn't be a problem, but
as far as far as I can tell it
> doesn't.
>
> Below is the current code - any comments welcome!
>
> void sendiic(char d)
> { I2C0DAT=d; I2C0CONCLR=8; while((I2C0CONSET & 0x08)==0) ; }
>
> char iistart(char adr) // returns true if no ack
> {
> I2C0CONSET=0x20; // startcond
> // an int here could cause enough delay that SI gets set
by the hardware
> I2C0CONCLR=8; // clear previous int from ee address
write
> while((I2C0CONSET & 0x08)==0); // this loop could hang if SI got
cleared after hardware set it
> I2C0DATr;
> I2C0CONCLR=0x28; // clear start+int
> while((I2C0CONSET & 0x08)==0); // ii address done
> return(I2C0STAT!=0x18);
> }
>
> void iistop(void)
> { I2C0CONSET=0x10; // stop
> I2C0CONCLR=0x08; // clear int
> while(I2C0CONSET & 0x10); // wait til stop done
> }
>
>
> void doreadeeprom(Int32U adr,Int32U nbytes,char *dest) // b27..4 i2c address
> {
> while(iistart(((adr>>24) & 0x0e) | 0xa0)) I2C0CONCLR=8;
>
> sendiic(adr>>8);
> sendiic(adr);
>
> iistart(((adr>>24) & 0x0e) | 0xa1);// repeated start
> I2C0CONSET=4; // ACK
>
> do {
> I2C0CONCLR=--nbytes?0x08:0x0c; // clear AA on last
> while((I2C0CONSET & 0x08)==0); // wait 1st byte
> *dest++=I2C0DAT;
>
> } while (nbytes);
> iistop();
>
>
> }
>

The secret it that the LPC2103 holds the I2C clock low (thus holding
the bus in a wait state) while the SI bit is set. So, until you clear
SI the start bit (or whatever the next state you've commanded) will
not occur.

Somewhere in the user manual is a single sentence that states
something like "while SI is set..." the low period of the SCL line is
stretched... or something like that. Search for the string I put in
quotes as I'm pretty sure that is the wording they used.

TC