2
\$\begingroup\$

I am simulating a project where two STM32s communicate with each other through SPI (HAL drivers), where the SPI Master is an STM32F4 and the SPI Slave is an STM32F3. Basically, the master will send 5 bytes of data (1 command byte followed by 4 dummy bytes) and the slave will respond back with 4 bytes of data to the master depending on the first command byte received.

How the system should operate: Pressing the user button on the master will send SPI data (in non-interrupt mode) to the slave (and the slave will receive the data in interrupt mode). The slave then sends data back to the master depending on the first received byte. There is a blue LED indicator on the slave device whether an SPI data has been received, and a green LED indicator on the master that indicates whether it receives the correct sequence of data from the slave. Below is how the clocked data should be sent/received by the master:

Clocked SPI Data Exchange

Slave device issue: For the first few button presses, the slave successfully receives data from the master. After a few cycles, from observing the LEDs and setting breakpoints in Debug mode, I noticed that the slave starts to receive data after every 2 button presses on the master. In the slave device, the data received is correct. I also have to note that the interrupt service routine on the master executes at every button press, so SPI data should be sent at every button press.

Master device issue: The master never receives the correct sequence of data. If, for example, the slave sends the data: 0x01, 0x02, 0x03, 0x04 in that order, the master would receive the data in this order: 0x02, 0x03, 0x04, 0x01. Furthermore, this also happens every 2 button presses.

This is what the event looks like:

  1. n button press:
    • Slave: Data is correctly received by slave (and sends data back to the master)
    • Master: Garbage value received
  2. (n+1) button press:
    • Slave: Data is not received by slave (and does not send data back because slave sends data based on received data)
    • Master: Relevant values received but in incorrect order
  3. (n+2) button press (same as what happened on the n-th button press):
    • Slave: Data is correctly received by slave (and sends data back to the master)
    • Master: Garbage value received and so on.

Here is the SPI configuration code for the Master:

static void MX_SPI1_Init (void) {

   /* SPI Configuration for the STM32F4 (Master) */
   /* APB2 Clock set to 8MHz to match APB2 clock of Slave*/

   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_2EDGE;
   hspi1.Init.NSS = SPI_NSS_SOFT;
   hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
   hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
   hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
   hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
   hspi1.Init.CRCPolynomial = 7;

   if (HAL_SPI_Init(&hspi1) != HAL_OK) {
      Error_Handler();
   }
}

The SPI configuration for the Slave:

static void MX_SPI1_Init (void) {

   /* SPI Configuration for the STM32F3 (Slave) */
   /* APB2 Clock is 8MHz */

   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_2EDGE;
   hspi1.Init.NSS = SPI_NSS_SOFT;
   hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
   hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
   hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
   hspi1.Init.CRCPolynomial = 7;
   hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
   hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;

   if (HAL_SPI_Init(&hspi1) != HAL_OK) {
      Error_Handler();
   }
   
   /* Set interrupt priority and enable interrupts for SPI1 */
   HAL_NVIC_SetPriority(SPI1_IRQn, 1, 0);
   HAL_NVIC_EnableIRQ(SPI1_IRQn);
}

Here is the relevant code for the Master (the Push Button interrupt segment):

void HAL_GPIO_EXTI_Callback (uint16_t GPIO_Pin) {

   /* Clear wake-up power flag */
   __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);

   if (GPIO_Pin == PUSH_BUTTON_PIN) {
      /* Transmit one command byte located in pTxBuff array of length cTxLen */
      HAL_SPI_Transmit(&hspi1, (uint8_t*)pTxBuff, cTxLen, spi_timeout);

      /* Wait for end of SPI transmission */
      while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);

      /* Transmit dummy values located in pTempTxBuff and store retrieved relevant data in pRxBuff */
      HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)pTempTxBuff, (uint8_t*)pRxBuff, cRxLen, spi_timeout);

      /* Flash LED if correct sequence stored in pRxBuff was received - cRxLen = 4 */
      if((pRxBuff[0] == DATA0) && (pRxBuff[1] == DATA1) && (pRxBuff[2] == DATA2) && (pRxBuff[3] == DATA3)) {
         /* Toggle dedicated green LED */
         ToggleLED();
      }

}

And the relevant code for the Slave:

void HAL_SPI_RxCpltCallback (SPI_HandleTypeDef *hspi) {
   /* Toggle blue LED when SPI receives data through interrupt */
   ToggleLED();

   /* Check the data received through SPI interrupt stored in pRxBuff[0] (array is of size 1) */
   if (pRxBuff[0] == COMMAND_DATA) {

      /**
       * pTxBuff := contains appropriate data to be sent to Master device based on this specific command
       * pTempRxBuff := array to store dummy variables, this array is of size 4
       */
      HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)pTxBuff, (uint8_t*)pTempRxBuff, cTxLen, spi_timeout);
   }

   /* Set device in SPI Receive interrupt mode again and store received data in pRxBuff of size 1 */
   HAL_SPI_Receive_IT(&hspi1, (uint8_t*)pRxBuff, cRxCmdLen);
}

I do not understand where the error is here. If it is only the master sending the data to the slave device, everything works fine. I have also matched the SPI configuration and bus clock speeds. I also tried changing the direction line to SPI_DIRECTION_1LINE as suggested elsewhere but it did not work. I also attempted using HAL_SPI_Transmit() and HAL_SPI_Receive() separately instead of HAL_SPI_TransmitReceive(), but the same issues still appear. Also, I would like to use HAL_SPI_TransmitReceive() on both to verify on the Master and the Slave that the devices are communicating properly.Any help would be greatly appreciated.

Thank you!

\$\endgroup\$
9
  • 2
    \$\begingroup\$ Sounds like you may have a sync problem either between trials or between expectation and reality. Consider using a scope or getting a cheap USB logic analyzer to run with sigrok. \$\endgroup\$ Jul 11 '20 at 2:12
  • 2
    \$\begingroup\$ You should not be doing all those SPI calls inside HAL_GPIO_EXTI_Callback. That callback is called directly from the EXTI interrupt handler, so you're then doing those blocking SPI calls from within the EXTI interrupt. Rather just set a volatile flag from EXTI and then in your main loop, do the SPI operations when it see's the flag get set.. \$\endgroup\$
    – brhans
    Jul 11 '20 at 3:04
  • 2
    \$\begingroup\$ Same goes for HAL_SPI_RxCpltCallback in the slave. You shouldn't be doing the blocking HAL_SPI_TransmitReceive from inside a callback (which is actually still inside the interrupt). Either use another volatile flag there, or do HAL_SPI_TransmitReceive_IT instead and make your HAL_SPI_RxCpltCallback stateful so that it remembers whether it last did a HAL_SPI_TransmitReceive_IT for 4 bytes or a HAL_SPI_Receive_IT for 1 byte. \$\endgroup\$
    – brhans
    Jul 11 '20 at 3:08
  • 2
    \$\begingroup\$ Also make sure that your trigger switch is properly debounced so that you are not accidentally initiating the SPI trigger sequences on top of each other. This is the first thing I thought of as I read your question. \$\endgroup\$ Jul 11 '20 at 5:20
  • \$\begingroup\$ @ChrisStratton What would the solution be if there are sync problems? One of the sync problems I could think of is that the Master's second transmission would probably start earlier because it will take time for the Slave device to check the first command byte sent by the Master. Do we also have to make the system clocks on both devices the same? The Master is currently operating at 16MHz but the Slave is operating at 8MHz. My assumption is that it should be fine because the SPI transaction would be clocked by the SPI Clock. \$\endgroup\$
    – Cimory
    Jul 11 '20 at 23:58
0
\$\begingroup\$

In SPI, MASTER and Slave are actually connected in a loop to the FIFO buffers. As shown in the picture.
Try flushing these data buffers and shift registers before each data frame transfer. On both master and slave controllers.

Diagram of SPI communication

(Image source: Electronics Hub - Basics of Serial Peripheral Interface (SPI))

\$\endgroup\$
1
  • \$\begingroup\$ Embedded Guy - Hi, As required by this site rule, when we include something in an answer (e.g. photo, image or text) which isn't our own original work, we need to properly reference (cite) it. That image has come from somewhere else, so in order to comply with that rule, it must be referenced. I found (what I believe to be) the original source and added that reference for you this time. Please can you do that in future? Thanks! (I recommend that you also read the tour and help center to see more site rules.) \$\endgroup\$
    – SamGibson
    May 26 at 14:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.