To add a timeout to the LPCOpen I2C stack, I suggest the following changes to i2c_17xx_40xx.c:
+volatile uint32_t i2c_busy_timeouts = 0;
+const uint32_t i2c_busy_limit = 9999; // adapt value to hardware setup and I2C clock rate
void Chip_I2C_EventHandler(I2C_ID_T id, I2C_EVENT_T event)
{
struct i2c_interface *iic = &i2c[id];
volatile I2C_STATUS_T *stat;
+ uint32_t busy_counter = 0;
/* Only WAIT event needs to be handled */
if (event != I2C_EVENT_WAIT) {
return;
}
stat = &iic->mXfer->status;
/* Wait for the status to change */
- while (*stat == I2C_STATUS_BUSY) {}
+ while (*stat == I2C_STATUS_BUSY) {
+ busy_counter++;
+ if (busy_counter > i2c_busy_limit) {
+ // I2C bus hang-up identified...
+ i2c_busy_timeouts++;
+ *stat = I2C_STATUS_BUSERR;
+ i2c[id].ip->CONSET = I2C_CON_STO; // set stop bit for forced access to the I2C bus
+ break;
+ }
+ }
}
and further down in Chip_I2C_MasterTransfer():
iic->mEvent(id, I2C_EVENT_WAIT);
iic->mXfer = 0;
- /* Wait for stop condition to appear on bus */
- while (!isI2CBusFree(iic->ip)) {}
+ if (xfer->status != I2C_STATUS_BUSERR) {
+ /* Wait for stop condition to appear on bus */
+ while (!isI2CBusFree(iic->ip)) {}
+ }
The timeout breaks the busy wait loop which otherwise would not exit on bus obstruction (e.g., due to holding either the SDA or SCL line LOW by any device on the bus) or bus hang-ups (e.g., due to intermittant signals by an uncontrolled source that generate a superfluous START condition or mask a STOP condition).
While we are at it, you can see that in Chip_I2C_MasterTransfer() after the event I2C_EVENT_WAIT has been handled, the pointer iic->mXfer is set to NULL. If thereafter an I2C interrupt occurs (maybe due to intermittant signals), a hard fault will generated. This is because the I2C interrupt service routine (at least in master mode) will call Chip_I2C_MasterStateHandler() and that function will then call handleMasterXferState() with mXfer as parameter. handleMasterXferState() will then happily dereference this NULL pointer and consequently generate a hard fault.
To fix this issue, I suggest the following changes to Chip_I2C_MasterStateHandler():
/* State change handler for master transfer */
void Chip_I2C_MasterStateHandler(I2C_ID_T id)
{
+ if (!i2c[id].mXfer) {
+ // Sometimes (e.g., when I2C is blocked intermittently)
+ // something (e.g., Chip_I2C_MasterTransfer())
+ // clears i2c[id].mXfer. In this case don't call
+ // handleMasterXferState() as it dereferences mXfer
+ // and this leads to a hard fault.
+ i2c[id].ip->CONCLR = I2C_CON_SI; // clear interrupt bit to stop isr from being called repeatedly
+ return;
+ }
if (!handleMasterXferState(i2c[id].ip, i2c[id].mXfer)) {
i2c[id].mEvent(id, I2C_EVENT_DONE);
}
}
Does that make sense to you?
Cheers!
Daniel
Will you tell me what did you do? I'm trying to solve the same issue.
Thanks!
My code suggestions are based on the LPCOpen I2C stack for LPC175x/176x mcu and are reproduced below. Also, I suggest to get a logic analyzer (if you don't have one already). I'm using a Saleae logic analyzer.
lpcware wrote:
Content originally posted in LPCWare by sundarapandian on Tue Apr 15 18:39:17 MST 2014
The loops inside the event handlers are intentionally in an infinite busy wait, as a way to catch bug in the I2C application or hardware (slave/bus lines). If you want to use Chip_I2C_SetMasterEventHandler API to override the function with a function of your own, with timeout.
Hi,
Thank you for providing this critical piece of information. I wonder which loops are " intentionally in an infinite busy wait".
So I set up an I2C bus with SDA and SCL permanently pulled down low to simulate a hardware bug or bus obstruction. Then I connected this bus to an LPC1769. Finally, I used lpcopen_2_10_lpcxpresso_nxp_lpcxpresso_1769 to create a minimal I2C application that reads bytes using Chip_I2C_MasterRead()
Indeed, according to the debugger, the execution is permanently stuck in Chip_I2C_EventHandler():
/* Wait for the status to change */
while (*stat == I2C_STATUS_BUSY) {}
Are you saying, this loop is an example for the intentionally added "infinite busy wait" in order to catch application or hardware bugs?
I'm baffled as I could not find any comments to this regard in i2c_17xx_40xx.c/h. That is, for high quality source code I expect that comments are added to indicate locations where an infinite loop is added to catch application or hardware bugs. The piece of code quoted above does not live up to this expectation. It looks "harmless" and in not way invites the developer to watch this location, e.g., by setting a break point to it.
I'm also bewildered that the LPCOpen example code does not take into account the advise given in the User Manual, e.g. UM10360, chapter 19.9.7.4 "I2C-bus obstructed by a LOW level on SCL or SDA" etc.
Thanks
Daniel
#P lpc_chip_175x_6x Index: src/i2c_17xx_40xx.c =================================================================== --- src/i2c_17xx_40xx.c(revision 69) +++ src/i2c_17xx_40xx.c(working copy) @@ -359,6 +359,7 @@ /* Chip polling event handler */ void Chip_I2C_EventHandlerPolling(I2C_ID_T id, I2C_EVENT_T event) { +int retry = 500; struct i2c_interface *iic = &i2c[id]; volatile I2C_STATUS_T *stat; @@ -373,6 +374,9 @@ if (Chip_I2C_IsStateChanged(id)) { Chip_I2C_MasterStateHandler(id); } +if(--retry == 0){ +LPC_I2Cx(id)->CONSET = I2C_CON_STO; +} } } |