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.

EK-TM4C129EXL: I2C Slave Driver FIFO Implementation

Part Number: EK-TM4C129EXL
Other Parts Discussed in Thread: CCSTUDIO

Hi,

I am working on a platform that will be using the TM4C129ENCPDT Arm chip. For development, I'm using the EK-TM4C129EXL and referencing the following TivaWare API. 

Description:

The goal of the design is to have some other host board act as an i2c/smbus master, which would trigger the slave interrupt. The master will send multi-byte write commands and single byte read commands. When prompted for a read, the slave will also need to respond with multiple bytes. Due to the multi-byte transactions, I want to leverage the i2c FIFO peripheral functionality. I have some basic functionality "working" (code posted below), but there are several quirks that I am running into, so I have several questions about the provided TivaWare API and my implementation of it. 

To test my slave drivers, I am using the protocol feature on an Analog Discovery 2 (AD2) to act as an i2c master. 

Theory of Operation:

  • master sends read or write request
  • this causes the i2c slave interrupt to trigger
  • in the interrupt, the slave calls I2CSlaveStatus(...) to determine the type of message received from the master
  • if the master is writing data, store the data in the provided buffer to move it out of the FIFO
  • if the master is requesting a read, it will have first sent a write message telling the slave to put the data in the Tx buffer, which will then get read out on the subsequent read command

Code:

Note - the status variable isn't doing anything, I am having issues with the I2CSlaveStatus(...) call. See question 1 below. 

uint8_t buffer[4] = {0};
uint8_t constBuffer[4] = {0xA0, 0xB0, 0xC0, 0xD0};

void i2cIntHandler(void)
{
    uint32_t status = I2CSlaveStatus(I2C0_BASE);

    if(status == I2C_SLAVE_ACT_RREQ)
    {
        status = status;
    }
    else if(status == I2C_SLAVE_ACT_TREQ)
    {
        status = status;
    }
    else
    {
        status = status;
    }

    uint8_t x = 1;
    uint8_t index = 0;
    while(x)
    {
        status = I2CSlaveStatus(I2C0_BASE);
        x = I2CFIFODataGetNonBlocking(I2C0_BASE, &buffer[index]);
        index++;
    }

//    while(x)
//    {
//        status = I2CSlaveStatus(I2C0_BASE);
//        x = I2CFIFODataGetNonBlocking(I2C0_BASE, buffer);
//    }

    for(uint8_t i = 0; i < 4; i++)
    {
        I2CFIFODataPutNonBlocking(I2C0_BASE, constBuffer[i] + buffer[i]);
    }

    I2CSlaveIntClear(I2C0_BASE);
}

void initSMBus(void)
{
    GPIOPinConfigure(GPIO_PB2_I2C0SCL);
    GPIOPinConfigure(GPIO_PB3_I2C0SDA);
    GPIOPinTypeI2CSCL(GPIO_PORTB_BASE, GPIO_PIN_2); // configure SCL line
    GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3);    // configure SDA line

    SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);
    while(!SysCtlPeripheralReady(SYSCTL_PERIPH_I2C0));

    uint8_t address = 0x12;
    I2CSlaveInit(I2C0_BASE, address);
    I2CSlaveFIFOEnable(I2C0_BASE, I2C_SLAVE_RX_FIFO_ENABLE | I2C_SLAVE_TX_FIFO_ENABLE);
    I2CRxFIFOConfigSet(I2C0_BASE, I2C_FIFO_CFG_RX_SLAVE | I2C_FIFO_CFG_RX_TRIG_4);
    I2CTxFIFOConfigSet(I2C0_BASE, I2C_FIFO_CFG_TX_SLAVE | I2C_FIFO_CFG_TX_TRIG_4);

    I2CSlaveIntEnable(I2C0_BASE);
    I2CIntRegister(I2C0_BASE, i2cIntHandler);
}

Read and Write Results:

The transactions are kinda working. I am using the subaddress field as a hacky way to send data when doing a read command - which the AD2 sends as a write then a read. I'd expect the output to always be A1 B2 C3 D4 after every read command. Using the debugger, I confirmed that the write messages are not getting fully parsed in, but I'd still expect to see the A B C D elements of the read since those are just hard coded constants. Furthermore, the read output changes. I think I have a fundamental misunderstanding as to how these FIFOs are supposed to be implemented. Insight is appreciated. 

Questions:

  1. I am using the I2CSlaveStatus call to determine what kind of message the master sent us. Using that differentiation will allow me to actually separate out that Tx and Rx FIFO accessing so they don't occur in every interrupt, like they do in my code example. However, I have noticed that this call is always returning zero. At one point, I was detecting the I2C_SLAVE_ACT_TREQ status by using breakpoints in the CCStudio debugger; however, I am unable to reliably implement that API call. This is a pretty important call in the slave design, so I'd really like to get it working. 
  2. In the initialization code on lines 57 and 58, what are those CFG TRIG_# flags? The API says it's the trigger level. What does that mean? Is it the FIFO size at which some triggering interrupt flag will occur?
  3. What are the FIFO sizes? I can't find any details about their specifics in the provided documentation. 
  4. I cannot find any examples on how to use this slave elements of this API anywhere online. Any examples or feedback on how these FIFOs actually work would be greatly appreciated. 

Thank you for your help,

Zach

  • Hello Zach,

    I am using the I2CSlaveStatus call to determine what kind of message the master sent us. Using that differentiation will allow me to actually separate out that Tx and Rx FIFO accessing so they don't occur in every interrupt, like they do in my code example. However, I have noticed that this call is always returning zero. At one point, I was detecting the I2C_SLAVE_ACT_TREQ status by using breakpoints in the CCStudio debugger; however, I am unable to reliably implement that API call. This is a pretty important call in the slave design, so I'd really like to get it working. 

    The I2CSlaveStatus is traditionally used for polling operations. You should use I2CSlaveIntStatusEx for interrupt operation to get the interrupt flag that was set to trigger the ISR entry.

    In the initialization code on lines 57 and 58, what are those CFG TRIG_# flags? The API says it's the trigger level. What does that mean? Is it the FIFO size at which some triggering interrupt flag will occur?

    They set the number of bytes that need to be received in the FIFO before an interrupt is fired to indicate the FIFO watermark has been reached. This allows you to determine how much/little the FIFO is filled before you try and read any data more data from it.

    What are the FIFO sizes? I can't find any details about their specifics in the provided documentation. 

    The FIFO is 8 bytes deep. This is documented in Section 21.3.5 FIFO and µDMA Operation.

    I cannot find any examples on how to use this slave elements of this API anywhere online. Any examples or feedback on how these FIFOs actually work would be greatly appreciated. 

    I don't believe we have a specific FIFO I2C Slave example readily available. We have a generic I2C slave example in TivaWare but that doesn't use the FIFO.

    See if using I2CSlaveIntStatusEx gets some of your issues here resolved, the FIFO APIs you are using seem correct at a glance. I think not getting the right interrupt flags is a contributing factor to the issues you are having with the implementation.

    Best Regards,

    Ralph Jacobi

  • Thank you for your help! I'll give these changes a shot and report back to this forum. 

  • After implementing Ralph's suggestions, things are behaving much better. I believe all my issues are resolved. 

    Here is the reworked code for reference.

    uint8_t buffer[8] = {0};
    uint8_t constBuffer[4] = {0xA0, 0xB0, 0xC0, 0xD0};
    
    void i2cIntHandler(void)
    {
    
        uint32_t status = I2CSlaveIntStatusEx(I2C0_BASE, true);
        if(status == I2C_SLAVE_INT_RX_FIFO_REQ)
        {
            uint8_t x = 1;
            uint8_t index = 0;
    
            while(x)
            {
                x = I2CFIFODataGetNonBlocking(I2C0_BASE, &buffer[index]);
                index++;
            }
        }
        else if(status == I2C_SLAVE_INT_TX_FIFO_REQ)
        {
            for(int i = 0; i < 4; i++)
            {
                uint8_t data = buffer[i] + constBuffer[i];
                I2CFIFODataPut(I2C0_BASE, data);
            }
        }
        else
        {
            // should not enter here
            status = status;
            assert(0);
        }
    
        I2CSlaveIntClearEx(I2C0_BASE, status);
    }
    
    void initSMBus(void)
    {
        GPIOPinConfigure(GPIO_PB2_I2C0SCL);
        GPIOPinConfigure(GPIO_PB3_I2C0SDA);
        GPIOPinTypeI2CSCL(GPIO_PORTB_BASE, GPIO_PIN_2); // configure SCL line
        GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3);    // configure SDA line
    
        SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);
        while(!SysCtlPeripheralReady(SYSCTL_PERIPH_I2C0));
    
        uint8_t address = 0x12;
        I2CSlaveInit(I2C0_BASE, address);
        I2CSlaveFIFOEnable(I2C0_BASE, I2C_SLAVE_RX_FIFO_ENABLE | I2C_SLAVE_TX_FIFO_ENABLE);
        I2CRxFIFOConfigSet(I2C0_BASE, I2C_FIFO_CFG_RX_SLAVE | I2C_FIFO_CFG_RX_TRIG_8);
        I2CTxFIFOConfigSet(I2C0_BASE, I2C_FIFO_CFG_TX_SLAVE | I2C_FIFO_CFG_TX_TRIG_8);
    
    //    I2CSlaveIntEnable(I2C0_BASE);
        I2CSlaveIntEnableEx(I2C0_BASE, I2C_SLAVE_INT_RX_FIFO_REQ | I2C_SLAVE_INT_TX_FIFO_REQ);
        I2CIntRegister(I2C0_BASE, i2cIntHandler);
    }

    Switching to SlaveIntEnableEx and SlaveInStatusEx did the trick. Since I am relying on the FIFOs, I use the I2C_SLAVE_INT_RX_FIFO_REQ and I2C_SLAVE_INT_TX_FIFO_REQ interrupt flags to determine whether the i2c is trying to write or read data. 

  • For any one who comes across this in the future, I added an Rx FIFO flush after the while loop but still within the if-block for improved comms reliability. You can run into some data parsing issues with varying message sizes without a flush call. 

    So add this line between lines 17 and 18:

    ```I2CRxFIFOFlush(I2C0_BASE);```