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).
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.
```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)
```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
```
To connect and test a Bluetooth audio device, follow these steps:
```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
```
Before proceeding, ensure that your RTL-SDR dongle is functioning correctly:
```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).
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:
Below is the Python code (main.py) to create an FM radio receiver using UNIHIKER and RTL-SDR:
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.
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 [email protected]:/root/
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!
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.