pico-examples icon indicating copy to clipboard operation
pico-examples copied to clipboard

Example request: DMA ping-pong

Open jonathangjertsen opened this issue 4 years ago • 6 comments

Would be nice with an example application of DMA ping-pong (in which the DMA alternates between two buffers so that one can be written to while the other is being processed).

jonathangjertsen avatar May 20 '21 16:05 jonathangjertsen

I'm implementing exactly this in a ringbuffer in https://github.com/biemster/pico-I2S, but it is not working yet.

biemster avatar Dec 01 '22 12:12 biemster

@jonathangjertsen , could you provide a little more detail on what you'd like to see in an example? For example, where does the data come from that's being scattered to the two buffers? Does completion of the processing trigger the next DMA transfer?

ajf58 avatar Jan 24 '23 16:01 ajf58

@ajf58

A typical example is continuously capturing ADC data at very high speed without any gaps. There is an example of using the DMA and ADC together, but it's just a one-shot.

I like this figure from TI's various processor datasheets (in this case taken from the TM4C1294NCPDT). I think it uses some generic ARM component for the DMA which may or may not be similar to the one used by the Pico

udma

jonathangjertsen avatar Jan 24 '23 17:01 jonathangjertsen

Thanks.

One approach to alternating between the two ping and pong buffers is to reconfigure the DMA channel from the IRQ handler once one buffer is full. For example, modifying the example in dma/channel_irq/channel_irq.c, we can can demonstrate ping-ponging between two buffers, allowing the on chip temperature to be oversampled and averaged. N.b. I've not dug into the RP2040 datasheet to determine whether this oversampling and averaging would have the desired effect of improving the resolution in the temperature reading, but it's a useful backdrop for the problem.

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

#include "hardware/adc.h"
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "hardware/timer.h"

#define CAPTURE_DEPTH 256

// Replace capture_buf[CAPTURE_DEPTH] with a ping and pong register
uint16_t ping[CAPTURE_DEPTH];
uint16_t pong[CAPTURE_DEPTH];

bool write_ping = true;
int dma_chan;
// Written to in the IRQ context, read from the main loop.
volatile uint32_t sum = 0;

void dma_handler() {
    // Clear the interrupt request.
    dma_hw->ints0 = 1u << dma_chan;

    void *write_addr;
    uint16_t *read_addr;
    if (write_ping) {
        // Ping was being written to, and has completed. Now read from ping, and write to pong.
        write_ping = false;
        read_addr = ping;
        write_addr = pong;
    } else {
        write_ping = true;
        read_addr = pong;
        write_addr = ping;
    }
    // Kick off the next transfer.
    dma_channel_set_write_addr(dma_chan, write_addr, true);
    // Process what's in the read buffer.
    uint32_t temp = 0;
    for (size_t i = 0; i < CAPTURE_DEPTH; i++) {
        // Mask the bottom 12 bits.
        temp += read_addr[i] & 0xFFF;
    }
    // Oversampled, so divide down.
    temp /= CAPTURE_DEPTH;

    // Update.
    sum = temp;
}

int main()
{
    stdio_init_all();
#ifdef PICO_DEFAULT_LED_PIN
    gpio_init(PICO_DEFAULT_LED_PIN);
    gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
#endif

    /* Initialize hardware AD converter, enable onboard temperature sensor and
     *   select its channel (do this once for efficiency, but beware that this
     *   is a global operation). */
    adc_init();
    adc_set_temp_sensor_enabled(true);
    adc_select_input(4);

    adc_fifo_setup(
        true,    // Write each completed conversion to the sample FIFO
        true,    // Enable DMA data request (DREQ)
        1,       // DREQ (and IRQ) asserted when at least 1 sample present
        false,
        false    // Don't shift, masking values is handled in dma_handler()'s processing of the read buffer.
    );

    adc_set_clkdiv(0);

    sleep_ms(1000);
    printf("Arming DMA\n");
    // Set up the DMA to start transferring data as soon as it appears in FIFO
    dma_chan = dma_claim_unused_channel(true);
    dma_channel_config cfg = dma_channel_get_default_config(dma_chan);

    // Reading from constant address, writing to incrementing byte addresses
    channel_config_set_transfer_data_size(&cfg, DMA_SIZE_16);
    channel_config_set_read_increment(&cfg, false);
    channel_config_set_write_increment(&cfg, true);

    // Pace transfers based on availability of ADC samples
    channel_config_set_dreq(&cfg, DREQ_ADC);

    dma_channel_configure(dma_chan, &cfg,
        ping,    // dst
        &adc_hw->fifo,  // src
        CAPTURE_DEPTH,  // transfer count
        false           // do not start, wait for dma_handler() to be called
    );

    printf("Starting capture\n");
    adc_run(true);

    // Tell the DMA to raise IRQ line 0 when the channel finishes a block
    dma_channel_set_irq0_enabled(dma_chan, 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);

    // Manually call the handler once, to trigger the first transfer
    dma_handler();

    float old_temp = 0;
    while (true) {
        /* 12-bit conversion, assume max value == ADC_VREF == 3.3 V */
        const float conversionFactor = 3.3f / (1 << 12);

        float adc = (float)  sum * conversionFactor;
        float tempC = 27.0f - (adc - 0.706f) / 0.001721f;
        if (old_temp != tempC) {
            printf("Onboard temperature = %.03f %c\n", tempC, TEMPERATURE_UNITS);
        }
        old_temp = tempC;

#ifdef PICO_DEFAULT_LED_PIN
        gpio_put(PICO_DEFAULT_LED_PIN, 1);
        sleep_ms(10);
        gpio_put(PICO_DEFAULT_LED_PIN, 0);
#endif
        sleep_ms(490);
    }

    return 0;
}

Some example output (yup, reading the first uninitialized data gives a nonsense value):

Arming DMA
Starting capture
Onboard temperature = 437.227 C
Onboard temperature = 14.499 C
Onboard temperature = 14.967 C
Onboard temperature = 15.435 C

ajf58 avatar Jan 26 '23 14:01 ajf58

There's a good example of DMA ping-pong writing to two buffers from ADC in https://github.com/zapta/simple_stepper_motor_analyzer/blob/main/platformio/src/acquisition/adc_dma.cpp

There's a messier examlple in https://github.com/kevinjwalters/galactic-bluetooth-audio/blob/spectrogram-tuner-dmaadc/uad/audiosourcedmaadc.cpp

kevinjwalters avatar Dec 29 '23 13:12 kevinjwalters