🛠 Acquisition ADC à haute fréquence avec le DMA sans CPU

RP2040 (generic board / chip) · avance · Schéma : IMG-rp2040-generic-avance-2-rp2040-dma-adc.svg — puce RP2040 avec le routeur DMA reliant ADC et SPI à un buffer en SRAM, deux buffers ping-pong, oscilloscope montrant un signal échantillonné, CPU endormi

Objectives

The RP2040 features 12 DMA channels connected to the AHB bus (datasheet, chapter 2.3.10 DMA): they move data between peripherals and memory without touching the CPU. Combined with the 12-bit ADC running in round-robin mode up to 500 ksamples/s, you can acquire a signal continuously while the two cores do something else (or sleep). Key pattern: double-buffered ping-pong with two DMA alarms to ensure no samples are ever lost.

Hardware

Complete code

// dma_adc.c — round-robin 4 canaux, DMA vers double buffer
#include "pico/stdlib.h"
#include "hardware/adc.h"
#include "hardware/dma.h"
#include "hardware/irq.h"

#define NSAMP 512
#define NCH 4                       // ADC0..ADC3
static uint16_t buf[2][NSAMP * NCH];
static volatile uint8_t half_ready; // bit0 = buf[0], bit1 = buf[1]

static void dma_handler(void) {
    uint c = dma_channel_get_irq0_status(0);
    dma_channel_acknowledge_irq0(0);
    if (c & 1u) {                    // fin de la 1re moitié
        dma_channel_set_write_addr(0, buf[1], false);
        half_ready |= 2;
    } else {                         // fin de la 2e moitié
        dma_channel_set_write_addr(0, buf[0], false);
        half_ready |= 1;
    }
}

int main(void) {
    stdio_init_all;
    adc_init;
    for (uint ch = 0; ch < NCH; ch++) adc_gpio_init(26 + ch);
    adc_select_input(0);
    adc_set_round_robin((1u << NCH) - 1);
    adc_fifo_setup(true, true, NCH, false, false);  // DMA dreq, niveau NCH
    adc_set_clkdiv(0);               // 500 kéchantillons/s (datasheet)

    int c = dma_channel_claim_unused(-1, true);
    dma_channel_config cfg = dma_channel_get_default_config(c);
    channel_config_set_read_increment(&cfg, false);   // FIFO ADC fixe
    channel_config_set_write_increment(&cfg, true);
    channel_config_set_transfer_data_size(&cfg, DMA_SIZE_16);
    channel_config_set_dreq(&cfg, DREQ_ADC);
    dma_channel_configure(c, &cfg, buf[0], &adc_fifo->fifodata,
                          NSAMP * NCH, false);
    irq_set_exclusive_handler(DMA_IRQ_0, dma_handler);
    irq_set_enabled(DMA_IRQ_0, true);
    dma_channel_set_irq0_enabled(c, true);
    dma_start_channel_mask(1u << c);
    adc_run(true);

    uint32_t n = 0, t0 = time_us_32;
    while (true) {
        if (half_ready & 1) { half_ready &= ~1u; n += NSAMP * NCH; }
        if (half_ready & 2) { half_ready &= ~2u; n += NSAMP * NCH; }
        if (time_us_32 - t0 > 1000000) {
            printf("%lu ech/s (CPU ne copie rien)\n", n);
            n = 0; t0 = time_us_32;
        }
    }
}

Expected verification

The console displays ~2,000,000/4 = 500,000 samples/s (4 channels in round-robin at 500 ksps — the ADC is limited to 500 ksamples/s by the datasheet), while the main loop only counts flags. Meanwhile, a blinking LED on the other core maintains perfect timing: proof that the transfer is 100% DMA. Disconnect the signal from GP26: the values become floating, but the stream never stops.

Common errors

Other tutorials for this board

Code examples for this board

← Back to the board 🛠 All tutorials