cancel
Showing results for 
Search instead for 
Did you mean: 

STM32 F0 SPI slave proper setup

Hello there,

So far I was designing only SPI masters in the STM32 MCU's- now I am doing a slave application and I am having problems.

First of, I was surprised that the STM32CubeF0-master repository doesn't consist of a single SPI slave example (please correct me if I am wrong).

I am trying to accomplish a simple functionality, that can be described as this:

  1. Use software nCS line,
  2. As soon as nCS goes low, turn on TxRx DMA transfer via HAL_SPI_TransmitReceive_DMA,
  3. Obtain and transfer at the same time the amount of bytes specified in the 4th parameter,
  4. On complete TxRx callback use the same function to enable DMA as in point 2,
  5. On nCS going high do HAL_SPI_DMAStop.

For some reason, my config only works without using HAL_SPI_DMAStop. I am not sure how is using it breaking the program flow. Here is my function for slave SPI DMA management:

rc_t spis_manage(spis_t* const spis, const bool on)
{
	UTIL_ASSERT(spis, e_rcPar);
	rc_t stat;
 
	if (HAL_SPI_STATE_READY != HAL_SPI_GetState(spis->hSpi))
	{
		if (e_rcOk != (stat = util_status(HAL_SPI_DMAStop(spis->hSpi))))
			return stat;
	}
 
	if (on)
	{
		stat = util_status(HAL_SPI_TransmitReceive_DMA(spis->hSpi,
				(uint8_t*)spis->bufTx, (uint8_t*)spis->bufRx, spis->size));
	}
 
	return stat;
}

It is used as follows;

  1. nCS goes low: spis_manage(x, TRUE);
  2. data transfer/ receive completed: spis_manage(x, TRUE),
  3. nCS goes high: spis_manage(x, false);

The incorrect behavior I am observing is that only the 1rst transfer is correct, i.e.

  1. nCS goes low,
  2. specified amount of bytes are transfered,
  3. nCS goes high.

After I repeat the operation, it takes 3 bytes more pushed from the SPI master to trigger the transfer/ receive complete callback, and I dont know why.

Is my program flow correct? I would appreciate all help.

11 REPLIES 11
TDK
Guru

Here's a slave SPI example:

https://github.com/STMicroelectronics/STM32CubeF0/tree/master/Projects/STM32F0308-Discovery/Examples/SPI/SPI_FullDuplex_ComDMA

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

Hi, thank you for answer.

Now I see that the example is indeed intended for SPI slave as well. Unfortunetaly, its is very basic. In the form from the example my program works as well. The problem starts when HAL_SPI_TransmitReceive_DMA is called more than once and when HAL_SPI_DMAStop is used at all. The example doesnt handle the cases in which the transfer is aborted before the expected amount of data is received/ transmitted.

Do you have any experience with SPI slave applications on STM32 and HAL?

I have even tried a complete init and deinit approach before and after each transaction, but that yields the same effects...

rc_t spis_manage(spis_t* const spis, const bool on)
{
	UTIL_ASSERT(spis, e_rcPar);
 
	const HAL_SPI_StateTypeDef state = HAL_SPI_GetState(spis->hSpi);
	rc_t stat;
 
	if ((HAL_SPI_STATE_READY) != state && (HAL_SPI_STATE_RESET != state))
	{
		if (e_rcOk != (stat = util_status(HAL_SPI_DMAStop(spis->hSpi))))
			return stat;
	}
 
	if (e_rcOk != (stat = util_status(HAL_SPI_DeInit(spis->hSpi))))
		return stat;
 
	if (on)
	{
		if (e_rcOk != (stat = util_status(HAL_SPI_Init(spis->hSpi))))
			return stat;
 
		stat = util_status(HAL_SPI_TransmitReceive_DMA(spis->hSpi,
				(uint8_t*)spis->bufTx, (uint8_t*)spis->bufRx, spis->size));
	}
 
	return stat;
}

The code works only if I never use HAL_SPI_DMAStop... Here is an exemplary Bus pirate log (I am using it as master device).

SPI>[
/CS ENABLED
SPI>%:10 r:32 %:1
DELAY 10ms
READ: 0x7D 0x38 0x99 0x2E 0x08 0x2F 0x32 0x30 0x0D 0x2F 0x97 0x33 0x0A 0x31 0x78 0x33 0x28 0x0A 0x7F 0x0A 0xFB 0x09 0x0F 0x0A 0x24 0x0A 0x79 0x09 0x84 0x09 0x84 0x09
DELAY 1ms
SPI>]
/CS DISABLED
SPI>[
/CS ENABLED
SPI>%:10 r:32 %:1
DELAY 10ms
READ: 0xFF 0xFF 0xFF 0x7D 0x38 0x90 0x2E 0x11 0x2F 0x32 0x30 0x0D 0x2F 0x97 0x33 0x0A 0x31 0x78 0x33 0xE6 0x0A 0x7F 0x0A 0xF1 0x09 0x0F 0x0A 0x24 0x0A 0x79 0x09 0x84
DELAY 1ms
SPI>%:10 r:1 %:1
DELAY 10ms
READ: 0x09
DELAY 1ms
SPI>%:10 r:1 %:1
DELAY 10ms
READ: 0x8E
DELAY 1ms
SPI>%:10 r:1 %:1
DELAY 10ms
READ: 0x09
DELAY 1ms
SPI>]
/CS DISABLED

As you see the second read lacks 3 bytes (0xFF 0xFF 0xFF). I need to read them manually one by one later in order to trigger the TxRx complete callback.

I have rounded the problem to the fact that it is not possible to properly abort the on going SPI transaction. Once the HAL_SPI_TransmitReceive_DMA is called, it doesnt matter if I abort and call HAL_SPI_TransmitReceive_DMA again- the buffered byte is still the one from the first call... Init + deinit of the whole SPI peripheral doesnt help here. Is there any procedure for properly aborting ongoing rxtx SPI transaction? I even turned off DMA and tried doing the same thing using interrupt approach for every 1 byte, same effect :(

S.Ma
Principal

Remember that in embedded MCUs, your SPI DMA TX/RX buffers will be anyway fixed size to fit them all. So the length byte will just cause SW latency to a HW continuous data stream.

As such, just forget all the SPI and DMA interrupts.

Even before NSS goes low, make sure the DMA RX channel points to your buffer in CIRCULAR mode. ==> If the master sends too many bytes, your SW won't 0-day poorly.

Use NSS EXTI interrupts (rise and fall). Of course, the master will need to pause communication if slaves are expected to do/setup something.

For example, trigger an interrupt on NSS rise edge : you have received potentially something, read the message, clear it, set the DMA for next incoming (I just wish there was a programming language to just describe the scheme like this 🙂 )

The master will have to have a minimum pause time between NSS high and low tough.

And do use full duplex communication before digging and optimizing/sweating.

If you'd like to complexity/abstract more, you can imagine a interrupt based handler/queue state machine which receives from a "SW FIFO" the SPI data block transfers instructions. (then the challenge is priority, latency, error management). Food for SPI Master thoughts...

S.Ma
Principal

Note: You can even fully reset SPI and DMA once NSS is high, usually this brute force works .

I guess your slave will someday send back data to host:

Know that if your SPI Slave has HW FIFOs, these FIFOs will be always filled by DMA.

When NSS goes high, these FIFO stays filled... and they will regurgitate their old data in the next transaction (as NSS is not an influential SPI signal). Looking for a FIFO flush bit? It's the SPI HW RESET peripheral bit. SPI enable bit is not potent here.

Hi thank you for answer,

The thing is I am not even using DMA anymore. At this point I am neraly certain that there is a bug in the F0 HAL libs, but havent found out where yet.

Find the following exemplary scenario:

  1. setup the TxRx transfer for 1 byte only with HAL_SPI_TransmitReceive_IT.
  2. receive/send 1 byte, then call HAL_SPI_TransmitReceive_IT, but increment tx and rx buffers addresses.
  3. now abort the transfer with HAL_SPI_Abort_IT and then even call spi deinit + spi init.
  4. setup as in point one.
  5. when the SPI master transfers 1 byte now, you will not give him what you have setup in point 4, but whatever was there after the transaction in point 2.

In general this means there is no way to reset the buffers addresses HAL_SPI_TransmitReceive_IT uses, unless an actual transaction is made by the master.

In order to overcome this, I had to always send 1 more byte via master, since the 1st received byte will always be invalid in case an abort was ever made in the program.

I use software NSS and handle it as external rising/ falling edge interrupt in order to setup and finish transactions.

Looking for a FIFO flush bit? It's the SPI HW RESET peripheral bit

This is exactly what I am looking for. Afaik the only way to flush the data is to read it- do I need to manually read the DR register?

You can't flush a SPI Slave FIFO TX, only the master sending SCK clocks can.