Back to all projects

Jan 2025 – Feb 2025

EE

Last edited

Quantization-Limited SNR on a 12-bit Multi-ADC Pipeline

Built a measurement system for digitising analog signals using a Raspberry Pi 3B with external MCP3201 12-bit ADCs and an op-amp low-pass filter front end. The system was evaluated through frequency-domain analysis, time-series inspection, and signal-to-noise ratio (SNR) calculations.

Verification confirmed correct waveform and amplitude reproduction, but identified a systematic DC offset visible in every FFT. Measured SNR at 1 kHz, 2 kHz, and 10 kHz was 55.22, 55.14, and 55.18 dB respectively, essentially flat across frequency, but ~19 dB below the 74 dB ideal-quantisation bound for a 12-bit converter.

The gap is consistent with quantisation noise in non-full-scale regions, power-supply interference, a lower-than-designed actual cutoff in the analog low-pass filter, and reference-voltage instability. Improvements identified for a follow-up: better EMI shielding, a low-noise reference, a more precise filter implementation, and post-hoc calibration of the systematic offset.

Affiliation

NTNU

Partners

Report

  • Lab report

Keywords

  • Digital Signal Processing (DSP)
  • Quantization
  • Noise Analysis
  • Fast Fourier Transform (FFT)
  • C
  • Python
  • Raspberry Pi
  • MCP3201 ADC

Deepdive

Introduction

This project is the measurement front-end for a radar/microphone pipeline built around a Raspberry Pi 3B+ and five external Microchip MCP3201 12-bit ADCs sampled in parallel over a shared SPI bus. The Pi has no native analog inputs, so the entire signal-acquisition chain hangs off SPI: a single DMA-driven clock and chip-select line is broadcast to all five ADCs, while each device’s MISO returns on its own GPIO, letting the host capture five simultaneous channels per SPI transaction. The lab was scoped around verifying that the pipeline actually delivers the precision its datasheet promises, quantization-limited SNR at 12 bits is 74dB74\,\text{dB}, and the question is how close a real, breadboard-mounted, supply-noisy implementation gets to that bound.

Problem Definition

Each ADC samples a continuous voltage v(t)[0,Vref]=[0,3.3]Vv(t) \in [0,\, V_{\text{ref}}] = [0,\, 3.3]\,\text{V} at rate fsf_s and emits a discrete code x[n]{0,1,,2N,1}x[n] \in \{0, 1, \dots, 2^N, 1\} with N=12N = 12. The Nyquist–Shannon condition fixes the upper end of the bandwidth that can be captured without aliasing,

fs    2fmax,f_s \;\geq\; 2 f_{\max},

and the resolution of each code is the LSB step

ΔV  =  Vref2N    0.8mV,\Delta V \;=\; \frac{V_{\text{ref}}}{2^N} \;\approx\; 0.8\,\text{mV},

so the worst-case quantization noise amplitude is

Qmax  =  ΔV2  =  Vref2N+1.Q_{\max} \;=\; \frac{\Delta V}{2} \;=\; \frac{V_{\text{ref}}}{2^{N+1}}.

For a full-scale sinusoidal input, the theoretical SNR of an ideal NN-bit ADC is the standard result

SNRideal  =  6.02N+1.76dB  =  74dB    at    N=12,\mathrm{SNR}_{\text{ideal}} \;=\; 6.02\,N + 1.76\,\text{dB} \;=\; 74\,\text{dB}\;\;\text{at}\;\; N=12,

which sets the headline target. The empirical SNR is computed from the measured spectrum as

SNR  =  10log10 ⁣(PsignalPnoise),\mathrm{SNR} \;=\; 10 \log_{10}\!\left( \frac{P_{\text{signal}}}{P_{\text{noise}}} \right),

with PsignalP_{\text{signal}} the energy in the input tone’s bin (and its ±f0\pm f_0 image), and PnoiseP_{\text{noise}} the integrated energy across the rest of the spectrum (DC bin excluded, since the DC offset is a deterministic bias rather than noise). The lab’s job is to quantify the gap between SNRideal\mathrm{SNR}_{\text{ideal}} and the realized SNR\mathrm{SNR} across three input frequencies and to attribute that gap to the dominant noise sources in the chain.

Approach

Architecture of the multi-ADC pipeline: an external PC controls a Raspberry Pi 3B+ over SSH, the Pi broadcasts a shared SPI clock and chip-select to five MCP3201 ADCs while each ADC returns its own MISO line, an LC supply filter feeds the ADC VDD/VREF rail, an Analog Discovery 2 drives the common IN+ test input, and raw 12-bit samples are windowed, FFTed, and reduced to an SNR figure compared against the 6.02N + 1.76 dB theoretical bound.
Measurement architecture. The Pi runs a DMA-driven bit-banged SPI master that broadcasts SCK and CS to five MCP3201 ADCs in parallel; each ADC returns its 12-bit conversion on a dedicated MISO line. The 3.3 V rail feeds VDD and VREF through an LC low-pass filter, and a common IN+ test signal from an AD2 lets the five channels be cross-compared.

The pipeline decomposes into four stages: a Raspberry Pi host configured for headless data acquisition, a multi-ADC SPI fan-out with a shared clock and per-channel MISO, an LC supply filter on the 3.3 V rail, and a host-side spectral-analysis pipeline that converts raw codes into SNR figures.

Raspberry Pi Host and SPI Fan-Out

The Pi is flashed with 32-bit Raspberry Pi OS via the Imager (with SSH, hostname, and Wi-Fi pre-configured), reached over SSH for control and SFTP for pulling sample dumps. All five MCP3201s share the SCK and CS lines and differ only in the GPIO their MISO is wired to (GPIO 18, 19, 20, 21, 22). A traditional SPI master would drive each device through its own CS, but the MCP3201 starts a fresh conversion on every CS falling edge regardless of MISO ownership, so a single CS pulse drives all five conversions simultaneously and the host reads the five MISO bits in lock-step. The C client uses pigpio’s rawWaveAddSPI interface, which DMA-bit-bangs the SPI transaction so the bit timings are guaranteed to within roughly 1μs1\,\mu\text{s} and the CPU is left free for buffer management. Sample rate is set to Tp=32μsT_p = 32\,\mu\text{s}, giving fs31250Hzf_s \approx 31\,250\,\text{Hz}.

LC Supply Filter

ADC resolution is only as good as the stability of VrefV_{\text{ref}} (per the LSB equation above), so the 3.3 V rail is filtered before it reaches VDD and VREF. A second-order LC topology with a 100mH100\,\text{mH} inductor and a 100μF+470μF+100nF100\,\mu\text{F} + 470\,\mu\text{F} + 100\,\text{nF} capacitor stack has transfer function

H(ω)  =  1jωC1jωC+jωL,H(\omega) \;=\; \frac{\frac{1}{j\omega C}}{\frac{1}{j\omega C} + j\omega L},

and at the 3dB-3\,\text{dB} point H=2/2|H| = \sqrt{2}/2, which solves to

fc  =  1+24π2CL    36.07Hzf_c \;=\; \sqrt{\frac{1 + \sqrt{2}}{4\pi^2 C L}} \;\approx\; 36.07\,\text{Hz}

with the nominal component values. Each ADC additionally carries two 1μF1\,\mu\text{F} decoupling capacitors (one between VREF and IN−, one between VDD and VSS) to suppress the high-frequency switching that the SPI clock and Pi-side DC-DC injects into the rail.

Schematic of the LC low-pass supply filter: a 100 mH series inductor between 3.3 V in and 3.3 V out, with 100 µF, 470 µF, and 100 nF shunt capacitors to ground.
Schematic of the LC supply filter. The 100 mH series inductor blocks high-frequency current variations while the 100 µF / 470 µF / 100 nF capacitor stack shunts ripple to ground, holding the 3.3 V rail that feeds every ADC’s VDD and VREF stable.

Spectral Analysis and SNR

Each captured channel is windowed with a Hann window

w[n]  =  0.5 ⁣[1,cos ⁣(2πnN1)],0nN1,w[n] \;=\; 0.5 \!\left[\, 1, \cos\!\left(\frac{2\pi n}{N-1}\right) \right], \qquad 0 \leq n \leq N-1,

which is a reasonable default between main-lobe width and side-lobe leakage; Hamming and Blackman were both available but Hann gave the cleanest peak/floor separation for sinusoidal test tones. The windowed sequence is zero-padded from NN to MM samples,

xp[n]  =  {x[n]0n<N,0Nn<M,x_p[n] \;=\; \begin{cases} x[n] & 0 \leq n < N, \\ 0 & N \leq n < M, \end{cases}

and the DFT is taken on the padded length,

Xp[k]  =  n=0M1xp[n]ej2πMkn,k=0,1,,M1.X_p[k] \;=\; \sum_{n=0}^{M-1} x_p[n]\, e^{-j\,\frac{2\pi}{M} k n}, \qquad k = 0, 1, \dots, M-1.

Padding does not add information but refines the frequency-axis step, which sharpens the visual location of the peak without changing the underlying energy spectrum. The SNR formula above is then evaluated by taking PsignalP_{\text{signal}} as the energy in the bins at ±f0\pm f_0 and PnoiseP_{\text{noise}} as the energy in every other bin except DC.

Experimental Verification

A 12 V sinusoid with 1V1\,\text{V} amplitude and 1V1\,\text{V} DC offset (so the signal lives in [0,2]V[0,\,2]\,\text{V}, comfortably inside the [0,3.3]V[0,\,3.3]\,\text{V} ADC range) is generated by an Analog Discovery 2 (AD2) and fanned out onto a common IN+ line that all five ADCs share. The same AD2 in impedance-measurement mode is used to characterise the supply filter’s inductor over 100Hz100\,\text{Hz} to 10kHz10\,\text{kHz}.

Photograph of the breadboard build, annotated: LC filter on top, signal lines, shared CS/CLK, 3.3 V rail, GND, and the yellow IN+ injection wire from the Analog Discovery 2 fanning out to the five MCP3201s.
Physical breadboard build. The LC filter sits at the top, the Pi Wedge adapter on the left routes the Pi’s GPIO header to the board, the five MCP3201s line the lower half, and the yellow wire carries the AD2 test signal onto the common IN+ trunk.

Results

The LC filter’s measured frequency response, swept with the AD2, shows the 3dB-3\,\text{dB} knee at 26.24Hz26.24\,\text{Hz} rather than the predicted 36.07Hz36.07\,\text{Hz}. Impedance-measuring the inductor across the same sweep gives an averaged inductance of L=126.8mHL = 126.8\,\text{mH} versus the nominal 100mH100\,\text{mH}, which alone shifts the theoretical knee to 32.03Hz32.03\,\text{Hz} and explains the bulk of the discrepancy, the residual 6Hz\sim 6\,\text{Hz} shift is attributable to capacitor tolerance, the inductor’s ESR/DCR, and parasitic effects on the breadboard. A small unexpected secondary knee around 50Hz50\,\text{Hz} is consistent with mains-frequency pickup or a resonance in the breadboard layout.

Measured amplitude response of the LC filter on a log-frequency x-axis, with the −3 dB cutoff marked as a red dot at 26.24 Hz.
Measured amplitude response of the LC filter. The −3 dB knee sits at 26.24 Hz rather than the 36.07 Hz predicted by the nominal component values, and a small secondary knee around 50 Hz hints at mains pickup or a layout resonance on the breadboard.
Measured inductor reactance Xₛ in ohms as a function of frequency on a log scale from 100 Hz to 10 kHz, with the averaged inductance annotated as 126.80 mH.
Inductor reactance sweep. Averaging L=Xs/(2πf)L = X_s / (2\pi f) across the sweep yields L=126.8mHL = 126.8\,\text{mH}, 27%27\,\% above the nominal 100mH100\,\text{mH}, which alone shifts the theoretical knee from 36.07Hz36.07\,\text{Hz} to 32.03Hz32.03\,\text{Hz} and accounts for most of the gap to the measured 26.24Hz26.24\,\text{Hz}.

The ADC chain was driven with single-tone sinusoids at three frequencies and the SNR computed channel-by-channel. The realized SNR sits about 19dB19\,\text{dB} below the 12-bit quantization bound and is essentially flat across the three test frequencies, which is consistent with broadband noise (quantization, supply ripple, and EMI pickup) dominating over any frequency-dependent loss in the analog front-end.

Input toneEmpirical SNRTheoretical 6.02N+1.766.02 N + 1.76Gap
1kHz1\,\text{kHz}55.22dB55.22\,\text{dB}74dB74\,\text{dB}18.78dB-18.78\,\text{dB}
2kHz2\,\text{kHz}55.14dB55.14\,\text{dB}74dB74\,\text{dB}18.86dB-18.86\,\text{dB}
10kHz10\,\text{kHz}55.18dB55.18\,\text{dB}74dB74\,\text{dB}18.82dB-18.82\,\text{dB}
FFT spectrum of the digitised 2 kHz test signal, Hann-windowed and zero-padded, with dominant peaks at ±2 kHz, a deterministic peak at 0 Hz from the 1 V DC offset, and a broadband floor roughly 80 dB below the signal peak.
FFT of the digitised 2 kHz test signal (Hann window, zero-padded). The two signal peaks sit at ±2kHz\pm 2\,\text{kHz}, the deterministic DC peak at 0 Hz comes from the 1V1\,\text{V} offset on the input, and the broadband floor across the rest of the band sets PnoiseP_{\text{noise}} in the SNR integral. The 1kHz1\,\text{kHz} and 10kHz10\,\text{kHz} spectra look qualitatively identical with peaks at ±1kHz\pm 1\,\text{kHz} and ±10kHz\pm 10\,\text{kHz} respectively.

The 74dB74\,\text{dB} ceiling is itself the quantization-limited SNR: the LSB-floor noise of an ideal 12-bit quantizer, with no other error source present. Hitting it requires that every other contributor, supply ripple, reference drift, EMI pickup, clock jitter, breadboard parasitics, is at least 10dB10\,\text{dB} below the quantization floor. The observed 55dB\sim 55\,\text{dB} is therefore not a quantization-noise problem in any direct sense; quantization sets the optimistic ceiling, and the gap is entirely additive noise on top of that ceiling. The frequency-flatness of the gap across 11, 22, and 10kHz10\,\text{kHz} is consistent with this: quantization noise is white in expectation, and so is broadband supply/EMI noise, so neither has a fingerprint that would single it out from the others without injecting a known disturbance and measuring the SNR delta.

Time-domain plots of all five channels for the 1V1\,\text{V}-amplitude 1V1\,\text{V}-offset test tone show identical waveforms with the correct frequency and shape, but with a systematic offset: the peaks sit slightly above 2V2\,\text{V} and the troughs slightly above 0V0\,\text{V}, indicating a small, channel-independent bias on the measured DC level. Because the bias is identical across all five ADCs, it is a property of the measurement chain (reference, input impedance, breadboard drop) rather than of any individual MCP3201.

Five stacked time-domain plots showing ADCs 1 through 5 each capturing the same 1 V amplitude, 1 V offset sinusoid; every channel reproduces the same waveform with peaks slightly above 2 V and troughs slightly above 0 V.
Time-domain traces of ADC 1–5 capturing the common AD2 sinusoid. All five channels reproduce the input shape identically, but the peaks sit slightly above the expected 2V2\,\text{V} and the troughs slightly above 0V0\,\text{V}, the offset is the same on every channel and is therefore a property of the measurement chain rather than of any individual MCP3201.

Future Work

The 19dB19\,\text{dB} gap between empirical and quantization-limited SNR is the headline number to attack. Three classes of fix change the cost/benefit calculation differently. First, the analog front-end is currently a breadboard with long jumper wires and shared rails, which makes it a near-perfect aerial for EMI from the bench environment and the Pi’s own switching regulators; lifting the design onto a soldered PCB with a continuous ground pour, short trace runs, and shielded analog islands is the single largest available improvement and would also resolve the unexplained 50Hz50\,\text{Hz} knee in the filter response. Second, the LC filter as built has a real inductance 27%27\,\% above nominal, which both shifts the cutoff and adds DCR-driven attenuation in the passband, replacing the 100mH100\,\text{mH} inductor with a tighter-tolerance part (or characterising the actual LL before laying out the filter) removes a contributor to in-band signal loss that directly drags PsignalP_{\text{signal}} down in the SNR ratio. Third, a more stable voltage reference (a dedicated LDO\text{LDO} or precision reference rather than the Pi’s 3.3 V rail) tightens VrefV_{\text{ref}} and therefore tightens the LSB, which removes a multiplicative noise term that the current rail leaves on the table.

The systematic offset visible in every ADC time series is a separate problem and a much cheaper one to solve. Because the offset is identical across channels and stable across runs, it can be measured once at calibration time (drive a known VcalV_{\text{cal}} into IN+, read all five codes, store the per-channel offset b^i\hat{b}_i) and subtracted in software during post-processing. Texas Instruments publishes a standard recipe for this kind of two-point ADC calibration; folding it into the existing Python pipeline is a few lines of code and removes a bias that currently has to be accounted for downstream every time the system is used.

A more fundamental redesign for the next iteration is to replace the bit-banged SPI master with the Pi’s native hardware SPI peripheral. The DMA-bit-banged path was chosen because it lets five independent MISO lines be sampled in lock-step on a controller that has only one hardware SPI peripheral, but it costs a non-trivial amount of CPU bandwidth in rawWaveAddSPI setup and limits the maximum sustainable sample rate well below what the MCP3201 datasheet permits (100ksps100\,\text{ksps} at VDD=2.7VV_{\text{DD}} = 2.7\,\text{V}, faster at 3.3V3.3\,\text{V}). Moving to a multi-channel ADC (or to a Pi 4/5 with multiple hardware SPIs) lifts that ceiling and pushes the project toward the radar/microphone sample rates the eventual application needs.