Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Mirko Pavleski
Published © GPL3+

DIY Retro Look FM Radio with Linear Scale

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.

IntermediateFull instructions provided10,182
DIY Retro Look FM Radio with Linear Scale

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
WS2812 Addressable LED Strip
Digilent WS2812 Addressable LED Strip
×1
SparkFun FM Tuner Basic Breakout - Si4703
SparkFun FM Tuner Basic Breakout - Si4703
×1
Rotary Encoder with Push-Button
Rotary Encoder with Push-Button
×1
Clss - D small Audio amplifier board
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

Schematic

Code

Code ROTARY

C/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 SEEK

C/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);
}

Credits

Mirko Pavleski

Mirko Pavleski

154 projects • 1301 followers

Comments