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.