Introductory Tour of the GNU Radio Project

http://www.gnu.org/software/gnuradio/images/gnuradio12.png


Introduction

I am writing this guide because GNU Radio is a great tool that can easily be overlooked. The documentation is poor and just getting started is not obvious. There is a tight niche between GNU Radio developers and programmers/users. I hope to explain GNU Radio, what it is, why its useful, and how to use it.

GNU Radio is a set of signal processing tools for the computer. It encompass hundreds of signal processing blocks and utility applications. GNU Radio can tie in with hardware such as the USRP and various ADC/DAC pci cards. Signal processing blocks are written in C++, while creating flow graphs and connecting signal blocks is done is an interpreted language called Python.

GNU Radio is...

  • An API for creating signal blocks (C++/Python)
  • A runtime environment for signal processing
  • A library of signal processing blocks
  • User scripts and applications
  • Hardware drivers (USRP/USRP2/VRT)
  • An application for creating flow graphs (GRC)

USRP

The USRP is big ADC/DAC with a USB plug, with a decent price (under $1k). This hardware device is probably GNU Radio's best friend.

http://www.ettus.com/images/USRP.jpg

It has removable daughterboards that cover most of the usable RF spectrum. The receiving ADC rate is 64 Megasamples per second, while the transmitting DAC rate is 128 Megasamples per second.


Installing GNU Radio

Installing GNU Radio is probably the biggest leap for a beginner. GNU Radio can run on any platform, however, some installations are easier than others. GNU Radio must be compiled from source and all of the dependencies have to be taken care of. If your heart is set on Windows, I recommend using Cygwin. If you can spare an extra PC, I recommend installing Ubuntu Linux.

Windows

GNU Radio definitely works in Windows XP, but be prepared to give up a day's worth of work. Windows users can install GNU Radio under the Cygwin or MinGW unix emulation environments.

Linux

Ubuntu Linux is very nice for GNU Radio because all the dependencies can be easily met. You simply have to select the correct check boxes and click install. Follow the Ubuntu Install Guide. Fedora Core also makes for a pretty easy installation, but with a little more effort. Follow the Fedora Core Install Guide. Then follow the build guide.


Using GNU Radio

Introdution

In this section, I will explain how to use the GNU Radio API in the Python programming language. If you are interested in a graphical interface like Simulink, check out GNU Radio Companion.

Python

A signal processing flow graph is implemented in Python. Python is an object oriented language, much like C++ and Java. Python code is very neat and organized and you will never have to compile it. There are no semicolons, or curly braces in Python. Rather, the code relies heavily on indentation levels and newlines. If you are familiar with programming, understanding the basics of Python should be trivial.

Helpful Python References


Learning By Example

The quickest way to understand how to use GNU Radio is to start with some basic examples. My examples are written in Python. You can copy them into a text file and run them with the Python interpreter.

A Simple Flow Graph

#bring in blocks from the main gnu radio package
from gnuradio import gr

#create the top block
tb = gr.top_block()

#create a signal source
src = gr.null_source(1) #1 indicates the data stream size

#create a signal sink
sink = gr.null_sink(1)

#connect the source to the sink
tb.connect(src, sink)

#run the flow graph
tb.run()

  • As you may have guessed, this flow graph does nothing. The null source spits out zeros, and the null sink takes a data stream and does nothing with it. Also, there is nothing in the flow graph to control the data rate, the CPU usage should climb to 100%.

The Phone-Tones Example

#bring in blocks from the main gnu radio package
from gnuradio import gr
#bring in the audio source/sink
from gnuradio import audio

#create the flow graph
tb = gr.top_block()

#create the signal sources
#parameters: samp_rate, type, output freq, amplitude, offset
src1 = gr.sig_source_f(32000, gr.GR_SIN_WAVE, 350, .5, 0)
src2 = gr.sig_source_f(32000, gr.GR_SIN_WAVE, 440, .5, 0)

#an adder to combine the sources
#the _ff indicates float input and float output
adder = gr.add_ff()

#create a signal sink
sink = audio.sink(32000)

#connect the adder
#the adder has multiple inputs...
#we must use this syntax to choose which input to use
tb.connect(src1, (adder, 0))
tb.connect(src2, (adder, 1))

#connect the adder to the sink
tb.connect(adder, sink)

#run the flow graph
tb.run()

  • Sampling Rates: The same sample rate, 32000, is used in the creation of signal sources and the audio sink. Certain blocks must agree on the rate at which they are sending or receiving data. However, the adder is not dependent on sampling rate, it simply adds whatever it receives.
  • Data Types: I will describe the data types in another section. In the meantime, take notice of the _ff in gr.add_ff and the _f in gr.sig_source_f. Most blocks indicate their input/output data types in their name. In this case, f is for float. The signal sources output a stream of floats, and the adder takes and outputs floats as well. Obviously, the audio sink must take a stream of floats. However, this is not indicated by the name.
  • Mutiple Inputs/Outputs: Certain blocks have more than one input or output. Some have two, some have infinite. The adder has one output and an infinte number of inputs. In the above example, we use only two inputs of the adder. Inputs and outputs have indexes starting at 0 and counting up. "tb.connect(src2, (adder, 1))" means that we want to connect the output of src2 to the second input of the adder. By not specifying an index, index 0 is implied. Therefore, "tb.connect((src2, 0), (adder, 1))" would have an identical effect.

It would be wise to check out the GNU Radio class list to see what kind of signal blocks are offered. The class listing only contains the blocks in the main GNU Radio package (gr). Other blocks are not documented there. Unofficial GNU Radio User Manual


USRP Examples

USRP Source

#bring in the USRP source/sink
from gnuradio import usrp

#create the usrp source
#0 represents the USRP number (in case you have multiple USRPs)
u = usrp.source_c(0)

#Set the decimation
u.set_decim_rate(decimation)

#Automatically choose the sub device
#(this can be done manually, see below)
subdev_spec = usrp.pick_rx_subdevice(u)

#Set the mux
u.set_mux(usrp.determine_rx_mux_value(u, subdev_spec))

#get the sub-device
subdev = usrp.selected_subdev(u, subdev_spec)

#Select receive antenna ('TX/RX' or 'RX2'): flex boards only!
subdev.select_rx_antenna('RX2')

#Set the gain
subdev.set_gain(gain)

#Tune the center frequency
u.tune(0, subdev, frequency)

USRP Sink

#bring in the USRP source/sink
from gnuradio import usrp

#create the usrp sink
#0 represents the USRP number (in case you have multiple USRPs)
u = usrp.sink_c(0)

#Set the decimation
u.set_interp_rate(interpolation)

#Automatically choose the sub device
#(this can be done manually, see below)
subdev_spec = usrp.pick_tx_subdevice(u)

#Set the mux
u.set_mux(usrp.determine_tx_mux_value(u, subdev_spec))

#get the sub-device
subdev = usrp.selected_subdev(u, subdev_spec)

#Enable transmit: flex boards only!
subdev.set_enable(True)

#Set the gain
subdev.set_gain(gain)

#Tune the center frequency
u.tune(subdev.which(), subdev, frequency)
  • Using a USRP source/sink: The examples above are not complete flow graphs. They demonstrate how to setup a USRP source/sink. The "u" variable represents the actual signal block. This block needs to be connected to other components in a flow graph.
    • For the USRP source: "fg.connect(u, input_block)".
    • For the USRP sink: "fg.connect(output_block, u)"
  • Sub Devices: A subdevice specification represents a daughter board on the USRP. Subdevices are identified by two parameters, the side and the subdevice number. A subdevice specification is of the form (side, subdevice). The side can be 0 (for side A) or 1 (for side B). The subdevice number can be 0 or 1, since some daughter boards have multiple inputs/outputs. Notice, "subdev_spec = usrp.pick_rx_subdevice(u)" will automatically pick one of the rx daughter boards currently plugged into the USRP. Similarly, "subdev_spec = usrp.pick_tx_subdevice(u)" will automatically pick one of the tx daughter boards. To be more exact, you could change this line to "subdev_spec = (1, 0)" to pick the first subdevice on side B.
  • The Mux: There is a register on the USRP that controls how the rx mux routes data to the daughter boards. "usrp.determine_rx_mux_value(u, subdev_spec)" automatically determines the register value based on the subdev_spec. In the same fassion, the USRP has a tx mux register. The value can be automatically determined with "usrp.determine_tx_mux_value". You can receive data from 1, 2, or 4 rx devices and send to 1 or 2 tx subdevices. However, there is no "nice" method to create the mux values for multiple sends/receives.
  • Tunning: Calling u.tune, sets the oscillation frequency of the carrier. For the USRP source: the first argument tells the USRP which DDC to use. This argument must always be 0 for DDC0. For the USRP sink: the first argument tells the USRP which DUC to use. This argument should be subdev.which() so that the DUC is chosen appropriately for your daughterboard.
  • Sampling Rates: The USRP ADC runs at 64 Megasamples per second. The output sampling rate should be (64 Msamps/s) / decimation. The USRP DAC runs at 128 Megasamples per second. The input sampling rate should be (128 Msamps/s) / interpolation.
  • IO: In the above example, I use "usrp.source_c" to output a complex stream. This line could easily be replaced with "usrp.source_s" to output a stream of interleaved shorts. The same rule applies to "usrp.sink_c" and "usrp.sink_s". See the Data Types section below for more information:

Data Types

Signal blocks communicate with each other via data streams. A stream is made up of individual elements, where all elements have a particular data type. GNU Radio has very strict data type checking, meaning: Input and output data types must match exactally or an error will be thrown at runtime.

Data types can be bytes, shorts, ints, floats, and complex. In addition, a data type could be a vector of type byte, short, int, float, or complex. In many ways, a regular data stream is just a stream of length-1 vectors.

  • Byte - 1 byte of data (8 bits per element)
  • Short - 2 byte integer
  • Int - 4 byte integer
  • Float - 4 byte floating point
  • Complex - 8 bytes (actually a pair of floats)

Interleaved Shorts

Some blocks with a _s suffix say that they take interleaved shorts rather than shorts. Interleaved shorts are I&Q pairs of short intergers. There is no difference between interleaved shorts and shorts. This is up to the interpretation of the block.

Bytes and Characters

Some blocks will output character streams or unsigned character streams. There is no difference between bytes and characters. Once again, this is up to the interpretation of the block.

Signal Block Naming Conventions

Usually, the name of the signal block indicates the data type. A signal block ending in _f indicates that the block will input or output a float (depending on its function). A signal block ending in _fc, indicates that the block will input a float and output a complex. Sometimes, a block may end in a _vff, indicating that the input and output are a vector of floats. You can assume: _b for byte, _s for short, _i for int... and so on.

Keep in mind, not all signal block names follow this suit. Some blocks input or output a particular data type, but do not specify this in the name. Ex: audio.sink always takes a stream of floats but does not end in _f.

Other blocks do not expect a specific input or output type. In these cases, you must specify the size of the data type (in bytes) as one of the signal block's parameters. Ex: gr.null_sink can take a stream of any data type, but the first parameter must be the size of the data type in bytes.


Moving On...

I have only described the very basic framework of GNU Radio. There is so much more. Get a firm grasp on python, try the examples that come with GNU Radio, and experiment with other signal blocks. You can do anything...

Home About Links Contact

Hits: 30194

http://www.gnu.org/graphics/gnu-head-sm.jpg