Project in progress
LW/MW/SW Stereo FM with RDS - DSP Radio Receiver V2.5

LW/MW/SW Stereo FM with RDS - DSP Radio Receiver V2.5 © GPL3+

This is a project of Radio Receiver LW/MW/SW Stereo FM with RDS, using Arduino Nano, Si473x Radio IC and 1.8in TFT color display ST7735.

  • 96,862 views
  • 96 comments
  • 115 respects

Components and supplies

Apps and online services

About this project

Radio was one of the first media of telecommunication for long distances, so it is a very old technology. However, listening to the radio or practicing the hobby of radio listening (SWL) is something that brings a lot of pleasure, especially when exploring the Shortwave Bands, where we can hear many interesting, distant and weird signals. Now, if you like Arduino and DIY, building your own radio receiver is a priceless experience. Let's go, try the project and check it out!

Features:

  • Stereo FM 64-108MHz with RDS (Radio Data System) with station ID and radio text auto-scrow information on the screen.
  • AM MW band for Europe (9kHz spacing) and MW band for USA (10kHz spacing).
  • LW band and 14 Shortwave bands, covering from 150kHz to 30MHz, including CB band.
  • Non volatile memory to store the actual frequency, band and screen theme on Arduino’s EEPROM. The data is kept even if the arduino is turned off.
  • Selectable AM bandwidth filters (6, 4, 3, 2.5, 2, 1.8, 1kHz) and FM bandwidth filters (Aut, 110, 84, 60, 40kHz).
  • Direct access to the FM band.
  • Atractive color screen look with numbers in 7 segmens style, easy to read.
  • Dual bargraph meters for Signal to Noise Meter and Signal Strength Meter plus ‘S’ values.
  • Band name indication (m) on the screen.
  • Selectable tuning steps of 1, 5, 9, 10kHz and the color scheme of the display (2 schemes).
  • Uses the Si4732-A10 (included a breakout PCB project) or the PL102BA-S V2 modules.
  • Controlled by an Arduino Nano + ST7735 TFT 1.8in display, 4 push-buttons, a SPDT switch and a rotary encoder.
  • Uses few componets and delivery surprisingly good performance.
  • Update May, 2022: added the firmware for the Pro Version (with SSB), see below.

Video:

Compilation of pictures took during the devl & construction of receiver

FM RDS and Shortwave + HAM with PL102BA-S V2 module

MW and FM RDS with PL102BA-S V2 module

Aliexpress Made in China version being sold?

Apparently the Chinese are already commercializing the project on Aliexpress. See the photo below:

And another radio here:

Description and Wiring diagram (schematics):

The Si473x is a complete receiver with DSP (digital signal processing) technology, the same used in SDR (software defined radio) receivers.

The Arduino controls and 'talks' with the Si473x IC via the I2C interface, writing data in it and reading data from it. The graphic interface is made up of a 1.8in ST7735 color TFT display, which comunicates via SPI data interface. Inside the Si473x IC, the radio frequency signal is digitalyzed thru A/D converter and processed by the DSP (digital signal processor) and then converted to audio thru D/A converter and sent to the output.

There are two electrical diagrams, one for use with the PL102BA-S V2 module and the other for use with the Si4732-A10 breakout PCB.

The antenna input circuit (RF front end) is simplistic, I did not use an RF pre-amp (LNA) or even more elaborate LPF / BPF filters and even so it is possible to get good radio receptions.

The S1 switch is used to select the antenna for Shortwave (SW) or Medium Wave / Long Wave (MW / LW). When switch S1 is in the MW position, the external shortwave antenna must be disconnected to obtain a cleaner reception, as in this case the ferrite rod plays the role of an antenna.

The D1 and D2 (BAV199 - dual diodes) can be replaced by 2 x 1N4148 or even ommited for simplification, I do not use them on my receiver, but it is a protection against electricity static discharges (ESD).

The arduino is powered by 7.8V (2x18650 batteries) applied to the VIN input. The arduino also provides stabilized voltages of 5V to supply the TFT display and 3.3V to supply the Si4732 (or PLB102).

A point that always raises doubts and there is a lot of information without practical basis and tests is about the difference in logic levels between the Arduino 5V and the IC Si473x.

As the Arduino has 5V logic levels on the I2C bus and the Si473x uses 3.3V, if necessary, a 5V to 3.3V Logic Level Converter Bi-Directional can be used on the I2C SCL / SDA bus and RESET line, between the Arduino and Si473x.

However, I do not use the Logic Level Converter in my project, I do use resistors, and in various pratical experiences (that I have been doing for more than 2 years) I never had any problem, I never had any Si4730/32/35 devices damaged, not even I2C communication issue and I never noticed any instability. In practice there is a resistive divider formed by the 1k resistor and the pullup resistor connected to 3.3V, so the logic level High never exceeds 3.3V (measured) on the Si473x pins. Without the Logic Level Converter you gain circuit simplification and a gain in I2C communication speed.

The ceramic decoupling caps C3-C6 are there to reduce the irradiation of noise (EMI) generated by the display and SPI data bus.

The Arduino's EEPROM memory can be rewritten up to 100,000 times without fail, according to technical paper (despite that in practical tests it reached more than 1 million of cycles before failing). To save the life time of the EEPROM, the data is saved once only when the PB2 push botton (SAVE) is pressed, so the memory will have a very long life, it can be said that it will never fail in normal use in this way.

An external audio amplifier must be used to amplify the sound of this radio, what can be a 'JBL' style amp box or any other audio amp that has a line input. Other choice is to assemble it yourself, see below a complete DIY LM386 audio amplifier project (schematics and PCB) that works very well on this project. Also the TDA2822 (stereo amplifier IC in a DIP-8 package) is a good option here as it is easy to find / cheap and delivery very good amount of sound.

The library used to control the Si473x:

In this project I use the high performance library Si4735-I2C-R4 authored by Mr. Michael J. Kennedy, a fantastic and innovative work that was written in 2012. It is the best existing library for Si473x, very stable, fast, friendly and has the professional level RDS decoder, much better than the libraries found on the internet today (btw which were based on Kennedy's work). I just made some updates to it to include some commands and configuration of the display pins.

Pictures of the circuit I set up:

Notes on the DSP radio chip (IC) used here:

1-The radio module used here was the PL102BA-S V2 (it has the IC Si4730-D60 QFN package). Only the module that comes with the Si4730-D60 IC with 3060 marking will receive shortwave bands and RDS on FM. Be aware that the module maked as NE928-10A V.01 will work only in AM/FM (not SW nor RDS). I ordered this module via Aliexpress. Below is the pictures and pinout of PL102BA-S V2 Module:

2-Recently I found this radio module on Aliexpress that might work in this project: Si4730 Si4731 Integrated High-quality FM / AM Radio Head Module

3-Si4732-A10 IC: The PLB102BA module became hard to find, so I decided to design a PCB breakout to use the Si4732-A10 IC (SOP16) which is easier to solder on the board, will receive shortwave bands and RDS on FM and is available on Aliexpress, Mouser, etc. This breakout board can be used in other radio projects with Si473x, including other libraries available on the internet.

Here is the link for you to order this PCB breakout (without the components) on the PCBWay website or even download for free the Gerber / BOM files to manufacture elsewhere.

Below are: the PCB design, schematics, pinout and BOM.

4-Additional comments: there are reports from users that RDS and Shortwave also worked with the Si4730-D60 and Si4734-D60 chips with the SSOP-24 package (not the QFN), these devices have the advantage of being cheaper, however I didn't have the opportunity to test them. Also remembering that the Si4735-D60 SSOP-24 can be used as the RDS / SW will work for sure.

I designed a breakout board adapter for the SI4730/34/35 with SSOP-24 package, click here to download the PCB layout.

5-In case you do not want to use the breakout board and/or use other MCU (microcontroller unit), below is the schematics of circuit using the Si4730-D60 / Si4734-D60 / Si4735-D60 chips with the SSOP-24 package, so that you can design your own printed circuit board. This circuit requires a MCU with I2C (Arduino Uno, Nano, ESP32, STM32, etc) in order to work.

Double Conversion Receiver:

For anyone interested in making a double conversion receiver with the SI5351 and SI4735(32), this is the block diagram of the receiver blocks that I built and it works great. Using JCR 10kHz-225MHz VFO. The mixer is the popular NE602 / SA612 IC.

Open Source Version:

Download the open source project, including the main sketch, libraries and the wiring diagram, etc by clicking here.

Pro Version (with SSB):

For those who want to try the version with SSB, I am making available here the firmware (.hex format) with SSB and many improvements (Pro Version, see the picture of it below). Download it here.

Note: To install firmware (.hex format) on Arduino UNO / Nano it is necessary to use the Xloader tool. There are some tutorials on Youtube explaining how to upload firmware.

Basically to upload the .hex file to Arduino Uno/Nano, connect the USB cable to Arduino Nano, open Xloader (on my PC I use Windows 10), select the correct COM port, select the Arduino type, select the file .hex and hit Upload in Xloader. That's all, really fast. I hope it works there too.

Also, in the Pro Version (with SSB) the push bottons have different functions, as shown in the schematics:

Important Note about Pro Version (with SSB): the circuit was designed to work with 6.5v to 8.5v power (coming from two 18650 batteries connected in series), and this voltage is applied to the VIN pin of the Arduino Nano, which regulates the voltages internally. The Arduino reads this battery voltage to indicate and uses this value to save the values to the EEPROM when the radio is turned off. If you supply the Arduino with voltage below this, the circuit will not work properly. Also, this version uses the Si473x I2C address 0x63, make sure to set it correctly on your hardware.

Instructions:

  • Open the scketch on Arduino IDE, install the libraries located in "install these libraries" folder and choose the correct I2C address for the Si4732 (0x11 or 0x63). The module PL102BA-S V2 uses address 0x63.
  • Compile the sketch and then load it to the Arduino Nano, Uno or Pro Mini.
  • Follow the schematics to wire the Arduino, display, PLB102 module or Si4732, rotary encoder, push bottons, etc.
  • The first time to power up the radio circuit, the initial values must be loaded into the EEPROM memory, with the following procedure: while pressing and holding the push button PB2, turn on the Arduino power and wait for the screen shows the message “DEFAULT VALUES”, now it is ready to work.
  • There are 4 push bottons and they have 2 functions each: short and long press (except PB3). Also there is one switch SPDT (S1).
  • Push botton PB1: to select the AM on and AM Band browser Up/Down selector: short press AM initiation coming from FM and then UP band, long press DOWN band (14 bands).
  • Push botton PB2: to ‘SAVE’* the current frequency, band, color theme to EEPROM and change the Tuning Steps (1, 5, 9, 10kHz): short press ‘SAVE’*, long press change Tstep. *Even if the arduino power is turned off and the power supply is removed, the information will be kept stored in the arduino's EEPROM memory and retrieved the moment it is turned on again.
  • Push botton PB3: to change bandwidth IF Filter for AM (6, 4, 3, 2.5, 2, 1.8, 1kHz) and FM (Aut, 110, 84, 60, 40kHz).
  • Push botton PB4: to select the FM on, RDS program type (station ID / radio text / auto scrow radio text) and color theme: short press FM initiation coming from AM and then RDS type change, long press will switch color theme from blue to black and vice versa.
  • Rotate the rotary encoder to tune up or down the frequency to search the stations.
  • The S1 switch is used to select the antenna for Shortwave (SW) or Medium Wave / Long Wave (MW / LW). When switch S1 is in the MW position, the external shortwave antenna must be disconnected to obtain a cleaner reception, as in this case the ferrite rod plays the role of an antenna.

About the antenna to be used:

  • For Shortwave reception use a 'longwire antenna', that is made of 7m long of piece of electric cable stretched outside or inside the house, at a height of at least 3 meters from the floor. Other types of antennas can also be used, such as the MiniWhip and the Magnetic Loop. For those who want to know a little more, a theory and project of typical 'longwire antenna' can be found here and for 'MiniWhip antenna' here.
  • For good reception in MW (Medium Wave) and LW (Long Wave) band should be used a ferrite rod antenna with 12cm x 1cm. For LW the inductance of ferrite coil should be at least 1000uH (1mH) in order to improve the reception in this band. The longer the ferrite rod is, the better is the reception.
  • For the FM band, a whip or wire with 70cm will be sufficient. For DX reception, a commercial external VHF/FM antenna can be used.

Some tips to improve radio reception:

  • Always supply the circuit with batteries. Avoid using switched power supply or DC / DC step down converter to power the this radio as these electronic power supply work with an oscillator of about 150kHz and this electromagnetic signal will interfere or even kill radio reception. Some power banks (battery modules) uses DC / DC step down converters.
  • Move away or turn off the electronic lamps that are close to the radio receiver, because these lamps also generate a lot of interference. Preferably use lamps with LED technology. Likewise, try to move the radio receiver away from computers and notebooks or laptops or turn these divices off when tuning and listening the radio.
  • Avoid using any kind of class D audio amplifiers (PWM) such as PAM8403 or TPA3118, etc, as these circuits works with high frequency and will disturb and generate noise in the AM/SW radio reception.
  • Shortwave and Mediumwave reception is highly dependent on the propagation of radio waves. There are certain times that are better and others are worse for reception. Usually in the late afternoon and at night, these are the best times for good reception of distant radio stations.

Final considerations about digital noise / interference generated by Arduino:

Every digital circuit with microcontroller and the I2C/SPI data/clock bus traffic generates a certain level of electromagnetic noise (EMI) that interferes and disturbs the radio reception, as it has harmonics that spread to various frequencies.

One way to try to reduce this problem is to place an insulated metallic plate (steel, aluminum or copper) below the breadboard and connected to the GND (negative battery pole) of the circuit or to assemble the entire circuit inside a metal box properly connected to the GND the circuit.

You must also use wires as short as possible in the connections between the TFT display and the Arduino. You should also distance the DSP Radio chip from the Arduino as much as possible, the ideal is to place the DSP Radio chip and the Arduino + Display in compartments separated by a grounded metal plate.

A well-planned printed circuit board (PCB) with separation of the digital part from the analog RF part and with ground plane will help a lot in noise reduction.

Good luck in assemblies! Julio Cesar.

TERMS OF USE:

  • This project is provided "AS IS", without any warranty of any kind, express or implied, including but not limited to the warranties of mechantability, fitness for a particular purpose and noninfringement.
  • This is a free project. The author of this project reserves the right to change, modify or do corrections to the hardware and/or software at any time without notice or obligation to persons who already assembled it.
  • Don't expect this receiver to perform as well or better than professional and commercial receivers, this is an amateur home project that aims at the personal satisfaction of setting up at home and seeing it work beyond the learning that this activity provides.

Code

DSP Radio Receiver Code Rev. 2.5C/C++
Just load it to Arduino Nano
/*********************************************************************************************************************************************************
  Si473x LW/MW/SW/CB AM/FM Stereo RDS Radio Receiver w/ ST7735 TFT Display and Arduino Nano/Uno - AM 150kHz To 30MHz - FM 64 to 108MHz Stereo with RDS.
  Please see the schematics for wiring connections and install all needed libraries that are located in the "INSTALL THESE LIBRARIES" folder.
  By J.CesarSound - ver 2.5 - Mar/2021.
**********************************************************************************************************************************************************/

//Libraries
#include <EEPROM.h>     //IDE Standard 
#include <SPI.h>        //IDE Standard
#include "Wire.h"       //IDE Standard
#include <TFT_ST7735.h> //BODMER graphics and font library for ST7735 TFT display
#include <Si4735.h>     //Michael Kennedy SI4735 I2C library modified by J.CesarSound
#include <Rotary.h>     //Ben Buxton https://github.com/brianlow/Rotary

//User preferences
//-------------------------------------------------------------------------------------------------------------------------------------------
#define SI7435_ADDRESS 0x63  //Si473x I2C address (0x11 = sen pin to GND or 0x63 = sen pin to 3v3), module PL102BA-S V2 uses 0x63
#define pb_0           A0    //AM mode and AM Band selector: short press AM / UP band, long press DOWN band
#define pb_1           A1    //Save current frequency, band, theme to EEPROM and Tuning Step selector: short press SAVE, long press Tstep
#define pb_2           A2    //Bandwidth IF Filter selector for AM and FM
#define pb_3           A3    //FM mode, RDS program type and color theme selector: short press FM / RDS type, long press switch color theme
//--------------------------------------------------------------------------------------------------------------------------------------------

TFT_ST7735 tft = TFT_ST7735();
Rotary r = Rotary(2, 3);
Si4735 radio;

unsigned int freq, fstep, freqold;
unsigned int freq_eeprom_am, freq_eeprom_fm;
int sm, sm_old, mx, sn, texsw = 0;
int stp = 2;
int mode = 2;
byte fr_eeprom_h, fr_eeprom_l, sts_am_fm, sts_theme;
byte encoder = 1, push_count = 0;
byte count, ctr;
byte filter = 0x03;
byte fil = 4;
byte fr_h;
byte fr_l;
byte ssb_m = 0x00;
byte mw_sw = 0x00;
byte am_fm;
bool hasRun = false, hasRun1 = false, hasRun2 = false, hasRun3 = false, rds_t = false;
unsigned int period = 0;
unsigned long time_now = 0;
unsigned int period_rds = 500;
unsigned long time_now_rds = 0;
unsigned int ca, cb, cc, cd, ce, cf, cg, ch;
bool buttonActive_0 = false, buttonActive_1 = false, longPressActive = false;
unsigned int longPressTime = 600;
unsigned long buttonTimer = 0;

void setup() {
  tft.init();
  tft.setRotation(3);
  Wire.begin();
  start_msg();
  tft.fillScreen(ca);

  pinMode(pb_0, INPUT_PULLUP);
  pinMode(pb_1, INPUT_PULLUP);
  pinMode(pb_2, INPUT_PULLUP);
  pinMode(pb_3, INPUT_PULLUP);

  if (digitalRead(A1) == LOW) {
    tft.fillScreen(TFT_BLUE); tft.setTextColor(TFT_WHITE, TFT_BLUE); tft.drawString("* DEFAULT VALUES *", 12, 58, 2);
    delay(500);
    am_fm = 0x20;
    freq = 10050;
    EEPROM.write(5, 6);
    eeprom_write();
  }

  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
  sei();

  eeprom_read(); sts_theme = EEPROM.read(6);
  if (sts_theme == false) blue_theme(); else black_theme();
  if (sts_am_fm == false) am_init(); else fm_init();
}

void loop() {
  if (freqold != freq) {
    freqold = freq;
    tune_freq();
    frequency();
    if (am_fm != 0x20) band_ind();
  }

  if (digitalRead(pb_0) == LOW) {
    delay(100);
    if (buttonActive_0 == false) {
      buttonActive_0 = true;
      buttonTimer = millis();
    }
    if ((millis() - buttonTimer > longPressTime) && (longPressActive == false)) {
      longPressActive = true;
      dec_preset();
    }
  } else {
    if (buttonActive_0 == true) {
      if (longPressActive == true) {
        longPressActive = false;
      } else {
        inc_preset();
      }
      buttonActive_0 = false;
    }
  }

  if (digitalRead(pb_1) == LOW) {
    delay(100);
    if (buttonActive_1 == false) {
      buttonActive_1 = true;
      buttonTimer = millis();
    }
    if ((millis() - buttonTimer > longPressTime) && (longPressActive == false)) {
      longPressActive = true;
      if (am_fm != 0x20) inc_step();
      if (am_fm == 0x20) setstepFM();
    }
  } else {
    if (buttonActive_1 == true) {
      if (longPressActive == true) {
        longPressActive = false;
      } else {
        eeprom_write();
      }
      buttonActive_1 = false;
    }
  }

  if ((digitalRead(pb_2)) == LOW) {
    delay(200);
    if (am_fm != 0x20) filterAM();
    if (am_fm == 0x20) filterFM();
    while (digitalRead(pb_2) == LOW);
  }

  if (digitalRead(pb_3) == LOW) {
    delay(500);
    if (am_fm != 0x20) {
      fm_init();
    } else rds_t = !rds_t;
    tft.drawString("                         ", 0, 74, 2);

    push_count++;
    if (push_count > 2) {
      sw_theme();
      while (digitalRead(pb_3) == LOW);
    }
  } else push_count = 0;

  if (millis() > time_now + period) {
    time_now = millis();
    rsq();
    smeter();
    snmeter();
    if (am_fm == 0x20) pilot();
  }

  if (am_fm == 0x20) {
    if (millis() > time_now_rds + period_rds) {
      time_now_rds = millis();
      rds();
    }
  }
}

ISR(PCINT2_vect) {
  char result = r.process();
  if (result == DIR_CW) set_frequency(1);
  else if (result == DIR_CCW) set_frequency(-1);
}

void set_frequency(short dir) {
  if (encoder == 1) {
    if (dir == 1) freq = freq + fstep;
    if (freq >= 30000) freq = 30000;
    if (dir == -1) freq = freq - fstep;
    if (freq <= 150) freq = 150;
  }
  if (encoder == 2) {
    if (dir == 1) freq = freq + fstep;
    if (freq >= 10810) freq = 10810;
    if (dir == -1) freq = freq - fstep;
    if (freq <= 6410) freq = 6410;
  }
}

void inc_preset() {
  count++;
  if (count > 14) count = 1;
  bandpresets();
}

void dec_preset() {
  count--;
  if (count < 1) count = 14;
  bandpresets();
}

void bandpresets() {
  tft.setTextSize(1); tft.setTextColor(cb, ca); tft.drawString("        ", 30, 57, 2);
  switch (count)  {
    case 1:
      freq = 198;
      stp = 1;
      setstep();
      break;
    case 2:
      freq = 522;
      stp = 4;
      setstep();
      break;
    case 3:
      freq = 520;
      stp = 3;
      setstep();
      break;
    case 4:
      freq = 3700;
      stp = 2;
      setstep();
      break;
    case 5:
      freq = 5000;
      stp = 2;
      setstep();
      break;
    case 6:
      freq = 6100;
      stp = 2;
      setstep();
      break;
    case 7:
      freq = 7200;
      stp = 2;
      setstep();
      break;
    case 8:
      freq = 10000;
      stp = 2;
      setstep();
      break;
    case 9:
      freq = 11940;
      stp = 2;
      setstep();
      break;
    case 10:
      freq = 13790;
      stp = 2;
      setstep();
      break;
    case 11:
      freq = 15400;
      stp = 2;
      setstep();
      break;
    case 12:
      freq = 17780;
      stp = 2;
      setstep();
      break;
    case 13:
      freq = 21525;
      stp = 2;
      setstep();
      break;
    case 14:
      freq = 27015;
      stp = 2;
      setstep();
      break;
  }
  if (hasRun2 == false) {
    am_init(); hasRun2 = true;
  }
  if (count >= 1 && count <= 4) period = 4000; else period = 500; band_ind();
}

void filterAM() {
  tft.setTextSize(1);
  tft.setTextColor(cb, ca);
  switch (fil) {
    case 0:
      fil = 1;
      filter = 0x00; tft.drawString("6k ", 135, 92, 2);
      break;
    case 1:
      fil = 2;
      filter = 0x01; tft.drawString("4k ", 135, 92, 2);
      break;
    case 2:
      fil = 3;
      filter = 0x02; tft.drawString("3k ", 135, 92, 2);
      break;
    case 3:
      fil = 4;
      filter = 0x06; tft.drawString("2k5", 135, 92, 2);
      break;
    case 4:
      fil = 5;
      filter = 0x03; tft.drawString("2k ", 135, 92, 2);
      break;
    case 5:
      fil = 6;
      filter = 0x05; tft.drawString("1k8", 135, 92, 2);
      break;
    case 6:
      fil = 0;
      filter = 0x04; tft.drawString("1k ", 135, 92, 2);
      break;
  }
  property_filter_AM();
}

void filterFM() {
  tft.setTextSize(1);
  tft.setTextColor(cb, ca);
  switch (fil) {
    case 0:
      fil = 1;
      filter = 0x00; tft.drawString("Aut", 135, 92, 2);
      break;
    case 1:
      fil = 2;
      filter = 0x01; tft.drawString("110", 134, 92, 2);
      break;
    case 2:
      fil = 3;
      filter = 0x02; tft.drawString("84k", 135, 92, 2);
      break;
    case 3:
      fil = 4;
      filter = 0x03; tft.drawString("60k", 135, 92, 2);
      break;
    case 4:
      fil = 0;
      filter = 0x04; tft.drawString("40k", 135, 92, 2);
      break;
  }
  fm_command();
}

void inc_step() {
  ctr++; if (ctr > 3) ctr = 1;
  byte eu[3] = {1, 4, 1}; byte us[3] = {1, 2, 3};
  if (count == 2) stp = eu[ctr];
  if (count != 2) stp = us[ctr];
  setstep();
}

void setstep() {
  tft.setTextSize(1);
  tft.setTextColor(cb, ca);
  switch (stp) {
    case 1:
      fstep = 1;
      tft.drawString(" 1k", 100, 57, 2);
      break;
    case 2:
      fstep = 5;
      tft.drawString(" 5k", 100, 57, 2);
      break;
    case 3:
      fstep = 10;
      tft.drawString("10k", 100, 57, 2);
      break;
    case 4:
      fstep = 9;
      tft.drawString(" 9k", 100, 57, 2);
      break;
  }
}

void setstepFM() {
  tft.setTextSize(1);
  tft.setTextColor(cb, ca);
  switch (stp) {
    case 0:
      stp = 1;
      fstep = 10;    // 100kHz
      tft.drawString(" x1 ", 100, 57, 2);
      break;
    case 1:
      stp = 0;
      fstep = 20;    // 200kHz
      tft.drawString(" x2 ", 100, 57, 2);
      break;
  }
}

void frequency() {
  tft.setTextColor(cg, ca);
  if (freq >= 10000) tft.drawNumber(freq, 0, 1, 7); if (freq < 10000 && freq >= 1000) tft.drawNumber(freq, 32, 1, 7);
  if (freq < 1000 && freq >= 100) tft.drawNumber(freq, 64, 1, 7); if (freq < 100 && freq >= 10) tft.drawNumber(freq, 96, 1, 7);
  if (freq < 10) tft.drawNumber(freq, 128, 1, 7); if (freq < 10000) tft.fillRect (2, 1, 28, 48, ca);
  if (freq < 1000) tft.fillRect (34, 1, 28, 48, ca);

  if (am_fm == 0x40 && freq >= 1000) tft.fillCircle(63, 51, 2, cg); else  tft.fillCircle(63, 51, 2, ca);
  if (am_fm == 0x20) tft.fillCircle(95, 51, 2, cg);  else tft.fillCircle(95, 51, 2, ca);
}

void snmeter() {
  if (sn > 42) sn = 42;
  byte range; if (am_fm == 0x20) range = 42; else range = 28;
  byte sn_g = map(sn, 0, range, 0, 65);
  tft.fillRect(31, 95, sn_g, 11, ce);
  tft.fillRect(31 + sn_g, 95, 65 - sn_g, 11, cf);
}

void smeter() {
  if (sm > 90) sm = 90; mx = sm;
  tft.fillRect(31, 113, sm, 11, cd);
  tft.fillRect(31 + sm, 113, 90 - sm, 11, cf);

  tft.setTextSize(1);
  tft.setTextColor(cb, ca);
  if (mx < 1) tft.drawString(" S0", 130, 111, 2);
  if ((mx >= 1) && (mx < 10)) tft.drawString(" S1", 130, 111, 2);
  if ((mx >= 10) && (mx < 25)) tft.drawString(" S3", 130, 111, 2);
  if ((mx >= 25) && (mx < 35)) tft.drawString(" S5", 130, 111, 2);
  if ((mx >= 35) && (mx < 50)) tft.drawString(" S7", 130, 111, 2);
  if ((mx >= 50) && (mx < 60)) tft.drawString(" S9", 130, 111, 2);
  if ((mx >= 60) && (mx < 70)) tft.drawString("+10", 130, 111, 2);
  if ((mx >= 70) && (mx < 75)) tft.drawString("+20", 130, 111, 2);
  if ((mx >= 75) && (mx < 80)) tft.drawString("+40", 130, 111, 2);
  if (mx >= 80) tft.drawString("+60", 130, 111, 2);
}

void layout() {
  tft.setTextColor(TFT_WHITE, TFT_BLUE);
  tft.drawFastHLine(0, 56, 160, cc);
  tft.drawFastVLine(125, 59, 12, cc);
  tft.drawFastHLine(0, 73, 160, cc);
  tft.drawFastHLine(0, 91, 160, cc);
  tft.drawFastHLine(0, 109, 160, cc);

  tft.drawFastHLine(30, 106, 67, cc); tft.drawFastHLine(30, 124, 92, cc);
  tft.drawFastVLine(30, 95, 11, cc); tft.drawFastVLine(30, 113, 11, cc);
  tft.drawFastVLine(96, 102, 4, cc); tft.drawFastVLine(121, 120, 4, cc);

  tft.setTextSize(1);
  tft.setTextColor(cb, ca);
  tft.drawString("Ts", 85, 57, 2);
  tft.drawString("S/N", 3, 92, 2);
  tft.drawString("BWF", 105, 92, 2);
  tft.drawString("SIG", 3, 111, 2);
}

void start_msg() {
  tft.fillScreen(TFT_BLACK);
  tft.setTextSize(1);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.drawString("JCR RADIO", 48, 20, 2);
  tft.drawString("AM/FM RDS Receiver", 15, 50, 2);
  tft.drawString("VER 2.5 - MAR/2021", 15, 80, 2);
  delay(1600);
}

void band_ind() {
  tft.setTextSize(1); tft.setTextColor(cb, ca);
  if (freq >= 1800 && freq <= 2000) tft.drawString("160m  ", 30, 57, 2); if (freq >= 2300 && freq <= 2495) tft.drawString("120m", 30, 57, 2);
  if (freq >= 3200 && freq <= 3400) tft.drawString("90m  ", 30, 57, 2); if (freq >= 3500 && freq <= 3800) tft.drawString("80m", 30, 57, 2);
  if (freq >= 3900 && freq <= 4000) tft.drawString("75m", 30, 57, 2); if (freq >= 4750 && freq <= 5500) tft.drawString("60m", 30, 57, 2);
  if (freq >= 5700 && freq <= 6200) tft.drawString("49m", 30, 57, 2); if (freq >= 7000 && freq <= 7299) tft.drawString("40m", 30, 57, 2);
  if (freq >= 7300 && freq <= 7600) tft.drawString("41m", 30, 57, 2); if (freq >= 9400 && freq <= 10000) tft.drawString("31m", 30, 57, 2);
  if (freq >= 10100 && freq <= 10150) tft.drawString("30m", 30, 57, 2); if (freq >= 11600 && freq <= 12100) tft.drawString("25m", 30, 57, 2);
  if (freq >= 13570 && freq <= 13870) tft.drawString("22m", 30, 57, 2); if (freq >= 14000 && freq <= 14350) tft.drawString("20m", 30, 57, 2);
  if (freq >= 15100 && freq <= 15830) tft.drawString("19m", 30, 57, 2); if (freq >= 18068 && freq <= 18168) tft.drawString("17m", 30, 57, 2);
  if (freq >= 17480 && freq <= 17900) tft.drawString("16m", 30, 57, 2); if (freq >= 21000 && freq <= 21450) tft.drawString("15m", 30, 57, 2);
  if (freq >= 21451 && freq <= 21850) tft.drawString("13m", 30, 57, 2); if (freq >= 24890 && freq <= 24990) tft.drawString("12m", 30, 57, 2);
  if (freq >= 25670 && freq <= 27999) tft.drawString("11m", 30, 57, 2); if (freq >= 28000 && freq <= 29700) tft.drawString("10m", 30, 57, 2);
  if (freq >= 150 && freq <= 515) tft.drawString("LW     ", 30, 57, 2);
  if (freq >= 520 && freq <= 1710 && count == 3) tft.drawString("MW US", 30, 57, 2);
  if (freq >= 522 && freq <= 1728 && count == 2) tft.drawString("MW EU", 30, 57, 2);
}

void tune_freq() {
  fr_h = (freq) >> 8;
  fr_l = (freq) & 0x00FF;
  if ((freq) < 1800) mw_sw = 0x00; else (mw_sw = 0x01);
  tune();
}

void rsq() {
  RSQMetrics rsq;
  radio.getRSQ(&rsq);
  sm = (rsq.RSSI);
  sn = (rsq.SNR) * 0.5 + sn * 0.5;
}

void pilot() {
  RSQMetrics rsq;
  radio.getRSQ(&rsq);
  tft.setTextSize(1);

  if (rsq.stereo == 1 && hasRun1 == false) {
    tft.setTextColor(TFT_WHITE, ca); tft.drawString(" Stereo ", 30, 57, 2); hasRun1 = true;
  }
  if (rsq.stereo == 0 && hasRun1 == true) {
    tft.setTextColor(TFT_DARKGREY, ca); tft.drawString(" Stereo ", 30, 57, 2); hasRun1 = false;
  }
}

void rds() {
  radio.getRDS();

  if (rds_t == false) {
    if (radio.rds.RDSSignal) {
      texsw++; if (texsw > 60) texsw = 1;
      tft.setTextSize(1); tft.setTextColor(cb, ca);
      if (hasRun == false) {
        tft.drawString("                         ", 0, 74, 2);
        hasRun = true;
      }
      tft.drawString("RDS ", 3, 74, 2);
      if (texsw > 0 && texsw < 41) tft.drawString(radio.rds.programService, 60, 74, 2);
      if (texsw == 41) tft.drawString("                        ", 33, 74, 2);
      if (texsw > 40 && texsw < 61) tft.drawString(radio.rds.radioText, 33, 74, 2);
      if (texsw == 1) tft.drawString("                        ", 33, 74, 2);
      hasRun3 = false;
    } else {
      radio.clearStationInfo();
      tft.setTextColor(cb, ca); tft.fillRect (0, 75, 3, 15, ca);
      if (hasRun3 == false) {
        tft.drawString("RDS   No Information      ", 3, 74, 2);
        hasRun3 = true;
      }
      hasRun = false;
    }
  }

  if (rds_t == true) {
    if (radio.rds.RDSSignal) {
      texsw += 8; if (texsw > 296) texsw = 0;
      tft.setTextSize(1); tft.setTextColor(cb, ca);
      if (hasRun == false) {
        tft.drawString("                         ", 0, 74, 2);
        hasRun = true;
      }
      tft.drawString(radio.rds.radioText, 20 - texsw, 74, 2); tft.fillRect (156, 75, 4, 15, ca);
      if (texsw == 296) tft.drawString("                             ", 0, 74, 2);
      hasRun3 = false;
    } else {
      radio.clearStationInfo(); texsw = 0;
      tft.setTextColor(cb, ca); tft.fillRect (0, 75, 3, 15, ca);
      if (hasRun3 == false) {
        tft.drawString("RDS   No Information        ", 3, 74, 2);
        hasRun3 = true;
      }
      hasRun = false;
    }
  }
}

void am_init() {
  encoder = 1;
  radio.begin(0, SI7435_ADDRESS);
  power_down();
  power_up_norm();
  delay(400);
  am_fm = 0x40;
  ssb_m = 0x00;
  eeprom_read();
  freq = freq_eeprom_am;
  if (count == 2) stp = 4; else stp = 2; setstep();
  tune();
  am_command();
  filter = 0x03; layout(); frequency(); if (count >= 1 && count <= 4) period = 4000; else period = 500;
  tft.setTextColor(cg, ca); tft.drawString("kHz ", 132, 57, 2); tft.setTextColor(cb, ca); tft.drawString("AM ", 3, 57, 2);
  tft.drawString("  AM/FM RDS Receiver  ", 0, 74, 2); tft.drawString("2k ", 135, 92, 2);
  tft.drawString("        ", 30, 57, 2);
}

void fm_init() {
  encoder = 2;
  radio.begin(0, SI7435_ADDRESS);
  power_down();
  power_up_FM();
  delay(400);
  am_fm = 0x20;
  ssb_m = 0x00;
  eeprom_read();
  freq = freq_eeprom_fm;
  tune(); pilot();
  radio.setMode(FM);
  fil = 0; filterFM();
  stp = 0; setstepFM(); fm_command();
  hasRun2 = false; count--; layout(); frequency(); period = 2000;
  tft.setTextColor(cg, ca); tft.drawString("MHz", 132, 57, 2); tft.setTextColor(cb, ca); tft.drawString("FM ", 3, 57, 2);
  tft.setTextColor(TFT_DARKGREY, ca); tft.drawString(" Stereo ", 30, 57, 2);
}

void sw_theme() {
  sts_theme = !sts_theme;
  if (sts_theme == false) blue_theme(); else black_theme();
  tft.fillScreen(ca); tft.setTextColor(cb, ca);
  hasRun1 = !hasRun1;
  fm_init();
  if (am_fm != 0x20) {
    fil = 4; filterAM();
  }
  if (am_fm == 0x20) {
    fil = 0; filterFM();
  }
}

void blue_theme() {
  ca = TFT_BLUE;       //Screen background
  cb = TFT_WHITE;      //Texts
  cc = TFT_LIGHTGREY;  //Lines
  cd = TFT_GREEN;      //S-Meter bargraph
  ce = TFT_MAGENTA;    //S/N bargraph
  cf = TFT_BLUE;       //Bargraph background
  cg = TFT_WHITE;      //Frequency and kHz MHz
  ch = TFT_CYAN;       //Reserved
  tft.fillScreen(ca);
}

void black_theme() {
  ca = TFT_BLACK;      //Screen background
  cb = TFT_WHITE;      //Texts
  cc = TFT_LIGHTGREY;  //Lines
  cd = TFT_GREEN;      //S-Meter bargraph
  ce = TFT_MAGENTA;    //S/N bargraph
  cf = TFT_BLACK;      //Bargraph background
  cg = TFT_CYAN;       //Frequency and kHz MHz
  ch = TFT_RED;        //Reserved
  tft.fillScreen(ca);
}

void am_command() {
  radio.setProperty(PROP_AM_SOFT_MUTE_MAX_ATTENUATION, 1); // 0 (OFF) - 63
  radio.setProperty(PROP_AM_AUTOMATIC_VOLUME_CONTROL_MAX_GAIN, 0x32C8); // 0x1000 - 0x7800
  radio.setProperty(PROP_AM_AGC_ATTACK_RATE, 4); // 4–248
  radio.setProperty(PROP_AM_AGC_RELEASE_RATE, 4); // 4–248
}

void fm_command() {
  radio.setProperty(PROP_FM_DEEMPHASIS, 0x0001); // 02 = 75 μs;  01 = 50 μs
  radio.setProperty(PROP_FM_CHANNEL_FILTER, filter); // 0 = automat; 1 = 110kHz; 4 = 40kHz
  radio.setProperty(PROP_FM_SOFT_MUTE_MAX_ATTENUATION, 0); // 0 = disable; 31 = max
  radio.setProperty(PROP_FM_BLEND_STEREO_THRESHOLD, 0); // 0 = stereo; 127 = mono
  radio.setProperty(PROP_FM_BLEND_MONO_THRESHOLD, 0); // 0 = stereo; 127 = mono
}

void power_down() {
  Wire.beginTransmission(SI7435_ADDRESS);
  Wire.write(0x11);
  Wire.endTransmission();
  delay(1);
}

void power_up_norm() {
  Wire.beginTransmission(SI7435_ADDRESS);
  Wire.write(0x01);
  Wire.write(0x11);
  Wire.write(0x05);
  Wire.endTransmission();
  delay(1);
}

void power_up_FM() {
  Wire.beginTransmission(SI7435_ADDRESS);
  Wire.write(0x01);
  Wire.write(0x10);
  Wire.write(0x05);
  Wire.endTransmission();
  delay(1);
}

void tune() {
  Wire.beginTransmission(SI7435_ADDRESS);
  Wire.write(am_fm);
  Wire.write(ssb_m);
  Wire.write(fr_h);
  Wire.write(fr_l);
  Wire.write(0x00);
  Wire.write(mw_sw);
  Wire.endTransmission();
  delay(1);
}

void property_filter_AM() {
  Wire.beginTransmission(SI7435_ADDRESS);
  Wire.write(0x12);
  Wire.write(0x00);
  Wire.write(0x31);
  Wire.write(0x02);
  Wire.write(0x00);
  Wire.write(filter);
  Wire.endTransmission();
  delay(1);
}

void eeprom_write() {
  if (am_fm == 0x20) {
    fr_eeprom_h = (freq) >> 8;
    fr_eeprom_l = (freq) & 0x00FF;
    EEPROM.write(0, fr_eeprom_l);
    EEPROM.write(1, fr_eeprom_h);
    EEPROM.write(2, 1);
  }
  if (am_fm != 0x20) {
    fr_eeprom_h = (freq) >> 8;
    fr_eeprom_l = (freq) & 0x00FF;
    EEPROM.write(3, fr_eeprom_l);
    EEPROM.write(4, fr_eeprom_h);
    EEPROM.write(2, 0);
    EEPROM.write(5, count);
  }
  EEPROM.write(6, sts_theme);
  tft.setTextColor(TFT_WHITE, TFT_MAGENTA); tft.drawString("SAVE", 126, 111, 2);
  delay(1500);
  tft.setTextColor(cb, ca); tft.drawString("     ", 126, 111, 2);
  delay(300);
}

void eeprom_read() {
  if (am_fm == 0x20) {
    fr_eeprom_l = EEPROM.read(0);
    fr_eeprom_h = EEPROM.read(1);
    freq_eeprom_fm = (fr_eeprom_h << 8U) | fr_eeprom_l;
  }
  if (am_fm != 0x20) {
    fr_eeprom_l = EEPROM.read(3);
    fr_eeprom_h = EEPROM.read(4);
    freq_eeprom_am = (fr_eeprom_h << 8U) | fr_eeprom_l;
    count = EEPROM.read(5);
  }
  sts_am_fm = EEPROM.read(2);
}

Schematics

Audio amp LM386 DIY Schematics
Audio amp LM386 DIY Schematics
Audio amp LM386 DIY PCB layout
It is not necessary to resize the PCB designs, they are already in the 1: 1 scale and you do not need to mirror them.
Audio amp LM386 DIY PCB top mask
Audio amp LM386 DIY PCB top mask

Comments

Similar projects you might like

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

Project tutorial by Mirko Pavleski

  • 51,021 views
  • 12 comments
  • 80 respects

DIY Retro Look FM Radio with Linear Scale

Project tutorial by Mirko Pavleski

  • 4,557 views
  • 0 comments
  • 14 respects

FM Radio

Project tutorial by Patrick Müller

  • 67,133 views
  • 13 comments
  • 117 respects

Mini Stereo Radio with RDA5807

Project tutorial by Mirko Pavleski

  • 15,652 views
  • 5 comments
  • 20 respects

DIY Retro Look FM Radio with TEA5767 Module

Project tutorial by Mirko Pavleski

  • 31,317 views
  • 17 comments
  • 72 respects

Homemade Arduino , Si4730 All Band Receiver (LW, MW, SW, FM)

Project tutorial by Mirko Pavleski

  • 14,869 views
  • 9 comments
  • 25 respects
Add projectSign up / Login