/// \file SerialRadio.ino
/// \brief Radio implementation using the Serial communication.
/// \author Matthias Hertel, http://www.mathertel.de
/// \copyright Copyright (c) by Matthias Hertel.\n
/// This work is licensed under a BSD 3-Clause license.\n
/// See http://www.mathertel.de/License.aspx
/// \details
/// This is a Arduino sketch radio implementation that can be controlled using commands on the Serial input.
/// It can be used with various chips after adjusting the radio object definition.\n
/// Open the Serial console with 115200 baud to see current radio information and change various settings.
/// Wiring
/// ------
/// The necessary wiring of the various chips are described in the Testxxx example sketches.
/// No additional components are required because all is done through the serial interface.
/// More documentation is available at http://www.mathertel.de/Arduino
/// Source Code is available on https://github.com/mathertel/Radio
/// History:
/// --------
/// * 05.08.2014 created.
/// * 04.10.2014 working.
/// * 15.01.2023 ESP32, cleanup compiler warnings
// https://www.techrm.com/step-by-step-guide-build-an-esp8266-fm-radio-with-infrared-remote-control-and-tft-display/
// #include <Arduino.h>
#include <EEPROM.h>
#include <Wire.h>
#include <Adafruit_GFX.h> // include Adafruit graphics library
#include <Adafruit_ST7735.h> // include Adafruit ST7735 TFT library
// ST7735 TFT module connections
#define TFT_RST 0 // TFT RST pin is connected to NodeMCU pin D4 (GPIO2)
#define TFT_CS 5 // TFT CS pin is connected to NodeMCU pin D3 (GPIO0)
#define TFT_DC 2 // TFT DC pin is connected to NodeMCU pin D0 (GPIO16)
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
#include <radio.h>
// all possible radio chips included.
#include <RDA5807M.h>
#include <RDA5807FP.h>
#include <SI4703.h>
#include <SI4705.h>
#include <SI47xx.h>
#include <TEA5767.h>
#include <RDSParser.h>
#include <DIYables_IRcontroller.h> // DIYables_IRcontroller library
#define IR_RECEIVER_PIN 34 // The ESP8266 pin D6 connected to IR controller
DIYables_IRcontroller_21 irController(IR_RECEIVER_PIN, 200); // debounce time is 200ms
String currentChannelAddressEE[9] = {"2", "7", "12", "17", "22", "27", "32", "37", "42"};
String channelToMemorize = "0";
String channelToReproduce = "";
String currentChannelAddressEEString = "";
int initialAddress1 = 0;
int indexArray = 0;
int lastCH = 0;
bool mute = false;
bool lastMute = false;
bool stereo = true;
bool lastStereo = true;
bool equal = false;
bool lastEqual = false;
unsigned long lastTimeRanCycle;
unsigned long measureDelayCycle = 500; // 0.5s
String cmdToExecute = "";
// ===== SI4703 specific pin wiring =====
#define ENABLE_SI4703
#ifdef ENABLE_SI4703
#if defined(ARDUINO_ARCH_AVR)
#define RESET_PIN 2
#define MODE_PIN A4 // same as SDA
#elif defined(ESP8266)
#define RESET_PIN D8 //
#define MODE_PIN D2 // same as SDA
#elif defined(ESP32)
#define RESET_PIN 4
#define MODE_PIN 21 // same as SDA
#endif
#endif
bool saveCurrentStation = false;
// Standard I2C/Wire pins for Arduino UNO: = SDA:A4, SCL:A5
// Standard I2C/Wire pins for ESP8266: SDA:D2, SCL:D1
// Standard I2C/Wire pins for ESP32: SDA:21, SCL:22
/// The radio object has to be defined by using the class corresponding to the used chip.
/// by uncommenting the right radio object definition.
// RADIO radio; ///< Create an instance of a non functional radio.
// RDA5807FP radio; ///< Create an instance of a RDA5807FP chip radio
// RDA5807M radio; ///< Create an instance of a RDA5807M chip radio
SI4703 radio; ///< Create an instance of a SI4703 chip radio.
// SI4705 radio; ///< Create an instance of a SI4705 chip radio.
// SI47xx radio; ///< Create an instance of a SI4720,21,30 (and maybe more) chip radio.
// TEA5767 radio; ///< Create an instance of a TEA5767 chip radio.
/// get a RDS parser
RDSParser rds;
String lastName = "";
int16_t kbValue;
bool lowLevelDebug = false;
/// Update the Frequency on the LCD display.
void DisplayFrequency() {
char s[12];
radio.formatFrequency(s, sizeof(s));
Serial.print("FREQ:");
Serial.println(s);
} // DisplayFrequency()
/// Update the ServiceName text on the LCD display.
void DisplayServiceName(const char *name) {
Serial.print("RDS:");
Serial.println(name);
tft.setCursor(0, 22);
tft.setRotation(1);
tft.setTextColor(ST7735_BLACK);
tft.setTextSize(2);
tft.println(lastName);
tft.setCursor(0, 22);
tft.setRotation(1);
tft.setTextColor(ST7735_CYAN);
tft.setTextSize(2);
String n = String(name);
n.trim();
tft.println(n);
lastName = n;
} // DisplayServiceName()
// - - - - - - - - - - - - - - - - - - - - - - - - - -
void RDS_process(uint16_t block1, uint16_t block2, uint16_t block3, uint16_t block4) {
rds.processData(block1, block2, block3, block4);
}
/// Execute a command identified by a character and an optional number.
/// See the "?" command for available commands.
/// \param cmd The command character.
/// \param value An optional parameter for the command.
void runSerialCommand(char cmd, int16_t value) {
// ----- control the volume and audio output -----
if (cmd == '+') {
// increase volume
int v = radio.getVolume();
radio.setVolume(++v);
} else if (cmd == '-') {
// decrease volume
int v = radio.getVolume();
if (v > 0)
radio.setVolume(--v);
} else if (cmd == 'u') {
// toggle mute mode
radio.setMute(!radio.getMute());
} else if (cmd == 's') {
// toggle stereo mode
radio.setMono(!radio.getMono());
} else if (cmd == 'b') {
// toggle bass boost
radio.setBassBoost(!radio.getBassBoost());
} else if (cmd == 'f') {
// control the frequency
radio.setFrequency(value);
} else if (cmd == '.') {
radio.seekUp(false);
} else if (cmd == ':') {
radio.seekUp(true);
} else if (cmd == ',') {
radio.seekDown(false);
} else if (cmd == ';') {
radio.seekDown(true);
} else if (cmd == '!') {
// not in help
RADIO_FREQ f = radio.getFrequency();
if (value == 0) {
radio.term();
} else if (value == 1) {
radio.initWire(Wire);
radio.setBandFrequency(RADIO_BAND_FM, f);
radio.setVolume(10);
}
} else if (cmd == 'i') {
// info
char s[12];
radio.formatFrequency(s, sizeof(s));
Serial.print("Station:");
Serial.println(s);
Serial.print("Radio:");
radio.debugRadioInfo();
Serial.print("Audio:");
radio.debugAudioInfo();
} else if (cmd == 'x') {
radio.debugStatus(); // print chip specific data.
} else if (cmd == '*') {
lowLevelDebug = !lowLevelDebug;
radio._wireDebug(lowLevelDebug);
}
} // runSerialCommand()
void testfillrects(uint16_t color1, uint16_t color2) {
tft.fillScreen(ST7735_BLACK);
for (int16_t x = tft.width() - 1; x > 6; x -= 6) {
tft.fillRect(tft.width() / 2 - x / 2, tft.height() / 2 - x / 2, x, x, color1);
tft.drawRect(tft.width() / 2 - x / 2, tft.height() / 2 - x / 2, x, x, color2);
}
}
void selectChannel(int requestChannel) {
Serial.println(String(requestChannel));
indexArray = requestChannel - 1;
if (saveCurrentStation) {
channelToMemorize = String(requestChannel);
} else {
cmdToExecute = "f";
// currentChannelAddressEE[9] = {"2", "7", "12", "17", "22", "27", "32", "37", "42"};
channelToReproduce = "";
currentChannelAddressEEString = currentChannelAddressEE[indexArray];
initialAddress1 = currentChannelAddressEEString.toInt();
for (int i = 0; i < 5; i++) {
channelToReproduce = channelToReproduce + char(EEPROM.read(initialAddress1 + i));
}
if (String(channelToReproduce.charAt(0)) == "0") {
channelToReproduce.remove(0, 1);
}
Serial.println(channelToReproduce);
}
}
/// Setup a FM only radio configuration with I/O for commands and debugging on the Serial port.
void setup() {
delay(3000);
tft.initR(INITR_BLACKTAB);
tft.fillScreen(ST7735_BLACK);
testfillrects(ST7735_YELLOW, ST7735_MAGENTA);
tft.fillScreen(ST7735_BLACK);
tft.setCursor(0, 0);
tft.setRotation(1);
tft.setTextColor(ST7735_RED);
tft.setTextSize(2);
tft.println("Starting in progress: wait a moment");
delay(3000);
tft.fillScreen(ST7735_BLACK);
tft.setCursor(0, 80);
tft.setRotation(1);
tft.setTextColor(ST7735_CYAN);
tft.setTextSize(2);
tft.println("STEREO");
irController.begin();
delay(1000);
// open the Serial port
Serial.begin(115200);
// delay(3000);
Serial.println("SerialRadio...");
// Initialize EEPROM
EEPROM.begin(512);
#if defined(RESET_PIN)
// This is required for SI4703 chips:
radio.setup(RADIO_RESETPIN, RESET_PIN);
radio.setup(RADIO_MODEPIN, MODE_PIN);
#endif
// Enable information to the Serial port
radio.debugEnable(true);
radio._wireDebug(lowLevelDebug);
// Set FM Options for Europe
radio.setup(RADIO_FMSPACING, RADIO_FMSPACING_100); // for EUROPE
radio.setup(RADIO_DEEMPHASIS, RADIO_DEEMPHASIS_50); // for EUROPE
// Initialize the Radio
if (!radio.initWire(Wire)) {
Serial.println("no radio chip found.");
delay(4000);
while (1) {
};
};
radio.setBandFrequency(RADIO_BAND_FM, 8930);
radio.setMono(false);
radio.setMute(false);
radio.setVolume(radio.getMaxVolume() / 2);
// setup the information chain for RDS data.
radio.attachReceiveRDS(RDS_process);
rds.attachServiceNameCallback(DisplayServiceName);
} // Setup
/// Constantly check for input commands and trigger command execution.
void loop() {
// some internal static values for parsing the input
static RADIO_FREQ lastFrequency = 0;
RADIO_FREQ f = 0;
if (millis() > lastTimeRanCycle + measureDelayCycle) {
f = radio.getFrequency();
if (f != lastFrequency) {
// print current tuned frequency
tft.setCursor(0, 0);
tft.setRotation(1);
tft.setTextColor(ST7735_BLACK);
tft.setTextSize(2);
String fToDelete = "F: " + String(lastFrequency / 100) + "." + String(lastFrequency % 100) + " MHz";
tft.println(fToDelete);
tft.setCursor(0, 0);
tft.setRotation(1);
tft.setTextColor(ST7735_CYAN);
tft.setTextSize(2);
String s = "F: " + String(f / 100) + "." + String(f % 100) + " MHz";
tft.println(s);
lastFrequency = f;
}
if (indexArray != lastCH) {
tft.setCursor(0, 43);
tft.setRotation(1);
tft.setTextColor(ST7735_BLACK);
tft.setTextSize(2);
tft.println("CH: " + String(lastCH + 1));
tft.setCursor(0, 43);
tft.setRotation(1);
tft.setTextColor(ST7735_CYAN);
tft.setTextSize(2);
tft.println("CH: " + String(indexArray + 1));
lastCH = indexArray;
}
if (mute != lastMute) {
if (mute) {
tft.setCursor(0, 62);
tft.setRotation(1);
tft.setTextColor(ST7735_CYAN);
tft.setTextSize(2);
tft.println("MUTE");
} else {
tft.setCursor(0, 62);
tft.setRotation(1);
tft.setTextColor(ST7735_BLACK);
tft.setTextSize(2);
tft.print("MUTE");
}
lastMute = mute;
}
if (stereo != lastStereo) {
if (stereo) {
tft.setCursor(0, 80);
tft.setRotation(1);
tft.setTextColor(ST7735_BLACK);
tft.setTextSize(2);
tft.println("MONO");
tft.setCursor(0, 80);
tft.setRotation(1);
tft.setTextColor(ST7735_CYAN);
tft.setTextSize(2);
tft.println("STEREO");
} else {
tft.setCursor(0, 80);
tft.setRotation(1);
tft.setTextColor(ST7735_BLACK);
tft.setTextSize(2);
tft.println("STEREO");
tft.setCursor(0, 80);
tft.setRotation(1);
tft.setTextColor(ST7735_CYAN);
tft.setTextSize(2);
tft.println("MONO");
}
lastStereo = stereo;
}
if (equal != lastEqual) {
if (equal) {
tft.setCursor(0, 99);
tft.setRotation(1);
tft.setTextColor(ST7735_CYAN);
tft.setTextSize(2);
tft.println("BASS BOOST");
} else {
tft.setCursor(0, 99);
tft.setRotation(1);
tft.setTextColor(ST7735_BLACK);
tft.setTextSize(2);
tft.print("BASS BOOST");
}
lastEqual = equal;
}
lastTimeRanCycle = millis();
}
cmdToExecute = "";
Key21 key = irController.getKey();
if (key != Key21::NONE) {
switch (key) {
case Key21::KEY_CH_MINUS:
Serial.println("CH-");
Serial.println(indexArray);
if (indexArray > 0) {
indexArray--;
cmdToExecute = "f";
// currentChannelAddressEE[9] = {"2", "7", "12", "17", "22", "27", "32", "37", "42"};
channelToReproduce = "";
currentChannelAddressEEString = currentChannelAddressEE[indexArray];
initialAddress1 = currentChannelAddressEEString.toInt();
for (int i = 0; i < 5; i++) {
channelToReproduce = channelToReproduce + char(EEPROM.read(initialAddress1 + i));
}
if (String(channelToReproduce.charAt(0)) == "0") {
channelToReproduce.remove(0, 1);
}
Serial.println(channelToReproduce);
}
break;
case Key21::KEY_CH:
Serial.println("CH");
saveCurrentStation = true;
break;
case Key21::KEY_CH_PLUS:
Serial.println("CH+");
Serial.println(indexArray);
if (indexArray < 8) {
indexArray++;
cmdToExecute = "f";
// currentChannelAddressEE[9] = {"2", "7", "12", "17", "22", "27", "32", "37", "42"};
channelToReproduce = "";
currentChannelAddressEEString = currentChannelAddressEE[indexArray];
initialAddress1 = currentChannelAddressEEString.toInt();
for (int i = 0; i < 5; i++) {
channelToReproduce = channelToReproduce + char(EEPROM.read(initialAddress1 + i));
}
if (String(channelToReproduce.charAt(0)) == "0") {
channelToReproduce.remove(0, 1);
}
Serial.println(channelToReproduce);
}
break;
case Key21::KEY_PREV:
Serial.println("<<");
cmdToExecute = ",";
tft.setCursor(0, 43);
tft.setRotation(1);
tft.setTextColor(ST7735_BLACK);
tft.setTextSize(2);
tft.println("CH: " + String(lastCH + 1));
break;
case Key21::KEY_NEXT:
Serial.println(">>");
cmdToExecute = ".";
tft.setCursor(0, 43);
tft.setRotation(1);
tft.setTextColor(ST7735_BLACK);
tft.setTextSize(2);
tft.println("CH: " + String(lastCH + 1));
break;
case Key21::KEY_PLAY_PAUSE:
Serial.println(">||");
Serial.println(saveCurrentStation);
if (saveCurrentStation && channelToMemorize != "0") {
Serial.println("save frequency: ");
char freq[12];
radio.formatFrequency(freq, sizeof(freq));
String stationDisplay = "";
String station = String(freq);
station.trim();
stationDisplay = station;
station.replace(".", "");
station.replace(" MHz", "");
if (station.length() == 4) {
station = "0" + station;
}
Serial.print(station);
Serial.print(" on position ");
Serial.print(channelToMemorize);
Serial.println();
tft.fillScreen(ST7735_BLACK);
tft.setCursor(0, 0);
tft.setRotation(1);
tft.setTextColor(ST7735_GREEN);
tft.setTextSize(2);
tft.println("Station " + stationDisplay + " saved on position " + channelToMemorize);
delay (3000);
tft.fillScreen(ST7735_BLACK);
tft.setCursor(0, 0);
tft.setRotation(1);
tft.setTextColor(ST7735_CYAN);
tft.setTextSize(2);
String s = "F: " + stationDisplay;
tft.println(s);
tft.setCursor(0, 43);
tft.setRotation(1);
tft.setTextColor(ST7735_CYAN);
tft.setTextSize(2);
tft.println("CH: " + String(channelToMemorize));
if (mute) {
tft.setCursor(0, 62);
tft.setRotation(1);
tft.setTextColor(ST7735_CYAN);
tft.setTextSize(2);
tft.println("MUTE");
}
if (stereo) {
tft.setCursor(0, 80);
tft.setRotation(1);
tft.setTextColor(ST7735_CYAN);
tft.setTextSize(2);
tft.println("STEREO");
} else {
tft.setCursor(0, 80);
tft.setRotation(1);
tft.setTextColor(ST7735_CYAN);
tft.setTextSize(2);
tft.println("MONO");
}
if (equal) {
tft.setCursor(0, 99);
tft.setRotation(1);
tft.setTextColor(ST7735_CYAN);
tft.setTextSize(2);
tft.println("BASS BOOST");
}
Serial.println(stereo);
Serial.println(mute);
Serial.println(equal);
// String currentChannelAddressEE[9] = {"2", "7", "12", "17", "22", "27", "32", "37", "42"};
int channelIndex = channelToMemorize.toInt() - 1;
int initialAddress = currentChannelAddressEE[channelIndex].toInt();
for (int i = 0; i < station.length(); i++) {
EEPROM.write(initialAddress + i, station[i]);
}
EEPROM.commit();
} else {
Serial.println("no channel saved");
}
saveCurrentStation = false;
channelToMemorize = "0";
break;
case Key21::KEY_VOL_MINUS:
Serial.println("–");
cmdToExecute = "-";
break;
case Key21::KEY_VOL_PLUS:
Serial.println("+");
cmdToExecute = "+";
break;
case Key21::KEY_EQ:
Serial.println("EQ");
cmdToExecute = "b";
equal = !equal;
break;
case Key21::KEY_100_PLUS:
Serial.println("100+");
cmdToExecute = "u";
mute = !mute;
break;
case Key21::KEY_200_PLUS:
Serial.println("200+");
cmdToExecute = "s";
stereo = !stereo;
break;
case Key21::KEY_0:
Serial.println("0");
break;
case Key21::KEY_1:
selectChannel(1);
break;
case Key21::KEY_2:
selectChannel(2);
break;
case Key21::KEY_3:
selectChannel(3);
break;
case Key21::KEY_4:
selectChannel(4);
break;
case Key21::KEY_5:
selectChannel(5);
break;
case Key21::KEY_6:
selectChannel(6);
break;
case Key21::KEY_7:
selectChannel(7);
break;
case Key21::KEY_8:
selectChannel(8);
break;
case Key21::KEY_9:
selectChannel(9);
break;
default:
Serial.println("WARNING: undefined key:");
break;
}
}
kbValue = channelToReproduce.toInt();
runSerialCommand(cmdToExecute.charAt(0), kbValue);
// check for RDS data
radio.checkRDS();
} // loop