I built a system that demodulates live FM (frequency modulated) radio signals on the DE1-SoC board using a RTL-SDR module and Raspberry Pi, enabling real-time signal processing for clear, immediate audio output. Using the RTL-SDR as a receiver, I connected it to a Raspberry Pi to extract the I and Q values. These values were transmitted to the DE1-SoC's Hard Processor System (HPS) via a TCP socket. Once received, the I and Q values were demodulated, filtered, and sent to the DAC for audio playback of WVBR, Ithaca’s Alternative radio station.
Since this class focuses on using FPGAs (field programmable gate arrays) for hardware acceleration, I anticipated that the FPGA would excel at handling complex, real-time computations. I also knew that radios receive and demodulate signals in real-time so I figured that the FPGA would be perfect for exploring digital signal processing for FM demodulation. Initially, I aimed to implement a complete software defined radio (SDR) system on the FPGA. However, given that I worked on the final project independently and had limited time to complete it, I decided to scale the scope down to focus on demodulation on the HPS, as the topic was new to me.
IQ Modulation
In Software-Defined Radios (SDR), IQ modulation is used to represent RF signals as two orthogonal components: I (In-phase) and Q (Quadrature). The I component corresponds to the real part of the signal, aligned with a cosine wave, while the Q component corresponds to the imaginary part, aligned with a sine wave (90° out of phase). Together, the complex signal I+jQ allows full representation of the signal's amplitude and phase. This orthogonal decomposition makes it possible to encode and process complex modulated signals.
In the context of FM signals, the transmitted signal can be expressed as:
$$ s(t) = A \cdot \cos(2\pi f_c t + \theta(t)) $$
where:
$ f_c $ is the carrier frequency, and
$ \theta(t)$ is the instantaneous phase that encodes the modulating signal.
The IQ representation of this signal is:
$$ I(t) = A \cdot \cos(2\pi f_c t + \theta(t)) $$
and
$$ Q(t) = A \cdot \sin(2\pi f_c t + \theta(t)) $$
IQ Demodulation
IQ demodulation extracts the original baseband information from a modulated RF signal by mixing it with two local oscillator signals: a cosine wave for the I component and a sine wave for the Q component. These operations shift the signal to baseband while preserving the amplitude and phase. The orthogonality of the cosine and sine ensures no interference between I and Q, enabling precise reconstruction of the transmitted signal. By analyzing the resulting IQ components, both amplitude and phase information can be extracted for further processing.
FM Demodulation Using IQ Values
FM demodulation using IQ values relies on detecting changes in the instantaneous phase of the signal. Since FM signals encode information in phase variations rather than amplitude, the phase angle $\theta$ can be computed as:
$$ \theta(t) = \arctan\left(\frac{Q(t)}{I(t)}\right) $$
To extract the frequency modulation, we take the derivative of the phase with respect to time. The instantaneous frequency $f(t)$ is given by the derivative of $\theta(t)$:
$$ f(t) = \frac{d\theta(t)}{dt} = \frac{d}{dt} \left( \arctan\left(\frac{Q(t)}{I(t)}\right) \right) $$
This derivative gives the rate of change of phase, which is proportional to the instantaneous frequency deviation of the signal. The instantaneous frequency deviation corresponds to the original modulating signal. This FM demodulation technique is also called phase discrimination.
Derivative Approximation
In my code, I approximate this computation by taking an approximated derivative of consecutive I/Q values first. Then I take the arctangent of the real and imaginary components of this approximated derivataive to get the derivative of phase. Although this approach was less accurate, it simplified the C code.
$$ \text{real} = I(t) \cdot I(t + \Delta t) - Q(t) \cdot Q(t + \Delta t) $$
$$ \text{imag} = I(t) \cdot Q(t + \Delta t) + Q(t) \cdot I(t + \Delta t) $$
These two expressions approximate the real and imaginary parts of the derivative that reflects the changes in the in-phase and quadrature components, respectively, from one time sample to the next.
Next, the arctangent of the imaginary and real part are taken to compute the derivative of the phase angle:
$$ f(t) \approx \frac{d\theta(t)}{dt} \approx \arctan\left( \frac{\text{imag}}{\text{real}} \right) $$
The decision to implement the FM demodulation in C on the HPS was driven by time constraints, as I was working alone. While C is easier to program, it is slower than Verilog on the FPGA. However, the audio played fine, so the tradeoff between simplicity and performance was acceptable for this project. Moving forward, I plan to continue working on the project by implementing the demodulation computations directly in Verilog. This will leverage the FPGA's parallel processing capabilities, improving both speed and efficiency, and allow for real-time processing of the FM signals. By shifting the computations to the FPGA, I aim to optimize the overall system performance and reduce the computational load on the HPS.
All the hardware components I used, except for the DE1-SoC, are open source (Raspberry Pi and RTL-SDR), and none of the techniques I employed are subject to patents, copyrights, or trademarks.
The RTL-SDR module is connected to the Raspberry Pi 4 Model B via USB. The Raspberry Pi 4 Model B is connected to an Ethernet packet switch, and the DE1-SoC is connected to the same packet switch to communicate via TCP.
To establish ethernet connection between the Raspberry Pi and HPS for TCP communication, complete the following steps:
1. Set up DE1-SoC board.
Configure the board as usual, ensuring its Ethernet is connected to a network switch.
2. Set up the Raspberry Pi
Connect the Raspberry Pi’s Ethernet port to the same network switch.
3. Install RTL-SDR Software
On the Raspberry Pi, install the necessary software to interface with the RTL-SDR radio dongle by following the steps written here. The steps include:
Run sudo apt-get install gnuradio
Launch gnuradio-companion
and verify it opens correctly
Run sudo apt-get install rtl-sdr gr-osmosdr
Plug in the RTL-SDR dongle
Run lsusb
to confirm the dongle is detected
Run rtl_test
to verify the test passes
4. Find the Raspberry Pi's IP Address
Run ifconfig
to find the IP address of the Raspberry Pi. Record the address so that it can be entered into the C program later when opening a TCP socket.
5. Start rtl_tcp
On the Raspberry Pi, execute the command:
rtl_tcp -a 10.253.17.37 -p 1234 -f 93500000 -s 900001
6. Receive TCP
On the ARM, run the command:
nc 10.253.17.37 1234
Once the TCP socket code is written and the IP address and port number are included, this step is no longer required. It is only necessary for verifying the establishment of the TCP connection.
On the Raspberry Pi, I launched the rtl_tcp program in the Terminal and configured the RTL-SDR to tune to WVBR-FM at 93.5 MHz, with a sample rate of 900,001 MHz. This setup allowed the program to begin extracting I/Q values from the SDR and transmitting them to the HPS through the TCP socket. I selected a sample rate of 900,001 MHz because it is the lowest valid rate supported by rtl_tcp. See the appendix for how rtl_tcp was set up.
In my final C program that runs on the HPS, I implemented a phase discriminator to demodulate FM signals. In the main loop, the FPGA's hardware registers are mapped into user space using mmap to interface with the audio FIFO and audio channels. A TCP socket is established over Ethernet to connect to the Raspberry Pi and receive interleaved I/Q (in-phase and quadrature) data. The received data is processed by decimating the I/Q values by a factor of 2. These I/Q values are then demodulated using the phase discriminator, which estimates the derivative of two sets of I and Q values and calculates the phase difference using an arctangent function. The result is passed through a 30-tap finite impulse response (FIR) filter which low passes the result to the FM baseband message frequency. The filter taps were predefined and calculated using my Python program. The filtered values are scaled up and sent to the FPGA's DAC (Digital-to-Analog Converter), provided there is space available in the audio FIFO. The program continuously processes incoming data in a loop to ensure real-time audio playback.
I encountered many challenges when writing the program. In the beginning, I spent a significant amount of time trying to get a sine wave to play on the FPGA’s DAC, only to realize that the wrong audio FIFO address was being used to read the FIFO. Although the offset I defined in the macros matched the one in QSYS, I discovered that Professor Land’s code handles the offsets slightly differently by incrementing the address, so I implemented that approach instead and it worked. Later, the challenging aspects of the program were more related to my lack of conceptual understanding than the coding itself. I spent a lot of time double-checking the atan2 function and the derivative to ensure they were calculating the correct values for demodulation, even though these parts were working correctly from the start. I eventually realized that I needed to scale the values because they were too small. After trial and error, I found the right scaling factor, but the result was still just loud white noise with a hint of melody. I continued checking the bit values of the audio being sent to the DAC, but ultimately discovered that the demodulated values required filtering. Implementing the filter was tricky for me due to my limited understanding of this area. After experimenting in Python, I determined that the FIR filter was effective and used a library to calculate the necessary taps to achieve the desired output.
Surprisingly, implementing the TCP socket was easier than I expected. I simply needed to set up the TCP socket in my code properly and ensure the correct port number and IP address were used. Initially, I thought I would have to buffer the incoming I/Q values from rtl_tcp myself as the sampling rate was so high, but the recv function handled that for me.
I borrowed some code from Van Hunter Adams for the derivative approximation and the FIR filter. I borrowed the audio example from Bruce Land’s ECE 5760 examples web page. I also borrowed open-source C code for the TCP socket from linuxcode.com and geeksforgeeks.org. The Raspberry Pi is running rtl_tcp, an open-source package that facilitates communication with the radio dongle.
I initially tried playing the audio without filtering the output, assuming that the RTL-SDR’s hardware filters would be sufficient and that I would not need to filter the digital values. Additionally, I attempted to use the state machine from the audio example in Verilog, rather than using C to output the audio, with the idea of building upon it and integrating FM demodulation into the state machine. However, I ran out of time before I could complete this integration, especially given the number of multipliers that would be required to get a decent audio output.
In the end, the FM demodulator functioned successfully, allowing me to play the WVBR-FM radio station in real-time. Most of my testing was done by listening to the audio output and comparing it to what was actually playing on the radio station. Although the audio output was initially noisy, I noticed significant improvements when I adjusted the antenna to specific positions, made sure the antenna’s surroundings were away from people, and opened the door. These adjustments likely enhanced the reception by reducing interference and improving the signal strength, which in turn clarified the audio quality. This observation highlights the importance of environmental factors, such as antenna orientation and surrounding obstructions, in optimizing FM signal reception.
Since the RTL-SDR functions solely as a receiver, I did not need to worry about interfering with other wireless devices or unintentionally violating FCC regulations. I also did not have to worry about any safety concerns with this project, as all the components were plug-and-play.
Currently, the user interface is not quite intuitive so more work in the future needs to be done to implement one. On the Raspberry Pi, a user must start rtl_tcp in the terminal and configure settings for the RTL-SDR before running the C program on the HPS so that it is tuned to the frequency of the station that they want to listen to. In the future, I plan to implement a user interface that allows the user to configure the RTL-SDR settings directly from the HPS using the command line.
Given the time constraints, my decision to work independently, and the unfamiliarity of the topic, I believe my results were satisfactory, as I was able to get the radio to play in real-time, though it was a bit noisy. With more background knowledge in signal processing and radio systems, I could have spent less time validating my FM demodulation technique in Python and more time on the implementation. Specifically, I would have moved the FM demodulation calculations to Verilog, where I would have written a state machine to process data from the HPS to the FPGA, demodulate the values, and send them to the audio channels. I would have utilized the Altera IP for the atan2 function and the multipliers from Lab 1 for the necessary computations. The C code would solely serve to allow the HPS to receive I/Q data from the Raspberry Pi and relay it to the FPGA. Additionally, I would have implemented a command-line interface that enables the user to tune the SDR and adjust the gain.
My design adhered to applicable standards by using open-source hardware components (Raspberry Pi and RTL-SDR). I used publicly available code from resources like GNU Radio, rtl_tcp, and standard TCP socket implementations. I did not engage in reverse engineering, and I ensured compliance with patents and trademarks by using open-source hardware and avoiding proprietary designs. I did not sign a non-disclosure agreement for sample parts. There are no immediate patent opportunities for my project, as it relies on existing, open-source technologies.
TCP Server-Client Implementation in C
Posix Socket with C Programming
Altera University Program Audio Core
Using the raspberry pi as an rtl sdr dongle server
Setting up rtl-sdr on raspberry pi 3