cancel
Showing results for 
Search instead for 
Did you mean: 

Difference between DMA normal mode and circular mode - using SPI to sample a fast signal

Lukasz Iwaszkiewicz
Associate III

Dear all. I stumbled across something I don't really understand. I'm trying to detect a signal using SPI1 on STM32f723 as fast as possible and fill a buffer. I configured SPI1 in SPI_DIRECTION_2LINES_RXONLY mode, set prescaler to SPI_BAUDRATEPRESCALER_2 hoping for 54MHz (I'm running 216MHz clock). Then I fed 6.75MHz square signal to the MISO. And when I configured DMA2 in DMA_NORMAL I get consistent values like 0x0f, 0x0f, 0x0f .... etc. However, when I switch to DMA_CIRCULAR i observe some kind of a shift in the data, like I was receiving more 0s than 1s. Is it even possible (I know it's a hack) to use SPI to consistently sample a signal at 54MHz? Maybe all it is able to guarantee is 54MHz during a single byte transfer, but it is adding some small delays between the consecutive bytes? My code:

 

void spiDmaConfig ()
{
        SPIx_SCK_GPIO_CLK_ENABLE ();
        SPIx_MISO_GPIO_CLK_ENABLE ();
        SPIx_CLK_ENABLE ();
        DMAx_CLK_ENABLE ();

        GPIO_InitTypeDef GPIO_InitStruct = {0};

        GPIO_InitStruct.Pin = SPIx_SCK_PIN;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = SPIx_SCK_AF;
        HAL_GPIO_Init (SPIx_SCK_GPIO_PORT, &GPIO_InitStruct);

        GPIO_InitStruct.Pin = SPIx_MISO_PIN;
        GPIO_InitStruct.Alternate = SPIx_MISO_AF;
        HAL_GPIO_Init (SPIx_MISO_GPIO_PORT, &GPIO_InitStruct);

        /*--------------------------------------------------------------------------*/

        DmaHandle.Instance = SPIx_RX_DMA_STREAM;

        DmaHandle.Init.Channel = SPIx_RX_DMA_CHANNEL;
        DmaHandle.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
        DmaHandle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_1QUARTERFULL;
        DmaHandle.Init.MemBurst = DMA_MBURST_SINGLE;
        DmaHandle.Init.PeriphBurst = DMA_PBURST_SINGLE;
        DmaHandle.Init.Direction = DMA_PERIPH_TO_MEMORY;
        DmaHandle.Init.PeriphInc = DMA_PINC_DISABLE;
        DmaHandle.Init.MemInc = DMA_MINC_ENABLE;
        DmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        DmaHandle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
        DmaHandle.Init.Mode = DMA_NORMAL; // <------- Here I tried CIRCULAR.
        DmaHandle.Init.Priority = DMA_PRIORITY_VERY_HIGH;

        HAL_DMA_Init (&DmaHandle);

        /* Associate the initialized DMA handle to the the SPI handle */
        __HAL_LINKDMA (&SpiHandle, hdmarx, DmaHandle);

        /*--------------------------------------------------------------------------*/

        SpiHandle.Instance = SPIx;
        SpiHandle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
        SpiHandle.Init.Direction = SPI_DIRECTION_2LINES_RXONLY;
        SpiHandle.Init.CLKPhase = SPI_PHASE_1EDGE;
        SpiHandle.Init.CLKPolarity = SPI_POLARITY_HIGH;
        SpiHandle.Init.DataSize = SPI_DATASIZE_8BIT;
        SpiHandle.Init.FirstBit = SPI_FIRSTBIT_MSB;
        SpiHandle.Init.TIMode = SPI_TIMODE_DISABLE;
        SpiHandle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
        SpiHandle.Init.CRCPolynomial = 7;
        SpiHandle.Init.NSS = SPI_NSS_SOFT;
        SpiHandle.Init.Mode = SPI_MODE_MASTER;

        if (HAL_SPI_Init (&SpiHandle) != HAL_OK) {
                /* Initialization Error */
                Error_Handler ();
        }

        /*--------------------------------------------------------------------------*/

        if (HAL_SPI_Receive_DMA (&SpiHandle, aRxBuffer0, BUFFER_SIZE) != HAL_OK) {
                /* Transfer error in transmission process */
                Error_Handler ();
        }

        /*--------------------------------------------------------------------------*/

        HAL_Delay (200);
        Error_Handler (); // Here I've got a breakpoint and I check the aRxBuffer0 contents
}

 

 Thanks.

1 ACCEPTED SOLUTION

Accepted Solutions
TDK
Guru

Logically, the CIRCULAR setting will not affect data getting into the buffer via DMA in a single pass. No mechanism for that. But since you set a breakpoint, and then read out the data, it seems like the data should be from a single pass. But maybe it's not.

Is DMA happening in the background when the chip is paused? Add new live expressions that show the same values. Are the value different? That's likely the issue. For example, I added "TIM1->CNT" twice here while at the breakpoint. Timer runs in the background, so value constantly changes but it only updates once when at the breakpoint.

TDK_0-1725456615334.png

 

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

View solution in original post

9 REPLIES 9
TDK
Guru

What you're doing should work. In circular mode, there won't be a delay between bytes. What are the exact values you're receiving and why do you think they're off?

Note that clocks are not perfect. The external clock will not be synchronized to your square wave input and the two will drift over time. With circular DMA in which the buffer is constantly updating, you will need to ensure you read out the data quickly enough.

Perhaps write the code in main() instead of Error_Handler()? The issue is likely with how you're reading out data. You should be using the half-complete and full-complete flags or callbacks to process data.

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

This would be a case for DBM = Double Buffer Mode in DMA, with which you can use 2 buffers.

Don't know if this is already supported in HAL / Cube, but I think the F723 DMA has this feature.

... and make the buffers big enough so you can work with one while the other is filled by DMA.

Thanks guys.

> "What are the exact values you're receiving and why do you think they're off?"
In DMA_NORMAL, with SPI1 running at 54MHz, and function gen. feeding 6.75Mhz square (6.75 == 54/8) i get:

LukaszIwaszkiewicz_0-1725373567917.png

As you can see we get 1e, 1e, 1e which in binary looks like 00011110 00011110 00011110 00011110 .... so IMHO this what it should look like. But the moment I replace DMA_NORMAL with DMA_CIRCULAR (or I play with double buffer mode which in turn enables circular mode) i get:

LukaszIwaszkiewicz_1-1725373721404.png

When I try to convert these to binary I get something like:

11110000
11110000
01111000
01111000
00111100
00111100
00011110
00011110
00001111
00001111
10000111
10000111
10000111
11000011
11000011
11100001
11100001
11110000
11110000
11110000
01111000
00111100
00111100

This is why I think "something's off"

> "Note that clocks are not perfect. The external clock will not be synchronized to your square wave input and the two will drift over time."

True. So I should maybe try to feed SCK into MISO? What do you think? Then I should get perfect 0b10101010101010 .... should I?

> "You should be using the half-complete and full-complete flags or callbacks to process data."

I put a breakpoint in half-complete and observed the same results.

> "This would be a case for DBM = Double Buffer Mode in DMA, with which you can use 2 buffers."

I did exactly that at first and this is how I stumbled across this. Ten, simplifying the code I discovered that simply DMA_CIRCULAR is doing this. I've read that enabling double-buffer the circular mode gets enabled automatically.

LCE
Principal

Try as you suggested, use a synchronized clock.

I can't see why CIRCULAR should make any problems, using this with SAI / I2S for audio on F767 / H7xy without glitches.

How do you output and check data? Debugger or UART? Both might be bottlenecks for data throughput, not only if not copied to an extra output buffer.

> How do you output and check data? Debugger or UART? Both might be bottlenecks for data throughput, not only if not copied to an extra output buffer.

I stop on a break point and check the memory (1024B buffer). I did that after a delay and in an rx-complete ISR.

TDK
Guru

Logically, the CIRCULAR setting will not affect data getting into the buffer via DMA in a single pass. No mechanism for that. But since you set a breakpoint, and then read out the data, it seems like the data should be from a single pass. But maybe it's not.

Is DMA happening in the background when the chip is paused? Add new live expressions that show the same values. Are the value different? That's likely the issue. For example, I added "TIM1->CNT" twice here while at the breakpoint. Timer runs in the background, so value constantly changes but it only updates once when at the breakpoint.

TDK_0-1725456615334.png

 

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

I had similar issue, bit shifting in received SPI_RX data array. Always use Circular mode, so can't comment if it persists in Normal.

I resolved this nuisance by setting TI mode:

        SpiHandle.Init.TIMode = SPI_TIMODE_DISABLE;

try to enable. Also in my setup I had CS pin (NSS) driven by hardware, so SPI was able to run in "Sync".

In case of absense CS input from external peripheral device, I'd try to configure a Timer with external clock, than use it as a divider over OC unit, routing output pin to SPI-NSS.

Jump back in teory. SPI data is sampled on edge clk line = data is based here as valid. When your input signal isnt based on this, your result will random when edge of SDA in is close to CLK edge... Irelevant how signal is sapled, when is async. Normal or circular changes nothink in it, only can change sampling more precise with circular and then more close to edges long time. In normal mode your code must reinit DMA and this create moved edges ....

Thanks everybody! I think TDK got it right! I added __HAL_DMA_DISABLE (hdma); in SPI_DMAReceiveCplt and set a breakpoint just after disabling the DMA and everything works smoothly now. I see all 0xf0 values even with circullar mode. The problem is that previously GDB was reading the 1024 long array much slower than DMA was storing values into it. And due to small clock difference between my function gen and the board itself I saw this "drift".