2025-09-09 11:45 AM - edited 2025-09-09 11:47 AM
I have an SPI slave running on STM32L4 in DMA mode. Here is the basic structure (simplified for this posting):
#define NSS (HAL_GPIO_ReadPin( SPI3_NSS_GPIO_Port, SPI3_NSS_Pin ))
#define BUFFERSIZE 32
uint8_t SpiTxBuffer[BUFFERSIZE], SpiRxBuffer[BUFFERSIZE];
typedef enum
{
Idle,
Wait,
Abort,
Done
} State_t;
State_t State = Idle;
int main(void)
{
/*
Usual MX boilerplate here.
*/
while(1)
{
switch (State)
{
case Idle:
if(!NSS)
{
if( HAL_SPI_TransmitReceive_DMA( &hspi, SpiTxBuffer, SpiRxBuffer,
BUFFERSIZE ) != HAL_OK )
{
Error_Handler();
}
State = Wait;
}
break;
case Wait:
if(NSS)
{
if( HAL_SPI_Abort_IT(&hspi) == HAL_OK)
{
State = Abort;
}
else
{
Error_Handler();
}
}
break;
case Abort:
break;
case Done:
DoSomethingWithTheData();
State = Idle;
}
DoSomethingElse(); /*Do some task while waiting for SPI to arrive*/
}
}
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
State = Done;
}
void HAL_SPI_AbortCpltCallback(SPI_HandleTypeDef *hspi)
{
State = Done;
}
In this circumstance, if the host (master) sends fewer bytes than expected and deactivates NSS, I detect this and abort the SPI exchange. However, the AbortCallback never gets called as evidenced by the fact that State never gets set to DONE.
Any ideas?
Edit: Fixed a typo
Solved! Go to Solution.
2025-09-10 6:49 AM
Hello @AMacd.1
According to your code the state will be set to Done when calling HAL_SPI_Abort_IT() inside HAL_SPI_AbortCpltCallback(). Then if the return value of this call is HAL_OK the state will be set again to Abort. In this case you will not detect that the callback is called.
2025-09-10 6:49 AM
Hello @AMacd.1
According to your code the state will be set to Done when calling HAL_SPI_Abort_IT() inside HAL_SPI_AbortCpltCallback(). Then if the return value of this call is HAL_OK the state will be set again to Abort. In this case you will not detect that the callback is called.
2025-09-10 11:03 AM - edited 2025-09-10 11:26 AM
I see my mistake now. My misunderstanding was based on my belief that functions that are "not blocking" return immediately and the callbacks are expected to occur at some time later (such as when an interrupt fires). Apparently, the function HAL_SPI_Abort_IT() calls the callback function before it returns.
Perhaps the intent of HAL_SPI_Abort_IT() is that it is to be called from an interrupt so that the main path of execution can look for the results of the callback. Is that correct?
So, there isn't any reason to call HAL_SPI_Abort_IT() instead of HAL_SPI_Abort() to abort an SPI DMA transfer from within the main loop is there? The documentation isn't very helpful.
2025-09-10 11:16 AM
OK, so if I change my code so that I call HAL_SPI_Abort() instead of HAL_SPI_Abort_IT(), and set the state to Done, the return value ends up being HAL_ERROR and I see the value 0x40 in hspi3.ErrorCode. What's going on here? Code Snippet:
case Wait:
if(NSS)
{
DebugVal = HAL_SPI_Abort(&hspi3);
if( DebugVal == HAL_OK)
{
State = Done;
}
else
{
Error_Handler();
}
}
break;
2025-09-10 11:45 AM - edited 2025-09-10 11:57 AM
> The documentation isn't very helpful.
This indeed is so, and users are encouraged to look in the source.
There you can find that ErrorCode 0x40 is HAL_SPI_ERROR_ABORT.
Function HAL_SPI_Abort sets status HAL_SPI_ERROR_ABORT in quite a few points, you can trace this function and find where it sets this status and if it can be ignored.
Function HAL_SPI_Abort_IT can be called from background program or interrupt handler of a lower priority than the SPI or its DMA (to not block the SPI or DMA interrupts).
2025-09-10 12:16 PM
@Pavel A. Yikes! OK, will do. TBH, its annoying that there are errors that "can be ignored". Rather like "the sky is falling" eh?
2025-09-10 1:39 PM
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.
2025-09-11 1:25 AM
Hello @AMacd.1
The function HAL_SPI_Abort_IT() can be used to abort a transfer in interrupt or DMA mode.
Your code should be updated as below:
#define NSS (HAL_GPIO_ReadPin( SPI3_NSS_GPIO_Port, SPI3_NSS_Pin ))
#define BUFFERSIZE 32
uint8_t SpiTxBuffer[BUFFERSIZE], SpiRxBuffer[BUFFERSIZE];
typedef enum
{
Idle,
Wait,
Abort,
Done
} State_t;
State_t State = Idle;
int main(void)
{
/*
Usual MX boilerplate here.
*/
while(1)
{
switch (State)
{
case Idle:
if(!NSS)
{
if( HAL_SPI_TransmitReceive_DMA( &hspi, SpiTxBuffer, SpiRxBuffer,
BUFFERSIZE ) != HAL_OK )
{
Error_Handler();
}
State = Wait;
}
break;
case Wait:
if(NSS)
{
HAL_SPI_Abort_IT(&hspi);
while(TransferAbort != 1);
State = Done;
}
break;
case Abort:
break;
case Done:
DoSomethingWithTheData();
State = Idle;
}
DoSomethingElse(); /*Do some task while waiting for SPI to arrive*/
}
}
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
State = Done;
}
void HAL_SPI_AbortCpltCallback(SPI_HandleTypeDef *hspi)
{
TransferAbort = 1;
}
2025-09-11 6:19 AM
> Rather like "the sky is falling" eh?
Depends on what leads to the point where the ST driver declares "error". If the FIFO fails to empty in 100 ms it looks suspicious. OTOH if the code expected to see some interrupt enabled but it is already clear then possibly it can be ignored. Keep debugging...
2025-09-11 1:32 PM
No, @Saket_Om.
The comment header in front of HAL_SPI_Abort() also says that this function can be used to abort a transfer in interrupt or DMA mode.
/**
* @brief Abort ongoing transfer (blocking mode).
* @PAram hspi SPI handle.
* @note This procedure could be used for aborting any ongoing transfer (Tx and Rx),
* started in Interrupt or DMA mode.
* This procedure performs following operations :
* - Disable SPI Interrupts (depending of transfer direction)
* - Disable the DMA transfer in the peripheral register (if enabled)
* - Abort DMA transfer by calling HAL_DMA_Abort (in case of transfer in DMA mode)
* - Set handle State to READY
* @note This procedure is executed in blocking mode : when exiting function, Abort is considered as completed.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_SPI_Abort(SPI_HandleTypeDef *hspi)
As explained by @Pavel A., "Function HAL_SPI_Abort_IT can be called from background program or interrupt handler of a lower priority than the SPI or its DMA (to not block the SPI or DMA interrupts)." Since I am not calling from an interrupt routine or "background" program, I do not need to use HAL_SPI_Abort_IT().
Your code is no different from having the abort callback set the state to Done and just waiting for it in the switch statement. Plus you add a completely unnecessary variable.
However, the actual bug in my example is entirely due to the order of operations. I should have set the State = Abort BEFORE calling HAL_SPI_Abort_IT():
...
State = Abort;
if( HAL_SPI_Abort_IT(&hspi3) != HAL_OK)
{
Error_Handler();
}
...
void HAL_SPI_AbortCpltCallback(SPI_HandleTypeDef *hspi)
{
State = Done;
}
...
Now the AbortCallback sets the Done state correctly. No need for a separate variable. Thanks for your help.