[βœ…] Using a DMA -> PIO pipeline to write to GPIO pins one byte at a time (for a function generator)

May 24, 2024, 19:45

lpeter1997

I'd like to setup DMA in a way that it writes bytes to GPIO pins 0-7. AFAIK you'd write a little PIO program for that which would actually write out the bytes the DMA transferred. Once the DMA write finishes with the data, I'd immediately like to retrigger it to keep feeding the same sequence of bytes over and over again. AFAIK I can just chain another DMA channel that then gets chained back to the first one. This is the code I came up with, which - unsurprisingly - does not work. I'm good with C++, but the Pico hardware (and low-level microcontroller programming) is relatively new for me. The end goal is a function generator, if context helps. Any help/pointers in the right dir is appreciated
cpp
#include <cstdint>
#include <vector>
#include "hardware/dma.h"
#include "hardware/pio.h"

void setup_pio(PIO pio, uint sm) {
    auto program = pio_program_t {
        .instructions = pio_program,
        .length = sizeof(pio_program),
        .origin = -1,  
    };
    auto offset = pio_add_program(pio, &program);
    pio_sm_config c = pio_get_default_sm_config();
    sm_config_set_out_pins(&c, 0, 8);
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

void setup_dma(PIO pio, int dmaChan0, int dmaChan1, std::vector<std::uint8_t> const& data) {
    // Channel 0, which outputs to the PIO, which in turn outputs to the pins
    dma_channel_config c = dma_channel_get_default_config(dmaChan0);
    channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
    channel_config_set_read_increment(&c, true);
    channel_config_set_write_increment(&c, false);
    // Chain to channel 1 when done
    channel_config_set_chain_to(&c, dmaChan1);
    dma_channel_configure(dmaChan0, &c, &pio->txf[0], data.data(), data.size(), false);
    // Channel 1 just retriggers channel 0
    c = dma_channel_get_default_config(dmaChan1);
    channel_config_set_chain_to(&c, dmaChan0);
    dma_channel_configure(dmaChan1, &c, nullptr, &pio->txf[0], 1, false);
}

// Usage
int main() {
    auto dataPoints = std::vector<std::uint8_t> { / ... / };

    auto chan0 = dma_claim_unused_channel(true);
    auto chan1 = dma_claim_unused_channel(true);

    auto pio = pio0;
    uint sm = pio_claim_unused_sm(pio, true);
    setup_pio(pio, true);
    setup_dma(pio, chan0, chan1, dataPoints);
    dma_channel_start(chan0);
}

lpeter1997

I've refactored the code. Unfortunately because of discord char limits I had to shorten it, but a nicer commented and formatted version can be found here: <https://raspberrypi.stackexchange.com/questions/148077/using-dma-pio-to-write-function-sample-points-to-a-dac-chip-periodically>

oops.se

Why not use a git service?

lpeter1997

A what? πŸ˜…

lpeter1997

The only git I know is for software version control but I'm not sure how that comes to the Pico hardware <@796000224690307072>

oops.se

You have a section of code above is that hardware?

oops.se

And there is NO need to ping people!

lpeter1997

Ah you meant the code, you could have just said that but I guess you are above the servers only rule πŸ˜›

lpeter1997

Sorry for the ping, you can simply mute tho

lpeter1997

After a loook of headache - and investigating generated PIO programs -, I've found the missing piece, it was this call: pio_sm_set_consecutive_pindirs(pio, sm, 0, 8, true);

oops.se

What are you implying?