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 support@ccsinfo.com

Forcing I2C to Hang

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



Joined: 27 Sep 2005
Posts: 25

View user's profile Send private message

Forcing I2C to Hang
PostPosted: Fri Apr 13, 2018 12:01 pm     Reply with quote

I have added a routine in the master to detect and recover from SDA getting stuck low due to a problem with a slave peripheral, as I have had this happen occasionally. How do I force the problem from the master so that I can verify that my recovery routine works?

I am currently using PCD 5.066 and a PIC24FJ256GB406.
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Fri Apr 13, 2018 1:52 pm     Reply with quote

I once wrote some code, intending to do this test. I wrote the following
routines to emulate CCS's software i2c library with the full source.
This way, you can write code to put in defects in the i2c bitstream for
testing purposes. For example, you could edit i2c_write() to to send 7
clocks instead of 8, or any other error you can think of.

I also have added the i2c reset routine at the end. The method used
in the routine comes from Analog Devices AN-686 appnote:
http://www.analog.com/media/en/technical-documentation/application-notes/54305147357414AN686_0.pdf

These routines were written for 18F, so to convert to PCD you would need
to change 'int8' to 'unsigned int8', etc. I notice the latch_low() macro
only works for PCH. If you want to use this code, I can try to re-write
it for PCD. I would need a few examples of #define statements from
your PIC's .h file, showing the lines for PIN_B0, PIN_B1, PIN_B2, at least.

Also this line would have to be changed for PCD:
Code:
define INSTR_CLK_FREQ  (getenv("CLOCK")/4)

All of it would have to be carefully reviewed to make it work with PCD.
(I don't have PCD).

If you use this code the first thing to do would be to test that it works
correctly with an i2c slave chip, such as an eeprom.

Code:

#define SDA_PIN  PIN_C4
#define SCL_PIN  PIN_C3

//#define NO_STRETCH  // This is normally commented out.

//#use i2c(Master, sda=SDA_PIN, scl=SCL_PIN) //, NO_STRETCH)

//-------------------------------------------
// Timing delay calculations for delays used between
// low level i2c operations.  These definitions take
// into account the PIC's oscillator frequency and the
// specified i2c speed.   The delays are automatically
// adjusted to be very similar in duration to the delays
// in the CCS i2c library routines.

#define I2C_CLK_FREQ   100000      // "Slow" i2c speed is 100 KHz

// This is the # of Tcy periods required to execute the instructions
// that create one bit-banged SCL clock (A full cycle - high and low).
#define I2C_CLK_CODE_CYCLES  17   

#define INSTR_CLK_FREQ  (getenv("CLOCK")/4)


#if  ((INSTR_CLK_FREQ/I2C_CLK_FREQ) - I2C_CLK_CODE_CYCLES) >= 2
  #define DELAY (((INSTR_CLK_FREQ/I2C_CLK_FREQ) - I2C_CLK_CODE_CYCLES)/2)
#else
  #define DELAY  1
#endif


//-------------------------------------------
// Macros for new pin i/o functions used in the i2c
// routines below.  CCS uses these functions in their own
// library code, but does not give them to user's.  So
// I wrote my own macros below.

#ifdef __PCH__
#define latch_low(pin)  \
bit_clear(*(int8 *)((pin >> 3) + 9), (pin & 7))
#else
#error latch_low() macro only works with 18F-series PICs.
#endif

// This function does the same thing as output_low(), except
// that it loads the LATx register bit before it sets the TRIS
// to be an output pin.  This is the same way the CCS library
// code does it.   It's important to setup the output Latch
// value first, and then turn on/off the TRIS.  This avoids
// a short glitch which could occur if you did the TRIS first.
#define drive_low(pin) latch_low(pin); output_drive(pin)

//-------------------------------------------

void i2c_start(void)
{
output_float(SDA_PIN);
delay_cycles(DELAY);
output_float(SCL_PIN); 
delay_cycles(DELAY+3);
drive_low(SDA_PIN);
delay_cycles(DELAY);
drive_low(SCL_PIN);
}

//------------------------------
void i2c_stop (void)
{
output_drive(SDA_PIN);
delay_cycles(1);
output_float(SCL_PIN);
#ifndef NO_STRETCH   
   while(!input_state(SCL_PIN));  // Check for clock stretching
#else
   delay_cycles(2);
#endif
delay_cycles(DELAY);
delay_cycles(3);
output_float(SDA_PIN);
delay_cycles(DELAY);
}

//------------------------------
int8 i2c_write(int8 data)
{
int8 i;

i = 8;

do{
   delay_cycles(DELAY);
   delay_cycles(2);
   drive_low(SCL_PIN);
   delay_cycles(DELAY);

   latch_low(SDA_PIN);  // Set sda latch low initially (but not tris).

   if(shift_left(&data,1,0)) // If data.7 == 1
      output_float(SDA_PIN); //  then let SDA float high
   else
      output_drive(SDA_PIN); // If data.7 == 0, drive sda low

   output_float(SCL_PIN);
#ifndef NO_STRETCH   
   while(!input_state(SCL_PIN));  // Check for clock stretching
#else
   delay_cycles(2);
#endif   
  }while(--i);

delay_cycles(DELAY);
drive_low(SCL_PIN);               
delay_cycles(DELAY);
output_float(SDA_PIN);
delay_cycles(DELAY);
delay_cycles(DELAY);

output_float(SCL_PIN);

#ifndef NO_STRETCH   
   while(!input_state(SCL_PIN));  // Check for clock stretching
#else
   delay_cycles(2);
#endif   

i = 0;
delay_cycles(DELAY);

if(input_state(SDA_PIN))
   bit_set(i, 0);

drive_low(SCL_PIN);
drive_low(SDA_PIN);

return(i);  // Return ACK status
}

//----------------------------------
int8 i2c_read(int8 ack)
{
int8 data;
int8 i;

i = 8;

do{
   output_float(SDA_PIN);
   delay_cycles(DELAY);
   delay_cycles(3);    // Compensates for shorter code on high SCL pulse
   output_float(SCL_PIN);

#ifndef NO_STRETCH   
   while(!input_state(SCL_PIN));  // Check for clock stretching
#else
   delay_cycles(2);
#endif   

   shift_left(&data,1, input_state(SDA_PIN));
   
   delay_cycles(DELAY);
   
   drive_low(SCL_PIN);

}while(--i);

output_float(SDA_PIN);
delay_cycles(DELAY);
latch_low(SDA_PIN);

if(ack)
   output_drive(SDA_PIN);
delay_cycles(DELAY);

output_float(SCL_PIN);               

#ifndef NO_STRETCH   
   while(!input_state(SCL_PIN));  // Check for clock stretching
#else
   delay_cycles(2);
#endif   

delay_cycles(DELAY);

drive_low(SCL_PIN);
delay_cycles(DELAY);

drive_low(SDA_PIN);

return(data);
}

//---------------------------------
// i2c_bus_reset(void)

// Returns TRUE if bus was reset, or False if not.

// The following text comes from the Analog Devices AN-686 appnote.
// Title:  Implementing an I2C® Reset
//
// It is the master’s job to recover the bus and restore control to the main
// program. When the master detects the SDA line stuck in the low state,
// it merely needs to send some additional clocks and generate a STOP
// condition. How many clocks will be needed?
// The number will vary with the number of bits that remain to be sent by
// the slave. The maximum would be 9. This number is derived from the
// worst-case scenario, the case where the processor was reset just after
// sending an ACK to the slave. Now the slave is ready to send 8 data bits
// and receive 1 ACK (or NAK in the case of a bus recovery).
// The procedure is as follows:

// 1) Master tries to assert a Logic 1 on the SDA line.
// 2) Master still sees a Logic 0 and then generates a clock
//    pulse on SCL (1-0-1 transition).
// 3) Master examines SDA. If SDA = 0, go to Step 2; if
//    SDA = 1, go to Step 4.
// 4) Generate a STOP condition.

// Note that this process may need to be repeated because
// the cleared SDA line may have been cleared for the next
// bit, which was a 1. There may be some concern about the
// effect this additional clocking and stopping has on other
// peripherals. There is no adverse effect; other slaves are
// not paying attention due to the fact that they have not
// been addressed. Only the slave that had the interrupted
// message will respond to the clocks
//
// The routine below is written according to the method given in this
// section of AN-686:  Solution 1: Clocking Through the Problem

int8 i2c_bus_reset(void)
{
int8 i;

// Check if hardware MSSP is in use. If so, disable it.
// (Not implemented yet).

// Let the i2c pull-up resistors pull SDA and SCL up to a high level.
output_float(SDA_PIN);
output_float(SCL_PIN);
delay_us(10);

// Now check if SCL is low.  If so, return False.
// We can't do the reset procedure if SCL is
// stuck at a low level.
if(!input_state(SCL_PIN))
   return(FALSE);

// It's possible for the i2c slave to be locked up,
// but with the SDA pin at a high level.  So we won't
// check for SDA = 1 at this point and return True.
// We will continue with the reset procedure below.


// If SDA is stuck at a low level, attempt to clear
// the condition by sending the slave up to 9 SCL clocks.
// Check SDA after each clock, to see if it's now at
// a high level.  If so, the slave has released the bus
// and we can return True.  If not, the slave is still
// locked up, so we return False.

for(i = 0; i < 9; i++)
   {
    output_low(SCL_PIN);  // Limit clock freq to 100 KHz
    delay_us(5);          //  which is the i2c "slow" speed.
    output_float(SCL_PIN);
    delay_us(5);

    // Check if the slave released the SDA pin.  If so,
    // generate a Stop condition and return success.   
    if(input_state(SDA_PIN)) 
      {
       // Generate a Stop condition.
       output_low(SDA_PIN);
       delay_us(5);
       output_float(SCL_PIN);
       delay_us(5);
       output_float(SDA_PIN);
       delay_us(5);
       
       return(TRUE);
      }
   }

// If we couldn't free the SDA line in 9 clocks, then give up
// and return failure.
return(FALSE);
}
SteveW



Joined: 27 Sep 2005
Posts: 25

View user's profile Send private message

PostPosted: Fri Apr 13, 2018 6:52 pm     Reply with quote

PCM Programmer - Thank you for the detailed response. I should have mentioned that I am using a hardware I2C interface. I will take a hard look at using your code in a function to solely create an error that can be triggered via a serial test port, then re-enable the hardware I2C function.

Also, perhaps I am missing the obvious, but I can't find the source code for any built-in functions in the CCS installation directory. Where do I find them?
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Fri Apr 13, 2018 7:32 pm     Reply with quote

They don't supply it. The above source code doesn't come from CCS.
Ttelmah



Joined: 11 Mar 2010
Posts: 19195

View user's profile Send private message

PostPosted: Fri Apr 13, 2018 11:42 pm     Reply with quote

I don't think what you want can be done.
Think about it. The I2C hangs because the slave device holds the SCK line low at a specific point in the clock sequence. Probably clock 9. Trying to simulate on the master is not going to actually simulate this hardware event, and you are not going to be able to simulate the timing except by luck....
What you could do, is add a second PIC as the slave device, and program this to hold the SCK line low after the eighth clock.
SteveW



Joined: 27 Sep 2005
Posts: 25

View user's profile Send private message

PostPosted: Thu Apr 19, 2018 9:45 pm     Reply with quote

Thank you both. I have only seen the data line getting stuck low. For now I will simply add a routine to look for the data line low and try clocking it to recover, and hope for the best.
Ttelmah



Joined: 11 Mar 2010
Posts: 19195

View user's profile Send private message

PostPosted: Thu Apr 19, 2018 11:39 pm     Reply with quote

The data line being held low shouldn't hang I2C. It'll mean there will be no replies, but the bus itself will remain able to 'work'. It should be possible to simply test for the invalid replies. Clock being held is normally the one that locks the bus, since then the master can't clock to get a reply. SDA low normally happens because the slave has got into an unexpected state. It's normally recoverable by sending a sequence of clocks to the device, until SDA goes high.
SteveW



Joined: 27 Sep 2005
Posts: 25

View user's profile Send private message

PostPosted: Fri Apr 20, 2018 10:26 am     Reply with quote

Got it, thanks.
allenhuffman



Joined: 17 Jun 2019
Posts: 537
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

PostPosted: Mon Sep 09, 2019 12:31 pm     Reply with quote

PCM programmer wrote:

These routines were written for 18F, so to convert to PCD you would need
to change 'int8' to 'unsigned int8', etc. I notice the latch_low() macro
only works for PCH. If you want to use this code, I can try to re-write
it for PCD. I would need a few examples of #define statements from
your PIC's .h file, showing the lines for PIN_B0, PIN_B1, PIN_B2, at least.
...


What is the latch_low() macro actually doing? We run PIC24s and I've been looking at this to see if we could use it on the Slave side to do some diagnostics.
_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Mon Sep 09, 2019 12:44 pm     Reply with quote

latch_low() takes a CCS pin number, such as PIN_B0, and deciphers the
encoding into the SFR memory address (for the LATB register in this case)
and the bit number. It then places these numbers into the bit_clear()
function and calls it to clear the specified bit in the LATB register.

The whole purpose of latch_low() is to set a LATx bit low, by specifying
a CCS pin number.

Actually, it just occurred to me that you could do the same thing, by
specifying #use fast_io() and output_low(). It generates the same code.


Last edited by PCM programmer on Mon Sep 09, 2019 1:15 pm; edited 1 time in total
allenhuffman



Joined: 17 Jun 2019
Posts: 537
Location: Des Moines, Iowa, USA

View user's profile Send private message Visit poster's website

PostPosted: Mon Sep 09, 2019 1:13 pm     Reply with quote

PCM programmer wrote:
The whole purpose of latch_low() is to set a LATx bit low, by specifying a CCS pin number.


I was just looking at the generated assembly for output_low(), output_float() (which isn't in my help file so I was glad to see it existed), etc. seeing what they were doing.

Is there anything that would prevent me from creating this for PIC24?
_________________
Allen C. Huffman, Sub-Etha Software (est. 1990) http://www.subethasoftware.com
Embedded C, Arduino, MSP430, ESP8266/32, BASIC Stamp and PIC24 programmer.
http://www.whywouldyouwanttodothat.com ?
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Mon Sep 09, 2019 1:16 pm     Reply with quote

You should be able to. See my new comment about latch_low() in my edited post.
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