Skip to main content
Explorer
September 3, 2024
Solved

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

  • September 3, 2024
  • 5 replies
  • 3811 views

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.

    This topic has been closed for replies.
    Best answer by TDK

    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

     

    5 replies

    Super User
    September 3, 2024

    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.

    Graduate II
    September 3, 2024

    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.

    Explorer
    September 3, 2024

    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.

    Graduate II
    September 4, 2024

    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.

    Explorer
    September 4, 2024

    > 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.

    Graduate II
    September 4, 2024

    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 ....

    TDKAnswer
    Super User
    September 4, 2024

    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

     

    Explorer
    September 4, 2024

    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.