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.

TI-RTOS I2C driver stuck when slave is not present

Other Parts Discussed in Thread: MSP430FR5969, MSP-EXP430FR5969

Hi,

I'm writing a simple application to read data from an I2C slave using a MSP430FR5969 as the master.

CCS v6.1, Compiler v4.4.5, TI-RTOS MSP43x v2.14.03.28, XDCtools v3.31.1.33_core

First I noticed that I2C_transfer() never returns in I2C_MODE_BLOCKING, so I searched for any clues on this support forum.

This post  identifies the same problem and confirms my suspicions.

Question: "I have also noticed that if the I2C transaction fails then the task will block forever, shouldn't the drivers have a timeout?"

Response: "There is a bug ID for this, but it hasn't been prioritized as its possible to use the I2C driver in I2C_MODE_CALLBACK where you can add your own semaphore with a timeout. You'd block code execution after calling I2C_transfer() and then perform a Semaphore_post in its callback function."

So, it's clear that there's a known bug but nothing has been done to fix this because there is a possible workaround. It would be helpful (I think), for instance, for TI to provide an example of the proposed workaround on the wiki page or perhaps add a sticky post at the top of this forum with workarounds for known Bug IDs such as this scenario. It would save a lot of time and frustration for everyone.

I have some questions regarding my implementation of this workaround. 

For my test there is no slave attached to the I2C bus and pull-ups. I'm using a MSP-EXP430FR5969 board.

Having the callback post the semaphore or it timing out while pending prevents the task lock-up but the problem lies when it times out rather than being posted by the callback.

If the callback is never called (and it isn't in my case) then the I2C transaction remains active and throws an assert error when attempting to close it using I2C_close(). My application will eventually be deployed in remote locations so it's crucial to ensure that it will not get stuck anywhere in the code under any circumstance.

There is no mechanism in the I2C driver to safely end the transaction if the transfer never completes. Is this what the unimplemented I2C_control() function is intended for in the future?

Reading through the driver source code I was able to identify what my application needs to do, but it it requires accessing I2CEUSCIB_Object member variables in the application code which is not ideal but I've tested it and it does work. There's a note in I2CEUSCIB.h on line 134 specifically stating that the application must not do this though.

  1. Is this a new bug?
  2. Is there any other way to safely end the current transaction if the driver was unable to perform this itself (via transferComplete)?
  3. Is my approach a valid one? I have placed any code which is modifying the driver variables within a critical section. See code below.
Void MyCallbackFxn(I2C_Handle handle, I2C_Transaction *msg, Bool transfer)
{	
	// Must check the transfer result. Not yet implemented
	
	if (msg->arg != NULL)
	{
		Semaphore_post((Semaphore_Handle)(msg->arg));
	}
}

Void taskFxn(UArg arg0, UArg arg1)
{
    uint8_t         txBuffer[1];
    I2C_Handle      i2c;
    I2C_Params      i2cParams;
    I2C_Transaction i2cTransaction;

    /* Create I2C for usage */
    I2C_Params_init(&i2cParams);
    i2cParams.bitRate = I2C_100kHz;
    i2cParams.transferMode = I2C_MODE_CALLBACK;
    i2cParams.transferCallbackFxn = (I2C_CallbackFxn) MyCallbackFxn;

    i2c = I2C_open(Board_I2C_TMP, &i2cParams);
    if (i2c == NULL) {
        System_abort("Error Initializing I2C\n");
    }
    else {
        System_printf("I2C Initialized!\n");
    }

    txBuffer[0] = 0xE3;
    i2cTransaction.slaveAddress = (128 >> 1);
    i2cTransaction.writeBuf = txBuffer;
    i2cTransaction.writeCount = 1;
    i2cTransaction.arg = sem_i2cTimeout;	// provide the callback with a semaphore

	I2C_transfer(i2c, &i2cTransaction);		// always returns true in I2C_MODE_CALLBACK

	/* Pend on the I2C transactions to have completed, or to timeout */
	if (Semaphore_pend(sem_i2cTimeout, 200) == FALSE)
	{
		/*
		 * The current transaction is still active!
		 * Never received the I2C transfer callback.
		 * An active transaction cannot be closed using I2C_close()
		 * without causing the an Assert error.
		 * Two I2CEUSCIB_Object member variables need to be modified
		 * as they would normally during a successful transaction.
		 * The modification must be done within a critical section.
		 */
		unsigned int key;

		// Critical section: start
		key = Hwi_disable();

		I2CEUSCIB_Object *object_i = i2c->object;
		object_i->currentTransaction = NULL;
		object_i->headPtr = NULL;

		Hwi_restore(key);
		// Critical section: end

		// Bus fault! Transaction never took place.
		System_printf("I2C Timeout!\n");
	}
	else
	{
		// Handle I2C transaction errors...
		// Process data...
	}

    /* Deinitialised I2C */
    I2C_close(i2c);
    System_printf("I2C closed!\n");
}

Thank you

  • Hi,

    There is currently an bug /enhancement request open to add a timeout parameter to be used for blocking calls. It has been committed to a future release, but we don't know the time frame for that release yet.

    In the SPI and UART modules, we have a cancel function (SPI_transferCancel, UART_readCancel, and UART_writeCancel). There was an edge case that caused implementing I2C_cancel to be a little more difficult. There is an open enhancement request for this also. It has not been committed to a future release yet. This is the function you would use to cancel a callback_mode I2C transfer if you timed out.

    Your work-around looks ok for your use case.

    fyi..the XXX_control are to allow future growth and to take advantage of device specific features. Generally the XXX_control calls are not portable across devices, so we try to avoid them, but sometimes they are needed to avoid complicating the interface (e.g. PWMTimerMSP432_CHANGE_PERIOD).

    Todd
  • Hi Todd,

    Thank you for the explanation and responding to my queries.

    I looked at the UART and SPI modules as you mentioned. With SPI though, the SPI_transferCancel has no implementation. Much like the xxx_control functions - not very useful.

    For now I'll implement my workaround but It would be nice to be able to write more generic/portable code without the need for such "hacks" to get the driver libraries working reliably.

    I'd be very keen to be informed when the future releases become available.

    I'm impressed with TI-RTOS. It's just unfortunate that some of the drivers are not thoroughly tested and somewhat lacking and buggy.

    Raf

  • I did not realize the msp430 drivers did not have the SPI_transferCancel implemented and I did not see the enhancement request (no time-frame for the implementation at this time). The 432 has the SPI_transferCancel does have it.

    We try to have a new release at least once a quarter.

    Todd