cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F103 SPI Slave strategy

DNeum.1
Associate II

I use STM32F103C8T6 as SPI slave and want to send data on request by master.

The master sends to the slave a byte, which has to be used to decide, what has to be done

or what data data has to be sent to the master, and a fill byte which is needed

to shift out the interesting byte to the master. In the slave, I use SPI RX interrupt, which

is called after the master has sent its request byte:

void spi_rxhandler(SPI_HandleTypeDef *hspi){
  leds_toggle(LED2);
 
  //check data from SPI rxbuffer
  switch(hspi->Instance->DR){
  case 0x47:
    execute_a_useful_function();
    break;
  case 0x8F:
    hspi->Instance->DR=0x12;
    break;
  case 0xC8:
    hspi->Instance->DR=0x34;
  break;
  default:
    hspi->Instance->DR=0x56;
    break;
  }
  __HAL_SPI_CLEAR_OVRFLAG(hspi);
}

But this is too late. The TX-Buffer has to be filled much earlier, this implementation

leads to sending the byte which is written to DR to the master in the next try, when

the master repeats its read attempt.

Transferred to the image 242 of the RM008, my slave has to read 0xA1 to decide to

send 0xF2 to master.

Is there a strategy to achieve this? I use the HAL API.

5 REPLIES 5

There's no magic, master has to wait until the slave calculates the answer. Toggle a pin to see when the answer is ready and change master's behaviour accordingly. One possible trick is that master sends one extra dummy byte between the "question" and "answer", but still have to be sure slave succeeds to calculate answer until that time.

You can improve latency by avoiding using Cube/HAL and increasing optimization level in compiler.

JW

DNeum.1
Associate II

Thank you for the quick reply. I made some additional tests.

The master sends at a speed of 125 KHz.

Even if I use the SPI1 peripheral without STM32 HAL in interrupt handler, which

means the stripped down interrupt handler looks this:

switch(SPI1->DR){
case 0x8F:
  SPI1->DR=0x12;
  break;
default:
  SPI1->DR=0x34;
  break;
}
uint32_t ovr=SPI1->SR;
(void)ovr;

and also after changing the optmization level from -Os to -O3, I observe the same behavior:

Master sends 0x8F FF. At first try, the master receives 0x00 00. At second and

all subsequent tries, the master receives 0x34 12. My (wrong) interpretation:

  1. 0x8F is shifted to MOSI, at the same time, 0x34 is shifted to MISO.  The 0x34 is the leftover from the last time slave wrote to SPI1->DR.
  2. SPI RX IRQ is triggered, slave sees 0x8F, writes 0x12 to DR
  3. 0xFF is shifted to MOSI, at the same time, 0x12 is shifted to MISO.
  4. SPI RX IRQ is triggered, slave sees 0xFF, writes 0x34 to DR

The open question is: If it would be that easy, why ends the first read attempt

with 0x0000 and not with 0x0012? So I assume, I made 2 mistakes:

  1. My interpretation is wrong
  2. My SPI slave initialization is wrong:
LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI1);
  __HAL_RCC_SPI1_CLK_ENABLE();
  HAL_SPI_DeInit(&hspi1);
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64;  //16MHz/64=250 kHz. Not relevant for slave
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.NSS = SPI_NSS_HARD_INPUT;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.Mode = SPI_MODE_SLAVE;
  HAL_SPI_Init(&hspi1);
  __HAL_SPI_ENABLE(&hspi1);
  //Enable RX interrupt
  HAL_NVIC_SetPriority(SPI1_IRQn,0,0);
  HAL_NVIC_EnableIRQ(SPI1_IRQn);
  __HAL_SPI_ENABLE_IT(&hspi1, SPI_IT_RXNE);

I don't have the option to let the master wait for a GPIO to signalize "ready to deliver data" or insert additional stuff bytes.

Instead of transmitting constants, increment and send some variable in master, and send it back by slave.

IMO slave does not "see" first byte of first transaction, maybe it's initialized later than master. It receives the second byte, and the "answer" to that is the third MISO byte i.e. first byte of second transaction.

JW

DNeum.1
Associate II

That sounds plausible. I did some further tests with incremented variables sent by master and try to send it back.

SPI-RX-handler:

  //receive data
  volatile uint8_t read_byte=SPI1->DR;
  //send back
  SPI1->DR=read_byte;
  //clear Flag
  volatile uint32_t ovr=SPI1->SR;
  (void)ovr;

The result:

Master send      Slave receive   Master receive
0x0000              0x0000          0x0000
0x0100              0x0100          0x0000
0x0200              0x0200          0x0000
0x0300              0x0300          0x0000
0x0400              0x0400          0x0000

Other Test: I increment read_byte by 1 before sending back to master. The result:

Master send      Slave receive   Master receive
0x0000               0x0000           0x0000
0x0100               0x0100           0x0102
0x0200               0x0200           0x0103
0x0300               0x0300           0x0104
0x0400               0x0400           0x0105
 
Slave restart
Master send       Slave receive    Master receive
0x0500               0x0500           0x0000
0x0600               0x0600           0x0107
0x0700               0x0700           0x0108
0x0700               0x0800           0x0109
 
Master restart
0x0000               0x0000           0x0101
0x0100               0x0100           0x0102
0x0200               0x0200           0x0103
0x0300               0x0300           0x0104

Next Test: I increment read_byte by 3. The result:

Master send      Slave receive   Master receive
0x0000              0x0000          0x0000
0x0100              0x0100          0x0304
0x0200              0x0200          0x0305
0x0300              0x0300          0x0306
0x0400              0x0400          0x0307
0x0500              0x0500          0x0308
0x0600              0x0600          0x0309

To check the received bytes, I wrote them to CAN-Bus (after SPI send back, just to do the SPI send back earlier earlier), so I am sure the slave receives both

bytes and can distinguis them.

How can this be interpreted? Both received bytes are incremented and sent back to master. Except for the first transaction, we are always "in time":

The first byte of transaction n is sent back while the second byte is received, and the second byte of transaction n is sent back while the first byte of

transaction n+1 is received.

But this is only possible if I pass the HAL API. If I want to do more complex operations (e.g. find the correct byte to send back depending on the first byte

received) I have to check if I need to use a higher clock my MCU.

The remaining problems are

  • the first transaction in every case
  • the case, when the DR data is not incremented, always sends back 0 for every byte.

I "solved" the problem. If I raise the clock of my STM32F103 to 24 MHz, the master receives the correct slave answer in every transaction up to 125 kHz SPI speed. If I raise the MCU clock to 64 MHz, I have correct transfers up to SPI speed 500 kHz.