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-TM4C123GXL: How to Create an I2C Delay?

Part Number: EK-TM4C123GXL
Other Parts Discussed in Thread: BOOSTXL-BASSENSORS, HDC2010

Hello,

I would like to create a longer delay in my code before my I2C Master write sequence. Currently, I have the line of code below:

SysCtlDelay(16000000u / 3u);

I believe this is to generate a 1 second delay, however, I'm not sure how this calculation is derived. If I wanted to create a longer delay, would I simply change these values? Say for instance, a 5 or 10 second delay?

  • Why not make your I2C accesses interrupt-driven so you don't spin cycles in a dead loop?

  • How would I go about doing this? I have a single Slave device on the bus which contains dozens of sub-addresses that I need to write to. I'm using I2C module 0 and Standard Mode speed (100Kbps)

  • To answer your original question, the function SysCtlDelay() is documented in TivaWare. A typical installation has the document in:

    C:\ti\TivaWare_C_Series-2.2.0.295\docs\SW-TM4C-DRL-UG-2.2.0.295.pdf

    See page 497.

    As Andy pointed out, in general you are better using interrupts to fire off repetitive task every 5 or 10 seconds. You can use the System Tick interrupt (described on page 533 of the same manual) or a general purpose timer. There is a TivaWare example on using the timers in:

    C:\ti\TivaWare_C_Series-2.2.0.295\examples\boards\ek-tm4c123gxl\timers

  • Hi, Rob,

    Attached is a very hacky module that sets up and reads an HDC2010 humidity/temperature sensor (on the BOOSTXL-BASSENSORS board) over I2C. Managing the details of the I2C transfer is done in the interrupt handler HDC2010_I2C_Hander(). 

    The _Init() function sets up the I2C port and then configures the 2010 with a series of I2C writes.Then for test reasons I read back the settings, and also read and print the manufacturer and product ID registers. Prior to each read or write I set a couple of flags indicating the operation, and then here's the trick: the entire I2C transfer is handled in the ISR. I invoke the ISR with MAP_IntTrigger(HDC_I2C_INT) -- it is a very old trick! -- and the ISR is a state machine that knows what to do. (I based this code off of the i2cm_drv.c TI provides in their sensorlib.) After triggering the ISR, I just spin and look for the state machine to complete as indicated by the state being set to I2C_OP_STOP.

    The HDC2010 has an interrupt out pin DRDY which connects to a GPIO configured for falling-edge interrupt. In the interrupt handler for that pin, a flag is set. The main program loop checks the state of that flag, and if it's set, it calls _ReadHumidity and _ReadTemperature to get those new readings. In each of those two functions I do I2C transactions to read the conversions.

    (I should note that the HDC2010 supports multibyte read and write and I'll implement that at some point, but this works well enough considering that accesses are basically one or two data bytes.)

    For your I2C expander which requires a dozen or so register writes to set up, you could expand the txbuf[] array to hold all of the register write data, modify the state machine for multiple bytes and let 'er rip. Modify the state I2C_OP_TXDATA to keep track of the number of bytes remaining and change MAP_I2CMasterControl() to use I2C_MASTER_CMD_BURST_SEND_CONT for all bytes except the last, which should use I2C_MASTER_CMD_BURST_FINISH.

    Good luck.

     

    hdc2010.c
    /*
     * hdc2010.c
     *
     * A driver for the TI HDC2010 humidity and temperature sensor.
     * Some of the ideas for this are borrowed from TI's i2cm_drv and modified:
     * This is hardcoded for a particular I2C port, not generic, and maybe I'll get
     * around to managing that.
     * Also the master part is specific to the needs of the 2010's data format.
     * The TI driver also keep track of the previous state. That remains,
     * but will probably get eliminated.
     *
     *  Created on: Mar 20, 2021
     *      Author: andy
     * Today is April 5, 2021.
     *
     * TODO:
     * a) move some things like g_uiSysClock and I2C slave address to a bsp.h or hardware.h
     * b) don't bother reading manufacturer ID or part ID at init time (that is there to
     *    prove it works, but maybe add functions to retrieve those things if desired.
     * c) Check to make sure we don't need I2C previous state and eliminate it if so.
     * d) All of the initialization registers can be written as a burst, so it might be
     *    interesting to set up that burst in advance and let it go, rather than doing
     *    one register write at a time.
     */
    #include <stdint.h>
    #include <stdbool.h>
    
    #include "hdc2010.h"
    
    #include "inc/hw_memmap.h"
    #include "inc/hw_gpio.h"
    #include "inc/hw_ints.h"
    
    #include "driverlib/rom.h"
    #include "driverlib/rom_map.h"
    #include "driverlib/i2c.h"
    #include "driverlib/pin_map.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/interrupt.h"
    #include "driverlib/gpio.h"
    
    #include "utils/uartstdio.h"
    
    /*
     * from main, needed to set up the I2C port.
     * This should be in a header.
     */
    extern uint32_t g_ui32SysClock;
    
    /*
     * Define the states for the I2C master state machine, and also the state register.
     */
    enum I2C_MASTER_STATE
    {
        I2C_OP_IDLE = 0,        // I2C master is not doing anything
        I2C_OP_TXDATA,          // send data to write to that slave.
        I2C_OP_RXDATA_SENDREG,  // send the register to read from
        I2C_OP_RXDATA_READ,     // get data read from that slave.
        I2C_OP_STOP,            // end of transaction
        I2C_ERR_STATE           // something bad happened!
    };
    
    volatile static uint8_t current_state;
    volatile static uint8_t previous_state;
    
    /*
     * Define operations to perform on the HDC2010.
     */
    enum HDC_OPERATION
    {
        HDC_OP_REGWRITE = 0,    // we want to write a value to a register
        HDC_OP_REGREAD          // we want to read a value from a register
    };
    
    static uint8_t hdc_op;      // the operation to perform in this I2C transaction
    
    /*
     * The transmit buffer is two bytes. The first is the register address, the second is the register value.
     * Perhaps I should just create two separate variables?
     */
    volatile static uint8_t txbuf[2];
    
    /*
     * The read buffer is one byte: what we read from the register.
     */
    volatile static uint8_t rxbuf;
    
    /*
     * local copy of the initialization parameters.
     * I am not sure this is necessary.
     */
    HDC2010_parameters_t hdc_p;
    
    /*
     * I2C slave address of the HDC2010 sensor.
     * This should be in a hardware.h file, not here.
     */
    #define HDC_I2C_ADDR (0x40U)
    
    /*
     * Flag set in DRDY handler, indicating we have a measurement result to read.
     */
    volatile static bool dataReady;
    
    /*
     * Conversion factors.
     */
    #define CELSIUS_PER_LSB      0.0025177F /* Degrees celsius per LSB            */
    #define RH_PER_LSB           0.0015259F /* Relative Humidity celsius per LSB  */
    
    /*
     * Initialize the HDC2010 from the provided parameters.
     */
    void HDC2010_Init(HDC2010_parameters_t *params)
    {
    
        // scope toggle setup.
        MAP_GPIOPinTypeGPIOOutput(GPIO_PORTJ_BASE, GPIO_PIN_1);
    
        /* Configure the pins I2C port attached to the HDC2010. */
        MAP_SysCtlPeripheralEnable(HDC_I2C_SYSCTL_PERIPH);
        while( !MAP_SysCtlPeripheralReady(HDC_I2C_SYSCTL_PERIPH) );
        MAP_GPIOPinConfigure(HDC_I2C_SDA_PIN_CFG);
        MAP_GPIOPinConfigure(HDC_I2C_SCL_PIN_CFG);
        MAP_GPIOPinTypeI2C(HDC_I2C_SDA_PORT, HDC_I2C_SDA_PIN);
        MAP_GPIOPinTypeI2CSCL(HDC_I2C_SCL_PORT, HDC_I2C_SCL_PIN);
    
        /* Configure the pin connected to the HDC2010's interrupt output as an interrupt input.
         * We interrupt on HDC2010's DRDY assertion. Interrupt is by default active low so enable the weak pull-up. */
        MAP_GPIOPinTypeGPIOInput(HDC_INT_PORT, HDC_INT_PIN);
        MAP_GPIOPadConfigSet(HDC_INT_PORT, HDC_INT_PIN, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);
        MAP_GPIOIntTypeSet(HDC_INT_PORT, HDC_INT_PIN, GPIO_FALLING_EDGE);
    
        /* Enable the pin interrupt. */
        MAP_GPIOIntClear(HDC_INT_PORT, HDC_INT_PIN);
        MAP_GPIOIntEnable(HDC_INT_PORT, HDC_INT_PIN);
        MAP_IntEnable(HDC_INT);
    
        /* clear the flag set in the DRDY pin interrupt */
        dataReady = false;
    
        /* initialize the I2C port as a master in Fast mode */
        MAP_I2CMasterInitExpClk(HDC_I2C_BASE, g_ui32SysClock, true);
        /* Enable interrupts for arb lost, stop, NAK, clock low timeout and data. */
        MAP_I2CMasterIntEnableEx(HDC_I2C_BASE,
                                 (I2C_MASTER_INT_ARB_LOST | I2C_MASTER_INT_STOP | I2C_MASTER_INT_NACK |
                                  I2C_MASTER_INT_TIMEOUT | I2C_MASTER_INT_DATA));
        /* Enable the I2C peripheral's interrupt. */
        MAP_IntEnable(HDC_I2C_INT);
        /* Enable the glitch filter. */
        MAP_I2CMasterGlitchFilterConfigSet(HDC_I2C_BASE, I2C_MASTER_GLITCH_FILTER_8);
    
        /*
         * Make a copy of the config regs.
         */
        hdc_p.intenablecfg = params->intenablecfg;
        hdc_p.meascfg = params->meascfg;
        hdc_p.resetdrdycfg = params->resetdrdycfg;
    
        /*
         * Initialize the HDC2010 over I2C.
         * Loading each register requires an I2C write transfer with two data bytes:
         *   1. the register address.
         *   2. data for that register.
         */
        hdc_op = HDC_OP_REGWRITE;
        txbuf[0] = HDC_INTENABLE_REG;
        txbuf[1] = hdc_p.intenablecfg;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        UARTprintf("Writing 0x80 to INTENABLE register\n");
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
            ;
    
        txbuf[0] = HDC_RICONF_REG;
        txbuf[1] = hdc_p.resetdrdycfg;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        UARTprintf("Writing 0x54 to RICONF register\n");
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
            ;
    
        txbuf[0] = HDC_MEASCONF_REG;
        txbuf[1] = hdc_p.meascfg;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        UARTprintf("Writing 0x00 to MEASCONF register\n");
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
            ;
    
        /* Read manufacturer ID from HDC2010.  */
        hdc_op = HDC_OP_REGREAD;
        txbuf[0] = HDC_MIDHIGH_REG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        UARTprintf("Reading MID register: ");
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
            ;
        UARTprintf("%x ", rxbuf);
    
        txbuf[0] = HDC_MIDLOW_REG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
            ;
        UARTprintf("%x\n", rxbuf);
    
        /* Read product ID from HDC2010.  */
        hdc_op = HDC_OP_REGREAD;
        txbuf[0] = HDC_DIDHIGH_REG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        UARTprintf("Reading DID register: ");
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
            ;
        UARTprintf("%x ", rxbuf);
    
        txbuf[0] = HDC_DIDLOW_REG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
            ;
        UARTprintf("%x\n", rxbuf);
    
        /* read back the register settings. */
        UARTprintf("READING BACK ALL REGISTERS ...\n");
        hdc_op = HDC_OP_REGREAD;
        txbuf[0] = HDC_TEMPLOW_REG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
           ;
        UARTprintf("TEMPLOW: %x\n", rxbuf);
    
        txbuf[0] = HDC_TEMPHIGH_REG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
           ;
        UARTprintf("TEMPHIGH: %x\n", rxbuf);
    
        txbuf[0] = HDC_INTRDY_REG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
           ;
        UARTprintf("INTREADY (0x04): %x\n", rxbuf);
    
        txbuf[0] = HDC_INTENABLE_REG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
           ;
        UARTprintf("INTENABLE (0x07): %x\n", rxbuf);
    
        txbuf[0] = HDC_RICONF_REG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
           ;
        UARTprintf("RICONF (0x0E): %x\n", rxbuf);
    
        txbuf[0] = HDC_MEASCONF_REG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
           ;
        UARTprintf("MEASCONF (0x0F): %x\n", rxbuf);
    
        // DONE!
        UARTprintf("HDC2010 init finished!\n");
    }
    
    
    /*
     * Start a conversion.
     * Typically, this is required only once at startup, and once initialized
     * the conversions will happen automatically.
     */
    void HDC2010_StartConversion(void)
    {
        dataReady = false; // not necessary.
        hdc_op = HDC_OP_REGWRITE;
        txbuf[0] = HDC_MEASCONF_REG;
        txbuf[1] = hdc_p.meascfg | HDC_MEASCONF_MEASTRIG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
           ;
    }
    
    /*
     * Was a conversion complete?
     */
    bool HDC2010_IsConvertDone(void)
    {
        return dataReady;
    }
    
    /*
     * Read the Interrupt/DRDY register, which also clears the DRDY interrupt flag.
     * Also clear the dataReady boolean here.
     */
    uint8_t HDC2010_ReadIntDRDY(void)
    {
        dataReady = false;
        hdc_op = HDC_OP_REGREAD;
        txbuf[0] = HDC_MEASCONF_REG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
           ;
        return rxbuf;
    }
    
    /*
     * Read the temperature.
     * Format it as degrees celsius * 0.001.
     * I would return a float but the various unsprintf() provided by TI doesn't handle float.
     */
    int32_t HDC2010_ReadTemperature(void)
    {
        uint16_t templsb;
        uint16_t tempmsb;
        uint16_t temperature;
        uint32_t retval;
        float temp;
    
        hdc_op = HDC_OP_REGREAD;
        txbuf[0] = HDC_TEMPLOW_REG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
           ;
        templsb = (uint16_t) rxbuf;
    
        hdc_op = HDC_OP_REGREAD;
        txbuf[0] = HDC_TEMPHIGH_REG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
           ;
        tempmsb = (uint16_t) rxbuf;
    
        temperature = (tempmsb << 8U) | templsb;
    
        temp = (CELSIUS_PER_LSB * ((float) temperature)) - 40.0f;
    
        // Scale temperature by 1000 so the result is actually in degrees C * 0.001.
        retval = (int32_t) (1000.0F * temp);
    
        return retval;
    }
    
    /*
     * Read the humidity.
     * Return as %RH * 0.001.
     */
    uint32_t HDC2010_ReadHumidity(void)
    {
        uint16_t humlsb;
        uint16_t hummsb;
        uint16_t hum;
        uint32_t retval;
        float humidity;
    
        hdc_op = HDC_OP_REGREAD;
        txbuf[0] = HDC_HUMLOW_REG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
           ;
        humlsb = (uint16_t) rxbuf;
    
        hdc_op = HDC_OP_REGREAD;
        txbuf[0] = HDC_HUMHIGH_REG;
        current_state = I2C_OP_IDLE;
        previous_state = I2C_OP_IDLE;
        MAP_IntTrigger(HDC_I2C_INT);
        while( current_state != I2C_OP_STOP )
           ;
        hummsb = (uint16_t) rxbuf;
    
        hum =  (hummsb << 8U) | humlsb;
    
        humidity = RH_PER_LSB * ((float) hum);
    
        // scale humidity by 1000 so the result is actually RH% * 0.001.
        retval = (uint32_t) (1000.0F * humidity);
    
        return retval;
    }
    
    /*
     * Interrupt handler for the DRDY pin.
     * This is invoked when the HDC2010 has finished a humidity and/or temperature conversion.
     * Ensure that this function is listed as the handler for the correct GPIO bank interrupt
     * in startup_ccs.c.
     * And if other pins on that bank also interrupt, then code to deal with that should be
     * added.
     */
    void HDC2010_DRDY_Handler(void)
    {
        //MAP_GPIOPinWrite(GPIO_PORTJ_BASE, GPIO_PIN_1, GPIO_PIN_1); // scope trigger
        MAP_GPIOIntClear(HDC_INT_PORT, HDC_INT_PIN);
        dataReady = true;
        //MAP_GPIOPinWrite(GPIO_PORTJ_BASE, GPIO_PIN_1, 0);           // scope trigger
    }
    
    /*
     * Interrupt handler for I2C events.
     * This implements a state machine which can handle different master operations.
     * Ensure that this function is listed as the handler for the correct I2C peripheral
     * in startup_ccs.c.
     */
    void HDC2010_I2C_Handler(void)
    {
        uint32_t intstatus;
    
        /* Get masked interrupt status and clear the flags. */
        intstatus = MAP_I2CMasterIntStatusEx(HDC_I2C_BASE, true);
        MAP_I2CMasterIntClearEx(HDC_I2C_BASE, intstatus);
    
        /* State decoder. */
        switch( current_state ) {
        case I2C_OP_IDLE:
            /*
             * Start of transaction.
             * Write the slave address.
             * If we want to write to a register, we Put the register address as the
             * outgoing data then start a burst.
             * If we want to read from a register, we have to select the register to
             * read back, so we Put that address and do a Single Send.
             */
            previous_state = current_state;
    
            if( hdc_op == HDC_OP_REGWRITE)
            {
                // Write to a register. This is a two-byte transfer: reg, reg data.
                MAP_I2CMasterSlaveAddrSet(HDC_I2C_BASE, HDC_I2C_ADDR, false);
                MAP_I2CMasterDataPut(HDC_I2C_BASE, txbuf[0]);
                MAP_I2CMasterControl(HDC_I2C_BASE, I2C_MASTER_CMD_BURST_SEND_START);
                current_state = I2C_OP_TXDATA;
            } else {
                // we want to read from a register, but we need to tell the HDC which one.
                MAP_I2CMasterSlaveAddrSet(HDC_I2C_BASE, HDC_I2C_ADDR, false);
                MAP_I2CMasterDataPut(HDC_I2C_BASE, txbuf[0]);
                MAP_I2CMasterControl(HDC_I2C_BASE, I2C_MASTER_CMD_SINGLE_SEND);
                current_state = I2C_OP_RXDATA_SENDREG;
            }
            break;
    
         case I2C_OP_TXDATA:
             previous_state = current_state;
             // The register address was sent -- was it NAKed?
             // if it was, stop.
             if( intstatus & I2C_MASTER_INT_NACK )
             {
                 // NAK, so end transfer
                 current_state = I2C_OP_STOP;
             } else if ( intstatus & I2C_MASTER_INT_STOP )
             {
                 // last byte of transfer went out, so done.
                 current_state = I2C_OP_STOP;
             }
    
             // Send the new register value, and that's the end of the burst.
             MAP_I2CMasterDataPut(HDC_I2C_BASE, txbuf[1]);
             MAP_I2CMasterControl(HDC_I2C_BASE, I2C_MASTER_CMD_BURST_SEND_FINISH);
             break;
    
         case I2C_OP_RXDATA_SENDREG:
             // Our register address was written, and we should get a STOP here.
             // We must start a new transfer to read the register value.
             previous_state = current_state;
             if( intstatus & I2C_MASTER_INT_NACK )
             {
                 // NAK, so end transfer
                 current_state = I2C_OP_STOP;
             } else if ( intstatus & I2C_MASTER_INT_STOP )
             {
                 // start a read transfer.
                 MAP_I2CMasterSlaveAddrSet(HDC_I2C_BASE, HDC_I2C_ADDR, true);
                 MAP_I2CMasterControl(HDC_I2C_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);
                 current_state = I2C_OP_RXDATA_READ;
             }
             break;
    
         case I2C_OP_RXDATA_READ:
             previous_state = current_state;
             current_state = I2C_OP_STOP;
             rxbuf = I2CMasterDataGet(HDC_I2C_BASE);
             break;
    
    
         case I2C_OP_STOP:
             // end of transfer. Whatever invoked the I2C transfer should be monitoring the current_state
             // variable for STOP to know the transaction has completed and it can continue.
             previous_state = current_state; // I think this is a placeholder.
             break;
    
         case I2C_ERR_STATE:
             // something bad happened, caller must check for this condition.
             current_state = I2C_ERR_STATE;
             break;
    
         default :
             current_state = I2C_ERR_STATE;
             break;
        } // case
    }