cancel
Showing results for 
Search instead for 
Did you mean: 

Best way to use DMA without knowing size of SPI transfer ?

sde c.1
Senior II

Hi,

device : STM32G473VBT6

What is the best way to process SPI data when we don't know how many bytes will come from the SPI master ?I use the SPI in full duplex slave.

The problem with DMA is that the DMA buffer is circular, where my processing functions are not. I don't have the time to copy bytes to another buffer, and code to access the circular buffer slows down significantly when processing 320 bytes .

i need to access the buffer as fast as i can.

I have used an easy solution ,see under "// reset spi to flush FIFO" , this reset the SPI every interrupt and set the DMA buffer back to element 0 of spi_buf_in.

The disadvantage is that this reset also takes 40µs at 170 Mhz.

This works mostly perfectly, but sometimes after 1000's SPI transfers my spi_buf_in data is corrupt (compared to the data i see trough the logic analyzer)

My guess is that this corruption comes because of the unconventional reset of the SPI.

How do i read incoming SPI data most elegantly at the highest speed ?

// SPI enable line will have positive flank once the data is transferred from PI to STM, this will be used as interrupt to
 
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){
	uint16_t SPISize;
	uint32_t head;
 
	if(GPIO_Pin == GPIO_PIN_12){ // spi enable line interrupt at positive edge
 
		head = __HAL_DMA_GET_COUNTER(phMainSPI->hdmarx); // get number of bytes received by the SPI rx DMA
		SPISize = (uint16_t) (SPI_SIZE - head);
 
                // processing spi_buf_in data ........
 
		// reset spi to flush FIFO
	       HAL_SPI_DeInit(phMainSPI);
	        __HAL_RCC_SPI2_FORCE_RESET();
	       __HAL_RCC_SPI2_RELEASE_RESET();
	       HAL_SPI_Init(phMainSPI);
		HAL_SPI_TransmitReceive_DMA(phMainSPI,spi_buf_out,spi_buf_in,320);
	}
}

1 ACCEPTED SOLUTION

Accepted Solutions
sde c.1
Senior II

this seems to do the trick, takes only 11µs and reset DMA pointer to the beginning of the buffer

#define RESETSPI {\
	HAL_SPI_Abort(&hspi2);\
	__HAL_RCC_SPI2_FORCE_RESET();\
	__HAL_RCC_SPI2_RELEASE_RESET();\
}

View solution in original post

9 REPLIES 9

You don't need to reinitialize SPI, just stop/start the Rx DMA, as outlined in RM. Basically it's: clear DMA_CCRx.EN, wait until it clears, reset status flags if needed, set DMA_CCRx.EN.

I don't know how to do that in Cube, I don't Cube.

JW

TDK
Guru

> How do i read incoming SPI data most elegantly at the highest speed ?

Set it up as a circular DMA transfer so you never need to deinitialize the peripheral. Poll for the number of characters available in the ring buffer by reading the NDTR register and keeping track of the head/tail pointers.

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

If I understood correctly, problem is in processing the received data, in situ for speed. Many algorithms become a nightmare should they account for wraparound - think FFT.

JW

i tried but that doesn't work, i use the HAL library and cube, i think i must initialise much more in that case

this takes time too, and limits the use of memory processing functions, like memcpy. but i can make these function myself with the above tip. And use them inline to pump up the speed.

using the ringbuffer seems complex too, for example each time SPI cs goes up, i re-initialise the SPI DMA to send and receive the next data with this command

HAL_SPI_TransmitReceive_DMA(phMainSPI,spi_buf_out,spi_buf_in,SPI_SIZE);

It's unclear how to manage the circular buffer with these kind of HAL functions.

I just need an easy and fast way (faster then 40µs) to initialise the DMA to write at the beginning of the buffer each SPI CS interrupt . Any idea how to do that?

With circular DMA you don't need to stop/reinitialize, simply remember where in buffer was the end of the previous chunk of data and after receiving next chunk of data, start processing there.

If you don't want to use circular DMA but you want stop/start, and using Cube/HAL does not work or takes too long, well then don't use Cube/HAL.

JW

TDK
Guru

> I just need an easy and fast way (faster then 40µs) to initialise the DMA to write at the beginning of the buffer each SPI CS interrupt . Any idea how to do that?

Use register level access. Stop the peripheral, reset the DMA for the new transfer, and start it again. HAL_SPI_DeInit does many more things than just stop the peripheral, such as deinitializing pins.

There is probably something that can be done with HAL_SPI_DMAStop as well, but I don't know the details or if it will execute in <40us.

As JW says, if HAL is too slow, don't use it.

I personally think the circular buffer is a better solution and that memory management is straightforward to check when the buffer wraps around and when it doesn't. You will occasionally lose time copying data, but you will save time by not re-initializing SPI/DMA. Copying 320 bytes is fast.

If you feel a post has answered your question, please click "Accept as Solution".
sde c.1
Senior II

this seems to do the trick, takes only 11µs and reset DMA pointer to the beginning of the buffer

#define RESETSPI {\
	HAL_SPI_Abort(&hspi2);\
	__HAL_RCC_SPI2_FORCE_RESET();\
	__HAL_RCC_SPI2_RELEASE_RESET();\
}