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.

MSP430 SPI RX Interrupt doesn't run after last byte

Other Parts Discussed in Thread: MSP430F5521

MCU: MSP430F5521

I am using SPI on USCIB0 to send bytes to an MCP2515 CAN controller.  Chip select is pulled low by a function to initialize the SPI transaction.  The Tx interrupt loads bytes from a software buffer into the UCB0TXBUF, and the Rx interrupt loads bytes from the UCB0RXBUF into the software buffer.  Additionally, once all bytes have been read, the Rx interrupt sets Chip select to high and indicates to software that the transaction has been completed.  Note I am also using UART on USCIA0.

About 10% of the time with this specific slave and USCIB0 configuration, the Rx interrupt does not fire after the last byte has been transmitted.  When firmware waits for the SPI transaction to close when this occurs, I see that UCB0IE has the Rx interrupt enabled, UCB0IFG has the Tx interrupt flag set, UCB0STAT indicates SPI is not busy, but the buffer indicates that the last byte has not been read yet.  There shouldn't be concurrency issues since only the transaction done flag is read by firmware while waiting for the transaction to complete.

Function to initialize transaction:

/* SM loads SPI datastructure, start transaction
 * tx_bytes: array of bytes to send
 * num_bytes: number of bytes to send
 * brd: Board to access (0,1,2)
 */
void init_CAN_SPI_transac(uint8_t *tx_bytes, uint8_t num_bytes){
	CAN_SPI_data.in_use_flag = 1;
	uint8_t i;
	for(i = 0; i < num_bytes; i++){			//Copy Tx data to SPI datastructure
		CAN_SPI_data.tx_bytes[i] = tx_bytes[i];
	}
	CAN_SPI_data.num_bytes = num_bytes;			//Copy Transaction size to SPI datastructure
	CAN_SPI_data.tx_ptr = 0;					//Reset pointers
	CAN_SPI_data.rx_ptr = 0;
	CAN_SPI_CS_ASSERT;
	CAN_SPI_INT_ENABLE;	//Enable SPI Interrupts
	return;
}

Function to end transaction:

/* SM gets recieved data, releases SPI datastructure
 * rx_data: buffer to store recieved data
 */
uint8_t get_CAN_SPI_rx_data(uint8_t *rx_data){
	uint8_t i;
	for(i = 0; i < CAN_SPI_data.num_bytes; i++){	//Copy data
		rx_data[i] = CAN_SPI_data.rx_bytes[i];
	}
	CAN_SPI_INT_DISABLE;
	CAN_SPI_data.data_ready = 0;
	CAN_SPI_data.in_use_flag = 0;						//Release SPI datastructure
	return CAN_SPI_data.num_bytes;
}

Interrupt:

/* CAN SPI USCIB0 Interrupt Handler
 * SPI Rx:
 * SPI Tx:
 */
#pragma vector=USCI_B0_VECTOR
__interrupt void USCIB0_ISR(void){
	if((UCB0IE & UCRXIE) && (UCB0IFG & UCRXIFG)){				//SPI Rxbuf full interrupt
		CAN_SPI_data.rx_bytes[CAN_SPI_data.rx_ptr] = UCB0RXBUF;	//Get latest byte from HW
		CAN_SPI_data.rx_ptr++;									//Flag reset with buffer read
		if(CAN_SPI_data.rx_ptr >= CAN_SPI_data.num_bytes){		//Done reading data
			CAN_SPI_CS_DEASSERT;									//Disable CS and disable interrupt
			CAN_SPI_RXINT_DISABLE;
			CAN_SPI_data.data_ready = 1;
		}
	}
	if((UCB0IE & UCTXIE) && (UCB0IFG & UCTXIFG)){
		UCB0TXBUF = CAN_SPI_data.tx_bytes[CAN_SPI_data.tx_ptr];	//Load next byte into HW buffer
		CAN_SPI_data.tx_ptr++;								//Flag reset with buffer write
		if(CAN_SPI_data.tx_ptr >= CAN_SPI_data.num_bytes){		//Done transmitting data
			CAN_SPI_TXINT_DISABLE;							//Disable Tx interrupt
		}
	} /*else {
		issue_warning(WARN_USCIB0_INT_ILLEGAL_FLAG);
	}*/
}

Relevant macros:

/* SPI Macros for CS */
#define CAN_SPI_BUF_SIZE 32
#define CAN_SPI_CS_ASSERT            (P2OUT &= ~BIT3)
#define CAN_SPI_CS_DEASSERT         (P2OUT |= BIT3)
#define CAN_SPI_TXINT_ENABLE        (UCB0IE |= UCTXIE)
#define CAN_SPI_TXINT_DISABLE       (UCB0IE &= ~UCTXIE)
#define CAN_SPI_RXINT_ENABLE        (UCB0IE |= UCRXIE)
#define CAN_SPI_RXINT_DISABLE       (UCB0IE &= ~UCRXIE)
#define CAN_SPI_INT_ENABLE                (UCB0IE |= (UCTXIE+UCRXIE))
#define CAN_SPI_INT_DISABLE                (UCB0IE &= ~(UCTXIE+UCRXIE))

CAN_SPI datastructure:

struct CAN_SPI_data_struct{
    uint8_t tx_bytes[CAN_SPI_BUF_SIZE];    //Bytes to send to slave
    uint8_t rx_bytes[CAN_SPI_BUF_SIZE];    //Bytes recieved from slave
    uint8_t tx_ptr;                    //Index of byte to transmit next
    uint8_t rx_ptr;                    //Index of next available empty space for recieved byte
    uint8_t num_bytes;                //Number of bytes to recieve/transmit
    uint8_t in_use_flag;            //Indicates if data has not been processed by main State machine
    uint8_t data_ready;                //Indicates that rx_bytes is valid
};
extern volatile struct CAN_SPI_data_struct CAN_SPI_data;

The main application code calls init_CAN_SPI_transac() in one state, then waits for CAN_SPI_data.data_ready to be set, after which it calls get_CAN_SPI_rx_data().  During a hang condition, the CAN_SPI_data.rx_ptr = 15 and CAN_SPI_data.tx_ptr = 16, indicating the rx interrupt has not been entered after recieving the last byte.  

A screenshot of the transaction.  SCLK and data are correct, though CS doesn't return to high after the transaction.

  • Nishant,
    It is not readily apparent as to what the issue is. I will continue to investigate to determine possible leads. Could you also provide a screen shot of a passing case (the 90% of time) where the CS returns high for comparison?

    Regards,
    Chris
  • This structure -- testing both RXIFG and TXIFG -- is hazardous. Depending on the instruction sequence generated by the compiler, and anything else (unrelated interrupts) going on in the system, it is not unlikely to get RX overruns.Overruns not only lose data, they cause your software to lose count.

    Consider: after the first write to TXBUF,  TXIFG is set immediately. If you get back to the ISR before the byte is sent (UCBUSY=0), TXIFG will be set (but not RXIFG). At that point you're off-by-one for the rest of the transaction.


    The solution, perhaps a bit counter-intuitive, is to drive everything off of RXIFG. When you get RXIFG, go ahead and write the TXBUF; since SPI Tx/Rx are (by definition) in lockstep, when you get an RXIFG the TXBUF must be empty.

    This leaves you with getting things started, but in your case you have a spot in your transac function where you know the SPI is idle and thus TXBUF is empty, so just write the first byte there.

  • Hi Chris,


    Thanks for offering to help.  This is the normal transaction.  Ignore the values of the last 8 bytes in the transaction as those are uninitialized data bytes. 

  • Hi Bruce,

    Thanks for your help. My understanding is that RXIFG is cleared when RXBUF is read, and TXIFG is cleared when TXBUF is read (as opposed to when the flag is read). Then in the case described, the interrupt will run to clear TXIFG, then when the first byte is received, the interrupt will run again to clear both RXIFG and TXIFG, possibly simultaneously. Since there are separate counters for rx and tx bytes (CAN_SPI_data.rx_ptr and CAN_SPI_data.tx_ptr) which are only incremented in the respective sections of the interrupt, the rx side of the transaction wont increment if only TXIFG is set, and vice versa. CS is only dependent on CAN_SPI_data.rx_ptr.
  • Nishant Pol said:
    TXIFG is cleared when TXBUF is read

    TXIFG is cleared when...

    1. you clear it by software or
    2. if you copy a data byte into the TX buffer and it is not moved to the transmit shift register because another byte is currently being shifted out.

    Dennis

  • Sorry, yes I meant when TXBUF was written to when originally empty (case 2). I don't write to UCB0IFG as both the UCB0RX and UCB0TX interrupts are cleared by reading/writing buffers correspondingly.
  • Update:
    Reducing the SPI clock to 100kHz (initially 1MHz) seems to fix the issue (no issues after 43 transactions). However, the same original code works fine at 1MHz data rate with a different slave (LDC1000) and different hardware setup.
  • >then when the first byte is received, the interrupt will run again to clear both RXIFG and TXIFG

    But if the ISR is delayed (due to something else in the system) the second byte in the pipeline will arrive and overrun the RXBUF. This is the hazard.

    In any case, the symptom you described is consistent with an Rx overrun, whatever the cause. It may be worth a couple of lines of code in the ISR to just count the number of times the OE bit is set. (Be sure to check OE first, since reading RXBUF clears it.)


    The USCI moves the Rx shift register contents into RXBUF after the final clock of the (current) byte, effectively providing 1.875 Rx FIFO bytes vs 2 Tx FIFO bytes.

    But that's not the only way it could have been done. I know of two different SPI units from [other guys] which allow a 2nd byte to sit in the Rx shift register (if the RXBUF still holds a 1st byte) up to the moment that the first clock of the 3rd byte ticks. That provides a size-2 Rx FIFO, so the structure you're using would work fine.

  • Yes, confirmed that OE in UCB0STAT is set when the fault happens. Thank you for your help.

**Attention** This is a public forum