The puzzle
A vibration monitor reads its sensor in a loop with a 1 ms delay and sees noise that no amount of averaging removes. A three-phase current meter reads its three channels “simultaneously” and computes a power factor that is slightly wrong. Both have the right ADC. What matters is when each conversion happens, and how the results get out.
STEP 1
Starting conversions
- Software start: code writes a start bit and waits for the result. Simple, but the sampling instant inherits every delay in the code: interrupts, other tasks, cache misses.
- Free-running: the ADC converts continuously at a rate set by its clock (the RP2040’s clock divider), and software or DMA collects results.
- Hardware trigger: a timer event or a pin edge starts each conversion (STM32:
ADC_EXTERNALTRIGCONV_T1_CC1and friends). The instant is set by hardware, within a few clock cycles.
STEP 2
Jitter is noise
A sample taken late or early measures the signal at the wrong time. For a sine of amplitude A and frequency f the error is up to 2πf·A times the timing error, and random timing errors with an rms value t_j act like noise with
independent of the converter’s resolution. The effective bits it allows are (SNR − 1.76) / 6.02.
STEP 3
Sequences over several inputs
Most microcontrollers have one ADC and a multiplexer. A sequence (STM32 scan mode, RP2040 round robin) converts several inputs in turn. Each input then gets the total rate divided by the number of inputs, and the inputs are sampled one conversion period apart, not at the same instant.
The RP2040 has one ADC and a five-way input multiplexer. In round-robin mode it converts the selected inputs in turn, so each input is sampled at the total rate divided by the number of inputs, and neighbouring inputs are sampled one conversion period apart, not at the same instant. A conversion takes 96 cycles of the 48 MHz ADC clock (500 kS/s back to back); the clock divider stretches the period to (1 + div) cycles.
On the RP2040 the total rate is set by the divider: a conversion takes 96 cycles of the 48 MHz ADC clock, and with a divider the period is (1 + div) cycles:
STEP 4
Getting results out
The RP2040’s ADC has a 4-entry FIFO: if it fills, new results are dropped and the OVER flag is set. At 500 kS/s the FIFO holds 8 µs of data, too little for an interrupt per sample to be comfortable, so continuous capture uses the FIFO’s DMA request (unit 12), as in the pico-examples dma_capture. ST’s HAL similarly chooses between an end-of-conversion flag per channel or per sequence, and notes that overrun detection needs interrupt or DMA mode. An overrun means samples were lost and the timing of the rest is no longer known: treat it as an error, not something to ignore.
STEP 5
Worked example: three channels at 1 kS/s each
Three inputs in round robin at 1 kS/s each need 3 kS/s in total, a period of 48 MHz / 3000 = 16 000 ADC clocks:
Consecutive inputs are sampled 1 / 3000 s ≈ 333 µs apart. For 50 Hz three-phase currents that is 6° of phase (333 µs × 50 Hz × 360°), exactly the kind of error that spoils a power-factor calculation unless the firmware corrects for it or the channels are converted back to back and interpolated.
MYTHS AND FACTS
Common misconceptions
sleep_ms(1) gives 1 kS/s
It gives about 1 kS/s with jitter from every interrupt and task; trigger from a timer instead.
Round robin samples all inputs at once
It samples them one after another; the skew is one conversion period.
Jitter only matters for fast ADCs
It matters for fast-changing signals, whatever the ADC’s speed.
A lost sample or two doesn’t matter
It breaks the timing assumption of every filter and frequency calculation downstream.
Check yourself
Answer in your head, then open the card.
What SNR does 1 µs of sampling jitter allow on a 5 kHz sine, and how many bits is that?
−20 log10(2π × 5000 × 10⁻⁶) ≈ 30.1 dB, about 4.7 bits.
What RP2040 divider gives 10 kS/s on a single input?
48 MHz / 10 000 = 4800 cycles, so div = 4799.
Two inputs in round robin run back to back (div = 0). What is each input’s rate and the skew between them?
500 kS/s total, 250 kS/s each, sampled 2 µs apart.
Why is the ADC FIFO’s DMA request preferable to an interrupt per sample at 500 kS/s?
An interrupt every 2 µs would consume much of the CPU and must never be late by more than the 4-sample FIFO (8 µs); DMA moves each sample without the CPU.
Sources (4)
- Raspberry Pi Ltd, pico-sdk 1.5.1, hardware_adc/adc.h — adc_set_round_robin(input_mask): “the ADC will use that input and move to the next one after a read”; adc_set_clkdiv: “Period of samples will be (1 + div) cycles on average … it takes 96 cycles to perform a conversion, so any period less than that will be clamped to 96”; FIFO “is 4 samples long, if a conversion is completed and the FIFO is full, the result is dropped”; dreq_en “Enable DMA requests when FIFO contains data”
- Raspberry Pi Ltd, pico-sdk 1.5.1, rp2040/hardware_regs/adc.h — FCS.OVER: the FIFO overflowed (write 1 to clear); DIV.INT and DIV.FRAC (“Fractional part of clock divisor. First-order delta-sigma”)
- STMicroelectronics, stm32f4xx-hal-driver, Inc/stm32f4xx_hal_adc.h — ExternalTrigConv sources such as ADC_EXTERNALTRIGCONV_T1_CC1 … T8_TRGO with ExternalTrigConvEdge rising/falling; ScanConvMode sequencer; ContinuousConvMode; EOCSelection per conversion (ADC_EOC_SINGLE_CONV) or per sequence (ADC_EOC_SEQ_CONV); overrun detection needs interrupt or DMA mode
- Raspberry Pi Ltd, pico-examples, adc/dma_capture/dma_capture.c — adc_fifo_setup with DREQ enabled and byte_shift; a DMA channel paced by DREQ_ADC moves 1000 samples into capture_buf while the CPU waits