🛠 Acquisition ADC à haute fréquence avec le DMA sans CPU
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
- 1 × generic RP2040 board + Pico SDK (C), target linked with
pico_stdlib hardware_adc hardware_dma pico_time - Signal to acquire on GP26 (ADC0) — output from a potentiometer or a low-frequency generator
- USB serial console at 115200 baud for the throughput meter
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
- Forgetting
adc_fifo_setup(true, true, ...)(dreq): the DMA does not receive a request, it empties the FIFO too quickly and copies duplicates — the classic symptom is a "staircase" signal. - Misunderstanding
adc_set_clkdiv(0): 0 = max speed 500 ksps; to sample slower, increase the divider, otherwise the ADC saturates the DMA channel. - A DMA handler that calls
printf: the IRQ should only switch the write address — heavy processing stays in the main loop. - Buffer larger than useful RAM: 264 KB total, two 4 KB buffers fit, 2 × 512 KB do not — and RAM shared with the DMA must be within the accessible 264 KB (all banks are accessible, except the last one used by the bootrom).
- Reading the buffer while the DMA is writing to it: this is exactly the role of ping-pong — never read the half currently being written.
- Confusing DMA and PIO: DMA moves data, PIO generates signals; together (PIO RX + DMA) they provide a 62.5 Msps logic analyzer — the famous
logic_analyzerexample from the SDK.
Other tutorials for this board
- Piloter la LED RGB embarquée WS2812 d'une carte RP2040 générique
- Avoir une console série sur une carte sans pont USB (UART0)
- Jouer une mélodie avec un buzzer passif et le PWM
- Afficher sur un OLED I2C SSD1306 et déboguer le bus
- Logger sur carte microSD en SPI (mode carte)
- Interruptions GPIO et alarmes matérielles avec le Pico SDK
Code examples for this board
clignotante.pyPythonbouton-interruption.pyPythonpwm-servo.pyPythonlecture-adc.pyPythonneopixel.pyPythonoled-i2c.pyPython