This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TM4C1294NCZAD: I2C Error Handling

Part Number: TM4C1294NCZAD

Hello TI,

I have to make some small updates to a legacy product.  Unfortunately, I have no access to the JTAG port on this, and can only update firmware using a Bootloader.  No debugging.

This device talks to a whole mess of different I2C based sensors, and mostly works well.  The only issue is that if a slave I2C device doesn't respond (missing, broken, etc), the TM4C locks somewhere in the I2CSend routine.

Can someone familiar with the peripheral libraries suggest what I can add to this code to handle a missing/non-responsive I2C Slave device?

Thanks very much!

P.S. I actually posted a similar question about this exact issue years ago and never really came up with a complete fix.  Back then, We had development version of this device (with JTAG access), but they've all been discarded over the years, so this is all the more difficult.

uint32_t I2C3Send(uint8_t slave_addr, uint8_t num_of_args, ...)
{
    uint8_t i;

    // Tell the master module what address it will place on the bus when
    // communicating with the slave.
    I2CMasterSlaveAddrSet(I2C3_BASE, slave_addr, false);

    //stores list of variable number of arguments
    va_list vargs;

    //specifies the va_list to "open" and the last fixed argument
    //so vargs knows where to start looking
    va_start(vargs, num_of_args);

    //put data to be sent into FIFO
    I2CMasterDataPut(I2C3_BASE, va_arg(vargs, uint32_t));

    //if there is only one argument, we only need to use the
    //single send I2C function
    if(num_of_args == 1)
    {
        //Initiate send of data from the MCU
        I2CMasterControl(I2C3_BASE, I2C_MASTER_CMD_SINGLE_SEND);

        // Wait until MCU is done transferring.
        while(!I2CMasterBusy(I2C3_BASE));
        while(I2CMasterBusy(I2C3_BASE));

        if(I2CMasterErr(I2C3_BASE)) return 0;

        //"close" variable argument list
        va_end(vargs);

    }

    //otherwise, we start transmission of multiple bytes on the
    //I2C bus
    else
    {
        //Initiate send of data from the MCU
        I2CMasterControl(I2C3_BASE, I2C_MASTER_CMD_BURST_SEND_START);

        // Wait until MCU is done transferring.
        while(!I2CMasterBusy(I2C3_BASE));
        while(I2CMasterBusy(I2C3_BASE));

        if(I2CMasterErr(I2C3_BASE)) return 0;

        //send num_of_args-2 pieces of data, using the
        //BURST_SEND_CONT command of the I2C module
        for(i = 1; i < (num_of_args - 1); i++)
        {
            //put next piece of data into I2C FIFO
            I2CMasterDataPut(I2C3_BASE, va_arg(vargs, uint32_t));
            //send next data that was just placed into FIFO
            I2CMasterControl(I2C3_BASE, I2C_MASTER_CMD_BURST_SEND_CONT);

            // Wait until MCU is done transferring.
            while(!I2CMasterBusy(I2C3_BASE));
            while(I2CMasterBusy(I2C3_BASE));

            if(I2CMasterErr(I2C3_BASE)) return 0;

        }

        //put last piece of data into I2C FIFO
        I2CMasterDataPut(I2C3_BASE, va_arg(vargs, uint32_t));
        //send next data that was just placed into FIFO
        I2CMasterControl(I2C3_BASE, I2C_MASTER_CMD_BURST_SEND_FINISH);
        // Wait until MCU is done transferring.
        while(!I2CMasterBusy(I2C3_BASE));
        while(I2CMasterBusy(I2C3_BASE));

        if(I2CMasterErr(I2C3_BASE)) return 0;

        //"close" variable args list
        va_end(vargs);

    }
    return 1;
}

SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C3);
SysCtlPeripheralReset(SYSCTL_PERIPH_I2C3);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOK);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOR);
GPIOPinConfigure(GPIO_PK4_I2C3SCL);
GPIOPinConfigure(GPIO_PR5_I2C3SDA);
GPIOPinTypeI2CSCL(GPIO_PORTK_BASE, GPIO_PIN_4);
GPIOPinTypeI2C(GPIO_PORTR_BASE, GPIO_PIN_5);
I2CMasterInitExpClk(I2C3_BASE, SysCtlClockGet(), true); //False = 100kbs, True = 400kbs
HWREG(I2C3_BASE + I2C_O_FIFOCTL) = 80008000;

  • Hi Benjamin,

    Aside from your reported issue, I'm surprised that the I2C works at all. In your second snippet of code. You use SysCtlClockGet(). This API can only be used for TM4C123 MCU, not TM4C129. This contradicts with the part number you selected for this thread which says TM4C1294NCZAD I2C Error Handling. I hope you are indeed using TM4C123. It is just a mistake when you create this thread with a wrong part number. 

    I2CMasterInitExpClk(I2C3_BASE, SysCtlClockGet(), true); //False = 100kbs, True = 400kbs

    This device talks to a whole mess of different I2C based sensors, and mostly works well.  The only issue is that if a slave I2C device doesn't respond (missing, broken, etc), the TM4C locks somewhere in the I2CSend routine.

    Now let's go back to your issue. 

    First, I can't really see an issue with your I2C3Send() other than SysCtlClockGet. SysCtlClockGet would be an non-issue if you are using TM4C123. But if you indeed use TM4C1294NCZAD then this would be the first thing to fix. 

      Have you checked what type of errors you are getting when the slave is not responding? 

      What is the state of the SDA line when you have any errors? If you get noise that generates extra transitions on SCL or SDA, the slave and master will be out of synchronization. For example, the slave is waiting for one more SCL clock but the master thinks that it has done transmitting  The solution is to use SCL as a digital I/O and bit-bang it low/high until the slave releases SDA. Vice versa of the situation can also happen. 

      I will suggest you first check the state of SCL and SDA lines. If the slave is holding SDA low, the only way to get the slave out of this state is with edges on SCL. Since the I2C module sees this as a different master holding the bus, it will not attempt to drive SCL. Bit banging is the only solution that can get you out of the lockup. Resetting the module, or time-out of the master does not change the state of the slave as the slave has no way of knowing the master has been reset. 

  • Charles,

    Sorry about that - We are using the TM4C1294, but the SysCtlClockGet() function has been replaced with a function that correctly returns the frequency.  I2C is oscilloscope confirmed to be operating at the intended frequency.

    In terms of SCL/SDA being held low, the particular test case that I'm working on is where an I2C device is missing or damaged and doesn't respond at all.  Basically, assume that there simply is no slave device to hold SCL/SDA low.

    I think my question really comes down to the operation of I2CMasterBusy().  In the case of a slave that doesn't respond at all, will that function ever return zero, or will it continue to assert MasterBusy indefinitely?  I've tried looking for a detailed description of these functions, but everything I've come across has been pretty high level.

    while(I2CMasterBusy(..)); is the only place I can see where the code could get stuck in a loop.  Is there something else I should be checking as well?
    while( (I2CMasterBusy) && !(I2CMasterErr) ) for instance?

    Also, in situations where I2CMasterErr does return an error, what is the correct method to clear the error before performing another I2C Transaction?

    Thank you,
    Ben

  • I think my question really comes down to the operation of I2CMasterBusy(). 

    Can you put a breakpoint at line 30, the line after I2CMasterBusy() is checked? Does it continue to line 30? If it did, then it did not hang due to I2CMasterBusy(). I2CMasterBusy() returns true when the I2C kernel is transmitting. Although the slave may not respond, but from the master point of view, it should clear the busy bit after it sends the command byte despite the slave does not respond with an ACK. In this case, you should get an ACK error. Please check if NACKRIS is set.

    Also, in situations where I2CMasterErr does return an error, what is the correct method to clear the error before performing another I2C Transaction?

    Clear the corresponding error bits by writing 1 to the I2CMICR register. You can use I2CMasterIntClear() or I2CMasterIntClearEX() to clear the error flags and perhaps disable the corresponding I2C module so it will not access the slave again since the slave does not exist.