Hello,
on the LPC5526, I'm using I2C-bus Flexcomm1 interface on pin 46 (SDA) and pin 47 (SCL). Further, I'm controlling the interface through the CMSIS API instead of the genuine SDK API.
I figured that the I2C system (e.g., CPU state, SDK statemachine, interrupts, etc.) gets stuck when SCL get pulled low for even a short amount of time while the I2C bus is idle. For example I used a normally open push button to pull SCL to GND for a brief amount of time while the I2C was idle.
After pulling SCL low and releasing it, further I2C transfer commands (e.g., ARM_DRIVER_I2C * Driver->MasterReceive()) fail as the completion callback is never called.
I traced the above in more detail:
This issue looks similar to the one reported in 2021.
Any ideas why this is? Does pulling low SCL somehow trigger an arbitration mechanism that is not handled properly?
Thanks.
Dan
Hello,
I tested an LPCXpresso55S36 board (running LPC5536 MCU) and I can see the same behavior.
Below find the example code which is based on the i2c driver's interrupt transfer example (from SDK_2.x_LPC5536). I had to move I2C from Flexcomm2 to Flexcomm7 as the development board has the pullups connected to Flexcomm7 pins. Use the Pins Tool to register pins #37 (SDA) and #65 (SCL). Also I had to add some code to avoid infinite loops on error and instead print out the status.
Rant: I don't know why NXP provides these odd examples which do not match the development board they provide.
#include <stdio.h>
#include <string.h>
#include "pin_mux.h"
#include "board.h"
#include "fsl_debug_console.h"
#include "fsl_i2c.h"
/*******************************************************************************
* Definitions
******************************************************************************/
#define EXAMPLE_I2C_MASTER_BASE (I2C7_BASE)
#define I2C_MASTER_CLOCK_FREQUENCY (12000000)
#define EXAMPLE_I2C_MASTER ((I2C_Type *)EXAMPLE_I2C_MASTER_BASE)
#define I2C_MASTER_SLAVE_ADDR_7BIT (0x7EU)
#define I2C_BAUDRATE (100000) /* 100K */
#define I2C_DATA_LENGTH (33) /* MAX is 256 */
/*******************************************************************************
* Prototypes
******************************************************************************/
/*******************************************************************************
* Variables
******************************************************************************/
uint8_t g_master_txBuff[I2C_DATA_LENGTH];
uint8_t g_master_rxBuff[I2C_DATA_LENGTH];
i2c_master_handle_t g_m_handle;
volatile bool g_MasterCompletionFlag = false;
volatile status_t transfer_status = 99;
/*******************************************************************************
* Code
******************************************************************************/
static void i2c_master_callback(I2C_Type *base, i2c_master_handle_t *handle, status_t status, void *userData)
{
/* Signal transfer success when received success status. */
transfer_status = status;
g_MasterCompletionFlag = true;
}
/*!
* @brief Main function
*/
int main(void)
{
i2c_master_transfer_t masterXfer = {0};
status_t reVal = kStatus_Fail;
/* attach 12 MHz clock to FLEXCOMM0 (debug console) */
CLOCK_SetClkDiv(kCLOCK_DivFlexcom0Clk, 0u, false);
CLOCK_SetClkDiv(kCLOCK_DivFlexcom0Clk, 1u, true);
CLOCK_AttachClk(BOARD_DEBUG_UART_CLK_ATTACH);
/* attach 12 MHz clock to FLEXCOMM2 (I2C master) */
CLOCK_SetClkDiv(kCLOCK_DivFlexcom7Clk, 0u, false);
CLOCK_SetClkDiv(kCLOCK_DivFlexcom7Clk, 1u, true);
CLOCK_AttachClk(kFRO12M_to_FLEXCOMM7);
/* reset FLEXCOMM for I2C */
RESET_PeripheralReset(kFC7_RST_SHIFT_RSTn);
BOARD_InitPins();
BOARD_BootClockPLL150M();
BOARD_InitDebugConsole();
PRINTF("\r\nI2C board2board interrupt example -- Master transfer.\r\n");
i2c_master_config_t masterConfig;
I2C_MasterGetDefaultConfig(&masterConfig);
/* Change the default baudrate configuration */
masterConfig.baudRate_Bps = I2C_BAUDRATE;
/* Initialize the I2C master peripheral */
I2C_MasterInit(EXAMPLE_I2C_MASTER, &masterConfig, I2C_MASTER_CLOCK_FREQUENCY);
/* Create the I2C handle for the non-blocking transfer */
I2C_MasterTransferCreateHandle(EXAMPLE_I2C_MASTER, &g_m_handle, i2c_master_callback, NULL);
while(1) {
PRINTF("Press return to receive from slave:");
GETCHAR();
PRINTF("Read:");
/* subAddress = 0x01, data = g_master_rxBuff - read from slave.
start + slaveaddress(w) + subAddress + repeated start + slaveaddress(r) + rx data buffer + stop */
masterXfer.slaveAddress = I2C_MASTER_SLAVE_ADDR_7BIT;
masterXfer.direction = kI2C_Read;
masterXfer.subaddress = 0; /* changed to 0 */
masterXfer.subaddressSize = 0; /* changed to 0 */
masterXfer.data = g_master_rxBuff;
masterXfer.dataSize = 1; /* changed to 1 */
masterXfer.flags = kI2C_TransferDefaultFlag;
reVal = I2C_MasterTransferNonBlocking(EXAMPLE_I2C_MASTER, &g_m_handle, &masterXfer);
/* Reset master completion flag to false. */
g_MasterCompletionFlag = false;
if (reVal != kStatus_Success)
{
PRINTF("error\n");
while(1) { }
}
/* Wait for transfer completed. */
while (!g_MasterCompletionFlag)
{
}
PRINTF(" status:%i = %x\n", transfer_status, transfer_status);
g_MasterCompletionFlag = false;
}
}
I2C_MasterGetDefaultConfig(&masterConfig);
/* Change the default baudrate configuration */
masterConfig.baudRate_Bps = I2C_BAUDRATE;
/* Initialize the I2C master peripheral */
I2C_MasterInit(EXAMPLE_I2C_MASTER, &masterConfig, I2C_MASTER_CLOCK_FREQUENCY);
BR
Hang
Dear @Harry_Zhang ,
Thank you for your suggestion.
I tried it and I have to say that it failed to solve this issue.
With timeout enabled, though the driver does not "hang", it now returns kStatus_I2C_EventTimeout on each call to I2C_MasterTransferNonBlocking() while the MCU does not transmit anything on I2C (according to my saleae logic analyzer).
I know that can "reinitialize master" on timeout and I already implemented the means to notice a timeout using FreeRTOS signaling primitives.
However, the MCU's I2C stack should not hang in "busy" mode in the first place when the SCL is pulled low while the I2C bus if free. This is the reasoning behind this statement: It should be of no concern to the MCU what happens on the I2C bus if the MCU is configured as master/controller and does not transfer any data on the bus.
When there are multi-controllers on same I2C bus, for example controller LPC5500 and controller X.
When the controller X pulls SCL low, this means that control of the I2C bus is taken by the controller X. The two controllers operating I2C may also have different clock speeds. It is possible that Controller X is slow and Controller LPC5500 is fast. If Controller LPC5500 can operate the I2C bus at this point, it will interrupt the I2C transmission that the controller X may not have completed, destroy Controller X's ownership of the I2C bus and cause data corruption. So this, in turn, would prevent the multi-controller functionality.
EVENTTIMEOUT and SCLTIMEOUT can be used to detect a stuck bus and potentially do
something to alleviate the condition. Please refer to user manual "33.7.3 Time-out".
BR
Hang
Dear @Harry_Zhang ,
Thank you for explaining bus contention. However, that's not the issue here.
I'm discussing a situation without another controller and without conflicting bus access.
I'm observing that the LPC5526 has issues when SCL gets pulled low (only for a short amount of time and immediately released again) while LPC5526 is not operating the I2C bus. Then, the next transaction from with the LPC5526 fails.
So why should there be a timeout, why is the LPC5526 stuck in "busy" mode?
1. Arbitration Loss: If the I2C bus is actively shared by multiple devices, pulling SCL low might trigger an arbitration process. If the MCU loses arbitration, it could become stuck in a waiting state, unable to proceed with the transfer.
Consider using a logic analyzer to capture I2C bus activity and observe the behavior during the SCL pull-down.
2. CMSIS API Limitations: While the CMSIS API provides a generic interface, it might not handle all specific scenarios or edge cases of the LPC5526's I2C implementation.
Try the Genuine SDK API: If possible, try using the genuine SDK API to see if it behaves differently. This might help isolate the issue to the CMSIS API implementation.
BR
Hang
Dear Hang,
Thank you for your reply:
1. It is my understanding of the I2C standard that arbitration takes place when two masters try to start using the I2C bus at the same time. Here, there is no other participant (neither slave nor master) connected to the I2C bus. Only the MCU LPC5526 is connected to the pull-ups and a logic analyzer is connected to monitor SCL and SDA. Further, the MCU is idle in terms of I2C communication when I pull down SCL briefly. The arbitration loss bit is not set.
In other words, there's no need for arbitration. Please quote the applicable part of the standard if I'm wrong.
2. This issue is independent of the API that you use. A soon as communication is started, the code in fsl_i2c.c controls the I2C peripheral. Most work is done in I2C_MasterTransferHandleIRQ() and I2C_RunTransferStateMachine(), both part of fsl_i2c.c. The CMSIS based code does not access the peripheral as it is just a wrapper around the genuine SDK API. According to my findings this issue is not located in the CMSIS API implementation.
Any help is appreciated.
Thanks.
Dan
I traced the MCU executing a single I2C read command on an empty I2C bus after I had pulled SCL low briefly (when the bus was idle).
After I2C_RunTransferStateMachine() handles state = kStartState, the I2C peripheral is stuck with MSTPENDING == 0 in I2C status register STAT:
This is unexpected: The I2C peripheral should not stay in this state for a longer period of time.
Rather it should perform the I2C transmission on the bus, receive a NACK, then set MSTPENDING to 1 and convey the received NACK through MSTSTATE bits in I2C status register to the driver. Setting MSTPENDING to 1 triggers an interrupt which in turn calls the I2C_RunTransferStateMachine().
As this does not happen, the driver remains in the "busy" condition and I2C is unusable from then on.
With this bug, the LPC5526 cannot support multi master operation on an I2C bus or am I missing something?
Best regards,
Dan
[Editing my posting seems not possible]
I'd like to add that I observe this behavior with no I2C targets connected to the bus.
That being said, even short pulses on SCL (e.g., by connecting an I2C device to the bus) are sufficient to make the I2C system go awry.