— Understanding Digital Signal Processing">
Classroom Glossary Public page

WIR-101 Week 8 — SDR Fundamentals: IQ Sampling and Signal Anatomy

994 words

"All of digital signal processing rests on one idea: if you sample a signal at twice its bandwidth, you can reconstruct it perfectly." — Understanding Digital Signal Processing, Richard Lyons (3rd ed., Ch. 2)


Lecture (50 min)

8.1 What a Software-Defined Radio Actually Does

A traditional radio is hardwired: fixed IF filters, analog demodulators, dedicated DSP chips for each mode. You cannot reprogram a 1995 FM car radio to receive ADS-B aircraft transponders.

An SDR replaces everything downstream of the antenna with software:

  1. Antenna: captures electromagnetic energy
  2. RF front-end: filters and amplifies; center frequency is tunable
  3. Mixer: downconverts the desired signal to baseband (shifts the carrier to 0 Hz)
  4. ADC (Analog-to-Digital Converter): samples the baseband at the configured sample rate
  5. Computer: performs all filtering, demodulation, decoding in software

The ADC output is a stream of IQ samples (in-phase and quadrature). From that stream, software can demodulate AM, FM, SSB, BPSK, QPSK, LoRa, ADS-B, or any other modulation by changing the algorithm -- no hardware change.

8.2 IQ Sampling: The Math You Need

A real bandpass signal at frequency f_c can be represented as:

x(t) = I(t) * cos(2π f_c t) - Q(t) * sin(2π f_c t)

Where I(t) is the in-phase component and Q(t) is the quadrature (90° offset) component. Together, I + jQ form a complex baseband representation. The SDR's hardware performs the downconversion; the ADC samples I and Q simultaneously.

In Python, IQ samples are stored as complex64 (two 32-bit floats per sample):

import numpy as np

samples = np.fromfile("capture.iq", dtype=np.complex64)
# samples[n] = I[n] + j*Q[n]

The instantaneous frequency is the derivative of the phase:

phase = np.angle(samples)
freq = np.diff(np.unwrap(phase)) / (2 * np.pi) * sample_rate

The instantaneous amplitude (power envelope) is:

amplitude = np.abs(samples)
power_dB = 20 * np.log10(amplitude)

8.3 The Nyquist-Shannon Sampling Theorem

You can reconstruct a signal exactly if and only if the sample rate >= 2 * signal_bandwidth.

For an RTL-SDR at 2 MHz sample rate, the observable bandwidth is 2 MHz. Signals more than 1 MHz from the center frequency are outside the observable band.

Consequence for SDR: to monitor 2.4 GHz Wi-Fi (22 MHz per channel), you need a sample rate >= 44 MHz. The RTL-SDR max is 2.4 MHz -- it can observe a fraction of one channel. The HackRF One supports up to 20 MHz sample rate (17 MHz reliable), enough for one 802.11 channel. The ANTSDR E200 with a 61.44 MHz sample rate can cover a full 802.11 channel.

8.4 The Fourier Transform: From Time to Frequency

The FFT (Fast Fourier Transform) converts a time-domain IQ buffer to a frequency-domain spectrum:

fft_size = 1024
spectrum = np.fft.fftshift(np.fft.fft(samples[:fft_size], fft_size))
frequencies = np.fft.fftshift(np.fft.fftfreq(fft_size, d=1/sample_rate)) + center_freq
power = 20 * np.log10(np.abs(spectrum))

The waterfall display (time on y-axis, frequency on x-axis, power as color) is a running sequence of FFT frames. Every SDR GUI (GQRX, SDR++, Inspectrum) displays this.

8.5 Sub-GHz Modulation Zoo

Modulation Description Common Usage
OOK/ASK On-off amplitude keying Garage doors (315/433 MHz), RC remotes, temperature sensors
FSK Two or more frequency states POCSAG pagers, AX.25 packet radio, some IoT sensors
GFSK Gaussian-filtered FSK Bluetooth Classic and BLE, Zigbee, some LoRa variations
LoRa CSS Chirp spread spectrum (swept frequency) LoRaWAN IoT networks at 433/868/915 MHz
BPSK/QPSK Phase shift keying GPS L1 C/A (BPSK), ACARS aircraft, GPS L2C (QPSK)

Identifying modulation from an IQ capture:

  • ASK: amplitude varies; IQ constellation looks like one cluster near origin and one cluster at a fixed distance
  • FSK: frequency varies; two distinct "blobs" in the frequency domain; constellation is a figure-8 in the IQ plot
  • BPSK: two phase states; constellation shows two clusters at opposite sides of the origin
  • QPSK: four phase states; constellation shows four clusters at 90° intervals

8.6 GNU Radio: The SDR Processing Framework

GNU Radio is a signal processing toolkit + graphical environment. The core abstraction is a flowgraph: a directed acyclic graph of processing blocks.

Basic SDR receive chain:

[osmocom Source] → [Low Pass Filter] → [WBFM Receive] → [Audio Sink]

Each block has:

  • Input ports: typed (complex, float, byte, int)
  • Output ports: typed
  • Parameters: sample rate, cutoff frequency, gain, etc.

GNU Radio Companion (GRC) provides a drag-and-drop GUI to build flowgraphs and generates the Python code. The generated .py file can be run headlessly for batch processing.

For the virtual path, use File Source in place of osmocom Source to process pre-captured IQ files.


Lab Preview

Lab 8 is the sub-GHz spectrum survey. You will use the RTL-SDR to scan the 315/433 MHz band, identify at least two distinct signal types from their modulation characteristics, and record a 10-second IQ capture of one signal. In Python, plot the FFT spectrum and the IQ constellation.


Homework

Reading (45 min): PySDR (Lichtman) Chapters 1-2: "Introduction to SDR" and "Sampling Theory." These cover Nyquist, complex baseband, and the IQ sampling model at tutorial depth with Python examples. Work through the examples.

Hands-on (60 min): In Python (no hardware needed), generate a synthetic BPSK signal:

import numpy as np
sample_rate = 1e6
bits = np.random.randint(0, 2, 1000)
symbols = 2*bits - 1  # BPSK: 0 -> -1, 1 -> +1
samples_per_symbol = 8
tx = np.repeat(symbols, samples_per_symbol).astype(np.complex64)
tx += 0.1 * (np.random.randn(len(tx)) + 1j*np.random.randn(len(tx)))

Plot the IQ constellation (plt.scatter(tx.real, tx.imag)). Add noise at several levels and observe how the constellation degrades. This is the concept behind SNR and BER.


Toolchain Diary Entry

First-introduce this week: GNU Radio Companion (GRC), Inspectrum, SDR++, PySDR workflow

gnuradio-companion: GUI flowgraph editor. Generates Python; can be run headlessly with python3 generated_flowgraph.py.

inspectrum capture.iq: interactive IQ file viewer. Plot amplitude over time, frequency waterfall, phase. Useful for identifying and measuring signal parameters before decoding.

sdr++ : modern cross-platform SDR++ frontend. Faster than GQRX for high sample rates; supports ANTSDR E200 via IIO source.

PySDR (pysdr on pip): Lichtman's tutorial + Python utility library. Companion to the book at pysdr.org.


Key Terms

  • IQ samples: in-phase (I) and quadrature (Q) complex samples; the raw output of every SDR; stored as complex64 (two float32 per sample)
  • Baseband: signal representation centered at 0 Hz (DC); the downconverted form an SDR produces
  • FFT: Fast Fourier Transform; converts time-domain IQ buffer to frequency-domain spectrum; the engine behind every SDR waterfall display
  • Nyquist rate: minimum sample rate = 2 × signal bandwidth required for alias-free reconstruction
  • Chirp (CSS): Chirp Spread Spectrum; LoRa's modulation; a continuous frequency sweep from f_low to f_high (up-chirp) or reverse; robust against multipath and narrowband interference
  • Flowgraph: GNU Radio's processing graph model; directed acyclic graph of signal-processing blocks connected by typed sample streams
  • Inspectrum: open-source IQ file analysis tool; time-frequency waterfall + phase/amplitude plot; useful for signal identification before decoding