cancel
Showing results for 
Search instead for 
Did you mean: 

STM32 HAL_SPI_Library on Blue Pill

NicoNX650
Associate III

Title edited to note that this relates to a Blue Pill board.


Hello everyone,
I'm starting a learning journey into the STM32 world, specifically studying the HAL library (my past programming experiences didn't rely on ready-made libraries).
Right now (after tinkering with GPIOs, LCD management, etc.) I'm working on the SPI dialogue. I have two STM32F103CBT6A development boards, one configured for "Full Duplex Master" and one for "Full Duplex Slave."
When trying to send 10 static bytes (10, 20, 30, 100) from both the master and slave sides using the "HAL_SPI_TransmitReceive_IT(&hspi1, TX_Buffer, RX_Buffer, 10)" function, I expect the oscilloscope to see the MOSI line equal to the MISO line without any offsets. This happens for a few seconds after resetting the slave (image 1), but then the bytes on the MISO line shift (image 2). Furthermore (I'm viewing the data received from the slave on an LCD) when this problem occurs, the slave also doesn't receive correctly.
After adjusting the codes for 10-byte transmission, I wanted to expand it to 150 bytes.
Thanks in advance to anyone who can point out where I'm going wrong.

27 REPLIES 27

@NicoNX650 wrote:

you're right. If I remember well the board name is Blue Pill.


Note that the Blue Pill is not an ST product, and most likely does not have a genuine STM32 chip fitted.

A complex system that works is invariably found to have evolved from a simple system that worked.
A complex system designed from scratch never works and cannot be patched up to make it work.

In the example, a button press is used to start the communication after a 100ms delay. The slave has already called HAL_SPI_TransmitReceive_IT by this time.

In your code, there is nothing to ensure the slave has already called HAL_SPI_TransmitReceive_IT before the master has. You could use a gpio pin to signal between the two. Or a sufficient delay.

On the slave side, you should poll for the status to ensure the previous transaction has completed before you call HAL_SPI_TransmitReceive_IT again. You're updating values on the LCD before that data has even been received.

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

You may find it useful to toggle a separate GPIO pin on each device to indicate the state of the code. Set it high just before HAL_SPI_TransmitReceive_IT and set it low when the operation is complete. Monitor those with a logic analyzer to ensure the slave is ready when the master sends.

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

Dear TDK,

when you say: You may find it useful to toggle a separate GPIO pin on each device to indicate the state of the code. Set it high just before HAL_SPI_TransmitReceive_IT and set it low when the operation is complete" it means that I have to set a GPIO (configured as output) before HAL_SPI_TransmitReceive_IT (on slave side) and I have to reset this GPIO when HAL_SPI_GetState(&hspi1) became !=HAL_SPI_STATE_BUSY_TX_RX? 

Then on master side I have to add a condition that perform HAL_SPI_TransmitReceive_IT only when the GPIO (configured as input) is low?

Thanks a lot

> Then on master side I have to add a condition that perform HAL_SPI_TransmitReceive_IT only when the GPIO (configured as input) is low?

No, this GPIO toggling would be purely "additional" code, and not directly related or interfering with th SPI transmission.
But attaching a scope or logic analyser to this GPIO pin, you can monitor the time spent in the function HAL_SPI_TransmitReceive_IT()  in realtime.

Dear all,

while studying, I improved the code by adding callback functions and error handling. Unfortunately, the result is always the same. By periodically exchanging the same 4 bytes between the master and slave, the MISO and MOSI signals go from equal (IMAGE_1) to different (IMAGE_2), and an increment variable is incremented when I encounter an error.
The master transmits once per second, so I think the slave has plenty of time to be ready. Attached you can find a video that shows what I mean.


I hope someone can help me; I've been struggling with this problem for days now, but I can't seem to figure it out.
Thank you all so much. The master and slave code are also attached. Best regards to the entire community.

 

//master code
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */

	  if (spi_master_done==1){
		  spi_master_done=0;
		  HAL_Delay(1);
		  isBusy=0;
	  }
	  if (isBusy == 0) {
		isBusy = 1;
		HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET);
		if (HAL_SPI_TransmitReceive_IT(&hspi1, mTxBuf, mRxBuf, 4) != HAL_OK) {
			isBusy = 0; // Errore avvio
			HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET);
		}
	}
	HAL_Delay(1000); 

  }
  /* USER CODE END 3 */
}


  /* USER CODE END SPI1_Init 1 */
  /* SPI1 parameter configuration*/
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */

  /* USER CODE END SPI1_Init 2 */

}
/* USER CODE BEGIN 4 */
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {
    if (hspi->Instance == SPI1) {
        // 3. Alza il CS: scambio completato
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET);
        spi_master_done=1;
    }
}


//Slave code

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();
  /* USER CODE BEGIN 2 */
  Lcd_PortType ports[] = { GPIOB, GPIOB, GPIOB, GPIOB };
  Lcd_PinType pins[] = {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_10, GPIO_PIN_11};

  Lcd_HandleTypeDef lcd;

LCD_4_BIT_MODE);

  lcd = Lcd_create(ports, pins, GPIOA, GPIO_PIN_1, GPIOA, GPIO_PIN_3, LCD_4_BIT_MODE);
  Lcd_clear(&lcd); // Clear screen
  HAL_SPI_TransmitReceive_IT(&hspi1, sTxBuf, sRxBuf, 4);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  Lcd_cursor(&lcd, 0,0);
	  Lcd_int(&lcd, sRxBuf[0]);
	  Lcd_cursor(&lcd, 0,4);
	  Lcd_int(&lcd, sRxBuf[1]);
	  Lcd_cursor(&lcd, 0,8);
	  Lcd_int(&lcd, sRxBuf[2]);
	  Lcd_cursor(&lcd, 0,12);
	  Lcd_int(&lcd, sRxBuf[3]);
	  Lcd_cursor(&lcd, 0,16);
	  Lcd_int(&lcd, peppe);
	  HAL_Delay(1000);
  }
  /* USER CODE END 3 */
}

static void MX_SPI1_Init(void)
{

  /* USER CODE BEGIN SPI1_Init 0 */

  /* USER CODE END SPI1_Init 0 */

  /* USER CODE BEGIN SPI1_Init 1 */

  /* USER CODE END SPI1_Init 1 */
  /* SPI1 parameter configuration*/
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_SLAVE;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_HARD_INPUT;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */

  /* USER CODE END SPI1_Init 2 */

}

/* USER CODE BEGIN 4 */
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {
    if (hspi->Instance == SPI1) {
        HAL_SPI_TransmitReceive_IT(&hspi1, sTxBuf, sRxBuf, 4);
    }
}

void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) {
    if (hspi->Instance == SPI1) {
        HAL_SPI_Abort_IT(hspi);
        peppe++;
        HAL_SPI_TransmitReceive_IT(&hspi1, sTxBuf, sRxBuf, 4);
    }
}

/* USER CODE END 4 */

 

> By periodically exchanging the same 4 bytes between the master and slave, the MISO and MOSI signals go from equal (IMAGE_1) to different (IMAGE_2), ...

> The master transmits once per second, so I think the slave has plenty of time to be ready. 

I think you need to explain more about the semantics of your protocol here.
What is transmitted with each byte, and what does the master expect back, and when ?

SPI is a synchronous protocol, and the slave device has no prior "knowledge" when a transmission happens. This means the first item (first byte for a 8-bit transfer) is more or less random, a value that happens be in the SPI transmit register at the slave side when the master starts.
Only once the slave has received an item (byte) he can process it and respond. In other words, a slave device cannot respond to a command/request within the same transfer cycle.

Depending on the slave, this might not even be possible within a back-to-back transfer of several items. "Simple" SPI slave devices, which are mostly logic/combinatorial-driven, can often do this, software-driven slave devices usually need more time.
With "simple devices" I mean e.g. memory devices or analogue sensors, which can dispatch a "read value" request byte immediately and and load the memory or output register value into the SPI transfer register within nanoseconds. The datasheet of the device will tell you more.

Dear Ozone,
Thank you very much for your reply.
My code would like to implement a static byte exchange, meaning always the same (in this case {0x11, 0x22, 0x33, 0x44}) on both the master and slave sides.
I'm perfectly familiar with the SPI logic; in the past, I've created many projects using this interface (even exchanging more than 200 bytes (variable, obviously) every 500 ms). In these projects, I've always used simple 8-bit Atmel MCUs.
Are you referring to the fact that the STM32 can't load the data into the buffer very quickly when the master starts transmitting?
If I understand correctly, then the STM32 has difficulty functioning as a slave (meaning it can't respond in time).
I actually developed the SPI dialogue with more than 200 bytes every 500 ms using a 32-bit DSP from Texas Instruments, but in this case it was only the master.
Hoping for your confirmation or denial, I thank you in advance.

> I'm perfectly familiar with the SPI logic; ...

That's ok, just wanted to make sure.

> If I understand correctly, then the STM32 has difficulty functioning as a slave (meaning it can't respond in time).

Not really "difficulties"..
But that depends on the situation.
Of course a slave cannot react to a command / transfer while the value is still about to be transmitted, that much is obvious.

However, I differ between a transfer (one item of the configured size is clocked in/out), and a full transfer cycle.
The latter most often consists of multiple individual transfers, starting with /SS going low and ending with /SS released.
If you want a reaction from a slave device to the previous transfer within a cycle, you need to give it enough time. A back-to-back transfer from an interrupt handler or let alone a DMA driven master is usually far too fast.

Additionally, you need to take the SPI peripheral into account. At least many newer MCUs use a FIFO, which complicates the handling in some instances. I suppose the F103 has not.
But the slave side can "preload" the FIFO or Tx registerif the first item is known in advance. 
And it might be necessary to clear the FIFO if the transfer length is not known in advance, or else the values stuck there would be transmitted with the next transfer cycle, causing an offset.

Thanks so much for your reply.
But why do you think my simple code doesn't work?
I'm stuck.
P.S. I just bought two Nucleo boards so you can help me with debugging.