SDR first project: initial setup, node-hackrf, GNU Radio on Linux, OS X, RPi 3 w/ FM tuner

Ordered from Sparkfun:

Screwed on the antenna to the port labeled “antenna”, inserted the included USB cable, plugged into a port on a Mac with OS X 10.11. No devices appear on the desktop or in /dev by default; software needed to drive HackRF.

A brand new HackRF One. Picture from @fd0

Why HackRF? There are many software-defined radio devices to choose from, from the $20 RTL-SDR to ~$1000+ USRP. The cheapest devices generally have a low frequency range and low RF bandwidth: the RTL-SDR can pickup only 3.2 MHz from 22 MHz to 2.2 GHz. Ettus B200 USRP has a much greater bandwidth of 61.44 MHz and wider frequency range of 70 MHz to 6 GHz, but is quite expensive for someone just getting started.

The HackRF is a good compromise (at least until the upcoming LimeSDR): only $300 for a 20 MHz bandwidth and frequency range of 10 MHz to 6 GHz (advertised range, but may be able to actually tune slightly lower and higher). Even though there are technically better devices at higher cost, there is plenty one can do with the mid-range HackRF.

node-hackrf

First tested with Node.js as I had it already installed on my system and am comfortable with the JavaScript development environment. There is a hackrf module on NPM, developed by @mappum and @mafintosh. Install:

npm install hackrf

but it fails trying to download prebuilt native code:

> hackrf@3.1.0 install /private/tmp/node_modules/hackrf
> prebuild — download
prebuild WARN install Prebuilt binaries for node version 0.12.7,1.0.4,1.8.4,2.4.0,3.0.0 are not available
prebuild ERR! configure error
prebuild ERR! stack Error: Invalid version number: 0.12.7,1.0.4,1.8.4,2.4.0,3.0.0

3.1.0 is actually io.js, a fork of Node.js since merged back into what is now Node v4 and later

Downgrading to 3.1.0 works, but using the latest version of Node.js — 6.2.1 at the time of this writing — would be preferable. Fortunately it is easy to fix, submitted pull request #4 to node-hackrf. With these changes, the Node.js module can connect to the HackRF device and receive transmissions:

node-hackrf $ npm install
node-hackrf $ node bin.js --rxgraph

Found 1 HackRF devices
HackRF version is 2014.08.1

#######################
#########################################
#######################################
#####
############
########
############
###############################################################################
###################

Now that we are receiving data, what can we do with it?

hackrf-stream provides a convenient Node stream API interface. The only current dependent of hackrf-stream published on NPM is hackrf-spectrograph, which shows a nifty ASCII art (using babar) FFT (using stft):

My unofficial hackrf-spectrograph repository: https://github.com/rxseger/hackrf-spectrograph

Now the next step would be to tune and demodulate a signal, my goal is receiving FM broadcast radio (the so-called “hello world” of SDR).

Here the solution with Node.js is non-obvious, so I’ll take a break from Node.js and turn to another, well-developed SDR platform instead: GNU Radio.

GNU Radio on Linux

I highly recommend Michael Ossmann’s online course Software Defined Radio with HackRF for an introduction to SDR with the GNU Radio toolkit.

For expediency I first tried to run GNU Radio under Linux in a virtual machine via VirtualBox on OS X. There is a GNU Radio Live DVD with everything needed preinstalled.

Plug in the HackRF, then attach it to the guest OS by selecting it under Devices > USB > Great Scott Gadgets HackRF One [0100]. Add an OsmoCom source and WX GUI FFT Sink, as described in the course. This was not successful — I could not get anything out of the HackRF over the VM, likely due to bandwidth limitations in virtualizing USB.

Time to install Linux natively. To keep it separated from OS X in the internal SSD of the iMac, I installed on a USB external drive using Mac Linux USB Loader (not UNetbootin). Booted with ‘nomodeset’ kernel option to fix missing video on the iMac Retina, installed gnuradio and gr-osmosdr packages, then executed gnuradio-companion from the command-line.

Follow the steps in lesson #1 to create a software FM radio. Here’s my flow graph:

FM radio receiver

And the FFT when receiving FM broadcast radio over the air:

With the Audio Sink, you can hear these transmissions

Success!

GNU Radio on Linux works well with the HackRF, but running Linux on this iMac is an awkward experience, for various reasons. So I’ll run it under Mac OS X. Easier said than done, but it is possible.

GNU Radio on OS X

Install Homebrew, and run `brew install gnuradio`. Installation succeeds, and then you can run `gnuradio-companion` from the command-line.

But there’s a problem: missing the osmocom source/sink. Without it we cannot talk to the HackRF, making GNU Radio not as useful. On Ubuntu, it installed in a separate gr-osmosdr package, try it with brew:

brew install gr-osmosdr
Error: No available formula with the name “gr-osmosdr”

Found this “tap” for brew: https://github.com/titanous/homebrew-gnuradio

It is marked as unmaintained. Try it anyways, sure enough, it fails. Review the forks, merge changes from jjeising and update for OS X 10.11 and GNU Radio 3.7.9.1. You can use my fork with these changes incorporated, follow the complete instructions at https://github.com/rxseger/homebrew-hackrf to install gr-osmosdr.

Test by running osmocom_fft from the command-line:

GNU Radio & gr-osmosdr on OS X 10.11.5 installed via Homebrew using rxseger/homebrew-hackrf

Load the FM receiver previously developed in Linux, and it works as expected. Now I have this SDR running on my desktop Mac computer, but why not run it on a smaller single-board computer instead?

GNU Radio on Raspberry Pi 3

Starting with this kit also from Sparkfun:

Unboxed the power adapter, case, board, SD card, and put it together. Inserted the preinstalled SD card into the SD slot on the board, plugged in an Ethernet cable and power. The system received an IP address from the DHCP server on my network (using the hostname recovery), I could ping it by no ports were open.

Connected the included FTDI 232R USB breakout board, to the wedge, and then to the GPIO port over the ribbon cable. Plugged in a USB cable, the Mac recognized the port and it appeared as /dev/tty.usbserial-AH03FLVX, attempted to connect with:

screen /dev/tty.usbserial-AH03FLVX 115200

but there was no output. What’s going on?

Ideally, one could attach a USB keyboard and HDMI video display to interact with the console. But I had neither handy.

Turns out, the default install is NOOBS, a graphical installer to select which OS you want. Raspbian does support headless operation, but you need to install it. Removed the SD card, inserted it into the adapter, plugged into the Mac, and imaged the latest Raspbian OS.

Now it boots up with the DHCP node name raspberrypi, with SSH enabled: username pi, password raspberry, as expected. Complete the headless setup described here, then install GNU Radio:

sudo apt install gnuradio
sudo apt install gr-osmosdr

But our FM radio receiver created in the previous section does not work: top_block reports errors about the window size. Turns out apt installed GNU Radio 3.7.5, instead of the latest 3.7.9.1 which we had with Homebrew on OS X. Fortunately the compatibility issue is easy to fix: double-click on the top block, and enter “1280, 1024" in the Window Size field, matching the default with a new flowgraph.

Fixing GNU Radio 3.7.9.1 to 3.7.5 compatibility on Raspberry Pi

The flow graph is now ready to run.. but it fails with another error:

wx._core.PyAssertionError: C++ assertion ok failed at ../src/unix/glx11.cpp(442) in GetGLXVersion(): GLX version not found

Solution, from lukbe: sudo apt install libgl1-mesa-swx11

Disable the audio sink for now, only showing the FFT, and run. We see something, but the slower Raspberry Pi CPU cannot keep up with the 20e6 sample rate, hence the “O” overflows:

Raspberry Pi 3 CPU: 1.2 GHz quad-core ARM Cortex A53, overflowing GNU Radio

Experimentation reveals a modest 5e6 samp_rate is achievable.

Re-enabling the audio sink reveals another error:

audio: using audio_alsa audio_alsa_sink[hw:0,0]: set_channels failed: Invalid argument

For some reason, the default device for the Audio Sink is not good enough. Double-click the block and enter “hw:0,1” in the Device Name field. Execute the flowgraph…and:

Getting closer, but 5M osmosdr samp_rate is still too much for this RPi 3

The audio is choppy and the FFT display lags. “OOOaUO” indicates the system cannot keep up, we are asking too much of it. htop confirms the processors are nearing their capacity. To lighten the load, reduce the samp_rate to 1e6. At last, the FM radio station is audible.

But its not a complete success. The WX slider control (and seemingly the entire GUI) is non-responsive during reception, presumably the GUI updating in addition to the digital signal processing overburdens the ARM CPU, even with GNU Radio’s Volk machine optimizations, neon_hardfp_orc on the Raspberry Pi for advanced SIMD NEON instructions.

A more practical RPi FM radio could use some other means to tune than GUI widgets on the console. Fortunately, the device includes a general-purpose I/O port and the starter kit includes a few buttons, all we need to make a physical digital tuner.

Using GPIO for volume/channel buttons on the Raspberry Pi

Wire up the Pi Wedge and plug it into the Raspberry Pi GPIO port and breadboard, insert each of the four push buttons, and jumper them to the GPIO ports as follows:

Wiring up the buttons to Raspberry Pi GPIO: G22, G27, G18, G17 on one side, and 3.3V on another

Pressing the buttons should now show a 1 instead of 0 for GPIO. 0–3 in `gpio readall`. These correspond to “Pi 3 physical pins” 11, 12, 13, and 15 and can be accessed with the Python RPi.GPIO module in BOARD mode:

import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setup([11, 12, 13, 15], GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
while True:
 print GPIO.input(11), GPIO.input(12), GPIO.input(13), GPIO.input(15)

Now we step away from GNU Radio Companion and edit the generated Python directly. Add to the generated source (default top_block.py) code to create a new thread using the threading module, poll the GPIO pins, and call set_audio_gain and set_channel_freq on the top block, as appropriate.

While I was at it, removed the WX GUI so this program can run headless. No need to use VNC anymore, it can be executed over SSH:

pi@raspberrypi:~/fm $ python top_block.py
linux; GNU C++ version 4.9.1; Boost_105500; UHD_003.007.003–0-unknown
Using Volk machine: neon_hardfp_orc
gr-osmosdr 0.1.3 (0.1.3) gnuradio 3.7.5
built-in source types: file osmosdr fcd rtl rtl_tcp uhd miri hackrf bladerf rfspace airspy
Using HackRF One with firmware 2014.08.1
gr::log :INFO: audio source — Audio sink arch: alsa
Press Enter to quit:

and use the buttons to change the volume and channel. You can find the final script here:

Like what you read? Give R. X. Seger a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.