2025-09-12 9:42 AM
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?
2025-09-12 2:33 PM
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!