LPC5526: I2C stuck after SCL pulled low briefly

取消
显示结果 
显示  仅  | 搜索替代 
您的意思是: 

LPC5526: I2C stuck after SCL pulled low briefly

1,035 次查看
danielholala
Senior Contributor II

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:

  • After the I2C transfer command, I see one I2C interrupt.
  • This ISR is calling I2C_MasterTransferHandleIRQ() (located in fsl_i2c.c).
  • That calls the I2C_RunTransferStateMachine() for handling state = kStartState.
  • That's is, no more I2C interrupts. The I2C state machine hangs in the middle of the transfer going nowhere.

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

 

 

标签 (1)
0 项奖励
回复
9 回复数

935 次查看
danielholala
Senior Contributor II

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;
}
}

 

0 项奖励
回复

813 次查看
Harry_Zhang
NXP Employee
NXP Employee

Hi @danielholala 

We found that if you enable timeout.
 

    I2C_MasterGetDefaultConfig(&masterConfig);

    masterConfig.enableTimeout = true;

    /* 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);



After a brief drop in SCL, it will appear timeout.
You can check EVENTIMEOUT and SCLTIMEOUT status.
If timeout occurs, please reinitialize the master.image (4).png

BR

Hang

0 项奖励
回复

794 次查看
danielholala
Senior Contributor II

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.

0 项奖励
回复

716 次查看
Harry_Zhang
NXP Employee
NXP Employee

Hi @danielholala 

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

0 项奖励
回复

658 次查看
danielholala
Senior Contributor II

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?

 

0 项奖励
回复

951 次查看
Harry_Zhang
NXP Employee
NXP Employee

Hi @danielholala 

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

0 项奖励
回复

947 次查看
danielholala
Senior Contributor II

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

 

0 项奖励
回复

973 次查看
danielholala
Senior Contributor II

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:

danielholala_0-1726587768667.png

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

 

 

0 项奖励
回复

1,028 次查看
danielholala
Senior Contributor II

[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.

0 项奖励
回复