"Lyons's argument in Chapter 7 is that filter design at intermediate level isn't about choosing between pre-canned IIR and FIR forms; it's about understanding which performance criterion the application actually demands." — VCA-RF-201 public page anchor-weave excerpt
Lecture (2 hr)
9.1 GNU Radio Architecture at Intermediate Depth
WIR-101 Week 9 introduced GNU Radio Companion (GRC) as a block-diagram DSP environment. RF-201 Week 9 opens the architecture: how GRC blocks work, how to write your own, and how the polyphase filter bank solves the sample-rate conversion problem that LoRa demodulation requires.
The GNU Radio signal graph model:
GNU Radio programs are directed acyclic graphs (DAGs) of signal-processing blocks. Each block has:
- Input ports: consumes items at some rate from upstream
- Output ports: produces items at some rate to downstream
- A
work()function: called by the GR scheduler with input/output buffers
Items are typed: float, complex, gr_complex (which is std::complex<float>). Every sample in a gr_complex stream is a 4-byte pair (I, Q as IEEE 754 float32).
The scheduler runs blocks in threads. The buffer between blocks is a circular buffer in shared memory. When enough items are available, the scheduler calls work(). This design makes GNU Radio naturally pipelined: blocks run concurrently.
Block types:
| Type | Production ratio | Example |
|---|---|---|
| Source | 0 inputs, N outputs | File Source, PlutoSDR Source |
| Sink | N inputs, 0 outputs | QT GUI Frequency Sink, File Sink |
| Sync Block | 1:1 ratio (M inputs → M outputs) | Add, Multiply, Low Pass Filter |
| Decimator | N:1 ratio (downsample by N) | Rational Resampler, Moving Average |
| Interpolator | 1:N ratio (upsample by N) | Interpolating FIR Filter |
| General Block | Arbitrary ratio | OFDM Serializer, correlation-based sync |
9.2 Writing a Custom Python GNU Radio Block
gr_modtool scaffolds a custom out-of-tree (OOT) module. This is the correct workflow; do not edit the GNU Radio source tree.
# Create an OOT module
gr_modtool newmod rfblocks
cd gr-rfblocks
# Add a synchronous block (1:1 ratio)
gr_modtool add ook_to_bits --block-type sync
# Build and install
mkdir build && cd build
cmake .. -DCMAKE_INSTALL_PREFIX=/usr
make -j4 && sudo make install && sudo ldconfig
The generated Python block stub at python/rfblocks/ook_to_bits.py:
import numpy as np
from gnuradio import gr
class ook_to_bits(gr.sync_block):
"""
Convert OOK envelope (magnitude) to binary bits using threshold.
Input: complex (IQ); Output: float (0.0 or 1.0)
"""
def __init__(self, threshold=0.1):
gr.sync_block.__init__(self,
name="ook_to_bits",
in_sig=[np.complex64],
out_sig=[np.float32])
self.threshold = threshold
def work(self, input_items, output_items):
in0 = input_items[0]
out = output_items[0]
# Envelope detection: magnitude of IQ
envelope = np.abs(in0)
out[:] = (envelope > self.threshold).astype(np.float32)
return len(output_items[0])
After installation, the block appears in GRC's block search. Connect it: PlutoSDR Source → Low Pass Filter → ook_to_bits → File Sink.
9.3 The Polyphase Filter Bank for LoRa Demodulation
From Week 6, LoRa demodulation is: dechirp → FFT → argmax. But there is a subtlety: the received LoRa signal may not be at exactly the nominal sample rate. The receiver sample rate (e.g., 500 kSPS) may not be an integer multiple of the LoRa symbol rate (2^SF / BW samples per symbol at the capture sample rate). Direct DFT demodulation is imprecise.
The polyphase filter bank (PFB) solution:
Lyons Ch 7 introduces the PFB as the efficient solution to polyphase filtering. For LoRa demodulation the specific application is a matched-filter synchroniser:
Instead of one matched filter (the dechirp), create M copies of the matched filter, each with a different fractional-sample timing offset (0/M, 1/M, 2/M, ..., (M-1)/M sample). Feed the received signal to all M branches simultaneously. The branch with the highest correlation output gives both the symbol value and the sub-sample timing offset.
This is the standard approach in gr-lora-sdr (the open-source LoRa GNU Radio implementation) and explains why the LoRa demodulator uses a polyphase form rather than a straightforward correlate-then-FFT.
Lab 5 connection: Lab 5 asks you to implement two LoRa demodulators — one straightforward FIR correlation + FFT, one polyphase. The performance difference is visible at low SNR or when timing offset is non-zero.
9.4 ANTSDR E200 Full-Duplex Lab (Lab 11)
Lab 11 uses the ANTSDR E200 in full-duplex mode: simultaneous transmit and receive on different frequencies (the AD9361 supports half-band transmit + receive with an isolation requirement; use at least 10 MHz separation to avoid TX→RX bleed).
gr-iio flowgraph for full-duplex:
PlutoSDR Source → Low Pass Filter → QT GUI Frequency Sink (RX display)
PlutoSDR Sink ← Signal Source ← (TX chain)
Both PlutoSDR Source and PlutoSDR Sink connect to ip:192.168.1.10. The AD9361 hardware handles the TX/RX switching.
TX parameters (for lab — sandboxed RF environment only):
- TX frequency: 433.5 MHz (offset from 433.92 MHz RX centre)
- TX sample rate: 2 MSPS
- TX gain: start at -20 dBm (low power; RF-shielded enclosure required)
- TX waveform: CW tone (Signal Source, freq=100kHz, amplitude=0.1) for loop-back test; then replace with modulated signal
RX verification: Set RX to 433.5 MHz at 2 MSPS. You should see the transmitted tone at +100 kHz offset in the spectrum display. If you see it, the ANTSDR E200 TX→RX loop is working.
9.5 gr-osmosdr and the Broader SDR Ecosystem
gr-osmosdr is a GNU Radio source/sink block that provides a unified interface to multiple SDR hardware backends: RTL-SDR, HackRF, bladeRF, LimeSDR, and USRP via the osmocom abstraction layer.
# In GRC: osmocom Source block settings
# Device Args: "rtl=0" for RTL-SDR, "hackrf=0" for HackRF, "bladerf=0" for bladeRF
# Sample Rate: match hardware capability
# Center Freq: target frequency in Hz
For the ANTSDR E200, use gr-iio (PlutoSDR Source/Sink) rather than osmocom because the E200's Ethernet-based IIO context is not supported in the osmocom layer. For HackRF and LimeSDR, gr-osmosdr is the recommended interface.
Homework
Reading (2 hr):
- Lyons Ch 7 §7.1-7.3 (filter design criteria; polyphase decomposition)
- GNU Radio wiki "OOT Module Tutorial" — full walkthrough
- gr-lora-sdr README (github.com/tapparelj/gr-lora-sdr) — read the architecture section
Hands-on (2.5 hr): Complete Lab 11 (ANTSDR E200 full-duplex). Then write the custom OOK block above and wire it into a simple flowgraph:
- PlutoSDR Source (or File Source from lab1-modulation-zoo.iq)
- Low Pass Filter (cutoff 10 kHz, transition 1 kHz)
- ook_to_bits block (threshold = 0.05)
- File Sink (save the bit stream to "ook_bits.bin")
- Verify: load ook_bits.bin in Python, count 0s and 1s, plot the first 200 samples
Include: GRC flowgraph screenshot + ook_bits.bin sample plot in your lab notebook.
Key Terms
- OOT module (Out-of-Tree): custom GNU Radio block outside the GR source tree; created with
gr_modtool newmod - sync_block: GR block type with 1:1 input-output sample ratio;
work()receives equal-length I/O buffers - Decimator / Interpolator: GR block types that change sample rate; decimator N:1; interpolator 1:N
- Polyphase filter bank (PFB): M copies of a prototype filter with fractional timing offsets; provides sub-sample timing recovery
- gr-iio: GNU Radio blocks for ADI IIO-based SDR hardware (AD9361 / AD9371 / ADRV9361); used for ANTSDR E200
- gr-osmosdr: unified GNU Radio hardware abstraction for RTL-SDR, HackRF, bladeRF, LimeSDR
- Work function: the
work()method in a GR block; called by the scheduler with input/output buffers; returns number of items produced - TX/RX isolation: spatial or frequency separation between simultaneous TX and RX antennas to prevent TX bleed; minimum 10 MHz freq offset for AD9361 half-duplex