2021-04-03 06:39 PM
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:
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");
}
Solved! Go to Solution.
2021-04-04 06:10 AM
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:
2021-04-04 01:40 AM
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
2021-04-04 03:59 AM
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();
}
2021-04-04 05:28 AM
> 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
2021-04-04 05:55 AM
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.
2021-04-04 06:10 AM
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:
2021-10-19 10:32 PM
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) {
}