Skip to main content

RTL-SDR receiver driver for bench automation — IQ capture, spectrum, streaming, calibration

Project description

⚠️ UNTESTED — This implementation has not been verified against physical hardware. Code is complete but has not been bench-tested. Verify behavior before relying on output.

rf-bench-drivers-rtlsdr

RTL-SDR receiver driver for the rf-bench bench automation suite. Wraps pyrtlsdr (librtlsdr) with PPM calibration, consistent import patterns, workflow helpers, and a thread-safe streaming generator.

Supported hardware:

  • RTL-SDR Blog v4 (R828D tuner, 1 PPM TCXO, bias tee) — recommended
  • RTL-SDR Blog v3 (R820T2 tuner, bias tee)
  • Generic RTL2832U-based DVB-T dongles (no TCXO — calibrate PPM before use)

Installation

pip install rf-bench-drivers-rtlsdr
# or, for the full rf-bench suite:
pip install rf-bench

System dependency: librtlsdr must be installed.

# Arch Linux
pacman -S rtl-sdr

# Debian / Ubuntu
apt install rtl-sdr librtlsdr-dev

# macOS
brew install librtlsdr

Linux udev rule (required to access the device as a non-root user):

SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838", \
    MODE="0664", GROUP="plugdev"

Save to /etc/udev/rules.d/99-rtlsdr.rules, then udevadm control --reload-rules and re-plug the dongle.


Quick start

from rf_bench.rtlsdr import RTLSDR

# Basic capture
with RTLSDR() as sdr:
    sdr.set_center_freq(144_390_000)   # 144.390 MHz — APRS
    sdr.set_sample_rate(2_400_000)
    sdr.set_gain(30)
    iq = sdr.capture_iq(262_144)
    print(f"Captured {len(iq)} samples, dtype={iq.dtype}")

# Power spectrum
with RTLSDR() as sdr:
    sdr.set_center_freq(433_920_000)
    sdr.set_sample_rate(2_400_000)
    iq = sdr.capture_iq(262_144)
    freq_hz, power_db = sdr.power_spectrum(iq, rbw_hz=1000)
    peak_idx = power_db.argmax()
    print(f"Strongest signal: {freq_hz[peak_idx]/1e6:.3f} MHz  ({power_db[peak_idx]:.1f} dB)")

# Streaming (ADS-B example)
with RTLSDR() as sdr:
    sdr.set_center_freq(1_090_000_000)
    sdr.set_sample_rate(2_000_000)
    sdr.set_gain(40)
    for block in sdr.stream_iq(block_size=65_536):
        process_modes_s(block)
        if should_stop:
            break
    sdr.stop_stream()

PPM calibration

The RTL-SDR v4 TCXO is nominally 1 PPM, but the actual offset should be measured against a known reference for best frequency accuracy:

  1. Connect the RTL-SDR to a signal of known frequency (e.g. the SDG1062X set to 10.000000 MHz, verified on the SSA).
  2. Tune the RTL-SDR to the same frequency.
  3. Compute the offset: ppm = (measured_hz - nominal_hz) / nominal_hz * 1e6
  4. Save it:
with RTLSDR() as sdr:
    sdr.save_calibration(ppm=0.8)    # adjust to your measured value

The correction is stored in ~/.rtlsdr_cal.json and loaded automatically on every subsequent RTLSDR() construction. Pass ppm_correction=0 explicitly to override.


Bias tee (RTL-SDR Blog v3/v4)

The bias tee supplies 5 V (~180 mA) on the SMA centre pin to power an inline LNA. Requires librtlsdr >= 2.0.0 (included in the Arch rtl-sdr package):

with RTLSDR() as sdr:
    sdr.set_bias_tee(True)    # power the LNA
    sdr.set_center_freq(137_620_000)
    sdr.set_sample_rate(2_400_000)
    iq = sdr.capture_iq(2_400_000 * 10)   # 10 s capture
    sdr.set_bias_tee(False)   # disable before disconnecting the LNA

Always disable the bias tee before removing the antenna or LNA to avoid back-feeding the RTL-SDR input with the bias voltage.


Device enumeration

If multiple dongles are attached, select by serial number for reproducibility:

# List all attached devices
for dev in RTLSDR.find_devices():
    print(dev)
# {'index': 0, 'serial': '00000001', 'name': 'Generic RTL2832U OEM'}

# Connect to a specific dongle
with RTLSDR(serial="00000001") as sdr:
    print(sdr.identify())

Set a permanent serial number with rtl_eeprom -s 00000001 (from the rtl-sdr package).


API reference

RTLSDR(serial=None, device_index=0, ppm_correction=None, sample_rate=2_400_000, gain='auto')

Opens the device. Raises RTLSDRError if the serial is not found. Raises RTLSDRBusyError if another process already has the device open.

Method Description
find_devices() Class method — list attached devices as [{'index', 'serial', 'name'}]
identify() Return device info dict: serial, tuner_type, valid_gains_db, sample_rate, center_freq, gain, ppm_correction
set_center_freq(freq_hz) Set tuning frequency; PPM correction applied automatically
set_sample_rate(rate) Set IQ sample rate in S/s (typical: 250_000 – 3_200_000)
set_gain(gain_db) Set gain in dB (snapped to nearest valid step) or 'auto'
set_bias_tee(enabled) Enable/disable bias tee (RTL-SDR Blog v3/v4 only)
capture_iq(num_samples) Capture a block; returns complex64 numpy array
power_spectrum(iq, rbw_hz) Welch PSD; returns (freq_hz, power_db) float32 arrays
scan_activity(threshold_db, num_samples) Quick scan; returns list of detected signals
stream_iq(block_size) Generator yielding complex64 blocks; call stop_stream() when done
stop_stream() Stop streaming and join the reader thread
save_calibration(ppm) Save PPM correction to ~/.rtlsdr_cal.json
close() Stop streaming and close device

Valid sample rates

The RTL2832U supports a continuous range from ~250 kS/s to 3.2 MS/s. Values outside 900 kS/s – 3.2 MS/s may have increased sample loss on some systems. Common choices:

Rate Bandwidth Typical use
250_000 200 kHz Narrow-band FM, single ISM channel
1_024_000 ~800 kHz ACARS, P25 channel bank
2_000_000 ~1.6 MHz ADS-B (1090 MHz)
2_048_000 ~1.6 MHz DVB-T, general use
2_400_000 ~2.0 MHz Weather satellites, wideband ISM
3_200_000 ~2.6 MHz Maximum reliable rate

Power spectrum note

power_spectrum() returns power values relative to the peak bin (0 dB = peak). The values are not calibrated absolute dBm — the RTL-SDR has no calibrated power reference. Use the SSA3032X Plus for absolute amplitude measurements; use the RTL-SDR for signal detection, classification, and protocol decode where relative power and frequency are sufficient.


Hardware connection notes

The RTL-SDR Blog v4 appears as a single USB device (RTL2832U, USB VID 0x0bda PID 0x2838 or variant). Only one process may open it at a time; RTLSDR() raises RTLSDRBusyError if it is already in use.

For ADS-B (1090 MHz), a dedicated 1090 MHz bandpass filter (FA 1090 MHz or similar) before the LNA significantly reduces out-of-band noise and improves decode rate.

For weather satellites (137 MHz), a V-dipole antenna (~54 cm elements, 120° spread) is optimal — omnidirectional in elevation, no tracking required for LEO passes.


License

GPL-3.0-or-later

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rf_bench_drivers_rtlsdr-0.1.2.tar.gz (25.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rf_bench_drivers_rtlsdr-0.1.2-py3-none-any.whl (23.8 kB view details)

Uploaded Python 3

File details

Details for the file rf_bench_drivers_rtlsdr-0.1.2.tar.gz.

File metadata

  • Download URL: rf_bench_drivers_rtlsdr-0.1.2.tar.gz
  • Upload date:
  • Size: 25.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for rf_bench_drivers_rtlsdr-0.1.2.tar.gz
Algorithm Hash digest
SHA256 cfcf47865fc9132503be4b23e7d76ff4aecbb6c7e8e0bba032887a14006ef3d7
MD5 a46936aa14a5061d54c7524c0a5b7fbc
BLAKE2b-256 12154d461a6dbf5dd43fed35c6cf874db1e0eed9ee15dd9d6324344974003e7b

See more details on using hashes here.

File details

Details for the file rf_bench_drivers_rtlsdr-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for rf_bench_drivers_rtlsdr-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 400071da536a9027b99e96e932b46d9373ab6c1ba4eeb9d092a72c5e403e09b1
MD5 2ab44f9eb8e46ffba0cd808791ce5393
BLAKE2b-256 16046eaf7866aa1873c0cfd2929c2a8bf43f3d304fd2aa3e44514d68b765d5e1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page