Initially I based my reset off of this:
3.1.16 Bus clear
Page 20: "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."
That seemed to catch a lot of the problems, but occasionally it would still lock up afterwards with both SDA and SCL high.
Then I found this:
Solution 1: Clocking Through the Problem
Page 2: "It is the master’s job to recover the bus and restore control to the main program. When the master detects the SDA line stuck in the low state, it merely needs to send some additional clocks and generate a STOP condition. How many clocks will be needed? The number will vary with the number of bits that remain to be sent by the slave. The maximum would be 9."
OK, so I need to bit bang a STOP condition...
K20 Sub-Family Reference Manual
Page 1185: "The master can terminate the communication by generating a STOP signal to free the bus. A STOP signal is defined as a low-to-high transition of SDA while SCL is asserted. The master can generate a STOP signal even if the slave has generated an acknowledgement, at which point the slave must release the bus."
My final code looks like this:
Code:
void unlockthebus(void){
Serial.println("Resetting i2c Bus...");
//change i2c pins back to digital IO
pinMode(19,OUTPUT);
pinMode(18,INPUT);
int count = 0;
//watch for sda to go high
//try pulsing the clock a max of 9 times
while (digitalRead(18) == 0 && count < 10 ){
//pulse clock once
digitalWrite(19,HIGH);
digitalWrite(19,LOW);
digitalWrite(19,HIGH);
count++;
Serial.println(count);
}
//bit bang a stop
pinMode(18,OUTPUT);
digitalWrite(18,LOW);
digitalWrite(19,HIGH);
digitalWrite(19,LOW);
digitalWrite(18,LOW);
digitalWrite(19,HIGH);
//try to restart wire using initial settings
Wire.begin(I2C_MASTER, 0x00, I2C_PINS_18_19, I2C_PULLUP_EXT, I2C_RATE_2400);
Serial.println("Fixed.");
}
I didn't need any delays between writing bits, but this will likely depend on your device. It would be best to bit bang at a speed similar to your i2c bus.
Then I altered sendTransmission and sendRequest to return true or false depending on I2C_S_BUSY. This way I can't get stuck in the while loop with a hung bus.
I replaced:
while(*(i2c->S) & I2C_S_BUSY)
with:
if(*(i2c->S) & I2C_S_BUSY){
return false;
}
(This could have an timeout before returning false if desired.)
I'll upload some full example code once I get it cleaned up...