icon

UNIHIKER: Software-defined radio (SDR)

0 717 Medium

This tutorial will guide you through setting up a UNIHIKER device with an RTL-SDR (Software Defined Radio) dongle to receive and play FM radio signals. The tutorial covers the hardware setup, software installation, Bluetooth configuration, and coding steps necessary to create an FM radio receiver (Python).

HARDWARE LIST
1 UNIHIKER
1 RTL-SDR dongle
1 Bluetooth speaker or headphones

To complete this project, you will need the following hardware components:

 

UNIHIKER board: The main controller for the project.

 

RTL-SDR dongle: A USB device that allows the reception of radio signals.

 

Antenna: For receiving radio signals.

 

Bluetooth speaker or headphones: To listen to the audio output.

 

STEP 1
Preparation

First, update your system and install the necessary rtl-sdr package via apt. To do this, you must first connect to UNIHIKER via SSH. UNIHIKER must also be connected to the Internet.

CODE
```shell
# update package list
$ apt update

# install rtl-sdr package
$ apt install -y rtl-sdr
```

In case of apt issue (eq. buster-backports Release no longer has a Release file) comment following entry (line 8)

CODE
```shell
cat -n /etc/apt/sources.list
     1  
     2  deb http://httpredir.debian.org/debian buster main contrib non-free
     3  #deb-src http://httpredir.debian.org/debian buster main contrib non-free
     4  
     5  deb http://httpredir.debian.org/debian buster-updates main contrib non-free
     6  #deb-src http://httpredir.debian.org/debian buster-updates main contrib non-free
     7  
     8  # deb http://httpredir.debian.org/debian buster-backports main contrib non-free
     9  #deb-src http://httpredir.debian.org/debian buster-backports main contrib non-free
    10  
    11  deb http://security.debian.org/ buster/updates main contrib non-free
    12  #deb-src http://security.debian.org/ buster/updates main contrib non-free
```
STEP 2
Bluetooth connection

To connect and test a Bluetooth audio device, follow these steps:

CODE
```shell
# scan for Bluetooth devices
$ bluetoothctl scan on

# pair with a Bluetooth device
$ bluetoothctl pair [mac_address]

# connect to a paired Bluetooth device
$ bluetoothctl connect [mac_address]

# verify if device is connected
$ bluetoothctl info [mac_address]

# play some test sound on connected device
$ aplay /usr/share/sounds/alsa/Front_Center.wav
```
STEP 3
Testing RTL-SDR via Command-Line

Before proceeding, ensure that your RTL-SDR dongle is functioning correctly:

CODE
```shell
# test RTL-SDR stick (output via sox)
$ rtl_fm -f 105.6M -M fm -s 200k -A fast -E deemp | sox -t raw -r 200k -e signed-integer -b 16 -c 1 -V1 - -d
```

The command tunes an RTL-SDR dongle to the FM radio station at 105.6 MHz, decodes the FM signal, applies de-emphasis, and then plays the audio output through the default audio device using sox. The rtl_fm tool processes the radio signal, while sox handles the playback.

 

-f 105.6M: This sets the frequency to 105.6 MHz.

 

-M fm: This specifies that the modulation type is FM (Frequency Modulation).

STEP 4
Project Preparation/Setup

Create a folder “RTL-SDR”. Then create another “img” folder in this folder and copy the following image into it.

 

Now create an empty file called "main.py" (in the RTL-SDR folder). When you have completed these steps, the project structure should look like this:

 

STEP 5
Python code

Below is the Python code (main.py) to create an FM radio receiver using UNIHIKER and RTL-SDR:

CODE
from pinpong.board import Board, Pin
from pinpong.extension.unihiker import button_a, button_b
from tkinter import Tk, Label
from PIL import ImageTk, Image
from subprocess import Popen, PIPE
from time import time


SCREEN_WIDTH: int = 240
SCREEN_HEIGHT: int = 320

MAX_FREQUENCY: float = 108.0
MIN_FREQUENCY: float = 87.5
FREQUENCY_STEP: float = 0.1
DEBOUNCE_TIME: int = 1


def update_frequency_label() -> None:
    """
    Update the frequency label with the current frequency value.

    :return: None
    """
    global frequency_label
    global current_frequency

    rounded_frequency = round(current_frequency, 1)
    frequency_label.config(text=f'{rounded_frequency} MHz')


def start_rtl_sdr(frequency: float) -> None:
    """
    Starts the RTL-SDR and SOX processes for receiving radio signals.

    :param frequency: The frequency in MHz to tune the RTL-SDR to.
    :return: None
    """
    global rtl_process
    global sox_process

    if rtl_process is not None:
        rtl_process.terminate()
        rtl_process = None

    if sox_process is not None:
        sox_process.terminate()
        sox_process = None

    rtl_command = [
        'rtl_fm',
        f'-f {frequency}M',
        '-M fm',
        '-s 200k',
        '-A fast',
        '-E deemp'
    ]
    sox_command = [
        'sox',
        '-t', 'raw',
        '-r', '200k',
        '-e', 'signed-integer',
        '-b', '16',
        '-c', '1',
        '-V1',
        '-',
        '-d'
    ]

    try:
        rtl_process = Popen(rtl_command, stdout=PIPE)
        sox_process = Popen(sox_command, stdin=rtl_process.stdout)
        update_frequency_label()
    except Exception as e:
        print(f"Error starting processes: {e}")
        if rtl_process is not None:
            rtl_process.terminate()
        if sox_process is not None:
            sox_process.terminate()


def debounce(func):
    """
    .. function:: debounce(func)

       Function decorator to debounce rapid function calls.

       :param func: The function to be debounced.
       :type func: callable
       :return: The debounced function.
       :rtype: callable

    """
    def wrapper(pin=None):
        global last_button_press_time

        current_time = time()
        if current_time - last_button_press_time >= DEBOUNCE_TIME:
            last_button_press_time = current_time
            func(pin)

    return wrapper


@debounce
def increase_frequency(pin=None) -> None:
    """
    Increase the current frequency by a specified frequency step.

    :param pin: The pin to increase the frequency for.
    :return: None
    """
    global current_frequency

    _ = pin
    if current_frequency < MAX_FREQUENCY:
        current_frequency += FREQUENCY_STEP
        start_rtl_sdr(round(current_frequency, 1))


@debounce
def decrease_frequency(pin=None) -> None:
    """
    Decreases the current frequency by a specified frequency step.

    :param pin: The pin to decrease the frequency for.
    :return: None
    """
    global current_frequency

    _ = pin
    if current_frequency > MIN_FREQUENCY:
        current_frequency -= FREQUENCY_STEP
        start_rtl_sdr(round(current_frequency, 1))


if __name__ == '__main__':
    Board("UNIHIKER").begin()

    rtl_process = None
    sox_process = None
    current_frequency = 105.6
    last_button_press_time = 0

    root = Tk()
    root.resizable(False, False)
    root.geometry(f'{SCREEN_WIDTH}x{SCREEN_HEIGHT}+0+0')
    root.config(bg='gray')

    image_label = Label(root)
    image_label.pack(fill='both')
    image_src = Image.open("img/radio.png")
    image = ImageTk.PhotoImage(image_src)
    image_label.config(image=image)

    frequency_label = Label(root, text=f'{current_frequency} MHz', font=('Arial', 24), bg='gray', fg='white')
    frequency_label.pack(padx=10, pady=25)

    start_rtl_sdr(current_frequency)

    button_a.irq(trigger=Pin.IRQ_RISING, handler=increase_frequency)
    button_b.irq(trigger=Pin.IRQ_RISING, handler=decrease_frequency)

    root.mainloop()

Key Components of the Code:

 

start_rtl_sdr(frequency): Starts the RTL-SDR and SOX processes for receiving and playing FM signals.

 

increase_frequency(pin=None) and decrease_frequency(pin=None): Handlers for increasing or decreasing the frequency when buttons are pressed.

 

debounce(func): Decorator to prevent multiple rapid calls to button handlers.

STEP 6
Send Project (Files) to UNIHIKER

To send the project files to the UNIHIKER board, you can use tools like SCP or SMB. Here's an example using SCP:

 

$ scp -r RTL-SDR root@10.1.2.3:/root/

STEP 7
Start application

Attention: Before starting, make sure that the speakers (via Bluetooth) and the RT-SDR dongle (via USB) are connected!

 

You can start the application via the touch display or via command line. Here the example via command-line.

 

$ python3 /root/RTL-SDR/main.py

 

If everything works up to this point, use the two UNIHIKER buttons (right side) to change the frequency. Since the Python script creates/terminates Linux processes in the background, don't press too quickly (otherwise it will cause zombie processes and crash). Maybe you can optimize these problems yourself!

STEP 8
Annotations

Here are some additional project ideas and enhancements that you can explore using the information from this tutorial:

 

- Implement an automatic scanning feature to find available FM stations. The program can scan through a range of frequencies and identify stations based on signal strength.

 

- Enable the recording of audio streams from the FM radio to files. This can be achieved by redirecting the output of the sox command to a file instead of the audio output device (or both via tee).

 

- Implement RDS decoding to display additional information transmitted by FM stations, such as the station name, song title, and artist. This requires parsing the RDS data stream from the RTL-SDR output.

 

- Add a graphical equalizer to adjust audio output settings such as bass and treble.

 

- Implement an alarm clock feature where the FM radio can turn on automatically at a set time, tuning to a specific station. This could be a fun alternative to traditional alarms.

 

 

License
All Rights
Reserved
licensBg
0