Read time 9 min
Radio waves are used to transfer information all around us. They are used in mobile phones, WLANs, all kinds of remote controls, traditional AM/FM radio stations, satellite communications, and numerous other places. Utilising radio technology is one of those things we take for granted in our everyday life, but we don’t necessarily know how everything works under the hood.
This blog post tries to explain the basic concepts on how to decode information from radio waves using Software-Defined Radio (SDR), with our satellite project “Reaktor Hello World” as an example case.
SDR: the basics
Software-defined radio as a concept means that some or all of the components in radio that have traditionally been implemented in hardware (e.g. mixers, filters, amplifiers, modulators/demodulators) are now implemented in software. This enables higher flexibility when experimenting, because you don’t need to change physical hardware components if you want to change, say, the modulation type. As a software engineer, it also helps that you can go in and read the source code to understand what’s happening. Upgrading software-defined radios after deployment is naturally easier than upgrading their hardware counterparts. If you know exactly what your needs are and they are not going to change, or if a high level of energy efficiency is a must, then traditional hardware radios are still probably the better choice. Check out this great and entertaining DEF CON talk from a couple years ago on what you can do with SDRs.
The cost of SDR hardware ranges from tens to thousands of dollars. The main features of SDRs are the frequency range, maximum bandwidth, whether is it only a receiver or also a transmitter, possible analog filters, and analog-to-digital converter resolution. You can find quite an extensive list of available SDR hardware here. In our project, we have mainly used HackRF Ones and USRP B200/B210 devices.
We prefer using USRPs because they work in full duplex mode and have better driver support for GNU Radio (TX burst length support), although they are considerably more expensive.
Transmitting data with modulation
In order to send data, the transmitting party must produce a carrier signal that the receiver can detect. The signal is usually sinusoidal and its frequency is known by the receiver. The encoding of data into the carrier signal is called modulation. We can modulate the signal by changing the amplitude, frequency or phase of the signal. As the receiver knows the frequency of the carrier signal, the data can be decoded by reversing the modulation process, i.e. demodulating the signal.
The simplest form of modulation is the amplitude modulation (AM) scheme known as On-Off Keying (OOK), where data is represented as the presence or absence of the carrier wave. On-off keying is most commonly used to transmit Morse code.
Data signal modulated with amplitude and frequency modulation. Source: https://en.wikipedia.org/wiki/Modulation
In our satellite’s UHF radio, we will most likely use the Gaussian Frequency-Shift Keying (GFSK) modulation. It’s one of the modulations supported by the satellite’s radio chip. The idea behind Frequency-Shift Keying (FSK) is that you encode different symbols as different frequencies. For example, a signal at 437.44MHz represents 1 and a signal at 437.46MHz represents 0. Then, you just quickly change between the frequencies based on your data. This results in a frequency modulated signal with center frequency of 437.45 MHz and deviation of 10KHz. You can also encode more than two bits at once using this scheme, e.g. the symbols 00, 11, 01 and 10 using four different frequencies.
The Gaussian filter in front of the FSK means that we apply a Gaussian filter to the change of carrier frequency. That way, instead of going directly from 437.44MHz to 437.46MHz, we make the transition more gradually. The filter helps us to reduce the interference with nearby frequencies caused by abrupt changes in carrier frequency.
Imagine you have now bought your own HackRF One or another SDR and are eager to test it out. The easiest way to see some signals is probably to start with Gqrx, which supports a wide range of devices. It also has a nice spectrum analyzer and waterfall display. If the features of Gqrx are not enough, you can move to GNU Radio, which is the toolkit that Gqrx is built on. GNU Radio offers interfaces to the SDR devices and performs signal processing before and after the SDR device, depending on whether you are transmitting or receiving (the processing that is traditionally done on dedicated hardware).
You can use GNU Radio either from code or for prototyping from the gnuradio companion GUI tool, which lets you generate radio pipelines by dragging and dropping processing blocks and connecting them visually, then generating code based on that. The blocks can correspond to some analog component. They process the stream of data and output the result to the next block. You can use both Python and C++ to create your own processing blocks. A tutorial for writing your own blocks is available here.
For interfacing SDR hardware with GNU Radio, you can use either source or sink blocks depending on whether you are receiving or transmitting. The main parameters for these blocks are center frequency, sampling rate, and gain. The format that used to feed or receive data from radio is complex I/Q data samples. Here and here you can find thorough explanations of what the I/Q means and why it is used. And here is a video on the subject. In short, it’s a way to describe the amplitude and phase of the sampled signal on the Cartesian coordinate system in a way that is not ambiguous.
Example radio pipeline
Imagine that we have a packet of data we want to send over the air modulated with FSK. How can we convert the data stream of ones and zeros into modulated I/Q data? It’s not necessary obvious, because I/Q data contains only the amplitude and phase information. However if you think about it a bit, you will realize that frequency is just the derivative of phase, meaning that by controlling how fast the phase changes, you can control the frequency. I/Q samples are generated at a fixed speed, so if the phase change between two consecutive samples increases, the frequency also increases and vice versa. If you use the GNU Radio’s frequency mod block for modulation you will also have to define a parameter that describes the maximum frequency deviation through phase change.
To demodulate a frequency-modulated signal in GNU Radio, you can use the quadrature demod block. The block calculates the phase difference between two consecutive samples and multiplies that by a user-definable constant, then outputs the result.
Before demodulation, we have to filter out the noise and unwanted parts of the signal, for example, if there is some other signal nearby. For filtering, you can use e.g. a combination of a band-pass filter, which filters unwanted frequencies, and a squelch, which filters samples with an amplitude below a certain threshold.
After demodulation, you will have a stream of phase differences a.k.a. frequencies you need in order to recover the encoded symbols from there and recognize where each symbol starts and stops. This can be done e.g. with the Clock Recovery MM block, which outputs the sample it thinks is the center of the symbol. If you use this block, I recommend reading this blog post as it explains the parameters better than the GNU Radio documentation and also explains some limits of the blocks, e.g. it doesn’t work on square signals. This means that in our example, the Clock Recovery MM block will only work if we are using the GFSK instead of plain FSK.
After the clock recovery, we have a stream of samples that we think are the center points of symbols. Now we just divide these samples into ones and zeros using the binary slicer block, which outputs greater-than-zero values as one, and zero and less as zero.
Finally we have a stream of bits from which we can try to find our packets. In the physical layer, packets usually start with a preamble. A preamble is typically a fixed-length bit sequence. For example, in ethernet frames it is 8 octets of 0x55 (10101010). Sending a preamble helps the receiver to synchronize the bit timing clock, adjust receiver gain and notice that there is data incoming. After the preamble, there is usually some kind of start of frame bit sequence to mark the end of the preamble and the start of the payload. After the payload, it’s useful to have some kind of checksum to check that the payload was correctly received. If there are bit errors, we can simply discard the frame; otherwise, we send the correctly received payload to be handled by the next higher level protocol. If you want to avoid retransmission when errors occur, you can use Forward Error Correction (FEC) to add some redundant information at the cost of data bandwidth. FEC can help in recovering from certain kind of errors.
A protocol-specific packet decoder can be implemented in GNU Radio. This can be done, for example, as a block which consumes bits as unpacked bytes containing only one bit of data per byte and has an internal state machine that keeps track of the state of the decoder, e.g. whether we are searching for the preamble, in the preamble, in the payload, etc. Finally, if the packet is found, the block outputs it as a message or tags it if tagged streams are used.
The previous steps are an example how SDR in combination with GNU Radio can be used to extract information from radio signals. Although this blog post only scratched the surface of SDRs and signal processing with one simple example, hopefully you learned something new.