Slave I2C interrupt latency?

Thread Starter

dtrott

Joined Sep 29, 2011
2
Hi all,
Here is an unusual one, maybe someone brighter than me can help.
I have a pic18f4550 as a USB device and a master to a pic16f88 acting as a slave that will be doing analogue sampling. At the moment, all the master is doing is repeating a write of one bight. After many hours not having luck trying to debug both, I hooked the master up to an eeprom just to get that one sorted from an I2C standpoint. No problems, too easy.
I did find that I hadn't set my baud rate correctly, it was at about 225kHz but now fixed that and its at 100kHz (verified with scope) and pins tied high with 2k2 resistors.
After i fixed that, the slave worked - kind of.
I have the master turning on a green light when an ack is received and a red light when a nack is received (easier to see than getting the scope triggering properly). With the master running by itself and the slave in debug with an ICD2 through MPLAB, I couldn't get any acks when running the program right after a chip burn. However, after a reset, I could, but then only acks for a second maybe (and some times more, sometime less).
So after some more hours of debugging I thought I had it. It was acking continuously. So I thought I'd clean up my code and get rid of all the temporary variables that I was using to find faults.
This is where it got strange. After I deleted one temporary variable in the isr (which did a second bitmask on the SSPSTATUS register, checking for a stop bit ) I could only get an ack for a second again. In the code below you'll see in the interrupt routine that there are 4 NOP instructions. One more than that, < 1 second of acking, one less than that, < 1 second of acking, 4 - continuous acking.
Anyone with ideas? Why is 4 nop's the magic number?
By the way, using Hi-Tech for the slave and C18 for the master.

Slave code:

Rich (BB code):
#include <htc.h>
#define true 1
#define BYTES 64
/***** Global Variable ********/
void delay(int); 
void init(void); 
void i2c_init(void); 
void i2c_start(void); 
void i2c_write(char); 
void i2c_stop(void); 
void i2c_wait(void); 
 
void i2c_init(void)
{  
 SSPEN      = 1;   //Enables Serial Port Mode    
 INTCONbits.PEIE = 1;       
 SSPCONbits.CKP =1; //Clock Released  
 SSPBUF = 0 ;
 
 /* SSPCON REGISTERS */ 
 SSPCONbits.SSPM = 0x6;
 
 /* SSPSTAT REGISTERS */ 
 TRISB4 = 1;  // SCL as input
 SMP = 0;   //Slew Rate Control Disabled 
 CKE   = 0;   //SMBus Specific Inputs Disabled
 SSPADD = 0xAE; 
} 
void delay(int pause)
{ 
   unsigned int i, j, k; 
 
   unsigned int clock = 6; 
   unsigned int cycle = 14; 
 
   for(i = 0; i <= pause; i++){ 
   for(j = 0; j <= clock; j++){ 
   for(k = 0; k <= cycle; k++){ 
      asm("nop"); 
   }}} 
}
void Initialise()
{ 
 SSPSTAT = 0;
 
 ANSEL = 0b1111;  // Select Channel 0 through to 3 as analogue inputs
 TRISA0 = 1;  // RA0 as input
 TRISA1 = 1;  // RA1 as input
 TRISA2 = 1;  // RA2 as input
 TRISA3 = 1;  // RA3 as input
 TRISB1 = 1;  // SDA as input
 
 ADCON0 = 0b11000001;
 ADCON1 = 0b10000000;
 INTCONbits.PEIE = 1;
 PIE1bits.SSPIE = 1;
 GIE = 1;
 
 TMR0IF=0;
 T0CS=0;
 TMR0IE=0;
 
 TMR0 = 0x00;
 
 OPTION_REG = 0b11001000;
}
void main(void)
{
 Initialise();
 i2c_init();
 while (true)
 {
  i2c_wait();
 }
}
void i2c_write(char byte)
{ 
   SSPBUF = byte; 
   i2c_wait();    
} 
void i2c_wait(void)
{ 
   while ( SSPIF==0 ); 
   SSPIF = 0; 
} 
void interrupt isr(void)
{
 unsigned char CASE_SWITCH;
 unsigned char temp;
 unsigned char dataToMaster;
 if (PIR1bits.SSPIF == 1)
 { 
  PIR1bits.SSPIF = 0; // reset SSP interrupt flag
  CASE_SWITCH = SSPSTAT & 0x2D;
  asm("nop"); 
  asm("nop"); 
  asm("nop");
  asm("nop"); 
 
  switch(CASE_SWITCH) 
  {
   case 0x09 : // master write last byte was addr
   {
    if(SSPCONbits.SSPOV == 0)
    { 
     SSPCONbits.CKP = 0;
     temp = SSPBUF; // just dummy value to clear
     SSPCONbits.CKP = 1;
     break;
    }
   }
 
   case 0x29: // master write last byte was data
   {
    if(SSPCONbits.SSPOV == 0)
    {
     SSPCONbits.CKP = 0;
     readValue = SSPBUF; // just dummy value to clear
     SSPCONbits.CKP = 1;
     break;
    }
   }
 
   case 0x0c: // master read last byte was addr
   {
    temp = SSPBUF; // just dummy value to clear
    SSPCONbits.CKP = 0;
    SSPBUF = dataToMaster;
    SSPCONbits.CKP = 1;
    break;
   }
 
   case 0x2c: // master read last byte was data
   {
     temp = SSPBUF; // just dummy value to clear
    SSPCONbits.CKP = 0;
    SSPBUF = dataToMaster;
    SSPCONbits.CKP = 1;
    break;
   }
 
   case 0x28:  // S = 1, R_W = 0, D_A = 1, BF = 0 
   {
    temp = SSPBUF; // just dummy value to clear
    // Master NACK 
   break; 
   }
 
   default:
   break;
  }
 }
}
Here is part of the master code, there is a load of USB code that i didn't include as part of this. This function gets called by a flag in the main function, set on a timer interrupt.

In main() is:
Rich (BB code):
writeByteI2C(rw, 0xAE, 0x78 );
the function on the master is:

Rich (BB code):
unsigned char writeByteI2C(  unsigned char rw,  unsigned char add,  unsigned char data )
{
 unsigned char result = -1;
 
 if (rw == 0)
 {
  add = 0xFE & add;
 } 
 else
 {
  add = 0x01 | add;
 }
 IdleI2C();                          // ensure module is idle
 StartI2C();                         // initiate START condition
 while ( SSPCON2bits.SEN );          // wait until start condition is over 
 //WriteI2C( ControlByte );          // write 1 byte - R/W bit should be 0
 IdleI2C();                          // ensure module is idle
 result = WriteI2C( add  );          // write address
 IdleI2C();                          // ensure module is idle
 if (result == 0)
 {
  PORTAbits.RA4 = 1;
  PORTAbits.RA5 = 0;
  result = WriteI2C ( data );     // Write data byte
  if (result == 0)
  {
   PORTAbits.RA4 = 1;
   PORTAbits.RA5 = 0;
  } 
  else
  {
   PORTAbits.RA4 = 0;
   PORTAbits.RA5 = 1;
  }
 } 
 else
 {
  PORTAbits.RA4 = 0;
  PORTAbits.RA5 = 1;
 } 
 IdleI2C();                          // ensure module is idle
 StopI2C();                          // send STOP condition
 while ( SSPCON2bits.PEN );          // wait until stop condition is over 
 return ( 0 );                       // return with no error
}
I have spent hours looking over forums and there are others with similar problems but nothing has seemed to work, maybe if someone could offer a solution to this it may also help others starting out in I2C between two PICs.

Thanks for reading,

Dan.
 
Last edited:

ErnieM

Joined Apr 24, 2011
8,377
I feel your pain. More like I felt it a few years back when I did a PIC slave device. While I got it to work I don't remember any particulars, except the official documentation was filled with errors.

FWIW here's my source, done for a p18f4550 on the C18 compiler. I'm not sure this still compiles in the current C18 compiler.

I believe state 2 is so long and convoluted to convert a "data address" to pointers to where I stored variables so I could read each one out.

Rich (BB code):
void SSPIF_Vector()
{
  _asm 
  GOTO SSPIF_Handler 
  _endasm
}

//#pragma code
//#pragma interrupt SSPIF_Handler
void SSPIF_Handler()
{
    unsigned char  only_imp_bits;            // will store the status of relevant bits of the SSPSTAT
    unsigned char  dummy;                    // place for dummy buffer read
    char I2C_byte;                            // buffer contents will be stored in this
    
    if(PIR1bits.SSPIF == 1)
    {
        only_imp_bits = SSPSTAT & 0b00101101;
        switch(only_imp_bits)
        {
  
            case 0b00001001 :                 // state1:  D/A' = 0; S=1; R/W' =0; BF=1; Master Write Address
                // WRITE M->S last byte was an address
                  I2C_DataIndex = 0;
                I2C_byte = SSPBUF;                    // dummy read to clear the 1-byte buffer
                break;    
             
            case 0b00101001 :                 // state2:  D/A' = 1; S=1; R/W' =0; BF=1; Master Write Data
                // WRITE M->S last byte was data
                I2C_byte = SSPBUF;            // read buffer and clear dirty flag
                if (I2C_DataIndex == 0)
                {
                    I2C_DataIndex = &szDelayTime1[0];
                    // we have a new virtual data pointer
                    switch (I2C_byte)
                    {
                        case DEVICE_CODE:
                            // virtual address == Timer Code
                            I2C_DataIndex = &TimerType;
                            break;
                        case TIME_STRING1:
                            // virtual address == TIME_STRING1
                            I2C_DataIndex = &szDelayTime1[0];
                            break;
                        case TIME_STRING2:
                            // virtual address == TIME_STRING2
                            I2C_DataIndex = &szDelayTime2[0];
                            break;
                        case TIME_STRING3:
                            // virtual address == TIME_STRING3
                            I2C_DataIndex = &szDelayTime3[0];
                            break;
                        case VUTIL_STRING:
                            // virtual address == VUTIL_STRING
                            I2C_DataIndex = &szVUTIL[0];
                            break;
                        case ICC_STRING:
                            // virtual address == ICC_STRING
                            I2C_DataIndex = &szIcc[0];
                            break;
                        case SPIKE_COUNT:
                            // virtual address == SPIKE_COUNT
                            I2C_DataIndex = &SpikeCount;
                            break;
                        case FAIL_CODE:
                            // virtual address == FAIL_CODE
                            I2C_DataIndex = &FailCode;
                            break;
                        case I2C_MODE:
                            // virtual address == I2C_State
                            I2C_DataIndex = &I2C_State;
                            break;
                        case LED_GREEN:
                            // virtual address == LED_GREEN
                            // we don't use the data, just do the function
                            SET_LED_GREEN;
                            break;
                        case LED_RED:
                            // virtual address == LED_RED
                            // we don't use the data, just do the function
                            SET_LED_RED;
                            break;
                        case LED_OFF:
                            // virtual address == LED_OFF
                            // we don't use the data, just do the function
                            SET_LED_OFF;
                            break;
                        default:
                            // leave I2C_DataIndex as null
                            break;
                    }
                }
                else
                {
                    // we have new data to write
                    *I2C_DataIndex = I2C_byte;
                    I2C_DataIndex++;
                }
                break;        
            
            case 0b00001101 :                 // state3:  D/A' = 0; S=1; R/W' =1; BF=1; Master READ Address
                // READ S->M last byte was address
                // load SSPBUF with first byte to send
                if (I2C_DataIndex != 0)
                {
                    SSPBUF = *I2C_DataIndex;
                    I2C_DataIndex++;
                }
                else
                {
                    SSPBUF = 0;
                }    
                break;    

            case 0b00101100 :                 /*state4:  D/A' = 1; S=1; R/W' =1; BF=0; Master READ Data*/
                // READ S->M last byte was data
                // load SSPBUF with next byte to send
                SSPBUF = *I2C_DataIndex;
                I2C_DataIndex++;
                break;    

            case 0b00101000 :                 /*state5:  D/A' = 1; S=1; R/W' =0; BF=0; Master Write NACK*/
                // master NACK'ed us... 
                break;    
            default:
                I2C_byte = SSPBUF;                    // dummy read to clear the 1-byte buffer
                break;    
        }
        SSPCON1bits.CKP = 1;        // release the SCL line if we're holding it down
        PIR1bits.SSPIF = 0;            /*clear the SSPIF bit*/        
        if (SSPCON1bits.SSPOV == 1)
        {
            // we had an overflow
            SSPCON1bits.SSPOV == 1;
            I2C_byte = SSPBUF;            // read buffer and clear dirty flag
        }
    }    
}

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

Thread Starter

dtrott

Joined Sep 29, 2011
2
Hi ErnieM,

Thanks for your reply.

I've managed to get it working a bit more robustly with the removal of the:
i2c_wait()
routine out of the main function, which just put the program in a different loop until the SSPIF was set. I was able to remove all of the NOP's. But I don't know any more as to why it is so temperamental.

Thanks again for reading and your code.

Dan.
 
Top