← All tutorials

ADC Fundamentals

How a voltage becomes numbers.

On this page

1 An ADC turns a wave into numbers 2 How fine the steps are 3 Three converters trade speed, precision and power 4 In practice 5 When an ADC reading goes wrong 6 References

An analog-to-digital converter (ADC) measures the voltage held at its input once per sample, at a fixed rate, and outputs each measurement as a whole number, a code. Three choices shape it: the voltage range its codes cover, how many codes it has, and how fast it samples. Range and code count set the smallest change it can see and the noise its rounding adds; how fast to sample is Aliasing and Anti-Aliasing Filters.

electrode microvolts amplifier gain 250x anti-alias cutoff 400 Hz ADC 1 kHz, 10 bit codes 0..1023 electrode microvolts picks up the brain's voltage amplifier gain 250x makes it big enough to measure anti-alias cutoff 400 Hz removes frequencies too fast to sample ADC 1 kHz, 10 bit 1000 samples a second, each rounded to a level codes 0..1023 1024 levels, stored as integers
Figure 1. The recording chain. The ADC samples in time and rounds in amplitude.

1 An ADC turns a wave into numbers

1.1 What the ADC measures

Every ADC has a reference voltage, Vref, that sets the top and bottom of what it can measure. The ADC on this page has Vref = 1.2 V and is differential: it measures one input minus the other, which can come out negative. So it reads from −1.2 V to +1.2 V, a span of 2.4 V. Anything beyond saturates, and the output flatlines at its top or bottom code.

Quantization rounds the voltage to the nearest code: a ramp goes in, a staircase comes out, and each stair step is one LSB (Least Significant Bit), the voltage width of one step. The resolution, B bits, sets how many steps fit in the range; together with Vref it sets the smallest detectable change.

How many codes B bits give.
codes = 2B
The code count is 2 multiplied by itself B times, B being the number of bits. Each bit doubles the count, because it splits every existing code in two, so the count goes 2, 4, 8 and on. At 10 bits that is 1,024 codes, numbered 0 to 1,023, and one more bit gives 2,048. The LSB card of Section 2.2 divides the range by this count.

Bit depth decides how fine the stairs are, and that sets your quantization noise floor (the level of rounding noise sitting under the signal), measured as the signal-to-noise ratio (SNR).

The sampling rate has its own failure, aliasing, and its own fix, the anti-alias filter in front of the ADC. Both live on Aliasing and Anti-Aliasing Filters.

2 How fine the steps are

2.1 The ADC staircase

Figure 2. A 1 V sine and its ADC output. 1The ±1.2 V rails, where it saturates.

2.2 How wide one stair step is

In volts, one LSB is the full span divided by the number of codes. At ±1.2 V and 10 bits that is 2.34 mV per code. A wider Vref stretches each step; more bits shrink every step.

How wide one stair step is.
LSB = 2·Vref2B
The codes spread evenly over the full span from −Vref to +Vref, which is 2Vref wide, so one step is that span divided by the code count 2B. Each extra bit halves the step, and a larger Vref widens it. It builds on the code count of Section 1.1, and the card in Section 2.3 rounds to these steps.

Figure 3. 1One LSB, the same width at every bit depth. 2A 100 µV spike to scale.

At 10 bits a 100 µV neural spike fills under a twentieth of one step, so the ADC cannot see it.

2.3 One sample, four steps

One conversion shifts, scales, rounds and clips a voltage to give its code.

The code the sample lands on, and the error left.
code = round(Vin − VminLSB)
Vin is the input voltage at the moment of sampling. Subtracting Vmin = −Vref puts the bottom of the range at zero, and dividing by the LSB counts the steps up. Rounding picks the nearest of the 2B levels.
0 ≤ code ≤ 2B − 1
B bits hold the codes 0 to 2B − 1. A voltage past a rail, an end of the range ±Vref, clips to an end code. That is saturation.
e = Vin − Vcode
The gap between the sample and its level. Rounding keeps it within half a step, unless the input was clipped. It builds on the LSB card of Section 2.2, and Section 2.4 turns this error into a noise floor.

Figure 4. A toy 3-bit ADC over ±1.2 V reading 0.42 V. 1The sample.

Two voltages converted to codes in NumPy; 1.5 V is past the rail and clips to the top code.

import numpy as np

V_REF, B = 1.2, 10                 # V_ref and B from the cards
lsb = 2 * V_REF / 2**B
v_in = np.array([0.42, 1.5])       # 1.5 V is past the rail

code = np.round((v_in + V_REF) / lsb)
code = np.clip(code, 0, 2**B - 1)
v_level = -V_REF + code * lsb
error = v_in - v_level             # e in the card
print(code, error)
  1. 1Sets Vref, B, one LSB (eq card in Section 2.2, line 1) and two input voltages, one past a rail.
  2. 2Shifts, divides by the LSB, rounds and clips (eq card in Section 2.3, lines 1 and 2).
  3. 3The voltage of each code's level and the rounding error (eq card in Section 2.3, line 3).

2.4 Rounding error becomes the noise floor

Rounding to the nearest code leaves a quantization error of at most half an LSB either way, even on a perfect noiseless signal. On a slow ramp the error is a sawtooth from −½ LSB to +½ LSB that resets at every step crossing.

On a real signal those sawtooth cycles fall out of phase and blur into noise with zero average, every value in the range about equally likely. That is a uniform random variable, so its variance (how spread out the errors are) is the LSB squared over twelve.

Over this page’s ±1.2 V range, the 3-bit converter of Figure 4 steps 0.3 V and a 16-bit one 36.6 µV; the card gives their typical rounding errors, about 0.087 V and 10.6 µV.

How big the rounding noise is.
variance = LSB212
When the input is equally likely to land anywhere within a step, the error is spread evenly over one LSB, and an even spread of width LSB has variance LSB²12. The noise floor therefore depends only on the step size, not on the signal.
σq = LSB√12
σq, the standard deviation, is the square root of that variance, the typical size of one error, about 0.29 LSB. It scales with the step, so halving the LSB halves it. It builds on the rounding error of Section 2.3, and the SNR cards in Section 2.5 use it as the noise power.

A check of the LSB²/12 rule on 100,000 random voltages at 8 bits, drawn below the top level so none saturates. The errors stay within about ±0.5 LSB, and the variance ratio printed last comes out near 1.0.

import numpy as np

V_REF, B, N_SAMPLES = 1.2, 8, 100_000
lsb = 2 * V_REF / 2**B
rng = np.random.default_rng(0)
v_in = rng.uniform(-V_REF, V_REF - lsb, N_SAMPLES)

code = np.round((v_in + V_REF) / lsb)
error = v_in - (-V_REF + code * lsb)
print(error.min() / lsb, error.max() / lsb)
print(error.var() / (lsb**2 / 12))
  1. 1Draws 100,000 voltages at 8 bits, all below the top level.
  2. 2Rounds each to a code and takes the error (eq card in Section 2.3, lines 1 and 3).
  3. 3The errors stay within about ±0.5 LSB.
  4. 4Error variance over LSB²/12, near 1 (eq card in Section 2.4, line 1).

Figure 5. 1Steps on the true ramp. 2Rounding error. 3How often each occurs.

2Max |error| = 0.5 LSB (always)
3σq = LSB√12

2.5 Each extra bit adds 6.02 dB of SNR

The signal-to-noise ratio (SNR) compares signal power to noise power, in decibels.

Noise sourceNoise power (V2)RatioSNR
Quiet bedroom10−51,00030 dB
Fan nearby10−31010 dB
10-bit rounding alone4.6 × 10−721,80043.4 dB
Figure 6. One voice memo, speech power 0.01 V2. Room noise powers illustrative.

In both rooms the room’s noise, not the converter’s rounding, sets the SNR.

SNR in decibels.
SNR = 10·log10(PsignalPnoise)
Psignal is the mean square of the signal and Pnoise the mean square of the noise, measured in a pause or worked out as in Section 2.4. The SNR says how many times stronger the signal’s power is than the noise’s, written as a level in dB. It is the decibel formula for a power ratio, with the noise power as the reference; the second card in Section 2.5 gives its best value per bit, and The Wiener Filter sets its gain from this ratio, one narrow band of frequencies at a time.

For a full-scale sine wave (one that swings across the whole range the ADC can measure), that ratio grows by about 6 dB for every bit.

The best SNR a B-bit ADC reaches on a full-scale sine.
SNR = 6.02B + 1.76 dB
Each extra bit halves the step, which halves the noise voltage and quarters the noise power, a gain of 6.02 dB per bit. The 1.76 dB comes from the sine's shape, because a full-scale sine's power over the rounding noise is 1.5 × 22B, and 10 log10 1.5 = 1.76. Ten bits give about 62 dB. It builds on the first SNR card and the rounding noise of Section 2.4, and the defaults table in Section 4 quotes it at 16 bits.

A sine half a step inside full scale, quantized at 10 bits. The SNR is the mean signal power over the mean error power, and it prints about 62 dB, as 6.02 B + 1.76 predicts.

import numpy as np

V_REF, B, N_SAMPLES = 1.2, 10, 100_000
FREQ = 0.01234                      # cycles per sample
lsb = 2 * V_REF / 2**B
amp = V_REF - lsb / 2
sig = amp * np.sin(2 * np.pi * FREQ * np.arange(N_SAMPLES))

code = np.clip(np.round((sig + V_REF) / lsb), 0, 2**B - 1)
quant = -V_REF + code * lsb
p_signal = np.mean(sig**2)
p_noise = np.mean((sig - quant)**2)
print(10 * np.log10(p_signal / p_noise))
  1. 1A 10-bit sine half a step inside full scale.
  2. 2Quantizes it (eq card in Section 2.3, line 1).
  3. 3Signal power over error power in dB, about 62 (first eq card in Section 2.5, checked against the second).

Figure 7. 1Each bit adds a 6.02 dB block. 2A full-scale sine adds 1.76 dB.

SNR = 62.0 dB
Noise voltage = halves each bit
6.02 dB for each bit, on a 1.76 dB base

2.6 Gain magnifies the signal and shrinks the input range

Neural signals are tens to hundreds of times smaller than the ADC's 2.34 mV step, so wired straight in they read as zero. The amplifier multiplies the electrode voltage by a constant gain. At a gain of 250 one code covers 9.375 µV at the electrode, but the input range that fits between the ±1.2 V rails shrinks by the same factor, so the signal clips sooner.

Gain multiplies the voltage, so at the electrode the step and the input range shrink by the same factor.
Vamp = gain × Vel
The amplifier multiplies the electrode voltage Vel by a constant gain to give Vamp, the voltage the ADC sees, which must stay within the ±1.2 V rails. The larger the gain, the smaller the electrode voltage that already reaches a rail.
LSBel = ADC LSBgain
LSBel is the step at the electrode. Every electrode voltage arrives gain times bigger, so one ADC step covers a slice that many times smaller there. At a gain of 250 the 2.34 mV step covers 9.375 µV at the electrode.
electrode range = ±Vrefgain
Dividing the rails by the same gain gives the electrode range, the input voltages that fit. Raising the gain shrinks the step and the input range together, so a finer step always comes with a bigger risk of clipping. It builds on the LSB card of Section 2.2, and the knob table in Section 4 sets the gain from it.

The gain card in numbers. Dividing by GAIN turns the 2.34 mV ADC step into 9.375 µV at the electrode and the ±1.2 V rails into a ±4.8 mV input range.

V_REF, B, GAIN = 1.2, 10, 250

lsb_adc = 2 * V_REF / 2**B
lsb_electrode = lsb_adc / GAIN
in_range = V_REF / GAIN            # electrode side, volts
print(lsb_adc, lsb_electrode, in_range)
  1. 1The reference, the bits and the gain.
  2. 2One ADC step (eq card in Section 2.2, line 1).
  3. 3The step at the electrode (eq card in Section 2.6, line 2).
  4. 4The electrode input range (eq card in Section 2.6, line 3).

Figure 8. A 1 mV spike at gains 1 to 2,000. 1The input range. 2The amplified spike.

1Electrode range = ±4.8 mV
2ADC sees 1.0 mV
OK

At the ADC input the range stays ±1.2 V whatever the gain. Adding bits splits it into finer steps; only a smaller reference voltage shrinks the range itself.

3 Three converters trade speed, precision and power

Flash compares the voltage against every level at once, with one comparator (a circuit that reports which of two voltages is larger) per level. It is the fastest, but it needs 2B − 1 comparators, so each extra bit doubles them; it stays at low bit depths and uses the most power.

SAR (successive-approximation register) finds one bit per comparison with the middle of the remaining range, so three comparisons cover eight levels. It sits in the middle, with medium speed and resolution and low power.

Sigma-delta samples far faster than it needs to and averages the noise away: slow, but extremely fine. A feedback loop keeps a running sum (sigma) of the difference (delta) between its coarse output and the input, which turns the rounding noise into fast wiggles from one sample to the next; averaging many samples smooths those wiggles away. Sampling far faster than needed is called oversampling; Aliasing and Anti-Aliasing Filters puts it to a second use.

inputFlashevery level at oncefastest, most powerSARhalve the range, one bit each3 comparisons for 8 levelsSigma-deltamany fast coarse samplesaveraged, slow, very fine
Figure 9. 1Flash, all levels at once. 2SAR, a bit per step. 3Sigma-delta, averaging.

The SAR search as a function, run on the 3-bit sample of Section 2.3. Each pass of the loop asks one higher-or-lower question and appends one bit, so sar(0.42) returns 5, binary 101.

def sar(v_in, v_ref=1.2, B=3):
    lsb = 2 * v_ref / 2**B
    lo, hi = -v_ref - lsb / 2, v_ref - lsb / 2  # half-step shift
    code = 0
    for _ in range(B):
        mid = (lo + hi) / 2
        bit = int(v_in >= mid)
        code = 2 * code + bit
        lo, hi = (mid, hi) if bit else (lo, mid)
    return code

print(sar(0.42))
  1. 1A 3-bit search over ±1.2 V, the range shifted by half a step.
  2. 2Compares the input with the middle of the range (marker 2 in Figure 9).
  3. 3Keeps the bit and halves the range toward the input.
  4. 4Returns code 5, binary 101, for 0.42 V.

4 In practice

Audio takes the same path as the electrode in Figure 1, with a microphone in its place. Software never sees volts, only the integer codes the converter wrote, usually inside a WAV file.

StepHow often, on what dataState it keepsCost
Gain stageset once at design time, on the electrode voltagenoneone amplifier
Flash ADConce per sample, on the held voltagenoneone comparator per level, all in one step
SAR ADConce per sample, one clock cycle per bit, on the held voltagethe bits found so farone comparison per bit
Sigma-delta ADCmany 1-bit samples per output, on the input voltagethe running sum, and the averaging filter that keeps one output in every R samplesone output for every R one-bit samples, where R, the oversampling ratio, is how many times faster it samples than it outputs

Rates, state and cost come from the sources in References.

A one-second tone stored as 16-bit codes and read back with SciPy. wavfile.read returns the rate and the int16 codes, and the last line turns them into floats between −1 and 1.

import numpy as np
from scipy.io import wavfile

FS, TONE_HZ, FULL_SCALE = 16000, 440, 32768
t = np.arange(FS) / FS
tone = 0.5 * np.sin(2 * np.pi * TONE_HZ * t)
codes = np.round(tone * (FULL_SCALE - 1)).astype(np.int16)
wavfile.write("tone.wav", FS, codes)

rate, read_back = wavfile.read("tone.wav")
print(rate, read_back.dtype)           # 16000 int16
signal_float = read_back.astype(np.float64) / FULL_SCALE
  1. 1A one-second 440 Hz tone at half of full scale.
  2. 2Rounds it to 16-bit codes (eq card in Section 2.3, line 1, at 16 bits).
  3. 3Writes the codes to a WAV file.
  4. 4Reads them back and scales them to floats between −1 and 1.
SettingDefaultSource
16-bit PCM WAVint16 codes, −32768 to +32767scipy.io.wavfile.read
24-bit PCM WAVreturned as int32, shifted to the top bitsscipy.io.wavfile.read
Best SNR at 16 bitsabout 98 dBthe SNR card in Section 2.5
Knob, where it entersRaise itLower itDefault
Sample rate fs, the time step 1/fs between samplesHigher frequencies fit below fs/2; more data per secondLess data; anything above fs/2 folds down, so below twice the highest frequency present the signal aliases16 kHz for speech (torchaudio kaldi.mfcc)
Bits B, in codes = 2BEach extra bit halves the step and adds about 6 dB of SNR; 16 bits give about 98 dBCoarser steps, louder quantization noise; B = 1 keeps only the sign16-bit PCM (scipy.io.wavfile.read)
Gain, multiplies the electrode voltage before the ADCFiner steps at the electrode, LSB/gain; the input range ±Vref/gain shrinks until normal signals saturateWider input range, coarser steps; at a gain of 1 one step is 2.34 mV and a 100 µV spike reads as zeroNone; pick it so the largest expected input stays inside ±Vref/gain (this page uses 250)

SciPy documents the reader at scipy.io.wavfile.read.

4.1 Gotchas

5 When an ADC reading goes wrong

What you seeLikely causeWhat to do
Flat tops on the peaksThe input went past ±Vref and saturatedLower the gain or widen Vref
The signal looks blocky, in visible stepsThe LSB is too big for the signalMore gain or more bits
A hiss floor you cannot lowerQuantization noise, LSB2/12More bits, 6.02 dB each, or more gain
Tones at frequencies you never hadAliasingSee Aliasing and Anti-Aliasing Filters

What goes wrong when sampling is too slow? That is the next page, Aliasing and Anti-Aliasing Filters.

6 References

  1. Kester, W., et al. (2004). Analog-Digital Conversion. Analog Devices, Chapter 2. The SNR derivation and the quantization-noise analysis this page leans on.
  2. Analog Devices. Tutorial MT-001: Taking the Mystery out of the Infamous Formula, "SNR = 6.02N + 1.76dB," and Why You Should Care.
  3. Bennett, W. R. (1948). Spectra of Quantized Signals. Bell System Technical Journal, 27. Why quantization error looks like uniform noise.