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.

MSP432WARE: Some DriverLib functions for I2C do not support timeout - how to handle or reset?

Part Number: MSP432WARE
Other Parts Discussed in Thread: TMP007

Hi folks,

I'm using the TI DriverLib for the I2C interface.  Many of the calls support a 'timeout" option, which prevents hanging.  However, there are some basic functions like I2C_isBusBusy(), I2C_getInterruptStatus(), I2C_masterIsStartSent(), I2C_masterReceiveMultiByteNext() and others which do not support a timeout option.

In particular, if I fail to read from a device (perhaps because it isn't connected), I often see the EUSCI_Bx.STATW.UCBBUSY bit set to 1, which indicates that the bus is "busy".  That means that then next time I try to do a read or write to some other device on the I2C bus, the following will hang:

while (MAP_I2C_isBusyBusy(moduleInstance));

There is no timeout for this function, and I do not see any way to clear the busy bit to permit other I2C transactions to continue.

If an I2C call fails with a timeout, what is the recommended way to reset the state of the I2C controller to permit other operations to continue (perhaps with other I2C devices on the same EUSCI_Bx channel?

It seems like this is a flaw in DriverLib, where some operations support timeout and some do not.  There also does not appear to be a simple way to put the I2C controller back into the state it was in before an operation failed due to a timeout.

Can someone please provide an example of how to properly reset the I2C interface following a timeout?  I have tried writing to the UCBBUSY bit to clear it, but that does not seem to work.

I have both TMP007 temperature sensors and AT24D02 EEPROMs on the same I2C interface, and need communications to be robust.  Your help is sincerely appreciated!

Scott

  • Scott,
    I will bring this to the attention of the driverLib authors. The API uses naming like 'is' and 'get' to indicate a snapshot in time and not a polling or blocking type of function. In the while loop you describe you would need to add your own polling timeout.

    while (MAP_I2C_isBusyBusy(moduleInstance) && --timeout);

    I would like to understand what is causing the busy bit to stay '1'. Per the technical reference manual, the UCBBUSY is cleared after a stop condition. When are you sending the stop?

    Thanks,
    Chris
  • Hi Chris,

    Thanks very much for the reply and the help.  Good to know that the busy bit is supposed to be cleared after sending the stop condition.  I had also thought of the tip about creating my own timeout for the busy-wait loops, but have not implemented them yet.

    My methods are still crude, but the hangs occurred when I had deliberately disconnected an I2C device to see if I could detect it and recover gracefully.  I'm running within an RTOS environment, so polled rather than interrupt-driven operation is fine, and I based these <incomplete> examples primarily on examples provided with DriverLib.  I have a feeling that when an operation fails, I am not always sending a STOP condition, so the I2C interface continues to look busy, and I will investigate further.

    For now, here is an excerpt from some I2C class code to write and read a series of bytes:

    //------------------------------------------------------------------------------
    // @brief  Writes to I2c address and register with specified data and length.
    //
    // @param  addr    I2C address to write to
    // @param  reg     Which register to write to
    // @param  pData   Pointer to data bytes to be written
    // @param  bytes   Number of bytes to write
    //
    // @return true if successful, false otherwise
    //------------------------------------------------------------------------------
    bool CI2C::Write(uint8_t addr, uint8_t reg, uint8_t *pData, uint8_t bytes)
    {
        bool success;   // Did operation succeed or fail?
        
        // Todo: Implement a delay
        // Wait until ready to write
        while (MAP_I2C_isBusBusy(_moduleInstance));
    
        // Load device slave address
        MAP_I2C_setSlaveAddress(_moduleInstance, addr);
    
        // Send start bit and register
        success = MAP_I2C_masterSendMultiByteStartWithTimeout(_moduleInstance, reg, I2C_TIMEOUT);
        if (!success)
        {
            return false;
        }
    
        // Wait for tx to complete
        while(!(MAP_I2C_getInterruptStatus(_moduleInstance, EUSCI_B_I2C_TRANSMIT_INTERRUPT0) &
                EUSCI_B_I2C_TRANSMIT_INTERRUPT0));
        
        // Check if slave ACK/NACK
        if((MAP_I2C_getInterruptStatus(_moduleInstance, EUSCI_B_I2C_NAK_INTERRUPT)) &
                EUSCI_B_I2C_NAK_INTERRUPT)
        {
            // If NACK, set stop bit and exit
            (void)MAP_I2C_masterSendMultiByteStopWithTimeout(_moduleInstance, I2C_TIMEOUT);
            return(false);
        }
    
        // Now write one or more data bytes
        while(1)
        {
            // Wait for next INT
            while(!(MAP_I2C_getInterruptStatus(_moduleInstance, EUSCI_B_I2C_TRANSMIT_INTERRUPT0) &
                    EUSCI_B_I2C_TRANSMIT_INTERRUPT0));
    
            // If no data to follow, we are done
            if(bytes == 0 )
            {
                success = MAP_I2C_masterSendMultiByteStopWithTimeout(_moduleInstance, I2C_TIMEOUT);
                MAP_I2C_clearInterruptFlag(_moduleInstance, EUSCI_B_I2C_TRANSMIT_INTERRUPT0);
                return success;
            }
            // If more, send the next byte
            else
            {
                success = MAP_I2C_masterSendMultiByteNextWithTimeout(_moduleInstance, *pData++, I2C_TIMEOUT);
                if (!success)
                {
                    return false;
                }
            }
            bytes--;
        }
    }
    
    //-----------------------------------------------------------------------------------
    // @brief  Reads from I2c address and register, providing pointer to data and length.
    //
    // @param  addr    I2C address to read from
    // @param  reg     Which register to read from
    // @param  pData   Pointer to data bytes to be read
    // @param  bytes   Number of bytes to read
    //
    // @return true if successful, false otherwise
    //-----------------------------------------------------------------------------------
    bool CI2C::Read(uint8_t addr, uint8_t reg, uint8_t *pData, uint8_t bytes)
    {
        bool success;   // Did operation succeed or fail?
        
        // Todo: Implement a delay
        // Wait until ready
        while (MAP_I2C_isBusBusy(_moduleInstance));
    
        // Load device slave address
        MAP_I2C_setSlaveAddress(_moduleInstance, addr);
    
        // Send start bit and register
        success = MAP_I2C_masterSendMultiByteStartWithTimeout(_moduleInstance, reg, I2C_TIMEOUT);
        if (!success)
        {
            return false;
        }
    
        // Wait for tx to complete
        while(!(MAP_I2C_getInterruptStatus(_moduleInstance, EUSCI_B_I2C_TRANSMIT_INTERRUPT0) &
                EUSCI_B_I2C_TRANSMIT_INTERRUPT0));
    
        // Check if slave ACK/NACK
        if((MAP_I2C_getInterruptStatus(_moduleInstance, EUSCI_B_I2C_NAK_INTERRUPT)) &
                EUSCI_B_I2C_NAK_INTERRUPT)
        {
            // If NACK, set stop bit and exit
            (void)MAP_I2C_masterSendMultiByteStopWithTimeout(_moduleInstance, I2C_TIMEOUT);
            return(false);
        }
    
        // Turn off TX and generate RE-Start
        MAP_I2C_masterReceiveStart(_moduleInstance);
    
        // Wait for start bit to complete
        while(MAP_I2C_masterIsStartSent(_moduleInstance));
    
        if((MAP_I2C_getInterruptStatus(_moduleInstance, EUSCI_B_I2C_NAK_INTERRUPT)) &
                EUSCI_B_I2C_NAK_INTERRUPT)
        {
            // If NACK, set stop bit and exit
            (void)MAP_I2C_masterSendMultiByteStopWithTimeout(_moduleInstance, I2C_TIMEOUT);
            return(false);
        }
                
        // Read one or more bytes
        while(bytes)
        {
            // If reading 1 byte (or last byte), generate the stop to meet the spec
            if(bytes-- == 1)
            {
                success = MAP_I2C_masterReceiveMultiByteFinishWithTimeout(_moduleInstance, pData, I2C_TIMEOUT);
                if (!success)
                {
                    return false;
                }
            }
            else
            {
                // Wait for next RX interrupt
                while(!(MAP_I2C_getInterruptStatus(_moduleInstance, EUSCI_B_I2C_RECEIVE_INTERRUPT0) &
                        EUSCI_B_I2C_RECEIVE_INTERRUPT0));
    
                // Read the rx byte
                *pData++ = MAP_I2C_masterReceiveMultiByteNext(_moduleInstance);
            }
        }
    
        MAP_I2C_clearInterruptFlag(_moduleInstance, EUSCI_B_I2C_TRANSMIT_INTERRUPT0);
        
        return(true);
    }
    
    

    As you can see, there are a mix of calls with timeout and others that only provide the "snapshot" you mentioned.  If I understand correctly, if I get a timeout and send a STOP condition before returning a false to indicate a failure, that _should_ clear any busy bit so that I can try to communicate with other I2C devices (which are on unique I2C addresses)?

    Thanks very much for your tips and help!

    Scott

  • Scott,
    If you have time, then I would encourage you to check out the software development kit and see how this is done in the TIRTOS/FreeRTOS environements. Here is an example with the TMP007 that you reference:

    dev.ti.com/.../

    Regards,
    Chris
  • Hi Chris,

    Thank you very much for the pointer to examples.  I will certainly take a look and see how it's done with TIRTOS/FreeRTOS.

    I fumbled into a method that restores control of the I2C controller, but it is probably not ideal.  I share it here just for completeness.  Whenever I detect a timeout or error, I invoke the following:

    //--------------------------------------------------------------------------
    /// @brief  Set the I2C controller back to a known state by forcing a STOP
    ///         condition and clearing any pending interrupts.
    //--------------------------------------------------------------------------
    void CI2C::ResetController()
    {
        // Force stop bit to clear busy
        BITBAND_PERI(EUSCI_B_CMSIS(_moduleInstance)->CTLW0,UCTXSTP_OFS) = 1;
        // Clear pending interrupt flags
        MAP_I2C_clearInterruptFlag(_moduleInstance, EUSCI_B_I2C_TRANSMIT_INTERRUPT0 |
                                   EUSCI_B_I2C_NAK_INTERRUPT | EUSCI_B_I2C_STOP_INTERRUPT);
    }
    
    

    This definitely helps, but the examples you pointed me to may have a better approach overall.

    Many thanks!

    Scott

  • Sorry to be dense, but could you tell me where the source is for I2C_init(), I2C_open(), I2c_transfer, and I2C_close()? From the #include files I see, they are probably brought in via ti/drivers/I2C.h, but I could not find this source in the navigation tree. Can you provide a link?

    Thanks again!
  • Scott,
    Thanks for posting the resolution. If you look in the C:\ti\tirex-content\simplelink_msp432_sdk_1_20_00_45\source\ti\drivers you will find the I2C.c file which is device agnostic and then if you look in the i2c folder you will find the I2CMSP432.c/h files.

    Hope that helps,
    Chris
  • Hi Chris, another really dumb question, but what do I need to download this content? Right now I just have DriverLib, and do not have TIRTOS, FreeRTOS, or the drivers you refer to. What package do I need to download to look at these driver files? Sorry if this should be obvious... :-)

    Scott
  • Scott,
    You can see the information here http://dev.ti.com/tirex/#/All . If you do have CCS then you can download through resource explorer; View->Resource Explorer. It is called the SImpleLink MSP432 SDK.

    Regards,
    Chris

**Attention** This is a public forum