Over the Counter Kitchen Radio

This project used the Si470x FM radio receiver and an ardurino to make an over the counter kitchen radio. These parts had been purchased for a different build that unfortunately met a premature end.

The concept for this project is simple. Play my favorite news and music radio stations while having a form factor that does not take up space on the kitchen counter. I have a small push button that toggles between pre-selected news and music stations built into the side of the radio.

Parts List

  1. Arduino Uno
  2. Si470x breakout board
  3. Logitech computer speakers
  4. 5V wall wart power supply
  5. Scrap plywood

Electrical Connections (Arduino/Si470x)

  • 3.3V / Vcc
  • GND / GND
  • A5 / SCLK
  • A4 / SDIO
  • D2 / RST

Electrical connections for the push button that toggles through the different radio stations is shown in the figure below. The I/O pin is connected to arduino D7.

Pull down resistor configuration for the push buttons.

All of the components were hot glued to a small piece of 1/2″ plywood.

The project was completed by mounting the plywood to the under side of the kitchen cabinets. I used a paper template to lay out the hole pattern on the under side of the cabinets. The final result can barely be seen when standing in the kitchen.

Under the cabinet radio

Here is the arduino code that makes it all tick.

/*
Modified by Ryan Lowe y12m05d26

1-6-2011
Spark Fun Electronics 2011
Nathan Seidle

This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).

To use this code, connect the following 5 wires:
Arduino : Si470x board
3.3V : VCC
GND : GND
A5 : SCLK
A4 : SDIO
D2 : RST
A0 : Trimpot (optional)

Look for serial output at 57600bps.

The Si4703 ACKs the first byte, and NACKs the 2nd byte of a read.

1/18 - after much hacking, I suggest NEVER write to a register without first reading the contents of a chip.
ie, don't updateRegisters without first readRegisters.

If anyone manages to get this datasheet downloaded
http://wenku.baidu.com/view/d6f0e6ee5ef7ba0d4a733b61.html
Please let us know. It seem to be the latest version of the programming guide. It had a change on page 12 (write 0x8100 to 0x07)
that allowed me to get the chip working..

Also, if you happen to find "AN243: Using RDS/RBDS with the Si4701/03", please share. I love it when companies refer to
documents that don't exist.

1/20 - Picking up FM stations from a plane flying over Portugal! Sweet! 93.9MHz sounds a little soft for my tastes,s but
it's in Porteguese.

ToDo:
Display current status (from 0x0A) - done 1/20/11
Add RDS decoding - works, sort of
Volume Up/Down - done 1/20/11
Mute toggle - done 1/20/11
Tune Up/Down - done 1/20/11
Read current channel (0xB0) - done 1/20/11
Setup for Europe - done 1/20/11
Seek up/down - done 1/25/11

The Si4703 breakout does work with line out into a stereo or other amplifier. Be sure to test with different length 3.5mm
cables. Too short of a cable may degrade reception.
*/

#include

int STATUS_LED = 13;
int resetPin = 2;
int SDIO = A4; //SDA/A4 on Arduino
int SCLK = A5; //SCL/A5 on Arduino
char printBuffer[50];
uint16_t si4703_registers[16]; //There are 16 registers, each 16 bits large

#define FAIL 0
#define SUCCESS 1

#define SI4703 0x10 //0b._001.0000 = I2C address of Si4703 - note that the Wire function assumes non-left-shifted I2C address, not 0b.0010.000W
#define I2C_FAIL_MAX 10 //This is the number of attempts we will try to contact the device before erroring out

//#define IN_EUROPE //Use this define to setup European FM reception. I wuz there for a day during testing (TEI 2011).

#define SEEK_DOWN 0 //Direction used for seeking. Default is down
#define SEEK_UP 1

//Define the register names
#define DEVICEID 0x00
#define CHIPID 0x01
#define POWERCFG 0x02
#define CHANNEL 0x03
#define SYSCONFIG1 0x04
#define SYSCONFIG2 0x05
#define STATUSRSSI 0x0A
#define READCHAN 0x0B
#define RDSA 0x0C
#define RDSB 0x0D
#define RDSC 0x0E
#define RDSD 0x0F

//Register 0x02 - POWERCFG
#define SMUTE 15
#define DMUTE 14
#define SKMODE 10
#define SEEKUP 9
#define SEEK 8

//Register 0x03 - CHANNEL
#define TUNE 15

//Register 0x04 - SYSCONFIG1
#define RDS 12
#define DE 11

//Register 0x05 - SYSCONFIG2
#define SPACE1 5
#define SPACE0 4

//Register 0x0A - STATUSRSSI
#define RDSR 15
#define STC 14
#define SFBL 13
#define AFCRL 12
#define RDSS 11
#define STEREO 8

// digital pin 7 has a pushbutton attached to it. Give it a name:
int pushButton = 7;

// radio stations
int country = 977;
int npr = 899;
boolean station = true;

void setup() {
pinMode(13, OUTPUT);
pinMode(A0, INPUT); //Optional trimpot for analog station control

Serial.begin(57600);
Serial.println();

si4703_init(); //Init the Si4703 - we need to toggle SDIO before Wire.begin takes over.

// make the pushbutton's pin an input:
pinMode(pushButton, INPUT);

// set volume to reasonable level
byte current_vol;
si4703_readRegisters(); //Read the current register set
current_vol = si4703_registers[SYSCONFIG2] & 0x000F; //Read the current volume level
current_vol = 7; //
if(current_vol < 16){
si4703_registers[SYSCONFIG2] &= 0xFFF0; //Clear volume bits
si4703_registers[SYSCONFIG2] |= current_vol; //Set new volume
si4703_updateRegisters(); //Update
Serial.print("Volume: ");
Serial.println(current_vol, DEC);
}

// go to default channel
gotoChannel(country); //Default the unit to a known good local radio station
}

void loop() {

if( debounce(pushButton,30) ){ // if button was pushed
station = !station; // togle station variable
if(station){ // go to NPR station
gotoChannel(npr);
}
else{ // go to country station
gotoChannel(country);
}
delay(1000); // wait a second
}
}

//Given a channel, tune to it
//Channel is in MHz, so 973 will tune to 97.3MHz
//Note: gotoChannel will go to illegal channels (ie, greater than 110MHz)
//It's left to the user to limit these if necessary
//Actually, during testing the Si4703 seems to be internally limiting it at 87.5. Neat.
void gotoChannel(int newChannel){
//Freq(MHz) = 0.200(in USA) * Channel + 87.5MHz
//97.3 = 0.2 * Chan + 87.5
//9.8 / 0.2 = 49
newChannel *= 10; //973 * 10 = 9730
newChannel -= 8750; //9730 - 8750 = 980

#ifdef IN_EUROPE
newChannel /= 10; //980 / 10 = 98
#else
newChannel /= 20; //980 / 20 = 49
#endif

//These steps come from AN230 page 20 rev 0.5
si4703_readRegisters();
si4703_registers[CHANNEL] &= 0xFE00; //Clear out the channel bits
si4703_registers[CHANNEL] |= newChannel; //Mask in the new channel
si4703_registers[CHANNEL] |= (1< si4703_updateRegisters();

//delay(60); //Wait 60ms - you can use or skip this delay

//Poll to see if STC is set
while(1) {
si4703_readRegisters();
if( (si4703_registers[STATUSRSSI] & (1< Serial.println("Tuning");
}

si4703_readRegisters();
si4703_registers[CHANNEL] &= ~(1< si4703_updateRegisters();

//Wait for the si4703 to clear the STC as well
while(1) {
si4703_readRegisters();
if( (si4703_registers[STATUSRSSI] & (1< Serial.println("Waiting...");
}
}

//Reads the current channel from READCHAN
//Returns a number like 973 for 97.3MHz
int readChannel(void) {
si4703_readRegisters();
int channel = si4703_registers[READCHAN] & 0x03FF; //Mask out everything but the lower 10 bits

#ifdef IN_EUROPE
//Freq(MHz) = 0.100(in Europe) * Channel + 87.5MHz
//X = 0.1 * Chan + 87.5
channel *= 1; //98 * 1 = 98 - I know this line is silly, but it makes the code look uniform
#else
//Freq(MHz) = 0.200(in USA) * Channel + 87.5MHz
//X = 0.2 * Chan + 87.5
channel *= 2; //49 * 2 = 98
#endif

channel += 875; //98 + 875 = 973
return(channel);
}

//Seeks out the next available station
//Returns the freq if it made it
//Returns zero if failed
byte seek(byte seekDirection){
si4703_readRegisters();

//Set seek mode wrap bit
//si4703_registers[POWERCFG] |= (1<si4703_registers[POWERCFG] &= ~(1<

if(seekDirection == SEEK_DOWN) si4703_registers[POWERCFG] &= ~(1<else si4703_registers[POWERCFG] |= 1<

si4703_registers[POWERCFG] |= (1<

si4703_updateRegisters(); //Seeking will now start

//Poll to see if STC is set
while(1) {
si4703_readRegisters();
if((si4703_registers[STATUSRSSI] & (1<

Serial.print("Trying station:");
Serial.println(readChannel());
}

si4703_readRegisters();
int valueSFBL = si4703_registers[STATUSRSSI] & (1< si4703_registers[POWERCFG] &= ~(1< si4703_updateRegisters();

//Wait for the si4703 to clear the STC as well
while(1) {
si4703_readRegisters();
if( (si4703_registers[STATUSRSSI] & (1< Serial.println("Waiting...");
}

if(valueSFBL) { //The bit was set indicating we hit a band limit or failed to find a station
Serial.println("Seek limit hit"); //Hit limit of band during seek
return(FAIL);
}

Serial.println("Seek complete"); //Tuning complete!
return(SUCCESS);
}

//To get the Si4703 inito 2-wire mode, SEN needs to be high and SDIO needs to be low after a reset
//The breakout board has SEN pulled high, but also has SDIO pulled high. Therefore, after a normal power up
//The Si4703 will be in an unknown state. RST must be controlled
void si4703_init(void) {
Serial.println("Initializing I2C and Si4703");

pinMode(resetPin, OUTPUT);
pinMode(SDIO, OUTPUT); //SDIO is connected to A4 for I2C
digitalWrite(SDIO, LOW); //A low SDIO indicates a 2-wire interface
digitalWrite(resetPin, LOW); //Put Si4703 into reset
delay(1); //Some delays while we allow pins to settle
digitalWrite(resetPin, HIGH); //Bring Si4703 out of reset with SDIO set to low and SEN pulled high with on-board resistor
delay(1); //Allow Si4703 to come out of reset

Wire.begin(); //Now that the unit is reset and I2C inteface mode, we need to begin I2C

si4703_readRegisters(); //Read the current register set
//si4703_registers[0x07] = 0xBC04; //Enable the oscillator, from AN230 page 9, rev 0.5 (DOES NOT WORK, wtf Silicon Labs datasheet?)
si4703_registers[0x07] = 0x8100; //Enable the oscillator, from AN230 page 9, rev 0.61 (works)
si4703_updateRegisters(); //Update

delay(500); //Wait for clock to settle - from AN230 page 9

si4703_readRegisters(); //Read the current register set
si4703_registers[POWERCFG] = 0x4001; //Enable the IC
// si4703_registers[POWERCFG] |= (1<si4703_registers[SYSCONFIG1] |= (1<

#ifdef IN_EUROPE
si4703_registers[SYSCONFIG1] |= (1< si4703_registers[SYSCONFIG2] |= (1< #else
si4703_registers[SYSCONFIG2] &= ~(1< #endif

si4703_registers[SYSCONFIG2] &= 0xFFF0; //Clear volume bits
si4703_registers[SYSCONFIG2] |= 0x0001; //Set volume to lowest
si4703_updateRegisters(); //Update

delay(110); //Max powerup time, from datasheet page 13
}

//Write the current 9 control registers (0x02 to 0x07) to the Si4703
//It's a little weird, you don't write an I2C addres
//The Si4703 assumes you are writing to 0x02 first, then increments
byte si4703_updateRegisters(void) {

Wire.beginTransmission(SI4703);
//A write command automatically begins with register 0x02 so no need to send a write-to address
//First we send the 0x02 to 0x07 control registers
//In general, we should not write to registers 0x08 and 0x09
for(int regSpot = 0x02 ; regSpot < 0x08 ; regSpot++) { byte high_byte = si4703_registers[regSpot] >> 8;
byte low_byte = si4703_registers[regSpot] & 0x00FF;

Wire.write(high_byte); //Upper 8 bits
Wire.write(low_byte); //Lower 8 bits
}

//End this transmission
byte ack = Wire.endTransmission();
if(ack != 0) { //We have a problem!
Serial.print("Write Fail:"); //No ACK!
Serial.println(ack, DEC); //I2C error: 0 = success, 1 = data too long, 2 = rx NACK on address, 3 = rx NACK on data, 4 = other error
return(FAIL);
}

return(SUCCESS);
}

//Read the entire register control set from 0x00 to 0x0F
void si4703_readRegisters(void){

//Si4703 begins reading from register upper register of 0x0A and reads to 0x0F, then loops to 0x00.
Wire.requestFrom(SI4703, 32); //We want to read the entire register set from 0x0A to 0x09 = 32 bytes.

while(Wire.available() < 32) ; //Wait for 16 words/32 bytes to come back from slave I2C device
//We may want some time-out error here

//Remember, register 0x0A comes in first so we have to shuffle the array around a bit
for(int x = 0x0A ; ; x++) { //Read in these 32 bytes
if(x == 0x10) x = 0; //Loop back to zero
si4703_registers[x] = Wire.read() << 8;
si4703_registers[x] |= Wire.read();
if(x == 0x09) break; //We're done!
}
}

void si4703_printRegisters(void) {
//Read back the registers
si4703_readRegisters();

//Print the response array for debugging
for(int x = 0 ; x < 16 ; x++) {
sprintf(printBuffer, "Reg 0x%02X = 0x%04X", x, si4703_registers[x]);
Serial.println(printBuffer);
}
}

// debounce digital pin input
boolean debounce(int pinnum, int bounce_time){
int x = 0; // counter
int val=0; // holds state op pin
int result; // output
do{

x=++x;
val = digitalRead(pinnum);
delay(1);
}
while (x < bounce_time + 1 && val == 1 ); if (x >= bounce_time){
result = true;
}
else{
result = false;
}

return result;
}
Advertisements

About Ryan

Ryan is currently a National Research Council Postdoctoral Research Fellow at the Air Force Research Laboratory. His research area includes Prognostic Health Management of Electronics. For more information please visit: www.rdlowe.com
This entry was posted in Project and tagged , , . Bookmark the permalink.

2 Responses to Over the Counter Kitchen Radio

  1. Pingback: Over the Counter Kitchen Radio using Arduino -Arduino for Projects

  2. Pingback: Philco Transistor Radio Hacked into Smart Phone Speakers | Visual Engineering

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s