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.

MSP432P401R: I2C stuck waiting for tx interrupt flag or start condition flag

Part Number: MSP432P401R

I am using Driverlib to program my MSP432 as an I2C master to interact with a sensor. To be configured correctly the sensor requires a few registers to be set. When attempting to start sending bytes with the I2C_masterSendMultiByteStart function, it gets stuck in the loop that "Poll[s] for transmit interrupt flag and start condition flag." Relevant portions of my code are below.

The I2C_MasterConfig Struct:

/* I2C Master Configuration Parameter */
const eUSCI_I2C_MasterConfig i2cConfig =
{
        EUSCI_B_I2C_CLOCKSOURCE_SMCLK,          // SMCLK Clock Source
        3000000,                                // SMCLK = 3MHz
        EUSCI_B_I2C_SET_DATA_RATE_100KBPS,      // Desired I2C Clock of 100khz
        0,                                      // No byte counter threshold
        EUSCI_B_I2C_NO_AUTO_STOP                // No Autostop
};

main begins setting clock frequencies, GPIOs, and configuring I2C on eUSCIB0. BME280_setup() is entered, and is there the trouble is.

int main(void)
{
    /* Disabling the Watchdog  */
    MAP_WDT_A_holdTimer();

    CS_setDCOCenteredFrequency(CS_DCO_FREQUENCY_12);                           // DCO to 12MHz
    MAP_CS_initClockSignal(CS_SMCLK, CS_DCOCLK_SELECT, CS_CLOCK_DIVIDER_4);   // HSMCLK to 3 MHz

    MAP_GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P1,               // P1.6 and P1.7 in I2C
            GPIO_PIN6 + GPIO_PIN7 + GPIO_PIN2 + GPIO_PIN3,                     // P1.2 and P1.3 in UART
            GPIO_PRIMARY_MODULE_FUNCTION);

    memset(BME280_RXTrimmingData, 0x00, TRIMMING_PARAM_BYTES);
    memset(BME820_RXData, 0x00, BME280_REC_BYTES);


    /* I2C Setup */
    MAP_I2C_initMaster(EUSCI_B0_BASE, &i2cConfig);                             // init I2C B0
    MAP_I2C_setSlaveAddress(EUSCI_B0_BASE, BME280_I2C_ADDRESS);                // set BME slave address
    MAP_I2C_setMode(EUSCI_B0_BASE, EUSCI_B_I2C_TRANSMIT_MODE);                 // set transmit mode to initially set sensor register values
    MAP_I2C_enableModule(EUSCI_B0_BASE);                                       // enable I2C to start operations
    MAP_Interrupt_enableInterrupt(INT_EUSCIB0);                                // enable I2C interrupt

    BME280_setup();

Here is void BME280_setup():

void BME280_setup()
{
    // set slave address and set to transmit mode
    MAP_I2C_setSlaveAddress(EUSCI_B0_BASE, BME280_I2C_ADDRESS); // BME280_I2C_ADDRESS defined as ((uint_fast16_t)0x77), consistent with the sensor's datasheet.
    MAP_I2C_setMode(EUSCI_B0_BASE, EUSCI_B_I2C_TRANSMIT_MODE);

    // set CONFIG - Disable IIR filtering
    MAP_I2C_masterSendMultiByteStart(EUSCI_B0_BASE, BME280_CONFIG_ADDR); // BME280_CONFIG_ADDR defined as ((uint8_t)0xF5), consistent with the sensor's datasheet
    MAP_I2C_masterSendMultiByteNext(EUSCI_B0_BASE, BME280_CONFIG_FILTER_OFF);

    // set CTL_MEAS - Pressure, Temperature 1 x oversample, Forced Mode
    MAP_I2C_masterSendMultiByteNext(EUSCI_B0_BASE, BME280_CTL_MEAS_ADDR);
    MAP_I2C_masterSendMultiByteNext(EUSCI_B0_BASE, BME280_CTL_MEAS_FORCED);

    // set CTL_HUM - Humidity 1 x oversample
    MAP_I2C_masterSendMultiByteNext(EUSCI_B0_BASE, BME280_CTL_HUM_ADDR);
    MAP_I2C_masterSendMultiByteFinish(EUSCI_B0_BASE, BME280_HUM_OS_1);

    /* Making sure the last transaction has been completely sent out */
    while (MAP_I2C_masterIsStopSent(EUSCI_B0_BASE));

    // get trimming parameters
    BME280_read_trimming_parameters();
}

MAP_I2C_masterSendMultiByteStart() is the driverlib function from i2c.c:

void I2C_masterSendMultiByteStart(uint32_t moduleInstance, uint8_t txData)
{
    //Store current transmit interrupt enable
    uint16_t txieStatus = EUSCI_B_CMSIS(moduleInstance)->IE & EUSCI_B_IE_TXIE0;

    //Disable transmit interrupt enable
    BITBAND_PERI(EUSCI_B_CMSIS(moduleInstance)->IE, EUSCI_B_IE_TXIE0_OFS) = 0;

    //Send start condition.
    EUSCI_B_CMSIS(moduleInstance)->CTLW0 |= EUSCI_B_CTLW0_TR
            + EUSCI_B_CTLW0_TXSTT;

    //Poll for transmit interrupt flag and start condition flag.
    while (BITBAND_PERI(EUSCI_B_CMSIS(moduleInstance)->CTLW0,
                EUSCI_B_CTLW0_TXSTT_OFS)
                || !BITBAND_PERI(EUSCI_B_CMSIS(moduleInstance)->IFG,
                        EUSCI_B_IFG_TXIFG0_OFS));

    //Send single byte data.
    EUSCI_B_CMSIS(moduleInstance)->TXBUF = txData;

    //Reinstate transmit interrupt enable
    EUSCI_B_CMSIS(moduleInstance)->IE |= txieStatus;
}

That while loop is where it is getting stuck. But if it is waiting for a tx interrupt flag, two statements ago the tx IE was disabled. So how is that consistent with expecting the flag to be raised? Either way, the while loop should also terminate when the start condition flag is raised. The previous statement supposedly sends the start condition, but somehow that is not raising the start condition flag? It seems like there is something wrong in my configuration of eUSCIB0, but I'm not seeing where it differs from what is described in the docs.

Thanks in advance!

  • What sort of board is the BME280 packaged in? Specifically, how are CSB and SDO configured? 

    Having the code stall at that particular point suggests something on the physical bus which prevents the I2C unit from properly manipulating SCL. 

  • I have CSB tied to VCC, which selects I2C interfact (if CSB gets pulled low, SPI is selected). SDO controls the slave address. If it is pulled to VCC (as I have it) the address is 0x77, if pulled low, it is 0x76.

    I watched SDA on a scope and it was constantly low. Never went high after I2C was configured.

  • If SDA is low before your program starts (the I2C unit isn't touching the bus) then either (a) there's something wrong with the pullups or (b) the slave is pulling it low.

    I expect you already checked (a), so my guess is (b). Do you have a way of resetting the BME280 (maybe disconnect its VCC pin)? It's possible it's still stuck from an earlier run.

  • Cycling the power does do the trick to get SDA to go high. But only if Vcc is disconnected only from the sensor. Cycling the entire system power doesn't work. So that's a bit odd. Looks like there's a particular instruction I'm calling that is causing SDA to go low, and it persists that way unless the sensor is cycled while the MSP432 is still running. 

    BME280_read_trimming_parameters() is the source of that issue. In particular the line sending the multibyte start.

    void BME280_read_trimming_parameters()
    {
        BME280_first_trimming_readout_in_progress = true;                   // 2 blocks of registers need to be read
        BME280_second_trimming_readout_in_progress = true;
        MAP_I2C_setMode(EUSCI_B0_BASE, EUSCI_B_I2C_RECEIVE_MODE);           // put in receive mode
        MAP_I2C_masterSendMultiByteStart(EUSCI_B0_BASE, BME280_CALIB00);    // establish the inital register to read   <- SDA goes low here!
        // initiate the read
        xferIndex = 0;
        BME280_initiate_read();
    
        while(BME280_first_trimming_readout_in_progress);                   // wait for registers to be read
    
        MAP_I2C_masterSendMultiByteStart(EUSCI_B0_BASE, BME280_CALIB26);    // establish the inital register to read
        BME280_initiate_read();
    
        while(BME280_second_trimming_readout_in_progress);                  // wait for registers to be read
    
        // perform the required operations to convert the register values to the P/H/T compensation parameters

    BME280_initiate_read() is below:

    void BME280_initiate_read()
    {
        MAP_I2C_masterReceiveStart(EUSCI_B0_BASE);                          // start reading
        MAP_I2C_enableInterrupt(EUSCI_B0_BASE,
                                EUSCI_B_I2C_RECEIVE_INTERRUPT0);            // enable RX interrupt
    }

    BME280_CALIB00 is

    #define BME280_CALIB00              ((uint8_t)0x88)     // as per the BME280 data sheet register mappings

    Am I missing (or inserting) something here?

  • After you do the SendMultiByteStart, you should wait for the byte to be sent before issuing the (repeated) Start for the read, otherwise it may get lost (particularly since your CPU is running pretty fast). This would look something like:

    > while (!I2C_getInterruptStatus(EUSCI_B0_BASE, EUSCI_B_I2C_TRANSMIT_INTERRUPT0)) /*EMPTY*/; // Wait for Tx byte to be sent

    Alternatively, you could do a Stop at the end of the Tx, and a (standard) Start for the read. [This is OK per BME280 data sheet Sec 6.2.2]. This would look something like:

    > I2C_masterSendSingleByte(EUSCI_B0_BASE, BME280_CALIB00);

    This has the advantage of having the library do all the bookkeeping. The cost is one extra I2C bus cycle (10usec), which for most purposes is pretty cheap.

    There's also a I2C_masterReceiveSingleByte(), if you're just working with single-byte reads. If you employ the pair, you can avoid an ISR entirely.

    [Edit: Oops, left out the "!"]