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.

Contention between I2C and SPI in MSP430G2553

We're using the MSP430 as I2C slave on USCI B0 and SPI master on USCI A0 (there's just one master and one slave on both buses).  I2C and SPI are both interrupt-driven in our most stable code, but we were getting superfluous and overwritten bytes in the SPI buffer so we changed the SPI code to polling-driven to avoid sharing the interrupt handlers.

That stabilized the SPI connection, but now the I2C locks up sometimes.  Our tests show evidence that the I2C fails in a small proportion of the cases when an SPI transaction occurs during an I2C transaction.  The failure mode seems to be that the I2C master either transmits or requests a byte and the interrupt flag is not set on the MSP430, so the MSP430 sits there waiting for something to happen, not realizing that the I2C master is waiting for it to read or write a byte.  What's mysterious to me is that this seems to happen when I2C and SPI are active contemporaneously, even though SPI is polling-driven and doesn't share the interrupt with the I2C.  It can happen at any time during I2C transmit or receive.  Previously, when I2C and SPI shared the interrupt, the I2C connection was mostly stable (but the SPI had problems during collisions as previously mentioned).  We have been able to work around the problem by synchronizing SPI to I2C, so that they are unlikely to collide, but we would like to understand why our code is failing.  (Additionally, we have a problem where the I2C locks up independently of the SPI every few hours, hopefully working this out will help solve that).

Here is our interrupt handling code:

#pragma vector=USCIAB0TX_VECTOR
__interrupt void USCI0TX_ISR_HOOK(void)
{
    /* USER CODE START (section: USCI0TX_ISR_HOOK) */
    if (IFG2 & UCA0TXIFG)
    {
    	// Finished SPI transmission (includes SPI receive)
    	//spi_ready_cb();
    }
    if (IFG2 & UCB0TXIFG)
    {
    	// I2C transmit request from master
    	i2c_tx_request_cb();
    }
    else if (IFG2 & UCB0RXIFG)
    {
    	// I2C receive data from master
    	i2c_byte_received_cb(UCB0RXBUF);
    }
    /* USER CODE END (section: USCI0TX_ISR_HOOK) */
}

#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCI0RX_ISR_HOOK(void)
{
    /* USER CODE START (section: USCI0RX_ISR_HOOK) */
    // This is the I2C state change vector.  The I2C Tx/Rx interrupts are handled in the USCI0TX vector.
    if (UCB0STAT & UCSTTIFG) {
    	// start condition
    	i2c_packet_start_cb();
    }
    else if (UCB0STAT & UCSTPIFG) {
    	// stop condition
    }
    else if (UCB0STAT & UCNACKIFG) {
    	// no ack (master only I think, this should be an error)
		IFG2 &= ~UCA0TXIFG;
    }
    else if (UCB0STAT & UCALIFG) {
    	// arbitration lost
    	error(INVALID_I2C_STATE);
    }
    // clear all the state change interrupt flags
    UCB0STAT &= ~(UCSTPIFG | UCSTTIFG | UCNACKIFG | UCALIFG);
    /* USER CODE END (section: USCI0RX_ISR_HOOK) */
}

Here's the polling function that handles the SPI (this is a copy-paste from the old interrupt handler, wrapped in a conditional):

static void _check_incoming_data()
{
	if (SPI_TX_FLAG_SET())
	{
		CLEAR_SPI_FLAGS();
		if (_last_idx_transmitted > 0)
		{
			_spi_buffer[_last_idx_transmitted] = UCA0RXBUF; // retrieve response to sent byte
		}

		// transmit next byte (if there is one)
		_last_idx_transmitted++;
		if (_spi_done())
		{
			/// @todo We can check if the last byte has completed by switching the SPI interrupt from Tx to Rx.
			// conclude the transaction
			_delay_cycles(48); // 3 SPI bits at 1 Mbps and 16 Mhz clock frequency
			END_SPI();
		}
		else
		{
			// there's more to send
			UCA0TXBUF = _spi_buffer[_last_idx_transmitted];
		}
	}
}

Here's our interrupt handler to receive data over I2C:

inline void i2c_byte_received_cb(uint8_t data)
{
	uint8_t crc;
	if (_rx_buffer_cursor == 0)
	{
		_invalid_req = true;
		// this is the first byte of a new message, figure out how many bytes to expect.
		switch(data)
		{
		case I2C_MSG_1 : _rx_message_length = sizeof(i2c_msg1_t); break;
		case I2C_MSG_2 : _rx_message_length = sizeof(i2c_msg2_t); break;
		case I2C_MSG_3 : _rx_message_length = sizeof(i2c_msg3_t); break;
		case I2C_MSG_4 : _rx_message_length = sizeof(i2c_msg4_t); break;
		case I2C_MSG_5 : _rx_message_length = sizeof(i2c_msg5_t); break;
		default:
			NACK();
			error(INVALID_COMMAND);
			return;
		}
	}
	if (_rx_buffer_cursor < sizeof(_rx_buffer))
	{
		_rx_buffer[_rx_buffer_cursor] = data;
		_rx_buffer_cursor++;
	}
	else
	{
		NACK();
		_invalid_req = true;
		error(COMMAND_TOO_LONG);
		return;
	}
	if (_rx_buffer_cursor == _rx_message_length - 1)
	{
		// just received the CRC byte, so verify it right away and set NACK if it doesn't work out.
		crc = compute_crc8(_rx_buffer, _rx_message_length - 1 - 1);	// yeah, yeah, we're doing a CRC in an ISR.
		if (crc == _rx_buffer[_rx_buffer_cursor - 1])
		{
			// copy the rx buffer into the double-buffer so that we can keep receiving data.
			memcpy(_rx_double_buffer, _rx_buffer, sizeof(_rx_buffer));
			//_bytes_received = _rx_message_length;
			_new_msg_ready = true;
		}
		else
		{
			NACK();
			_tx_message_length = 0;
			_num_crc_failures++;
		}
	}
	else if (_rx_buffer_cursor == _rx_message_length)
	{
		_invalid_req = false;
	}
}

And the interrupt handler for writing to I2C:

inline void i2c_tx_request_cb()
{
	if (_tx_buffer_cursor > sizeof(_tx_buffer))
	{
		UCB0TXBUF = 0xFF;
		error(INTERNAL_ERROR);
	}
	if (_tx_buffer_cursor < _tx_message_length && !_invalid_req)
	{
		UCB0TXBUF = _tx_buffer[_tx_buffer_cursor];
		_tx_buffer_cursor++;
	}
	else
	{
		// Not sure why we're getting an extra Tx request interrupt, but not writing to the buffer
		// doesn't seem to break anything.

		IFG2 &= ~UCB0TXIFG;
		P2OUT ^= BIT4;	// toggle a GPIO
		// right now we're initiating a read over SPI at the end of an I2C message.
		temperature_initiate_read();
	}
}

Here's an example where an SPI/I2C collision doesn't result in the I2C stalling:

And here are some examples where the I2C stalls during a collision:

  • Hey Neil,

    Have you looked into the errata for this device yet?  On the G2553 Erratasheet, take a look at USCI29.  I don't know for sure that this is root cause, but it is worth taking a look.  Also, I don't think you are implementing the workaround for USCI30.  This might be related to the issue you are seeing when SPI is not running.  Again, not sure, but this one definitely needs to be fixed, or you WILL run into issues eventually.

    Just a couple of things to check before diving really deep into your code.


    Mike

  • Thanks for your reply. We did implement the workaround for USCI29 but I could see USCI30 being a problem here. I'll fix that up and get back to you.
  • It looks a little better after moving the read of UCB0RXBUF to the start of the ISR. It still fails, but after ~1000 seconds instead of after ~200 seconds, and in my tests it has only failed after receiving the first data byte of the command (i.e. it doesn't ack the second byte) instead of failing at an arbitrary point in the transaction. Progress!
  • And just after I post that, it fails after 48 seconds on the third byte of received data.


    Edit: and after that, a failure after 160 seconds on the third byte of the transmitted reply.

  • Hi Neil,

    Sounds like you are trying to use workaround A for USCI30 (read the UCBxRXBUF promptly after an UCBxRXIFG).  Can you try implementing workaround B (stall the bus until after the critical timing has passed).  It is important to note that for the workaround to function as desired, you need to continually check UCSCLLOW during the >3x BitClock time period, to ensure that the UCSSCLLOW flag has been set for the entire >3x BitClock time period.  If, while checking continuously, UCSSCLLOW is observed to be low, you need to wait until it gets set high again, then restart the >3x BitClock time period, still checking continuously.

    The purpose of this is to make the device stall the I2C bus (after the 7th bit of the next byte comes over the bus) before reading UCBxRXBUF.  This guarantees the critical time window has passed before reading UCBxRXBUF.  You will be able to observe the stall on your scope trace, as every byte that the 430 is a receiver for will be stalled (after the first data byte).  Note that the last byte of the transaction should not have the workaround implemented, as you will never get to a point where UCSCLLOW will stay set for >3x BitClock cycles.

    Mike

  • I've implemented fix b for erratum USCI30 in the interrupt handler, as follows:

    #pragma vector=USCIAB0TX_VECTOR
    __interrupt void USCI0TX_ISR_HOOK(void)
    {
        /* USER CODE START (section: USCI0TX_ISR_HOOK) */
    	static bool next_byte_is_last_byte = false;
    	uint16_t start_time;
        if (IFG2 & UCA0TXIFG)
        {
        	// Finished SPI transmission (includes SPI receive)
        	//IFG2 &= ~UCA0TXIFG;
        	//spi_ready_cb();
        }
        if (IFG2 & UCB0TXIFG)
        {
        	// I2C transmit request from master
        	//IFG2 &= ~UCB0TXIFG;
        	i2c_tx_request_cb();
        }
        else if (IFG2 & UCB0RXIFG)
        {
        	// I2C receive data from master
        	//IFG2 &= ~UCB0RXIFG;
        	if (!next_byte_is_last_byte)
        	{
        		while (UCB0STAT & UCSCLLOW == 0);
        		start_time = TA1R;
        		// make sure USCSCLLOW is high for more than 3 I2C bit rate cycles (i.e. 30 microseconds, or 60 cycles at 2 MHz)
        		while (TA1R - start_time <= 80)
        		{
        			if (UCB0STAT & UCSCLLOW == 0)
        			{
        				start_time = TA1R;
        			}
        		}
        	}
        	next_byte_is_last_byte = i2c_byte_received_cb(UCB0RXBUF);
        }
        /* USER CODE END (section: USCI0TX_ISR_HOOK) */
    }

    My tests so far have run for a little longer before dying, and the failure mode has been more consistent (I2C stops transmitting the byte after an SPI transaction starts during the slave->master message), but I've also seen I2C die in the middle of an SPI transaction during the master->slave transmission once or twice.  In any case, I2C still stalls during some collisions with SPI even though SPI is not interrupt driven.

**Attention** This is a public forum