16
Luminary Micro TM LM3SXXXX / 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)
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()