How Software Defined Radio Works: From Analog Front-End to Digital Signal Processing
Try the interactive lab for this articleTake the quiz (6 questions · ~5 min)A radio receiver built in 1960 and a radio receiver built in 2020 both do the same job: they extract a desired signal from the electromagnetic soup arriving at the antenna. The difference is where the work happens. In the 1960 radio, every stage is a physical circuit: a tuned LC filter selects the frequency, a mixer shifts it down, a crystal filter sets the bandwidth, and an analog demodulator recovers the audio. Change anything about what the radio does and you are reaching for a soldering iron.
Software Defined Radio inverts this. You digitize the incoming signal as early as possible, then do the filtering, mixing, demodulation, and decoding in software. The same hardware that receives FM broadcast radio at 98 MHz can receive aircraft transponders at 1090 MHz, decode weather satellite images at 137 MHz, or monitor the ISM band at 433 MHz. Nothing changes physically. You run a different program.
This post covers how that works, from the analog front-end that conditions the signal before digitization, through the ADC that converts it to numbers, to the digital signal processing chain that turns those numbers into something useful. The mathematics involved are not optional decoration; they are the reason SDR works at all.
1. What SDR Actually Means
In a traditional superheterodyne receiver (the architecture that dominated the 20th century), the signal processing chain is fixed in hardware:
- Antenna captures RF
- Bandpass filter selects a frequency range
- Low Noise Amplifier boosts the signal
- Mixer multiplies the signal with a local oscillator, producing an Intermediate Frequency
- IF filter sets the channel bandwidth
- Demodulator extracts the information (AM envelope detector, FM discriminator, etc.)
Every one of those stages is a physical component. Want to change the IF bandwidth? Replace the crystal filter. Want to switch from AM to FM demodulation? Different circuit. Want to add a digital modulation scheme that did not exist when the radio was built? Buy a new radio.
SDR replaces most of this chain with software. The boundary between analog and digital moves as close to the antenna as the available ADC technology allows. In a minimal SDR, the analog chain is just: antenna, filter, LNA, mixer, and ADC. Everything after the ADC (channel selection, filtering, demodulation, decoding) is code running on a general-purpose processor, FPGA, or GPU.
The tradeoff is real. A hardware filter at a specific frequency will outperform a software filter in terms of dynamic range, power consumption, and latency. A purpose-built FM demodulator circuit running at its design frequency will produce cleaner audio than an SDR running the same demodulation in software on an 8-bit ADC. But the SDR can do a thousand different things with the same hardware, can be updated without physical changes, and costs a fraction of what dedicated hardware for each application would cost.
This tradeoff explains why SDR has not replaced traditional radio everywhere. Military and space applications, where power and performance margins are critical, still use a lot of dedicated hardware. But for development, research, education, spectrum monitoring, amateur radio, and any application where flexibility matters more than squeezing the last decibel of performance, SDR has become the standard approach.
The SDR Spectrum of Flexibility
Not all SDR is created equal. There is a spectrum from "mostly hardware" to "mostly software":
- Hardware-centric SDR: FPGA does the heavy DSP (channelization, filtering), CPU handles higher-level protocol decoding. Examples: Ettus USRP with FPGA fabric, military JTRS radios.
- Hybrid SDR: Some filtering and decimation in hardware (often on the ADC chip itself), most processing on the host CPU. Examples: RTL-SDR, most hobbyist platforms.
- Pure software SDR: The ADC just streams raw samples; everything happens on the CPU or GPU. This is the conceptual ideal but requires enormous compute for wideband signals.
The rest of this post follows the signal from antenna to decoded output, covering each stage in the depth it deserves.
2. The RF Front-End
Before any digital processing can happen, the analog front-end must condition the signal arriving at the antenna. This stage determines what the SDR can actually receive: its frequency range, sensitivity, and susceptibility to interference.
The Antenna
An antenna converts electromagnetic waves into voltage variations on a conductor. The antenna's physical dimensions determine which frequencies it captures efficiently. A quarter-wave monopole for FM broadcast (around 88 to 108 MHz) is about 75 cm long. A quarter-wave antenna for ADS-B at 1090 MHz is about 6.9 cm. The relationship is straightforward:
λ = c / f
Quarter-wave length = λ / 4 = c / (4f)Where c is the speed of light (approximately 3 × 10⁸ m/s) and f is the frequency in hertz.
Most SDR users start with the small whip antenna included with their dongle, which is a compromise antenna that works poorly across a wide range rather than well at any specific frequency. Serious SDR work uses purpose-built antennas matched to the frequency of interest: a dipole for VHF, a discone for wideband monitoring, a QFH (Quadrifilar Helix) for weather satellite reception, a Yagi for directional gain.
Bandpass Filtering
The first filter in the chain is a bandpass filter that rejects signals outside the frequency range of interest. This matters because strong out-of-band signals (like a nearby FM broadcast transmitter, or a GSM base station on a rooftop in central Athens) can overload the LNA or ADC, causing intermodulation distortion that corrupts the signal you actually want.
For a hobbyist SDR, external bandpass or band-reject filters (sometimes called notch filters) are common accessories. A typical FM broadcast notch filter attenuates the 88 to 108 MHz band by 40+ dB to prevent nearby FM stations from saturating the front-end when you are trying to receive something weak in an adjacent band.
The Low Noise Amplifier (LNA)
After filtering, the signal is extremely weak. A satellite signal arriving at your antenna might be at -130 dBm (0.00000000000001 milliwatts). The LNA boosts this signal while adding as little noise as possible. The critical parameter is the noise figure (NF), measured in decibels: it describes how much noise the amplifier adds above the theoretical minimum (thermal noise).
A good LNA has a noise figure of 0.5 to 2 dB. The RTL-SDR's built-in R820T2 tuner has a noise figure of roughly 3.5 to 4.5 dB depending on frequency, which is acceptable for many applications but noticeably worse than a dedicated LNA.
By the Friis formula, the first amplifier's noise figure dominates the system noise figure:
NF_system = NF₁ + (NF₂ - 1)/G₁ + (NF₃ - 1)/(G₁·G₂) + ...Where NF_n is the noise factor (linear, not dB) of stage n, and G_n is the gain of stage n. This is why putting a good LNA first, close to the antenna, matters so much. If G₁ is high (say, 20 dB or 100x in linear terms), the noise contributions of all subsequent stages become negligible.
The Mixer and Superheterodyne Architecture
Here is the core problem: the signal of interest might be at 1090 MHz (ADS-B), or 137 MHz (NOAA satellites), or 433 MHz (ISM band sensors), or 1575 MHz (GPS L1). No affordable ADC can directly sample signals at these frequencies with adequate resolution. Even at 137 MHz, you would need an ADC running at least 274 MSPS (by Nyquist), and at 1090 MHz you would need 2180 MSPS, both with enough bits of resolution to be useful. Such ADCs exist, but they cost thousands of euros and consume significant power.
The solution, used since the 1930s, is the superheterodyne architecture. A mixer multiplies the incoming RF signal with a locally generated sine wave (the Local Oscillator, or LO):
RF_signal × LO_signal = cos(2πf_RF·t) × cos(2πf_LO·t)
= ½[cos(2π(f_RF - f_LO)t) + cos(2π(f_RF + f_LO)t)]This trigonometric identity is the entire basis of frequency conversion in radio. The multiplication produces two new frequencies: the sum (f_RF + f_LO) and the difference (f_RF - f_LO). A low-pass filter removes the sum term, leaving only the difference: the Intermediate Frequency (IF).
If you want to receive a 1090 MHz signal and your ADC can handle signals up to 10 MHz, you set the LO to 1080 MHz. The IF comes out at 10 MHz, which the ADC can sample comfortably. Want to receive 433 MHz instead? Change the LO to 423 MHz. Same IF, same ADC, different received frequency. This is what "tuning" means in a superheterodyne receiver.
In an SDR like the RTL-SDR, the R820T2 tuner chip contains the LNA, mixer, and LO in a single IC. It outputs an IF signal that the RTL2832U digitizes. The tuning (LO frequency) is programmable over USB, which is how software controls what frequency the SDR receives.
Direct Sampling Alternatives
Some higher-end SDRs skip the mixer entirely and sample RF directly. This requires very fast ADCs with adequate resolution. The Ettus USRP B210, for example, uses a 61.44 MSPS ADC and can directly sample signals up to about 30 MHz (the HF band). For higher frequencies, it still uses a mixer.
The trend in SDR hardware is toward higher direct-sampling bandwidth as ADC technology improves, but for the foreseeable future, the superheterodyne approach remains dominant above VHF frequencies.
3. Sampling and the Nyquist Theorem
Once the analog front-end delivers a conditioned signal at an intermediate frequency (or at baseband), the ADC converts it to a stream of discrete digital samples. This conversion is governed by sampling theory, and getting it wrong means the signal is irrecoverably corrupted.
The Nyquist-Shannon Sampling Theorem
The theorem states: a bandlimited signal with maximum frequency f_max can be perfectly reconstructed from its samples if the sampling rate f_s is greater than 2 × f_max:
f_s > 2 · f_maxThe quantity 2 · f_max is called the Nyquist rate. If the signal occupies a bandwidth B centred around some frequency, the sampling rate must exceed 2B.
For example: FM broadcast radio has a bandwidth of about 200 kHz (±75 kHz deviation plus stereo pilot and subcarriers). To capture one FM channel, you need a sampling rate greater than 400 kHz. The RTL-SDR typically operates at 2.4 MSPS (million samples per second), which captures about 2.4 MHz of bandwidth, enough for roughly 12 FM channels simultaneously.
Aliasing
When the sampling rate is too low, frequencies above f_s/2 (the Nyquist frequency) fold back into the sampled spectrum, overlapping with legitimate signals. This is aliasing, and it is not a subtle degradation; it produces phantom signals that are indistinguishable from real ones in the sampled data.
Consider an ADC sampling at 2.4 MSPS. The Nyquist frequency is 1.2 MHz. A signal at 1.5 MHz (which is 0.3 MHz above the Nyquist frequency) appears in the sampled data as a signal at 0.9 MHz (1.2 - 0.3 = 0.9 MHz). There is no way to tell, from the samples alone, whether there was a real signal at 0.9 MHz or an aliased signal from 1.5 MHz.
This is why the analog front-end includes an anti-aliasing filter: a low-pass filter that attenuates all frequencies above f_s/2 before the ADC. Without it, every signal above the Nyquist frequency folds into the passband and corrupts the data.
Quantization: Bits, Noise, and Dynamic Range
An ADC has a finite number of bits. Each bit approximately doubles the number of discrete voltage levels the ADC can represent. The relationship between bit depth and dynamic range is:
Dynamic range (dB) ≈ 6.02 × N + 1.76Where N is the number of bits. This gives:
| ADC Bits | Dynamic Range (dB) | Voltage Levels |
|---|---|---|
| 8 | 49.9 | 256 |
| 12 | 74.0 | 4,096 |
| 14 | 86.0 | 16,384 |
| 16 | 98.1 | 65,536 |
The RTL-SDR uses an 8-bit ADC. That 49.9 dB of dynamic range means it can only distinguish signals that differ in power by a factor of about 100,000 (in linear terms). In practice, this means a strong signal (like a nearby FM transmitter) can completely mask weak signals. A 16-bit SDR like the Ettus USRP has nearly 50 dB more dynamic range, making it far better at receiving weak signals in the presence of strong ones.
Quantization noise is the error introduced by rounding the analog voltage to the nearest discrete level. It sets a noise floor that cannot be reduced by any amount of digital processing. More bits mean a lower noise floor and a cleaner signal.
Common ADC Specifications in SDR Hardware
| SDR Platform | ADC Bits | Max Sample Rate | Effective Bandwidth |
|---|---|---|---|
| RTL-SDR | 8 | 2.4 MSPS | ~2.4 MHz |
| Airspy Mini | 12 | 6 MSPS | ~6 MHz |
| HackRF One | 8 | 20 MSPS | ~20 MHz |
| LimeSDR Mini | 12 | 30.72 MSPS | ~30 MHz |
| Ettus USRP B210 | 12 | 61.44 MSPS | ~56 MHz |
| Ettus USRP X310 | 14/16 | 200 MSPS | ~160 MHz |
The sample rate directly determines how much spectrum you can observe simultaneously. The RTL-SDR's 2.4 MHz window is narrow; the USRP X310's 160 MHz window can capture an entire broadcast band at once.
4. IQ Sampling and Complex Signals
If you sample a real-valued signal (one wire from the ADC), you get a real signal. Real signals have a symmetric spectrum: the positive and negative frequency components are mirror images. This symmetry means half your bandwidth is redundant. A 2.4 MSPS real ADC captures 2.4 MHz of bandwidth, but only 1.2 MHz is usable because the negative frequency half is just a mirror.
IQ sampling eliminates this waste and solves several other problems.
Quadrature Sampling
Instead of one ADC, IQ sampling uses two. The incoming IF signal is split and mixed with two copies of the local oscillator that are 90 degrees apart (in quadrature):
I(t) = signal(t) × cos(2πf_LO·t) (In-phase component)
Q(t) = signal(t) × sin(2πf_LO·t) (Quadrature component)After low-pass filtering to remove the sum-frequency terms, I(t) and Q(t) represent the in-phase and quadrature components of the signal at baseband (centred at 0 Hz instead of at the IF frequency).
The Complex Baseband Signal
The I and Q components form a complex signal:
s(t) = I(t) + j·Q(t)Where j is the imaginary unit. This complex representation is not an abstraction for mathematical convenience. It is a direct description of the signal's amplitude and phase at each instant:
Amplitude: A(t) = √(I(t)² + Q(t)²)
Phase: φ(t) = arctan(Q(t) / I(t)) [using atan2 for proper quadrant]With complex baseband representation, the spectrum is no longer symmetric. Positive frequencies represent signals above the LO frequency; negative frequencies represent signals below it. A 2.4 MSPS IQ sample stream captures 2.4 MHz of usable bandwidth (from -1.2 MHz to +1.2 MHz relative to the centre frequency), compared to 1.2 MHz for a real-valued signal at the same sample rate.
Why IQ Matters: The Image Frequency Problem
In a real-valued superheterodyne receiver, a signal at f_LO + f_IF and a signal at f_LO - f_IF both produce the same IF frequency after mixing. If you are tuned to 100 MHz with a 10 MHz IF, both 110 MHz and 90 MHz produce a 10 MHz IF output. The unwanted signal (at 90 MHz, in this case) is called the image frequency.
Traditional receivers solve this with an image-reject filter: a bandpass filter before the mixer that passes 110 MHz and rejects 90 MHz. This filter must be redesigned or retuned for every receive frequency, which is impractical in a wideband SDR.
IQ sampling solves the image problem inherently. Because the complex signal distinguishes positive frequencies from negative frequencies, a signal at f_LO + f_IF appears at +f_IF in the complex baseband, while a signal at f_LO - f_IF appears at -f_IF. They are separated without any additional filtering.
The IQ Constellation Diagram
For digitally modulated signals (PSK, QAM), the I and Q values at each symbol instant form a point in the complex plane. The constellation diagram plots these points:
- BPSK: Two points on the real axis: (+1, 0) and (-1, 0). One bit per symbol.
- QPSK: Four points at 45-degree offsets: (±1, ±1)/√2. Two bits per symbol.
- 16-QAM: 16 points in a 4×4 grid. Four bits per symbol.
- 64-QAM: 64 points in an 8×8 grid. Six bits per symbol.
The constellation is a direct visualization of the signal's quality. With a clean signal, the points cluster tightly at their ideal positions. Noise causes the points to spread into clouds around the ideal locations. When the clouds overlap, the receiver makes bit errors.
Error Vector Magnitude (EVM) quantifies this:
EVM = √(mean(|received - ideal|²)) / √(mean(|ideal|²)) × 100%A typical requirement for 64-QAM demodulation is EVM below 8%. For 256-QAM, it tightens to about 3.5%.
5. The FFT: Looking at the Spectrum
A spectrum analyser shows signal power as a function of frequency. In an SDR, this is computed from the time-domain IQ samples using the Fast Fourier Transform.
From DFT to FFT
The Discrete Fourier Transform converts N time-domain samples into N frequency-domain bins:
X[k] = Σ_{n=0}^{N-1} x[n] · e^{-j2πkn/N} for k = 0, 1, ..., N-1Computing this directly requires O(N²) complex multiplications. The FFT (Cooley-Tukey algorithm, 1965) reduces this to O(N log N) by exploiting symmetry and periodicity in the DFT matrix. For N = 1024, this is a speedup factor of about 100. For N = 1,048,576 (2²⁰), the speedup is about 52,000.
Frequency Resolution and FFT Size
Each FFT bin represents a frequency range of:
Δf = f_s / NWhere f_s is the sample rate and N is the FFT size. For an RTL-SDR at 2.4 MSPS:
| FFT Size (N) | Frequency Resolution (Δf) | Time Window |
|---|---|---|
| 256 | 9,375 Hz | 106.7 μs |
| 1,024 | 2,344 Hz | 426.7 μs |
| 4,096 | 585.9 Hz | 1.707 ms |
| 16,384 | 146.5 Hz | 6.827 ms |
| 65,536 | 36.6 Hz | 27.31 ms |
There is a fundamental tradeoff: better frequency resolution requires a longer time window (more samples), which means less time resolution. You cannot have both simultaneously. This is a consequence of the uncertainty principle (not just a quantum mechanics concept; it applies to all wave phenomena).
Windowing and Spectral Leakage
The FFT assumes the input signal is periodic with period N. If the signal frequency does not align exactly with an FFT bin centre, the energy "leaks" into adjacent bins, producing broad skirts instead of a sharp peak. This is spectral leakage.
Window functions taper the signal to zero at the edges of the FFT block, reducing leakage at the cost of slightly wider main lobes (worse frequency resolution). Common choices:
| Window | Main Lobe Width | Sidelobe Level | Use Case |
|---|---|---|---|
| Rectangular (none) | Narrowest | -13 dB | When signals align with bins |
| Hann | 1.5× wider | -31 dB | General purpose spectrum display |
| Hamming | 1.4× wider | -42 dB | Signal measurement |
| Blackman | 1.7× wider | -58 dB | Detecting weak signals near strong ones |
| Flat-top | 3.8× wider | -44 dB | Accurate amplitude measurement |
Python FFT Example
Here is a complete example that captures samples from an RTL-SDR (using the pyrtlsdr library), computes the FFT, and plots the power spectrum:
import numpy as np
import matplotlib.pyplot as plt
# Simulating IQ samples (replace with rtlsdr.read_samples() for real hardware)
# Parameters
fs = 2.4e6 # Sample rate: 2.4 MSPS
fc = 98.0e6 # Centre frequency: 98 MHz (FM broadcast)
N = 4096 # FFT size
duration = N / fs # Capture duration
# Generate time axis
t = np.arange(N) / fs
# Simulated signal: FM station at fc + 200kHz and fc - 500kHz
# In baseband, these appear at +200kHz and -500kHz
signal = (0.8 * np.exp(1j * 2 * np.pi * 200e3 * t) +
0.3 * np.exp(1j * 2 * np.pi * (-500e3) * t))
# Add complex Gaussian noise
noise = 0.05 * (np.random.randn(N) + 1j * np.random.randn(N))
samples = signal + noise
# Apply Hamming window
window = np.hamming(N)
windowed = samples * window
# Compute FFT
spectrum = np.fft.fftshift(np.fft.fft(windowed))
# Convert to power spectral density in dB
power_db = 20 * np.log10(np.abs(spectrum) / N + 1e-12)
# Frequency axis
freqs = np.fft.fftshift(np.fft.fftfreq(N, d=1/fs))
freqs_mhz = (freqs + fc) / 1e6
# Plot
plt.figure(figsize=(12, 5))
plt.plot(freqs_mhz, power_db, linewidth=0.5)
plt.xlabel('Frequency (MHz)')
plt.ylabel('Power (dB)')
plt.title(f'Power Spectrum, FFT size={N}, Hamming window')
plt.grid(True, alpha=0.3)
plt.xlim([freqs_mhz[0], freqs_mhz[-1]])
plt.tight_layout()
plt.show()This code demonstrates the complete FFT pipeline: window the samples, compute the FFT, shift zero-frequency to centre, convert to dB, and plot with a proper frequency axis. The fftshift call rearranges the output so that negative frequencies appear on the left and positive on the right, matching the conventional spectrum display.
Waterfall Displays and Spectrograms
A single FFT gives one snapshot of the spectrum. A waterfall display (spectrogram) stacks successive FFTs vertically, with time on one axis, frequency on the other, and colour representing power. This reveals signals that appear and disappear over time, like pulsed transmissions, frequency-hopping radios, or intermittent interference.
The spectrogram is computed by splitting the sample stream into overlapping blocks, windowing each block, computing the FFT, and arranging the results into a 2D matrix. The overlap (typically 50%) prevents signals at block boundaries from being attenuated by the window function.
6. Digital Filtering
Once the signal is in the digital domain, software filters replace the analog LC and crystal filters of traditional receivers. Digital filters are the workhorse of SDR signal processing; they select channels, reject interference, and shape signals for demodulation.
FIR Filters
A Finite Impulse Response filter computes each output sample as a weighted sum of the current and past input samples:
y[n] = Σ_{k=0}^{M-1} h[k] · x[n-k]Where h[k] are the filter coefficients (taps) and M is the filter length. The frequency response is entirely determined by the tap values. FIR filters are always stable (they cannot oscillate) and can have exactly linear phase (constant group delay across all frequencies), which is critical for preserving signal integrity in communications.
The number of taps determines the filter's sharpness. A narrow transition bandwidth (the gap between the passband and the stopband) requires more taps. A rough guideline:
M ≈ 4 / (Δf / f_s)Where Δf is the transition bandwidth and f_s is the sample rate. For a low-pass filter with a 10 kHz transition bandwidth at 2.4 MSPS, you need roughly 960 taps. That is 960 multiply-accumulate operations per output sample.
IIR Filters
An Infinite Impulse Response filter uses feedback (past output samples) in addition to past input samples:
y[n] = Σ_{k=0}^{M} b[k] · x[n-k] - Σ_{k=1}^{N} a[k] · y[n-k]IIR filters achieve sharper frequency responses with fewer coefficients than FIR filters, but they have nonlinear phase and can be unstable if designed carelessly. In SDR, FIR filters are preferred for most channelization and signal conditioning tasks, while IIR filters appear in specific applications like audio processing after demodulation.
Decimation: Reducing the Sample Rate
After filtering to select a narrow channel, the sample rate is usually much higher than needed. An FM channel is 200 kHz wide, but the SDR is sampling at 2.4 MSPS. After applying a low-pass filter that passes only the 200 kHz channel, you can safely reduce the sample rate by keeping only every Mth sample (decimation by factor M):
Decimation factor M = f_s / f_desired = 2,400,000 / 240,000 = 10After decimation by 10, the sample rate drops to 240 KSPS, and all subsequent processing (demodulation, audio output) operates on 10x fewer samples. This is critical for computational efficiency.
Decimation without prior filtering causes aliasing (the same problem as inadequate ADC sample rate, but now in the digital domain). The low-pass filter before decimation is called the decimation filter or anti-aliasing filter.
Python FIR Filter Example
import numpy as np
from scipy.signal import firwin, lfilter
# Design a low-pass FIR filter for FM channel selection
fs = 2.4e6 # Input sample rate
cutoff = 100e3 # Cutoff frequency (half of 200 kHz channel)
transition = 20e3 # Transition bandwidth
num_taps = 101 # Filter length
# Design filter using windowed sinc method
taps = firwin(num_taps, cutoff, fs=fs, window='hamming')
# Apply to IQ samples
# 'samples' is a complex numpy array from the SDR
filtered = lfilter(taps, 1.0, samples)
# Decimate by factor of 10
decimation_factor = 10
decimated = filtered[::decimation_factor]
new_fs = fs / decimation_factor # 240 kHz
print(f"Filter: {num_taps} taps, cutoff={cutoff/1e3:.0f} kHz")
print(f"Decimated: {fs/1e6:.1f} MSPS -> {new_fs/1e3:.0f} KSPS")For production SDR applications, polyphase filter banks are used instead of the naive filter-then-decimate approach. A polyphase decimator combines the filter and decimation into a single operation, computing only the output samples that survive decimation. This reduces computation by a factor of M.
Filter Design in Practice
Choosing filter parameters is an engineering decision with real consequences:
- Too few taps: Poor stopband attenuation, adjacent channel signals leak through
- Too many taps: Wastes CPU, adds latency (group delay = (M-1)/(2·f_s) seconds)
- Cutoff too narrow: Clips the edges of the desired signal, causing distortion
- Cutoff too wide: Lets in noise and interference from adjacent channels
For FM broadcast reception with an RTL-SDR, a reasonable starting point is: 101 taps, 120 kHz cutoff, Hamming window, decimate by 10 from 2.4 MSPS to 240 KSPS.
7. Demodulation in Software
Demodulation is where the filtered, decimated IQ samples become intelligible information: audio, data packets, images. Each modulation scheme requires a different demodulation algorithm, and this is precisely where SDR's flexibility shines. A hardware FM receiver can only demodulate FM. An SDR running the appropriate software demodulates anything.
AM Demodulation
Amplitude Modulation encodes information in the signal's envelope (amplitude). For an IQ signal, AM demodulation is trivially simple:
audio(t) = |s(t)| = √(I(t)² + Q(t)²)Take the magnitude of the complex signal at each sample. That is the entire demodulator. In practice, you also remove the DC offset (carrier component) and apply a low-pass filter to limit the audio bandwidth:
import numpy as np
def demod_am(iq_samples):
"""Demodulate AM from complex IQ samples."""
# Envelope detection: magnitude of complex signal
envelope = np.abs(iq_samples)
# Remove DC (carrier)
audio = envelope - np.mean(envelope)
# Normalize
audio = audio / np.max(np.abs(audio))
return audioFM Demodulation
Frequency Modulation encodes information in the signal's instantaneous frequency. The instantaneous frequency is the time derivative of the phase:
f_inst(t) = (1 / 2π) · dφ(t)/dtIn discrete time, with IQ samples:
φ[n] = arctan(Q[n] / I[n]) (using atan2)
audio[n] = φ[n] - φ[n-1] (phase difference)But computing arctan and then differencing has numerical issues (phase wrapping at ±π). A more robust approach computes the phase difference directly using complex conjugate multiplication:
audio[n] = arg(s[n] · conj(s[n-1]))Where arg() returns the angle of a complex number and conj() is the complex conjugate. The product s[n] · conj(s[n-1]) has an angle equal to the phase difference, without any wrapping problems.
The complete derivation: if s[n] = A·e^{jφ[n]}, then:
s[n] · conj(s[n-1]) = A² · e^{j(φ[n] - φ[n-1])}
arg(s[n] · conj(s[n-1])) = φ[n] - φ[n-1] = 2π · f_inst · T_sWhere T_s = 1/f_s is the sample period. The result is proportional to the instantaneous frequency deviation, which is the audio signal.
Here is a complete FM demodulator in Python:
import numpy as np
from scipy.signal import firwin, lfilter, resample_poly
def demod_fm(iq_samples, fs_in, fs_audio=48000, channel_bw=200e3):
"""
Demodulate wideband FM from complex IQ samples.
Parameters:
iq_samples: complex numpy array of IQ data
fs_in: input sample rate (Hz)
fs_audio: desired audio output rate (Hz)
channel_bw: FM channel bandwidth (Hz)
"""
# Step 1: Low-pass filter to select FM channel
num_taps = 101
lpf_taps = firwin(num_taps, channel_bw / 2, fs=fs_in, window='hamming')
filtered = lfilter(lpf_taps, 1.0, iq_samples)
# Step 2: Decimate to ~240 kHz
dec_factor = int(fs_in / (channel_bw * 1.2))
decimated = filtered[::dec_factor]
fs_dec = fs_in / dec_factor
# Step 3: FM demodulation via conjugate product
# Compute s[n] * conj(s[n-1])
product = decimated[1:] * np.conj(decimated[:-1])
phase_diff = np.angle(product)
# Step 4: De-emphasis filter (50 μs time constant for Europe)
# In Europe, FM broadcast uses 50 μs pre-emphasis
tau = 50e-6 # 50 microseconds
alpha = 1 - np.exp(-1 / (fs_dec * tau))
audio = np.zeros(len(phase_diff))
audio[0] = phase_diff[0]
for i in range(1, len(phase_diff)):
audio[i] = audio[i-1] + alpha * (phase_diff[i] - audio[i-1])
# Step 5: Resample to audio rate
up = int(fs_audio)
down = int(fs_dec)
from math import gcd
g = gcd(up, down)
audio_out = resample_poly(audio, up // g, down // g)
# Normalize
audio_out = audio_out / np.max(np.abs(audio_out))
return audio_outNote the de-emphasis filter in step 4. FM broadcast uses pre-emphasis (boosting high frequencies before transmission) and de-emphasis (attenuating them at the receiver) to improve the signal-to-noise ratio at higher audio frequencies. The time constant differs by region: 50 microseconds in Europe and most of the world, 75 microseconds in the Americas and South Korea.
Digital Modulation: PSK and QAM
For digital signals (PSK, QAM), demodulation is more complex because you need to recover the symbol timing and carrier phase in addition to making symbol decisions.
Symbol timing recovery determines exactly when to sample the IQ stream to get one sample per symbol. The Mueller-Muller algorithm and the Gardner algorithm are common approaches. They work by examining the samples around the expected symbol instant and adjusting a numerically-controlled oscillator (NCO) to track the symbol clock.
Carrier recovery removes any residual frequency and phase offset between the transmitter and receiver oscillators. For QPSK, the Costas loop is a standard approach. It generates an error signal from the received constellation and uses a PLL (Phase-Locked Loop) to track the carrier.
Decision: once timing and carrier are recovered, each symbol instant yields an (I, Q) point. The demodulator maps it to the nearest valid constellation point. For QPSK, this is simple quadrant detection. For 64-QAM, it requires comparing against a grid of 64 ideal points.
The bit error rate (BER) depends on the distance between constellation points relative to the noise:
BER_QPSK ≈ erfc(√(E_b/N_0)) / 2Where E_b/N_0 is the energy per bit to noise density ratio and erfc is the complementary error function.
8. The RTL-SDR: A 25 Euro Radio Lab
In 2012, Antti Palosaari and Eric Fry discovered that the Realtek RTL2832U, a cheap DVB-T (digital television) USB dongle chipset, could be put into a raw sample mode that streamed IQ data to the host computer. This turned a mass-produced €8 TV tuner into a general-purpose SDR receiver. The SDR community has never been the same.
Architecture
The RTL-SDR consists of two main ICs:
- R820T2 Tuner: Contains the LNA, mixer, and programmable LO. Covers 24 MHz to 1,766 MHz (with gaps). Outputs an IF signal.
- RTL2832U: Contains the ADC (8-bit, up to 2.4 MSPS) and USB interface. Streams raw IQ samples to the host.
The signal path is: antenna, R820T2 (tune + downconvert), RTL2832U (digitize + USB transfer), host computer (all DSP in software).
Specifications and Limitations
| Parameter | RTL-SDR (v3/v4) |
|---|---|
| Frequency range | 24 MHz to 1,766 MHz |
| Sample rate | Up to 2.4 MSPS (3.2 MSPS unstable) |
| ADC resolution | 8-bit |
| Dynamic range | ~50 dB |
| Frequency accuracy | ±1 ppm (TCXO on v3/v4) |
| Interface | USB 2.0 |
| Price | €25 to €35 |
| Transmit capability | None |
The limitations are significant but well-understood:
8-bit ADC: The 50 dB dynamic range means strong signals can desensitize the receiver. A cell tower a few hundred metres away in Berlin can obliterate weak signals. External filtering and careful gain control are essential.
Frequency stability: Early RTL-SDR dongles used cheap 28.8 MHz crystals with ±30 ppm drift, translating to ±30 kHz error at 1 GHz. The v3 and v4 versions include a TCXO (Temperature Compensated Crystal Oscillator) with ±1 ppm stability, adequate for most applications.
No transmit: The RTL-SDR is receive-only. You cannot use it to transmit signals.
USB 2.0 throughput: At 2.4 MSPS with 8-bit I + 8-bit Q, the data rate is 4.8 MB/s. USB 2.0 can handle this, but barely. At 3.2 MSPS, dropped samples become frequent.
What You Can Receive
The RTL-SDR's frequency range of 24 to 1,766 MHz covers a remarkable range of signals:
| Signal | Frequency | Modulation | Notes |
|---|---|---|---|
| FM broadcast | 87.5 to 108 MHz | Wideband FM | Stereo decode possible |
| DAB+ digital radio | 174 to 240 MHz | OFDM/COFDM | European digital broadcast standard |
| Aircraft ADS-B | 1090 MHz | PPM | Position, altitude, speed of aircraft |
| Aircraft ACARS | 131.550 MHz | AM/MSK | Text messages between aircraft and ground |
| Marine AIS | 161.975/162.025 MHz | GMSK | Ship positions and identification |
| NOAA weather sat | 137.1/137.9125 MHz | APT (AM/FM) | Direct weather satellite imagery |
| Meteor-M satellite | 137.1/137.9 MHz | QPSK (LRPT) | Higher-resolution weather images |
| ISM band (Europe) | 433.05 to 434.79 MHz | Various | Temperature sensors, doorbells, etc. |
| ISM band (global) | 868 MHz (EU) / 915 MHz (US) | Various | LoRa, Zigbee, smart meters |
| Amateur 2m band | 144 to 146 MHz | FM/SSB | Ham radio communications |
| Amateur 70cm band | 430 to 440 MHz | FM/SSB/digital | Ham radio, APRS, packet radio |
| Pager (POCSAG) | 148 to 172 MHz | FSK | Still operational in some areas |
| Galileo E1 signal | 1575.42 MHz | BOC(1,1) | At the edge of RTL-SDR range, marginal |
| GSM downlink | 925 to 960 MHz | GMSK | Observe channel structure (not decrypt) |
For European users specifically, DAB+ reception is a practical application. DAB+ uses OFDM in the VHF Band III (174 to 240 MHz) with 1.536 MHz channel bandwidth, fitting neatly within the RTL-SDR's 2.4 MHz capture window. Open-source DAB+ decoders like welle.io and dablin work directly with RTL-SDR input.
Weather satellite reception is a popular entry point. NOAA 15, 18, and 19 orbit at about 850 km altitude, passing over Europe several times daily. Their APT (Automatic Picture Transmission) signal at 137 MHz is strong enough to receive with the stock RTL-SDR antenna, though a purpose-built V-dipole or QFH antenna dramatically improves image quality. The Russian Meteor-M2 satellites transmit higher-resolution LRPT images on nearby frequencies.
9. GNU Radio: The SDR Toolkit
GNU Radio is an open-source signal processing framework that provides the software infrastructure for SDR. It is written in C++ for performance, with a Python API for building and controlling signal processing flowgraphs.
Flowgraph Architecture
A GNU Radio flowgraph is a directed graph of signal processing blocks connected by streams of samples:
- Source blocks produce samples: from an SDR device (RTL-SDR, USRP), from a file, or generated synthetically.
- Processing blocks transform samples: filters, resamplers, demodulators, decoders.
- Sink blocks consume samples: audio output, file writers, GUI displays, network streams.
Each block declares its input and output types (complex float, real float, bytes, etc.) and its sample rate relationship to its inputs. GNU Radio's scheduler manages buffer allocation and threading automatically.
Building an FM Receiver
Here is a GNU Radio flowgraph (expressed in Python) that receives FM broadcast radio using an RTL-SDR:
#!/usr/bin/env python3
"""
FM broadcast receiver using GNU Radio and RTL-SDR.
Receives European FM at a specified frequency with 50 μs de-emphasis.
"""
from gnuradio import gr, blocks, analog, filter as gr_filter, audio
from gnuradio.filter import firdes
import osmosdr
class fm_receiver(gr.top_block):
def __init__(self, freq=98.0e6):
gr.top_block.__init__(self, "FM Receiver")
# Parameters
samp_rate = 2.4e6 # RTL-SDR sample rate
audio_rate = 48000 # Audio output rate
fm_channel_bw = 200e3 # FM channel bandwidth
demod_rate = 240e3 # Rate after initial decimation
decimation = int(samp_rate / demod_rate) # = 10
# Source: RTL-SDR via osmosdr
self.rtlsdr = osmosdr.source(args="numchan=1")
self.rtlsdr.set_sample_rate(samp_rate)
self.rtlsdr.set_center_freq(freq)
self.rtlsdr.set_gain(40) # RF gain in dB
self.rtlsdr.set_freq_corr(0) # PPM correction
# Low-pass filter: select 200 kHz FM channel, decimate to 240 kHz
lpf_taps = firdes.low_pass(
gain=1.0,
sampling_freq=samp_rate,
cutoff_freq=100e3,
transition_width=20e3,
window=firdes.WIN_HAMMING
)
self.lpf = gr_filter.fir_filter_ccf(decimation, lpf_taps)
# FM demodulator
fm_deviation = 75e3 # ±75 kHz for wideband FM
self.fm_demod = analog.wfm_rcv(
quad_rate=demod_rate,
audio_decimation=int(demod_rate / audio_rate)
)
# De-emphasis: 50 μs for Europe
self.deemph = analog.fm_deemph(
fs=audio_rate,
tau=50e-6
)
# Audio sink
self.audio_sink = audio.sink(audio_rate)
# Connect the flowgraph
self.connect(self.rtlsdr, self.lpf, self.fm_demod,
self.deemph, self.audio_sink)
if __name__ == '__main__':
# Tune to a local FM station in Barcelona
receiver = fm_receiver(freq=98.0e6)
print("Receiving FM at 98.0 MHz. Press Ctrl+C to stop.")
receiver.run()Sample Rate Propagation
One of the subtleties of GNU Radio is that blocks must agree on sample rates at their connections. The scheduler does not enforce this; it simply passes buffers. If you connect a source running at 2.4 MSPS directly to an audio sink expecting 48 kHz, you will hear garbage (or nothing) with no error message.
The programmer is responsible for ensuring consistent rates:
RTL-SDR source: 2,400,000 sps (complex)
→ Low-pass filter (decimate by 10): 240,000 sps (complex)
→ FM demodulator (decimate by 5): 48,000 sps (real)
→ De-emphasis: 48,000 sps (real)
→ Audio sink: 48,000 sps (real)Each decimation stage must match the ratio between input and output rates. Getting this wrong produces audio at the wrong pitch, buffer overflows, or corrupted output.
GNU Radio Companion
GNU Radio Companion (GRC) is a graphical interface that lets you build flowgraphs by dragging and connecting blocks. It generates Python code. For learning and prototyping, GRC is invaluable because you can see the signal at any point in the chain using GUI sink blocks (time plot, frequency plot, constellation plot, waterfall).
GRC is not a toy. Many production SDR applications started as GRC flowgraphs before being optimised into pure C++ or embedded code.
Beyond FM: Other GNU Radio Applications
The GNU Radio ecosystem includes out-of-tree modules for:
- gr-satellites: Decoding amateur and scientific satellite telemetry
- gr-gsm: GSM channel analysis (passive, receive-only)
- gr-adsb: ADS-B aircraft transponder decoding
- gr-lora: LoRa modulation and demodulation
- gr-dab: DAB/DAB+ digital broadcast radio reception
- gr-rds: FM Radio Data System decoding (station names, traffic info)
Each module implements the specific DSP chain for its protocol, built on top of GNU Radio's core infrastructure.
10. Advanced SDR: From Hobby to Professional
The RTL-SDR is a remarkable entry point, but the SDR landscape extends far beyond €25 USB dongles. As requirements increase (wider bandwidth, higher dynamic range, transmit capability, frequency accuracy), the hardware scales accordingly.
SDR Hardware Comparison
| Platform | ADC Bits | Sample Rate | Frequency Range | Duplex | TX | Price (approx.) |
|---|---|---|---|---|---|---|
| RTL-SDR v4 | 8 | 2.4 MSPS | 24 MHz to 1.77 GHz | RX only | No | €30 |
| Airspy R2 | 12 | 10 MSPS | 24 MHz to 1.8 GHz | RX only | No | €200 |
| HackRF One | 8 | 20 MSPS | 1 MHz to 6 GHz | Half duplex | Yes | €300 |
| LimeSDR Mini 2 | 12 | 30.72 MSPS | 10 MHz to 3.5 GHz | Full duplex | Yes | €250 |
| ADALM-Pluto | 12 | 61.44 MSPS | 325 MHz to 3.8 GHz | Full duplex | Yes | €180 |
| Ettus USRP B210 | 12 | 61.44 MSPS | 70 MHz to 6 GHz | Full duplex | Yes | €2,000 |
| Ettus USRP X310 | 14 | 200 MSPS | DC to 6 GHz | Full duplex | Yes | €6,000+ |
The jump from RTL-SDR to HackRF One or LimeSDR adds transmit capability, which opens up experimentation with building your own radio protocols, testing receivers, generating test signals, and (with appropriate licences) amateur radio transmission.
The jump to Ettus USRP-class hardware adds research-grade performance: higher dynamic range, better frequency stability (10 MHz reference input, GPS-disciplined oscillator support), wider instantaneous bandwidth, and FPGA fabric for custom real-time processing.
Transmit Considerations
Transmitting radio signals is regulated everywhere. In Europe, you need an amateur radio licence (CEPT harmonised) to transmit on amateur bands, and a specific allocation or experimental licence for other frequencies. Transmitting on frequencies you are not authorised to use is illegal and can cause real harm: interfering with aviation, emergency services, or cellular infrastructure.
The HackRF One and LimeSDR output power is low (a few milliwatts to tens of milliwatts), but even small amounts of power on the wrong frequency can cause problems, particularly if harmonics fall on sensitive frequencies.
Professional and Research Applications
Spectrum monitoring: Regulatory agencies like EETT (Greece), BNetzA (Germany), and Agentschap Telecom (Netherlands) use SDR-based systems to monitor spectrum usage, detect illegal transmitters, and enforce licensing compliance. Wideband SDR receivers with high dynamic range continuously scan bands and flag anomalies.
Cellular network testing: SDR platforms can implement base station emulators (for testing handset firmware) or passive monitors (for analysing network coverage and performance). Open-source projects like srsRAN implement full 4G and 5G base stations on SDR hardware, enabling researchers to build private cellular networks for testing.
Satellite ground stations: The SatNOGS project (started in Athens and now a global network) uses SDR-based ground stations to receive telemetry from amateur and scientific satellites. Each ground station is typically a Raspberry Pi with an RTL-SDR or Airspy, a rotatable antenna, and GNU Radio-based decoding software. The network has hundreds of stations across Europe and worldwide.
Radio astronomy: The European LOFAR (Low-Frequency Array) uses thousands of simple antennas across the Netherlands, Germany, France, and other countries, connected to digital receivers that are, conceptually, massive SDR systems. Each antenna's signal is digitized and the array is "steered" in software by computing phase delays, the same IQ processing concepts scaled to continental baselines.
SIGINT and electronic warfare: Military and intelligence applications are the historical origin of SDR. The ability to rapidly adapt to new signal types, scan wide bandwidths, and implement arbitrary demodulators in software is a strategic advantage. The U.S. military's JTRS (Joint Tactical Radio System) and its successors are SDR platforms, and European equivalents exist under programmes like ESSOR.
Direction Finding and Geolocation
With multiple synchronised SDR receivers, you can determine the direction or location of a transmitter. Techniques include:
Time Difference of Arrival (TDOA): Multiple receivers record timestamps when they detect the same signal. The time differences constrain the transmitter's position to hyperbolic curves; three or more receivers fix the position.
Phase interferometry: Two or more antennas connected to coherent receivers measure the phase difference of the incoming signal. The phase difference depends on the angle of arrival and the antenna spacing:
φ = (2π · d · sin(θ)) / λWhere d is the antenna spacing, θ is the angle of arrival, and λ is the wavelength. With a known d and measured φ, you can solve for θ.
The KerberosSDR project uses four coherent RTL-SDR receivers for direction finding, demonstrating that even hobbyist hardware can perform radio direction finding when properly synchronised.
The Future of SDR
Several trends are reshaping the SDR landscape:
5G NR testbeds: SDR platforms are used to prototype and test 5G New Radio waveforms before committing to silicon. The flexibility to experiment with different numerologies, beamforming algorithms, and scheduling strategies is invaluable. Projects like srsRAN and OpenAirInterface run on Ettus USRPs and similar hardware.
Cognitive radio: Radios that sense the spectrum and dynamically select unused frequencies to transmit on. SDR is the enabling technology because the radio must be able to operate on arbitrary frequencies with arbitrary modulation, which is exactly what SDR provides. European research under the HORIZON programme has funded significant cognitive radio development.
Wideband direct-sampling: ADC technology continues to improve. Modern RF ADCs (like the Analog Devices AD9213) can directly sample signals up to 10 GHz at 10.25 GSPS with 12-bit resolution. As these devices become cheaper, the mixer stage becomes unnecessary for an ever-wider range of frequencies, and the "digitize everything" ideal of SDR moves closer to reality.
GPU acceleration: Signal processing workloads (FFTs, filtering, correlation) map well onto GPU architectures. NVIDIA's cuSignal library and GPU-accelerated GNU Radio blocks can process sample rates that would overwhelm a CPU alone. This enables real-time processing of wideband signals that previously required dedicated FPGA implementations.
Machine learning for signal classification: Identifying modulation types, detecting anomalies, and classifying signals in congested spectrum are increasingly being handled by neural networks trained on SDR-captured datasets. The AMC (Automatic Modulation Classification) problem, given a snippet of IQ samples, determine the modulation scheme, is a well-studied ML application in the SDR domain.
Closing Notes
SDR is one of those technologies where the gap between understanding the theory and doing something practical is remarkably small. A €25 RTL-SDR dongle, a computer, and free software (GNU Radio, SDR++, GQRX) are enough to receive FM radio, decode aircraft positions, capture weather satellite images, and analyse the radio environment around your apartment in Amsterdam or your office in Barcelona.
The signal processing chain is the same regardless of what you are receiving: antenna captures RF, the front-end conditions and downconverts it, the ADC digitizes it, and software does everything else. The "everything else" is where all the interesting work happens: FFTs to see the spectrum, filters to select channels, demodulators to extract information, decoders to turn bits into meaning.
The mathematics covered here (Nyquist sampling, complex IQ representation, Fourier transforms, filter design, demodulation algorithms) are not specific to SDR. They are the foundation of all digital communications and signal processing. Understanding them through hands-on SDR experimentation, where you can see the spectrum, inspect the IQ data, and hear the result of your processing chain, is one of the most effective ways to build real intuition for how radio works.
If you have never plugged in an RTL-SDR and tuned to your local FM station, start there. Then try ADS-B. Then try decoding a NOAA satellite pass. Each step adds a new layer of understanding, and the hardware investment is less than a meal at a decent taverna in Plaka.