Currently I am running a FreeRTOS environment on the K64 in which I have a task setup to communicate with various I2C peripherals once a second. However, I've been noticing while debugging that when ever I do a software reset or re-flash my code, the I2C interface will more often than not return that it is busy on the very first instance of an I2C transaction after a reset. I feel like this is likely related to the possibility that the interface was reset before any stop or acknowledge conditions were met and the slave devices are in a dead-lock keeping the interface busy.
How do I handle the case of an I2C Dead lock scenario? I found the following application note regarding the topic:
Its a little dated, but it has software functions for I2C Recovery. It does not seem like the fsl_i2c.c driver handles recovery. There are flags that are set for a timeout but it does not seem to be used for anything.
I assume the approach would be that in all of my software which attempts an I2C transaction, I would need to poll if the interface is busy and do a manual count at which point a 'timeout' event would happen and the I2C interface would have to be reset by sending a couple of clock pulses followed by a stop signal.
Has anyone already encountered something similar to this/implemented this solution?
I think sending clock pulses until SDA is back high, as proposed by Mark, is better than always sending 9 clock pulses.
A slave hangs the SDA line low in the following cases :
- when it sends an ACK bit to acknowledge a byte that has been written to it by the master (address byte or data byte). In this case only one clock pulse is needed to make the slave release SDA. Doing 9 clock pulses would write an extra byte to the device with value 0xFF (undriven SDA) and would stop at the next ACK with the slave driving SDA low again. This may be dangerous !
- when it sends a data bit at 0 because a byte is read from it by the master. In this case, the number of clock pulses depends on the data; as many as required to reach the next data bit at 1, or the ACK bit where the slave releases SDA (the ACK is done by the master during a read).
So as Mark, I prefer doing only the required number of clock pulses to get SDA high. At this point both SDA and SCL should be high, so the next start done on the bus by the master will be detected by the slaves that will abort any pending transfer and answer as expected.
From the I2C spec:
I2C-bus specification and user manual
Rev. 6 — 4 April 2014
3.1.16 Bus clear
In the unlikely event where the clock (SCL) is stuck LOW, the preferential procedure is to
reset the bus using the HW reset signal if your I2C devices have HW reset inputs. If the
I2C devices do not have HW reset inputs, cycle power to the devices to activate the
mandatory internal Power-On Reset (POR) circuit.
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.
This same condition can arise from a variety of sources. In one of my applications, various conditions can reset the CPU--the problem is that if the CPU is reset mid-transaction, the I2C bus will be left in an undefined state. Sending the 9 pulses as described forces the slave device that was in use to release the bus.
In my k64f baremetal project I have found 2 issues with the i2c communication.
Issue 1) is the same as yours. Sometimes after flashing and initiating a debug session i2c will be stuck. I've never seen it happening with the release build, so I don't worry too much about it. I just power cycle the board and most times it will debug just fine.
Issue 2) is that sometimes the i2c communication will stop due to not receiving a stop signal from the slave device, locking the i2c in a busy state. As a workaround I set up a pseudo-watchdog to check if the busy flag is on and restart the i2c driver. So far, seems to fix the lockup issue.
Below is an example of my pseudo-watchdog ISR:
/* Clear interrupt flag.*/
PIT_ClearStatusFlags(PIT, kPIT_Chnl_2, PIT_TFLG_TIF_MASK);
/*deal with I2C*/
if(I2C0->S & kI2C_BusBusyFlag)
//preserve transfer status
//restore transfer status
Although not an exact answer to your question, I hope this information is useful to you.