CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

I2C Slave lockups

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
jsummerour



Joined: 15 Nov 2008
Posts: 4

View user's profile Send private message

I2C Slave lockups
PostPosted: Sun Nov 16, 2008 9:29 pm     Reply with quote

I'm writing the firmware for a new servo controller board, which acts as an I2C slave device. I have all of the code debugged regarding the servo timing, but what I'm finding is that the i2c communications locks up after a varying amout of time. It might be 10 seconds, or it might be 5 minutes.

The PIC I'm using is an 18F452.

The application uses three timers, Timer1, Timer3, and has three ISRs, timer1_Isr(), timer3_Isr(), and ssp_Isr().

Timer 3 and Timer 1 are set up as high priority irqs, and SSP is a normal irq. However, I've implemented I2C clock stretching for SSP, to serve as flow control back to the host device (also a PIC in this case) when T1 and T3 are irq'ing SSP, and holding up I2C processing.

The ssp_Isr() follows the proper processing methodology mentioned in tons of forums here (see code below).

Specifically what I'm seeing is that once the board locks up, SDA and SCL are both low. I can reset power on the board, and communications resumes just fine (until it locks up again).

Additionally, the board only locks up when I'm sending I2C commands to it, and not when it's just sitting there spitting out PWM timing for the servos. Once the i2c comm locks up, the PWM output of the board continues to work just fine (verified with a Logic Analyzer). Also, I have verified that when T1 and T3 irqs are turned off, I2C communications functions properly, and doesn't lock up.

I have verified that the Clock Stretching is working. You actually can't communicate w/ the board at all without it (since T1 and T3 are higher priority irqs). However, I have to believe that this still isn't enough to prevent the other timers from clobber the i2c communications somehow.

Could some one look at our code below (I removed a lot of superfluous stuff that isn't neccessarily pertinent to this problem) and maybe suggest some ideas Idea to try to get it to work? I would greatly appreciate it, as I'm at wits end on this, and I'm ready to finish this project and move on to the next one.

Thanks guys!

Jason

// =========
// Start of code
// =========
Code:
#include "18f452.h"
#fuses h4,nowdt,noprotect,put,nolvp
#device HIGH_INTS=TRUE
#use delay(clock=40000000)
#priority TIMER3,TIMER1,SSP
#use fast_io(a)
#use fast_io(b)
#use fast_io(c)
#use fast_io(d)
#use fast_io(e)

#byte PIC_SSPBUFF=0xFC9
#byte PIC_SSPADD=0xFC8
#byte PIC_SSPSTAT=0xFC7
#byte PIC_SSPCON1=0xFC6
#byte PIC_SSPCON2=0xFC5
#bit CKP = PIC_SSPCON1.4   // 1 = Release Clock, 0 = Holds Clock Low (clock stretch)
#bit SEN = PIC_SSPCON2.0   // 1 = Clock Stretching Enabled for Slave Tx and Rx, 0 = Clock Stretching Enabled for Slave Tx only
#bit BF = PIC_SSPSTAT.0    // B1 = data transmit in progress, 0 = data transmit complete
#bit WCOL = PIC_SSPCON1.7        // I2C Write Collision
#bit SSPOV =PIC_SSPCON1.6       // I2C Buffer Overflow

// ============================================================
// Valid address range for this device: 0x5A(90) to 0x64(100):
// ============================================================
#use i2c(slave, SDA=PIN_C4, SCL=PIN_C3, address=0x5A, slow, force_hw)


...

#INT_SSP
void ssp_interrupt()
{
    BYTE servoId;
    BYTE id;
    BYTE state, incoming;
    BYTE groupId;
    unsigned long pos, time;
    boolean mode;

    state = i2c_isr_state();

    // I2C Write
    if (state < 0x80)                     
    {                   
        incoming = i2c_read();
        buffer[bytecounter] = incoming;
        bytecounter++;       
                 
        // Don't overrun the buffer:
        if (bytecounter >= MaxBufferSize)
        {
            bytecounter = 0;
            CKP = 1;  // Set CKP bit to enable Master to continue sending data
            BF = 0;   // Clear BF bit to Data Transmit complete
            bovf = true;
            return;   
        }
                 
        if (bytecounter == 3)
        {
            // Store servo id that we'll read next time around:
            // i2c w 90 2 <servoId>
            //       0  1     2    // Buffer pos
            if (buffer[1] == 2)
            {
                servoToRead = buffer[2];
                queryServoStatus = true;
                bytecounter = 0;
                CKP = 1;  // Set CKP bit to enable Master to continue sending data
                BF = 0;   // Clear BF bit to Data Transmit complete   
                return;               
            }                   
        }
        // We got a full servo position/time command:
        // Command Format:
        // i2c w 90 4 <servo id> <position high> <position low> <time high> <time low>   // 7 Bytes
        //       0  1      2             3               4            5           6      // Buffer pos
        if (bytecounter == 7)
        {
            // Store servo position/time immediately
            if (buffer[1] == 4)
            {
                memcpy(setServoParams, buffer, 7);
                setServoPos = true;
                bytecounter = 0;   
                CKP = 1;  // Set CKP bit to enable Master to continue sending data
                BF = 0;   // Clear BF bit to Data Transmit complete   
                return;                                                                                           
            }         
        }    }
    // I2C Read
    if (state == 0x80)                     
    {
        if (queryFirmwareVersion)
        {
            sendFirmwareVersion = true;
            queryFirmwareVersion = false;
        }
        else if (queryServoStatus)
        {
            sendServoStatus = true;
            queryServoStatus = false;
        }
        //CKP = 1;  // Set CKP bit to enable Master to continue sending data
    }
}

...

#int_TIMER3 HIGH
void TIMER3_isr()
{
  ...
}

...

#int_timer1 HIGH
void timer1_isr()
{
   ....
}

...

void main ()
{   
    BYTE servoId;
    int data, eepromState;
    unsigned long position, time;

    disable_interrupts(GLOBAL);
    disable_interrupts(INT_SSP);
    disable_interrupts(INT_TIMER1);
    disable_interrupts(INT_TIMER3);
   
    // Enable I2C Clock Stretching
    SEN = 1;

    set_tris_a(0);
    set_tris_b(0);  // LED Pins 6,7
    set_tris_c(0B00011000);  // I2C I/O Pins 3,4
    set_tris_d(0);
    set_tris_e(0);
   
    InitServoTable(); 
   
    // Timer 1
    setup_timer_1( T1_INTERNAL | T1_DIV_BY_8 );

    // Timer 3    setup_timer_3( T3_INTERNAL | T3_DIV_BY_4 );
    setup_timer_3( T3_INTERNAL | T3_DIV_BY_4 );
   
    Port_b_pullups(true);
   
    enable_interrupts(GLOBAL);
    enable_interrupts(INT_SSP);
    enable_interrupts(INT_TIMER3); // Timer 3 Overflow

    set_timer3(65500);
   
    while (1)
    {     
        if (bovf)
        {
            bovf = false;
            printf("BOVF");           
        }
        else if (sendServoStatus)
        {
            SendServoInformation();
            sendServoStatus = false;
        }
        else if (sendFirmwareVersion)
        {
            SendFirmwareInformation();
            sendFirmwareVersion = false;
        }
        else if (setServoPos)
        {
            servoId = (byte)setServoParams[2];
            if ((1 <= servoId) && (servoId <= 25))
            {
                position = ParsePosition(setServoParams[3], setServoParams[4]);
                time = ParseTime(setServoParams[5], setServoParams[6]);
                SetServoPosition(servoId, position, time);
                //printf("s: %d to pos: %lu time: %lu\r\n", servoId, position, time);
            } 
            setServoPos = false;
        }
    }
}
future



Joined: 14 May 2004
Posts: 330

View user's profile Send private message

PostPosted: Mon Nov 17, 2008 3:42 am     Reply with quote

Try testing the SSPOV bit, if it is true then reset it.
Ttelmah
Guest







PostPosted: Mon Nov 17, 2008 4:31 am     Reply with quote

Yes.
I'd do two things. At the end of your ISR, test the SSPOV bit, and if it is set clear it, and also test the buffer full bit (which should be empty, since you have read the buffer in the ISR), and if it is still set, read the dummy byte.
So:
Code:

   //Make sure no error bits set
   if (OV==1) OV=0;
   if (BF==1) incoming=SSPBUF;

This is assuming you have OV, and BF defined as the overrun, and buffer full bits, and SSPBUF as the data register.
Then, add a 'clock' to one of your timers. In the I2C ISR, have this set to a large value (perhaps equivalent to about 10 seconds), and have the timer decrement it.
The timer code, would be something like:
Code:

   if (timeout) --timeout;
   else {
      //You will only get hre, if no call has occured to the I2C, in the total
      //time needed to get this counter to = 0.
      SSPEN=0;
      DELAY_CYCLES(1);
      SSPEN=1;
   }

This disables, and re-enables the SSP port (assuming you have SSPEN defined), if no I2C transaction takes place in the period defined by the timeout of the counter.
This is a 'belt and braces' recovery strategy.

I'd suspect your actual problem is not reading the data register, in the write routines. The data sheet, has the following line in it for most (all?) chips:
"The user must read the SSPBUF, even if only transmitting data, to avoid setting overflow (must be cleared in software).".

It is a common error in the posted I2C routines.
If you want to keep completely CCS, then use:
Code:

if (i2C_data_is_in())  incoming = i2c_read();


Before you call the switch statement, rather than only doing the read inside the read parts of the statement.

I have a coupl eof systems, using a 'ruggedised' high voltage I2C (12v, with high power transceivers), running s couple of dozen feet in qute a noisy environment, and with these tests ad recovery in place, I2C, has been 100% reliable.

Best Wishes
jsummerour



Joined: 15 Nov 2008
Posts: 4

View user's profile Send private message

PostPosted: Mon Nov 17, 2008 10:07 am     Reply with quote

Future, and Ttelmah,

Awesome! These sound like some great suggestions! I will try them tonight when I get home.

I like the recovery strategy...Been trying to figure out how to implement something similar. Smile

I'll post tonight and let you guys know if this fixes my problem. Thanks again for the fast reply!
_________________
Jason
jsummerour



Joined: 15 Nov 2008
Posts: 4

View user's profile Send private message

PostPosted: Sun Dec 14, 2008 8:14 pm     Reply with quote

Hmm.

I have tried the suggestions above, and I still see the same behavior. Any more ideas?

Here's my additions per the suggestions:

I put the recovery strategy counter in my 20msec timer 3.

Code:


#byte PIC_SSPBUFF=0xFC9
#byte PIC_SSPADD=0xFC8
#byte PIC_SSPSTAT=0xFC7
#byte PIC_SSPCON1=0xFC6
#byte PIC_SSPCON2=0xFC5
#bit CKP   = PIC_SSPCON1.4      // 1 = Release Clock, 0 = Holds Clock Low (clock stretch)
#bit SEN   = PIC_SSPCON2.0      // 1 = Clock Stretching Enabled for Slave Tx and Rx, 0 = Clock Stretching Enabled for Slave Tx only
#bit BF    = PIC_SSPSTAT.0      // B1 = data transmit in progress, 0 = data transmit complete
#bit WCOL  = PIC_SSPCON1.7      // I2C Write Collision
#bit SSPOV = PIC_SSPCON1.6      // I2C Buffer Overflow
#bit SSPEN = PIC_SSPCON1.5      // Synchronus Serial Port Enable Bit

unsigned long sspTimer = 500;

#INT_SSP
void ssp_interrupt()
{
    BYTE servoId;
    BYTE id;
    BYTE state, incoming;
    BYTE groupId;
    unsigned long pos, time;
    boolean mode;

    state = i2c_isr_state();

    // I2C Write
    if (state < 0x80)                     
    {     
        incoming = i2c_read();
       
        buffer[bytecounter] = incoming;
        bytecounter++;       
        sspTimer = 500;

        ....
    }

    if (state == 0x80)                     
    {
        ...
    }
    ...

    if (SSPOV==1)
    {
        SSPOV=0;
    }
    if (BF==1)
    {
        incoming = i2c_read();
    }
}

// 16-Bit Timer
// 20 msec Timer
#int_TIMER3 HIGH
void TIMER3_isr()
{
    ... 
    // I2C recovery strategy...
    if (sspTimer)
    {
        --sspTimer;
    }
    else
    {
        // Disable/Reenable SSP port
        SSPEN = 0;
        delay_cycles(1);
        SSPEN = 1;   
    }
}


Do this look correct? I would appreciate any more suggestions, as I'm pretty much at a standstill.

Best Regards!

Jason
_________________
Jason
Ttelmah
Guest







PostPosted: Mon Dec 15, 2008 3:59 am     Reply with quote

OK.
I'd look at three things:
First try not using the CCS functions at all. I have had problems with 'unexplained' behaviour when using their functions, and it is possible you are hitting such a situation. I have used a whole 'suite' of different 'hombrew' functions in the past, and something like:
Code:

#byte SSPSTAT=0xFC7
#byte SSPCON=0xFC6
#byte SSPCON2=0xFC5
#bit RW=SSPSTAT.2
#bit DA=SSPSTAT.5
#bit BF=SSPSTAT.0
#bit OV=SSPCON.6
#byte SSPBUF=0xFC9

#INT_SSP
void ssp_interupt ()
{
   BYTE incoming;
   static int8 state=0;

   if ((DA==1) && (RW==0)) {
      //Here a _data_ write transaction (read here)
      incoming = SSPBUF;
      if (state==0) {
         state=1;
      }
      else if(state >= 1)   {                  //Second received byte is data
         buffer[state-1] = incoming;
         state++;
         //You will need to set _ACK_ here on your last byte, and clear it
         //on the others
      }
      //Should never get here
      else {
         state=0;
      }
   }
   else if (DA==0) {
      //Here address
      incoming=SSPBUF;
      state=0;
      if (RW==1) {
         //Here master is requesting data
         do {
           WCOL=0;
           SSPBUF=//Whatever you want to send...;
         }
         while (WCOL==1);
         CKPREL=1;
      }
   }
   else {
      //Here data, and read request, not write
      do {
         WCOL=0;
         SSPBUFF=//Load rest of data;
      }
      while (WCOL==1);
      CKPREL=1;
   }
   //Make sure no error bits set
   if (OV==1) OV=0;
   if (BF==1) incoming=SSPBUF;
}

You'll need to sort this out a bit...

Second, I'd not have the code to set the timer counter, in the SSP interrupt. If the SSP is getting into an unhandled state, this interrupt could be being called, but the data not being properly handled. I'd have the code in something that reflects a _good_ transaction having happened.

Third, what hardware protection have you got on the I2C lines?. It is possible that what you are seeing, is the actual I2C hardware being 'spiked' by electrical noise, resulting it's internal latches hanging. Though the reset will clear a software problem with the port, or a han, in it's internal state machine, it won't clear such a hardware problem. My lines will always have protection circuitry where they enter the board.

Best Wishes
rnielsen



Joined: 23 Sep 2003
Posts: 852
Location: Utah

View user's profile Send private message

PostPosted: Mon Dec 15, 2008 12:07 pm     Reply with quote

I'm not sure if this would help your problem but I always use a I2C bus accelerator LTC1694. It helps square up the signals.

Ronald
Ttelmah
Guest







PostPosted: Mon Dec 15, 2008 3:50 pm     Reply with quote

Agree wholeheartedly.
I use the P82B96, on longer links through noisy enviroments.

Best Wishes
asmallri



Joined: 12 Aug 2004
Posts: 1635
Location: Perth, Australia

View user's profile Send private message Send e-mail Visit poster's website

PostPosted: Mon Dec 15, 2008 3:53 pm     Reply with quote

Have you checked the I2C bus? Are the pull-ups in place?
_________________
Regards, Andrew

http://www.brushelectronics.com/software
Home of Ethernet, SD card and Encrypted Serial Bootloaders for PICs!!
jsummerour



Joined: 15 Nov 2008
Posts: 4

View user's profile Send private message

PostPosted: Fri Dec 19, 2008 11:08 am     Reply with quote

Sorry to take so long to respond...I really appreciate everyone's responses! I will verify the noise issues this weekend. And yes Asmallri, we do have pullups in place. Smile

If it is due to noise, the LTC1694 chip sounds like a winner!

Best Regards!
_________________
Jason
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
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