E310 FM Receiver

From GNU Radio
Revision as of 11:18, 30 June 2025 by O V R (talk | contribs)
Jump to navigation Jump to search
Beginner Tutorials

Introducing GNU Radio

  1. What is GNU Radio?
  2. Installing GNU Radio
  3. Your First Flowgraph

Flowgraph Fundamentals

  1. Python Variables in GRC
  2. Variables in Flowgraphs
  3. Runtime Updating Variables
  4. Signal Data Types
  5. Converting Data Types
  6. Packing Bits
  7. Streams and Vectors
  8. Hier Blocks and Parameters

Creating and Modifying Python Blocks

  1. Creating Your First Block
  2. Python Block With Vectors
  3. Python Block Message Passing
  4. Python Block Tags

DSP Blocks

  1. Low Pass Filter Example
  2. Designing Filter Taps
  3. Sample Rate Change
  4. Frequency Shifting
  5. Reading and Writing Binary Files

SDR Hardware

  1. RTL-SDR FM Receiver
  2. B200-B205mini FM Receiver
  3. E310 FM Receiver

This tutorial describes how to receive broadcast commercial radio stations transmitting Frequency Modulated (FM) signals using the Ettus Research E310.

USRP (E310) Setup Guide

This page provides step-by-step instructions to configure a USRP E310 for:

  • Receiving signals with a Python script
  • Remote control via SSH
  • Using GNU Radio on a host machine

1. Hardware Connection

  • Plug the antenna into the RX2-A port on the USRP. The RX2-A port is dedicated to receive (RX) operations.
  • Turn on the USRP and ensure it has a stable power supply.

2. Initialization of the USRP

see dedicated page


3. Determine the USRP’s IP Address

On your host machine (connected to the same network):

sudo snap install nmap    # Install nmap if not already present
nmap -sn 10.0.0.0/24      # Scan the local subnet
This finds the DHCP‑assigned IP of the USRP (e.g., 10.0.0.100).

4. Assign a Static IP Address

To simplify SSH access, give the USRP a fixed IP.

a. Create the static_ip.sh Script

SSH into the USRP (using the DHCP IP) and run:

nano static_ip.sh

Paste the following:

#!/bin/sh

# 1. Flush existing IP addresses
ip addr flush dev eth0

# 2. Assign static IP and netmask
ip addr add 10.0.0.200/24 dev eth0

# 3. Bring the interface up
ip link set eth0 up

# 4. Add default gateway
ip route add default via 10.0.0.1
Each step configures the Ethernet interface (eth0) for static addressing.

c. Run the Script

bash static_ip.sh
The IP remains active until the next reboot.

5. SSH Connection

On the host:

ssh root@10.0.0.200
You now have remote shell access to the USRP.

6. Mount USRP Filesystem via SSHFS

To edit USRP files locally:

sshfs root@10.0.0.200:/ ~/remote_usrp
Mounts the USRP’s root directory at `~/remote_usrp` on the host.

7. Configure Geany IDE

To simplify the workflow, we will use Geany, a lightweight IDE, on the host machine to:

  • Edit files located on the USRP (via SSHFS)
  • Launch scripts remotely on the USRP (via SSH)
  • Centralize all development and execution within one interface
This allows you to work entirely from the host, avoiding the need to manually SSH into the USRP or use a separate editor.


a. Open Build Commands

In Geany, go to Build → Set Build Commands.


b. Create the start_usrp_script.sh Script

Before applying the static IP, prepare the Geany startup helper on the USRP:

nano /home/root/start_script_geany.sh

Paste the following:

#!/bin/sh

cleanup() {
    echo '[INFO] Stop requested'
    pkill -f RX_FM_USRP_UDP.py
    exit 0
}

trap cleanup INT TERM

python3 /home/root/RX_FM_USRP_UDP.py
This script installs a cleanup handler that intercepts interrupt or termination signals, cleanly kills the FM‑UDP Python process, and exits. When Geany’s “Execute” command runs this script remotely, RX_FM_USRP_UDP.py is launched and properly managed.

c. Update the Build Commands

In "Independent Commands", to the right of "Run Remote", add:

scp "%f" root@0.0.0.200:/tmp/ && ssh root@0.0.0.200 'python3 /tmp/"%f"'
This single command performs two actions back-to-back: first, it securely transfers the file you’re editing to the USRP’s temporary directory over SSH; then, once that transfer completes successfully, it opens an SSH session on the USRP and immediately invokes Python 3 to run the uploaded file from its temporary location. In other words, it bundles “copy the script over” and “execute it remotely” into one seamless operation.

In "Execute commands", to the right of "Execute", add:

ssh -t root@0.0.0.200 "bash -i -c '/home/root/start_script_geany.sh'"

8. TX/RX Codes and flowgraphs

a. RX Code

This script is written in Python to be more easily edited and executed remotely via Geany on the host machine, providing flexibility and rapid iteration.

#!/usr/bin/env python3  # Corrected for execution on host
# -*- coding: utf-8 -*-

import time
import signal
import sys
from gnuradio import gr, blocks, analog, filter, uhd, zeromq
from gnuradio.filter import firdes

from message_to_freq import message_to_freq  # Custom block: maps incoming ZMQ messages to frequency updates
from message_to_gain import message_to_gain  # Custom block: maps incoming ZMQ messages to gain updates

class RX_FM_USRP_UDP(gr.top_block):
    def __init__(self):
        gr.top_block.__init__(self, "Rx FM USRP UDP Headless")

        ##################################################
        # Variables
        ##################################################
        self.samp_rate = 2e6           # Sample rate for USRP
        self.gain = 15                 # Initial gain (dB)
        self.freq = 102.5e6            # Center frequency (Hz)
        self.freq_cos = 300e3          # Offset for cosine mixing (Hz)
        self.bw = 200e3                # RF bandwidth (Hz)

        ##################################################
        # USRP Source
        ##################################################
        self.uhd_source = uhd.usrp_source(
            ",".join(('', '')),      # Empty args: will use default device
            uhd.stream_args(cpu_format="fc32", channels=[0]),
        )
        self.uhd_source.set_samp_rate(self.samp_rate)
        self.uhd_source.set_center_freq(self.freq, 0)
        self.uhd_source.set_gain(self.gain, 0)
        self.uhd_source.set_antenna("RX2", 0)
        self.uhd_source.set_bandwidth(self.bw, 0)

        ##################################################
        # Signal Processing Chain
        ##################################################
        # Generate a cosine wave for mixing
        self.sig_source = analog.sig_source_c(
            self.samp_rate, analog.GR_COS_WAVE, self.freq_cos, 1, 0
        )
        # Multiply RF signal with cosine to shift frequency
        self.mult = blocks.multiply_vcc(1)

        # Low-pass filter to isolate FM bandwidth
        self.lowpass = filter.fir_filter_ccf(
            decimation=10,
            taps=firdes.low_pass(1, self.samp_rate, 90e3, 5e3, firdes.WIN_HAMMING)
        )

        # Rational resampler to adjust sample rate for UDP sink
        self.resampler = filter.rational_resampler_ccc(
            interpolation=12, decimation=15
        )

        # Send complex baseband samples over UDP to host
        self.udp_sink = blocks.udp_sink(
            gr.sizeof_gr_complex, '10.67.44.132', 9997, 1472, True
        )

        ##################################################
        # ZMQ Control: Frequency Updates
        ##################################################
        self.zmq_pull_freq = zeromq.pull_msg_source(
            'tcp://10.67.44.132:9996', 100
        )
        self.msg_freq_handler = message_to_freq(self.set_freq)
        self.msg_connect(
            (self.zmq_pull_freq, 'out'),
            (self.msg_freq_handler, 'in')
        )

        ##################################################
        # ZMQ Control: Gain Updates
        ##################################################
        self.zmq_pull_gain = zeromq.pull_msg_source(
            'tcp://10.67.44.132:9995', 100
        )
        self.msg_gain_handler = message_to_gain(self.set_gain)
        self.msg_connect(
            (self.zmq_pull_gain, 'out'),
            (self.msg_gain_handler, 'in')
        )

        ##################################################
        # Block Connections
        ##################################################
        self.connect((self.uhd_source, 0), (self.mult, 0))
        self.connect((self.sig_source, 0), (self.mult, 1))
        self.connect((self.mult, 0), (self.lowpass, 0))
        self.connect((self.lowpass, 0), (self.resampler, 0))
        self.connect((self.resampler, 0), (self.udp_sink, 0))

    def set_freq(self, freq):
        """Update center frequency on the fly."""
        # print(f"[INFO] Updating frequency to {freq/1e6:.2f} MHz")
        self.freq = freq
        self.uhd_source.set_center_freq(freq, 0)

    def set_gain(self, gain):
        """Update gain on the fly."""
        # print(f"[INFO] Updating gain to {gain:.1f} dB")
        self.gain = gain
        self.uhd_source.set_gain(gain, 0)

def main():
    tb = RX_FM_USRP_UDP()

    def cleanup(signum=None, frame=None):
        """Handle termination signals to stop flowgraph cleanly."""
        print("[INFO] Stop requested via signal.")
        tb.stop()
        tb.wait()
        sys.exit(0)

    # Catch Ctrl+C and termination signals
    signal.signal(signal.SIGINT, cleanup)
    signal.signal(signal.SIGTERM, cleanup)

    tb.start()
    print("[INFO] Flowgraph started. Waiting for ZMQ commands (freq/gain)...")

    try:
        while True:
            time.sleep(1)
    except Exception as e:
        print(f"[ERROR] Unexpected exception: {e}")
        cleanup()

if __name__ == '__main__':
    main()

This script defines and runs a headless GNU Radio flowgraph on the USRP E310 that continuously receives FM broadcasts, applies signal processing, and streams the resulting complex baseband samples over UDP to a host computer. It begins by initializing key parameters—sample rate (2 MHz), RF center frequency (e.g. 102.5 MHz), gain, mixing offset, and bandwidth—all of which can be easily adjusted in code. The script then configures the USRP source block to use these parameters (including selecting the RX2 antenna port), and constructs a processing chain that mixes the incoming RF signal down with a cosine wave, filters it with a low‑pass FIR filter to isolate the FM spectrum, and resamples it to match the network transport rate before sending it out via a UDP sink. Meanwhile, two ZeroMQ pull sockets listen for real‑time control messages from the host—one for frequency updates and one for gain adjustments. When a message arrives, custom handler blocks invoke set_freq() or set_gain() to retune the USRP or change its gain without restarting the flowgraph. The script also installs a cleanup function that traps interruption or termination signals, allowing a graceful shutdown that stops streaming and releases hardware resources. Finally, after starting the flowgraph, it enters an infinite sleep loop to keep the process alive and responsive until the user requests it to stop.

b. TX Flowgraph

9. Running the Flow

  1. In Geany, open `~/remote_usrp/home/root/usrp_fm_receiver.py`.
  2. Press “Execute” to start the script on the USRP.
  3. On your host, launch GNU Radio Companion and run `fm_receiver_host_udp.py` to receive the UDP stream.
The USRP script streams FM over UDP; GNU Radio handles it on the host side.

Additional Notes

  • To make the IP configuration persistent, consider adding `static_ip.sh` to `/etc/rc.local` or creating a `systemd` service.
  • Verify compatibility of Python and GNU Radio versions between the USRP and host.
  • IP addresses have been anonymized, but this obviously needs to be adapted to the use case.

Last updated: 30 June 2025