cancel
Showing results for 
Search instead for 
Did you mean: 

SPI Slave with DMA: How to receive packets of unknown length?

Geend
Associate

Ok guys. I think I'm slowly turining insane. Someone please help me. I already wasted so many hours on this...

I'm currently fighting with getting SPI to work in slave mode with DMA. So let me explain what I'm trying to do.

The STM32(F303RE) is connected to a RaspberryPi. The Raspi sends messages over SPI to the STM32 and excpects a response message. SPI Freuqentcy is only 500 kHz.

The message format is:

 MB1(1 byte)|MB2(1 byte)|messeageId(1 byte)|payloadLength(1 byte)|payload(payloadLength bytes)|CRC(4 bytes)

MB1(0x22) and MB2(0x33) are magic bytes I use to detect the start of a message.

The the RPi sends messages with arbitrary length to the STM32, waits a couple of ms and then starts a second spi transfer for the response (only transmitting 0x00 bytes, while the STM32 can write the response). Length of the response is known by the Raspi and the STM32. Length of the inital message is only known by the Raspi in advance... After some more ms the next message is send to the STM32.

There are two problems with my current implementation:

1) Data in the recieve buffer is sometimes prefixed by 1 to 4 0x00 bytes.

2) Sometimes the recieve buffer only contains seemingly random data (only in the first few bytes of the buffer. The remainding buffer is 0x00).

3) If 1 or 2 occurs the STM32 my code blocks for many ms and I can't figure out where it blocks.

I created a small example project with just the SPI code: https://github.com/Geend/Stm32DmaSpiSlaveTest/blob/master/Src/main.c

It's generated with Stm32CubeMX. The Stm32CubeMX project file is also in the repository.

I think the interesstings parts are the following functions in main.c:

void loop();

HAL_StatusTypeDef restartDma(uint16_t size, bool clearWB);

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin);

I also attached a DSView logic anylyzer log file and a few screenshots here.

The documentation of the HAL SPI API is a bit sparse. I'm 100% sure I'm not using it correctly. Especially the usage of HAL_SPI_Transmit_DMA, HAL_SPI_Receive_DMA, HAL_SPI_DMAStop, HAL_SPI_Abort and the handling of SPI NSS. Maybe my entire approch to this is wrong and there is a better solution...

So here is my question: How recieve SPI messages with unknown length on an STM32 using DMA?

5 REPLIES 5

Generally, if you leave SPI to receive into a circular buffer, you can poll NDTR to see and process data being received (there's a gotcha with NDTR in the dual-port DMA, but that's not the case of 'F3).

Now how much sophistication you want in that polling, whether doing it from the main() loop, or from a timer interrupt, or from a exti-restarted-timer-timeout interrupt, or whatever else what suits you, is up to you.

I don't Cube.

JW

Thanks for the answer. But don't I have to restart the DMA anyway when I switch from Receiving to Transmitting? I do this for every packet, so using circular mode would be kind of pointless I think

> don't I have to restart the DMA anyway when I switch from Receiving to Transmitting?

There's no real reason to do it but then you can also do it if you wish.

Except that then you'll see in Rx DMA some of those bytes which were received during Tx and remained in the SPI FIFO (or Rx buffer), so you'd need to purge it before the Rx. I'm not sure how to do that, maybe disabling/enabling SPI will do it, but maybe you'd need to reset SPI in RCC. This is a recurring question here and it's usually not documented well, but I am not going to investigate all the many variants of SPI ST managed to put into the various models through the time.

The point for your original question - how to manage Rx of unknown lengths - is still the same - have ready Rx DMA when you want to receive, with sufficient space for the maximum length message, and then just poll NDTR to see how bytes arrive. There's surely length transmitted by the master, or some endmark, so as soon as you have enough data in DMA buffer you can start to process them.

JW

turboscrew
Senior III

There is at least 4 bytes (the CRC) after length field. Maybe you could first zero at least the length field (in the buffer), and then receive 8 bytes (I guess that's the minimum). During receive you can poll the length field, and if it's not zero, it's the length. Then after the 8-byte receive is done, start receiving the rest. Now you know how much is left. If the length field doesn't change and the 8 byte DMA finishes, the length was zero. The four bytes after length should give you a moment to prepare for the next receive.

JMogo.1
Associate II

Hi @Geend​ just in case that you have not figure this out, you may want to check if you micro controller uses cache for instructions or data (I'm not sure on the M3 but the H7 ad F7 has it), in that case you need to invalidate the cache so you can read the latest data. Usually using this function in the HAL SCB_InvalidateDCache_by_Addr

This may not have anything to do.