Releasing a stuck I2C bus

fernando_g

Senior Member
#1
The I2C bus is so useful, and there are so many different available devices, that on each successive designs one starts to use more and more devices.

As wonderful as it is, one should remember that the I2C bus was never intended for long distances or noisy environments. That doesn't preclude us naive engineers from pushing our luck....until we run out of it.

I had a project where I required to use a system on which a pair of devices would have to be outside, connected by a 150 cm flat ribbon cable.
I followed the usual precautions: generous capacitive decoupling at the remote device, routing the SCL and SDA signals separate from each other (to prevent crosstalk), using only the I2Cslow mode, and operating all the devices from 5 volt (to increase noise immunity).

Everything worked just fine for a year. But one of the external devices (a combined temperature-RH sensor) its performance was unsatisfactory, which was corroborated on several online forums.
The suggestion was to use a different, highly praised device, which unfortunately ran only on 3 volt. The breakout board already incorporated an LDO and level shifter, and upon bench testing it, its performance was well above that of the previous device.

Placing the device on its real environment however, soon revealed that the bus randomly yet reliably would get stuck. The SDA signal would remain held low. With the aid of logic analyzer, I figured it out that the temperature/RH slave was holding the line low, preventing further bus operation.

I found then an NXP datasheet (UM10204), which on page 20 has this very valuable piece of information:

If the data line (SDA) is stuck LOW, the master should send nine clock pulses. The device that held the bus LOW should release it sometime within those nine clocks. If not, then use the HW reset or cycle power to clear the bus.

Wanting to avoid the hardware solution, which would require cutting and patching the main board, I decided to attempt the SW solution first.
IT WORKED!
So I'm sharing how I implemented my solution. Tested on a 14M2.

Code:
read_temperature:	
	'on the next lines, temp from I2C will be read
	HI2CSETUP I2Cmaster, %10000000, i2cslow, i2cbyte 'address0x80
	HI2COUT (%11110011) '0xF3 trigger temp measurement
pause 50 '50 ms delay required for temperature conversion
	HI2CIN (raw_tempMSB,raw_tempLSB) 'read temperature MSB, LSB
if pinB.4 = 1 then 'if SDA has been released properly, bypass the I2C clearing routine
	goto read_RH
endif
gosub clear_SDA 'however if bus is stuck, clock it 9 times
goto read_temperature 'retake the temperature reading

read_RH:
'on the next lines, RH from I2C will be read
HI2CSETUP I2Cmaster, %10000000, i2cslow, i2cbyte 'address0x80
HI2COUT (%11110101) '0xF5 trigger RH measurement
pause 17 '17ms reading delay
HI2CIN (raw_RH_MSB,raw_RH_LSB) 'read humidity MSB, LSB 
if pinB.4 = 1 then 'if SDA has been released properly, bypass the unsticking routine
	goto next_process
endif
gosub clear_SDA 'however if bus is stuck, clock it 9 times
goto read_RH 'retake the RH reading

next_process:


clear_SDA:
	setfreq m8 'to meet 5us pulse with 5us spacing
high B.3 'set SCL to high
for ctloop= 0 to 8 '9 pulses per NXP datasheet
	pulsout B.3, 1
	pauseus 1
next ctloop
setfreq m4
return
 
Top