analyze_spectrum
Overview
analyze_spectrum performs FFT-based spectrum analysis for ADC characterization, computing key metrics including ENOB, SNR, SNDR, SFDR, THD, and harmonic content. This is the primary tool for frequency-domain ADC performance evaluation.
Syntax
from adctoolbox import analyze_spectrum
# Simplest usage
result = analyze_spectrum(signal, fs=100e6)
# With harmonic analysis
result = analyze_spectrum(signal, fs=100e6, harmonic=5)
# With custom windowing
result = analyze_spectrum(signal, fs=100e6, window='blackman', nfft=8192)
# With averaging
result = analyze_spectrum(signal, fs=100e6, num_avg=4, mode='coherent')
# With interactive plotting
result = analyze_spectrum(signal, fs=100e6, show_plot=True)
Parameters
signal(array_like) — Input ADC signal (1D array)fs(float) — Sampling frequency in Hzharmonic(int, default=9) — Number of harmonics to analyze (2-20)window(str, default='blackman') — Window function: 'rect', 'hann', 'hamming', 'blackman', 'flattop'nfft(int, optional) — FFT length. If None, uses len(signal)num_avg(int, default=1) — Number of segments for averagingmode(str, default='coherent') — Averaging mode: 'coherent' or 'power'show_plot(bool, default=False) — Display interactive spectrum plotax(matplotlib axis, optional) — Axis for plotting
Returns
Dictionary containing:
Metrics:
enob— Effective Number of Bitssnr_db— Signal-to-Noise Ratio (dB)sndr_db— Signal-to-Noise-and-Distortion Ratio (dB)sfdr_db— Spurious-Free Dynamic Range (dB)thd_db— Total Harmonic Distortion (dB)sinad_db— Signal-to-Noise-and-Distortion (alternative name for SNDR)nsd_dbfs_hz— Noise Spectral Density (dBFS/Hz)
Spectrum Data:
freq— Frequency bins (Hz)magnitude_db— Spectrum magnitude (dBFS)fundamental_bin— FFT bin of fundamental frequencyfundamental_freq— Fundamental frequency (Hz)harmonic_bins— FFT bins of harmonics (list)harmonic_powers— Power of each harmonic (dB, list)
Algorithm
1. Signal Preprocessing
# Apply window
windowed_signal = signal * window_function
# Zero-pad if nfft > len(signal)
if nfft > len(signal):
windowed_signal = np.pad(windowed_signal, (0, nfft - len(signal)))
2. FFT Computation
spectrum = np.fft.fft(windowed_signal, n=nfft)
magnitude = np.abs(spectrum[:nfft//2])
magnitude_db = 20 * np.log10(magnitude / nfft * 2) # Convert to dBFS
3. Fundamental Detection
# Find peak in first Nyquist zone (excluding DC)
fundamental_bin = np.argmax(magnitude[1:nfft//2]) + 1
fundamental_freq = fundamental_bin * fs / nfft
4. Harmonic Identification
harmonic_bins = [fundamental_bin * k for k in range(2, harmonic + 1)]
# Fold harmonics above Nyquist back into spectrum
harmonic_bins_folded = [fold_bin_to_nyquist(h, nfft) for h in harmonic_bins]
5. Metric Calculation
# Signal power (fundamental)
P_signal = magnitude[fundamental_bin]**2
# Harmonic power
P_harmonics = sum([magnitude[h]**2 for h in harmonic_bins_folded])
# Noise power (excluding signal, harmonics, DC)
P_noise = sum(magnitude**2) - P_signal - P_harmonics - magnitude[0]**2
# Metrics
SNR = 10 * log10(P_signal / P_noise)
THD = 10 * log10(P_harmonics / P_signal)
SNDR = 10 * log10(P_signal / (P_noise + P_harmonics))
ENOB = (SNDR - 1.76) / 6.02
SFDR = fundamental_mag_db - max_spur_mag_db
Examples
Example 1: Basic Spectrum Analysis
import numpy as np
from adctoolbox import analyze_spectrum
# Generate test signal
N = 2**13
fs = 100e6
fin = 123/N * fs # Coherent frequency
t = np.arange(N) / fs
signal = 0.5 * np.sin(2*np.pi*fin*t) + np.random.randn(N) * 10e-6
# Analyze
result = analyze_spectrum(signal, fs=fs)
print(f"ENOB: {result['enob']:.2f} bits")
print(f"SNDR: {result['sndr_db']:.2f} dB")
print(f"SFDR: {result['sfdr_db']:.2f} dB")
print(f"THD: {result['thd_db']:.2f} dB")
Example 2: With Interactive Plotting
result = analyze_spectrum(signal, fs=800e6, show_plot=True)
# Opens interactive plot showing spectrum, harmonics, and metrics
Example 3: Windowing Comparison
windows = ['rect', 'hann', 'blackman', 'flattop']
for win in windows:
result = analyze_spectrum(signal, fs=fs, window=win)
print(f"{win:10s}: SFDR={result['sfdr_db']:6.2f} dB, "
f"SNDR={result['sndr_db']:6.2f} dB")
Example 4: Coherent Averaging
# Average 4 segments coherently
result = analyze_spectrum(signal, fs=fs, num_avg=4, mode='coherent')
# Reduces noise floor by ~6 dB (factor of 4)
Window Function Selection
Window |
ENBW* |
Peak Side Lobe |
Use Case |
|---|---|---|---|
rect |
1.0 |
-13 dB |
Coherent signals only |
hann |
1.5 |
-32 dB |
General purpose |
hamming |
1.36 |
-43 dB |
Better side lobe rejection |
blackman |
1.73 |
-58 dB |
High dynamic range (default) |
flattop |
3.77 |
-93 dB |
Accurate amplitude measurement |
*ENBW = Equivalent Noise Bandwidth (bins)
Interpretation
ENOB (Effective Number of Bits)
ENOB ≈ N-bit ADC: Ideal performance
ENOB < N - 2: Poor performance, investigate noise/distortion
ENOB > N: Oversampling or dithering improving effective resolution
SFDR (Spurious-Free Dynamic Range)
SFDR > 80 dB: Excellent linearity
60 dB < SFDR < 80 dB: Good for most applications
SFDR < 60 dB: Significant spurs, check for:
Power supply coupling
Clock feedthrough
Intermodulation products
THD (Total Harmonic Distortion)
THD < -80 dB: Very linear
-60 dB < THD < -80 dB: Acceptable for most uses
THD > -60 dB: Nonlinearity issues
Common Issues
Non-Coherent Sampling
# BAD: Arbitrary frequency causes spectral leakage
signal = np.sin(2*np.pi*10.1234e6*t) # Non-coherent
# GOOD: Use coherent frequency
from adctoolbox import find_coherent_frequency
fin_coh, bin_num = find_coherent_frequency(fs, 10e6, N)
signal = np.sin(2*np.pi*fin_coh*t)
Insufficient FFT Length
# BAD: Low resolution
result = analyze_spectrum(signal[:512], fs=fs) # Only 512 points
# GOOD: More frequency resolution
result = analyze_spectrum(signal, fs=fs, nfft=8192)
See Also
analyze_spectrum_polar— Polar phase spectrum analysisfind_coherent_frequency— Calculate coherent test frequencies
References
IEEE Std 1241-2010, "IEEE Standard for Terminology and Test Methods for ADCs"
F. J. Harris, "On the Use of Windows for Harmonic Analysis with the DFT," Proc. IEEE, 1978
Application Note AN-9675, "Coherent Sampling Calculator," Analog Devices