AnsweredAssumed Answered

Optimizing SPI ISRs for high speed transfers

Question asked by iorbitearth on Feb 20, 2014
Latest reply on Feb 21, 2014 by gonzalez.laurent
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.


Outcomes