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.

MSP430FR5962: I2C Clock held low

Part Number: MSP430FR5962
Other Parts Discussed in Thread: MSP430FR5949

I have an MSP430FR5949 & MSP430FR5962 connected over an I2C bus, no other units on the bus & pullups present.

The I2C link is configured as multi-master, in this instance the MSP430FR5949 is acting as the master & MSP430FR5962 as the slave receiver.
The issue I see is that on a random basis, the receiver holds the clock low. This is then acted upon by the clock low timeout & the peripheral(s) reset as per the recommendations.
The timing of this appears random, difficult to measure, but something in the order of 1 in 100,000 messages fail in this way.
When the failures is observed, it is always after the seventh bit of a byte, but the byte position appears random.
This appears as though the UCBnRXBUF register has not been read and the peripheral is clock stretching as a result in the receive overflow condition.
In fact if I deliberately introduce a random non-read of the UCBnRXBUF I observe the same symptoms.

However, I'm sure that In the instance when this happens under normal circumstances, I've read & buffered (via an external callback function) the received data as I can trap this in the debugger and observe my buffered data.

This application has several other interrupts active and as a result there will be some variable latency in handling of any single interrupt, but not of such length as to be significant in relation to the clock low timeout.

I've added some GPIO pin toggle in the interrupt to observe the sequence of events in the receiver & can see the start detected interrupt, a number of receive interrupts including the one for reading the byte immediately prior to the one that hangs on the 7th bit so I'm confident that I've read the UCBnRXBUF register for the previous received byte.

I can only imagine that there is some timing or interrupt interaction which is causing this.
So my question is: are there any known issues with this operation and if not, what in detail, are the exact conditions which can trigger the receiver to enter clock stretching after receipt of the 7th bit of data?

Sorry, the I2C interrupt code is slightly messy owing to being re-used, but you get the general idea.
If this request turns up no answers, I'll have to attempt to reproduce the issue with much simpler code, but this is not something I've budgeted time for in my project.

Example capture shows initial write (with interrupt handler on CH7) followed by 2 successful rx bytes (with interrupt handlers on CH7) followed by 7 bits of a 3rd data byte causing clock low until subsequently handled. Why is the 3rd byte held-up if I've read the UCBnRXBUF for the previous 2 data bytes?

///############################################################################
/// @fn              void USCI_B0_ISR(void)
/// @param[in]       < None >
/// @return          < None >
/// @see Description ISR for eUSCIB0 on logical I2C Channel 0
/// @warning         Uses : USCI_B0
///############################################################################
#pragma vector = USCI_B0_VECTOR
_INTERRUPT_ void I2C_USCI_B0_ISR(void)
{
    UINT8 DataByte;

    /* Deduce source of interrupt */
    switch( __even_in_range(UCB0IV, USCI_I2C_UCBIT9IFG))
    {
        case USCI_NONE:
            break;

        case USCI_I2C_UCALIFG:  /* Arbitration lost interrupt */
            switch(CurrentConfig[I2C_CHAN_0].IntState)
            {

                case I2C_INT_STATE_MA_TX:
                    /* signal the result */
                    CurrentConfig[I2C_CHAN_0].TxResult = I2C_EOM_RESULT_FAIL_ARBITRATION;
                    CurrentConfig[I2C_CHAN_0].IntState = I2C_INT_STATE_IDLE;
                    break;

                case I2C_INT_STATE_MA_RX:
                    /* signal the result */
                    CurrentConfig[I2C_CHAN_0].RxResult = I2C_EOM_RESULT_FAIL_ARBITRATION;
                    CurrentConfig[I2C_CHAN_0].IntState = I2C_INT_STATE_IDLE;
                    break;

                case I2C_INT_STATE_SL_TX:
                    /* unexpected operation */
                    CurrentConfig[I2C_CHAN_0].TxResult = I2C_EOM_RESULT_UNKNOWN;
                    CurrentConfig[I2C_CHAN_0].IntState = I2C_INT_STATE_IDLE;
                    break;

                case I2C_INT_STATE_SL_RX:
                    /* unexpected operation */
                    CurrentConfig[I2C_CHAN_0].RxResult = I2C_EOM_RESULT_UNKNOWN;
                    CurrentConfig[I2C_CHAN_0].IntState = I2C_INT_STATE_IDLE;
                    break;

                default:
                    /* nothing to do */
                    break;

            } /* end  switch(CurrentConfig[I2C_CHAN_0].IntState) */
            break;

        case USCI_I2C_UCNACKIFG:    /* no ack received (master only) */
            switch(CurrentConfig[I2C_CHAN_0].IntState)
            {
                case I2C_INT_STATE_MA_TX:
                    /* generate a stop & terminate */
                    UCB0CTLW0 |= UCTXSTP;   /* generate a stop  */
                    CurrentConfig[I2C_CHAN_0].TxResult = I2C_EOM_RESULT_FAIL_NACK;
                    CurrentConfig[I2C_CHAN_0].IntState = I2C_INT_STATE_IDLE;
                    break;

                case I2C_INT_STATE_MA_RX:
                    /* generate a stop & terminate */
                    UCB0CTLW0 |= UCTXSTP;   /* generate a stop  */
                    CurrentConfig[I2C_CHAN_0].RxResult = I2C_EOM_RESULT_FAIL_NACK;
                    CurrentConfig[I2C_CHAN_0].IntState = I2C_INT_STATE_IDLE;
                    break;

                case I2C_INT_STATE_SL_TX:
                    /* unexpected operation */
                    CurrentConfig[I2C_CHAN_0].TxResult = I2C_EOM_RESULT_UNKNOWN;
                    CurrentConfig[I2C_CHAN_0].IntState = I2C_INT_STATE_IDLE;
                    break;

                case I2C_INT_STATE_SL_RX:
                    /* unexpected operation */
                    CurrentConfig[I2C_CHAN_0].RxResult = I2C_EOM_RESULT_UNKNOWN;
                    CurrentConfig[I2C_CHAN_0].IntState = I2C_INT_STATE_IDLE;
                    break;

                default:
                    /* nothing to do */
                    break;

            } /* end  switch(CurrentConfig[I2C_CHAN_0].IntState) */
            break;

        case USCI_I2C_UCSTTIFG:     /* Start condition (& own address detected if slave)
                                       only used if slave to determine operating mode for
                                       this transaction */
SET_CHANNEL7_HIGH;
            if(0U == (UCB0CTLW0 & UCMST))
            {
                /* slave mode */
                if(0U != (UCB0CTLW0 & UCTR))
                {
                    CurrentConfig[I2C_CHAN_0].IntState = I2C_INT_STATE_SL_TX;
                }
                else
                {
                    CurrentConfig[I2C_CHAN_0].IntState = I2C_INT_STATE_SL_RX;
                    CurrentConfig[I2C_CHAN_0].RxResult = I2C_EOM_RESULT_IN_PROGRESS;
                    CurrentConfig[I2C_CHAN_0].IntSlaveRxd = FALSE;
                }
            }
            /* A start means this is a new transaction */
            CurrentConfig[I2C_CHAN_0].ByteCounter = 0U;
SET_CHANNEL7_LOW;
            break;

        case USCI_I2C_UCSTPIFG:     /* Stop condition detected */
SET_CHANNEL7_HIGH;
            switch(CurrentConfig[I2C_CHAN_0].IntState)
            {
                case I2C_INT_STATE_SL_TX:   /* deliberate fall-through */
                case I2C_INT_STATE_MA_TX:
                    CurrentConfig[I2C_CHAN_0].TxResult = I2C_EOM_RESULT_OK;
                    CurrentConfig[I2C_CHAN_0].IntState = I2C_INT_STATE_IDLE;
                    break;

                case I2C_INT_STATE_SL_RX:
                    /* In slave rx mode, the stop condition physically occurs after the last byte has been received
                       BUT there is occasionally sufficient latency in the interrupt handler that
                       both the rx data & stop condition are present on entry to the interrupt handler.
                       As the stop condition has priority in the UCBxIV register used to control which
                       interrupt source is handled, we need to check for this & handle the last byte
                       before changing the IntState otherwise we'll end up setting the message received
                       condition with one byte less & ignoring the subsequent rx byte interrupt */
                    if(0U != (UCB0IFG & (UCRXIFG0 + UCRXIFG1 + UCRXIFG2+ UCRXIFG3)))
                    {
                        DataByte = UCB0RXBUF;
                        if(NULL != CurrentConfig[I2C_CHAN_0].SuppliedCfg.CfgRxByteCBPtr)
                        {
                            CurrentConfig[I2C_CHAN_0].SuppliedCfg.CfgRxByteCBPtr(DataByte, CurrentConfig[I2C_CHAN_0].ByteCounter);
                            CurrentConfig[I2C_CHAN_0].ByteCounter++;
                        }
                    }
                    /* check to see if this is a Gen Call or addressed message */
                    if(0U != (UCB0STATW & UCGC))
                    {
                        CurrentConfig[I2C_CHAN_0].RxResult = I2C_EOM_RESULT_OK_GENCALL;
                    }
                    else
                    {
                        CurrentConfig[I2C_CHAN_0].RxResult = I2C_EOM_RESULT_OK;
                    }
                    CurrentConfig[I2C_CHAN_0].IntSlaveRxd = TRUE;
                    CurrentConfig[I2C_CHAN_0].IntState = I2C_INT_STATE_IDLE;
                    break;

                case I2C_INT_STATE_MA_RX:
                    /* check to see if this is a Gen Call or addressed message */
                    if(0U != (UCB0STATW & UCGC))
                    {
                        CurrentConfig[I2C_CHAN_0].RxResult = I2C_EOM_RESULT_OK_GENCALL;
                    }
                    else
                    {
                        CurrentConfig[I2C_CHAN_0].RxResult = I2C_EOM_RESULT_OK;
                    }
                    CurrentConfig[I2C_CHAN_0].IntState = I2C_INT_STATE_IDLE;
                    break;

                default:
                    /* nothing to do */
                    break;

            } /* end  switch(CurrentConfig[I2C_CHAN_0].IntState) */
SET_CHANNEL7_LOW;
            break;

        case USCI_I2C_UCRXIFG3:     /* deliberate fall-through, Slave 3 Data received (addr idx = I2C_MY_ADDR_QUATERNARY) */
        case USCI_I2C_UCRXIFG2:     /* deliberate fall-through, Slave 2 Data received (addr idx = I2C_MY_ADDR_TERTIARY) */
        case USCI_I2C_UCRXIFG1:     /* deliberate fall-through, Slave 1 Data received (addr idx = I2C_MY_ADDR_SECONDARY) */
        case USCI_I2C_UCRXIFG0:     /* Data received (addr idx = I2C_MY_ADDR_PRIMARY) */
            /* unconditionally read data byte to avoid overflow even if we discard it */
            DataByte = UCB0RXBUF;
SET_CHANNEL7_HIGH;
            switch(CurrentConfig[I2C_CHAN_0].IntState)
            {
                case I2C_INT_STATE_SL_RX:
                    /* slave doesn't have control over number of bytes */
                    if(NULL != CurrentConfig[I2C_CHAN_0].SuppliedCfg.CfgRxByteCBPtr)
                    {
                        CurrentConfig[I2C_CHAN_0].SuppliedCfg.CfgRxByteCBPtr(DataByte, CurrentConfig[I2C_CHAN_0].ByteCounter);
                        CurrentConfig[I2C_CHAN_0].ByteCounter++;
                    }
                    break;

                case I2C_INT_STATE_MA_RX:
                    /* master controls the number of bytes & generates the terminating operation.
                       we need to signal the stop if this is the penultimate byte and we're
                       not performing auto-stop generation */
                    if((I2C_AUTO_STOP_GENERATE != CurrentConfig[I2C_CHAN_0].AutoStopType) &&
                       (CurrentConfig[I2C_CHAN_0].ByteCounter == (CurrentConfig[I2C_CHAN_0].DataLen - 2U)))
                    {
                        UCB0CTLW0 |= UCTXSTP;   /* generate a stop  */
                    }
                    /* handle the data */
                    if(NULL != CurrentConfig[I2C_CHAN_0].SuppliedCfg.CfgRxByteCBPtr)
                    {
                        CurrentConfig[I2C_CHAN_0].SuppliedCfg.CfgRxByteCBPtr(DataByte, CurrentConfig[I2C_CHAN_0].ByteCounter);
                    }
                    CurrentConfig[I2C_CHAN_0].ByteCounter++;
                    break;

                case I2C_INT_STATE_SL_TX:   /* deliberate fall-through - unexpected operation */
                case I2C_INT_STATE_MA_TX:   /* deliberate fall-through - unexpected operation */
                default:
                    /* nothing to do */
                    break;

            }   /* end switch(CurrentConfig[I2C_CHAN_0].IntState) */
SET_CHANNEL7_LOW;
            break;

        case USCI_I2C_UCTXIFG3:     /* deliberate fall-through, Slave 3 Tx buffer empty (addr idx = I2C_MY_ADDR_QUATERNARY) */
        case USCI_I2C_UCTXIFG2:     /* deliberate fall-through, Slave 2 Tx buffer empty (addr idx = I2C_MY_ADDR_TERTIARY) */
        case USCI_I2C_UCTXIFG1:     /* deliberate fall-through, Slave 1 Tx buffer empty (addr idx = I2C_MY_ADDR_SECONDARY) */
        case USCI_I2C_UCTXIFG0:     /* Tx buffer empty (addr idx = I2C_MY_ADDR_PRIMARY) */
            switch(CurrentConfig[I2C_CHAN_0].IntState)
            {
                case I2C_INT_STATE_SL_TX:
                    /* slave uses TxPending to indicate a response has been made available
                       once all data has been exhausted, if the master requests more then
                       we can only ignore it & allow the master to react to the timeout
                       as the slave will hold the clock low while waiting for more data */
                    if((TRUE == CurrentConfig[I2C_CHAN_0].TxPending) &&
                       (CurrentConfig[I2C_CHAN_0].ByteCounter < CurrentConfig[I2C_CHAN_0].DataLen))
                    {
                        /* if we're prefixing the data with our I2C address, then do it first */
                        if(TRUE == CurrentConfig[I2C_CHAN_0].TxPfixMyAddr)
                        {
                            UCB0TXBUF = CurrentConfig[I2C_CHAN_0].SuppliedCfg.CfgMyAddr;
                            CurrentConfig[I2C_CHAN_0].TxPfixMyAddr = FALSE; /* Clear this as it's once only & transmission request specific */
                        }
                        else
                        {
                            UCB0TXBUF = *(CurrentConfig[I2C_CHAN_0].TxDataPtr + CurrentConfig[I2C_CHAN_0].ByteCounter);
                            CurrentConfig[I2C_CHAN_0].ByteCounter++;
                        }
                    }
                    break;

                case I2C_INT_STATE_MA_TX:
                    /* master will have cleared the TxPending on entry to this state */
                    if(CurrentConfig[I2C_CHAN_0].ByteCounter < CurrentConfig[I2C_CHAN_0].DataLen)
                    {
                        /* if we're prefixing the data with our I2C address, then do it first */
                        if(TRUE == CurrentConfig[I2C_CHAN_0].TxPfixMyAddr)
                        {
                            UCB0TXBUF = CurrentConfig[I2C_CHAN_0].SuppliedCfg.CfgMyAddr;
                            CurrentConfig[I2C_CHAN_0].TxPfixMyAddr = FALSE; /* Clear this as it's once only & transmission request specific */
                        }
                        else
                        {

                            UCB0TXBUF = *(CurrentConfig[I2C_CHAN_0].TxDataPtr + CurrentConfig[I2C_CHAN_0].ByteCounter);
                            CurrentConfig[I2C_CHAN_0].ByteCounter++;
                        }
                    }
                    else
                    {
                        /* on transfer of the last byte of data to the shift register, the subsequent
                         TXIFG interrupt needs to set the generation of a stop bit after the next ACK
                         unless we're using auto-stop generation */
                        if(I2C_AUTO_STOP_GENERATE != CurrentConfig[I2C_CHAN_0].AutoStopType)
                        {
                            UCB0CTLW0 |= UCTXSTP;   /* generate a stop  */
                        }
                    }
                    break;

                case I2C_INT_STATE_SL_RX:   /* deliberate fall-through - unexpected operation */
                case I2C_INT_STATE_MA_RX:   /* deliberate fall-through - unexpected operation */
                default:
                    /* nothing to do */
                    break;

            }   /* end switch(CurrentConfig[I2C_CHAN_0].IntState) */
            break;

        case USCI_I2C_UCBCNTIFG:    /* Byte count threshold reached */
            /* Not currently used owing to the peripheral inherent 255 byte limitation */
            break;

        case USCI_I2C_UCCLTOIFG:    /* Clock low timeout */
            CurrentConfig[I2C_CHAN_0].IntState = I2C_INT_STATE_CLK_ERROR;
            break;

        case USCI_I2C_UCBIT9IFG:    /* 9th clock cycle per bit (ack position) except address */
            /* nothing to do */
            break;

        default:
            /* nothing to do */
            break;
    } /* end switch( __even_in_range( UCB0IV, USCI_I2C_UCBIT9IFG)) */

} /* end I2C_USCI_B0_ISR() */

  • Hi Steven,

    long code portion but some general questions:

    1. Are you using LPM if yes which ones?

    2. Which clocks are your CPU and your eUSCI is running from?

    3. Which other modules can drive interrupts? Did you try to disable all other interrupt sources?

    4. If the device (always the receive right?) stalls the bus how is it released again (via WDT or RST pin)?
        Can you describe a bit better what happens to the receiver if it goes stuck? Does any other function work or is "only" I2C which fails?

  • Dietmar,

    Apologies for long code, but interrupt is common for all multi-master I2C operations so handles Master Tx, Master Rx, Slave Tx & Slave Rx.

    1. No, not using Low Power Modes.

    2. Main clock is DCO generated at 8MHz:

    void SYS_init( void )
    {
        // Oscillator Setup
        CSCTL0_H = 0xA5;                           // Unlock register
        CSCTL1   = DCORSEL + DCOFSEL0 + DCOFSEL1;  // DCO setting
        CSCTL2   = SELA_1 + SELS_3 + SELM_3;       // ACLK = vlo,SMCLK = MCLK = DCO
        CSCTL3   = DIVA_0 + DIVS_0 + DIVM_0;       // CLK dividers
        CSCTL0_H = 0x01;                           // Lock Register
    <SNIP>

    UART clock is SMCLK:

        /* configure the eUSCIBx for I2C operation */
        /* default the eUSCI to I2C mode, 7 bit addressing, Multi-master,
          (master)clock source SMCLK(8Mhz) */
        UCB0CTLW0 = (UCMODE_3 + UCSYNC + UCMM + UCSSEL__SMCLK + UCSWRST);
    
        /* default clock low timeout 34ms, No Auto Stop Gen,
           Deglitch time 50ns(I2C specified) */
        UCB0CTLW1 = (UCCLTO0 + UCCLTO1);
    

    3. Using several interrupts:
    USCI_A0_VECTOR
    USCI_A1_VECTOR
    USCI_B0_VECTOR
    TIMER0_A0_VECTOR
    TIMER3_A0_VECTOR
    TIMER2_A0_VECTOR
    ADC12_VECTOR

    I tried removing ADC interrupt, no change to observed issue.
    All others required for module to function so can't be easily disabled.
    Further investigation would require writing of a specific test project to be able to eliminate these interrupt sources.

    4. I've Identified that it is always the slave receiver that holds clock low.
    Clock Low Timeout interrupt is configured & enabled, detection (both master & slave as code is common) causes non-interrupt state control to reset states, reset UART via UCB0CTLW0  = UCSWRST; then re-initialise the UART for I2C & continue.
    This reset process works, the current message is lost, but once reset the application continues to pass data packets until the next clock held low event approx 100,000 messages later. (application has master polling the slave receiver approx every 40ms.)

    Only the I2C comms appears to be affected, other functions continue to operate (I2C code is specifically designed to be non-blocking)

    Thanks
    Steven.

  • Also:

    This is I2C at 100kHz.

    Also tried at 400kHz with the same outcome.
    But as the rx data handling callback function called from within UCRXIFGn interrupt is not fast it can take approx same time as a rx byte at 400kHz which may lead to other issues as some clock stretching is inevitable so reverted back to original 100kHz rate.
    However, this introduction of some required clock stretching at 400kHz did not have any effect on the observed rate of the stuck low feature.

  • Hi Steven,

    thanks for information so it's good to know that "only" I2C module is affected and the device is still able to detect timeout react on it and reset the eUSCI module in way to make functional again.

    So another question are you able to record SCL and SDA for the last working cycle by triggering with the scope on SCL for timeout. Would be interested if there might be glitches on clock or data. Do you think you can check this? By the way which pull up values did you use?

  • Dietmar,

    Unfortunately I'm not in a position to re-create the condition with an oscilloscope attached at the present time, I've moved onto another critical path in the project and can't spend the non-deterministic time taken to investigate this further at present.
    I do however have the peripherals I2C glitch filter enabled at 50ns as per the I2C requirements.

    Pullups on both SDA & SCL 5k Ohms to +3V3

    Doing a search of this forum I notice that the following has some similarities, but sadly I note that the root cause was never identified, merely worked around with a custom timeout. Currently I wait for the I2C peripheral clock low timeout then reset the peripheral.
    Their speculation revolved around multiple interrupts having a race condition, which would fit with the observed timing/rate of observed issue.

    https://e2e.ti.com/support/microcontrollers/msp430/f/166/t/804617

  • Steven,

    mh... without details on the signals hard to say but I had a problem with I2C using 4.7 kOhm pull ups but with pretty long traces.

    From time to time the I2C got stuck because slave did not acknowledged. Reason was that the signals were so bad that the address was not recognized correctly. I reduced them to 1.5 kOhm and it worked. The point was there were 10 slaves and one master and we had pretty long traces.

    Maybe you can give it a trial and reduce pull ups to make them stronger making larger capacitance of the lines less critical.
    Run it again over long time and see if the stuck still happens.

  • Hi Steven,

    did you had a chance to try the other pull ups? I will keep this post open until end of the week (15th November) waiting for an update before I close it.

  • Dietmar,

    I do not currently have access to the hardware, I'll try and see if I can set up the fault condition again, but it may take a few days.

    While I'm not eliminating the pullup values, noise or poor signal shapes, it doesn't make logical sense to me as a software engineer.
    Your observed problems you stated were as a result of the slave not ACKing the data, but in my case, the slave must have successfully ACKed the previous byte(s) otherwise the master would not have continued to transmit. And the receiver has not ACKed the last byte in the case of the observed problem, as it's the receiver that is holding the clock low before the last data bit so it will never reach the ACK slot.
    This logically then is a different symptom from your observation.

    Similarly, if the slave had  not received data bits or clock bits owing to noise issues then the receiver holding the clock low would be an odd thing for it to do, especially at the same 7th bit position within a byte.

    If the receiver has seen noise on data or clock then, allowing for the enabled glitch filter, surely it would act on the data and accept it if a spurious clock were received, or possibly see a spurious start/stop condition and act accordingly. I don't believe that the receiver is doing anything out of sequence, but may be wrong.

    I'll try and rebuild the hardware and software to generate the fault and report back.

    Thanks,
    Steven.

  • Steven,

    thanks for response. I agree that what I said is not proven to be the root cause but should give some ideas based on my experience.

    Once you can better limit the conditions when the receiver goes stuck would help a lot to further discuss what might going. So looking forward for more data from your site.

  • Please do not close this yet, I'm currently rebuilding my development hardware to reproduce the fault.

    Then I will provide further information with  'scope traces & pullup values.

    Thanks.

  • Steven,

    thanks for the update will keep it open, looking forward to further information. If there are any questions during measurement don't hesitate to contact us.

  • Dietmar,

    I finally managed to perform some more tests & looks like you're correct with the pullup values being the root cause.

    I've setup the original configuration & can repeatedly trigger the same observed clock held low behaviour at approx 1 hour intervals which approximates to every 100,000 messages.
    I was incorrect in my original assumption of pullup, it turns out that the original hardware is using 10k Ohm pullups.

    Added 2k2 in parallel for approx 1k8 total and repeated the test with no other changes.
    Test has now run for excess of 30 hours without issue.

    I'm happy to accept that the value of the pullups are the cause of the observed behaviour.
    Thanks for your help.

    Steven.

  • Steven,

    great to hear that this resolved the issue. Thanks a lot also for the positive feedback highly appreciated.

**Attention** This is a public forum