Pi FM Radio

There are few options for FM radio projects on the Pi, such as the RDA5807M and TEA5767 chips. We’ve built an Arduino FM radio using the RDA5807M chips but we found the documentation to be quite weak. There are some Python libraries for TEA5767 chips, unfortunately however you have no volume control with this chip.

RTL-SDR is a software defined radio (SDR) that uses a DVB-T TV tuner dongle based on the RTL2832U chipset. RTL-SDR dongles are well priced at $10-$15 and they are easy to use for Pi FM radio projects. Software Defined Radios have a large list applications that can done, some of the cooler projects include: tracking airplanes and ships, free-to-air TV, and monitoring satellite data.

Getting Started

For setup an external speaker is required (powered speakers work the best). The RTL-SDR dongle includes an externally connected antenna, if possible try to place the antenna close to a window.

fm_setup

To install the basic software enter:


sudo apt-get install rtl-sdr

The rtl_fm utility is an FM demodulator that is used to read and sample a selected frequency. The output of rtl_fm needs to be directed to an audio player program such as aplay. There are a few sampling and buffering options that must be defined. The syntax with the required options to play an FM radio station at 107.9 MHz is:

rtl_fm -f 107.9e6 -M wbfm -s 200000 -r 48000 | aplay -r 48k -f S16_LE

The rtl_fm application needs to be stopped when a new radio station is selected. If the rtl_fm is running in the background the ps command can be used to find process IDs. The kill command can then be used to terminate the task. An example to find and terminate the rtl_fm task:

pi@raspberrypi:~ $ ps -e | grep rtl_fm
1709 pts/0 00:00:33 rtl_fm
pi@raspberrypi:~ $ kill 1709
pi@raspberrypi:~ $ ps -e | grep rtl_fm
pi@raspberrypi:~ $

Adjusting the Volume

Typically the audio would be from an external speaker rather than the HDMI connection. To force the audio connection, use raspi-config and select the “Advanced” menu option, then selection “Audio”.

advanced

There are a few ways to adjust the volume on the audio. One method is to use the amixer utility. An example to change the radio volume to 70%:

amixer sset "PCM" 70%

A Python Example

For our basic testing we created a simple Python command line application. This application accepts new FM radio frequencies and volume settings. When a new radio frequency is selected the rtl_fm task needs to be stopped and restarted.

This application used a few Python libraries. The os library has the os.system call to shell out to external programs like amixer. The os.kill call terminates processes with a give process ID. The subprocess library has the call:

# simple PI FM radio utility using a RTL SDR dongle 

import subprocess, signal, os

def newstation(station):
    global process, stnum
      
    part1 = "rtl_fm -f " 
    part2 = "e6 -M wbfm -s 200000 -r 48000 | aplay -r 48k -f S16_LE"
    cmd = part1 + station + part2
    
    print 'Playing station :', station 

    # kill the old fm connection
    if process != 0:
        process = int(subprocess.check_output(["pidof","rtl_fm"] ))
        print "Process pid = ", process
        os.kill(process,signal.SIGINT) 

    # start the new fm connection
    print cmd
    process = subprocess.Popen(cmd, shell=True)


def setvolume(thevolume):

    os.system('amixer sset "PCM" ' + thevolume)
    print 'volume = ' , thevolume


process = 0

while True:
    answer = raw_input("Enter a radio station (i.e. 107.9) or volume (i.e. 50%): ")

    if answer.find('%') > 0:  
        setvolume(answer)
    else: 
        newstation(answer)                      

Summary

After the command line FM radio is working, it is possible to create standalone PI radio projects that use pushbuttons, TV remotes or Wii controllers to adjust the volume and stations. We built some projects using PiFace and LCD button shields.

The LCD Python code is below:

#!/usr/bin/python
# Pi FM Radio using an LCD Shield for controlling the stations and volume 

import os, subprocess, signal
import time
import Adafruit_CharLCD as LCD


def newstation(direction):
        global stnum, stations, process

        print 'stnum=',stnum,'direction=',direction              
          
        part1 = "rtl_fm -f " 
        part2 = " -M wbfm -s 200000 -r 48000 | aplay -r 48k -f S16_LE"

        if (stnum + direction  -1):
                stnum = stnum + direction
                print('Playing station :', stations[stnum]) 
                cmd = part1 + stations[stnum] + part2
                if process != 0:
                        process = int(subprocess.check_output(["pidof","rtl_fm"] ))
                        print "Process pid = ", process
                        os.kill(process,signal.SIGINT) 
                # start the new fm connection
                print cmd
                process = subprocess.Popen(cmd, shell=True)
            


def setvolume(voldif):
        global thevolume
        if (thevolume + voldif > 0) and (thevolume + voldif <100):
                thevolume = thevolume + voldif
                os.system('amixer sset "PCM" ' + str(thevolume) + '%')
                print 'volume = ' , thevolume
                

lcd = LCD.Adafruit_CharLCDPlate()
lcd.set_color(0,0,0)

## Add your own stations and station info
stations = ['95.3e6','94.7e6','102.9e6','107.9e6']
sinfo = ['95.3 country', '94.7 light','102.9 easy','Y108 Rock\nHamilton ']
thevolume = 40

stnum = 1               #pick a starting station
process = 0
newstation(0)
lcd.message(sinfo[stnum])
setvolume(thevolume)


print 'Press Ctrl-C to quit.'
while True:
        if lcd.is_pressed(LCD.UP):
                setvolume(5)
                time.sleep(0.25)
        if lcd.is_pressed(LCD.DOWN):
                setvolume(-5)
                time.sleep(0.25)
        if lcd.is_pressed(LCD.LEFT):
                newstation(-1)
                lcd.clear()
                lcd.message(sinfo[stnum])
                time.sleep(0.25)
        if lcd.is_pressed(LCD.RIGHT):
                newstation(1)
                lcd.clear()
                lcd.message(sinfo[stnum])
                time.sleep(0.25)   

Arduino FM Radio

There are a few FM receiver modules that are available. We used an FM module that is based on the RDA5807M chip and unlike some of the other chips this one supports volume control. To keep our project compact we used the Arduino Nano, but we tested the UNO and Mega and they both worked well.

fmradio

There are a lot of options on how to control your FM radio. We liked the idea of using an old TV remote because this allowed us to change the volume and radio stations anywhere in the room. For this project we used:

• an I2C FM receiver module ($13)
• any Arduino module that has SDA and SCL pins
• an IR receiver ($5)
• a small bread board and some jumpers

The FM receiver module works on the I2C bus that connects to the SDA and SCL pins. Depending on which IR chip you use the wiring will vary slight. The key thing is to connect the data pin correctly. For our example we used pin 11.

circuit2

FM Module

When we first starting working this project there was no Arduino FM library available, so we started to by talking directly with the I2C bus. However now an Arduino FM library is available.

The RDA5807M chip [3] on the FM receiver module has a number of register addresses that we needed to write to. The key ones were:

  • REGISTER 2 – Initialize the chip (0xC003)
  • REGISTER 2 – Enable radio communications (0xC00D)
  • REGISTER 3 – Set the frequency
  • REGISTER 5 – Set the volume

We needed to pause between initializing and enabling radio communications. Setting the radio frequency is done by offsetting the frequency from the minimum range (870) and then splitting the value into high and low bytes.

The volume on register 5 is adjusted between a value of 0, the lowest, and 15 or 0xF the highest. There are other options on register 5 so our code changed the value from 0x84D0 to 0x84DF.

The Arduino modules uses the wire.h library to read and write to I2C devices. To write to a register the important commands are:

  • Wire.beginTransmission
  • Wire.write
  • Wire.endTransmission

An example using these commands would be:


//This is an example of setting the volume to 1
//This sets up communications to a device
Wire.beginTransmission(0x11); // 0x11 is the RDA5807M chip
Wire.write(0x05); // address 0x05 is where you want to write to
Wire.write(0x84); // write 0x84 to the first byte of address 0x05
Wire.write(0xD1); // write 0xD1 to the second byte of address 0x05
Wire.endTransmission(); // finish the write

We made a simple radio test program (Listing 1) that set up the radio to a local frequency at volume 1.


#include <Wire.h>

int freq;
int freqB;
byte freqH, freqL;

void setup()
{
Wire.begin();

// Initialize the RDA5807M chip

Wire.beginTransmission(0x11); // Device address is 0x11
Wire.write(0x02); // Register address 0x02
Wire.write(0xC0); Wire.write(0x03); // Initialize the settings
Wire.endTransmission(); // stop condition
delay(500); // wait 500ms

Wire.beginTransmission(0x11); // Device address is 0x11
Wire.write(0x02); // Register address 0x02
Wire.write(0xC0); Wire.write(0x0D); // Setup the radio for communications
Wire.endTransmission();
delay(500);

// Define an FM station to listen to

freq = 1079; // 107.9 MHz our local FM station
freqB = freq - 870; // chip needs to have freq offset from lowest freq (870)
freqH = freqB>>2; // you need to break the offset freq into 2 parts (hi/low)
freqL = (freqB&3)<<6; // Shift channel selection for matching register 0x03

Wire.beginTransmission(0x11);
Wire.write(0x03);
Wire.write(freqH); // write High freq byte
Wire.write(freqL + 0x10); // write Low freq byte
Wire.endTransmission();

// The volume is from 0-F and its the first bytes, leave all the bytes (0x84D0 - 0x84DF)

Wire.beginTransmission(0x11);
Wire.write(0x05);
Wire.write(0x84); Wire.write(0xD1); // set volume to 1
Wire.endTransmission();
}

void loop()
{

}

Finding TV Remote Codes

We used a small program to find the IR (Infrared) codes from our TV remote. For our program we used the volume up, volume down, channel up and channel down keys. Different TV remotes will have different codes so you will have to find the codes that work with your remote.


/*
IR TEST PROGRAM

PINOUTS:

LEFT = DATA PIN
MIDDLE = GND
RIGHT = 3.3 volts

*/
#include <IRremote.h>

int RECV_PIN = 11;

IRrecv irrecv(RECV_PIN);

decode_results results;

void setup()
{
Serial.begin(9600);
irrecv.enableIRIn(); // Start the receiver
Serial.println("Setup Complete");
}

void loop()
{
if (irrecv.decode(&results))
{
Serial.println(results.value, HEX);
irrecv.resume(); // Receive the next value
delay(500);

}
}

ircode

When our IR test program was running we used the Arduino monitor window to show us the different key codes. We manually recorded these codes and used them in our final project.

Using a TV Remote

Our goal was to use a TV remote to change the FM station and to adjust the volume. There are a lot of websites you can use to find nearby radio stations. A good website for this is: http://radio-locator.com.

Our final program  used a number of predefined FM stations that would change with channel up and channel down on the TV remote. The remote’s volume up and down would change the radio’s volume. Remember to change the IR codes to match your TV remote, and the FM stations for your area.


// RDA5807M Radio controlled with a TV Remote
//
#include <Wire.h>
#include <IRremote.h>

int RECV_PIN = 11; //Connect the IR data pin in 11
IRrecv irrecv(RECV_PIN);
decode_results results;

int volume = 0; // start with the volume low
int channellist[]= {999,1029,1021,1079}; // Define some radio stations
int maxc = 4; // Define the number of radio stations used
int channel = 2; // Start with a favorite station

void setup()
{
Wire.begin();

Serial.begin(9600);

irrecv.enableIRIn(); // Start the receiver

radio_init();
setfreq(channellist[channel]);
setvolume(volume);

}
void loop() {
if (irrecv.decode(&results)) {

switch (results.value) {
case 0xE0E0E01F:
Serial.println("vol up");
if (volume < 15) {
volume++;
setvolume(volume);
}
break;
case 0xE0E0D02F:
Serial.println("vol down");
if (volume > 0) {
volume--;
setvolume(volume);
}
break;
case 0xE0E048B7:
Serial.println("chan up");
if (channel < maxc) {
channel++;
setfreq(channellist[channel]);
}
break;
case 0xE0E008F7:
Serial.println("chan down");
if (channel > 0) {
channel--;
setfreq(channellist[channel]);
}
break;
default:
Serial.println(results.value, HEX);
}
irrecv.resume(); // Receive the next value
}
delay(100);
}
//===============================
void setvolume(int thevolume)
{
byte volbyte;

volbyte = thevolume + 0xD0;
Wire.beginTransmission(0x11);
Wire.write(0x05);
Wire.write(0x84); Wire.write(volbyte);
Wire.endTransmission();
delay(500);
}
//===============================
void setfreq(int thefreq)
{
int freqB;
byte freqH, freqL;

freqB = thefreq - 870;
freqH = freqB >> 2;
freqL = (freqB & 3) <<6;

Wire.beginTransmission(0x11);
Wire.write(0x03);
Wire.write(freqH); // write frequency into bits 15:6, set tune bit
Wire.write(freqL + 0x10);
Wire.endTransmission();
delay(500);
}
//================================
void radio_init()
{
Wire.beginTransmission(0x11); // Device address 0x11 (random access)
Wire.write(0x02); // Register address 0x02
Wire.write(0xC0); Wire.write(0x03); // Initialize the settings
Wire.endTransmission();
delay(500); // wait 500ms to finalize setup

Wire.beginTransmission(0x11); // Device address 0x11 (random access)
Wire.write(0x02);
Wire.write(0xC0); Wire.write(0x0D); // Setup radio settings
Wire.endTransmission();
delay(500); // wait 500ms to finalize settings
}

Packaging our Project

We wanted to make our radio project portable, so we came up with two ideas. The first idea was to put all the electronics into a plastic container. For this we used a soap dish ($1) that we drilled three holes in. The first two holes were for the power and speaker cords. The last hole was for the IR receiver. To power our project we used a solar charger ($10), so we could listen to music outside.

soapdish1

Our second idea was to place the electronics inside the pocket of a cooler bag. It is important to have the IR receiver poking out of the pocket.

coolerbag

There are also some fun projects where you could use LCD Keyboard Shields or Touchscreens.

lcd