cancel
Showing results for 
Search instead for 
Did you mean: 

FIFO Fails to Clear on Abort - Slave SPI with DMA - STM32L451RE

AMacd.1
Senior

In a project using the STM32L451RET6, SPI3 is configured as follows
* Full Duplex Slave
* Hardware NSS Disable
* Frame Format Motoroloa
* Data Size 8
* CPOL High
* CPHA 2 Edge
* DMA:
  * SPI3_TX Ch2 Mem to Per, Priority Low
  * SPI3_RX Ch1 Per to Mem, Priority High

This works fine for a fixed packet length. Sometimes, however, the master may send a shorter packet (i.e. less than BUFFERLEN). In this case, it is necessary to end the slave SPI receive session and process the master's command. Consider the following code snippet:

#define NSS (HAL_GPIO_ReadPin( NSS_GPIO_Port, NSS_Pin ))
#define BUFFERSIZE 16

uint8_t SpiTxBuffer[BUFFERSIZE], SpiRxBuffer[BUFFERSIZE];

typedef enum
{
  Idle,
  Wait,
  Done,
  WaitForNSS,
  Error
} State_t;

State_t State = Idle;

int main(void)
{
  /**
    Usual MX boilerplate here.
    STM32L451REx
    SPI Slave on SPI3 with DMA, 8 bit data, soft NSS
  */
  while(1)
  {
    switch (State)
    {
      case Idle:
        if(!NSS)
        {
          State = Wait;
          if( HAL_SPI_TransmitReceive_DMA( &hspi3, SpiTxBuffer, SpiRxBuffer,
              BUFFERSIZE ) != HAL_OK )
          {
            Error_Handler();
          }
        }
        break;

      case Wait:
        if(NSS) /*Master ended SPI transfer early (shorter that BUFFERSIZE)*/
        {
          State = Done;
          if( HAL_SPI_Abort(&hspi3) != HAL_OK)
          {
            Error_Handler();
          }
        }
        break;

      case Done:
        State = WaitForNSS; /*In case BUFFERSIZE was received but NSS is slow*/
        break;

      case WaitForNSS:
        if(NSS) /*OK now we're sure we can begin to process command*/
        {
          DoSomethingWithTheData();
          State = Idle; /*Ready for next go around*/
        }
        break;

      default:
        Error_Handler();
        break;
    }
    DoSomethingElse();  /*Do some task while waiting for SPI to arrive*/
  }
}

void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
  State = Done;
}

void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
  State = Error;
}

 

When the master sends the entire buffer all at once, no problems occur.

However, when the master raises NSS early (a shorter message), the return value of HAL_SPI_Abort() ends up being HAL_ERROR with the value HAL_SPI_ERROR_ABORT (0x40) appearing in hspi3.ErrorCode.

After some digging, I found that a breakpoint hits at line 4073 of stm32l4xx_hal_spi.c, in the function SPI_WaitFifoStateUntilTimeout(), called by SPI_EndRxTxTransaction at line 4134. This means that the receive fifo is not being emptied in 100ms!!! Strange since the master's clock is completely stopped at this point.

If I ignore the return value of HAL_SPI_Abort(), the subsequent HAL_SPI_TransmitReceive_DMA does not return HAL_OK. Plus, the data being shifted out is no longer correct.

I saw several references to adding the following lines after the abort completes (ignoring the return value)

__HAL_RCC_SPI3_FORCE_RESET();
__HAL_RCC_SPI3_RELEASE_RESET();

but this did not solve the problem.

I don't understand why the FIFO level bits are not going to zero. Shouldn't just 4 reads of the DR be enough to totally clear the FIFO?

 

 

1 ACCEPTED SOLUTION

Accepted Solutions
AMacd.1
Senior

I found a simple solution to my problem.

For one thing, I abandoned using the HAL_..._Abort function entirely and wrote my own:

void ShortPacketAbort (void)
{
  __HAL_RCC_SPI3_FORCE_RESET();
  __HAL_RCC_SPI3_RELEASE_RESET();
  MX_SPI3_Init();
}

In addition, I needed to add the following line to the user code block at the beginning of MX_SPI3_Init():

  hspi3.State = HAL_SPI_STATE_RESET;

Here's what the Abort state case statement looks like now:

      case Wait:
        if(NSS) /*Master ended SPI transfer early (shorter that BUFFERSIZE)*/
        {
          State = Done;
          ShortPacketAbort();
        }
        break;

Now, when NSS is raised by the master, the abort operation completes in 48us and the subsequent startup works without errors.

This works for me.

 

View solution in original post

3 REPLIES 3
AMacd.1
Senior

OK, so I just realized something: SPI_WaitFifoStateUntilTimeout() repeatedly reads the DR register.  However, perhaps that will only clean out the receive fifo (if it was even necessary at the time abort was called).  This doesn't help with any of the data sitting in the transmit fifo.  While the receive fifo level (FRLVL) reads zero, the FTLVL reads 0b11!

So how do I clear the transmit fifo?  There's no clock coming from the master.

Also how can this be done as early as possible?  It's not very good to have to wait a full 100ms before the slave can react and fix this.  There needs to be a way to get the SPI peripheral to completely reset as soon as possible after the master has raised NSS!

 

Petr Sladecek
ST Employee

Hello, you need to consider few facts. Above all, at SPI version applied at L4, SPI disable doesn't flush but preserve FIFOs content. Moreover, it doesn't enough to abort the SPI only. You need to abort associated DMA streams as well, too. Note once SPI is reenabled, it starts to propagate its DMA requests again immediately till both the FIFOs are not fully serviced. I suggest to implement interrupt from NSS end edge while apply HW SPI reset and mainly DMA streams abort before SPI is reenabled at case the DMA transactions are not fully completed. For more details I suggest to study AN5543.

Best regards,

Petr

AMacd.1
Senior

I found a simple solution to my problem.

For one thing, I abandoned using the HAL_..._Abort function entirely and wrote my own:

void ShortPacketAbort (void)
{
  __HAL_RCC_SPI3_FORCE_RESET();
  __HAL_RCC_SPI3_RELEASE_RESET();
  MX_SPI3_Init();
}

In addition, I needed to add the following line to the user code block at the beginning of MX_SPI3_Init():

  hspi3.State = HAL_SPI_STATE_RESET;

Here's what the Abort state case statement looks like now:

      case Wait:
        if(NSS) /*Master ended SPI transfer early (shorter that BUFFERSIZE)*/
        {
          State = Done;
          ShortPacketAbort();
        }
        break;

Now, when NSS is raised by the master, the abort operation completes in 48us and the subsequent startup works without errors.

This works for me.