Skip to main content
DNeum.1
Associate II
October 28, 2020
Question

STM32F103 SPI Slave strategy

  • October 28, 2020
  • 4 replies
  • 1836 views

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.

This topic has been closed for replies.

4 replies

waclawek.jan
Super User
October 28, 2020

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
DNeum.1Author
Associate II
October 28, 2020

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.

waclawek.jan
Super User
October 28, 2020

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
DNeum.1Author
Associate II
October 29, 2020

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.

DNeum.1
DNeum.1Author
Associate II
January 6, 2021

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.