cancel
Showing results for 
Search instead for 
Did you mean: 

SPI receive times out due to SPI_FLAG_RXNE not reseting in SPI_EndRxTransaction.

Vartvar
Associate II

MCU is STM32F427. I'm using SPI3 to read from a transmit only slave. My configuration looks like this:

/* SPI3 parameter configuration*/
  hspi3.Instance = SPI3;
  hspi3.Init.Mode = SPI_MODE_MASTER;
  hspi3.Init.Direction = SPI_DIRECTION_2LINES_RXONLY;
  hspi3.Init.DataSize = SPI_DATASIZE_16BIT;
  hspi3.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi3.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi3.Init.NSS = SPI_NSS_SOFT;
  hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
  hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi3.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi3.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi3.Init.CRCPolynomial = 10;

When it comes time to read the code does the following:

  1. Sets CS low via gpio
  2. calls HAL_SPI_Receive
  3. Sets CS high via gpio

Stepping through the code in (2), I see the correct data received in two 8 bit reads. However, when the HAL code gets to the end of HAL_SPI_Receive, it does the following.

/* Check the end of the transaction */
  if (SPI_EndRxTransaction(hspi, Timeout, tickstart) != HAL_OK)
  {
    hspi->ErrorCode = HAL_SPI_ERROR_FLAG;
  }

This sets the error code every time and the HAL_SPI_Receive call fails.

If I comment out the following code in SPI_EndRxTransaction, the read succeeds.

static HAL_StatusTypeDef SPI_EndRxTransaction(SPI_HandleTypeDef *hspi,  uint32_t Timeout, uint32_t Tickstart)
{
  if ((hspi->Init.Mode == SPI_MODE_MASTER) && ((hspi->Init.Direction == SPI_DIRECTION_1LINE)
                                               || (hspi->Init.Direction == SPI_DIRECTION_2LINES_RXONLY)))
  {
    /* Disable SPI peripheral */
    __HAL_SPI_DISABLE(hspi);
  }
 
  /* Erratasheet: BSY bit may stay high at the end of a data transfer in Slave mode */
  if (hspi->Init.Mode == SPI_MODE_MASTER)
  {
    if (hspi->Init.Direction != SPI_DIRECTION_2LINES_RXONLY)
    {
      /* Control the BSY flag */
      if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_BSY, RESET, Timeout, Tickstart) != HAL_OK)
      {
        SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
        return HAL_TIMEOUT;
      }
    }
// COMMENT OUT THIS BLOCK AND THE SPI RECEIVE WORKS
//    else
//    {
//      /* Wait the RXNE reset */
//      if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_RXNE, RESET, Timeout, Tickstart) != HAL_OK)
//      {
//        SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
//        return HAL_TIMEOUT;
//      }
//    }
// END COMMENTED BLOCK
  }
  else
  {
    /* Wait the RXNE reset */
    if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_RXNE, RESET, Timeout, Tickstart) != HAL_OK)
    {
      SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);
      return HAL_TIMEOUT;
    }
  }
  return HAL_OK;
}

Can someone please explain what I'm doing wrong? Thank You!

5 REPLIES 5
TDK
Guru

Save yourself the headache and use regular 2 line mode with the MOSI pin uninitialized. Receive only master has an awkward hardware implementation.

SPI_DIRECTION_2LINES_RXONLY doesn't get any use in the F4 CubeMX examples. I wouldn't be surprised if it didn't work.

If you feel a post has answered your question, please click "Accept as Solution".

The Rx-only SPI mode (and bidirectional mode in Rx) runs clocks continuously until SPI is disabled. As described in RM, this disable must happen *during* reception of the last intended frame not *after* it, as it is written in Cube. So, in Cube, as the disable comes late, the next frame starts to be received.

Note that the test for RXNE is outright wrong there. If baudate is low and/or compiler optimizations are enabled, the test will happen before that extraneous frame is received, and the RXNE flash set afterwards will cause problems when starting receiving the next frame. In the other case -as happened here - the extra frame is received completely and RXNE is set at time of test, then code waits until timeout (potentially forever if set so), as there's of course nothing which would clear RXNE in that waiting loop.

Remedy for this particular case is, as TDK said, to use the normal fullduplex mode without assigning a MOSI pin in GPIO.

The real solution of course would be to fix this in Cube for cases where Rx-only and more importantly, bidir, is genuinely needed, but apparently nobody cares.

JW

Vartvar
Associate II

Thank you @TDK​ and @Community member​!

Yes, I see the free running clock on my scope when using  hspi3.Init.Direction = SPI_DIRECTION_2LINES_RXONLY. It makes sense that the receive buffer would have unintended data from these extra trailing clocks.

If I instead change the initialization code to simply hspi3.Init.Direction = SPI_DIRECTION_2LINES the acquisition succeeds.

One more question re. proper configuration. In MX Device Configuration, SPI3 is quite limited due to other MCU pins already assigned. I cannot choose Full-Duplex master. What is the correct way to circumvent this limitation, since I have no need for MOSI? I was hoping to keep things compatible with cube so that future automatic code generation doesn't clobber any tweaks I need to make for this problem.

Thanks again!

Modify the code generated by CubeMX. No way to get CubeMX to generate it that way without initializing the MOSI pin.
If you feel a post has answered your question, please click "Accept as Solution".

Thanks @TDK​,

What I ended up doing was just modifying MX_SPI3_Init() like so:

static void MX_SPI3_Init(void)
{
 
  /* USER CODE BEGIN SPI3_Init 0 */
 
  /* USER CODE END SPI3_Init 0 */
 
  /* USER CODE BEGIN SPI3_Init 1 */
 
  /* USER CODE END SPI3_Init 1 */
  /* SPI3 parameter configuration*/
  hspi3.Instance = SPI3;
  hspi3.Init.Mode = SPI_MODE_MASTER;
  //hspi3.Init.Direction = SPI_DIRECTION_2LINES_RXONLY;
  hspi3.Init.DataSize = SPI_DATASIZE_16BIT;
  hspi3.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi3.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi3.Init.NSS = SPI_NSS_SOFT;
  hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
  hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi3.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi3.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi3.Init.CRCPolynomial = 10;
  //if (HAL_SPI_Init(&hspi3) != HAL_OK)
  //{
  //  Error_Handler();
  //}
  /* USER CODE BEGIN SPI3_Init 2 */
  /*
   * We must configure SPI_DIRECTION_2LINES_RXONLY in MX due to pin limitations.
   * This has a strange implementation in ST's HAL code. See the following.
   * https://community.st.com/s/question/0D53W00000pT7bPSAS/spi-receive-times-out-due-to-spiflagrxne-not-reseting-in-spiendrxtransaction
   * By switching to SPI_DIRECTION_2LINES here, we can use the default HAL code without modification.
   */
   hspi3.Init.Direction = SPI_DIRECTION_2LINES;
   // Optional: comment out this same code above USER CODE BEGIN SPI3_Init 2
   if (HAL_SPI_Init(&hspi3) != HAL_OK)
   {
	   Error_Handler();
   }
  /* USER CODE END SPI3_Init 2 */
 
}