RF Front End is a 14-part series that zooms all the way in on the layer the
SDR Internals series only
skimmed: the RF source. It is the story of how
GopherTrunk turns three families
of cheap USB dongles — RTL-SDR, Airspy (R2/Mini and HF+), and HackRF — into a
clean stream of IQ samples, in pure Go, with no libusb, no librtlsdr,
no CGO at all.
Where SDR Internals asked what is the pipeline?, this series asks how do you
actually talk to the metal? — the Device contract, the self-registering
driver registry, hand-rolled USB transport on three operating systems, the
RTL2832U register dance, the R820T tuner, real-to-complex sample conversion, and
the hotplug-aware device pool. And because driver work is where theory meets
hardware reality, every part carries a “problem we hit” section: a real bug
(RTL-SDR Blog V4 deafness, GC-churn IQ loss, a silent USB reaper deadlock) and
the Go code that fixed it.
New to radio first? Start with the Learn RF & SDR path, then come back here for the implementation.<ol class="post-list series-list"><li class="post-card"> <h2 class="post-card__title">RF Front End, Part 1: Why Drive Radios in Pure Go?</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">The opening post of RF Front End, a 14-part deep dive into GopherTrunk’s RF source layer — the pure-Go USB drivers that turn RTL-SDR, Airspy, and HackRF hardware into IQ samples without libusb, librtlsdr, or any C dependency.</p></li><li class="post-card"> <h2 class="post-card__title">RF Front End, Part 2: The Device Contract</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">A method-by-method tour of GopherTrunk’s Device interface — the one abstraction four very different radios hide behind — and the optional, type-asserted extension interfaces that expose RTL-SDR-only quirks without polluting the universal contract.</p></li><li class="post-card"> <h2 class="post-card__title">RF Front End, Part 3: The Driver Registry & Enumeration</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk’s self-registering driver registry keeps the core hardware-agnostic — drivers register at init(), the binary’s import set chooses the hardware, and EnumerateAll walks every backend to list attached dongles while staying resilient when one driver fails.</p></li><li class="post-card"> <h2 class="post-card__title">RF Front End, Part 4: Talking to USB Without libusb</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk speaks to RTL-SDR hardware over raw kernel USB interfaces instead of linking libusb, and the single Transport contract that lets one driver run on three wildly different OS USB stacks.</p></li><li class="post-card"> <h2 class="post-card__title">RF Front End, Part 5: USB on Linux — USBDEVFS & Async URBs</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk’s Linux USB backend hand-rolls USBDEVFS ioctl encoding, submits a ring of async URBs, and reaps them in a single goroutine for sustained IQ throughput — all in pure Go behind a safe Transport API.</p></li><li class="post-card"> <h2 class="post-card__title">RF Front End, Part 6: USB on macOS & Windows</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk reaches the same Transport contract through macOS IOKit (via purego, with OS-thread-pinned reader goroutines) and Windows WinUSB (with overlapped I/O), and why macOS forced us to own the calling thread.</p></li><li class="post-card"> <h2 class="post-card__title">RF Front End, Part 7: RTL-SDR I — Bringing Up the RTL2832U</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk brings up the RTL2832U demodulator in pure Go — the order-sensitive init sequence captured verbatim from librtlsdr, the I2C bridge to the tuner, GPIO and bias-tee, and the USB register transport underneath it all.</p></li><li class="post-card"> <h2 class="post-card__title">RF Front End, Part 8: RTL-SDR II — The R82xx Tuner & the Blog V4 Deafness</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">GopherTrunk’s pure-Go R820T/R828D tuner driver — PLL tuning, a shadow-register cache that makes read-modify-write free, and tuner auto-detection. Plus the headline bug: why the RTL-SDR Blog V4 came up deaf and mistuned by 1.8×, and how a manual override plus per-band input switching fixed it.</p></li><li class="post-card"> <h2 class="post-card__title">RF Front End, Part 9: RTL-SDR III — IQ Streaming & the GC-Churn Bug</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">Sustained IQ streaming from the RTL2832U in pure Go — 32 async USB buffers, a deep consumer channel, and a bit-identical U8-to-complex64 lookup table. Plus the headline bug: per-chunk allocations whose GC churn shed a quarter of the control channel’s live IQ, and the zero-allocation reuse ring that fixed it.</p></li><li class="post-card"> <h2 class="post-card__title">RF Front End, Part 10: Airspy R2/Mini & HF+ — Real Samples to Complex Baseband</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk turns the Airspy R2/Mini’s bare real ADC stream into complex baseband entirely on the host — a leaky DC blocker, a multiplier-free Fs/4 mix, and a stateful half-band Hilbert pair — then folds in the Airspy HF+, whose native int16 IQ needs none of it.</p></li><li class="post-card"> <h2 class="post-card__title">RF Front End, Part 11: HackRF One — Signed 8-Bit IQ End to End</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">The HackRF One driver end to end — the simplest IQ format of the three, signed 8-bit interleaved I,Q scaled to complex64; enumeration across One/Jawbreaker/Rad1o PIDs; and the open-time board-ID readback that fixed empty probe gains by establishing identity and the gain ladder before streaming.</p></li><li class="post-card"> <h2 class="post-card__title">RF Front End, Part 12: The SDR Pool & USB Hotplug Watchdog</h2> <p class="post-card__meta"> Deep dives </p><p class="post-card__desc">How GopherTrunk manages a fleet of opened dongles behind one pool — assigning control, voice, and wideband roles, running a USB watchdog that re-enumerates every 30 seconds to catch hotplug, and reacquiring a device after the kernel hands it a new bus address. Plus the re-open race that taught us to serialize retune behind stream teardown.</p></li></ol><p class="blog-feed-link"> See all deep dives or subscribe via RSS. </p>