Intermittently, my fsl_i2c driver code is getting stuck in a forever while loop while trying to read bytes from an external I2C EEPROM chip.
I'm using the fsl_i2c driver for the MK64F and am trying to read 4 bytes via I2C read using I2C_MasterTransferBlocking(). At the end of reading the 4 bytes in I2C_MasterReadBlocking(), when (rxSize == 0U), the driver code executes I2C_MasterStop().
This is supposed to issue a STOP command with the following:
/* Issue the STOP command on the bus. */
base->C1 &= ~(uint8_t)(I2C_C1_MST_MASK | I2C_C1_TX_MASK | I2C_C1_TXAK_MASK);
However, the code then gets stuck in the while loop:
while(0U != (base->S & (uint8_t)kI2C_BusBusyFlag))
{
}
By my understanding the BUSY bit of base->S should be cleared when a STOP signal is detected. Why did the base->C1 write fail to issue the STOP command?
It also appears that the NACK that is supposed to be issued after the 4th byte is read is not actually being issued:
if (rxSize == 1U)
{
base->C1 |= I2C_C1_TXAK_MASK;
}
As mentioned at the top of the post, the I2C_MasterReadBlocking works most of the time (I can read out all of the 4096 bytes from this EEPROM many times before occasionally running into this issue).
Is it possible that there is a bug in the I2C_MasterReadBlocking() code from the fsl_i2c.c driver (copied below with #ifdef's removed)
while (0U != (rxSize--))
{
if (rxSize == 0U)
{
if (0U == (flags & (uint32_t)kI2C_TransferNoStopFlag))
{
/* Issue STOP command before reading last byte. */
result = I2C_MasterStop(base);
}
else
{
/* Change direction to Tx to avoid extra clocks. */
base->C1 |= I2C_C1_TX_MASK;
}
}
/* Clear the IICIF flag. */
base->S = (uint8_t)kI2C_IntPendingFlag;
/* Read from the data register. */
*rxBuff++ = base->D;
if (rxSize == 1U)
{
/* Issue NACK on read. */
base->C1 |= I2C_C1_TXAK_MASK;
}
}
You can see that the data register is read before the NACK is configured for the last byte (if(rxSize == 1U)). From the K64 Reference Manual, reading the data register is responsible for initiating clocking-out of the next byte:
In master receive mode, reading this register initiates receiving of the next byte of data.
Therefore, if an interrupt occurs before C1 is configured for NACK but after the data register is read, it's possible that the last byte is clocked out with C1 still configured for ACK.
By comparison, look at lines 159 and 160 of https://github.com/jwr/kinetis_i2c/blob/main/i2c.c, which inverts the order of the C1 configurating and the data register read.
Hello, my name is Pavel, and I will be supporting your case, could you tell me what examples from the SDK are you based on?
Best regards,
Pavel
Hi @Pavel_Hernandez, thanks for following up. I am using the fsl_i2c.c and fsl_i2c.h drivers included with SDK_2_7_0_TWR_K64F120M (Version 2.7.0 (303 2019-12-19)).
You could look at the i2c_polling_b2b_transfer_master example.
Hello, I´m using the SDK version 2.11 and the example works as expected.
Best regards,
Pavel
I also have no trouble running the example as is. The issue I believe I am having is that, when using the I2C blocking code from the fsl driver in a system where there are interrupts, a poorly timed interrupt can occur after the
/*Read from the data register. */
*rxBuff++ = base->D
but before the
/* Issue NACK on read. */
base->C1 |= I2C_C1_TXAK_MASK;
As a result, the C1 register is still configured for ACK when the I2C hardware begins clocking out the next byte (triggered by the data register read). That means that after what should be the final byte is clocked out, an ACK is issued, the I2C_MasterStop() is never actually able to issue the STOP command, and the bus gets stuck in the busy state indefinitely.
I found that in my system, inverting the order of these lines of code makes the I2C communication more reliable.
Hello, thanks for your information, unfortunately, the example blocking does not have the robustness to detect that kind of event, I suggest changing the I2C to the non-blocking mode to get a better performance.
Best regards,
Pavel