Project tutorial
DIY Retro Look FM Radio with Linear Scale

DIY Retro Look FM Radio with Linear Scale © GPL3+

Contains a linear scale in which the frequency is displayed with an color LED dot which is an integral part of the WS2812 LED strip.

  • 5,728 views
  • 0 comments
  • 15 respects

Components and supplies

Necessary tools and machines

09507 01
Soldering iron (generic)

Apps and online services

About this project

The device presented in the video is a standard FM Arduino radio but with an nice retro look. Contains a linear scale in which the frequency is displayed with an color LED dot which is an integral part of the WS2812 LED strip. There are several ways to select stations: standard with rotary encoder, predefined (memorized) stations in Code and auto dial (seek) function

The radio contains the following components:

-Arduino Nano microcontroller

-Si4703 cheap FM radio board

-WS2812 LED strip with 21 LEDs

-Small D-class audio amplifier board

-Speaker

-and rotary encoder

For the same hardware I will present you two different ways of working which are actually two different codes.

In the first case, the stations are selected manually using a rotary encoder which in this case simulates a variable capacitor. Also, in the code, we can redefine known radio station frequencies which we can select then by pressing the rotary encoder switch. We can actually "memorize" favorite stations. This time we will not talk about the sensitivity and selectivity of the receiver because our focus is the unusual scale with a retro look. Anyway the features are limited by the radio chip that is used.

In the second case we have automated station dialing using the seek function. This function is activated by briefly turning the encoder left or right. A blue LED blinking indicates a dialing, a green LED is for a mono station received and a red LED is for a stereo station received. The codes are taken from the Franz-Josef Haffner blog, and I made small modifications to the number of LEDs in the bar, Brightness of LEDs and the sensitivity threshold for radio signal detection in the second code. On his blog you can see a large number of old radios modified in different ways.

And now a few words about the making. The Si4703 is powered by 3.3V, so a level shifter between it and the Arduino Nano is required. As you can see in the diagram I did not use a level shifter, but not because it is so correct, but because I did not have it at the moment. Surprisingly, the device works quite normally without this part.

Finally, I placed the whole device in a suitable retro-look housing made of 5mm PVC and then coated with a self-adhesive label. At the end of the video you can follow the method of making this radio.

Code

Code ROTARYC/C++
//bis jetzt fehlerfreie Funktion!
//Hertel-Library si4703.h
//learning about NeoPixel:
//https://learn.adafruit.com/adafruit-neopixel-uberguide/arduino-library
//
/// Wiring
/// ------
/// The SI4703 board has to be connected by using the following connections:
/// | Arduino UNO pin    | Radio chip signal  |
/// | -------------------| -------------------|
/// | 3.3V (red)         | VCC                |
/// | GND (black)        | GND                |
/// | A5 or SCL (yellow) | SCLK               |
/// | A4 or SDA (blue)   | SDIO               |
/// | D2 (white)         | RST                |

//http://www.bristolwatch.com/arduino/arduino2.htm

#include <Arduino.h>
#include <Wire.h>
#include <radio.h>
#include <si4703.h>

#include <Adafruit_NeoPixel.h>
#define Din 4
int anz_led = 21; // Anzahl der LED's
Adafruit_NeoPixel strip = Adafruit_NeoPixel(anz_led, Din, NEO_GRB + NEO_KHZ800);
int wc = 0;
int led = 0;

// Input buttons on radio
#define preset 8
#define volDown 9            
#define volUp 10

//Rotary Encoder http://henrysbench.capnfatz.com/henrys-bench/arduino-sensors-and-input/keyes-ky-040-arduino-rotary-encoder-user-manual/
int clk = 3;  // Connected to CLK on KY-040
int dt = 7;  // Connected to DT on KY-040

// Si4703 radio chip
#define FIX_BAND     RADIO_BAND_FM   ///< The band that will be tuned by this sketch is FM.
SI4703 radio;    // Create an instance of Class for Si4703 Chip

// Variables definition
volatile int channel = 8800; // universal count
volatile byte INTFLAG1 = 0; // interrupt status flag
int volume = 12;

void setup()
{
  // Set internal pull up resistors on inputs
  pinMode(preset, INPUT_PULLUP);
  pinMode(volDown, INPUT_PULLUP);      
  pinMode(volUp, INPUT_PULLUP);

  //Rotary Encoder KY-040 pull up's built in
  pinMode (clk, INPUT);
  pinMode (dt, INPUT);

  // interrupt 1 digital pin 3 positive edge trigger
  attachInterrupt(digitalPinToInterrupt(clk), flag, RISING);

  Serial.begin(9600);
  Serial.println(channel);

  // Set all radio setting to the fixed values.
  radio.init();
  radio.setBandFrequency(FIX_BAND, channel);
  radio.setVolume(volume);
  radio.setMono(false);
  radio.setMute(false);
  Serial.println("fixed values");

  //Initialize LED-Stripes
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  strip.setBrightness(64); //range 0 (off) to 255 (max brightness)
  UpdateLed();
  Serial.println("LED-Stripes");

}

void loop()
{
    
  // Volume down
  if (digitalRead(volDown) == LOW)
   {
     if (volume > 0) volume--;
     radio.setVolume(volume);
     delay(100);
   }

  // Volume up
  if (digitalRead(volUp) == LOW)
   {
     if (volume < 15) volume++;
     radio.setVolume(volume);
     delay(100);
   }

  //Rotary Encoder
  if (INTFLAG1)   {
       INTFLAG1 = 0; // clear flag
       Serial.println(channel);
       Serial.println("ISP ausgelost");
       radio.setFrequency(channel);
       UpdateLed();
       delay(40);
  } // end if  
    
  // Preset-Stationen http://www.w3ii.com/de/arduino/arduino_switch_case_statement.html
  if (digitalRead(preset) == LOW)
  {
    switch (channel){
    case 8990: channel = 9150; break;
    case 9150: channel = 9420; break;
    case 9420: channel = 9480; break;
    case 9480: channel = 9700; break;
    case 9700: channel = 10540; break;
    default: channel = 8990;
    }
    Serial.print("Preset: ");
    Serial.println(channel);
    radio.setFrequency(channel);
    UpdateLed();
    delay(11);
  }
  delay(50);  
}//end loop

//ISR for Encoder: http://www.bristolwatch.com/arduino/arduino2.htm
void flag() {
  INTFLAG1 = 1;
  // add 1 to count for CW
  if (digitalRead(clk) && digitalRead(dt)) {
    channel+=10 ;
    if (channel > 10800) channel = 8700;
  }
  // subtract 1 from count for CCW
  if (digitalRead(clk) && !digitalRead(dt)) {
    channel-=10 ;
    if (channel < 8700) channel = 10800;
  }
}

void UpdateLed()
{
strip.setPixelColor(led, strip.Color( 0, 0, 0));
led = map(channel, 8700, 10800, 0, (anz_led - 1));
strip.setPixelColor(led, Wheel(wc));
strip.show();
delay(20);
}

uint32_t Wheel(byte WheelPos) {
  if (WheelPos < 85) {
    return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } else if (WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else {
    WheelPos -= 170;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}
Code SEEKC/C++
/// SI4703 Nano Rotary Neostrip sketch mit der SI4703_Breakout library von Simon Monk. Auf die aktuellere Version achten! Diese
/// hat bei der Objektdefinition 4 Parameter, die alte Version nur 3.
/// Jupp Haffner 15.9.2017
///
/// Wiring
/// ------
/// The SI4703 board has to be connected by using the following connections:
/// | Arduino UNO pin    | Radio chip signal  |
/// | -------------------| -------------------|
/// | 3.3V (red)         | VCC                |
/// | GND (black)        | GND                |
/// | A5 or SCL (yellow) | SCLK               |
/// | A4 or SDA (blue)   | SDIO               |
/// | D2 (white)         | RST                |

#include <Arduino.h>
#include <Wire.h>
#include <radio.h>
#include <Si4703_Breakout.h>

/*SI4703 - Pins*/
int resetPin = 2;
int SDIO = A4;
int SCLK = A5;
int RDSInterruptPin = 3; // GPIO2 for RDS Interrupt

int StereoLED = 11;
int AF_LED    = 12;

int volume = 28;
int rssi; //signal-level
int tune; //AFC
int stereo;

/*Init SI4703 Driver*/
Si4703_Breakout radio(resetPin, SDIO, SCLK, RDSInterruptPin);

//Rotary Encoder http://henrysbench.capnfatz.com/henrys-bench/arduino-sensors-and-input/keyes-ky-040-arduino-rotary-encoder-user-manual/
int clk = 3;  // Connected to CLK on KY-040
int dt = 7;  // Connected to DT on KY-040
volatile int channel = 1017;
volatile byte INTFLAG1 = 0; // interrupt status flag
volatile byte UP_FLAG = 0; //seekUp
volatile byte DOWN_FLAG = 0; //seekDn

#include <Adafruit_NeoPixel.h>
#define Din 4
int anz_led = 21; // Anzahl der LED's
Adafruit_NeoPixel strip = Adafruit_NeoPixel(anz_led, Din, NEO_GRB + NEO_KHZ800);
// Parameter 1 = number of pixels in strip
// Parameter 2 = pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
int wc = 0; //wheel-color 0 = grun, 80 = rot, 160 = blau
int led = 0;
int led_pos = channel;

void setup()
{

  //Rotary Encoder KY-040 pull up's built in
  pinMode (clk, INPUT);
  pinMode (dt, INPUT);
  // interrupt 1 digital pin 3 positive edge trigger
  attachInterrupt(digitalPinToInterrupt(clk), flag, RISING);
     
  //Initialize and Power up the SI4703
  radio.powerOn();
  radio.powerOn();
  radio.setVolume(volume);
  radio.setChannel(channel);

  //Print Informations to Serial Monitor
  Serial.begin(9600);
  displayInfo();

  strip.begin();
  strip.setBrightness(64); //range 0 (off) to 255 (max brightness)
  strip.show(); // Initialize all pixels to 'off'
  UpdateLed();

}

void loop()
{

//Rotary Encoder
  if (INTFLAG1)   {
       Serial.println("ISP ausgelost");
       if (UP_FLAG) SI4703_seekUpAuto();
       if (DOWN_FLAG) SI4703_seekDnAuto();
       // clear flags
       INTFLAG1 = 0;
       UP_FLAG = 0;
       DOWN_FLAG = 0;
       delay(40);
  }
}

//ISR for Encoder: http://www.bristolwatch.com/arduino/arduino2.htm
void flag() {
  INTFLAG1 = 1;
  // CW
  if (digitalRead(clk) && digitalRead(dt)) {
    UP_FLAG = 1;
  }
  // CCW
  if (digitalRead(clk) && !digitalRead(dt)) {
    DOWN_FLAG = 1;
  }
}

uint32_t Wheel(byte WheelPos) {
  if (WheelPos < 85) {
    return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } else if (WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else {
    WheelPos -= 170;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}

void displayInfo (void)
   {
   rssi=radio.getRSSI();
   tune=radio.getTune();
   stereo=radio.getStereo();
   Serial.print("\n\nChannel: "); Serial.print(channel/10.);Serial.println(" MHz");
   Serial.print("Volume: "); Serial.println(volume);
   Serial.print("RSSI: ");Serial.print(rssi);Serial.println("dB");
   Serial.print("Tune: ");
   if(tune==0)Serial.println("AFC Tuned!!");
   if(tune==1)Serial.println("AFC Tuning...");
   Serial.print("Stereo: ");
   if(stereo==1){Serial.println("true");digitalWrite(StereoLED, HIGH);}
   if(stereo==0){Serial.println("false");digitalWrite(StereoLED, LOW);}
   }

void SI4703_seekUpAuto (void)
{
      channel+=1;
      radio.setVolume(0);
      radio.setChannel(channel);
      delay(100);
      for(int seekup=channel;; seekup++)
      {
         if(seekup>1080)seekup=875;
         Serial.print(seekup/10.);Serial.println(" MHz");
         radio.setChannel(seekup);
         led_pos = seekup;
         UpdateLed();
         delay(30);
         int SI4703AFC_Tune=radio.getTune();
         int SI4703RSSI_Tune=radio.getRSSI();
         delay((SI4703RSSI_Tune*3));
         if(((SI4703AFC_Tune==false)&&(SI4703RSSI_Tune>25)))
         {
            radio.setVolume(volume);
            channel=seekup;
            displayInfo();
            break;
         }
      }   
}

void SI4703_seekDnAuto (void)
{
      channel-=1;
      radio.setVolume(0);
      radio.setChannel(channel);
      delay(100);
      for(int seekdown=channel;; seekdown--)
      {        
         if(seekdown<=875)seekdown=1080;
         Serial.print(seekdown/10.);Serial.println(" MHz");
         radio.setChannel(seekdown);
         led_pos = seekdown;
         UpdateLed();
         delay(30);
         int SI4703AFC_Tune=radio.getTune();
         int SI4703RSSI_Tune=radio.getRSSI();
         delay((SI4703RSSI_Tune*3));
         if(((SI4703AFC_Tune==false)&&(SI4703RSSI_Tune>25)))
         {
            radio.setVolume(volume);
            channel=seekdown;
            displayInfo();
            break;
         }
      }
}

void UpdateLed()
{
rssi=radio.getRSSI();
tune=radio.getTune();
stereo=radio.getStereo();
strip.setPixelColor(led, strip.Color( 0, 0, 0));
led = map(led_pos, 870, 1080, 0, (anz_led - 1));
wc = 0; //grun
if (rssi >= 25) wc = 80; //rot
if (tune) wc = 160; //blau
strip.setPixelColor(led, Wheel(wc));
strip.show();
delay(20);
}

Schematics

Schematic
Untitled sketch bb vaoeignxkz

Comments

Similar projects you might like

DIY Retro Look FM Radio with TEA5767 Module

Project tutorial by Mirko Pavleski

  • 31,319 views
  • 17 comments
  • 72 respects

DIY Si4730 All Band Radio (LW, MW, SW, FM)

Project tutorial by Mirko Pavleski

  • 51,034 views
  • 12 comments
  • 80 respects

Simple FM Radio

Project tutorial by Alexander

  • 43,601 views
  • 14 comments
  • 45 respects

Art Deco FM Radio Project Using Arduino

Project tutorial by Nick Koumaris

  • 13,070 views
  • 2 comments
  • 27 respects

DIY Sensitive Software Defined Radio with AD9850 VFO

Project tutorial by Mirko Pavleski

  • 14,127 views
  • 0 comments
  • 22 respects

FM Radio

Project tutorial by Patrick Müller

  • 67,133 views
  • 13 comments
  • 117 respects
Add projectSign up / Login