Clyde Pinky

Raspberry Pi Pico: DMA Part 1

I don’t know much about DMA, so having a Pico is a good opportunity to learn.

First Attempt

The SDK has a list of interesting functions. My understand is that the DMA allows me to copy data from A to B without involving the CPU in the copy. So let’s give this a go with the simplest possible program I can think of.

#include "hardware/dma.h"
#include "pico/stdlib.h"
#include <stdint.h>
#include <stdio.h>

int main()
{
    stdio_init_all();

    // required: true means that this will panic if there is no DMA channel
    int channel = dma_claim_unused_channel(true);

    // configuration - default is fine for a single word copy
    dma_channel_config_t config = dma_channel_get_default_config(channel);

    uint32_t source = 0xdeadbeef;
    uint32_t dest = 0x12345678;

    // transfer a single word
    uint32_t transfer_count = dma_encode_transfer_count(1);

    printf("Before transfer: %08x\n", dest);

    dma_channel_configure(channel, &config, &dest, &source, transfer_count,
                          true);

    printf("After transfer: %08x\n", dest);

    return 0;
}

Does it work?

Before transfer: 12345678
After transfer: 12345678

It does not. But we didn’t give it a chance. Adding sleep_ms(10) after the transfer:

    ...
    sleep_ms(10);
    printf("After transfer: %08x\n", dest);

And we get:

Before transfer: 12345678
After transfer: deadbeef

So the data was copied, but sleeping randomly isn’t great. How do we know if 10ms is enough? Or too much?

Second Attempt

Let’s create a semaphore:

static semaphore_t sem;

(yes, just a static variable, we’ll do better later).

Set it up:

    // initialise semaphore
    sem_init(&sem, 0, 1);

    // enable irq0 for DMA channel
    dma_channel_set_irq0_enabled(channel, true);

    // Configure the processor to run dma_handler() when DMA IRQ 0 is
    // asserted
    irq_set_exclusive_handler(DMA_IRQ_0, dma_handler);
    irq_set_enabled(DMA_IRQ_0, true);

And then wait for it:

    printf("Before transfer: %08x\n", dest);

    dma_channel_configure(channel, &config, &dest, &source, transfer_count,
                          true);

    sem_acquire_blocking(&sem);
    printf("After transfer: %08x\n", dest);

The DMA IRQ handler:

static void dma_handler(void)
{
    dma_channel_acknowledge_irq0(channel);
    sem_release(&sem);
}

Let’s run this:

Before transfer: 12345678
After transfer: deadbeef

OK, that seems to work, but are we actually getting the signal, or is it just going though and by some timing chance we’re getting the right answer? Let’s remove the call to sem_release() and try again:

Before transfer: 12345678

Well, it just hangs after that. So yes, the IRQ handler is getting called, we’re waiting for the semaphore and it’s working as expected.

Of course, just sitting and waiting for the transfer to happen… we might as well just use the CPU. The whole point is that we can do other things at the same time, and maybe also program the DMA to do stuff automatically. But for now this will be enough.