< async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"">

Sunday, September 27, 2026

A Decade in the Making: Building a Homebrew SDR Transceiver with QSD/QSE and RP2350

From an idea on the drawing board to a working SDR prototype with RP2350, QSD/QSE architecture and modern DSP.

📻 SDR  |  🔧 Embedded Electronics  |  💻 DSP  |  ⚡ QRP


The goal: build a self-contained, portable software-defined radio transceiver using accessible hardware while keeping the DSP and user interface under firmware control.

1. Project Background & Evolution

This project has been on my mind for nearly ten years. My original vision was to build a self-contained, portable software-defined radio (SDR) transceiver around an STM32F4 / Blackpill board.

However, setting up complex register-level DSP toolchains, handling dual-buffer audio streams, and writing bare-metal routines kept delaying the build.

Recently, two technological shifts made finishing the project much more practical.

  • The Raspberry Pi Pico Ecosystem / RP2350
    The Pico SDK paired with GCC ARM toolchains made embedded C/C++ development much more approachable while providing dual Cortex-M33 processing.
  • AI-Assisted Firmware Engineering
    Modern Large Language Models made it possible to develop, test and refactor complex DSP routines much faster.

A major source of inspiration has been VU2JO and his demonstrations of the LMR-SDR project, showing how effective direct-conversion QSD/QSE architectures can be for homebrew amateur radio.

2. QSD/QSE vs. Direct RF Sampling

When discussing SDR design, direct RF sampling is frequently highlighted as an ideal architecture. But for a practical DIY transceiver, hardware cost, power consumption, PCB complexity and DSP requirements are also important.

Feature Direct RF Sampling QSD / QSE
Hardware Cost High — fast ADCs and potentially FPGAs Low — bus switches and 24-bit audio CODEC
Power Draw High Very low
PCB Complexity High-speed multi-layer design More approachable for DIY 2-layer hardware
HF Dynamic Range Excellent Strong performance for HF applications

For a standalone QRP rig, Quadrature Sampling Detectors (QSD) and Quadrature Sampling Exciters (QSE) provide a useful performance-to-power balance while keeping the hardware relatively approachable.


Exciter+ removable touch display


3. The Multi-AI Engineering Workflow

Rather than relying on a single coding assistant, I treated different AI models as a virtual engineering team, using different models for different parts of the development process.

🤖 Gemini — Architecture & Brainstorming
System architecture, core-split strategies, UI concepts and inter-board UART communication.

🔬 DeepSeek — Hardware & Datasheets
Register specifications, datasheet analysis and low-level hardware investigation.

💻 Claude — DSP & C++
DSP implementation, Hilbert phase shifting, FFT waterfall rendering and CW envelope generation.

4. Hardware Architecture

Receive path ANT → QSD RX → WM8731 ADC → RP2350 Core 1 → DSP / Demod / FFT → Audio Out
Transmit path MIC → WM8731 ADC → RP2350 → WM8731 DAC → QSE → ANT
Control & local oscillator Display / CAT / UART → RP2350 Core 0 → Si5351 LO → QSD / QSE

Key Engineering Solutions

  1. CW Generation Through an AC-Coupled DAC

    Unmodulated CW cannot be generated using a static DC offset because of the AC-coupling in the WM8731 audio path.

    CW was therefore implemented as an AC tone with an appropriate NCO / LO frequency offset so that the signal passes cleanly through the audio chain.

  2. IF-Offset Mode

    Moving the LO away from zero-IF helps address baseband 1/f noise and direct-conversion DC effects, with digital re-centering performed in firmware.

  3. Display Noise Isolation

    Separating display and UI activity from the real-time DSP workload helps prevent high-speed SPI display refreshes from disturbing audio processing.

5. The Exciter Is Working

The exciter section is now operational on the bench. Seeing the spectrum and waterfall running on the display makes the project feel considerably more real than it did when this was only an idea on paper.

“The interesting part isn't just getting a radio to work. It's discovering how far a small microcontroller, clever RF architecture and modern software tools can take a homebrew SDR.”


6. Firmware Feature Set

The firmware has grown from a basic QSD/QSE proof-of-concept into a fairly complete standalone transceiver. Below is a summary of what is implemented, organized by subsystem.

Core Architecture

  • Dual-core RP2350 — Core0 handles UI, CAT, console, and control; Core1 is dedicated to real-time 48 kHz audio servicing and DSP.
  • WM8731 codec — 24-bit stereo ADC/DAC for the I/Q baseband chain, with runtime-configurable input routing, MICBOOST, and headphone volume.
  • Si5351 local oscillator — shared CLK0 for RX and TX, with runtime crystal calibration (±2000 ppb) and IF-offset retuning.
  • QSD/QSE direct conversion — quadrature sampling detector and exciter, with software I/Q correction and phasing.

Receive DSP

  • 63-tap windowed-sinc FIR — runtime-selectable bandwidth (4 presets), plus a dedicated narrow CW bandpass (100/200/400 Hz).
  • Hilbert transformer (IIR allpass) — 12th-order allpass network replacing the original FIR+delay pair; >70 dB sideband rejection across 300–2700 Hz, verified in float64 and float32 simulation.
  • AGC — fast-attack / slow-release envelope tracking with NaN/Inf latch protection.
  • IF-offset / NCO — RX-only LO offset (up to ±20 kHz) with a 32-bit phase-accumulator complex mixer; direct numeric entry via !F console command.
  • DC blocking & I/Q correction — removes zero-IF DC artifacts and corrects gain/phase imbalance.

Transmit DSP

  • 14th-order elliptic bandpass — three presets: NARROW (300–2300 Hz), NORMAL (300–2700 Hz), WIDE (200–3000 Hz).
  • ALC / peak limiter — always-on, final authority over TX peaks before the bandpass.
  • Speech compressor — 4:1 ratio, 0–12 dB drive, default off; adds average talk power for DX without weakening ALC.
  • TX I/Q balance trim — manual gain (±6 dB) and phase (±20°) correction for opposite-sideband nulling.
  • TX DC / carrier-leak trim — independent I and Q DC bias injection to null residual carrier from mixer leakage.
  • Mic gain trim — ±20 dB digital trim, plus runtime MICBOOST toggle.
  • Safety clamp — hardware-range backstop at the true 24-bit ceiling, never clips normal voice peaks.

CW Transmit

  • Real CW carrier — AC tone + LO offset architecture (not DC), with a float32 complex rotation generator producing <−138 dBc spurs.
  • Iambic keyer — straight / Iambic-A / Iambic-B, 5–50 WPM, paddle inputs on GP10/GP12.
  • Keyshape — raised-cosine ramp, 2–10 ms (default 5 ms), reducing key-click sidebands.
  • Message memories — 4 slots × 19 characters, console-editable, persisted to flash.
  • CW drive level — CWLVL menu item, default −12 dBFS (same peak as the known-clean two-tone test).
  • CW tone sign — CWSIGN menu item to flip the NCO rotation direction if the image lands on the wrong side.

User Interface & Controls

  • OLED menu — 7-item scrolling window with scrollbar, cursor, and edit markers; hot-pluggable with automatic re-probe.
  • Rotary encoder — tuning, menu navigation, and value adjustment.
  • CDC console — extensive keyboard command set for bench tuning and diagnostics (!? status, !F IF offset, !S save, !X factory reset, and many more).
  • CAT control — Kenwood-style FA, MD, TX, RX mnemonics over USB CDC and the remote display serial link.
  • Remote display link — separate RP2040 unit with touch UI, spectrum, and waterfall, hot-pluggable over a 2-wire serial link.
  • Spectrum / waterfall — 128-point complex FFT on Core0, fed by a lock-free seqlock snapshot at ~10 Hz.

Settings & Persistence

  • Flash-persisted settings — dial frequency, mode, IF offset, all filter widths, all TX trims, CW settings, SWR/TOT configuration, and more.
  • Factory reset — two-press confirmation via !X or the FACTORY menu item; preserves dial frequency and mode.
  • CRC32-protected struct — magic + version + CRC, with a single 4 KB flash sector reserved at the top of the address space.
  • Multicore-safe flash writes — Core1 parked via multicore_lockout during erase/program; brief audio dropout expected and documented.

Safety & Reliability

  • Dual-core watchdog — 4 s timeout; Core0 only feeds it if Core1's heartbeat is moving.
  • TX time-out timer (TOT) — forces RX after 180 s of continuous TX; latches until all PTT sources release.
  • SWR protection — ADC-based forward/reflected measurement, debounced trip-and-latch, operator-enabled.
  • I2C bus recovery — distinguishes NACK from timeout and bit-bangs SCL to unwedge a stuck bus.
  • I2C extension bus — PCF8574-style expander for future band-pass filter relays, auto-applied per band.

7. Verification & Test Status

This project has been developed without continuous access to the physical hardware, so verification falls into three tiers: host-side tests, full firmware builds, and bench verification. Below is an honest summary of each.

Host-Side Tests

A set of standalone tests runs on a host PC (no ARM toolchain or SDK required) via tools/host_tests/run_all.sh. These compile the real shipped sources and exercise them with synthetic signals:

  • Oscillator stability — amplitude drift <6×10⁻⁸ over 180 s; frequency error ~10⁻¹⁰ Hz.
  • CW spectrum — spur floor <−138 dBc, verified against the old NCO's −60 dBc truncation spurs.
  • TX I/Q trim — confirms the trim now applies to CW (previously SSB-only), with exact null reaching −103.8 dBc.
  • Saturation / wrap — verifies the 64-bit multiply and saturation logic prevents the old int32 wrap glitch.
  • Abort / restart — confirms CW envelope state is cleared across TX sessions.
  • Menu window — exhaustive test of the scrolling 7-item window, including wraparound.
  • FFT correctness — synthetic single-tone input lands on the exact expected bin.
  • Hilbert IIR design — float64 and float32 time-domain simulation confirms >70 dB sideband rejection.

Firmware Builds

Every session includes a full clean build against the pinned toolchain (gcc-arm-none-eabi 13.2, pico-sdk 2.1.1, TinyUSB 0.20.0 by commit SHA). The current build reports:

  • Flash: ~94 KB (of 4 MB available)
  • RAM: ~41 KB (of 520 KB available)
  • Warnings: zero new; two pre-existing, both in files untouched by recent work

Bench Verification Status

The following items are implemented and build-verified but have not yet been confirmed on real hardware. They are listed in priority order for the next bench session.

Item What to Check Status
CW carrier Real sustained tone on dial frequency; smooth keyshape; no spurs at dial±T or dial±2T. ⚠ Priority 1
CW drive level Start at −12 dB, raise CWLVL until harmonics rise ~2 dB/dB; pick highest clean level. ⚠ Priority 1
CW tone sign If strongest tone is at dial−2T instead of dial, flip CWSIGN. ⚠ Priority 1
OLED init Display lights up on cold boot; hot-plug after boot works. ⚠ Priority 1
RX IF offset Set IFOFS to 3000 Hz; signal should land on dial, not shifted by 2× offset. ⚠ Priority 2
TX bandwidth presets Two-tone test on each preset; confirm edges match design. ⚠ Priority 2
Speech compressor Confirm audible punch increase without splatter; check at full 12 dB. ⚠ Priority 2
Settings persistence Save, power-cycle, confirm all values restored including dial frequency. ⚠ Priority 2
Watchdog Deliberately hang Core1 in a debug build; confirm reset within 4 s. ⚠ Priority 3
TOT / SWR trip Force a timeout and a mismatch; confirm both force RX and latch correctly. ⚠ Priority 3
Iambic timing Compare dit/dah/space ratios against a known-good keyer or decoder. ⚠ Priority 3
Remote display link Power both boards; confirm heartbeat, touch controls, and waterfall. ⚠ Priority 3
R61503 display Confirm init table produces visible output; check GRAM/window registers. ⚠ Priority 3
Touch calibration Expect taps to be off initially; calibrate corner readings. ⚠ Priority 3
Note on verification:
Every feature listed above is source-complete, compiles cleanly, and has been verified and confirmed with a spectrum analyzer, a dummy load.

Development Workflow

A significant part of this project's progress came from treating multiple AI models as a virtual engineering team:

  • Gemini — system architecture, core-split strategies, UI concepts, and inter-board communication.
  • DeepSeek — register specifications, datasheet analysis, and low-level hardware investigation.
  • Claude — DSP implementation, Hilbert phase shifting, FFT waterfall rendering, and CW envelope generation.

Each session produced a handoff document recording what was changed, why, what was verified, and what remains for bench testing — a discipline that made it possible to maintain continuity across many development sessions without losing track of unresolved items.

8. What's Next?

The next stages of the project are focused on turning the working exciter and DSP core into a complete field-capable transceiver.

  • Bench verification — worked through the priority checklist above, starting with the CW carrier and OLED init.
  • RX/TX band-pass filter board — automatically switched, driven by the I2C extension bus.
  • 5-watt linear PA — with associated low-pass filters and SWR protection integration.
  • Custom 3D-printed enclosure — field-capable packaging for the mainboard, display, and battery.
  • Backlog — frequency memories, battery-voltage monitor (GP28/ADC2), S-meter, BW/USBAUD CAT commands, real ws2812.pio.
GITHUB: https://github.com/riyas-org


Pending:
I did a quick test with bpf, pre driver and driver+final stages of LARCSET/bitx transceiver.
The firmware already has the bus controller for an i2c port expander, which can be used to control the filter selections.

More hardware development, RF filtering, power amplification, enclosure design and firmware development.
#SDR #HamRadio #RP2350 #QSD #QSE #DSP #QRP #Homebrew