Si4703 FM Radio

 

Si4703 FM Radio

June 2013

This project is built around the Si4703 FM Tuner Breakout Board from Silicon Labs. It's still available from many suppliers on eBay for around 9 UKP including postage (from China) so represents good value for money.

The Circuit

An ATmega328 controls the Si4703 FM Tuner and drives the Nokia 5110 48x84 LCD display.

The ATmega328 RESET, RXD and TXD pins, together with GND, are brought out to pin headers for connecting to the programming hardware. I use a separate RS232-to-TTL converter, such as this one to save having a converter on board.

The two interrupt pins (D2 and D3) connect to a rotary encoder switch for up/down tuning of the Si4703 tuner. Input D4 connects to the push switch on the rotary encoder.

The Nokia 5110 display is driven using ATmega328 pins D9, D10, D11, D12 and D13. The Nokia display is available with several different breakout boards so the pin assignments could be changed if required. I used pin A0 as a digital output to'control' the LED backlight. Some Nokia displays require a GND connection for the backlight; the one I used requires Vcc to turn the backlight on.

The display is connected to the PCB with a short 8-way ribon cable providing additional flexibility in the pin assignments.

I used a display from DX-deal extreme for this project.

Analogue pins A3, A4 and A5 are assigned to the Si4703 tuner. Audio output from the tuner is normally taken from the 3.5mm stereo socket. In the current design, Left and Right audio channels are picked up from the connections on the back of the tuner breakout board, combined into mono through the two 1K resistors and applied to the 'hot' end of a 10K potentiometer, the slider of which feeds the audio input of a TDA7233 low voltage audio amplifier. Operating at around 3.5 volts, the amplifier provides about 85 mW of audio power into an 8 ohm speaker. The circuitry around the TDA7233 is taken directly from the datasheet.

I've used a 3000mAh Lithium-Ion battery for the power supply. The fully charged voltage (4.2v) doesn't seem to upset the Nokia display and quickly drops to around 3.9 volts anyway. As Lithium batteries can deliver a LOT of current in the event of a fault, I've included a 250mA resettable fuse.

Currently, it's necessary to remove the battery to charge it externally. The intention is to provide a suitable charging socket (with a normally closed contact) on the radio so the PCB provides a removable jumper which will be replaced by a two-pin header plug (connected to the normally closed contact) in order to disconnect the battery from the radio circuitry when it's on charge. Depending on the charger, the polyfuse rating will need increasing. My charger will be around 600mA maximum, so a 900mA polyfuse would be ok. With a trip current around 1.2A, it's still well below the battery's maximum charge/discharge rate.

It may seem unnecessary to disconnect the radio supply during charging but the charger circuit (IC) needs to see the charge current reduce to a pre-set minimum value (10% of the maximum charge current) before it will terminate the charge. With the radio connected, it's possible this lower limit would never be reached so the charger so would never terminate the charge. With Lithium batteries, I prefer to err on the side of caution.

(Lithium charger now completed here.)

PCB Layout

       

       

PCB Artwork (Approx actual size)

Download Circuit Wizard layout.

Download actual size PDF layout.

 

Construction

 

 

 

The ATmega328 'sketch'

The software definitely needs improving! It works sufficiently to make a usable radio but isn't very user-friendly. The encoder interrupt routines need some sort of software debouncing on the switch contacts.

Press and hold the encoder knob during power-up in order to set the Nokia display contrast using the encoder up/down knob. Press again to store the contrast value in EEPROM

Press the encoder knob to toggle between 'preset' and 'seek'. In seek mode, the code attempts to retrieve the RDS station information (but always seems to return 'Radio 1' !)

Press the encoder knob to save the current preset frequency in EEPROM. The radio will start up on that frequency in preset mode next time.

Download SparkFunSi4703 Library
Download Adafruit_GFX Library
Download Adafruit_PCD8544 Library


#include <SPI.h>
#include <SparkFunSi4703.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
#include <EEPROM.h>

#define CE 9
#define RST 10 
#define DC 11
#define DIN 12 
#define CLK  13  

#define PUSH 4

Adafruit_PCD8544 lcd = Adafruit_PCD8544(CLK, DIN, DC, CE, RST);

int resetPin = A3; 
int SDIO = A4;
int SCLK = A5;

int LED = A0;

Si4703_Breakout radio(resetPin, SDIO, SCLK);
int channel = 875;
int volume = 7;
char rdsBuffer[10];

volatile int number = 0;                
int oldnumber = number;
int lastnumber;

volatile boolean halfleft = false;      // Used in both interrupt routines
volatile boolean halfright = false;

int fmFreq[11] = {985, 893, 911, 933, 958, 967, 971, 974, 1004, 1011, 1059};
char* fmName[11] = {"Radio 1", "Radio 2", "Radio 3", "Radio 4", "Mersey",
                      "City FM", "Buzz", "Rock FM", "Smooth", "Classic ", "Cty Tlk"};
                      
byte savedContrast = 0;                      

int mode = 1;
int limit = 10;
long timer;
int contrast = 55;
bool rds = false;
float volts;
long count;

void setup() {
  
  pinMode(RST, OUTPUT);        
  pinMode(CE, OUTPUT);        
  pinMode(DC, OUTPUT);         
  pinMode(DIN, OUTPUT);       
  pinMode(CLK, OUTPUT); 
  
  pinMode(LED, OUTPUT);

  pinMode(resetPin, OUTPUT); 
  
  pinMode(2, INPUT);
  digitalWrite(2, HIGH);                // Turn on internal pullup resistor
  pinMode(3, INPUT);
  digitalWrite(3, HIGH);                // Turn on internal pullup resistor
  attachInterrupt(1, isr_2, FALLING);   // Call isr_2 when digital pin 3 goes LOW
  attachInterrupt(0, isr_3, FALLING);   // Call isr_3 when digital pin 2 goes LOW 
  
  pinMode(PUSH, INPUT);
  digitalWrite(PUSH, HIGH); 
  
  radio.powerOn();
  radio.setVolume(volume);
  
  number = EEPROM.read(0);
  if (number > 10) {
    number = 10;
  }
  radio.setChannel(fmFreq[number]);
  
  contrast = EEPROM.read(1);
  if (contrast > 60) {
    contrast = 60;
  }
  lcd.begin(contrast);            // set LCD contrast
  lcd.clearDisplay();
  digitalWrite(LED, HIGH);

  if (digitalRead(PUSH) != LOW) {
   lcd.println("Preset"); lcd.println();
   lcd.setTextSize(2);
   lcd.print(fmName[number]); 
   mode = 1;
  } else {
    lcd.println("Set");
    mode = 3;
  }
  lcd.display();   
  while(digitalRead(PUSH) == LOW);  
  count = millis();
}

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1126400L / result; // Back-calculate AVcc in mV
  return result;
}

void showDisplay() {
   if (mode == 1) {
      limit = 10;
      radio.setChannel(fmFreq[number]);
        
      lcd.clearDisplay();
      lcd.setTextSize(1);
      lcd.println("Preset "); lcd.println();
      lcd.setTextSize(2);
      lcd.print(fmName[number]);  
      lcd.setCursor(52, 36);
      lcd.setTextSize(1);
      lcd.print(volts);
      lcd.print("v");
      lcd.display();   
   }
   if (mode == 2) {
      limit = 10;
      if (number != oldnumber){
        channel = fmFreq[number];
        oldnumber < number ? channel = radio.seekUp() : channel = radio.seekDown();
        oldnumber = number;
      }
      lcd.clearDisplay();
      lcd.setTextSize(1);
      lcd.println("Seek");lcd.println();
      lcd.setTextSize(2);
      if (channel < 1000) lcd.print(" ");
      lcd.print((float) channel/10, 1);
      lcd.setTextSize(1);
      lcd.setCursor(58, 23);
      lcd.print(" MHz");
      lcd.setCursor(0, 36);
      lcd.print(rdsBuffer);      
      lcd.setCursor(52, 36);
      lcd.print(volts);
      lcd.print("v");      
      lcd.display();
   }   
}


void loop() {
  
  volts = readVcc();
  volts = volts / 1000;  
  if (millis() - count > 30000) {
      lcd.setContrast(contrast - (int)volts % 10);
      showDisplay();
      count = millis();
  }       
  
  if (digitalRead(PUSH) == LOW) {
    EEPROM.write(0, number);
    EEPROM.write(1, contrast);
    delay(50);
     mode == 1? mode = 2 : mode = 1 ;
     lcd.clearDisplay();
     lcd.setTextSize(1);
     if (mode == 1) {
        lcd.println("Preset");
        lcd.println();
        lcd.setTextSize(2); 
        number = lastnumber;
        lcd.print(fmName[number]); 
     }     
     if (mode == 2) {
        lcd.println("Seek");
        lcd.println();
        lcd.setTextSize(2); 
        lastnumber = number;
        channel = fmFreq[number];
        if (channel < 1000) lcd.print(" ");  
        lcd.print((float) channel/10, 1);
        lcd.setTextSize(1);
        lcd.setCursor(58, 23);
        lcd.print(" MHz");     
     }     
     lcd.display();     
     while(digitalRead(PUSH) == LOW);
     delay(200);
  }
  
  if(number != oldnumber){              // Change in value ?
  
   if (mode == 1) {
      limit = 10;
      radio.setChannel(fmFreq[number]);
      showDisplay();  
      
   }
   if (mode == 2) {
      limit = 10;
      if (number != oldnumber){
        channel = fmFreq[number];
        oldnumber < number ? channel = radio.seekUp() : channel = radio.seekDown();
        oldnumber = number;
      }
      showDisplay();
   }
   if (mode == 3) {
      limit = 60;
      lcd.setContrast(contrast+number);
      contrast = contrast + number;
   }
    oldnumber = number;
  } 
  
  rds = rdsBuffer[0] != '\0';
  
  if (!rds && (mode == 2) && (millis() - timer > 5000)){
    radio.readRDS(rdsBuffer, 2000);
    if (rdsBuffer[0] != '\0') {
      rds = true;
      lcd.setCursor(0, 36);
      lcd.print(rdsBuffer);
      lcd.display();
    } 
  } 
}


void isr_2(){                                              // Pin2 went LOW
  delay(1);                                                // Debounce time
  timer= millis();
  rds = false;
  if(digitalRead(2) == LOW){                               // Pin2 still LOW ?
    if(digitalRead(3) == HIGH && halfright == false){      // -->
      halfright = true;                                    // One half click clockwise
    }  
    if(digitalRead(3) == LOW && halfleft == true){         // <--
      halfleft = false;                                    // One whole click counter-
      number--;                                            // clockwise
      if (number < 0) number = 10;
    }
  }
}

void isr_3(){                                             // Pin3 went LOW
  delay(1);                                               // Debounce time
  timer = millis();
  rds = false;
  if(digitalRead(3) == LOW){                              // Pin3 still LOW ?
    if(digitalRead(2) == HIGH && halfleft == false){      // <--
      halfleft = true;                                    // One half  click counter-
    }                                                     // clockwise
    if(digitalRead(2) == LOW && halfright == true){       // -->
      halfright = false;                                  // One whole click clockwise
      number++;
      if (number > 10) number = 0;
    }
  }
}


 

Back to Index

This site and its contents are © Copyright 2005 - All Rights Reserved.