cancel
Showing results for 
Search instead for 
Did you mean: 

Optimizing SPI ISRs for high speed transfers

jakeforsberg
Associate II
Posted on February 20, 2014 at 21:18

I'm losing bytes during SPI receive transactions, and I believe it's an optimization problem in my interrupt service routine. I'd love some input from those with more experience in high data rate applications.

My application implements a full-duplex slave to an external SPI master device. In this case, the slave is the STM32F3 and the master is a beaglebone.

The frames are variable size, and there's no issues with small transfers. However, I see bytes lost with packets larger than 60 or 70 bytes. I assumed I was simply missing the first or last byte, but generating a counting sequence frame from the master, I can see with GDB that the received frame is missing bytes throughout the frame, typically in middle region.

The beaglebone master has a 16MHz SPI clock.

The slave is using an external clock source and PLL to run at 72MHz, with both PCLCKs running at 36MHz.

I use an external interrupt to detect the active chip select and a while loop in the SPI ISR to receive each byte while the FIFO reception level bits in the status register indicate the Rx FIFO is full.

My main questions are (1) have I implemented my receive ISR in a dumb way, (2) is there a good practice approach to optimizing the SPI slave interrupt routing, and (3) is the master clock just too fast for my slave configuration?

/**
* @brief Initialize SPI interface.
*/
void spi_init()
{
/* Init structure declaration */
SPI_InitTypeDef spi_init_struct;
NVIC_InitTypeDef nvic_init_struct;
DMA_InitTypeDef dma_init_struct;
EXTI_InitTypeDef exti_init_struct;
/* Enable peripheral clocks for SPI and EXTI chip select */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
/* Initialize SPI peripheral */
SPI_I2S_DeInit(SPI1);
SPI_StructInit(&spi_init_struct);
spi_init_struct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
spi_init_struct.SPI_Mode = SPI_Mode_Slave;
spi_init_struct.SPI_DataSize = SPI_DataSize_8b;
spi_init_struct.SPI_CPOL = SPI_CPOL_Low;
spi_init_struct.SPI_CPHA = SPI_CPHA_2Edge;
spi_init_struct.SPI_NSS = SPI_NSS_Hard;
spi_init_struct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
spi_init_struct.SPI_FirstBit = SPI_FirstBit_MSB;
spi_init_struct.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &spi_init_struct);
/* Configure the nvic Priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* Initialize NVIC for SPI */
nvic_init_struct.NVIC_IRQChannel = SPI1_IRQn;
nvic_init_struct.NVIC_IRQChannelPreemptionPriority = 0;
nvic_init_struct.NVIC_IRQChannelSubPriority = 0;
nvic_init_struct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic_init_struct);
/* Initialize the FIFO threshold */
SPI_RxFIFOThresholdConfig(SPI1, SPI_RxFIFOThreshold_QF);
/* Enable SPI */
SPI_Cmd(SPI1, ENABLE);
/* Initialize the chip select external input line for the SPI slave peripheral */
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource15);
exti_init_struct.EXTI_Line = EXTI_Line15;
exti_init_struct.EXTI_Mode = EXTI_Mode_Interrupt;
exti_init_struct.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
exti_init_struct.EXTI_LineCmd = ENABLE;
EXTI_Init(&exti_init_struct);
/* Initialize NVIC struct for EXTI */
nvic_init_struct.NVIC_IRQChannel = EXTI15_10_IRQn;
nvic_init_struct.NVIC_IRQChannelPreemptionPriority = 3;
nvic_init_struct.NVIC_IRQChannelSubPriority = 1;
nvic_init_struct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic_init_struct);
}
/**
* @brief This interrupt handler detects rising and falling edges of the slave select line.
*/
void EXTI15_10_IRQHandler()
{
/* Handle the external interrupt for SPI chip select (from beaglebone) */
if (EXTI_GetITStatus(EXTI_Line15) == SET)
{
/* Clear pending bit */
EXTI_ClearITPendingBit(EXTI_Line15);
if (GPIOA->IDR & GPIO_Pin_15)
{
/* Wait on SPI busy flag */
while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET);
/* Waiting until RX FIFO is empty */
while (SPI_GetReceptionFIFOStatus(SPI1) != SPI_ReceptionFIFOStatus_Empty);
/* Disable the SPI interrupt */
SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, DISABLE);
SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_OVR, DISABLE);
/*
* New frame is ready... 
*/
}
else if (!(GPIOA->IDR & GPIO_Pin_15))
{
/* Slave select line is low, enable the SPI interrupt */
SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, ENABLE);
SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_OVR, ENABLE);
}
}
}
/**
* @brief Handle the SPI interrupt.
*/
void SPI1_IRQHandler()
{
/* SPI in receiver mode */
if (SPI_I2S_GetITStatus(SPI1, SPI_I2S_IT_RXNE) == SET)
{
while (SPI1->SR & SPI_ReceptionFIFOStatus_Full)
{
/* This is the same as active_frame->data[idx++] = SPI_ReceiveData8(SPI1) but doesn't call assert macro
active_frame->data[idx++] = *(__IO uint8_t *) (((uint32_t)SPI1) + 0x0C);
}
}
/* SPI error */
if (SPI_I2S_GetITStatus(SPI1, SPI_I2S_IT_OVR) == SET)
{
SPI_ReceiveData8(SPI1);
SPI_I2S_ClearFlag(SPI1, SPI_I2S_IT_OVR);
}
}

Note that in the SPI ISR I have tried using the SPI_ReceiveData8 function directly. This implementation bypasses the peripheral library in an attempt to optimize the ISR, but this doesn't seem to help.

#slave-spi-isr-interrupt-optimize
3 REPLIES 3
Posted on February 20, 2014 at 21:59

A 2 MHz interrupt rate is a tad high, try to use DMA, or use a tight polling loop.

Tips, buy me a coffee, or three.. PayPal Venmo Up vote any posts that you find helpful, it shows what's working..
jakeforsberg
Associate II
Posted on February 21, 2014 at 03:41

I'd love to use DMA in this case, but I haven't been clever enough to setup DMA to receive variable frame sizes. I have no idea how large the frame will be until I've received it! If there's a technique for this, please share.

I was able to solve the missing bytes issue by further optimizing my ISR. I simply provided the compiler with branch prediction information via__builtin_expect() macros. Here's my updated ISR:


#define likely(x) __builtin_expect((x), 1)
#define unlikely(x) __builtin_expect((x), 0)
/**
* @brief Handle the SPI interrupt.
*/
void SPI1_IRQHandler()
{
/* SPI in receiver mode */
if (likely(SPI_I2S_GetITStatus(SPI1, SPI_I2S_IT_RXNE) == SET))
{
while (SPI1->SR & SPI_ReceptionFIFOStatus_Full)
{
active_frame->data[idx++] = *(__IO uint8_t *) (((uint32_t)SPI1) + 0x0C);
}
}
/* SPI error */
if (unlikely(SPI_I2S_GetITStatus(SPI1, SPI_I2S_IT_OVR) == SET))
{
SPI_ReceiveData8(SPI1);
SPI_I2S_ClearFlag(SPI1, SPI_I2S_IT_OVR);
}
}

If anyone else is having similar issues, there's opportunity to further optimize by bypassing the SPI_I2S_GetITStatus library function and addressing the bits directly. I actually need to expand this ISR to check the first byte and setup a DMA transfer, so I may need to continue the optimization effort.

Thanks.

stm322399
Senior
Posted on February 21, 2014 at 08:45

If I were you i'll start with dumping call to SPI_I2S_GetITStatus. This function is certainly too big for your needs (Do you have FULL_ASSERT defined when you compile ?).

Additionally I am surprised that you are waiting the FIFO to be full before popping incoming data. I guess that you have to pop data as soon as FIFO *is not empty*, that could make the difference.