Problem: I am using the K22, a MK22FN512VLH12 processor with KSDK 1.3. I found that after leaving the code running for 15 minutes or so, frequently polling an accelerometer on the I2C bus, the code fails the assert at the beginning of the I2C_HAL_SendStop function. Further investigation led me to see that in I2C_DRV_MasterIRQHandler, we are taking the "if (wasArbLost)" condition, indicating that I2C bus arbitration was lost.
The K22 is releasing the lines before the stop, as shown in the bottom trace in the picture. The top trace is a correct transfer.
I also found that if I continuously call I2C_DRV_MasterReceiveDataBlocking, it will produce this same failure within 2-3 minutes.
Isolate the problem: The K22 is the only master on the bus, so I isolated the bus so that only the K22 and the accelerometer were on the line. The problem continued. I removed the accelerometer and replaced it with a geomagnetic sensor and had the same result. This effectively indicated that the K22 is the cause of the problem.
Putting a 25us delay after every call to I2C_DRV_MasterReceiveDataBlocking causes it to no longer fail the assert in the I2C_HAL_SendStop function. It will then return "kStatus_I2C_AribtrationLost" the first time through. On subsequent calls, it will then fail the "if (!master->i2cIdle)" condition in I2C_DRV_MasterReceive and return "kStatus_I2C_Busy" everytime it is called, effectively disabling the I2C bus.
Reading the values of the I2C Status Register (I2Cx_S) during the I2C_DRV_MasterIRQHandler function indicates that the BUSY bit is set and doesn't clear.
When arbitration is lost, what is the proper way to reset the BUSY bit? According to the K22's reference manual, the BUSY bit of the I2C Status Register (I2Cx_S) is cleared when a STOP signal is detected. If I call I2C_HAL_SendStop when it returns from I2C_DRV_MasterReceiveDataBlocking it will fail the assert. The only effective workaround I have found is to call I2C_DRV_MasterDeinit followed by I2C_DRV_MasterInit whenever I2C_DRV_MasterReceiveDataBlocking returns "kStatus_I2C_AribtrationLost." Not a very elegant solution.
解決済! 解決策の投稿を見る。
Hello Mike Litster:
I tried to reproduce your issue with a FRDM-K22F by continuously calling I2C_DRV_MasterReceiveDataBlocking() to receive data from the on-board accelerometer, but I did not get any arbitration lost.
However I then forced an arbitration lost in the bus by pulling the SDA line low externally and your description of the resulting situation is correct. I think this is an oversight in the I2C driver in KSDK v1.x.
Please try the next workaround to solve your issue:
1) From the I2C_DRV_MasterIRQHandler() set the i2cIdle flag as true when arbitration lost is detected, like next:
if (wasArbLost)
{
I2C_HAL_ClearArbitrationLost(base);
master->status = kStatus_I2C_AribtrationLost;
/* Disable I2C interrupt in the peripheral.*/
I2C_HAL_SetIntCmd(base, false);
if (master->isBlocking)
{
OSA_SemaPost(&master->irqSync);
}
master->i2cIdle = true;
return;
}
Then rebuild the KSDK platform library.
2) To reset the BUSY bit disable and enable the I2C module from your code like next:
i2c_result = I2C_DRV_MasterReceiveDataBlocking(BOARD_ACCEL_I2C_INSTANCE, &accDev.slave, &accel_cmd, 1, &accelData, 12, 200);
if(i2c_result == kStatus_I2C_AribtrationLost)
{
I2C_HAL_Disable(I2C0);
I2C_HAL_Enable(I2C0);
}
This should reset the BUSY bit to 0.
I hope this helps in your case.
Best Regards!
Jorge Gonzalez
-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------
First off I would like to say this thread was very helpful. The workarounds provided were exactly what we needed.
Mike did you ever figure out what was causing the lost arbitration in the first place?
Hello Mike Litster:
I tried to reproduce your issue with a FRDM-K22F by continuously calling I2C_DRV_MasterReceiveDataBlocking() to receive data from the on-board accelerometer, but I did not get any arbitration lost.
However I then forced an arbitration lost in the bus by pulling the SDA line low externally and your description of the resulting situation is correct. I think this is an oversight in the I2C driver in KSDK v1.x.
Please try the next workaround to solve your issue:
1) From the I2C_DRV_MasterIRQHandler() set the i2cIdle flag as true when arbitration lost is detected, like next:
if (wasArbLost)
{
I2C_HAL_ClearArbitrationLost(base);
master->status = kStatus_I2C_AribtrationLost;
/* Disable I2C interrupt in the peripheral.*/
I2C_HAL_SetIntCmd(base, false);
if (master->isBlocking)
{
OSA_SemaPost(&master->irqSync);
}
master->i2cIdle = true;
return;
}
Then rebuild the KSDK platform library.
2) To reset the BUSY bit disable and enable the I2C module from your code like next:
i2c_result = I2C_DRV_MasterReceiveDataBlocking(BOARD_ACCEL_I2C_INSTANCE, &accDev.slave, &accel_cmd, 1, &accelData, 12, 200);
if(i2c_result == kStatus_I2C_AribtrationLost)
{
I2C_HAL_Disable(I2C0);
I2C_HAL_Enable(I2C0);
}
This should reset the BUSY bit to 0.
I hope this helps in your case.
Best Regards!
Jorge Gonzalez
-----------------------------------------------------------------------------------------------------------------------
Note: If this post answers your question, please click the Correct Answer button. Thank you!
-----------------------------------------------------------------------------------------------------------------------
Hi Jorge,
This solution fixed some of my arbitration issues, but I am still seeing cases where I get an arbitration error. Is there a way to disable arbitration? The MCU I am using is: MK20DX256.
On my system I have an accelerometer and an RTC on the I2C0, both are slave devices.
When I get the arbitration error, I see that SDA line is held low.
I have tried to completely disable the i2c0 bus and it doesn't seem to help.
Any debugging ideas? Attached are good and bad cases of the i2c0 register and a plot of the i2c lines.
Thanks,
Abid
Thanks Jorge.
Any reason why you don't just make this change instead, i.e put the arbitration failure check inside the I2C_DRV_MasterReceive function. This way, if you have multiply i2c ports, you would not need to copy this code in all sections.
+++ b/MQX_OS/BSP/platform/drivers/src/i2c/fsl_i2c_master_driver.c
@@ -378,6 +378,8 @@ void I2C_DRV_MasterIRQHandler(uint32_t instance)
if (wasArbLost)
{
I2C_HAL_ClearArbitrationLost(base);
master->status = kStatus_I2C_AribtrationLost;
/* Disable I2C interrupt in the peripheral.*/
I2C_HAL_SetIntCmd(base, false);
{
OSA_SemaPost(&master->irqSync);
}
+ /* Indicate I2C bus is idle. */
+ master->i2cIdle = true;
return;
}
@@ -836,6 +838,12 @@ static i2c_status_t I2C_DRV_MasterReceive(uint32_t instance,
/* Indicate I2C bus is idle. */
master->i2cIdle = true;
}
+ /* workaround provided by NXP: https://community.nxp.com/thread/386211 */
+ else if (master->status == kStatus_I2C_AribtrationLost)
+ {
+ I2C_HAL_Disable(base);
+ I2C_HAL_Enable(base);
+ }
return master->status;
}
Jorge,
Thanks for your quick response, that did the trick!