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.

MSP430FR2476: I2C stop condition not generated after ISR

Part Number: MSP430FR2476


Hello,

We have implemented a polling-based I2C master driver on our project. Under most conditions we get the correct behavior, but the peripheral fails to generate a stop condition after entering an ISR on the last received byte during a read (see capture below). Our function instead blocks waiting for the UCTXSTP flag to clear until we reach our timeout period. If SDA happens to be held low at the end of the final byte, then the peripheral will detect the bus to be in a busy state, causing a permanent lockup on all subsequent transactions. Attaching a debugger in this state confirms that UCTXSTP was set in firmware (see output below).

So far we have discovered two workarounds:

1. After timing out on UCTXSTP, manually toggle the serial lines as GPIO to force a stop condition. This returns the bus to an idle state and prevents a complete lockup.

2. Disable interrupts within the I2C read function. This eliminates the issue completely at the expense of ISR latency.

Our suspicion is we are violating the sequence diagram outlined in the user's guide, but it is not apparent to us. The relevant code from our driver is shown below.

#define MAX_FLAG_WAIT_MS   (uint32_t) 100
#define FLAG_WAIT_STEP_US   100
#define FLAG_WAIT_LOOPS    (uint32_t)((MAX_FLAG_WAIT_MS*1000) / FLAG_WAIT_STEP_US)
static bool _waitForFlag(uint16_t flag)
{
    uint32_t retry_count = 0;
    while(++retry_count < FLAG_WAIT_LOOPS)
    {
        if(GET_INT_FLAGS() & UCNACKIFG)
        {
            CLR_INT_FLAGS(UCNACKIFG);

            // Generate stop condition.
            HWREG16(EUSCI_BASE_ADDR + OFS_UCBxCTLW0) |= UCTXSTP;
            return false;
        }
        else if(GET_INT_FLAGS() & flag)
        {
            CLR_INT_FLAGS(flag);
            return true;
        }

        __delay_cycles(DELAY_US_TO_CYCLES(FLAG_WAIT_STEP_US));

    }
    return false;
}

// Control word wait function
// Used for checking completion of start/stop conditions
static bool _waitForCwFlag(uint16_t flag)
{
    uint32_t retry_count = 0;
    while(++retry_count < FLAG_WAIT_LOOPS)
    {
        // Wait for flag to clear to indicate operation complete
        if(!(HWREG16(EUSCI_BASE_ADDR + OFS_UCBxCTLW0) & flag))
        {
            return true;
        }

        __delay_cycles(DELAY_US_TO_CYCLES(FLAG_WAIT_STEP_US));

    }
    return false;
}

static bool _I2C_MASTER_read(uint8_t i2cAddr, void *pDst, uint16_t nBytes)
{
    ASSERT((pDst != NULL) && (nBytes > 0));

    uint8_t *pByte = pDst;

	// Reinit if peripheral locked in busy state
    if(EUSCI_B_I2C_isBusBusy(EUSCI_BASE_ADDR))
    {
        _I2C_MASTER_reinit();
    }

    // Clear any previous NACK
    HWREG16(EUSCI_BASE_ADDR + OFS_UCBxIFG) &= ~UCNACKIFG;

    // Set I2C address.
    EUSCI_B_I2C_setSlaveAddress(EUSCI_BASE_ADDR, i2cAddr);

    // Enable rx mode and generate start condition.
    HWREG16(EUSCI_BASE_ADDR + OFS_UCBxCTLW0) &= ~UCTR;
    HWREG16(EUSCI_BASE_ADDR + OFS_UCBxCTLW0) |= UCTXSTT;
    // Wait for start condition
    if(!_waitForCwFlag(UCTXSTT)) {
        return true;
    }

    for((void) 0; nBytes > 0; nBytes--)
    {
        if(nBytes == 1)
        {
            // On last byte, generate stop condition.
            HWREG16(EUSCI_BASE_ADDR + OFS_UCBxCTLW0) |= UCTXSTP;
            // Wait for stop condition
            if(!_waitForCwFlag(UCTXSTP)) {
                return true;
            }
        }
        if(!_waitForFlag(UCRXIFG0))
        {
            // Generate stop condition.
            HWREG16(EUSCI_BASE_ADDR + OFS_UCBxCTLW0) |= UCTXSTP;
            // Wait for stop condition
            _waitForCwFlag(UCTXSTP);

            return true;
        }

        // Read a byte.
        *pByte = HWREG16(EUSCI_BASE_ADDR + OFS_UCBxRXBUF);
        pByte++;
    }

    return false;
}

I2C_Master_Handler_t *I2C_MASTER_initHandler(I2C_Master_Handler_t *pI2CHdl,
        uint32_t subsysClk)
{
    ASSERT(pI2CHdl != NULL);

    EUSCI_B_I2C_initMasterParam i2cParam;

    // Select I2C peripheral.
    GPIO_setAsPeripheralModuleFunctionOutputPin(I2C_MASTER_PORT,
            I2C_MASTER_SDA_PIN, GPIO_PRIMARY_MODULE_FUNCTION);
    GPIO_setAsPeripheralModuleFunctionOutputPin(I2C_MASTER_PORT,
            I2C_MASTER_SCL_PIN, GPIO_PRIMARY_MODULE_FUNCTION);

    // Initialize I2C parameters.
    i2cParam.selectClockSource = EUSCI_B_I2C_CLOCKSOURCE_SMCLK;
    i2cParam.i2cClk = subsysClk;
    I2C_MASTER_subSysClk = subsysClk;
    i2cParam.dataRate = EUSCI_B_I2C_SET_DATA_RATE_100KBPS;
    i2cParam.byteCounterThreshold = 0;
    i2cParam.autoSTOPGeneration = EUSCI_B_I2C_NO_AUTO_STOP;

    // Configure and enable the I2C bus.
    EUSCI_B_I2C_initMaster(EUSCI_BASE_ADDR, &i2cParam);
    EUSCI_B_I2C_setMode(EUSCI_BASE_ADDR, EUSCI_B_I2C_TRANSMIT_MODE);
    EUSCI_B_I2C_enable(EUSCI_BASE_ADDR);

    CLR_INT_FLAGS(UCTXIFG0);

    return pI2CHdl;
}

  • What I think I see in your data capture is the I2C port waiting with SCL low at the end. Probably waiting for you to read RXBUF. You say this problem occurs because of an ISR. Presumably an ISR for some other device. Is that kept short? Is it doing something that could annoy the I2C port?

    The timing of setting TXSTP shouldn't matter much. If you set it while a byte is being received, a NACK and STOP will occur when that byte completes. If you set it before reading RXBUF, the NACK and STOP should happen immediately. Or so the guide says.

**Attention** This is a public forum