Category: RTL-SDR

Fixing a Locked-Up RTL-SDR 700 km Away Using uhubctl USB Power Cycling

Over on Medium, Jugy depin has shared a useful troubleshooting write-up describing how they recovered a frozen RTL-SDR on a remote Raspberry Pi station located 700 km away, with no physical access available. The dongle had stopped responding with  usb_claim_interface error -6 and Failed to open rtlsdr device #0 errors, while still showing up in lsusb.

After ruling out the usual suspects, such as DVB drivers, conflicting processes, permissions, and even a full reboot, they concluded that the RTL2832U had locked up at the USB hardware level. To make things worse, they discovered that a Raspberry Pi reboot from the terminal does not actually power-cycle its USB ports.

The fix was to use uhubctl to cut and restore power to only the specific port the SDR was plugged into, after first carefully identifying which port that was (so as not to accidentally kill the Ethernet port and lose remote access entirely). The commands shown in the post performed a true hardware-level reset equivalent to unplugging and replugging the dongle, and rtl_test confirmed the device came back cleanly.

Jugy recommends that anyone running remote SDR stations either build uhubctl into a healthcheck script or add a smart plug for unattended recovery.

Build a Cubesat Reviews a Discovery Drive Prototype and Sets up SatNOGS

Over on YouTube Manuel from the 'Build a Cubesat' channel has uploaded a video testing a prototype version of our Discovery Drive antenna rotator. If you are unaware, Discovery Drive is our new antenna rotator product for applications like satellite tracking and general antenna positioning that is currently being crowd-funded over on Crowd Supply. There are two days left in the campaign.

In the video, Manuel overviews the Discovery Drive, shows the internals, and walks us through the web UI. He goes on to show how it can be set up with the SatNOGS project. The SatNOGS project has volunteers set up ground-based satellite stations, and anyone can use those stations to log an observation anywhere in the world.

We note that he mentioned some trouble with getting SatNOGS to rotate the Discovery Drive over zenith. We have added a note to our Wiki showing how this can be fixed by specifying the correct rotational limits for the Discovery Drive.

Discovery Drive Antenna Rotator Preview

RTL-SDR 433: A New Android App for Decoding 433 MHz Sensors with rtl_433

Thank you to Christian Ebner from ebcTech, who has submitted news about his newly released Android app RTL-SDR 433, which lets you run the rtl_433 decoder directly on your phone using an RTL-SDR dongle connected via a USB OTG cable.

The app bundles rtl_433 as a native Android library and supports all 258 device protocols out of the box, including weather stations, TPMS, wireless doorbells, PIR motion sensors, energy meters, door/window contacts, and remote sockets. Decoding runs entirely on-device with no internet connection required, no root, and no special drivers. It uses the standard Android USB Host API together with a libusb Android port.

The UI is built with Jetpack Compose and Material 3, and shows a live list of unique sensors with expandable cards (temperature, pressure, RSSI, raw JSON) plus a full history log. The app is free to try with a decreasing per-session reading limit, and a one-time purchase for a few dollars removes the limit permanently.

We note that the GPL-licensed native layer (rtl_433, rtl-sdr, libusb Android port and EBC's integration glue) is published openly at github.com/ebc81/rtlsdr433-native-gpl in compliance with GPL-2.0, while the UI layer remains closed-source. 

More information about the app is available on the ebcTech page at https://ebctech.eu/rtl-sdr-433-android.

RTL SDR 433 for Android

Tactical_FSK_Modem: An Open Software MFSK Image & Text Modem for PC and Android

Thanks to Ibrahim (YD1RUH), who wrote in to share his open-source open-software project Tactical_FSK_Modem, which turns a standard PC or Android device into an audio-based MFSK transceiver for sending images and text over a radio link. Conceptually similar to SSTV or HF FAX, it adds Hamming (7,4) Forward Error Correction that wraps every 4 data bits into a 7-bit block and repairs single-bit errors in real time, significantly lowering BER in low-SNR conditions. The system forces a hardened 720p vertical resolution for noise resistance, and a 1400 Hz → 1000 Hz → 1400 Hz VIS-like "start melody" handles automatic RX canvas reset and sync with no manual alignment.

Pre-built Windows and Android binaries are available in the repo, and the Android port is probably the most interesting part. Operators can connect a smartphone to HT, ham radio, or an SDR to send tactical images directly from the field. 

We note that while the code is Apache 2.0 licensed, we don't appear to see any source code in the repo, but the .exe and .apk files are available to download. Ibrahim notes that he is actively looking for feedback and collaboration to further improve the system's robustness for tactical and emergency communication use cases.

Licensing Update: Ibrahim has clarified that he mistakenly referred to the project as open-source, but his intention was to actually refer to it as 'open-software'. The software is free, but the source code is not provided.

Tactical FSK Modem UI
Tactical FSK Modem UI

Stream1090: A New Approach to ADS-B Demodulation Using CRC-Based Framing Instead of Preamble Detection

Over on GitHub, Martin (mgrone) recently released stream1090, a new open source C++ Mode-S demodulator that takes a fundamentally different approach to finding aircraft messages. Rather than searching for the traditional preamble pulse sequence as dump1090 and readsb do, stream1090 continuously maintains shift registers and identifies valid messages based on their CRC checksum. In busy airspace where preambles can be corrupted by overlapping signals, this approach theoretically cannot miss a message as long as the data itself is intact. Since the CRC is always being computed, it can also be used for single-bit error correction.

The software supports both RTL-SDR and Airspy dongles. It's lightweight enough to run on a Raspberry Pi Zero 2W. Stream1090 is a demodulator only, designed to pipe output into readsb or dump1090-fa via socat, slotting into your existing ADS-B stack as a drop-in replacement for the demodulation stage.

If you have an ADS-B station in a high-traffic area, let us know if Stream1090 increases your message rate! There is also a discussion about it on FlightAware, where many people have indicated that they are getting great results.

Stream1090 GitHub Readme
Stream1090 GitHub Readme
 

BrowSDR: Turn Your HackRF or RTL-SDR Into a Browser-Based Remote WebSDR

Joel (jLynx), known for his work on the HackRF Mayhem firmware, has released an open-source project called BrowSDR that turns a HackRF or RTL-SDR into a fully browser-based SDR receiver. The application connects to your SDR directly via WebUSB and uses a high-performance Rust/WebAssembly DSP pipeline running in Web Workers for smooth, real-time spectrum and waterfall display. It supports WFM, NFM, AM, SSB, CW, and raw IQ demodulation, along with RDS decoding and POCSAG pager decoding. A standout feature is the ability to open unlimited simultaneous VFOs, each with independent demodulation and DSP settings, with the developer having tested up to 62 running at once.

The real killer feature is remote access. Using WebRTC, you can share your locally connected SDR and access it from anywhere in the world through a browser with no server setup required. BrowSDR also includes built-in Whisper AI transcription that can live-transcribe audio from each VFO independently. The project currently supports HackRF, HackRF Pro, and the RTL-SDR Blog V4, with AirSpy and LimeSDR support coming soon. It also works on Android devices with a USB-C cable. BrowSDR is open source under the AGPL-3.0 license and a live demo is available at browsdr.jlynx.net.

BrowSDR Interface with POCSAG Decoding
BrowSDR Interface with POCSAG Decoding

RTLSDR-NEXT: A Ground-Up Rust Rewrite of the RTL-SDR Driver

Thank you to Matthew Delashaw, who has written in and shared a guest post with us. Matthew has rewritten the 2013 librtlsdr library from the ground up in Rust. His motivations for doing so and the results are explained in the post below:


I actually started down this path as an "interest". There was a Ham radio Technical Interest Group I was planning on attending a meeting. I had already wanted to convert my Raspberry Pi into a fallback radio receiver for potential internet outages and listening to storm chasers on SKYWARN. Now I have the "v4" dongle, and a full end-to-end SDR solution. !Spoilers, I'm releasing a native smart phone client soon.

The RTL2832U chipset has powered affordable software-defined radio for over a decade. The reference driver, librtlsdr, was written in C around 2013 and follows the same architectural pattern it always has: a blocking callback loop, manual buffer management, and a programming model that predates modern async runtimes by years.

rtlsdr-next is a ground-up Rust rewrite. It exposes SDR data as a native Tokio Stream, ships a zero-allocation DSP pipeline, and has first-class support for the RTL-SDR Blog V4 — a newer hardware variant the upstream driver handles correctly but never cleanly abstracted. The result is faster, safer, and substantially easier to build applications on top of.

1.49 GiB/s IQ conversion on Pi 5  ·  ~45ms frequency switching (was ~270ms with 20 I2C toggles)  ·  0 allocations in the streaming hot path


Why rewrite it at all?

The C driver works. Millions of people run it daily via OpenWebRX, GQRX, SDR++, and friends. But its architecture creates friction at every layer: the callback-based stream makes backpressure impossible to reason about, the I2C bus is hammered with redundant open/close cycles, and the conversion routine uses a 256-entry lookup table whose cache pressure eats into throughput on modern out-of-order cores.

More practically: trying to integrate librtlsdr into a modern async Rust application means spawning a dedicated thread, wrapping callbacks in channels, and handling all the lifetime gymnastics manually. For every project that does this, someone reinvents the same boilerplate. There are plenty of Rust "wrappers" out there That exemplifies this.


The stream architecture

The primary interface is a standard async stream. A SampleStream wraps a background USB reader thread that feeds raw IQ bytes into a tokio::mpsc channel. The F32Stream layer sits on top and handles conversion, decimation, DC removal, and AGC — all in a single pipeline with no intermediate heap allocations.

let mut stream = driver.stream_f32(8)   // ÷8 → 256 kSPS
    .with_dc_removal(0.01)
    .with_agc(1.0, 0.01, 0.01);

while let Some(Ok(iq)) = stream.next().await {
    // interleaved f32 I/Q, ready to demodulate
}

The blocking USB read thread never touches the async runtime. Sample delivery to async consumers happens entirely through the channel, and the PooledBuffer type ensures the backing buffers are returned to the pool via Drop — no explicit lifecycle management needed at the call site.

  • SampleStream — Blocking USB thread → tokio::mpsc channel. Pre-allocated buffer pool. Flush-on-tune via broadcast::Sender.
  • F32Stream — Convert → decimate (FIR) → DC remove → AGC. Processes split I/Q in-place. No per-block allocation.
  • PooledBuffer — Returns buffer to pool on Drop. try_send with blocking fallback thread — the pool never silently starves.
  • BoardOrchestratorV4Orchestrator / GenericOrchestrator produce a TuningPlan. Board logic never leaks into chip drivers.

The I2C repeater optimization

Every register write to the R828D tuner chip goes through an I2C bridge in the RTL2832U. The bridge must be explicitly opened and closed around each transaction. In a naive implementation — which is what the reference driver does — every call to set_frequency independently opens and closes the repeater for each register write.

A full frequency switch involves setting the PLL, MUX, filter coefficients, and various control registers. That adds up to roughly 20 open/close cycles, and each one costs ~13ms of USB round-trip time.

The fix: a single with_repeater(|| { ... }) closure that holds the bridge open for the entire mux + PLL sequence. One open, one close, all the work done in between.

// Before: ~20 repeater toggles ≈ 270ms
self.set_mux(hz)?;   // 10 writes, each with open/close
self.set_pll(hz)?;   // 10 writes, each with open/close

// After: 1 repeater toggle ≈ 45ms
self.with_repeater(|| {
    self.set_mux_raw(hz)?;
    self.set_pll_raw(hz)?;
    Ok(())
})?;

The distinction between write_reg_mask (opens and closes the repeater itself) and write_reg_mask_raw (no repeater toggle, must be inside a bracket) is enforced by convention throughout the codebase. Any raw variant called outside a bracket is a bug that surfaces immediately as a timeout rather than silently returning stale data.


Converter throughput

librtlsdr converts raw IQ bytes to float via a static 256-entry lookup table. It is a reasonable approach from an era when float math was expensive and cache was plentiful. On the Cortex-A76 inside the Pi 5, the situation is inverted: the NEON FPU is underutilized and random-access table reads create cache pressure that limits throughput.

The arithmetic equivalent — (x as f32 - 127.5) / 127.5 — is computed in two instructions per sample and is trivially auto-vectorized by LLVM. The compiler emits NEON FMLA instructions without any manual intrinsics.

Operation librtlsdr (C) rtlsdr-next (Rust)
Standard conversion (256KB) 172.32 µs · 1.42 GiB/s 164.35 µs · 1.49 GiB/s
V4 inverted conversion 256.07 µs · 976 MiB/s 170.81 µs · 1.43 GiB/s
FIR decimation ÷8 N/A 615 µs · 426 MSa/s

The V4 inversion case is a particularly notable optimization. librtlsdr implements it as a two-pass operation: first a full LUT conversion, then a second pass to negate every Q sample. The Rust implementation folds both into a single pass, processing I and Q pairs together and avoiding a complete re-read of the output buffer.


RTL-SDR Blog V4 specifics

The V4 is a substantial hardware revision. It ships with an R828D tuner (not R820T), adds an HF upconverter and a GPIO-switched triplexer, and has several initialization quirks that librtlsdr discovered through usbmon traces and EEPROM string detection.

The board logic is isolated entirely in V4Orchestrator. Given a target frequency, it returns a TuningPlan — the actual tuner frequency, whether spectral inversion is needed, which triplexer path to select, and whether the frequency falls inside a notch band. The R828D chip driver never touches a GPIO.

Notable quirks baked into the driver: the R828D responds at I2C address 0x74 rather than the R820T's 0x34; frequencies below 28.8 MHz are upconverted by adding the crystal frequency, and the resulting spectrum is inverted (Q = –Q). Every demodulator register write must be followed by a dummy read of page 0x0a register 0x01 — the hardware requires this as a flush sync, and omitting it causes subsequent control transfers to stall with a pipe error.


Built-in DSP pipeline

The dsp module ships a complete demodulation stack. The decimator uses a windowed-sinc FIR with NEON acceleration on aarch64, with a scalar fallback that LLVM auto-vectorizes on x86_64. The FM demodulator is a quadrature discriminator with configurable de-emphasis. AM uses a two-stage DC-subtraction envelope detector. SSB uses the phasing method with a 65-tap Hilbert transformer windowed with Blackman-Harris for high sideband rejection.

All demodulators maintain state across block boundaries — the history overlap buffer in the decimator ensures the FIR convolution is correct at every chunk edge, which is essential for continuous streaming.


Standalone servers

Two installable binaries ship alongside the library. rtl_tcp implements the standard RTL-TCP protocol and is compatible with OpenWebRX+, GQRX, and SDR++. websdr is a self-contained WebSocket SDR server with a full spectrum and waterfall UI embedded as a compiled-in HTML file — no separate web server needed. Both support TLS. The WebSDR binary accepts --cert and --key flags for wss:// connections, which are required by iOS App Transport Security when using a public domain.

  • OpenWebRX+ — confirmed working
  • GQRX — confirmed working
  • SDR++ — confirmed working
  • Corona SDR (iOS) — confirmed working

Getting started

cargo install rtlsdr-next

# Smoke test — run this first
RUST_LOG=info cargo run --release --example hw_probe

# Start an rtl_tcp server
rtl_tcp --address 0.0.0.0 --port 1234

# Start the WebSDR UI
websdr --address 0.0.0.0 --port 8080

On Linux, set up a udev rule for persistent USB access without sudo. On Windows, Zadig is required to swap the DVB-T driver to WinUSB — build works without it, but the USB runtime requires it at runtime.


Source on GitHub at github.com/mattdelashaw/rtlsdr-next. Licensed Apache 2.0. Benchmarks measured on Raspberry Pi 5 (aarch64) and AMD Ryzen 7600X (x86_64) with cargo build --release, no target-cpu=native.

Keep and eye out for the smart phone app release here: Spectral Bands

rtlsdr-next running with GQRX
rtlsdr-next running with GQRX

Adding ACARS Decoding to an ADS-B Flight Tracker

Over on his blog, cynicalGSD has written a detailed post about how he extended his home ADS-B flight tracking setup to also decode ACARS. His existing system runs an RTL-SDR dongle on a Raspberry Pi feeding a database and Flask web app. Adding ACARS required a second RTL-SDR and a separate VHF dipole antenna tuned for 129–131 MHz.

ACARS (Aircraft Communications Addressing and Reporting System) is a text-based datalink that has been in use since 1978, carrying short messages between aircraft and ground stations. It includes messages such as OOOI events (Out of gate, Off ground, On ground, Into gate), pilot weather reports, maintenance fault codes, and gate and fuel data. The key feature of their implementation is cross-referencing ACARS messages with existing ADS-B records via aircraft registration and ICAO hex address, enriching flight records with precise departure and arrival timestamps from the airline's own reporting system.

The full write-up covers the database schema, Python integration using acarsdec, gain tuning tips, and the Flask web interface. cynicalGSD mentions that the code is available for anyone interested, but we didn't see a link, so please comment on his post if you are interested.

Technical Summary of cynicalGSD's ACARS + ADS-B implementation.
Technical Summary of cynicalGSD's ACARS + ADS-B implementation.