Trunet Radio

Trunet's Place - Portfolio
Jump to: navigation, search
This article is currently undergoing a major expansion or revamping by the author who placed this notice here. However, you are free to help in the construction of this page by improving it. Please review the edit history if you would like to contact the user who put up this notice. If this article has not been edited by that user in a while, please remove this template.

Description

A simple multi band(AM, FM) radio with RDS displaying on LCD and capacity to be controlled wireless. This project shows a working radio being controlled by an IPhone using TouchOSC. I choose TouchOSC because of its editor that turns possible to design your remote control interface in your PC, MAC or Linux.

"Open Sound Control (OSC) is a protocol for communication among computers, sound synthesizers, and other multimedia devices that is optimized for modern networking technology."

BOM(Bill of material)

Hardware

Though this project is using a shield, I'll not post a schematic here. Just stack the radio shield in your arduino uno.

Fixing your Si4735

Logic Level Converter

Thanks to mikem on this post on arduino forum, it's possible and easy to fix your shield. Follow the pictures below to solder the 1N4148 diode on the shield. Don't forget to cut the RIGHT trace below your shield.

Arduino is 5V and Si4735 is 3.3v. Arduino could't see the difference between HIGH/LOW. Some excerpts from forum post:

  • "Adding a diode shifts the voltage seen by the Arduino up by 0.6V".
  • "Arduino is configured with a pullup on pin 12. So while GPO1 varies between 0 and 3V, the voltage seen by the Arduino varies between 0.6 and 3.6V. The diode acts like a voltage shifter."

The steps are:

  • Soldering the diode

IMG 8955.JPG

  • Cutting the trace

IMG 8954.JPG

An alternative to this is to use a logic level converter from spark fun, read more on ka1kjz blog. This option will make impossible to solder the xbee socket on shield.

AREF tied to GND

Another problem as noted by a sparkfun member on this comment is the AREF tied to GND on the shield, so all your analog reads will be 1023. Cut the AREF pin out of the shield.

LCD

The trimpot is first pin GND, middle pin on pin 3 of your LCD, last pin +5V. Note that trimpots/potentiometers normally are unpolarized though it's possible to invert GND/+5V connections.

Connect LCD following this table:

LCD Pin Arduino Pin
1 GND
2 +5V
3 TRIMPOT
4 A0
5 GND
6 7
11 6
12 5
13 4
14 3
15 +5V
16 GND

XBee

Start soldering the 2mm 10 pin XBee Sockets on the breakout board and than the break away headers. After this, solder the two 10k resistors on the first line of Si4735 shield protoboard and then the xbee breakout. I have some pictures to you.

IMG 0195.JPG IMG 0194.JPG IMG 0190.JPG IMG 0195.JPG

Software

Arduino

You'll need:

Si4735 Library

Sketch

#include <LiquidCrystal.h>
#include <SPI.h>
#include <Si4735.h>
#include <XBee.h>
 
Si4735 radio;
 
XBee xbee = XBee();
 
uint8_t payload[] = { 0, 0, 0 };
 
XBeeAddress64 addr64 = XBeeAddress64(0x0013a200, 0x40665db3);
ZBTxRequest zbTx = ZBTxRequest(addr64, payload, sizeof(payload));
XBeeResponse response = XBeeResponse();
ZBRxResponse rx = ZBRxResponse();
ZBTxStatusResponse txStatus = ZBTxStatusResponse();
 
long lastUpdate;
byte pos;
 
LiquidCrystal lcd(14, 7, 6, 5, 4, 3);
 
void setup() {
  xbee.begin(38400);
 
  lcd.begin(16, 2);
  lcd.clear();
 
  radio.begin(FM);
 
  radio.tuneFrequency(9810);
 
  lastUpdate = millis();
  pos = 0;
}
 
void loop() {
  xbee.readPacket();
  if (xbee.getResponse().isAvailable()) {
    if (xbee.getResponse().getApiId() == ZB_RX_RESPONSE) {
      xbee.getResponse().getZBRxResponse(rx);
      // 0x01 = seek up
      // 0x02 = seek down
      // 0x03 + BYTE + BYTE = tune frequency
      // 0x04 = getFrequency, answers type 0x04, highFreq, lowFreq
      // 0x11 = volume up
      // 0x12 = volume down
      // 0x13 + BYTE = set volume(0-63)
      // 0x14 = get volume, answers 0x14, volume(0-63)
      // 0x15 = mute
      // 0x16 = unmute
      switch(rx.getData(0)) {
        case 0x01:
          radio.seekUp();
          break;
        case 0x02:
          radio.seekDown();
          break;
        case 0x03:
          radio.tuneFrequency((int)(rx.getData(1) * 256) + rx.getData(2));
          break;
        case 0x04:
          bool valid;
          int freq;
          byte b1, b2;
          freq = radio.getFrequency(valid);
          b1 = freq >> 8;
	  b2 = freq & 0x00FF;
          payload[0] = 0x04;
          payload[1] = b1;
          payload[2] = b2;
          xbee.send(zbTx);
          break;
        case 0x11:
          radio.volumeUp();
          break;
        case 0x12:
          radio.volumeDown();
          break;
        case 0x13:
          if (rx.getData(1) >= 0 && rx.getData(1) <= 63) {
            radio.setVolume(rx.getData(1));
          }
          break;
        case 0x14:
          byte volume;
          volume = radio.getVolume();
          payload[0] = 0x14;
          payload[1] = volume;
          xbee.send(zbTx);
          break;
        case 0x15:
          radio.mute();
          break;
        case 0x16:
          radio.unmute();
          break;
        case 0xf0:
          if (rx.getData(1) == 0) {
            radio.begin(FM);
          } else if (rx.getData(1) == 1) {
            radio.begin(AM);
          } else if (rx.getData(1) == 2) {
            radio.begin(SW);
          } else if (rx.getData(1) == 3) {
            radio.begin(LW);
          }
          break;
        case 0xf1:
          radio.end();
          break;
        default:
          break;
      }
    }
  }
 
  //RDS to LCD
  radio.readRDS();
 
  if ((millis() - lastUpdate) > 500) {
    lastUpdate = millis();
    char ps[9];
    char radioText[65];
    radio.getRDS(ps, radioText);
 
    lcd.setCursor(0, 0);
    if (strlen(ps) == 8) {
      lcd.print(ps);
    }
 
    if (strlen(radioText) == 64) {
      if (pos < 64 - 16) {
        for (byte i=0; i<16; i++) {
          lcd.setCursor(i, 1);
          lcd.print(radioText[pos + i]);
        }
      } else {
        byte nChars = 64 - pos;
        for (byte i=0; i<nChars; i++) {
          lcd.setCursor(i, 1);
          lcd.print(radioText[pos + i]);
        }
        for(byte i=0; i<(16 - nChars); i++) {
          lcd.setCursor(nChars + i, 1);
          lcd.print(radioText[i]);
        }
      }
      pos++;
      if(pos >= 64) pos = 0;
    }
  }
}

Client Software

It's written in python. This software is work in progress as I make it work with txOSC on python.

You'll need:

fmradio.py

from xbee import ZigBee
import serial
 
ser = serial.Serial('/dev/tty.usbserial-A600ezgH', 38400)
 
xbee = ZigBee(ser, escaped=True)
 
addr = "\x00\x13\xA2\x00\x40\x6F\xB9\xC4"
 
# 0xf0 = turn on radio and mode selection(below)
# 0x00 = FM
# 0x01 = AM
# 0x02 = SW
# 0x03 = LW
#xbee.send("tx", frame_id="\x01", dest_addr_long=addr, dest_addr="\xff\xfe", data="\xf0\x00")
 
# 0xf1 = turn off
#xbee.send("tx", frame_id="\x01", dest_addr_long=addr, dest_addr="\xff\xfe", data="\xf1")
 
# 0x01 = seek up
#xbee.send("tx", frame_id="\x01", dest_addr_long=addr, dest_addr="\xff\xfe", data="\x01")
 
# 0x02 = seek down
#xbee.send("tx", frame_id="\x01", dest_addr_long=addr, dest_addr="\xff\xfe", data="\x02")
 
# 0x03 = set frequency
freq = 9810 # 98.1 Mhz
b1 = freq & 0xff
b2 = freq >> 8
xbee.send("tx", frame_id="\x01", dest_addr_long=addr, dest_addr="\xff\xfe", data="\x03" + chr(b2) + chr(b1))
 
# 0x04 = get frequency
#xbee.send("tx", frame_id="\x01", dest_addr_long=addr, dest_addr="\xff\xfe", data="\x04")
 
# 0x11 = volume up
#xbee.send("tx", frame_id="\x01", dest_addr_long=addr, dest_addr="\xff\xfe", data="\x11")
 
# 0x12 = volume down
#xbee.send("tx", frame_id="\x01", dest_addr_long=addr, dest_addr="\xff\xfe", data="\x12")
 
# 0x13 = set volume
volume = 63
#xbee.send("tx", frame_id="\x01", dest_addr_long=addr, dest_addr="\xff\xfe", data="\x13" + chr(volume))
 
# 0x14 = get volume
#xbee.send("tx", frame_id="\x01", dest_addr_long=addr, dest_addr="\xff\xfe", data="\x14")
 
# 0x15 = mute
#xbee.send("tx", frame_id="\x01", dest_addr_long=addr, dest_addr="\xff\xfe", data="\x15")
 
# 0x16 = unmute
#xbee.send("tx", frame_id="\x01", dest_addr_long=addr, dest_addr="\xff\xfe", data="\x16")
 
while True:
	try:
		response = xbee.wait_read_frame()
		print response
	except KeyboardInterrupt:
		break
	if (response.get("rf_data")):
		if (response.get("rf_data")[0] == "\x04"):
			fr1 = ord(response.get("rf_data")[1]) * 256
			fr2 = ord(response.get("rf_data")[2])
			print "Station: " + str(float(fr1 + fr2)/100)
		elif (response.get("rf_data")[0] == "\x14"):
			print "Volume: " + str(ord(response.get("rf_data")[1]))
 
ser.close()

IPhone TouchOSC Interface

I'm implementing, not ready yet.

Help/Support

As always, if you need any help, ask on my forum. I'm always looking and answering things there.