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() */