cancel
Showing results for 
Search instead for 
Did you mean: 

(Solved) Unable to reset SPI busy flag in slave mode by any means

DavidAlfa
Senior II

The solution was to hard reset the SPI module using RCC_APB1RSTR register.

HAL includes macros for this:

__HAL_RCC_SPI2_FORCE_RESET();
__HAL_RCC_SPI2_RELEASE_RESET();

With these it completely resets and can be initialized again!

I'm using SPI slave mode. STM32F411. Tried with a 32F103CB and does the same thing.

Everything works fine if the communication is perfect, but I need to be able to recover from errors that could appear, ex. missing a clock pulse or some random noise.

When a certain time passes, if the SPI module is still busy, it means that it missed some clock pulses and it's still waiting for the rest.

This condition requires resetting the spi shift register, so the next frame is read correctly.

I checked the errata sheet, states that the BSY flag can get stuck in high state and suggests to use RXNE instead.

From 1st to 7th clock pulses the only flag that changes is BSY, nothing else changes in SR, CR1 and CR2 registers.

So that's the only way I have to know if the the SPI lost clocks and needs reset!

After the 8th clock BSY resets itself, always. And this is the only way to reset it.

But it's completely impossible to reset BSY when it got stuck in the middle of a transfer.

I tried this without success:

  • Turn SPE bit off with __HAL_SPI_DISABLE(hspi). Disables SPI but clears nothing.
  • Turn SPI clock off with __HAL_RCC_SPIx_CLK_DISABLE(). The registers go to 0, but restore the values when re-enabling the clock.
  • Using HAL_SPI_Abort(). The function hangs for 8mS waiting for end of transaction, which will never happen because we have no more clocks! Hits the timeout and returns error.
  • Msp Deinit / SPI Handle reset / Mx SPI Init / SPI start. Even wiping hspi2. The BSY remains.
HAL_SPI_MspDeInit(&hspi2);
__HAL_SPI_RESET_HANDLE_STATE(&hspi2);
memset((uint8_t*)&hspi2, 0,sizeof(hspi2));
MX_SPI2_Init();
HAL_SPI_Receive_IT(&hspi2, spiData, 2);

 With other MCUs I used in the past this was as easy as turning off the peripheral, clearing the flags and enabling it again.

I've been fighting with this issue a whole day. Am I missing something?

I've seen some similar posts here and in other forums, no one had a solution for this.

For now the only and dirty method was to use an external IO to pump the clock until the BSY is cleared:

// FIX_pin is joined with SCK.
// It's always an input, unless BSY bug appears, then is set as output until the problem is fixed.
while(SPI2->SR & SPI_SR_BSY){
	HAL_GPIO_WritePin(FIX_GPIO_Port, FIX_Pin, 1);
	asm("nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop");
	HAL_GPIO_WritePin(FIX_GPIO_Port, FIX_Pin, 0);
	asm("nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop");
}

1 ACCEPTED SOLUTION

Accepted Solutions
DavidAlfa
Senior II

I found there's a HAL macro for that:

__HAL_RCC_SPI2_FORCE_RESET();

__HAL_RCC_SPI2_RELEASE_RESET();

Finally!!! This resets the SPI peripheral! Thanks a lot!:clapping_hands:

View solution in original post

6 REPLIES 6

Pulse NSS. You can do it by turning NSS to internal and toggling the "internal NSS" bit.

Also resetting SPI in the respective RCC_APBxRSTR should work.

JW

Thanks. I will try the NSS method.

The RCC way doesn't work, that's what I tried by calling HAL_SPI_MspDeInit(&hspi2), it actually does this:

__HAL_RCC_SPI2_CLK_DISABLE()

Which translates to:

#define __HAL_RCC_SPI2_CLK_DISABLE()  (RCC->APB1ENR &= ~(RCC_APB1ENR_SPI2EN))

And as I said, all the registers reset to 0, but when I enable RCC again they restore their previous values.

I tried the SSI bit. SSM is enabled. Doesn't work either. Added delays just in case. Nothing.

#define delay()	asm("nop\nnop\nnop\nnop\nnop\nnop\nnop\nnop")
 
 
if(SPI2->SR & SPI_SR_BSY){				// If BUSY flag set
	delay();
	SPI2->CR1 |= (uint32_t)SPI_CR1_SSI;		// Disable select
	delay();
	__HAL_SPI_DISABLE(&hspi2);			// Disable SPI
	delay();
	while(SPI2->SR & SPI_SR_BSY){			// Wait for bit to clear (stuck here)
		delay();
	}
	__HAL_SPI_ENABLE(&hspi2);			// Enable SPI
	delay();
	SPI2->CR1 &= ~(uint32_t)SPI_CR1_SSI;		// Enable select
	delay();
}

> The RCC way doesn't work, that's what I tried by calling HAL_SPI_MspDeInit(&hspi2) [...] (RCC->APB1ENR &= ~(RCC_APB1ENR_SPI2EN))

I wrote:

> RCC_APBxRSTR

not RCC_APBx_ENR.

> I tried the SSI bit. SSM is enabled. Doesn't work either.

Interesting. How do you detect "doesn't work", exactly?

JW

DavidAlfa
Senior II

Did you read the line where it says "// Wait for bit to clear (stuck here)"?

SR7 (BSY) is enabled all the time. Only if I send more clocks it goes away.

My bad, I read it wrong, I will check that register, thanks.

DavidAlfa
Senior II

I found there's a HAL macro for that:

__HAL_RCC_SPI2_FORCE_RESET();

__HAL_RCC_SPI2_RELEASE_RESET();

Finally!!! This resets the SPI peripheral! Thanks a lot!:clapping_hands:

AWeie.1
Associate

Thanks for your solution, This solve my issue too. I was long searching for these two functions.

For completeness here what I ended up doing to reset my spi devices:

// Some helper functions to control SPI1,2 and 3
 
void rcc_spi_force_reset(SPI_HandleTypeDef *hspi)
{
  if(hspi->Instance==SPI1)
  {
    __HAL_RCC_SPI1_FORCE_RESET();
  }
  else if(hspi->Instance==SPI2)
  {
    HAL_GPIO_WritePin(USER_GPIO_DBG_GPIO_Port, USER_GPIO_DBG_Pin,GPIO_PIN_SET);
    __HAL_RCC_SPI2_FORCE_RESET();
  }
  else if(hspi->Instance==SPI3)
  {
    __HAL_RCC_SPI3_FORCE_RESET();
  }
}
 
void rcc_spi_release_reset(SPI_HandleTypeDef *hspi)
{
  if(hspi->Instance==SPI1)
  {
    __HAL_RCC_SPI1_RELEASE_RESET();
  }
  else if(hspi->Instance==SPI2)
  {
    HAL_GPIO_WritePin(USER_GPIO_DBG_GPIO_Port, USER_GPIO_DBG_Pin,GPIO_PIN_RESET);
    __HAL_RCC_SPI2_RELEASE_RESET();
  }
  else if(hspi->Instance==SPI3)
  {
    __HAL_RCC_SPI3_RELEASE_RESET();
  }
}
 
void spiSlaveReStart(SPI_HandleTypeDef *hspi) {
  HAL_SPI_DeInit(hspi);
  asm("nop");
  asm("nop");
  rcc_spi_force_reset(hspi);
  asm("nop");
  asm("nop");
  rcc_spi_release_reset(hspi);
  HAL_SPI_Init(hspi);
}

Then as I am using DMA for package transfer, I reset my SPI and DMA transfer each time my ClockSelect line (Interrupt line in my case) goes inactive.

Performance side note: On my STM32F767ZI it takes me arround 10 to 11 us to do the entire reset procedure of the spi

//restet SPI and corresponding DMA   
 
 spiSlaveReStart(mHspi);
    HAL_SPI_DMAStop(mHspi);
    if (HAL_SPI_TransmitReceive_DMA(mHspi, reinterpret_cast<uint8_t*>(&mTxSpiBuffer),  reinterpret_cast<uint8_t*>(&mRxSpiBuffer), sizeof(AccelTxData)) != HAL_OK) {
    }