Quantization and the ADC
The analog-to-digital converter slices the amplified signal into discrete levels. Rounding to the nearest level injects quantization error, which behaves like added noise. For an ideal N-bit converter driven full-scale by a sinusoid, the best possible signal-to-noise ratio is a clean function of bit depth.
\mathrm{SNR}_{\mathrm{dB}} = 6.02\,N + 1.76Ideal quantization SNR: each extra bit buys about 6 dB. A 10-bit ADC gives ~62 dB, comfortably below the electrode/amplifier noise for neural work — which is deliberate.
For an ideal converter, each extra bit of resolution buys about 6 dB more signal-to-noise. So the bit count directly sets the cleanest digitization physics allows — before any real-circuit imperfections.
- \mathrm{SNR}_{\mathrm{dB}}
- Ideal quantization signal-to-noise ratio in decibels.
- N
- Number of bits in the converter.
- 6.02\,N
- The roughly 6 dB gained per bit added.
A 10-bit ADC gives about 62 dB — deliberately below the electrode and amplifier noise for neural work.
The design insight is counterintuitive: you do not want an excellent ADC. Since the true analog noise floor is a few μV, quantizing much finer than that just burns power and produces bits that only encode noise. The right ADC resolution places the quantization step just below the analog noise — typically 8–12 bits — so nothing real is lost and nothing is wasted. Real converters fall short of the ideal, captured by the effective number of bits.
\mathrm{ENOB} = \dfrac{\mathrm{SNDR}_{\mathrm{dB}} - 1.76}{6.02}Effective number of bits, backed out from the measured signal-to-noise-and-distortion ratio; it is the honest resolution once real-circuit imperfections are counted.
Run the ideal formula backwards using the real, measured signal quality to find how many bits of resolution you actually get. It's the honest resolution once real-circuit imperfections are counted — usually a bit or two below the nominal bit count.
- \mathrm{ENOB}
- Effective number of bits — the resolution you really achieve.
- \mathrm{SNDR}_{\mathrm{dB}}
- Measured signal-to-noise-and-distortion ratio, in dB.
A converter sold as 12-bit but measuring 68 dB SNDR really delivers only about 11 effective bits.
Sampling and aggregate throughput
Each channel must be sampled above twice its bandwidth (Nyquist). The spike band to ~5–7 kHz forces f_s around 20–30\,\mathrm{kSa/s} per channel; LFP-only channels can be far slower. Multiply across the array and you recover the firehose from Guide 1.
This is where the pipeline branches. You can transmit raw broadband and decode off-implant — accurate but power-hungry on the radio — or you can spend a little on-chip compute to send far fewer bits. The rest of this guide and the next are about that trade, because the radio, not the amplifier, is usually the power hog.
Time-division multiplexing: one ADC, many channels
Because a full ADC is large and power-hungry, you cannot give every electrode its own. Instead, time-division multiplexing (TDM) cycles one fast ADC across many channels: a switch connects channel 1, the ADC samples, then channel 2, and so on. An ADC shared over M channels must run M times faster than any single channel.
- A multiplexer selects channel k and connects its sample-and-hold to the shared ADC input.
- The node must settle to within ½ LSB before conversion — the settling budget is one slot, T_slot = 1 / (M · f_s).
- The ADC converts, the sample is stored or streamed, and the multiplexer advances to channel k+1.
- After M slots every channel has been visited once; the cycle repeats at each channel's sample instant.
On-chip spike detection: taming the firehose
Most of the broadband stream is baseline between spikes. If the application only needs spike times and waveforms, detecting spikes on the implant and sending only small snippets around each one slashes the data rate by one to two orders of magnitude. The simplest detector is an amplitude threshold; a more robust one is the nonlinear energy operator (NEO / Teager energy), which emphasizes the sharp, high-frequency shape of a spike.
\psi[x_n] = x_n^2 - x_{n-1}\,x_{n+1}The discrete nonlinear energy operator: large when the signal is both high in amplitude and high in frequency — precisely a spike — and small for slow baseline drift.
A cheap detector that lights up only when the signal is both large and fast-changing — exactly what a spike is — while staying near zero for slow baseline drift. In effect it tracks instantaneous energy, which scales with amplitude squared times frequency squared.
- \psi[x_n]
- The energy-operator output at sample n — big for spikes.
- x_n
- The current signal sample.
- x_{n-1}\,x_{n+1}
- The product of the neighbours on either side.
The output is large for a sharp, fast spike but tiny for a slow baseline wander.
The threshold itself must be robust to the fact that noise level varies across channels and drifts over time. A widely used estimator sets the threshold as a multiple of a median-based noise estimate, which resists being inflated by the very spikes you are trying to detect.
\mathrm{Thr} = c\,\sigma_n, \qquad \sigma_n = \dfrac{\mathrm{median}\!\left(|x|\right)}{0.6745}Median-based noise estimate (the 0.6745 factor converts median-absolute-deviation to a Gaussian σ); a threshold multiplier c of about 4–5 is typical.
Set a spike-detection threshold at a few times the noise level — but estimate that noise robustly from the median of the absolute signal (which spikes barely perturb) rather than the standard deviation (which spikes inflate). The 0.6745 factor converts the median-absolute-deviation into a Gaussian sigma.
- \mathrm{Thr}
- The threshold a sample must exceed to count as a spike.
- c
- The threshold multiplier, typically around 4 to 5.
- \sigma_n
- The robust noise estimate the threshold is built from.
- \mathrm{median}(|x|)/0.6745
- Median absolute value converted to an equivalent Gaussian sigma.
With c \approx 4, only excursions past four sigma above the noise count as detected spikes.
# On-chip-style spike detection with a robust, self-tuning threshold
import numpy as np
def detect_spikes(x, c=4.5, refractory=15):
sigma = np.median(np.abs(x)) / 0.6745 # robust noise estimate
thr = c * sigma
neo = x[1:-1]**2 - x[:-2] * x[2:] # nonlinear energy operator
above = np.where(neo > thr**2)[0] + 1 # NEO is compared to thr^2
spikes, last = [], -refractory
for i in above: # enforce a refractory gap
if i - last >= refractory:
spikes.append(i); last = i
return spikesBeyond detection: on-chip compression
Even after thresholding, high-count arrays produce a lot of data, and some applications need the LFP or full waveforms too. On-chip data compression fills the gap: lightweight, lossy or lossless schemes — delta coding, waveform templates, learned dictionaries — that shrink the payload before it reaches the radio. The design rule is that a compressor is only worth including if the power it saves on telemetry exceeds the power it burns to compute, a balance we make precise in Guide 4.