🛠 Acquisition ADC à haute fréquence avec le DMA sans CPU
Objetivos
El RP2040 posee 12 canales DMA conectados al bus AHB (hoja de datos, capítulo 2.3.10 DMA): desplazan datos entre periféricos y memoria sin tocar la CPU. Combinado con el ADC de 12 bits round-robin hasta 500 k muestras/s, puedes adquirir una señal de forma continua mientras los dos núcleos hacen otra cosa (o duermen). Patrón clave: el doble búfer ping-pong con dos alarmas DMA para no perder nunca muestras.
Material
- 1 × placa RP2040 genérica + Pico SDK (C), objetivo vinculado con
pico_stdlib hardware_adc hardware_dma pico_time - Señal a adquirir en GP26 (ADC0) — salida de un potenciómetro o de un generador de baja frecuencia
- Consola serie USB 115200 para el medidor de caudal
Código completo
// 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;
}
}
}
Verificación esperada
La consola muestra ~2 000 000/4 = 500 000 muestras/s (4 canales en round-robin a 500 ksps — el ADC está limitado a 500 k muestras/s según la hoja de datos), mientras que el bucle principal solo cuenta banderas. Durante este tiempo, un LED parpadeante en el otro núcleo mantiene un temporización perfecta: la prueba de que la transferencia es 100 % DMA. Desconecta la señal de GP26: los valores se vuelven flotantes, pero el flujo nunca se interrumpe.
Errores frecuentes
- Olvidar
adc_fifo_setup(true, true, ...)(dreq): el DMA no recibe solicitud, vacía la FIFO demasiado rápido y copia duplicados — el síntoma clásico es una señal «en escalera». adc_set_clkdiv(0)mal entendido: 0 = velocidad máxima 500 ksps; para muestrear más lentamente, aumenta el divisor, si no, el ADC satura el canal DMA.- Handler DMA que hace un
printf: la IRQ debe simplemente alternar la dirección de escritura — el procesamiento pesado permanece en el bucle principal. - Búfer más grande que la RAM útil: 264 KB en total, dos búferes de 4 KB pasan, 2 × 512 KB no — y la RAM compartida con el DMA debe estar dentro de los 264 KB accesibles (todos los bancos lo están, excepto el último utilizado por el bootrom).
- Leer el búfer mientras el DMA escribe: es exactamente el papel del ping-pong — nunca leas el half en curso.
- Confundir DMA y PIO: el DMA desplaza datos, la PIO fabrica señales; ambos juntos (PIO RX + DMA) dan un analizador lógico a 62,5 Msps — el famoso ejemplo
logic_analyzerdel SDK.
Otros tutoriales para esta placa
- 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
Ejemplos de código para esta placa
clignotante.pyPythonbouton-interruption.pyPythonpwm-servo.pyPythonlecture-adc.pyPythonneopixel.pyPythonoled-i2c.pyPython