HackRF Shell: It’s alive! Wha ha HA HA HA!

The shell is functioning, including RX functionality (not TX). Currently the process looks like so:

christopher@nightshade:~/Repos/hackrf-shell$ ./hackrf-shell 
GNU Guile 2.2.3
Copyright (C) 1995-2017 Free Software Foundation, Inc.

Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
This program is free software, and you are welcome to redistribute it
under certain conditions; type `,show c' for details.

Enter `,help' for help.
scheme@(guile-user)> (load "hackrf-shell-lib.scm")
scheme@(guile-user)> (define d (hackrf-open))
scheme@(guile-user)> (hackrf-sensible-defaults d)
scheme@(guile-user)> (define c (hackrf-cb-rx-to-file "out.bin"))
scheme@(guile-user)> (hackrf-start-rx d c)
scheme@(guile-user)> (hackrf-stop d c)

hackrf-sensible-defaults is simply an alias for

(hackrf-set-freq d 99500000)
(hackrf-set-sample-rate d 8000000)
(hackrf-disable-amp d)
(hackrf-set-lna-gain d 16)
(hackrf-set-vga-gain d 16))

The hackrf-start-rx procedure must receive a callback function which will handle the data each time a block of data is received through libhackrf. The callback function receives a pointer object to the data buffer, which can be converted to a bytevector with pointer->bytevector, and also the byte length of the buffer (an int). User can put together their own callback functions, though I was planning to add more for common use cases. Here are the ones included now:

(define (hackrf-cb-rx-to-stream out)
  (lambda (b bl)
    (let ([bv (pointer->bytevector b bl)])
      (put-bytevector out bv))))

(define (hackrf-cb-rx-to-file filename)
  (hackrf-cb-rx-to-stream
   (open-output-file filename)))

I could expand this by adding more such scheme functions for common use cases (e.g., FM demodulation or frequency analysis) with helper functions written in C to handle intensive mathematical operations (e.g., FFT). You can see here the power of Guile scheme, allowing user to load in any Guile scheme code they want, while I can provide helper functions coded in C to allow for maximum efficiency in critical mathematical operations.

The current code simply provides the raw 8-bit IQ data, but the user may want to receive the IQ data as 32-bit floating point numbers. This should be an easy function to add, which I will code in C to allow for SSE/AVX style compiler optimizations. It still remains to be seen if I will run into a performance bottle neck with the byte to float conversions, but since I do not need to do any IPC with hackrf-shell, I do not expect this to be a problem.

To confirm received data is not junk, I saved about 1 GB of IQ data to “out.bin”, converted the data to floating point values using Gnu Radio, and then ran the data through an FM demodulator Gnu Radio program. I was able to tune into several different radio stations recorded in the save data, such as a local country music station, and I did not hear any distortion or skips.

git clone git://git.librehacker.com/pub/git/hackrf-rkt.git

HackRF Shell Project

So, I got really far in the hackrf-stream project, with essentially everything working to control the HackRF and to manage the RX streams, and then ran into a fatal snag. Everything was based on the idea that I could use FIFO OS pipes to move the data from the C program to the processing program, but FIFO OS pipes turned out to be not fast enough for my needs. In the end, I was only able to move about 90% of the data live across the pipe, and profiling indicated the bottleneck was in system calls, rather than in number crunching. 90% is a lot of data, but I needed 100% going across.

So, I was faced with multiple options on how to proceed. On approach would be to switch instead to Shared Memory Objects for IPC. That I believe would have removed the i/o performance problem, but then all the control applications would need POSIX Shared Memory support, which rather went against the whole idea of having a simple, language agnostic interface.

Another idea was to abandon the idea of a simple, language agnostic interface, and just use POSIX Shared Memory Objects anyway. Unfortunately, Racket Scheme does not have a shared memory interface. Instead, it uses something called “places”, which is shared memory, but only allows sharing with other Racket programs. Chicken Scheme does have a POSIX shm interface, but I wasn’t sure if I really wanted to start using Chicken Scheme just for that reason.

Where I landed in the end was a different approach: embedding Guile Scheme inside my C program. This means when I start the C program, I’m dropped into a Guile Scheme shell, from which I can call whatever C functions I want (after writing the appropriate stubs). This takes me full circle back to what I originally had wanted to do with this project, but instead of using a Racket FFI, I am actually embedding Guile scheme inside the C application, with the C application providing all the core control and performance functionality. If it works, this should be the most fun approach.

I have renamed the project the “HackRF Shell” project.

git clone git://git.librehacker.com/pub/git/hackrf-rkt.git

Rotary Harmonograph with Dampening

To get some more artistic variety in the simulated rotary harmonograph output, we introduce a dampening, i.e., causing the pen and table to run down as though from friction. Also, we can nudge our simple ratio slightly, to add a small element of discord or imperfection.

Here is the adjusted function:

(define (rot ang/tunit t phase dampfact)
  (* (make-rectangular (expt dampfact t) 0)
   (expt ei
         (+ phase (* ang/tunit t)))))

With very fine control of the ratios, dampening factor, and number of samples, we can produce a variety of effects. (I’m guessing this would be much more difficult with a physical harmonograph.)

Rotary Harmonographs

Reading about rotary harmonographs in the Quadrivium, it was of course necessary for me to write scheme code to simulate one. This simulates a pen moving circularly in one direction, on a piece of paper moving circularly in the other direction. This, in turn, provides an interesting way to visualize harmonic ratios.

Formulas on the Internet focus on trigonometric functions for x and y axes. But it seems more appealing to deal with this using rotations of complex numbers. This is the basic math:

#lang racket
(require plot)
(define ei (exp 0+1i))
(define (rot ang/tunit t phase)
(expt ei (+ phase (* ang/tunit t))))
(define tau (* 2 pi))
(define (compnums ti samples f1 f2)
(let ([ang0 (/ tau f1)]
[ang1 (/ tau f2)])
(map (lambda (n)
(let ([t (* n ti)])
(+
(rot ang0 t 0)
(rot ang1 t 0))))
(range 0 (- samples 1))))

And we need some interface code to generate the plot:

(define (complex2vec c) (vector (real-part c) (imag-part c)))
(define (rotaryplot ti samples f1 f2)
(plot
(lines
(map
complex2vec
(compnums ti samples f1 f2)))))

Here, we have the basic circle generated by matching frequencies:

Next we do the octave (2:1):

We get quite a different plot if we have the pen turning in the opposite direction of the paper:

And the 4:3 ratio (known as a “Fourth” in music):

This would be more artistic if we introduced some dampening (friction) like in a physical harmonograph, and a small amount of discord. But, it is getting late, and I have to face that day job tomorrow, so that will have to wait for some other evening.