0

I'm attempting to make a function-generator (or AWG) with a Raspberry Pi Pico. For that, I'd like to utilize the DMA and PIO capabilities of the chip. On a DMA channel I would copy and direct sample points to a small PIO program that writes each byte to 8 GPIO legs. Once the copy is done, the channel would restart, which I attempted doing by chaining the first DMA channel to a second one, which would overwrite the read address of the first one, then be chained to it.

This is a minimal code I've tried to come up with after looking at some samples, utilizing the C/C++ SDK:

#include <cstdio>
#include <cstdint>
#include <vector>
#include "pico/stdlib.h"
#include "hardware/dma.h"
#include "hardware/pio.h"

int main() {
    stdio_init_all();

    // Build a simple square wave sample buffer of 4096 samples
    auto samplePoints = std::vector<std::uint8_t>(4096);
    for (auto i = 0; i < 4096; ++i) {
        samplePoints[i] = (i < 2048) ? 0 : 255;
    }

    auto* samplePointsData = samplePoints.data();

    // Allocate 2 DMA channels
    // The data channel will output 32 bits at a time to the PIO state machine
    // The control channel will simply restart the data channel when it's done
    auto dmaDataChan = dma_claim_unused_channel(true);
    auto dmaCtrlChan = dma_claim_unused_channel(true);

    // Allocate the PIO state machine
    auto pio = pio0;
    auto sm = pio_claim_unused_sm(pio, true);

    // Configure the control channel to restart the data channel when it's done
    auto ctrlChanConfig = dma_channel_get_default_config(dmaCtrlChan);
    channel_config_set_transfer_data_size(&ctrlChanConfig, DMA_SIZE_32);
    channel_config_set_read_increment(&ctrlChanConfig, false);
    channel_config_set_write_increment(&ctrlChanConfig, false);
    channel_config_set_chain_to(&ctrlChanConfig, dmaDataChan);
    channel_config_set_irq_quiet(&ctrlChanConfig, true);
    channel_config_set_high_priority(&ctrlChanConfig, true);
    channel_config_set_enable(&ctrlChanConfig, true);
    dma_channel_configure(
        dmaCtrlChan, 
        &ctrlChanConfig, 
        // Write to the read address of the data channel
        &dma_hw->ch[dmaDataChan].read_addr,
        // Read from the sample points data pointer
        &samplePointsData,
        // One 32-bit word
        1,
        // Don't start yet
        false);

    // Configure the data channel to output 32 bits at a time to the PIO state machine
    auto dataChanConfig = dma_channel_get_default_config(dmaDataChan);
    channel_config_set_transfer_data_size(&dataChanConfig, DMA_SIZE_32);
    channel_config_set_read_increment(&dataChanConfig, true);
    channel_config_set_write_increment(&dataChanConfig, false);
    channel_config_set_dreq(&dataChanConfig, pio_get_dreq(pio, sm, true));
    channel_config_set_chain_to(&dataChanConfig, dmaCtrlChan);
    channel_config_set_irq_quiet(&dataChanConfig, true);
    channel_config_set_high_priority(&dataChanConfig, true);
    channel_config_set_enable(&dataChanConfig, true);
    dma_channel_configure(
        dmaDataChan,
        &dataChanConfig,
        // Write to the FIFO
        &pio->txf[sm],
        // Read from the sample points data
        samplePointsData,
        // 4096 samples, 32 bits at a time
        4096 / 4,
        // Don't start yet
        false);

    // Configure pins 0-7 to to PIO output
    for (auto i = 0; i < 8; ++i) {
        pio_gpio_init(pio, i);
    }

    // Build a simple PIO program that outputs 8 bits at a time
    std::uint16_t out_instr = pio_encode_out(pio_pins, 8);
    auto pioProgram = pio_program_t{
        .instructions = &out_instr,
        .length = 1,
        .origin = -1,
    };
    auto offset = pio_add_program(pio, &pioProgram);

    // Configure the state machine
    auto smConfig = pio_get_default_sm_config();
    sm_config_set_out_pins(&smConfig, 0, 8);
    sm_config_set_out_shift(&smConfig, true, true, 32);
    sm_config_set_wrap(&smConfig, offset, offset);
    sm_config_set_clkdiv_int_frac(&smConfig, 1, 0);
    pio_sm_init(pio, sm, offset, &smConfig);

    // Start the PIO state machine
    pio_sm_set_enabled(pio, sm, true);
    // Start the DMA channels
    dma_start_channel_mask((1u << dmaDataChan) | (1u << dmaCtrlChan));

    while (true) {
        sleep_ms(1000);
    }
}

Unfortunately, I can't get any reaction out of the DMA-PIO pipeline, measuring the GPIO pins 0 to 7 with an oscilloscope, they are all at a constant voltage level.

Please note, that what I'm trying to do already exists, these ideas are not original at all. The source where I've seen it done this way can be found here, but the code is in MicroPython and IMO it's really hard to understand compared to the C APIs - at least the way that version is written, writing to mem32 directly.

Edit: After a lot of research, I've refactored the code, it's hopefully easier to understand. It still doesn't work unfortunately, but I think it should be a lot closer to correct, and should imitate a lot closer what that python program is doing.

1 Answer 1

0

After a lot of searching - and looking at existing PIO programs -, I've found the missing puzzle piece, it's a call to pio_sm_set_consecutive_pindirs. If anyone wants to look at the full fixed code, here it is:

#include <cstdio>
#include <cstdint>
#include <vector>
#include "pico/stdlib.h"
#include "hardware/dma.h"
#include "hardware/pio.h"

int main() {
    stdio_init_all();

    // Build a simple square wave sample buffer of 4096 samples
    auto samplePoints = std::vector<std::uint8_t>(4096);
    for (auto i = 0; i < 4096; ++i) {
        samplePoints[i] = (i < 2048) ? 0 : 255;
    }

    auto* samplePointsData = samplePoints.data();

    // Allocate 2 DMA channels
    // The data channel will output 32 bits at a time to the PIO state machine
    // The control channel will simply restart the data channel when it's done
    auto dmaDataChan = dma_claim_unused_channel(true);
    auto dmaCtrlChan = dma_claim_unused_channel(true);

    // Allocate the PIO state machine
    auto pio = pio0;
    auto sm = pio_claim_unused_sm(pio, true);

    // Configure the control channel to restart the data channel when it's done
    auto ctrlChanConfig = dma_channel_get_default_config(dmaCtrlChan);
    channel_config_set_transfer_data_size(&ctrlChanConfig, DMA_SIZE_32);
    channel_config_set_read_increment(&ctrlChanConfig, false);
    channel_config_set_write_increment(&ctrlChanConfig, false);
    channel_config_set_chain_to(&ctrlChanConfig, dmaDataChan);
    channel_config_set_irq_quiet(&ctrlChanConfig, true);
    channel_config_set_high_priority(&ctrlChanConfig, true);
    channel_config_set_enable(&ctrlChanConfig, true);
    dma_channel_configure(
        dmaCtrlChan, 
        &ctrlChanConfig, 
        // Write to the read address of the data channel
        &dma_hw->ch[dmaDataChan].read_addr,
        // Read from the sample points data pointer
        &samplePointsData,
        // One 32-bit word
        1,
        // Don't start yet
        false);

    // Configure the data channel to output 32 bits at a time to the PIO state machine
    auto dataChanConfig = dma_channel_get_default_config(dmaDataChan);
    channel_config_set_transfer_data_size(&dataChanConfig, DMA_SIZE_32);
    channel_config_set_read_increment(&dataChanConfig, true);
    channel_config_set_write_increment(&dataChanConfig, false);
    channel_config_set_dreq(&dataChanConfig, pio_get_dreq(pio, sm, true));
    channel_config_set_chain_to(&dataChanConfig, dmaCtrlChan);
    channel_config_set_irq_quiet(&dataChanConfig, true);
    channel_config_set_high_priority(&dataChanConfig, true);
    channel_config_set_enable(&dataChanConfig, true);
    dma_channel_configure(
        dmaDataChan,
        &dataChanConfig,
        // Write to the FIFO
        &pio->txf[sm],
        // Read from the sample points data
        samplePointsData,
        // 4096 samples, 32 bits at a time
        4096 / 4,
        // Don't start yet
        false);

    // Configure pins 0-7 to to PIO output
    for (auto i = 0; i < 8; ++i) {
        pio_gpio_init(pio, i);
    }
    pio_sm_set_consecutive_pindirs(pio, sm, 0, 8, true);

    // Build a simple PIO program that outputs 8 bits at a time
    std::uint16_t out_instr = pio_encode_out(pio_pins, 8);
    auto pioProgram = pio_program_t{
        .instructions = &out_instr,
        .length = 1,
        .origin = -1,
    };
    auto offset = pio_add_program(pio, &pioProgram);

    // Configure the state machine
    auto smConfig = pio_get_default_sm_config();
    sm_config_set_out_pins(&smConfig, 0, 8);
    sm_config_set_out_shift(&smConfig, true, true, 32);
    sm_config_set_wrap(&smConfig, offset, offset);
    sm_config_set_clkdiv_int_frac(&smConfig, 1, 0);
    pio_sm_init(pio, sm, offset, &smConfig);

    // Start the PIO state machine
    pio_sm_set_enabled(pio, sm, true);
    // Start the DMA channels
    dma_start_channel_mask((1u << dmaDataChan) | (1u << dmaCtrlChan));

    while (true) {
        sleep_ms(1000);
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.