Playing with keyfobs and baudline is a lot of fun, now lets try something more complex where the output data has more meaning (even if we decode the keyfob data, it's probably not going to show anything logical other than a big random number - feel free to try though!)
The transmitter in this case is a junky 433mhz transmitter meant for use with Arduino, which runs about $2:
We know this one transmits at 433mhz, so lets do a capture with the HackRF at 435MHz:
$ hackrf_transfer -r 435MHz-ASK-HACKRF-CW-8M.iq -f 435000000 -s 8000000
When we load this into Baudline (see previous post about this), we get:
Who remembers their CW?
GRC allows you to build flow graphs with a GUI. They're then compiled to python (and can be run independently of GRC). Writing code using the GNU Radio blocks can achieve the same thing, but GRC has a lot of helpful features to make it simpler for GNU Radio newbies (of which I'm definitely one). One downside is the GRC UI can sometimes make it challenging to find block - if you're having trouble finding something in the list, you can start typing the name and it should search for it.
Step one is to load our log file into a format GNU Radio can use easily. The HackRF logs are IQ data pairs, stored as unsigned 8 bit integers (you can read more about IQ, or Quadrature Sampling, here). GNU Radio components generally want complex IQ data, so we need to transform the file to that (as a big ugly image that breaks the layout entirely on the blog page):
You can get the grc xml file here.
A lot of things are going on here, and we haven't even started trying to do anything interesting!
This uses the standard "File Source" component to load the file, then converts UChars (unsigned 8 bit values) to floats in the "UChar to Float" block. The "Deinterleave" stage splits the single stream of IQIQIQ data into I and Q channels, then recombines them as a "complex" stream via "Float to Complex", which most GNU Radio stages expect.
The "Add Const" stage then re-centers the data around 0; since it uses unsigned ints, with a range of 0-256, the HackRF samples center around 127. (I had to bug Mike Ossmann about this - all the more reason to go sign up for the HackRF kickstarter - if it makes the stretch goal, we get videos of GNU Radio courses!)
Finally, the "Throttle" stage does, perhaps, the obvious - throttles the throughput to 8M. Without this stage, it could easily swamp the system it is running on. In general I've found it makes sense to throttle to the rate of the capture, because there can be odd interactions between rate and bandwidth otherwise - with IQ samples, the rate and bandwidth are linked.
The throttle stage uses the variable throttle_rate, found at the top of the image. This lets you easily set the rate for different capture, with one significant gotcha: GRC doesn't represent numbers in a format it can actually use! GRC can accept raw numbers, or scientific notation (or hex, or octal, or any other format Python supports), but represents them as a human readable value which is NOT accepted. In this case, 8000000 or 8e6 are acceptable values, however 8M is not. Go figure.
So, now we have the file loaded in a format we can use. What next?
The first step is to look at our signal - we can direct the output of throttle into a FFT sink like this:
And get a view of our signal:
Where we see our DC spike at our capture frequency (435MHz), and some of our signal, around 433.8MHz.
Now, look at the setup of the FFT sink - by default GNU Radio doesn't know (and doesn't care) what your center frequency is. Unless you tell it otherwise, your center frequency is 0. The signal would then be at -1.2MHz. Nothing really cares about this, but I like to know where things really are, so I like to configure the FFT to be accurate:
In this case, the "Sample Rate" and "Baseband Freq" are set to variables, "samp_rate" and "capture_freq". "Sample Rate" should match the input sample rate (in this case, 8M, the value we throttled to) or else the FFT won't update properly.
We use variable blocks in GRC, so:
The Frequency XLating Filter allows us to do three things at once:
- Shift the center frequency of the signal. We'll use this to "scroll down" to our desired signal.
- Apply a pass filter. This filters out parts of the signal we don't care about, so when we're measuring things later on, we're only measuring our signal and not some other radio noise.
- Decimate the signal. Contrary to the actual definition of the word, in SDR terms, Decimate simply means Divide. This reduces the bandwidth and sample of the signal, which reduces the amount of processing power need to handle it. Since this reduces the sample rate, we'll have to remember this in future blocks!
Here we set the decimation using variables again - they're so much more convenient than trying to change all the dependent modules later. To get the decimation value, we divide the incoming sample rate by the target rate - in this case, 50KHz, a relatively arbitrary value - it's wide enough to encompass our signal, and we don't have to fiddle too much because our signal is VERY strong in this sample. In reality we could probably use a much smaller value here. We have to cast the result to an integer, otherwise GRC gets angry; Since this is really all Python under the covers, we use the Python integer cast, int(...).
Under "Taps", we use the variable firdes_tap - more on this later. This performs the filtering to isolate our signal.
Finally under "Center Frequency", we tell it to shift - again using variables. We want to shift down to our signal, so we need to shift by about 1.2MHz, or 435MHz - 433.828MHz. Since we're shifting down, we make it negative.
I use GNU Radio 3.6 - the latest cutting edge release is 3.7, which has changed some things around. Specifically in this case, the "Center Frequency" is now treated as the "actual" center frequency. If you're on GNU Radio 3.7, remove the negative sign here. This, and other changes, can be found here.
[ EDIT 09/02/2013 - Having updated to GNU Radio 3.7, I'm not sure what the changelog really indicates should be done - a negative offset as shown in the example script works fine, and a positive does not. ]
The firdes_tap variable defines our low-pass filter. The key parameters here are:
- Gain (1) - We're not adding any gain into the signal.
- Sample rate (samp_rate) - This has to match the sample rate coming in, in this case 8M
- Cutoff frequency (2000) - We're defining the lower cutoff at 2KHz
- Transition band frequency (20000) - Set the transition band of the filter. This should be less than half of the sample rate (in our case our decimated sample rate is 50k, so 20k is reasonable)
After passing through the filter, decimation, and re-centering, we can put the signal back into a FFT display and see something like this:
Notice that the signal covers much less bandwidth, and is silenced except around our target area. Playing with the values in the low_pass filter will yield different results, and you can learn a lot more about lowpass filter setup here.
Remember as well to set up your FFT plot properly:
Specifically, since we have decimated our signal to our target rate, it is now only 50KHz. We have also changed the center frequency of the signal, so we set both "Sample Rate" and "Baseband Freq" to our target variables.
Now we have a lower-bandwidth, centered, filtered, ASK / OOK signal. What can we do with it?
ASK and OOK are basically "high volume for one" and "low volume for zero". They vary the amplitude (hence "Amplitude Shift Keying"). We can detect this in GNU Radio by measuring the magnitude of the signal:
The "Complex to Mag" converts a complex IQ signal to a simple magnitude. Because in our sample our signal is so strong, and we've filtered it so that only our signal is being looked at, we can do a simple magnitude calculation on it. If we had other stray signals still in the sample, we wouldn't be able to take the sample route.
Notice how the "Out" tab of the "Complex to Mag" block is orange instead of blue - this indicates the output is a Float, and that blocks which connect to this must expect a float.
We can set the scope sink to expect a float via the "Type" dropdown menu.
Running our GRC now gives us the scoped output of the magnitude of the signal. Using the mousewheel to scroll out the scope time range a little, we see:
Which looks gratifyingly like the signal we saw in baudline back in the beginning, only we've constructed it by isolating our desired signal, focusing in on it, and converting it to magnitude counts.
You can get the sample data file here, and the GRC file here.
Next post, we'll focus on taking this signal from GRC and processing it back into data.
Thanks to Mike Ossmann, everyone in #hackrf on Freenode, and everyone who helped proof-read this and offered advice on the post.
* Go here for Part 1, looking at data from the HackRF in Baudline