Author Topic: I2C device dead-lock recovery (Kinetis)  (Read 7203 times)

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3237
    • View Profile
    • uTasker
I2C device dead-lock recovery (Kinetis)
« on: April 26, 2014, 05:45:08 AM »
Hi All

While working on a FRDM-KL25Z and continuously reading the accelerometer data via I2C bus I noticed that when powering the board it was always reliable, but when resetting the board (commanded reset or reset button) there was quite a high chance that the I2C bus would be blocked.

The reason for this is due to the fact that the accelerometer reading (4 bytes) is taking place using continuous repeated-start mode and the slave device is practically sending an acknowledgement about 15% of the time. If the reset button is pressed during the time that the accelerometer is sending the I2C ACK it will block in that state and continue driving the SDA line. After the reset is is not possible to use the I2C bus since it is continuously in the busy state and further resets of the processor don't help - a power cycle does however since it resets the slave device.

In the mentioned case the risk of this state was very high and so a reliable solution was investigated that would automatically detect the state and resolve.

The solution is as follows:

- rather than configure the I2C pins when the I2C interface is initialised they are left as inputs with pull-ups enabled

- the first time that the I2C bus is to be used for transmission (transmission takes place as first operation of any read or write) the bus state is checked.

- if it is detected in the busy state, the SCL pin is set to an output and clocks generated until the busy state no longer exists (the clocks remove the slave from its present ACK state - several clocks may be required)

- the pins are then set to their final I2C peripheral usage

- once the pins are set no further checking is required for subsequent I2C use and normal interrupt or DMA driven modes are possible

 
The result was that, although the I2C slave was still holding the bus in about 15% of reset cases, the automatic detection/recovery reliably resolved it with no user code intervention.

 
This is now included as integral part of the uTasker I2C driver code - for reference, the detection/recovery code for the pins as used by the accelerometer on the KL25 freedom board is show below.
 

In case of interest in trying this, I have attached a binary for the FRDM-KL25Z which continuously reads the accelerometer via I2C - no pauses). If the SDA line is measured when the reset button is held down it will be seen that the bus busy state is reprocuced often - subsequent recovery is 100%. The accelerometer values are printed to the UART (via OpenSDA virtual COM) ever 100th reading (about 4x per second).

 

Regards

 

Mark

 

Pin configuration during I2C initialisation is restricted to ensuring they are inputs:

_CONFIG_PORT_INPUT_FAST_HIGH(E, (PORTE_BIT25 | PORTE_BIT24), (PORT_ODE | PORT_PS_UP_ENABLE));

 

On first use the following code checks the bus state and recovers if neede, followed by setting the final peripheral pin functions:

 

while (_READ_PORT_MASK(E, PORTE_BIT25) == 0) {   // if the SDA line is low we clock the SCL line to free it

    _CONFIG_DRIVE_PORT_OUTPUT_VALUE_FAST_HIGH(E, PORTE_BIT24, 0, (PORT_ODE | PORT_PS_UP_ENABLE)); // set output '0'

    fnDelayLoop(10);

    _CONFIG_PORT_INPUT_FAST_HIGH(E, PORTE_BIT24, (PORT_ODE | PORT_PS_UP_ENABLE));

    fnDelayLoop(10);

}

_CONFIG_PERIPHERAL(E, 25, (PE_25_I2C0_SDA | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SDA on PE25 (alt. function 5)

_CONFIG_PERIPHERAL(E, 24, (PE_24_I2C0_SCL | PORT_ODE | PORT_PS_UP_ENABLE)); // I2C0_SCL on PE24 (alt. function 5)