Maker.io main logo

PiPyPirate Radio

2023-07-18 | By Adafruit Industries

License: See Original Project

Courtesy of Adafruit

Guide by Carter Nelson

Overview

ship_1

So called "pirate" radio stations have existed as long as radio ‎broadcast has existed. These unlicensed operator’s setup shop in ‎various locations and start broadcasting whatever content they ‎wished. Shown above is the lightship used as a base for Radio ‎Veronica broadcasting offshore of the Netherlands in the 1960's. ‎Being based offshore on a boat makes it extra pirate-y!

In this guide we'll show how to use the Adafruit Si4713 FM ‎Transmitter to create your own little pirate radio station. A Raspberry ‎Pi provides the source for music storage and playback. Attaching a ‎small 1.3" PiTFT provides a display for playback song information and ‎status.

FM Radio? Really?

Pretty much no one carries a personal FM radio receiver with them ‎these days. So, if you're all like pepperidge-farm-remembers / i-was-‎there-Gandalf at the mention of FM radio and wondering what's the ‎point, here are some ideas:

  • Create a personal radio station for someone that does not have ‎a smartphone. Perhaps for an older generation member more ‎comfortable with FM radio technology, as was heartwarmingly ‎done in this post

  • Broadcast audio to cars at an ad hoc "drive in" style gathering. ‎Cars still have FM radio receivers

  • Other imaginative use :)

Parts

Hardware Setup

In this guide we demonstrate using a Raspberry Pi Model 3B. ‎However, the processing being done is minimal, and actually any ‎model Pi could probably handle this task. A USB port makes ‎connecting the USB audio adapter easy.

The 1.3" PiTFT used is another trade-off. It's pretty small and only ‎provides two buttons for user interaction. But it leaves some of the ‎Pi's GPIO pins available, which are needed for connecting the ‎Si4713's reset line. Also, it has a STEMMA QT connector which makes ‎the I2C connections easy.

Here's a connection diagram of the overall hardware setup.

setup_2

  • Attach the PiTFT to the Pi GPIO header

  • Use the STEMMA QT connector on the PiTFT to connect to ‎power and I2C to the Si4713

  • Connect Si4713 RST to GPIO 26 on the Pi

  • Plug the USB audio adapter into an available USB port on the ‎Pi

  • Connect the USB audio adapter to the Si4713 input using ‎an audio cable

Here is a list of the various hardware items shown.

The Si4714 is the FM radio transmitter:

For getting good audio out from the Raspberry Pi, a USB audio ‎adapter is used:

To connect the USB audio adapter to the FM radio, a 3.5mm stereo ‎plug/plug cable is needed. This cable is nothing special and one is ‎often included with various media devices. So, check your electronic ‎drawer first - you may already have one.

The 1.3" PiTFT provides status and buttons for user interaction:

The Si4713 is connected to the Pi through the PiTFT's STEMMA QT ‎connector. This cable can be used:

And of course, need a Raspberry Pi. We based this guide on the Pi 3 ‎Model B:

The setup above also shows a breadboard. But direct connections to ‎the Si4713 is also possible.

Software Setup

OK, let's get all the necessary software bits installed and checked out. ‎The first few are covered in other guides, which are linked to from ‎here. It works best to do these in the order shown here, checking ‎that each step works before moving to the next.

Use the Lite version of the Raspberry Pi OS.

Blinka Installation

Follow this guide page for initial Pi setup (including the OS) and ‎Blinka installation:

Blinka Pi Installation

Be sure the blinkatest.py script from that setup runs as expected ‎before moving on.

PiTFT Setup

This guide uses the PiTFT directly via Python. So, follow this setup ‎page from the PiTFT main guide:

PiTFT Setup

The guide covers a couple of TFTs, and ‎the rgb_display_minipitfttest.py test script appears to be configured ‎for the smaller TFT - not the one used in this guide. ‎However, it's a simple fix to change to the 1.3" TFT.

Change these lines:

Download File

Copy Code
display = st7789.ST7789(
    board.SPI(),
    cs=cs_pin,
    dc=dc_pin,
    rst=reset_pin,
    baudrate=BAUDRATE,
    width=135,
    height=240,
    x_offset=53,
    y_offset=40,
)

to this:

Download File

Copy Code
disp = st7789.ST7789(
    board.SPI(),
    cs=cs_pin,
    dc=dc_pin,
    rst=reset_pin,
    baudrate=BAUDRATE,
    width=240,
    height=240,
    x_offset=0,
    y_offset=80,
)

Don't skip the DejaVu TFT Font installation done in this guide. Those ‎fonts are used again later.

FM Radio Setup

To install the CircuitPython library for the Si4713 FM radio transmitter, ‎follow this guide page:

Si4713 Setup

Test using the frequency scan example from that guide. If that runs ‎OK, then it should be installed and working correctly.

USB Audio Adapter Setup

The Pi has built in support for the USB audio adapter, so there's ‎nothing extra needed in terms of software. Simply plug the USB ‎audio adapter into one of the Pi's USB ports and run the following ‎command:

Copy Code
aplay -l

This will list the audio devices that the Pi has. There will likely be ‎more than one, but the USB audio adapter should show up in the list. ‎Look for C-Media USB Headphone Set in the output.‎

Here's an example:

audioset_3

The USB audio adapter shows up as card 2.

Take note of what card number the USB audio adapter shows up as. ‎This may be needed later.

Music Player Software

To serve up audio, we'll use the Music Player Daemon software. To ‎install it, along with a couple of other tools, run the following:

Copy Code
sudo apt-get install mpd mpc ncmpc

This will install:

  • mpd - the main music server (daemon)

  • mpc - a simple command line tool for controlling mpd

  • ncmpc - a simple text-based interface music player

After installation, run the following as a simple test:

Copy Code
mpc status

This just queries the current status of the server, which won't be ‎anything exciting at this point. It should look like this:

queries_4

But that verifies the server has been installed correctly and is ‎running.

Python Library for MPD

The python-mpd2 library allows for controlling mpd playback from ‎within Python. This is another pip install:

Copy Code
sudo pip3 install python-mpd2

MPD Configuration

Music and Playlists Folders

We'll store music and playlist files in the default pi users home ‎directory. Use mkdir to create these folders. An initial cd is done to ‎make sure the current directory is the pi user's home directory ‎‎(/home/pi).

Run these commands:

Copy Code
cd
mkdir music playlists

Nothing exciting should happen.

folders_5

  • The /home/pi/music directory will contain the actual music ‎files, like MP3s, etc

  • The /home/pi/playlists directory will contain playlists

MPD Configuration

The main file that controls the mpd configuration is located ‎in /etc/mpd.conf. A default file is added during the installation of ‎mpd. There is a lot of content in this file, however the vast majority is ‎commented out and is just there for reference. For this guide, it is ‎possible to use a very minimal configuration.

First, let's move (rename) the original file so it's still available as a ‎backup:

Copy Code
sudo mv /etc/mpd.conf /etc/mpd_orig.conf

Now use a text editor to add the contents below to a new ‎empty /etc/mpd.conf.

‎Download File‎

Copy Code
music_directory		"/home/pi/music"
playlist_directory	"/home/pi/playlists"

audio_output {
       type            "alsa"
       name            "USB Audio Adapter" 
       device          "hw:2,0"
}

This sets the following:

  • Sets the music_directory and playlist_directory to the ‎locations we created previously

  • The audio_output lines enable using the USB audio adapter. ‎Actually, selecting this output is done later

The 2 in "hw:2,0" is the card number for the USB audio adapter. ‎Change this number as needed based on alplay -l output.

Adding this content to /etc/mpd.conf can be done using the nano text editor as shown below.

Use the command sudo nano /etc/mpd.conf to open the nano editor ‎on a new file.

command_6

The contents should initially be empty. If not, make sure a backup ‎was made, and then just delete everything.

backup_7

Now copy the configuration file contents provided above and paste it ‎into the file.

copy_8

Press <CTRL><X> to exit. It will prompt to save the file. Press Y and ‎hit <ENTER>.

press_9

If nano asks for a file name, make sure it's /etc/mpd.conf and ‎press <ENTER>.

name_10

Restart MPD Server

For the configuration changes to take effect, the mpd server needs ‎to be restarted. Use the following command:

Copy Code
sudo systemctl restart mpd

This should not generate any additional output. So, running mpc ‎status as a quick sanity check can help to make sure the mpd server ‎actually did restart.

status_11

For comparison, here is what mpc status will output if the mpd server ‎is not running:

comparison_12

Adding Music

There are two steps for adding music:

  • Add the music files (mp3, etc. ) to the /home/pi/music directory

  • Create playlists files (m3u) in the /home/pi/playlists directory

Playlists are really an optional feature from mpd's point of view. ‎However, the Python radio program works by specifying a playlist to ‎use for broadcast. So, we'll go through both steps.

Add Music Files

This is pretty simple, just dump all the mp3 files into ‎the music directory:

Copy Code
music
├── song1.mp3
└── song2.mp3

However, if there are a lot of music files, from numerous artists and ‎albums, then this can get messy. The mpd server will search the ‎music folder recursively. So, a good way to organize things is into a ‎hierarchy of artist/album/song. Something like this:‎

Copy Code
music
├── artist1
│ ├── album1
│ │├── song1.mp3
│ │ └── song2.mp3
│ └── album2
│     └── song1.mp3
└── artists2

To provide examples for this guide, we'll use these two short mp3 ‎files. Click each button to download them.

beats.mp3‎

happy.mp3‎

Copy them to the Raspberry Pi. To keep things simple, we'll just ‎place them directly in the music directory, so it ends up looking like ‎this:

Copy Code
music
├── beats.mp3
└── happy.mp3

Create Playlist

A playlist is a simple text file with a .m3u extension. Each line of the ‎playlist file references a music file. The full path of the file relative to ‎the music folder is used.

Let's create one called test.m3u using the two example files ‎downloaded in the previous section.

Use nano to create the test.m3u playlist file in the playlist directory.

create_13

Add the names of the two mp3 files.

add_14

Press <CTRL><X> to save and exit.

save_15

Multiple playlist files can be created and stored in ‎the playlist directory.

Update MPD

Anytime new music files are added, or playlists added or updated, ‎the mpd server needs to be told to update its internal database. This can be done using mpc:

Copy Code
mpc update

This will generally be very fast. But if there are a lot of new files to be ‎searched through, it can take many seconds. The update progress ‎can be checked using the status command with mpc. If this does not ‎show "Updating DB", then the update is complete.

updating_16

What's The Frequency, Kenneth?

The Si4713 FM Radio Transmitter uses the same FM radio band as ‎regular domestic radio. So, a lot of frequencies will already be ‎occupied by local radio stations - like all the ones you hear driving ‎around in your car.

To find an available frequency, we can use the Si4713 itself, which ‎has the built-in ability to measure the noise for a given FM frequency. ‎By scanning across the frequencies in the FM broadcast band, ‎locations where frequencies are occupied can be determined.

Use the code below to run a frequency scan. This is really just ‎the same basic demo from the Si4713 guide, with added output to a ‎file.

Download Project Bundle

Copy Code
# SPDX-FileCopyrightText: 2023 Carter N. for Adafruit Industries
# SPDX-License-Identifier: MIT

import board
import digitalio
import adafruit_si4713

radio = adafruit_si4713.SI4713(
    board.I2C(),
    reset=digitalio.DigitalInOut(board.D26),
    timeout_s=0.5
)

with open("freq_scan.dat", "w") as fp:
    for f_khz in range(87500, 108000, 50):
        noise = radio.received_noise_level(f_khz)
        fp.write("{},{}\n".format(f_khz/1000.0, noise))
        print('{0:0.3f} mhz = {1} dBuV'.format(f_khz/1000.0, noise))

View on GitHub

The output is simple. For each frequency, a relative noise level is ‎given. The higher the value, the stronger the radio station signal. So, ‎look for frequencies with the lowest values.

The data can also be plotted to help find the occupied areas. Pick a ‎frequency somewhere between the peaks.

plotted_17

Radio Code

At this point, all the supporting software pieces should be in place, ‎music has been added, playlist(s) created, and an available ‎broadcast frequency has been determined. Now we can use a ‎Python program to configure the Si4713 FM radio transmitter, start ‎music playback, and use the TFT to provide status and basic control ‎‎(via the buttons).

Here's the code. Save a copy of this as radio.py in the pi users home ‎directory /home/pi.

The default code expects the music files and playlist added from the ‎previous section to be in place.

‎Download Project Bundle

Copy Code
# SPDX-FileCopyrightText: 2023 Carter N. for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import board
import digitalio
import adafruit_si4713
from PIL import Image, ImageDraw, ImageFont
from adafruit_rgb_display import st7789
import mpd

#--| User Config |-----------------------------------
FREQ = 89.00
PLAYLIST = "test"
STATION_NAME = "PiPyPirate Radio"
UPDATE_RATE = 0.5
#----------------------------------------------------

#==| SETUP |=========================================================

# Display
disp = st7789.ST7789(
    board.SPI(),
    height=240,
    y_offset=80,
    rotation=180,
    cs=digitalio.DigitalInOut(board.CE0),
    dc=digitalio.DigitalInOut(board.D25),
    rst=digitalio.DigitalInOut(board.D24),
    baudrate=64000000,
)

backlight = digitalio.DigitalInOut(board.D22)
backlight.switch_to_output()
backlight.value = True

background = Image.open("radio_bg.png")
STAT_FNT = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed-Bold.ttf", 55)
STAT_CLR = (30, 100, 200)
INFO_FNT = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)
ARTS_CLR = (0, 100, 0)
ALBM_CLR = (0, 100, 0)
TITL_CLR = (0, 100, 0)
PROG_CLR = (0, 100, 0)

# Buttons
button1 = digitalio.DigitalInOut(board.D23)
button1.switch_to_input(pull=digitalio.Pull.UP)
button2 = digitalio.DigitalInOut(board.D24)
button2.switch_to_input(pull=digitalio.Pull.UP)

# Radio
radio = adafruit_si4713.SI4713(
    board.I2C(),
    reset=digitalio.DigitalInOut(board.D26),
    timeout_s = 0.5
)
radio.tx_frequency_khz = int(FREQ * 1000)
radio.tx_power = 115
radio.configure_rds(0xADAF, station=STATION_NAME.encode())

# MPD
mpc = mpd.MPDClient()
mpc.connect("localhost", 6600)
mpc.stop()
mpc.clear()
mpc.load(PLAYLIST)
mpc.play()
mpc.repeat(1)
#====================================================================

def button1_handler():
    if status['state'] == 'play':
        mpc.pause()
    else:
        mpc.play()

def button2_handler():
    mpc.next()

def update_display():
    image = background.copy()
    draw = ImageDraw.Draw(image)

    draw.text(
        (150, 20),
        "{:>5.1f}".format(FREQ),
        anchor="mt",
        font=STAT_FNT,
        fill=STAT_CLR
    )

    if status['state'] == 'play':
        r = 10 * (1 + int(time.monotonic() % 3))
        draw.arc( (30-r, 20-r, 30+r, 20+r),
            120, 60,
            fill = (0, 0, 0),
            width = 3
        )

    info = mpc.currentsong()
    artist = info.get('artist', 'unknown')
    album = info.get('album', 'unknown')
    song = info.get('title', 'unknown')
    draw.text( (5, 150), artist, font=INFO_FNT, fill=ARTS_CLR )
    draw.text( (5, 170), album, font=INFO_FNT, fill=ALBM_CLR)
    draw.text( (5, 190), song, font=INFO_FNT, fill=TITL_CLR)
    rds_info = "{}:{}:{}".format(artist, album, song)
    radio.rds_buffer = rds_info.encode()

    perc = float(status['elapsed']) / float(status['duration'])
    draw.rectangle( (5, 215, 235, 230), outline=PROG_CLR)
    draw.rectangle (
        (5, 215, 5 + int(230*perc), 230),
        fill=PROG_CLR
    )

    disp.image(image)

last_update = time.monotonic()

print("Now broadcasting {} on {}FM".format(STATION_NAME, FREQ))

while True:
    now = time.monotonic()
    try:
        status = mpc.status()
    except ConnectionError:
        mpc.connect("localhost", 6600)
        status = mpc.status()
    if not button1.value:
        button1_handler()
        while not button1.value:
            time.sleep(0.001)
    if not button2.value:
        button2_handler()
        while not button2.value:
            time.sleep(0.001)
    if now - last_update > UPDATE_RATE:
        update_display()
        last_update = now

View on GitHub

Background Image

A static image file is used for the background on the PiTFT. ‎Download this image:

static_18

And save it as radio_bg.py onto the Pi in /home/pi - the same ‎directory where radio.py is saved.

Configure and Run

There are a few lines at the top of the radio.py code that can be ‎changed. Look for these lines:

Download File

Copy Code
#--| User Config |-----------------------------------
FREQ = 89.00
PLAYLIST = "test"
STATION_NAME = "PiPyPirate Radio"
UPDATE_RATE = 0.5
#----------------------------------------------------

And change, if needed, as follows:

  • FREQ - Set this to the frequency that was found to be available ‎from the frequency scan performed in the previous section

  • PLAYLIST - Set this to the playlist that will be broadcast over ‎the radio. The default test playlist was created earlier in this ‎guide

  • STATION_NAME - Change this text to be your station name. It ‎actually gets broadcast (via RDS) and will show up on radios ‎that display this kind of information

  • UPDATE_RATE - This sets how often the TFT display is ‎refreshed, in seconds. The default 0.5 value should be fine, but ‎the adjustment is here if needed

Once the changes have been saved, run the radio.py program to ‎start broadcasting:‎

Copy Code
python3 radio.py

It will print the station name and frequency being used. As long as ‎the program is running, the radio is broadcasting.

print_19

Info and Status

When the radio.py program runs, the PiTFT is updated with ‎playback information and status. ‎

  1. Animated radio "waves" will show around the antenna if the ‎radio is broadcasting.‎

  2. These three lines show artists, album, and title for the current ‎song playing.‎

  3. Along the bottom is a playback progress bar.‎

  4. The billboard displays the currently set broadcast frequency. ‎Tune to this!

info_20

Playback Control

The two buttons on the PiTFT provide some minimal control of ‎playback.

  1. Pause/resume playback. The radio "waves" will stop animating ‎when paused.

  2. Skip to next song in playlist.

playlist_21

制造商零件编号 1958
KIT FM XMITTER RDS/RBDS SI4713
Adafruit Industries LLC
制造商零件编号 1475
USB AUDIO ADAPTER - WORKS WITH R
Adafruit Industries LLC
制造商零件编号 4484
MINI PITFT 1.3" FOR RASPBERRY PI
Adafruit Industries LLC
制造商零件编号 SC0022
SBC 1.2GHZ 4 CORE 1GB RAM
Raspberry Pi
制造商零件编号 4209
JST SH 4-PIN TO PREMIUM MALE HEA
Adafruit Industries LLC
Add all DigiKey Parts to Cart
TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum