Real-Time FM Demodulator using USRP E310
Overview
The purpose of this project was to use the USRP E310 Software Defined Radio (SDR) platform to implement a real-time FM demodulator, testing primarily with radio signals. This was done in C++, utilizing the UHD drivers that Ettus Research has provided. The E310 receives a block of samples, which it downconverts, then processes. Once the block of samples is processed, it is output to the system using the ALSA library. In order to confirm real-time operation, the Boost C++ libraries were used. The completion of this project acts as a springboard, allowing the team to develop more complicated projects using the USRP E310.
Motivation
We did this project as a proof of concept for on-board demodulation on the E310 USRP.
System Overview: Hardware
An Ettus Research USRP E310 was used in the real-time FM demodulator. The USRP E310 platform offers a number of features that make it ideal for this project, primarily the C++ USRP Hardware Driver (UHD) that Ettus Research has provided to interface with the SDR. In addition, the USRP E310 has an ARM A9 processor provided by the Xilinx Zynq 7020 SoC, allowing for a stripped-down version of Linux to run on the radio. This allows the E310 to be a completely self-contained unit, avoiding the latency that occurs when information is moved between the RF frontend and the processing code. These two features make the USRP E310 a good device for this use case.
System Overview: Software
All of the interfacing with the radio was done with the UHD, which simplified running the radio down to configuration, then a blocking recieve function to read in a block of samples. Because the recieve function is blocking, multithreading is used to allow the processing of the data to occur at the same time as a new block is recieved. This is done using the Pthreads library. In addition, a number of Boost libraries were used as well. Finally, the ALSA library was used to send the processed samples to the audio hardware of the E310. A list of libraries used is given in the code section.
Implementation Details
The code is a modified version of the rx_samples_to_file example that Ettus provides with the UHD. This example was extremely flexible, allowing for many of the options to be configurable. Most of this configuration was removed to make the code easier to read. The configuration variables are instead set in initialization code. The sampling rate is configured to be 640 ksps. Each sample is a complex float. The system is set to tune to 107.3 MHz, the frequency of a local radio station. The gain is set to be 40 dB. With this information, the UHD is used to configure the radio. After the system details are initialized, stream-related variables are declared. The stream is initialized, and then ALSA hardware settings are set. ALSA is set up in non-blocking playback mode, with 2 interleaved channels. Because only one output buffer is produced, this could also be configured to a single channel with slight modifications. The software settings are then set, letting the playback buffer fill 1200 samples before it starts playing. The ALSA handle is then initialized. After, a while loop is entered. This while loop processes the incoming samples until the escape command (Ctl+C) is pressed.
Inside the while loop, two tasks are happening concurrently. One task recieves the samples that are coming in, and the other task processes a block of samples. Because this has to happen at the same time for real-time operation, a double-buffer is needed. One buffer has samples read into it, and the other buffer is processed. After the receive buffer is full, the buffers switch roles. The receive task is fairly straightforward. If this is the first time through the while loop, a flag is changed to denote that the while loop has been run, and then the recv function is called. Otherwise, the task waits for the last receive to finish, swaps the buffers, and calls the next recv function. Because this is the longest task, it is isolated into another thread.
The FM demodulation occurs after the recv task is started. First, arctan2 is calculated with the I and Q readings, which are stored in the buff_in element of the input struct. This value is then adjusted to be between 0 and 2*pi. Then, the difference between each sample and the sample before it is taken. This results in a buffer that is 1 sample less, but with downsampling this is not an issue. The resulting data is then filtered through a 20th order FIR low-pass filter and downsampled. The downsampled buffer is then passed to the ALSA write function.
Minor Considerations
There are two buffers being used that are external to the program: a buffer holding incoming samples from the radio and a buffer holding samples that will be output to the sound driver. The sound buffer fills faster than the incoming buffer does. Because of this, ALSA is configured to non-blocking mode, so that write commands will drop samples instead of blocking operation until the buffer is opened. This does not have an audible effect on the sound output, and should be fine for this proof of concept.
Confirming Real-Time Operation
In order to time the demodulation, we used the boost::system_time data type. The time is recorded when the recv function is called. It is then compared to the time after the processed data is written to the audio FIFO. The maximum difference between these two numbers is saved. In order to check operation, the maximum time for the loop to run is compared to the maximum time for the loop to run while only receiving and writing to file. In both cases, the maximum time taken is around 16.005 ms, which roughly corresponds to the receiving of 10000 samples. This shows that the FM demodulator is running in real-time.