🛠 Acquisition ADC à haute fréquence avec le DMA sans CPU
Objectifs
Le RP2040 possède 12 canaux DMA connectés au bus AHB (datasheet, chapter 2.3.10 DMA) : ils déplacent des données entre périphériques et mémoire sans toucher au CPU. Combiné à l'ADC 12 bits round-robin jusqu'à 500 kéchantillons/s, tu peux acquérir un signal en continu pendant que les deux cœurs font autre chose (ou dorment). Pattern clé : le double buffer ping-pong avec deux alarmes DMA pour ne jamais perdre d'échantillons.
Matériel
- 1 × carte RP2040 générique + Pico SDK (C), cible liée avec
pico_stdlib hardware_adc hardware_dma pico_time - Signal à acquérir sur GP26 (ADC0) — sortie d'un potentiomètre ou d'un générateur basse fréquence
- Console série USB 115200 pour le débitmètre
Code complet
// 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;
}
}
}
Vérification attendue
La console affiche ~2 000 000/4 = 500 000 échantillons/s (4 canaux en round-robin à 500 ksps — l'ADC est limité à 500 kéchantillons/s par la datasheet), pendant que la boucle principale ne fait que compter des drapeaux. Pendant ce temps, une LED clignotante sur l'autre cœur garde un timing parfait : la preuve que le transfert est 100 % DMA. Débranche le signal de GP26 : les valeurs deviennent flottantes, mais le flux ne s'interrompt jamais.
Erreurs fréquentes
- Oublier
adc_fifo_setup(true, true, ...)(dreq) : le DMA ne reçoit pas de demande, il vide la FIFO trop vite et recopie des doublons — le symptôme classique est un signal « en escalier ». adc_set_clkdiv(0)mal compris : 0 = vitesse max 500 ksps ; pour échantillonner plus lentement, augmente le diviseur, sinon l'ADC sature le canal DMA.- Handler DMA qui fait un
printf: l'IRQ doit juste basculer l'adresse d'écriture — le traitement lourd reste dans la boucle principale. - Buffer plus grand que la RAM utile : 264 Ko au total, deux buffers de 4 Ko passent, 2 × 512 Ko non — et la RAM partagée avec le DMA doit être dans les 264 Ko accessibles (toutes les banques le sont, sauf la dernière utilisée par le bootrom).
- Lire le buffer pendant que le DMA l'écrit : c'est exactement le rôle du ping-pong — ne lis jamais le half en cours.
- Confondre DMA et PIO : le DMA déplace des données, la PIO fabrique des signaux ; les deux ensemble (PIO RX + DMA) donnent un logic analyzer à 62,5 Msps — le fameux exemple
logic_analyzerdu SDK.
Autres tutoriels pour cette carte
- 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
Exemples de code pour cette carte
clignotante.pyPythonbouton-interruption.pyPythonpwm-servo.pyPythonlecture-adc.pyPythonneopixel.pyPythonoled-i2c.pyPython