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.

TivaWare I2C Loopback Example has a problem

Other Parts Discussed in Thread: TM4C123GH6PM

Hi,

I'm using the I2C Loopback example to teach myself how to operate the I2C module. So that I can in turn teach it to my students. I'm taking the loopback code straight from the example and converting it line by line to use direct register access instead of peripheral driver library functions. But I believe I've come across an error in the code. Just for the record I'm using the Tiva C Launchpad (tm4c123gh6pm), TivaWare 2.1.0.12573, and CCS 6.0.0.00190. The part that has an issue is when the master is requesting to receive data from the slave. The original (problematic) TivaWare code:

//
// Reset receive buffer.
//
for(ui32Index = 0; ui32Index < NUM_I2C_DATA; ui32Index++)
{
pui32DataRx[ui32Index] = 0;
}

//
// Indicate the direction of the data.
//
UARTprintf("\n\nTranferring from: Slave -> Master\n");

//
// Modifiy the data direction to true, so that seeing the address will
// indicate that the I2C Master is initiating a read from the slave.
//
I2CMasterSlaveAddrSet(I2C0_BASE, SLAVE_ADDRESS, true);

//
// Do a dummy receive to make sure you don't get junk on the first receive.
//
I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);

//
// Dummy acknowledge and wait for the receive request from the master.
// This is done to clear any flags that should not be set.
//
while(!(I2CSlaveStatus(I2C0_BASE) & I2C_SLAVE_ACT_TREQ))
{
}

for(ui32Index = 0; ui32Index < NUM_I2C_DATA; ui32Index++)
{
//
// Display the data that I2C0 slave module is transferring.
//
UARTprintf(" Sending: '%c' . . . ", pui32DataTx[ui32Index]);

//
// Place the data to be sent in the data register
//
I2CSlaveDataPut(I2C0_BASE, pui32DataTx[ui32Index]);

//
// Tell the master to read data.
//
I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);

//
// Wait until the slave is done sending data.
//
while(!(I2CSlaveStatus(I2C0_BASE) & I2C_SLAVE_ACT_TREQ))
{
}

//
// Read the data from the master.
//
pui32DataRx[ui32Index] = I2CMasterDataGet(I2C0_BASE);

//
// Display the data that the slave has received.
//
UARTprintf("Received: '%c'\n", pui32DataRx[ui32Index]);
}

//
// Tell the user that the test is done.
//
UARTprintf("\nDone.\n\n");

//
// Return no errors
//
return(0);
}

I don't think that "Dummy Receive" stuff before the for loop does what it says it does. What it seems to do is queue up a receive. That while loop makes it so that as soon as the slave has acknowledged that the master is requesting data it goes into the for loop. During that first UART printf in the for loop the master is already waiting for data. The slave puts data into its data register which triggers the transmit back to the master. The Master receives it and then queues up the next receive. This program "works" in that the 3 bytes of data get sent and received correctly but the problem is that after the third receive the master has queued up a fourth so when "Done" is printed the slave's TREQ (transmit requested) flag is '1'. So if you wanted to add on to the end of this program the master would already be waiting for the slave to send more data.

The comments attached to the code seem to spell out that the entire transfer of a single character should take place during a single iteration of that for loop. Either that "dummy receive" functionality is unnecessary or improperly implemented. Here's what my code looks like now (there is a mix of peripheral driver functions and direct register access as I'm still working on it, sorry if that's confusing):

//
// Reset receive buffer.
//
for(ui32Index = 0; ui32Index < NUM_I2C_DATA; ui32Index++)
{
pui32DataRx[ui32Index] = 0;
}

//
// Indicate the direction of the data.
//
UARTprintf("\n\nTranferring from: Slave -> Master\n");

//
// Modifiy the data direction to true, so that seeing the address will
// indicate that the I2C Master is initiating a read from the slave.
//
//I2CMasterSlaveAddrSet(I2C0_BASE, SLAVE_ADDRESS, true);
I2C0_MSA_R |= I2C_MSA_RS;

//
// Do a dummy receive to make sure you don't get junk on the first receive.
//
//I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);
//I2C0_MCS_R = I2C_MCS_START|I2C_MCS_STOP|I2C_MCS_RUN;

//
// Dummy acknowledge and wait for the receive request from the master.
// This is done to clear any flags that should not be set.
//
//while(!(I2CSlaveStatus(I2C0_BASE) & I2C_SLAVE_ACT_TREQ))
//{
//}

for(ui32Index = 0; ui32Index < NUM_I2C_DATA; ui32Index++)
{
//
// Display the data that I2C0 slave module is transferring.
//
UARTprintf(" Sending: '%c' . . . ", pui32DataTx[ui32Index]);

//
// Tell the master to read data.
//
I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);

//
// Wait until the slave acknowledges it needs to transfer.
//
while(!(I2CSlaveStatus(I2C0_BASE) & I2C_SLAVE_ACT_TREQ))
{
}

//
// Place the data to be sent in the data register
//
I2CSlaveDataPut(I2C0_BASE, pui32DataTx[ui32Index]);

//
// Wait until module is done transferring.
//
//while(I2CMasterBusy(I2C0_BASE))
while(I2C0_MCS_R & I2C_MCS_BUSBSY)
{
}

//
// Read the data from the master.
//
pui32DataRx[ui32Index] = I2CMasterDataGet(I2C0_BASE);

//
// Display the data that the master has received.
//
UARTprintf("Received: '%c'\n", pui32DataRx[ui32Index]);
}

//
// Tell the user that the test is done.
//
UARTprintf("\nDone.\n\n");

//
// Return no errors
//
return(0);
}

My version gets rid of the "dummy receive" stuff all together (it's commented out so you can see where it was). It successfully transfers the 3 bytes. During the first printf in the for loop the master isn't already waiting for data and during the final printf the I2C module is actually Idle as opposed to having the master waiting for another transfer.

It seems to me like this is how the I2C control flow is "supposed" to work and that the TivaWare should be updated to something similar to this. If the dummy stuff needs to be re-added for whatever reason it should be implemented differently.

If I'm seeing things incorrectly or am misunderstanding anything please explain where I went wrong. If you need a complete version of my code let me know. (I tried to only post the pertinent pieces, given that its a standard TivaWare example). Thanks.

  • Hello Josh,

    First of, thanks a lot for sending the version of the tool and software.

    The TIVAWare I2C Loopback example has a problem which we figured out quite a few months back

    http://e2e.ti.com/support/microcontrollers/tiva_arm/f/908/t/317558.aspx

    It would be corrected in the next release. I hope the above mentioned post link is the answer you have been looking for.

    Regards

    Amit

  • Also be aware there is an issue when in Master mode that the I2CMCS register has an errata where it will reset ADRACK and DATACK when BUSY is reset. Use I2CMRIS to successfully read the ACK status. (This works even if interrupts are disabled.)

    A forum search will net a few nuggets of sample code along these lines.

  • The problem I pointed out seems to be a totally different thing... The TivaWare code does "work" it doesn't get trapped in any loops, it finishes after successfully transferring all the data. The problem is that when it's done, I2C0 isn't actually idle the way it should be. It's requested another byte of data and is waiting for the slave to acquiesce. The loop where the master requests the slave to transfer data, and the slave sends the data doesn't flow the way the comments suggest it does. It works by "accident" because the program stops where it does, the "bug" has no obviously apparent symptoms. I only noticed it because I was monitoring the slave's status register. If the I2C module tried to do something more after that loop the bug would present itself because it would likely get stuck in a "status check loop" like the link you pasted described.

  • Hello Josh

    That is the exact issue I pointed in the earlier post. It always presented a problem when the next I2C Transaction was done.

    Regards

    Amit

  • Okay. I think I misread it the first time it seemed like you were talking about something earlier in the code.

  • Hello Josh,

    Furthermore the post has the correction that you may want to take into account when doing the application code

    Regards

    Amit

  • This is the correction you are referring to?:

    A simple code fix to get it to work every time was to put SysCtlPeripheralReset(SYSCTL_PERIPH_I2C6) after the SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C6). After doing the above, it worked every time.

    I will look into why the Slave is in Transmit State.

    I don't want to be argumentative, but I don't think that is actually a solution to the problem. That is again just hiding the symptom of the problem. My understanding is that all that will do is reset the I2C module's registers to be in there "default" state. As if the power had been reset. But the code beyond that still has the bug as I've outlined it above. And the program's basic control flow will not function the way it is supposed to.

  • Hello Josh,

    Yes, that was one way to get the example working when using the CPU restart. As for the actual fix that would be going, it looked something like the following to replace the I2CMasterControl(I2C6_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE) and the subsequent TREQ poll

    if(ui32Index < (NUM_I2C_DATA-1)) {

                    I2CMasterControl(I2C6_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);

                    //

                    // Wait until the slave is done sending data.

                    //

                    while(!(I2CSlaveStatus(I2C6_BASE) & I2C_SLAVE_ACT_TREQ))

                    {

                    }

     

    } else {

                    I2CMasterControl(I2C6_BASE, I2C_MASTER_CMD_BURST_RECEIVE_FINISH);

    }

    Regards

    Amit