Interrupt based I2C on Stellaris LM4f232h5qd

Hi,

I am very new to Stellaris LM4F232 controller. I have to implement Interrupt based I2C communication on LM4F232 (as a master). For that i am refering following code.

#define STATE_IDLE         0
#define STATE_WRITE_NEXT   1
#define STATE_WRITE_FINAL  2
#define STATE_WAIT_ACK     3
#define STATE_SEND_ACK     4
#define STATE_READ_ONE     5
#define STATE_READ_FIRST   6
#define STATE_READ_NEXT    7
#define STATE_READ_FINAL   8
#define STATE_READ_WAIT    9

//*****************************************************************************
//
// The variables that track the data to be transmitted or received.
//
//*****************************************************************************
static unsigned char *g_pucData = 0;
static unsigned long g_ulCount = 0;

//*****************************************************************************
//
// The current state of the interrupt handler state machine.
//
//*****************************************************************************
static volatile unsigned long g_ulState = STATE_IDLE;

//*****************************************************************************
//
// The error routine that is called if the driver library encounters an error.
//
//*****************************************************************************
#ifdef DEBUG
void
__error__(char *pcFilename, unsigned long ulLine)
{
}
#endif

//*****************************************************************************
//
// The I2C interrupt handler.
//
//*****************************************************************************
void
I2CIntHandler(void)
{
    //
    // Clear the I2C interrupt.
    //
    I2CMasterIntClear(I2C0_MASTER_BASE);

    //
    // Determine what to do based on the current state.
    //
    switch(g_ulState)
    {
        //
        // The idle state.
        //
        case STATE_IDLE:
        {
            //
            // There is nothing to be done.
            //
            break;
        }

        //
        // The state for the middle of a burst write.
        //
        case STATE_WRITE_NEXT:
        {
            //
            // Write the next byte to the data register.
            //
            I2CMasterDataPut(I2C0_MASTER_BASE, *g_pucData++);
            g_ulCount--;

            //
            // Continue the burst write.
            //
            I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_CONT);

            //
            // If there is one byte left, set the next state to the final write
            // state.
            //
            if(g_ulCount == 1)
            {
                g_ulState = STATE_WRITE_FINAL;
            }

            //
            // This state is done.
            //
            break;
        }

        //
        // The state for the final write of a burst sequence.
        //
        case STATE_WRITE_FINAL:
        {
            //
            // Write the final byte to the data register.
            //
            I2CMasterDataPut(I2C0_MASTER_BASE, *g_pucData++);
            g_ulCount--;

            //
            // Finish the burst write.
            //
            I2CMasterControl(I2C0_MASTER_BASE,
                             I2C_MASTER_CMD_BURST_SEND_FINISH);

            //
            // The next state is to wait for the burst write to complete.
            //
            g_ulState = STATE_SEND_ACK;

            //
            // This state is done.
            //
            break;
        }

        //
        // Wait for an ACK on the read after a write.
        //
        case STATE_WAIT_ACK:
        {
            //
            // See if there was an error on the previously issued read.
            //
            if(I2CMasterErr(I2C0_MASTER_BASE) == I2C_MASTER_ERR_NONE)
            {
                //
                // Read the byte received.
                //
                I2CMasterDataGet(I2C0_MASTER_BASE);

                //
                // There was no error, so the state machine is now idle.
                //
                g_ulState = STATE_IDLE;

                //
                // This state is done.
                //
                break;
            }

            //
            // Fall through to STATE_SEND_ACK.
            //
        }

        //
        // Send a read request, looking for the ACK to indicate that the write
        // is done.
        //
        case STATE_SEND_ACK:
        {
            //
            // Put the I2C master into receive mode.
            //
            I2CMasterSlaveAddrSet(I2C0_MASTER_BASE, 0x50, true);

            //
            // Perform a single byte read.
            //
            I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);

            //
            // The next state is the wait for the ack.
            //
            g_ulState = STATE_WAIT_ACK;

            //
            // This state is done.
            //
            break;
        }

        //
        // The state for a single byte read.
        //
        case STATE_READ_ONE:
        {
            //
            // Put the I2C master into receive mode.
            //
            I2CMasterSlaveAddrSet(I2C0_MASTER_BASE, 0x50, true);

            //
            // Perform a single byte read.
            //
            I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);

            //
            // The next state is the wait for final read state.
            //
            g_ulState = STATE_READ_WAIT;

            //
            // This state is done.
            //
            break;
        }

        //
        // The state for the start of a burst read.
        //
        case STATE_READ_FIRST:
        {
            //
            // Put the I2C master into receive mode.
            //
            I2CMasterSlaveAddrSet(I2C0_MASTER_BASE, 0x50, true);

            //
            // Start the burst receive.
            //
            I2CMasterControl(I2C0_MASTER_BASE,
                             I2C_MASTER_CMD_BURST_RECEIVE_START);

            //
            // The next state is the middle of the burst read.
            //
            g_ulState = STATE_READ_NEXT;

            //
            // This state is done.
            //
            break;
        }

        //
        // The state for the middle of a burst read.
        //
        case STATE_READ_NEXT:
        {
            //
            // Read the received character.
            //
            *g_pucData++ = I2CMasterDataGet(I2C0_MASTER_BASE);
            g_ulCount--;

            //
            // Continue the burst read.
            //
            I2CMasterControl(I2C0_MASTER_BASE,
                             I2C_MASTER_CMD_BURST_RECEIVE_CONT);

            //
            // If there are two characters left to be read, make the next
            // state be the end of burst read state.
            //
            if(g_ulCount == 2)
            {
                g_ulState = STATE_READ_FINAL;
            }

            //
            // This state is done.
            //
            break;
        }

        //
        // The state for the end of a burst read.
        //
        case STATE_READ_FINAL:
        {
            //
            // Read the received character.
            //
            *g_pucData++ = I2CMasterDataGet(I2C0_MASTER_BASE);
            g_ulCount--;

            //
            // Finish the burst read.
            //
            I2CMasterControl(I2C0_MASTER_BASE,
                             I2C_MASTER_CMD_BURST_RECEIVE_FINISH);

            //
            // The next state is the wait for final read state.
            //
            g_ulState = STATE_READ_WAIT;

            //
            // This state is done.
            //
            break;
        }

        //
        // This state is for the final read of a single or burst read.
        //
        case STATE_READ_WAIT:
        {
            //
            // Read the received character.
            //
            *g_pucData++  = I2CMasterDataGet(I2C0_MASTER_BASE);
            g_ulCount--;

            //
            // The state machine is now idle.
            //
            g_ulState = STATE_IDLE;

            //
            // This state is done.
            //
            break;
        }
    }
}

//*****************************************************************************
//
// Write to the Atmel device.
//
//*****************************************************************************
void
AtmelWrite(unsigned char *pucData, unsigned long ulOffset,
           unsigned long ulCount)
{
    //
    // Save the data buffer to be written.
    //
    g_pucData = pucData;
    g_ulCount = ulCount;

    //
    // Set the next state of the interrupt state machine based on the number of
    // bytes to write.
    //
    if(ulCount != 1)
    {
        g_ulState = STATE_WRITE_NEXT;
    }
    else
    {
        g_ulState = STATE_WRITE_FINAL;
    }

    //
    // Set the slave address and setup for a transmit operation.
    //
    I2CMasterSlaveAddrSet(I2C0_MASTER_BASE, 0x50 | (ulOffset >> 8), false);

    //
    // Place the address to be written in the data register.
    //
    I2CMasterDataPut(I2C0_MASTER_BASE, ulOffset);

    //
    // Start the burst cycle, writing the address as the first byte.
    //
    I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_START);

    //
    // Wait until the I2C interrupt state machine is idle.
    //
    while(g_ulState != STATE_IDLE)
    {
    }
}

//*****************************************************************************
//
// Read from the Atmel device.
//
//*****************************************************************************
void
AtmelRead(unsigned char *pucData, unsigned long ulOffset,
          unsigned long ulCount)
{
    //
    // Save the data buffer to be read.
    //
    g_pucData = pucData;
    g_ulCount = ulCount;

    //
    // Set the next state of the interrupt state machine based on the number of
    // bytes to read.
    //
    if(ulCount == 1)
    {
        g_ulState = STATE_READ_ONE;
    }
    else
    {
        g_ulState = STATE_READ_FIRST;
    }

    //
    // Start with a dummy write to get the address set in the EEPROM.
    //
    I2CMasterSlaveAddrSet(I2C0_MASTER_BASE, 0x50 | (ulOffset >> 8), false);

    //
    // Place the address to be written in the data register.
    //
    I2CMasterDataPut(I2C0_MASTER_BASE, ulOffset);

    //
    // Perform a single send, writing the address as the only byte.
    //
    I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_SINGLE_SEND);

    //
    // Wait until the I2C interrupt state machine is idle.
    //
    while(g_ulState != STATE_IDLE)
    {
    }
}

//*****************************************************************************
//
// This example demonstrates the use of the I2C block to connect to an Atmel
// AT24C08A EEPROM.
//
//*****************************************************************************
int
main(void)
{
    unsigned char pucData[16];
    unsigned long ulIdx;

    //
    // Set the clocking to run directly from the crystal.
    //
    SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN |
                   SYSCTL_XTAL_6MHZ);

    //
    // Init the PDC and the LCD.
    //
    PDCInit();
    PDCLCDInit();
    PDCLCDBacklightOn();

    //
    // Enable the peripherals used by this example.
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);

    //
    // Indicate that the I2C example is running.
    //
    PDCLCDSetPos(0, 0);
    PDCLCDWrite("I2C running...", 14);

    //
    // Enable processor interrupts.
    //
    IntMasterEnable();

    //
    // Configure the appropriate pins to be I2C instead of GPIO.
    //
    GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_2 | GPIO_PIN_3);

    //
    // Initialize the I2C master.
    //
    I2CMasterInitExpClk(I2C0_MASTER_BASE, SysCtlClockGet(), false);

    //
    // Enable the I2C interrupt.
    //
    IntEnable(INT_I2C0);

    //
    // Enable the I2C master interrupt.
    //
    I2CMasterIntEnable(I2C0_MASTER_BASE);

    //
    // Write a data=address pattern into the first 16 bytes of the Atmel
    // device.
    //
    for(ulIdx = 0; ulIdx < 16; ulIdx++)
    {
        pucData[ulIdx] = ulIdx;
    }
    AtmelWrite(pucData, 0, 16);

    //
    // Read back the first 16 bytes of the Atmel device and verify that it
    // contains the data it should.
    //
    AtmelRead(pucData, 0, 16);
    for(ulIdx = 0; ulIdx < 16; ulIdx++)
    {
        if(pucData[ulIdx] != ulIdx)
        {
            PDCLCDSetPos(0, 1);
            PDCLCDWrite("Data error.", 11);
            while(1)
            {
            }
        }
    }

    //
    // Success.
    //
    PDCLCDSetPos(0, 1);
    PDCLCDWrite("Success.", 8);

    //
    // Finished.
    //
    while(1)
    {
    }
}

I have some Questions in this code

1. Why "STATE_WAIT_ACK  and STATE_SEND_ACK" states are used.(We dont do these steps while polling based transmission). Actually we finish sending of data after sending I2C_MASTER_CMD_BURST_SEND_FINISH.

2. As we will get interrupt after sending each byte.  is it neccesory to check transmission error at start of  ISR Routine. why in this code error is check at state STATE_WAIT_ACK? i.e after compliting whole transaction(after getting STOP bit) .

  • I agree with your assessment of those two states.  I have implemented a lot of I2C code based on the example you're referring to (the biggest being the SMBus stack in StellarisWare/utils) and have typically excluded those states since they aren't really useful.  I also don't understand why a read operation would be requested after a failed NACK on a write...  I also agree that error checking should be done at the beginning of each ISR.  In my implementations, I simply read the I2CMCSR to see if errors occurred before doing any data processing.  I believe this to be a much safer implementation than this example.

  • In reply to Eric Ocasio:

    Hi Eric

    Thanks for your suggestion, i removed that two states and my code is working fine.

  • In reply to vivek patil:

    I too am working with the new M4 class CPU and having an issue registering my I2C ISR in CC5. I have tried adding the Startup_ccs.c and modifying the handler there and just simply adding this line in my InitI2C routine:

    IntRegister (I2C0_MASTER_BASE,I2CIntHandler);   // Hook the ISR

    In either cazse I get the following linker error:

    <Linking>

    >> WARNING: --code_state=32 is invalid for Cortex-M (ignored)

    "../lm4f232h5qd.cmd", line 37: error #10099-D: run placement fails for object

    ".vtable", size 0x26c (page 0). Available ranges:

    SRAM size: 0x8000 unused: 0x7cac max hole: 0x7cac

    error #10010: errors encountered during linking; "Test Processor.out" not

    built

    Thoughts? Thanks, Jeff

  • In reply to Jeffrey Jacobson:

    Hi Jeff,

    That's not an error I've seen before.  It seems like there is either a problem with the way you are declaring your interrupt handler or the way your project settings are set up.  Would you be able to post your startup file and the “.cmd” file for this project? Also, did you create this project from scratch? If so,  you may want to try copying the C from this project into an already working project from StellarisWare (maybe the hello example?) and re-type the relevant changes to the startup file to see if the pre-made project settings eliminate the problem.

    Regards,

    Christian

  • In reply to Jeffrey Jacobson:

    Jeffrey Jacobson
    just simply adding this line in my InitI2C routine:IntRegister (I2C0_MASTER_BASE,I2CIntHandler);   // Hook the ISR

    Beware when poster (or boss) uses "simply!"  Rarely simple.  Earlier poster succeeded without "Registering" his interrupts - your "simple" is likely cause...

    From SW-DRL-UG-8555 pg 246: Re: Function IntRegister

    "The use of this function (directly or indirectly via a peripheral driver interrupt register function) moves the interrupt vector table from flash to SRAM"  Therefore, care must be taken when linking the application to ensure that the SRAM vector table is located at the beginning of SRAM; otherwise the NVIC does not look in the correct portion of memory for the vector table...

    Suggest that you model original poster's interrupt format - master intRegister in the future...

  • In reply to cb1_mobile:

    I used an existing sys/bios example called 'typical' as my starting point and used the examples people have posted to move forward. My simply statement was in reference to making the call per the examples to hook my ISR without the need to add startup files. That is what did not work. Its amazing to me that with all the people out there that there is not a single FULL sample that actually works. I need mine to be interrupt driven not just force bytes out like the examples show. Also, not able to see a working example using Port B pins 2 & 3.

    So, I went back to the 'original' poster and that code does not work either. I have had a TI FAE out here for a full day and he cant get it to link or work so its not just me. It would be great if at the end of fixing someone's issue, the complete solution is posted instead of bits and pieces that only fix their problem but not the rest of us who do not have all the hidden, at there are tons of them, tricks that need to be done to SYS/BIOS to get it to work.

    Sorry for venting but I am no closer to solving my issue then I was last Thursday.

    My goal is to use Port B I2C0 as a interrupt driven I2C interface on  the LM4F232 part using SYS/BIOS and the dev kit.

    I will keep looking and trying things. When I find the solution, I will for sure post the complete solution not just bits & pieces.

  • In reply to Christian Jolivet:

    I used the 'typical' example under sys/Bios as my starting point. Again, I am trying to get this to work under SYS/BIOS. When you create that project there is no startup file added...

    I will keep looking for help with this. Thanks, Jeff

  • In reply to Jeffrey Jacobson:

    I feel your pain - did offer what I believed to be logical (and documented) solution.  And - minus your SYS/BIOS - I did get the original "LMI" version of poster's interrupt driven I2C to work several years back.

    Suspect that the relative "newness" of SYS/BIOS may be magnifying your issue - often such developments require time to earn full, "bullet-proof" status.

    As both marketing & tech guy - the more you can do to influence others to adopt SYS/BIOS - the more resources TI can direct in this direction.  (to my mind - the features/functions/benefits of SYS/BIOS have not appeared in any convincing fashion yet on this forum...)  Wish you well - this issue outside my areas of contribution...

  • In reply to cb1_mobile:

    Thank you for your help as it has proven to be outstanding!

    We will figure this out

    Jeff

  • In reply to Jeffrey Jacobson:

    Well the patient is now back at least among the living. I am able to get the ISR hooked using the hwi of sys/bios so at this point I am past that. I am watching the code and for some reason in my write code, I get the start with the address followed by an extra (unexpected) data byte. The call that I think is doing it is:

        //
        // Start the burst cycle, writing the address as the first byte.
        //
        I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_START);

    The whole write routine is:

    //*****************************************************************************
    //
    // Write to the I2C device.
    //
    //*****************************************************************************
    void I2CWrite(unsigned char *pucData, unsigned long ulBaseAddress, unsigned long ulOffset,unsigned long ulCount)
    {
        //
        // Save the data buffer to be written.
        //
        g_pucData = pucData;
        g_ulCount = ulCount;

        //
        // Set the next state of the interrupt state machine based on the number of
        // bytes to write.
        //
        if(ulCount != 1)
        {
            g_ulState = STATE_WRITE_NEXT;
        }
        else
        {
            g_ulState = STATE_WRITE_FINAL;
        }

        //
        // Set the slave address and setup for a transmit operation.
        //
        I2CMasterSlaveAddrSet(I2C0_MASTER_BASE, ulBaseAddress | (ulOffset >> 8), false);

        //
        // Start the burst cycle, writing the address as the first byte.
        //
        I2CMasterControl(I2C0_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_START);

        //
        // Wait until the I2C interrupt state machine is idle.
        //
        while(g_ulState != STATE_IDLE);
    }

    Any thoughts why that line would initiate a data byte to be sent?

    When I get this fully bugged out today, I will post the solution to this also!

    Thanks, Jeff