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.

I2C NACK detection using Interrupts/Polling

Part Number: TMS320F28377S
Other Parts Discussed in Thread: TCA9555, C2000WARE

I have developed an I2C communication driver for a multi-slave system for the TMS320F2837xS family of controllers that I am unit-testing on the LaunchPadXL for 28377S.

I encountered a hardware issue with one of the evaluation modules (IO_EXPANDER_EVM, repopulated with TCA9555) used as a slave device, where the TCA9555 does not ACK even when addressed correctly. This is a hardware setup issue because I have a second EVM repopulated with the TCA9555 which has the same address pin configuration, which is working correctly. 

I have been able to detect these NACKs because I've been using a protocol analyzer to monitor the I2C bus transactions. (attached snip with "*" in the Addr column denoting NACKs).

I have included a flag in my driver to indicate if a NACK is received based on the I2C Status Register (I2CSTR). However, the NACK bit never seems to be set. I believe I am missing something while polling the NACK bit. The 2837xS controller is always the master and being used in non-repeat mode.

My interrupt sources for read and write transaction are ACCESS_READY and STOP_CONDITION detected. I check the NACK bit in the interrupt service routine

Is the NACK bit in the status register cleared every time a stop condition is issued?

Do I have to explicitly enable NACK interrupts to be able to detect the NACK? If I do this, will an expected NACK (generated by the master controller) at the end of a normal read also generate an interrupt that I will have to account for in the ISR?

  • Hi Ruta,

    In your multi-slave hardware setup are you saying that you have multiple TCA9555s on the same bus, with one that is working and one that is not? Or is the working case with completely different hardware?

    Could you share your ISR code in which the NACK bit is being polled? It's possible that the I2CSTR.NACK bit is being cleared when you read I2CISRC, within your ISR, for the interrupt code per the documentation below. I'm not certain that this is the behavior you're experiencing however.

    I2CISRC Register Description:

    You shouldn't have to enable the NACK interrupt to poll the I2CSTR.NACK bit and see it get set. The bit description in the TRM states "The CPU can poll NACK or use the NACK interrupt request."

    The NACK interrupt is only applicable when the C2000 device is a transmitter, so a NACK generated by the master will not be be detected.

    Best,

    Kevin

  • Hi Kevin, thanks for your reply. I see I caused a little ambiguity so let me clear it up (my apologies).

    1. My driver is to be scaled to multiple slave devices, (not all IO expanders, but an RTC, DAC_EVM and IO expander EVM). The multi-slave support is work in progress.

    2. I am unit testing the two IO Expander EVMs (with TCA 9555) independently (they're not on the same I2C bus at the same time, because they both have the same address pin configurations). I am running the same unit test code which basically toggles the LEDs on Port 0.

    3. One of the IO expanders works fine, I can see the LED toggling action. It is addressed at 0x27H as all address pins are high.

    The second IO expander has the same address pin configuration. When I swap it onto the bus (removing the first one altogether) and run the same unit test, I get a series of NACKs (as shown in the image).

    I need to be able to detect these NACKs in the software, and had therefore included a check on the NACK bit when the ISR triggers.

    However, when the STOP_CONDITION_DETECTED interrupt fires, it seems the NACK bit is never set.

    Based on re-reading the technical reference and thanks to your first image above, I am beginning to suspect that the NACK bit is cleared every time the I2CISRC register is read.

    I'll share my ISR handling logic on this thread. I will modify the unit test to perform a read on the fault IO Expander EVM and post the result shortly.

    **Please Note: The function is named InterruptHandler but takes a parameter by design, it is called by the void parameter function which is actually mapped to the Hwi n BIOS cfg.

    void i2cInterruptHandler(uint32_dtc baseAddress)
    {
        I2C_InterruptSource i2cInterruptSrc;
        uint16_dtc i;
    
        i2cInterruptSrc = I2C_getInterruptSource(baseAddress);
    
        if (I2C_INTSRC_STOP_CONDITION == i2cInterruptSrc)
        {
            //If stop was detected at the end of a write after NACK, reset the message status
            //If stop condition was detected at the end of a write as normal, reset message status
            if (MSGSTAT_WRITE_BUSY == currMsgPtr->messageStatus)
            {
                if(I2C_getStatus(baseAddress) & I2C_STS_NO_ACK)
                {
                    currMsgPtr->messageStatus = MSGSTAT_NACK_RECD;
                }
                else
                {
                    currMsgPtr->messageStatus = MSGSTAT_INACTIVE;
                }
            }
            //Else, if stop condition was detected at the end of a successful read, copy the data into the message buffer
            else if (MSGSTAT_READ_BUSY == currMsgPtr->messageStatus)
            {
                for (i = 0; i < (currMsgPtr->dataByteCount); i++)
                {
                    currMsgPtr->dataByteBuffer[i] = I2C_getData(baseAddress);
                }
    
                currMsgPtr->messageStatus = MSGSTAT_INACTIVE;
            }
        }
    
        else if (I2C_INTSRC_REG_ACCESS_RDY == i2cInterruptSrc)
        {
    
            // This interrupt will not be triggered if master issues a write with a stop condition
            //If the slave receiver NACKs the address setup request, update the message status and issue STOP
            //NACK is expected at the end of a normal read.
    
    
            //check for NACK bit in the status register
            if (I2C_getStatus(baseAddress) & I2C_STS_NO_ACK)
            {
               //Send stop condition and clear the NACK bit
                I2C_sendStopCondition(baseAddress);
                I2C_clearStatus(baseAddress, I2C_STS_NO_ACK);
    
                if(MSGSTAT_SEND_NOSTOP_BUSY == currMsgPtr->messageStatus)
                {
                    currMsgPtr->messageStatus = MSGSTAT_NACK_RECD;
                }
            }
    
            //Master needs to put itself into receiver mode at the end of
            //address setup.
            else if (MSGSTAT_SEND_NOSTOP_BUSY == currMsgPtr->messageStatus)
            {
                //Now send a new start condition as master receiver
                currMsgPtr->messageStatus = MSGSTAT_RESTART;
    
                //Set Slave address
                I2C_setSlaveAddress(baseAddress, currMsgPtr->slaveChipAddress);
                //Set the number of bytes to read
                I2C_setDataCount(baseAddress, currMsgPtr->dataByteCount);
                //I2C_MDR_MST = 1, I2C_MDR_TX = 0
                I2C_setConfig(baseAddress, I2C_MASTER_RECEIVE_MODE);
                //I2C_MDR_STT = 1, I2C_MDR_STP = 1
                I2C_sendStartCondition(baseAddress);
                //Stop condition will be sent on the bus when the data byte counter counts down
                I2C_sendStopCondition(baseAddress);
    
                currMsgPtr->messageStatus = MSGSTAT_READ_BUSY;
            }
        }
    
        else    //Corrective action in case of spurious interrupt on the PIE channel corresponding to I2C
        {
            I2C_clearInterruptStatus(baseAddress, i2cInterruptSrc);
        }
        //Send PIE ack for group 8 interrupts
        Interrupt_clearACKGroup(INTERRUPT_ACK_GROUP8);
    }

  • Update to the above post: I modified the unit test to simply read the port status on the faulty (NACK-ing) EVM. The logic within the ACCESS_READY section in the ISR executed and changed the message status to reflect the NACK, and then the stop condition was generated. The NACK bit remains 0 or gets cleared after reading I2CISRC register after the interrupt fires for a STOP_CONDITION_DETECTED.
  • Hi Ruta,

    OK. To clarify on your latest test, when the I2C_INTSRC_REG_ACCESS_RDY interrupt triggers the NACK status flag is correctly set. The status bit works as expected then, correct?

    For the I2C_INTSRC_STOP_CONDITION  interrupt case, what if you perform your test commenting out the below line so that the interrupt source isn't read before reading the status register? Is the NACK bit set then?

    i2cInterruptSrc = I2C_getInterruptSource(baseAddress);

    This would be to test if reading the interrupt source clears the NACK flag when an I2C_INTSRC_ARB_LOST, I2C_INTSRC_NO_ACK, or I2C_INTSRC_STOP_CONDITION interrupt occurs.

    Best,

    Kevin

  • Hi Kevin,

    Thanks for the reply.

    1. I ran the test with the NACKing IO expander to simply read the port status. When the I2C_INTSRC_REG_ACCESS_RDY interrupt triggered, my read message flag (see line 52 above) changed to MSGSTAT_NACK_RECD. Therefore when the interrupt triggered, the NACK bit must have been set.
    The action taken upon NACK received during address setup is to clear the NACK bit and send a stop condition.
    I observed the register status after this and the NACK bit at that time was 0.

    2. I commented out the line you mentioned and directly checked the value returned by I2C_getInterruptSource(baseAddress). The behavior is the same.

    So does this mean that simply calling I2C_getInterruptSource() after a STOP condition and not an ACCESS_READY condition, will clear the NACK bit in the I2C status register? Is there a workaround in this situation?
  • Hi Ruta,

    OK, (1) and (2) are what I'd expect to happen based on the documentation.

    Ruta Bhave said:
    So does this mean that simply calling I2C_getInterruptSource() after a STOP condition and not an ACCESS_READY condition, will clear the NACK bit in the I2C status register?

    That seems to be the case since the I2C_getInterruptSource() function reads the I2CISRC.INTCODE register bits, therefore clearing the corresponding status bits.

    Ruta Bhave said:
    Is there a workaround in this situation?

    A possible workaround for your case could be using the I2C_getStatus(baseAddress) function to detect STOP conditions instead of I2C_getInterruptSource().

    Best,

    Kevin

  • Hi Kevin,

    I made the following small modification just to check if the I2C_getStatus() called before I2C_getInterruptSource() will allow me to read the status register with the NACK bit set when I receive the NACKs from the TCA9555.

    void i2cInterruptHandler(uint32_dtc baseAddress)
    {
        I2C_InterruptSource i2cInterruptSrc;
        bool_dtc nackWasDetected;
        uint16_dtc i;
    
        nackWasDetected = I2C_getStatus(baseAddress) & I2C_STS_NO_ACK;
    
        i2cInterruptSrc = I2C_getInterruptSource(baseAddress);
    
        if (I2C_INTSRC_STOP_CONDITION == i2cInterruptSrc)
        {
            //If stop was detected at the end of a write after NACK, reset the message status
            //If stop condition was detected at the end of a write as normal, reset message status
            if (MSGSTAT_WRITE_BUSY == currMsgPtr->messageStatus)
            {
                if(nackWasDetected)
                {
                    currMsgPtr->messageStatus = MSGSTAT_NACK_RECD;
                }
                else
                {
                    currMsgPtr->messageStatus = MSGSTAT_INACTIVE;
                }
            }
    

    However this still changes the messageStatus flag to MSGSTAT_INACTIVE and not MSGSTAT_NACK_RECD as I would expect. I am beginning to suspect if the NACK bit is getting set at all, as should be the case given that something is wrong with the address pins on the EVM and the TCA9555 isn't recognizing the address 0x27. I am quite lost about why this should happen.

    The contents of the I2C Mode Register (I2CMDR) after the STOP condition are 0x4220 : Free run, Transmitter Mode, Module enabled. The Master (MST) bit is automatically cleared when the I2C Master generates a stop condition.

    Additionally, using the I2C_getStatus() function to detect a stop condition instead of reading the I2CISRC register within the ISR leads to incorrect behavior. 

    if(I2C_getStatus(baseAddress) & I2C_STS_STOP_CONDITION)
        {
            nackWasDetected = I2C_getStatus(baseAddress) & I2C_STS_NO_ACK;
            //If stop was detected at the end of a write after NACK, reset the message status
            //If stop condition was detected at the end of a write as normal, reset message status
            if (MSGSTAT_WRITE_BUSY == currMsgPtr->messageStatus)
            {
                if(nackWasDetected)
                {
                    currMsgPtr->messageStatus = MSGSTAT_NACK_RECD;
                }
                else
                {
                    currMsgPtr->messageStatus = MSGSTAT_INACTIVE;
                }
            }

    This is how I temporarily modified the original ISR and in this case, the messageStatus flag doesn't change to either NACK_RECD or INACTIVE. So for some reason, the if() condition is not evaluating to true.

    I will revert to my original implementation for now and try to figure out why the NACK bit can't be read using I2C_getStatus() before reading I2CISRC.

    If you could point me in the right direction to detect the NACK after a STOP condition, I'd appreciate it.

    Thank you so much for your help thus far.

  • Hi Ruta,

    In your latest code snippet what value does I2C_getStatus() return in line 1? Does it include the NACK bit? Can check this by storing the returned value like below and setting a breakpoint to check the value:

    uint16_t I2C_int_flag_status; // Variable to store status value
    
    I2C_int_flag_status = I2C_getStatus(baseAddress);
    
    ESTOP0; // Breakpoint here then check I2C_int_flag_status

    This behavior seems odd to me, I would have expected the NACK bit to be set when running with your latest code snippet. I'll try to test this behavior on my side when I get some time.

    You've already reviewed the i2c_ex2_eeprom.c example in C2000ware, correct? At the directory below

    C:\ti\c2000\C2000Ware_1_00_06_00\driverlib\f2837xs\examples\cpu1\i2c\i2c_ex2_eeprom.c

    It looks like this example detects a NACK after a STOP condition based on the msgStatus state machine. See the comments in the below:

        //
        // Interrupt source = stop condition detected
        //
        if(intSource == I2C_INTSRC_STOP_CONDITION)
        {
            //
            // If completed message was writing data, reset msg to inactive state
            //
            if(currentMsgPtr->msgStatus == MSG_STATUS_WRITE_BUSY)
            {
                currentMsgPtr->msgStatus = MSG_STATUS_INACTIVE;
            }
            else
            {
                //
                // If a message receives a NACK during the address setup portion of
                // the EEPROM read, the code further below included in the register
                // access ready interrupt source code will generate a stop
                // condition. After the stop condition is received (here), set the
                // message status to try again. User may want to limit the number
                // of retries before generating an error.
                //
                if(currentMsgPtr->msgStatus == MSG_STATUS_SEND_NOSTOP_BUSY)
                {
                    currentMsgPtr->msgStatus = MSG_STATUS_SEND_NOSTOP;
                }

    Best,

    Kevin

  • Hi Kevin, thanks for your reply

    I added the breakpoint and ran another test on both the EVMS- the functioning one and the NACKing one.

    Please note that for both the boards I am running a simple unit test which configures one of the ports on the device as an output port and then writes alternating bit patterns to the port which toggle the LEDs (on the good board this behavior is verified). This unit test only issues I2C write commands which include the STP bit at the end of a normal write

    1. On the problematic board, I observed the I2C registers at the breakpoint in the interrupt handler. As shown in the I2CSTR register window, ARDY is set and SCD is clear. So it seems when the addressed device is NACKing the transmission, an ACCESS_READY interrupt is generated. My handler routine detects the NACK bit in the ACCESS_READY section, clears it and issues a STOP condition which gets subsequently detected. This explains why the nackWasDetected flag resets to 0. However it does not explain why despite issuing a write command which sends a STOP, the interrupt is ACCESS_READY and not STOP.  

    2. For the functioning board, I saw all register statuses change as expected.

    Kevin Allen18 said:

    You've already reviewed the i2c_ex2_eeprom.c example in C2000ware, correct? At the directory below

    C:\ti\c2000\C2000Ware_1_00_06_00\driverlib\f2837xs\examples\cpu1\i2c\i2c_ex2_eeprom.c

    It looks like this example detects a NACK after a STOP condition based on the msgStatus state machine. See the comments in the below:

        //
        // Interrupt source = stop condition detected
        //
        if(intSource == I2C_INTSRC_STOP_CONDITION)
        {
            //
            // If completed message was writing data, reset msg to inactive state
            //
            if(currentMsgPtr->msgStatus == MSG_STATUS_WRITE_BUSY)
            {
                currentMsgPtr->msgStatus = MSG_STATUS_INACTIVE;
            }
            else
            {
                //
                // If a message receives a NACK during the address setup portion of
                // the EEPROM read, the code further below included in the register
                // access ready interrupt source code will generate a stop
                // condition. After the stop condition is received (here), set the
                // message status to try again. User may want to limit the number
                // of retries before generating an error.
                //
                if(currentMsgPtr->msgStatus == MSG_STATUS_SEND_NOSTOP_BUSY)
                {
                    currentMsgPtr->msgStatus = MSG_STATUS_SEND_NOSTOP;
                }

    Here- the NACK is being detected in the ACCESS_READY interrupt condition , not the STOP condition, and subsequently the NACK is cleared and the STOP condition sent. This comment reflects the actions taken after that STOP condition is detected in the subsequent interrupt. My interrupt handler is similar in terms of detection, except that it also handles putting the I2C master in receiver mode after an ACCESS_READY.

    Is there any explanation why in the case of a NACK received from a slave, the ARDY bit is set but the SCD is not? I am issuing write commands which are expected to send a STP condition.

    Thank you for your help, I really appreciate it.

  • Hi Kevin, regarding my previous query, I might have found an explanation about why despite issuing a write with a stop condition, the ACCESS_READY interrupt gets triggered. 

    http://processors.wiki.ti.com/index.php/I2C_Tips#Detecting_and_handling_NACK

    It seems that the STP bit is cleared in the hardware, therefore the ACCESS_READY interrupt gets triggered after a NACKing transaction. Following this and the rest of the steps in the link above, I was able to implement a workaround for the NACK issue which seems to be working.

    Does this seem like a valid explanation? I'd really appreciate if you could confirm.

    Thank you.

  • Hi Ruta,

    Ruta Bhave said:

    It seems that the STP bit is cleared in the hardware, therefore the ACCESS_READY interrupt gets triggered after a NACKing transaction. Following this and the rest of the steps in the link above, I was able to implement a workaround for the NACK issue which seems to be working.

    Does this seem like a valid explanation? I'd really appreciate if you could confirm.

    This seems probable and the measures discussed in the wiki appear valid to use (i.e. same I2C module in c6000 as C2000 device).

    I'd still like to run some tests on my side to verify this for myself, but this looks to be the case and match up with sample SW I've written in the past. I'll assess if documentation updates need to be made afterwards.

    thanks,

    Kevin

  • Hi Ruta,

    I've taken note to look into this further and run tests myself in the future, but do not have the time right now.

    I will be marking this post as closed unless you are needing further assistance.

    Best,
    Kevin