Author Topic: Fixing an IIC lockup with SDA held low  (Read 38000 times)

Offline mhoneywill

  • Full Member
  • ***
  • Posts: 173
    • View Profile
Fixing an IIC lockup with SDA held low
« on: August 05, 2010, 03:15:18 PM »
Hi Mark,

We've it a problem where a noise spike can cause the IIC bus to lockup the condition is described here on a TI website here http://processors.wiki.ti.com/index.php/I2C_Tips

The problem is described below

A problematic scenario can arise if the processor/I2C module gets reset while it is in the middle of mastering a transfer. In this scenario the external slave might be holding SDA low to transmit a 0 (or ACK). In this case it will not release SDA until it gets another falling edge on SCL. Even in this case it's not until it tries to transmit a '1' that it will actually release SDA after seeing SCL fall. The end result is that the bus will hang. If the I2C tries to initiate a new transfer it will hit an "arbitration lost" condition because SDA won't match the address it's sending. There are a couple ways to recover from this scenario.

Option 1: For devices that mux the SCL/SDA pins with GPIO, the easiest thing is to configure the pins for GPIO operation and toggle SCL until the slave releases SDA. At this point you should be able to resume normal operation.

Option 2: Many devices don't mux SCL/SDA with GPIO since the I2C I/O cells are often special open drain cells. A workaround has been reported to work even on these devices. By configuring the I2C for "free data format" and then reading a byte the I2C will immediately start sending clocks to input data (rather than trying to send an address). This can be used to free up the bus.


I.m not sure what "free data format" is, so it seems like the solution is to trap the condition and to switch the SCL line back to GPIO then toggle it a nuber of times (say 16) to get out of this state. Then restart the IIC bus. This poses a number of questions

1. Looking at the Port configration Marcro's it looks like they are not configured for selecting a GPIO function after they have been used for a pheripheral. (They don't access the GPIOAFSEL_x registers) I suggest adding GPIOAFSEL_##ref &= ~(pins); as shown below.

2. Looking at the code below, why is the statement SRCR2 &= ~CGC_GPIO##ref; repeated? is this a typo or for timing reasons.

3. If I reinitialise the IIC using fnConfigIIC can you forsee any problems?

4. How do I detect the problem in the first pace, as the IIC module does not report errors (I guess I could try reading a register which I know to ge a fixed value and if I see 0xff then assume an error)

Code: [Select]
// Port macros
//
// Configure pins as output, including enabling power and digital use. eg. _CONFIG_PORT_OUTPUT(A, PORTA_BIT0);
//
#define _CONFIG_PORT_OUTPUT(ref, pins)  RCGC2 |= CGC_GPIO##ref; SRCR2 &= ~CGC_GPIO##ref; SRCR2 &= ~CGC_GPIO##ref; \
GPIOAFSEL_##ref &= ~(pins); GPIODEN_##ref |= (pins); GPIODIR_##ref |= (pins); _SIM_PORT()
// Configure pins as input, including enabling power and digital use. eg. _CONFIG_PORT_INPUT(B, PORTB_BIT2);
//
#define _CONFIG_PORT_INPUT(ref, pins)   RCGC2 |= CGC_GPIO##ref; SRCR2 &= ~CGC_GPIO##ref; SRCR2 &= ~CGC_GPIO##ref; \
GPIOAFSEL_##ref &= ~(pins); GPIODEN_##ref |= (pins); GPIODIR_##ref &= ~(pins); _SIM_PORT()

// Configure Pullups and Pulldowns
#define _CONFIG_PORT_PULLUP(ref, pins)   GPIOPUR_##ref |= (pins); _SIM_PORT()
#define _CONFIG_PORT_PULLDN(ref, pins)   GPIOPDR_##ref |= (pins); _SIM_PORT()
#define _CONFIG_PORT_INPUT_PULLUP(ref, pins)   RCGC2 |= CGC_GPIO##ref; SRCR2 &= ~CGC_GPIO##ref; SRCR2 &= ~CGC_GPIO##ref; \
GPIOAFSEL_##ref &= ~(pins); GPIODEN_##ref |= (pins); \
GPIODIR_##ref &= ~(pins); GPIOPUR_##ref |= (pins); _SIM_PORT()
#define _CONFIG_PORT_INPUT_PULLDN(ref, pins)   RCGC2 |= CGC_GPIO##ref; SRCR2 &= ~CGC_GPIO##ref; SRCR2 &= ~CGC_GPIO##ref; \
GPIOAFSEL_##ref &= ~(pins); GPIODEN_##ref |= (pins); \
GPIODIR_##ref &= ~(pins); GPIOPDR_##ref |= (pins); _SIM_PORT()
« Last Edit: August 05, 2010, 03:23:28 PM by mhoneywill »

Offline mhoneywill

  • Full Member
  • ***
  • Posts: 173
    • View Profile
Re: Fixing an IIC lockup with SDA held low
« Reply #1 on: August 05, 2010, 08:01:01 PM »
Well its partially working, the concept works but I can't just re-initialise my IIC task and re-run 

fnOpen( TYPE_IIC, FOR_I_O, &tIICParameters);

I don't think the drivers were ever coded to be  restarted. If I try re-initialising the task then the software crashes, I'm presuming its because IICPortID is not getting allocated properly.

Next I tried going a bit deeper and running fnConfigIIC(&tIICParameters); this improved things. But code is still not starting back up again cleanly, and the application resrats soon after. I'm suspecting some buffers need to be cleared.

When I detect IIC lockup, (done via a timeout counter) I run this bit of code

      {
      fnKickSCL();                           // Clock the SCL line 16 times to free up SDA
      fnConfigIIC(&tIICParameters);   // Init IIC hardware
      IICIdle = true;                        // Reset my internal flag that flags if a transmission is in progress
      }

Where fnKickSCL() is defined below

void fnKickSCL(void)
{
    int i;
   int delay;
   #define IICDELAYTIME 10

   _CONFIG_PORT_OUTPUT(B, PORT_BIT2);
   _CONFIG_PORT_INPUT_PULLUP(B, PORT_BIT3);
   
   for(i=0; i<16; i++)
   {
      for(delay=0; delay<IICDELAYTIME; delay++) _CLEARBITS(B, PORT_BIT2);
      for(delay=0; delay<IICDELAYTIME; delay++) _SETBITS(B, PORT_BIT2);
   }
}

This is now at least restarting the IIC Bus as can be seen in the attached logic trace, I've got to find out where the software error is.

How do I go about resetting the IIC buffers, I don't mind about loosing data?

Cheers

Martin

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3243
    • View Profile
    • uTasker
Re: Fixing an IIC lockup with SDA held low
« Reply #2 on: August 05, 2010, 10:52:31 PM »
Hi Martin

The driver interfaces are generally not designed to be closed - this is quite a rare requirement in (small, dedicated) embedded systems.

Remember that the open will allocate buffer resources for the driver and so opening a second interface will also try to allocate another set (if the second open is not already blocked). Therefore an interface (and its handle) should only be allocated once.

You can usually reset all buffers by commanding a flush. However I see that the I2C driver doesn't support the flush option. It is however easy to add and could also be executed in the same way as the tty driver code, which simply sets the buffer pointers and counters back to the initial state:

        ptTTYQue->tty_queue.get = ptTTYQue->tty_queue.put = ptTTYQue->tty_queue.QUEbuffer;
        ptTTYQue->msgs = ptTTYQue->tty_queue.chars = 0;

Note that there may be an input and output buffer to perform this on.

Regards

Mark

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3243
    • View Profile
    • uTasker
Re: Fixing an IIC lockup with SDA held low
« Reply #3 on: August 09, 2010, 11:53:14 PM »
Hi Martin

The port macros (also peripherals) are 'one-shot'. They neither respect moving from one peripheral to another nor moving from a peripheral back to a port. These are quite rare in most projects. Therefore an additional command is indeed required. Either this could generally be added (I have however avoided it up to now) or else a 'reset' macro could be added which first sets the port pin(s) back to its default (i.e. GPIO input in most cases), after which the normal macro can be used again.

In my code base I don't have the double SRCR2 &= ~CGC_GPIO##ref; Therefore it was certainly a typo and I corrected it at some point.

fnConfigIIC() should not be re-used. This will otherwise try to allocate another handle and also take a new set of buffers, etc.

As to the I2C glitch problem. You are talking to someone who has spend more than his fair share of (wasted) hours on I2C bus lock-ups... In fact this is a reason why I haven't made any plans to (ever) add I2C slave functionality to get people trying to play with multi-master I2C bus systems. It is very easy to get such systems working but to get something working reliably is another story, especially when some masters are based on ASICs with undesired effects such a glitching etc...(maybe standard chips are better but I got burned and prefer to stay away - also there are often easier ways to network such devices).

One problem is that a lock-up due to a slave holding the SDA line low may not be detected at all by the controller. Maybe the controller will read back its signals and notice that something is not right but I don't know that it is always the case. I was using an external Phillips controller once and I don't think that it reported such things since it saw the slave's ACK and that was OK for it. I also had the problem that these were dedicated ports and I couldn't control them to unlock the bus. The solution was to connect two a GPIO to the lines so that these could read in the states (we also had a state when an external device would get in a strange state after a reset) and then do some forced toggling (set as output and drive some pulses) and get the bus to recover somehow.

Since the Luminary parts have multiplexed pins (check that they can also really drive high since some I2C  pins - eg. LPC2xxx - are also open-drain only as GPIO) you should be able to achieve monitoring and recovery although I don't know the best way to actually detect the state - probably you have more experience in the meantime as to the best solution in this case (?).

Regards

Mark

Offline mhoneywill

  • Full Member
  • ***
  • Posts: 173
    • View Profile
Re: Fixing an IIC lockup with SDA held low
« Reply #4 on: October 28, 2010, 06:54:44 PM »
I've been having more problems with the IIC hardware on the Luminary Micro Chips see my post here

http://e2e.ti.com/support/microcontrollers/stellaris_arm_cortex-m3_microcontroller/f/471/p/71089/257873.aspx#257873

I'll keep this forum updated with any updates.

Martin