rx_tools: command-line SDR tools for RTL-SDR, bladeRF, HackRF, and more (rx_fm, rx_sdr, rx_power) — ported from librtlsdr
librtlsdr provides quite a few useful command-line tools for RTL-SDR dongles (including rtl_fm, rtl_sdr, and rtl_power). rx_tools is derived from these popular tools, but adds support for other more powerful SDR hardware, including the bladeRF and HackRF, thanks to the SoapySDR abstraction layer.
As they are intended to be a mostly drop-in replacement for librtlsdr’s tools:
- rtl_fm → rx_fm
- rtl_sdr → rx_sdr
- rtl_power → rx_power
…existing documentation and guides on using rtl_* should be still useful when using rx_*, but with added hardware compatibility and other enhancements. In this article I’ll provide some background and motivation for these tools, and at the end demonstrate a couple interesting use cases.
If you just want to try out the new tools now, check out https://github.com/rxseger/rx_tools or skip to the end for examples, otherwise if you’re interested in more context, read on.
The Pioneers: GNU Radio, USRP
GNU Radio was founded in 2001, and one of it first developers created Ettus Research and USRP. Featured in Wired in 2006, GNU Radio Opens an Unseen World, although software-defined radio has arguably existed in some form since the 1970s, GNU Radio and the USRP were clearly breaking new ground in building the foundation of modern software-defined radio:
Their original goal was to receive digital television (something I also experimented with, with some success: Receiving ATSC digital television with an SDR), they succeeded and then some. However, the USRP is quite an expensive device, prices ranging in the $1000's of dollars, out of the range of casual hobbyists.
The Affordable: RTL-SDR, librtlsdr
Then came an unexpected development: the RTL-SDR. The designers of the Realtek RTL2832U, primarily used as an inexpensive ($5–20) DVB-T television tuner, built-in a software-defined radio mode originally for receiving FM and DAB radio broadcasts. When developing a Linux driver for this chip, Eric Fry discovered the SDR mode and the rest is history.
librtlsdr was born, unlocking the true potential of the device, and bringing SDR into the hands of even the most casual hobbyist. Although more limited than say the USRP in many ways, the RTL-SDR is quite a powerful peripheral, a good start for those new to SDR. For more background and a more comprehensive introduction, see Luaradio’s New to SDR?.
As a low-cost device, the RTL-SDR is commonly used on low-cost single-board computers especially the Raspberry Pi, also priced in the tens of dollars, or personal computers built on the Intel Atom. With not much computing power to spare, command-line tools are preferred, hence librtlsdr ships with several, most usefully: rtl_fm, rtl_sdr, and rtl_power.
You may have noticed a glaring gap in cost, orders of magnitude, of the these two SDRs discussed above:
- $1000’s: USRP
- $100’s: ???
- $10's: RTL-SDR
What lies in between? There are several products, ranging from the low to high hundreds of dollars. The two I am familiar with and will be using in this article are the Great Scott Gadget’s HackRF One and Nuand’s bladeRF x40 (brief comparison of the two here: Upgrading from HackRF One to bladeRF x40), but this is an interesting emerging area, expect more developments as the high-end and low-end SDRs converge.
Yet there is one problem: software.
A Survey of SDR Interfacing Libraries
The great promise of SDR is “it’s just software” — with sufficiently capable hardware, you can write radio software to do practically anything. However, not all software is made equal.
SDR applications must interface with the hardware by some mechanism. Software built on GNU Radio, such as gqrx, commonly uses the gr-osmosdr osmocom abstraction layer, supporting a multitude of devices. RTL-SDR, HackRF, bladeRF, you name it.
Not all software is as compatible, some is written hardcoded to use libraries from specific SDR vendors. If you’re lucky there might be a fork for your SDR. As I covered in SDR calibration via GSM FCCH using Kalibrate and LTE-Cell-Scanner on RTL-SDR and HackRF, the “kalibrate” GSM frequency calibration tool has at least four separate forks: kalibrate for USRP, kalibrate-rtl, kalibrate-hackrf, and kalibrate-bladeRF — one for each SDR.
JiaoXianjun/LTE-Cell-Scanner has support for all of RTL-SDR, HackRF, and bladeRF, but it accomplished this feat by linking against all three libraries: librtlsdr, libhackrf, libbladerf. The developer had to specifically code for each device individually, a lot of work and not scalable to new devices.
The excellent dump1090-mutability ADS-B receiver uses librtlsdr directly (just like rtl_fm/rtl_sdr/rtl_power), so it only supports RTL-SDR. More details in Flight tracking with ADS-B using dump1090-mutability and HackRF One on OS X, but there’s also SDRplay/dump1090 for the SDRplay RSP, and dump1090_sdrplus for the RTL-SDR, HackRF, AirSpy, and SDRplay (but not the bladeRF).
Messy, isn’t it?
There have been efforts to improve this situation. spcutler/SDRIO was announced March 2014 in SDRIO: A hardware abstraction layer for SDR devices, but it has been inactive for two years. gr-osmosdr is a pretty good abstraction layer, but focused on GNU Radio.
Even within the librtlsdr library there is fragmentation: ostensibly git://git.osmocom.org/rtl-sdr.git is the official version, but I also found and submitted pull requests to https://github.com/librtlsdr/librtlsdr (note master vs development branch), not to mention https://github.com/keenerd/rtl-sdr/, which has many tool enhancements from the original author of rtl_fm and rtl_power, Kyle Keen.
Aside: Better Compatibility through Unix Pipes
The power of Unix pipes can help improve the SDR interfacing compatibility situation (indeed, csdr is solely focused on piping). dump1090 can read from stdin using “--ifile -” arguments, from any source. It can be piped from HackRF ‘-’ stdin/stdout using hackrf_transfer, with PR-261 Add support for transmitting/receiving from stdin/stdout.
rtl_tcp can listen on a TCP port, gr-osmosdr device flags rtl_tcp=127.0.0.1:1234 can connect to to it; rtl_sdr ‘-’ pipes to stdout, rtl_fm (see rtl_fm guide) can pipe sox for playing the demodulated audio, or multimon-ng for digital signal decoding.
Pipes are a powerful abstraction, but a full compatibility API offers more flexibility. For example, you may want a scanning tool to tune to various frequencies, which wouldn’t be possible over a Unix pipe dumping raw I/Q samples. A proper clean abstracted API is desired, fortunately there is one:
SoapySDR: a clean interface
It provides a vendor-neutral interface, including to librtlsdr, libhackrf, libbladerf, uhd, and even gr-osmosdr. Whatever SDR you have, it is most likely supported, or if not it support could be added by a plugin. SDRplay, Red Pitaya, S9C-ExtIO, EVB7, NovenaRF, Lime Suite LimeSDR, …
Porting notes: rtl_fm, rtl_sdr, rtl_power
All said and done, I set out to port a subset of librtlsdr tools to SoapySDR in order to add support for additional devices, expanding compatibility beyond the fabled RTL-SDR to at least the bladeRF and HackRF.
My inital port to SoapySDR was relatively straightforward, making the appropriate API changes, such as:
- rtlsdr_dev_t → SoapySDRDevice
- rtlsdr_open(&dev, index) → dev=SoapySDRDevice_make(&args)
- rtlsdr_close → SoapySDRDevice_unmake
- rtlsdr_set_center_freq → SoapySDRDevice_setFrequency
- rtlsdr_set_agc_mode → SoapySDRDevice_setGainMode
- rtlsdr_set_and_get_tuner_bandwidth → SoapySDRDevice_setBandwidth*
rtlsdr_read_async() on a different thread was replaced with:
Then I renamed rtl_* to rx_*, since the tools are no longer tied to RTL-SDRs.
Supporting the HackRF out-of-the-box required tweaking the default gain settings. HackRF has three gain components, which can be set individually, or with setGain() distributed amongst all of them. TODO: cli flag to set gain components, rtl auto-gain
Supporting the bladeRF required switching to the CS16 format, from CU8/CS8, to support the 12-bit ADC. This is a good idea regardless, because both rtl_fm and rtl_power internally converted from 8-bit unsigned to 16-bit signed integers. Complex = pairs of I/Q values, each 16-bit or 8-bit (or …):
- CS16 = complex signed 16-bit
- CS8 = complex signed 8-bit (+127 to convert from CU8; HackRF native)
- CU8 = complex unsigned 8-bit (native RTL-SDR)
To allow the tools full use of stdout — for rx_fm, the demodulated FM audio, for rx_sdr, the raw I/Q samples, for rx_power, the CSV logging — I redirect stdout to stderr, so any log messages from SoapySDR or drivers will not interfere with the output.
Another tweak for bladeRF: continue on overflows, seen at startup with bladeRF but quickly recovers.
For rx_power supporting HackRF, a more drastic change was needed: 64-bit frequencies. rtl_power used 32-bit signed integers (uint32_t, or int on my system) for frequency calculations, which were limited to 2³¹ GHz (~2.1 GHz). Expanding to 64-bit signed allows 2⁶⁴ = ~9 exahertz, should be enough for anyone unless you are using software-defined x-rays or gamma rays.
Not ported, other rtl_* tools & alternatives
librtlsdr includes a handful of other tools, which I did not port:
rtl_adsb — haven’t tested, seems that dump1090 is more popular (although it also uses librtlsdr, could be worth porting as well).
rtl_ir, rtl_eeprom, rtl_test —are RTL-SDR specific.
Now that the porting is out of the way, and we have these shiny new rx_fm/rx_sdr/rx_power tools, what can we do with them?
rx_fm + bladeRF: police scanner for UHF
kmkeen’s rtl_fm guide describes a number of use cases for the tool, also applicable to rx_fm. As a simple test, I used rx_fm with a bladeRF as a police scanner for the UHF band.
This use case admittedly does not showcase the advantages of bladeRF very well (12-bit ADC, full duplex TX/RX, FPGA, etc.) but it’s a decent example of the device-independent compatibilities of rx_fm.
Based on the rtl_fm example, I also merged the ‘-E wav’ support from keenerd/rtl-sdr. This adds a .wav (RIFF) file header, obviating the need to pass the audio format details to sox. uhf_scan_b.sh is simply:
./rx_fm -E wav -M fm -s 12k -l 25 -f … | play -t wav -
Frequencies omitted here, but they are passed in as multiple -f arguments. Use a site such as RadioReference to find the FM public safety frequencies available in your area. Scan results after a period of multiple hours:
rx_sdr: raw I/Q data for processing in GNU Radio or other tools
I haven’t seen many use cases for rtl_sdr, but it can be useful to capture raw samples for later analysis; rx_sdr is now the device-independent analogue. Instead of using e.g. hackrf_transfer, you can use rx_sdr, for any SDR.
The new ‘-F’ option specifies the output format, currently: CU8, CS8, CS16 (described above), or CF32. rx_sdr defaults to CU8 for drop-in compatibility with rtl_sdr. GNU Radio’s native format is CF32, complex float 32-bit. So to capture an FM broadcast station, you could run:
./rx_sdr -f 97.7M -F CF32 /tmp/fm
then use the “File Source” in GNU Radio (type “Complex”):
and the samples captured through rx_sdr are processed by GNU Radio.
rx_power + HackRF: scanning the spectrum up to 6 GHz
At last, we arrive at what I wanted to do all along, which initated this porting project: scan the full 6 GHz spectrum with a HackRF using rtl_power. With rx_power, this is finally possible.
To scan a large range of spectrum, generating CSV output:
./rx_power -f 0.1M:6G:1M -e 24h hackrf-power-1m.csv
Then convert to a .png using rtl-sdr-misc/heatmap.py:
python ~/rf/rtl-sdr-misc/heatmap hackrf-power-1m.csv hackrf-1m.png
Here’s what we see:
The image is super wide, but zooming in some interesting emissions can be found. 6 GHz is a significant expansion of the range observed with an RTL-SDR dongle up to about 1.7 GHz in Finding interesting signals: heat maps and callsigns. Some highlights:
The (noise?) on the right extends to about 2.75 GHz. There is a regular repeating pattern 3.5–3.6 GHz, possibly interference?
WiMax can operate at 3.5 GHz, but the regular pattern may suggest something else, unclear. If anyone has any ideas let me know. Then there’s this above 4 GHz, in the IEEE C-Band (4–8 GHz), possibly satellites?
The shifting frequency suggests Doppler shift (but at a 1 MHz scale? maybe something else). There are various bands with weaker power at 4.72 GHz, 4.9 GHz, 5.2 GHz, 5.1 GHz (two strong bands), 5.16 GHz, 5.23 GHz:
What are these? Harmonics, artifacts, nonsense? Or something real?
It takes until 5.660 GHz to reach a known signal, faint but noticeable:
This is 802.11ac Wi-Fi, operating on channel 132, in the Unlicensed National Information Infrastructure (U-NII) 5 GHz World band.
If anyone wants to run rx_power with their SDR and post their resulting heatmap, feel free to, I’d be interested particularly in the GHz range.
rx_tools building on SoapySDR for bladeRF and HackRF and RTL-SDR support is only the beginning, other SDRs ought to be supported, possibly requiring small tweaks, or they may already work — I’ve only tested with these three devices, but welcome bug reports from users using others.
Another benefit of rx_tools is that it is separated from the librtlsdr library, allowing it evolve independently. I have merged several patches from various librtlsdr forks, including my own small fixes/enhancements, and welcome any pull requests for anyone who wants to contribute further, making the command-line tool suite even better.
Even if you only have an RTL-SDR, you can still use rx_tools and benefit from the continued development, and continue using them once/if you decide to upgrade to a more powerful and sophisticated SDR device.