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.

  • 48,308 views
  • 67 comments
  • 79 respects

Components and supplies

Apps and online services

About this project

Update Mar/2021: this is version 2.5 of my DSP Project Radio Receiver with several improvements in software and hardware. Now comes with new features like RDS for FM, non-volatile memory to store the frequency / band even when the Arduino is turned off, MW band for Europe with 9kHz spacing, direct access button for the FM band, button with UP / DOWN to select the LW/MW/SW bands, buttons with dual function (short / long press), reorganization of the screen layout, less noise at reception and easier to use the receiver. It also includes the design and Gerber files of a breakout PCB for using the IC Si4732-A10. Try the project and check it out!

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

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.

Video:

80m HAM with Si4732-A10

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

MW and FM RDS with PL102BA-S V2 module

Description:

The Si473x is a complete receiver with DSP (digital signal processing) technology, the same used in SDR 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 and then converted to audio thru D/A converter and sent to the output.

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. See below a complete DIY LM386 AMP project (schematics and PCB) that works very well on this project.

Wiring diagram (schematics):

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 (dual diodes BAV199) 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.

Was added some ceramic decoupling caps to display wires to reduce the SPI irradiation of noise.

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.

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). 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 pinout and pictures of a PL102BA-S V2:

2-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.

3-Additional comments: there are reports from users that RDS and Shortwave also worked with the Si4730-D60 and Si4734-D60 chips (with SSOP-24 package), 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.

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 good reception in MW and LW band should be user a ferrite rod antenna with 12cm x 1cm. 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. Never use switched power supply or DC / DC step down converter to power the Arduino as these electronic power supply work with an oscillator of about 150kHz and this electromagnetic signal will interfere or even kill radio reception.
  • Be aware that some power banks (battery modules) use DC / DC step down converters which can also interfere with radio reception.
  • 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.
  • Avoid using class D audio amplifiers (PWM), as these also work with high frequency and will disturb and generate noise in the 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 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! J. CesarSound.

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.

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

  • 31,921 views
  • 8 comments
  • 64 respects

Arduino TEA5767 FM Radio Receiver

Project tutorial by Nick Koumaris

  • 51,698 views
  • 6 comments
  • 36 respects

DIY Sensitive Software Defined Radio with AD9850 VFO

Project tutorial by Mirko Pavleski

  • 6,344 views
  • 0 comments
  • 15 respects

Soldering Iron Controller for Hakko 907

Project tutorial by Alexander

  • 39,271 views
  • 22 comments
  • 76 respects

Pi-Arduino Internet Radio

Project tutorial by Ruben Zilzer

  • 12,930 views
  • 5 comments
  • 16 respects

Auto-Keyer for Radio "Fox Hunting"

Project tutorial by nfarrier

  • 6,227 views
  • 9 comments
  • 12 respects
Add projectSign up / Login