cancel
Showing results for 
Search instead for 
Did you mean: 

Using HAL SPI on STM32F072RB | SCK asserts (incorrect idle) after reset

JCame.1
Associate III

Hi all,

First post on here so apologies if I make some errors in my question format/clarity.

I am doing some testing using the SPI HAL library attempting to communicate with another chip, everything works as expected and I can successfully send bytes and receive the expected data.

There is, however, one slight bugbear that I can't seem to explain or overcome. Straight after the STM32F072RB is reset the SCK signal is held high (implying a high idle state), however I have set my SPI up to communicate using MODE 0, i.e. low idle state, and sample on the rising edge. This high idle state causes the first received data to be left-shifted by 1 bit, as the first transition the clock performs, after the SS is asserted, is falling edge indicating the data to change for the subsequent rising edge. All transactions after this first are correct.

This is incorrect behaviour, and SCK should idle low at all times. I have tried pulling the GPIO low before setting it up as an alternate function pin, pulling it low afterwards, applying pull-up/pull-down resistors to no avail. (Although I should note that the pull up/down resistors would never have worked, as the slave chip has a pull-up resistor on its input!). My assumption is the pull-up resistor on the slave chip is interfering with the SS signal, but why it only occurs on start-up I am unsure of.

I have come up with a somewhat janky fix, by which I call an SPI transmit without asserting the SS signal. This causes SCK to cycle without sending any data, after which SCK correctly idles low, seemingly indefinitely. See my code snippets and logical analyser output below:

SPI GPIO initialisation (commented code was an attempt at an earlier fix):

void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hspi->Instance==SPI2)
  {
  /* USER CODE BEGIN SPI2_MspInit 0 */
 
  /* USER CODE END SPI2_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_SPI2_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
 
    /**SPI2 GPIO Configuration    
    PB13     ------> SPI2_SCK
    PB14     ------> SPI2_MISO
    PB15     ------> SPI2_MOSI 
    */
    GPIO_InitStruct.Pin = SPI2_MISO_Pin | SPI2_MOSI_Pin; //SPI2_SCK_Pin | SPI2_MOSI_Pin
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF0_SPI2;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
 
//    HAL_GPIO_WritePin(GPIOB, SPI2_SCK_Pin, GPIO_PIN_RESET);
//    GPIO_InitStruct.Pin = SPI2_SCK_Pin;
//    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
//    GPIO_InitStruct.Pull = GPIO_NOPULL;
//    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
//    GPIO_InitStruct.Alternate = GPIO_AF0_SPI2;
//    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
//    HAL_GPIO_WritePin(GPIOB, SPI2_SCK_Pin, GPIO_PIN_RESET);
 
    GPIO_InitStruct.Pin = SPI2_SCK_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF0_SPI2;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  /* USER CODE END SPI2_MspInit 1 */
  }
}

SPI parameters and my janky fix of a dummy transmit):

static void MX_SPI2_Init(void)
{
 
  /* USER CODE BEGIN SPI2_Init 0 */
    uint8_t temp_send[1] = {0x00};
  // init PA9 - to be used as CS
  GPIOA->ODR |= GPIO_ODR_9;
  GPIOA->MODER &= ~GPIO_MODER_MODER9_Msk;
  GPIOA->MODER |= GPIO_MODER_MODER9_0;
  GPIOA->OTYPER &= ~GPIO_OTYPER_OT_9;
  /* USER CODE END SPI2_Init 0 */
 
  /* USER CODE BEGIN SPI2_Init 1 */
 
  /* USER CODE END SPI2_Init 1 */
  /* SPI2 parameter configuration*/
  hspi2.Instance = SPI2;
  hspi2.Init.Mode = SPI_MODE_MASTER;
  hspi2.Init.Direction = SPI_DIRECTION_2LINES;
  hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi2.Init.NSS = SPI_NSS_SOFT;
  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi2.Init.CRCPolynomial = 7;
  hspi2.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
  hspi2.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
 
  if (HAL_SPI_Init(&hspi2) != HAL_OK)
  {
    Error_Handler();
  }
 
  /* USER CODE BEGIN SPI2_Init 2 */
  /* I *think* there's some sort of bug with the way the HAL sets up the GPIO clock pin for use in SPI.
   * No matter what I do with the clock pin, even if I FORCE it low before assigning it as an alternate function, it always asserts itself during initialisation (giving the impression of a high idle state)
   *  --> this has the adverse effect of putting aXiom 1 clock cycle ahead, hence everything comes out LS by 1 (this only happens on the first transaction)
   * My VERY jammy way of getting around this is to pretend to send a byte without asserting the CS pin, this somehow 'tricks' the clock into idling low without triggering aXiom
   * I don't like how this works, but it does...oh well!
   */
  HAL_SPI_Transmit(&hspi2, temp_send, sizeof(temp_send), 0);
 
  /* USER CODE END SPI2_Init 2 */
 
}

Logical waveform showing high clock idle (first transaction after reset):

0693W000004Gh6FQAS.png

Logical waveform showing low clock idle (every subsequent transaction):

0693W000004Gh6tQAC.png

Whilst I have come up with some sort of solution for my problem, I would prefer to have a slightly more robust one that doesn't involve 'waggling' SCK. So my question is:

Does anyone know of a way to ensure SCK idles low correctly on start-up? And is this a known issue?

Many thanks,

J

1 ACCEPTED SOLUTION

Accepted Solutions
The clock isnt output until SPE=1, which doesnt happen in hal_spi_init.
When you call transmit, it sets spe=1 and enables the clock output.
If you feel a post has answered your question, please click "Accept as Solution".

View solution in original post

4 REPLIES 4
TDK
Guru

> Straight after the STM32F072RB is reset the SCK signal is held high (implying a high idle state), however I have set my SPI up to communicate using MODE 0

After reset, your pin isn't initialized anymore and is in high-z input mode. If you need it to reflect a certain state, you'll need to add an external pullup/down resistor to enforce that.

Initializing and enabling SPI before asserting CS low also works.

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

Hi TDK, thanks for responding.

Okay that is good to know, I had assumed that was the case! As I said the slave I am communicating with already has a pull-up resistor on its input so seems something like the second option is my way forward. Having said that, I'm fairly certain I have already done something along those lines (although could be wrong, I'm still quite a novice).

Do you have any idea what causes the behaviour of the clock idling high before the first transaction?

The clock isnt output until SPE=1, which doesnt happen in hal_spi_init.
When you call transmit, it sets spe=1 and enables the clock output.
If you feel a post has answered your question, please click "Accept as Solution".

Ah that makes a lot of sense, I was wondering where it was being enabled! Thanks very much for your help 😀