|
|
View previous topic :: View next topic |
Author |
Message |
Fred Flintstone Guest
|
I2C slave: detecting a NACK. |
Posted: Tue Mar 21, 2006 7:44 am |
|
|
I searched through the forum and found a post by Paolo Minazzi that describes the exact same problem i'm having with the I2C slave, but nobody replied with an answer.
I'm using a 16F88 as the slave with my own I2C code (based on the CCS examples). The problem is detecting when the NACK from the master is sent. I have too tried to use "ack = i2c_write(data);" in the slave but it always returns '0' (ACK) regardless of whether an ACK or NACK was sent from the master.
The other problem i'm having is also the same - when the master communicates with a NACK on the read from the slave, the interrupt in the slave triggers twice (causing code to execute that shouldn't resulting in buffers getting out of sync). If I could detect the difference between an ACK and a NACK in the slave, then I would be able to work around this 'double triggering', but as it stands I can't!
Any ideas?
Regards, Dave.
The original post:
Quote: |
I have 2 doubts about i2c slave mode using interrupt.
In this case the interrupt routine should be similar to this:
Code: |
#int_ssp
void isr_routine()
{
char tmp=i2c_isr_state();
if (tmp==0)
{ // received a START + I2C_ADR
}
else
if ((tmp>=1) && (tmp<=0x7F))
{ // received a byte.
// i2c_read() must be used to read the byte
}
else
if (tmp==0x80)
{ // received a START + ( I2C_ADR | 1)
// i2c_write(data) must be used to write a byte
}
else
if (tmp>0x80)
{ // master has read the byte sent by the slave.
// i2c_write(data) must be used to write a byte
}
}
|
The FIRST is to understand when the master has read the LAST byte, that is when it doesn't want bytes anymore.
When it occours, the master should send a NOACK and STOP, otherwise it should send an ACK.
I know that should be possible to use
ack=i2c_write(data)
and read the ack variable.
But in my experience this doesn't work.
ack is always 0, that is an ACK !!!
The SECOND problem is that after the NOACK sent by the master, the PIC receives an other intewrrupt.
Is there a clean and secure method to understand the end of a i2C packet when the PIC is in slave mode ?
Thanks !
Paolo Minazzi
|
|
|
|
Ttelmah Guest
|
|
Posted: Tue Mar 21, 2006 8:29 am |
|
|
I was going to post to the other thread, but it seems to have disappeared....
I wrote an I2C slave, some time ago, which runs fine. However it 'predates' the I2C_ISR_STATE function, and reflected problems I have always had with the CCS I2C functions. My code was written for the 16Fxx6 chips, dirctly from the Microchip application note.
Now the extra interrupt, is both correct, and normal!. What should happen, is when the byte you have just _sent_, is complete, and the master responds with the ACK/NACK, another interrupt occurs. The behaviour is problematic, because, first the I2C write function waits for the byte to transfer - it shouldn't - instead you should just write the byte to the SSPBUF register, and secondly, because this next interrupt, needs to detect that there is now a NACK cycle. The 'NACK' cycle, is marked by the R/W bit being cleared.
So, I'd suggest, writing data directly to SSPBUFF (and read it as well), and then at the start of the handler code, either work out what I2C_ISR_STATE will return for a NACK, or look for this specific bit pattern on the processor registers.
Best Wishes |
|
|
Fred Flintstone Guest
|
|
Posted: Tue Mar 21, 2006 10:36 am |
|
|
Thanks Ttelmah! As usual you save the day! I've spent days trying to figure this out, and with your suggestion of looking at the R/W bit in SSPSTAT it all works correctly!
I found that I2C_ISR_STATE just increments normally on the second 'NACK' cycle so I couldn't use that. After digging out the 16F88 datasheet and messing with a logic analyser, all I had to do was to add the following lines:
Code: |
#define SSPSTAT 0x94
#bit SSPSTAT_RW = SSPSTAT.2
|
and change the line in the SSP interrupt from:
Code: |
else if (i2c_state >= 0x80)
{
//Do I2C write to master stuff...
}
|
to:
Code: |
else if ((i2c_state >= 0x80) & (SSPSTAT_RW == 1))
{
//Do I2C write to master stuff...
}
|
so it now only responds to the read request from the master when the I2C state machine is actually in 'read' mode (according to the datasheet it gets reset to 'write' mode when the 'NACK' is received), which fixes all the problems I was having!
I can't help but think that the CCS I2C functions could be a little bit better.
On a side note... The PIC seemed to lock up somewhere inside the above interrupt code whenever I issued a write to one of the PORTB pins before the write to the I2C using i2c_write(data); There was however not a problem if I changed the pin to one on PORTA or did the write after the i2c_write function. Another oddity thats probably got a perfectly good explanation...maybe.
If you could direct me to your I2C to have a look at how you did everything manually, then that would be much appreciated Ttelmah.
Thanks again! |
|
|
Ttelmah Guest
|
|
Posted: Tue Mar 21, 2006 3:51 pm |
|
|
Yes. Agree about 1000%...
The functions are 'fine', for simple 'mainline' I2C, but really don't work very well at all for interrupt driven code.
It looked very helpful, when they added I2C_ISR_STATE, but if you compare it with the application note from MicroChip, it 'misses' seperating the NACK state, which is a pain.
The same is true of the SSP routines.
Best Wishes |
|
|
Ttelmah Guest
|
|
Posted: Tue Mar 21, 2006 3:55 pm |
|
|
I'll see if I can find the old code, tomorrow.
I'd suspect the 'lockup', is something like SSPOV getting set (which the standard code does not handle).
Best Wishes |
|
|
Ttelmah Guest
|
|
Posted: Tue Mar 21, 2006 4:36 pm |
|
|
Ok. I have just retyped the core, since what I had, was more complex (included setting the address from a write etc..). Have tried to add 18 support (no guarantees...).
Code: |
#ifdef __PCM___
#byte SSPCON=0x14
#byte SSPSTAT=0x94
#bit SSPBUF=0x13
#else
#byte SSPCON=0xFC6
#byte SSPSTAT=0xFC7
#bit SSPBUF=0xFC9
#endif
#bit P=SSPSTAT.4
#bit DA=SSPSTAT.5
#bit S=SSPSTAT.3
#bit RW=SSPSTAT.2
#bit BF=SSPSTAT.0
#bit WCOL=SSPCON.7
#bit SSPOV=SSPCON.6
#bit SSPEN=SSPCON.5
//My I2C buffer
#define BSIZE 32;
//Note buffer size must be binary multiple
int8 buffer[BSIZE];
#define read_i2c() (SSPBUF)
#define write_i2c(x) SSPBUF=(x)
#define MWLBA ((S==1)&&(RW==0)&&(DA==0)&&(BF==1))
#define MWLBD ((S==1)&&(RW==0)&&(DA==1)&&(BF==1))
#define MRLBA ((S==1)&&(RW==1)&&(DA==0)&&(BF==0))
#define MRLBD ((S==1)&&(RW==1)&&(DA==1)&&(BF==0))
#define MNACK ((S==1)&&(RW==0)&&(DA==1)&&(BF==0))
//Note that for the 18 chips, you could add GCEN as another bit to test
#int_SSP
void SSP_int_handler(void) {
static int8 ctr;
int8 tmp;
if(MWLBA) {
//Here I have an address match for a write operation
ctr=0;
tmp=read_i2c();
}
else if(MWLBD) {
//Here data arriving from master
buffer[ctr++]=read_i2c();
ctr &= (BSIZE-1);
//Limit buffer counter to buffer size
}
else if(MRLBA) {
//Here an address match for a read command
ctr=0;
write_i2c(buffer[ctr++]);
}
else if(MRLBD) {
//Here sending data to master
write_i2c(buffer[ctr++]);
ctr &= (BSIZE-1);
}
else if(MNACK) {
//Here NACK from master
SSPEN=0;
SSPEN=1;
//Reset I2C module
}
if (SSPOV) SSPOV=0;
if (WCOL) WCOL=0;
}
|
This directly mimics the MicroChip application note.
Best Wishes |
|
|
Eugeneo
Joined: 30 Aug 2005 Posts: 155 Location: Calgary, AB
|
|
Posted: Tue Mar 21, 2006 5:43 pm |
|
|
I've had the same lockup problem using a the f819 (almost the same as the f88). I had to read the spi register twice to clear the error.
Code: |
#bit SSPOV = 0x14.6
#bit BF = 0x94.0
#bit WCOL = 0x14.7
#bit SSPIF = 0X0C.3
|
|
|
|
Fred Flintstone Guest
|
|
Posted: Tue Mar 21, 2006 6:14 pm |
|
|
Thanks for the code Ttelmah. It will certainly come in handy.
As for the 'lockup' I described, i've now done some more testing and found that lots of writes to any of the portB pins causes the I2C interrupt to fail to fire (most of the time) after the initial interrupt and write that first addresses the slave. i.e. attempts at writing or reading the actual data does not result in any further interrupts.
The simplest code i've found to have this problem with is as follows:
Code: |
void main(void)
{
while(1)
{
output_high(BUSY_LED);
output_low(BUSY_LED);
}
}
|
This results in an LED flashing at 500KHz at all times and the interrupt fails to fire in this situation.
If however I change the code to:
Code: |
void main(void)
{
while(1)
{
output_high(BUSY_LED);
delay_us(100);
output_low(BUSY_LED);
}
}
|
This results in the LED flashing at 9.8KHz and the interrupt never fails to fire (it seems).
This behaviour works the same on all portB pins I have free (RB0, RB3, and RB6). The odd thing however is that if the output is changed to any of the portA pins, then both code implementations work flawlessly.
I tried your suggestion of clearing the SSPOV bit but it didn't cure the problem. I've also checked the 16F88 errata and can't see anything that could possibly be causing the problem.
It almost sounds like a problem with the PIC to me. This PIC was a sample device obtained from Microchip a few weeks ago - perhaps they've given me an early revision that proved to be duff?
Any suggestions? Thanks! |
|
|
Fred Flintstone Guest
|
|
Posted: Tue Mar 21, 2006 6:22 pm |
|
|
Could you elaborate on that a bit more Eugeno. What was the code you actually used to read the spi register twice?
Thanks.
Eugeneo wrote: | I've had the same lockup problem using a the f819 (almost the same as the f88). I had to read the spi register twice to clear the error.
Code: |
#bit SSPOV = 0x14.6
#bit BF = 0x94.0
#bit WCOL = 0x14.7
#bit SSPIF = 0X0C.3
|
|
|
|
|
Ttelmah Guest
|
|
Posted: Wed Mar 22, 2006 3:27 am |
|
|
I do still wonder about SSPOV. The thing is, that the internal hardware is not limited to the I2C rate in use, and the receiver shift register, can trigger off really high frequency signals on the clock line If after a character is 'read' at the start of the interrupt handler, a second character is seen as arriving, before the interrupt routine exits, then the interrupt flag will get set, but will then be cleared, when the handler exits, and the code will have 'missed' servicing this. This will then give a SSPOV, and the interrupt handler will stop working.
I tend to suspect that something is triggering 'noise' on a line somewhere when you do the I/O...
Now, the 'best' way round this, is the same trick used in RS232 interrupt handlers, to cope with high data rates. If you modify the interrupt code as follows:
Code: |
#ifdef __PCM__
#bit SSPIF=0xC.3
#else
#bit SSPIF=0xF9E.3
#endif
#INT_SSP NOCLEAR
#int_SSP
void SSP_int_handler(void) {
while (SSPIF) {
//Do the normal handling code here. As soon as you read the I2C
//data, or write a new byte, set SSPIF=0 _immediately_
}
//Here add the test and clear shown in my code, for SSPOV, and WCOL
}
|
Here, _you_ take over responsibility for clearing SSPIF (note 'NOCLEAR'), and you also make sure before you exit the routine, that firstly the interrupt has not retriggered (the 'while' loop), and secondly that the error bits are not set.
I suspect you will find that this makes the code work.
Best Wishes |
|
|
Fred Flintstone Guest
|
|
Posted: Wed Mar 22, 2006 5:12 am |
|
|
You're spot on again Ttelmah. As soon as I read 'noise on the line' the first thing I did was to put a 100nF capacitor on the clock line to ground, and it cured the problem instantly. The I2C now communicates without a hitch from what I can see. I will get the scope on it later to see make sure the clock isn't distorting too much, plus I will also try to implement your solution just to be safe.
Such a simple problem but it just didn't spring to mind. Silly me - I should have known better.
Sorry to have wasted your time!
Thanks. |
|
|
Paolo Guest
|
|
Posted: Thu Apr 06, 2006 7:31 am |
|
|
Take a look at Application Note 734 that explain the solution of the problem.
Regards,
Paolo Minazzi |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|