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

RP2040 (carte / puce générique) · 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

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

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

Autres tutoriels pour cette carte

Exemples de code pour cette carte

← Retour à la fiche 🛠 Tous les tutoriels