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.

MSP430FR2311: MSP430 acting as I2C slave clock and data both stuck low

Part Number: MSP430FR2311

I ran into a strange issue in field testing where we found a unit with our MSP430 slave pulling both SCL and SDA low.  I know this because after we removed the MSP430 slave device, both SCL and SDA returned high, so I know the MSP430 slave was driving the bus.  I'm not sure how it got into this state.  Unfortunately we didn't have a debugger with us or any way to get the device back to the lab in that state.  I've attached my I2C slave code below.  It is based off example code in the MSP430FR2311 SDK.  I just added untested code today to implement the clock low timeout so hopefully that can be a last ditch effort to at least get SCL free and I guess it will also free SDA since we reset the entire I2C module.  

A few questions:

  • Should I be doing more when the USCI_I2C_UCSTPIFG is set?  I'm thinking of how a transaction might get corrupted and the master sends a stop, but I don't reset the state machine back to receive a command address.
  • Does my clock low timeout configuration appear correct?

Thanks

void initI2C()
{
    UCB0CTLW0 = UCSWRST;                      // Software reset enabled
    UCB0CTLW0 |= UCMODE_3 | UCSYNC;           // I2C mode, sync mode
    UCB0CTLW1 |= UCCLTO_1;					  // 28ms clock low timeout
    UCB0I2COA0 = SLAVE_ADDR | UCOAEN;         // Own Address and enable					  
    UCB0CTLW0 &= ~UCSWRST;                    // clear reset register
    UCB0IE |= UCRXIE + UCSTPIE + UCCLTOIE;	  // Enable RX Interrupt, Stop Interrupt, Clock low timeout interrupt	
}

//******************************************************************************
// I2C Interrupt ***************************************************************
//******************************************************************************

#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector = USCI_B0_VECTOR
__interrupt void USCI_B0_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(USCI_B0_VECTOR))) USCI_B0_ISR (void)
#else
#error Compiler not supported!
#endif
{
  //Must read from UCB0RXBUF
  uint8_t rx_val = 0;
  switch(__even_in_range(UCB0IV, USCI_I2C_UCBIT9IFG))
  {
    case USCI_NONE:          break;         // Vector 0: No interrupts
    case USCI_I2C_UCALIFG:   break;         // Vector 2: ALIFG
    case USCI_I2C_UCNACKIFG:                // Vector 4: NACKIFG
      break;
    case USCI_I2C_UCSTTIFG:  break;         // Vector 6: STTIFG
    case USCI_I2C_UCSTPIFG:
		// SHOULD WE BE DOING MORE HERE IF WE GET A STOP??? For example, reset the state machine?  Go back to RX_REG_ADDRESS MODE?
        UCB0IFG &= ~(UCTXIFG0);
        break;         // Vector 8: STPIFG
    case USCI_I2C_UCRXIFG3:  break;         // Vector 10: RXIFG3
    case USCI_I2C_UCTXIFG3:  break;         // Vector 12: TXIFG3
    case USCI_I2C_UCRXIFG2:  break;         // Vector 14: RXIFG2
    case USCI_I2C_UCTXIFG2:  break;         // Vector 16: TXIFG2
    case USCI_I2C_UCRXIFG1:  break;         // Vector 18: RXIFG1
    case USCI_I2C_UCTXIFG1:  break;         // Vector 20: TXIFG1
    case USCI_I2C_UCRXIFG0:                 // Vector 22: RXIFG0
        rx_val = UCB0RXBUF;
        switch (SlaveMode)
        {
          case (RX_REG_ADDRESS_MODE):
              ReceiveRegAddr = rx_val;
              I2C_Slave_ProcessCMD(ReceiveRegAddr);
              break;
          case (RX_DATA_MODE):
              ReceiveBuffer[ReceiveIndex++] = rx_val;
              RXByteCtr--;

              if (RXByteCtr == 0)
              {
                  //Done Receiving MSG
                  SlaveMode = RX_REG_ADDRESS_MODE;
                  UCB0IE &= ~(UCTXIE);
                  UCB0IE |= UCRXIE;                          // Enable RX interrupt
                  I2C_Slave_TransactionDone(ReceiveRegAddr);
                  i2cFlag = 1;								 // set flag to signal main that there is a new command to process
                  __bic_SR_register_on_exit(LPM0_bits); 	// Exit LPM0 on reti
              }
              break;
          default:
              __no_operation();
              break;
        }
        break;
    case USCI_I2C_UCTXIFG0:                 // Vector 24: TXIFG0
        //switch (SlaveMode)
        //{
          //case (TX_DATA_MODE):

              UCB0TXBUF = TransmitBuffer[TransmitIndex++];
              TXByteCtr--;
              if (TXByteCtr == 0)
              {
                  //Done Transmitting MSG
                  //SlaveMode = RX_REG_ADDRESS_MODE;
                  SlaveMode = TX_REG_ADDRESS_MODE;
                  UCB0IE &= ~(UCTXIE);						 // Disable TX interrupt
                  UCB0IE |= UCRXIE;                          // Enable RX interrupt
                  I2C_Slave_TransactionDone(ReceiveRegAddr);
              }
              //break;
          //default:
              //__no_operation();
              //break;
        //}
        break;                      // Interrupt Vector: I2C Mode: UCTXIFG
	case UCCLTOIFG:
		// Reset I2C if clock stuck low (we are likely holding it low for some stupid reason...)
		UCB0CTLW0 = UCSWRST; 						// Put I2C into reset
		SlaveMode = RX_REG_ADDRESS_MODE;			// Reset State Machine
		UCB0IE &= ~(UCTXIE);						// Disable TX interrupt
        UCB0IE |= UCRXIE;                           // Enable RX interrupt
		UCB0CTLW0 &= ~UCSWRST; 						// Take I2C out of reset
		break;
    default: break;
  }
}

void I2C_Slave_ProcessCMD(uint8_t cmd)
{
    ReceiveIndex = 0;
    TransmitIndex = 0;
    RXByteCtr = 0;
    TXByteCtr = 0;
    uint16_t ui16Value;

    switch (cmd)
    {
        case (CMD_RD_ID):                // No motion intensity set point
            ui16Value = id;
            break;
        case (CMD_RD_FW):                // No motion intensity set point
            ui16Value = fw;
            break;
        case (CMD_RD_D):                // IF we are receiving data copy buffer to register
            ui16Value = d;
            break;
        case (CMD_RD_STRENGTH):                // IF we are receiving data copy buffer to register
            ui16Value = strength;
            break;
        case (CMD_RD_TEMPERATURE):                // IF we are receiving data copy buffer to register
            ui16Value = temperature;
            break;
        case (CMD_RW_D_E):                // IF we are receiving data copy buffer to register
            ui16Value = d_e;
            break;
        default:
            __no_operation();
            break;
    }

    setupRXDataMode();
    CopyArrayUINT(ui16Value, TransmitBuffer);
}

void I2C_Slave_TransactionDone(uint8_t cmd)
{
	// ALLOW WRITES TO FRAM
    //Get previous write protection setting
    uint8_t state = HWREG8(SYS_BASE + OFS_SYSCFG0_L);
    #ifdef DFWP
        uint8_t wp = DFWP | PFWP;
    #else
        uint8_t wp = PFWP;
    #endif

    #ifdef FRWPPW
        HWREG16(SYS_BASE + OFS_SYSCFG0) = FWPW | (state & ~wp);
    #else
        HWREG8(SYS_BASE + OFS_SYSCFG0_L) &= ~wp;
    #endif

    uint16_t temp = CopyTwoByteToUINT(ReceiveBuffer);

    switch (cmd)
    {
        case (CMD_RD_ID):                   
            break;
        case (CMD_RD_FW):               
            break;
        case (CMD_RD_D):                   
            break;
        case (CMD_RD_STRENGTH):                   
            break;
        case (CMD_RD_TEMPERATURE):                   
            break;
        case (CMD_RW_CNTL):                    // Control register
            if (SlaveMode == RX_REG_ADDRESS_MODE)       // IF we are receiving data copy buffer to register
            {
                cntl = temp;  // receive new value
            }
            break;  // WM
        case (CMD_RW_D_E):                    // Control register
            if (SlaveMode == RX_REG_ADDRESS_MODE)       // IF we are receiving data copy buffer to register
            {
                d_e = temp;  // receive new value
            }
            break;
        default:
            __no_operation();
            break;
    }

    //Restore previous write protection setting
    #ifdef FRWPPW
        HWREG16(SYS_BASE + OFS_SYSCFG0) = FWPW | state;
    #else
        HWREG8(SYS_BASE + OFS_SYSCFG0_L) = state;
    #endif

    if(SlaveMode != RX_REG_ADDRESS_MODE)
    {
        SlaveMode = RX_REG_ADDRESS_MODE;            // end transmit mode
    }
}

void setupRXDataMode(void)
{
	// Sets up i2c to either receive data or transmit data.  Will continue in RX mode, but also enable Transmit interrupt flag.  If TX interrupt flag there is a repeat start with the write flag set and then will transmit instead
    SlaveMode = RX_DATA_MODE;
    TXByteCtr = TYPE_1_LENGTH;
    RXByteCtr = TYPE_1_LENGTH;
    UCB0IE |= UCTXIE;                        // Enable TX interrupt
    UCB0IE |= UCRXIE;                        // Enable RX interrupt
}

  • Hi Andrew,

    Is there anything else on the I2C bus that might be causing the MSP I2C slave to get stuck in a weird state? I did a search for similar phenomena and this E2E post ( https://e2e.ti.com/support/microcontrollers/msp430/f/166/t/301670 ) seems to also have a I2C line held low issue that was being caused by another device on the I2C bus affecting the MSP430.

    Are you able to recreate the issue consistently or is it sporadic?

    • Should I be doing more when the USCI_I2C_UCSTPIFG is set?  I'm thinking of how a transaction might get corrupted and the master sends a stop, but I don't reset the state machine back to receive a command address.
      • You have the freedom to perform whatever action you want when a master sends a STOP command
    • Does my clock low timeout configuration appear correct?
      • It looks like you are setting the UCSWRST which is what is recommended in Section 24.3.7.3 of the device family user's guide so you should be OK

    Best regards,

    Matt

  • Matt,

    There were two MSP430s basically running the same I2C code, just different addresses and slightly different functionality.  I unplugged one slave and SCL and SDA still remained low.  Unplugging the second slave and it released the bus.  The only way I can explain SCL being stuck low is if the MSP430 got messed up during a clock stretch operation where it was waiting for either the read buffer to be read or transmit buffer to have data copied to it by the processor, but somehow the CPU never did so.  SCL being stuck low is the worst thing that can happen because the master can't do anything.  That is why I was asking about some flaw in my state machine.

  • Andrew,

    Makes sense! Are you not seeing the USCI peripheral get properly reset when the UCCLTOIFG interrupt is triggered? If the SCL line is stuck low I would presume that your ISR process of resetting the USCI I2C module would get the I2C link back up and running.

    Best regards,

    Matt

  • Hello,

    I have not heard back regarding this issue in over a week so I am going to assume the issue was resolved. If this was not the case please go ahead and comment back with more information regarding the current status.

    Best regards,

    Matt

  • Hi Matt,

    I think I have the clock low timeout implemented.  I was testing it and it seems just grounding  SCL does not cause the interrupt to fire.  I must first pull SDA low while SDA is high and then pull SCL low.  I'm just doing this with wires attached to the launchpad.  Is there any more documentation on how this interrupt is triggered?  The user's guide does not say too much about how exactly it works.  I appears it looks for a "start" condition first.

    Thanks

  • Hello,

    Section 24.3.7.3 of the user's guide gives a detailed description of what circumstances are required to trigger a clock low timeout interrupt:

    Best regards,

    Matt

**Attention** This is a public forum