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:




