SPI through DMA

Hi everyone.

I’m new to RIOT OS and I using it with stm32f103c8. I want to transfer data to SPI using DMA. It’s just not clear for me what to do. Basically, I already controlling WS2812b with SPI, and want to get rid of spi_transfer_bytes calls, and hand over this job to DMA controller. There are just no info of how to do this.

#include "periph/gpio.h"
#include "periph/spi.h"
#include "ztimer.h"
#include <board.h>
#include <stdint.h>
#include "periph/cpu_dma.h"

#define LED_COUNT (60)
#define RESET_PULSE (60)
#define PIXELS_STACK_SIZE (LED_COUNT * 24 + 60)

static uint8_t PIXELS_STACK[PIXELS_STACK_SIZE];

int main(void) {
    ztimer_sleep(ZTIMER_USEC, 3000000);
    gpio_toggle(LED0_PIN);

    spi_acquire(0, SPI_CS_UNDEF, SPI_MODE_1,  KHZ(5200));

    while (1) {
        for (int i = 0; i < PIXELS_STACK_SIZE - RESET_PULSE; ++i) {
            PIXELS_STACK[i] = 0b11111100;
        }

        spi_transfer_bytes(0, SPI_CS_UNDEF, false, &PIXELS_STACK, NULL, PIXELS_STACK_SIZE);

        gpio_toggle(LED0_PIN);
        ztimer_sleep(ZTIMER_USEC, 1000000);
        
        for (int i = 0; i < PIXELS_STACK_SIZE - RESET_PULSE; ++i) {
            PIXELS_STACK[i] = 0b10000000;
        }

        spi_transfer_bytes(0, SPI_CS_UNDEF, false, &PIXELS_STACK, NULL, PIXELS_STACK_SIZE);

        gpio_toggle(LED0_PIN);
        ztimer_sleep(ZTIMER_USEC, 1000000);
    }

}

I assume that I need to call dma_configure with right params, although I’m not sure. It’s not clear for me what is destination buffer in my case.

/**
 * @brief   Configure a DMA stream for a new transfer
 *
 * @param[in]  dma     logical DMA stream
 * @param[in]  chan    DMA channel (on stm32f2/4/7, CxS or unused on others)
 * @param[in]  src     source buffer
 * @param[out] dst     destination buffer
 * @param[in]  len     number of transfers to perform
 * @param[in]  mode    DMA mode
 * @param[in]  flags   DMA configuration
 *
 * @return < 0 on error, 0 on success
 */
int dma_configure(dma_t dma, int chan, const volatile void *src, volatile void *dst, size_t len,
                  dma_mode_t mode, uint8_t flags);

Also, I don’t understand how can I setup SPI (spi_acquire call). Maybe there already SPI implementation with using DMA? I just can’t find it.

UDP: I finally found a .c file with SPI impl on STM. I saw the usage of DMA in it. So, SPI on stm32 uses DMA already?

But I’m still curios about the destination param. Here is example from RIOT/cpu/stm32/periph/spi.c (row 264):

        dma_setup(spi_config[bus].tx_dma,
                  spi_config[bus].tx_dma_chan,
                  (uint32_t*)&(dev(bus)->DR),
                  DMA_MEM_TO_PERIPH,
                  DMA_DATA_WIDTH_BYTE,
                  0);

dev is a function:

static inline SPI_TypeDef *dev(spi_t bus)
{
    return spi_config[bus].dev;
}

What is DR? And where I can find it?

When you enable periph_dma the SPI api will automatically use DMA for all transfers if the SPI peripheral has .tx_dma and .rx_dma configured.

There is still room for improvement though: There should be some heuristic to not use DMA for transfers below a certain treshold as e.g. for single byte transfers the DMA setup overhead will take longer than the single byte transfer would have taken.

In your case you would also not need .rx_dma as your use-case is TX only - but this can still be fixed in the driver.