cancel
Showing results for 
Search instead for 
Did you mean: 

STM32U5 LPDMA with SPI and circular buffer

Filling5681
Associate

I have a sensor (SPI slave) that follows this process:

  1. Master sends configuration
  2. Master sends start command
  3. Every millisecond (or according to configuration):
    1. The slave signals that data is ready by pulling a pin low
    2. The master pulls CS low
    3. The master sends a clock signal and reads the MISO line while the slave transfers data (in my case, 11 bytes)
    4. The master pulls CS high

I've been able to configure the LPBAM to follow this basic process without too many issues. I set up a queue in the LPBAM tool in STM32CubeIDE with a single circular SPI receive data node without any trigger. The SPI device itself is set with a trigger on EXTI4, which I've connected to the data ready pin of the sensor.

However where this falls apart is collecting the read data in RAM. I want to put the sensor readings in a circular buffer or something similar and only wake the CPU when the buffer is full but I can't figure out how to make it work. If I set the "Number of Data" on the SPI receive data node, the clock signal is just held high longer.

I tried reading through the reference manual and came up with the following. The main difference that I hoped would help is that I can set the size of the SPI transfer in SPI3's CR2 register and the size of the DMA buffer separately. My hope was that the DMA would transfer one byte per request and the SPI would send one request per byte (the FIFO is set elsewhere). However I get a DMA interrupt after only two SPI reads (and then SPI chokes up and stops, I'm guessing because I haven't cleared any of its flags):

__HAL_SPI_DISABLE(&hspi3);
// Enable RX DMA requests.
SET_BIT(SPI3->CFG1, SPI_CFG1_RXDMAEN);
// Switch to simplex receiver.
SPI3->CFG2 |= (0b10 << 17);
// Set data size.
SPI3->CR2 |= 11;
// Set trigger to EXTI4 falling edge.
SPI3->AUTOCR |= (SPI_AUTOCR_TRIGEN | SPI_AUTOCR_TRIGPOL | (4 << 16));

// Reset LPDMA channel 0.
SET_BIT(LPDMA1_Channel0->CCR, 0x2);
// Enable error interrupts and half/complete transfer interrupts.
SET_BIT(LPDMA1_Channel0->CCR, (1<<14) | (1<<12) | (1<<11) | (1<<10) | (1<<9) | (1<<8));
// DINC, everything else default.
SET_BIT(LPDMA1_Channel0->CTR1, (1<<19));
// Set request to spi3 RX, BREQ to 0.
SET_BIT(LPDMA1_Channel0->CTR2, (0<<11) | 2);
// Events on block level.
CLEAR_BIT(LPDMA1_Channel0->CTR2, (1<<31) | (1<<30));
// Set size of buffer.
SET_BIT(LPDMA1_Channel0->CBR1, 500);
// Set source to SPI3 RX register.
LPDMA1_Channel0->CSAR = (uint32_t)&SPI3->RXDR;
// Set destination to buffer.
LPDMA1_Channel0->CDAR = (uint32_t)READBUFFERNAME;

// Enable LPDMA Channel 0.
SET_BIT(LPDMA1_Channel0->CCR, 1);

// Enable SPI3.
__HAL_SPI_ENABLE(&hspi3);

I found AN5816 and the presentation "DMA: Circular buffering &
double buffering" but if I understand correctly, both of these approaches rely on circular buffer support in the ADC node itself, which is missing in the SPI node.

The best I can do at the moment is to trigger an interrupt on every SPI receive and manually check on the CPU whether CDAR is approaching the end of the buffer. If it is, restart DMA at the start of the buffer. Restarting the DMA is so slow that I can easily lose a reading though, which isn't permitted in my application.

Aside from that, I could electrically connect the data ready output from the sensor into one of the LPTIM peripherals and use the timer to trigger an interrupt when the number of pulses reaches the buffer size but that feels indirect and hacky. I'd prefer to at least count transfer complete events from the LPDMA.

I feel like this should be a common use-case but I couldn't find any useful documentation or examples for it.

Does anyone have any hints?

0 REPLIES 0