В продолжение темы «Скрытые возможности ADC и DMA: быстрые измерения на Raspberry Pi Pico» приведем минимальную, но правильную схему ADC + DMA для Raspberry Pi Pico (RP2040 / Pico 2 аналогично), с пояснением почему именно так.
Архитектура решения
Идея простая и «осциллографическая»:
ADC работает в свободном режиме с заданной частотой → кладёт выборки в FIFO → DMA автоматически копирует их в массив в SRAM → CPU получает готовый буфер и занимается обработкой (FFT, фильтрация, USB, сохранение).
CPU не участвует в процессе измерений — это ключ.
Аппаратная схема (минимальная, но корректная)
Вход ADC (например, GPIO26 / ADC0)
┌─────[ 1k–10k ]─────┐
│ │
SIGNAL ──┤ ├── GPIO26 (ADC0)
│ │
└─────[ 100 nF ]─────┘
|
GND
Почему так:
- Резистор ограничивает ток и снижает влияние входной ёмкости ADC
- Конденсатор образует RC-фильтр (антиалиасинг, подавление шума)
- Значения подбираются под частоту дискретизации
Вход 0–3.3 В, без защиты — если сигнал «грязный», добавь диоды.
Настройка ADC (C SDK)
Инициализация ADC
adc_init();
adc_gpio_init(26); // GPIO26 → ADC0
adc_select_input(0); // канал ADC0
Настройка FIFO
adc_fifo_setup(
true, // enable FIFO
true, // enable DMA request
1, // DREQ when at least 1 sample
false, // no ERR bit
false // 12-bit samples (no shift)
);
Важно: FIFO + DREQ — это ключ для DMA.
Частота дискретизации
adc_set_clkdiv(96.0f);
Формула:
Fs = 48 MHz / clkdiv
Примеры:
clkdiv = 96→ 500 kS/sclkdiv = 1000→ 48 kS/s (аудио)
Настройка DMA
Конфигурация канала DMA
#define SAMPLE_COUNT 4096
uint16_t samples[SAMPLE_COUNT];
int dma_chan = dma_claim_unused_channel(true);
dma_channel_config cfg = dma_channel_get_default_config(dma_chan);
channel_config_set_transfer_data_size(&cfg, DMA_SIZE_16);
channel_config_set_read_increment(&cfg, false);
channel_config_set_write_increment(&cfg, true);
channel_config_set_dreq(&cfg, DREQ_ADC);
Пояснение:
DMA_SIZE_16→ ADC выдаёт 12 бит в 16-битном словеread_increment = false→ читаем всегда ADC FIFOwrite_increment = true→ пишем в массивDREQ_ADC→ синхронизация по готовности ADC
Запуск DMA
dma_channel_configure(
dma_chan,
&cfg,
samples, // куда писать
&adc_hw->fifo, // откуда читать
SAMPLE_COUNT,
false
);
Запуск измерений
adc_run(true);
dma_start_channel_mask(1u << dma_chan);
Ожидание завершения:
dma_channel_wait_for_finish_blocking(dma_chan);
adc_run(false);
Теперь samples[] содержит ровно SAMPLE_COUNT измерений с идеально равномерным интервалом.
Что делать дальше с данными
Примеры:
Аудио
sample_voltage = samples[i] * 3.3f / 4096.0f;
FFT
- одно ядро: сбор
- второе ядро: FFT (kissFFT, CMSIS-DSP)
Осциллограф
- DMA → ring buffer
- USB CDC → PC
- отрисовка в Python / Qt
Реальные цифры (RP2040 @ default clock)
| Параметр | Значение |
|---|---|
| Максимальная Fs | ~480–500 kS/s |
| Джиттер | ≈ 0 |
| Загрузка CPU | ≈ 0% |
| Пропуски | отсутствуют |
Частые ошибки
— читать ADC в цикле
— включать IRQ вместе с DMA
— использовать float во время сбора
— забывать антиалиасинг
— пытаться читать несколько каналов «синхронно»
Итоги
Это каноническая схема использования ADC в Pico:
- ADC → FIFO → DMA → RAM
- CPU и ядра не мешают измерениям
- тайминги стабильны, результат предсказуем
По архитектуре это настоящий измерительный pipeline, а не «хоббийный опрос АЦП».