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.