Trunet Radio
| 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. |
Contents |
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)
- Arduino UNO
- SparkFun SI4735 AM & FM Receiver Shield
- Diode Small Signal - 1N4148 - This is needed because a design mistake from sparkfun guys. See this post on arduino forum.
- Basic 16x2 Character LCD
- 10k Linear Trimpot - To control LCD brightness
- Breakout Board for XBee Module
- 2 x 2mm 10pin XBee Socket
- Break Away Headers - Straight
- 2 x Resistor 10k Ohm 1/6th Watt PTH - XBee is 3.3v and you arduino 5v. We'll make a simple shift logic level using a voltage divider to keep xbee safe.
- XBee 2mW Wire Antenna - Series 2 (ZB) - You'll need an extra Series 2(ZB) XBee plugged on your PC. XBee functioning will not be covered on this project page.
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
- Cutting the trace
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.
Software
Arduino
You'll need:
- XBee-Arduino Library
Si4735 Library
- Available on github: https://github.com/trunet/Si4735
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.