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.