Introduzione
In questo articolo, esploreremo insieme come trasformare il tuo ESP8266 in una ESP8266 FM radio , cioè un ricevitore in modulazione di frequenza comandato da un telecomando a raggi infrarossi e dotato di un display TFT. Sei pronto a immergerti nel mondo della radio FM con un tocco di magia tecnologica?
Il progetto si basa su un ESP8266 che gestisce un modulo Si4703 per la sintonizzazione di stazioni radio FM. Una caratteristica distintiva di questo sistema è la facile interazione, resa possibile grazie a un telecomando a raggi infrarossi collegato all’ESP8266. Questo telecomando consente di impartire comandi comodamente, rendendo l’esperienza di ascolto radiofonico estremamente personalizzabile e comoda.
Il display TFT integrato fornisce in tempo reale informazioni dettagliate sulla stazione in ascolto, inclusi dati come la frequenza, le informazioni RDS, il canale e varie impostazioni della radio, come il mute/unmute, lo stereo/mono e il bass booster. La visualizzazione chiara e intuitiva sul display rende facile comprendere e regolare tutte le impostazioni desiderate.
Una delle funzionalità principali è la possibilità di effettuare uno scan manuale delle frequenze in avanti/indietro, offrendo un controllo completo sulla ricerca della stazione desiderata. Inoltre, il sistema consente la memorizzazione di un massimo di 9 stazioni radio preferite nella memoria EEPROM dell’ESP8266, rendendo l’accesso rapido e agevole.
Il controllo del volume, la modalità mute, la commutazione stereo/mono e l’opzione di bass booster sono ulteriori caratteristiche che consentono di adattare l’ascolto alle preferenze personali. Queste opzioni vengono gestite tramite il telecomando .
Per l’ascolto, l’utente può scegliere tra l’utilizzo di auricolari per un’esperienza personale o collegare il sistema a un sistema audio esterno dotato di moduli LM386 e altoparlanti. Questa versatilità consente di adattare l’ascolto alle diverse esigenze e circostanze.
In sintesi, questo sistema rappresenta un progetto completo e personalizzabile per gli appassionati di radio, offrendo una vasta gamma di funzionalità e una semplice interfaccia utente per un’esperienza di ascolto radiofonico su misura.
Come al solito, per la realizzazione dello sketch useremo l’ottimo PlatformIO.
Il modulo Si4703
Il modulo Si4703 rappresenta il cuore tecnologico di questo progetto radiofonico, conferendo al sistema la capacità avanzata di sintonizzazione delle stazioni FM. Dotato di funzionalità di alta qualità e precisione, il Si4703 integra un ricevitore radio FM altamente sensibile, consentendo la ricezione di segnali radio con estrema chiarezza. Il suo design compatto e affidabile lo rende ideale per l’integrazione in progetti fai-da-te come questo, fornendo una solida base per la sintonizzazione delle frequenze radio.
Uno dei tratti distintivi del Si4703 è la sua capacità di gestire il sistema RDS (Radio Data System), fornendo informazioni supplementari sulla stazione in ascolto. Queste informazioni includono dettagli come il nome della stazione, il tipo di programma e altro ancora, arricchendo l’esperienza radiofonica dell’utente. La compatibilità con l’RDS aggiunge un livello di interattività e informazioni al sistema, rendendo più coinvolgente l’ascolto delle stazioni preferite.
La gestione intelligente della frequenza e la capacità di scansionare manualmente attraverso le stazioni offrono un controllo totale all’utente, consentendo una personalizzazione completa dell’ascolto. Inoltre, la possibilità di memorizzare stazioni preferite nella memoria EEPROM dell’ESP8266 amplia ulteriormente le opzioni di accesso rapido, garantendo che le stazioni preferite siano sempre a portata di mano.
Il Si4703, con la sua progettazione affidabile e le funzionalità avanzate, si dimostra un componente essenziale per chiunque desideri creare un sistema radiofonico personalizzato e di alta qualità. La sua versatilità e precisione contribuiscono a rendere questo progetto un’esperienza di ascolto radiofonico straordinaria e altamente adattabile alle preferenze dell’utente.
Questo dispositivo è direttamente collegato con l’ESP8266 e comunica con esso tramite il protocollo I2C.
Il bus I2C
Il bus I2C (Inter-Integrated Circuit) è il bus tramite il quale il modulo Si4703 comunica con l’ESP8266. Esso è un popolare protocollo di comunicazione seriale utilizzato per collegare vari dispositivi e sensori a microcontroller, microprocessori e altri dispositivi embedded. Ecco alcuni punti chiave da considerare:
- Comunicazione a due fili: il bus I2C utilizza solo due fili per la comunicazione: uno per la trasmissione dei dati (SDA, Serial Data) e uno per la sincronizzazione del clock (SCL, Serial Clock). Questa semplicità lo rende ideale per collegare più dispositivi su un unico bus.
- Master e Slave: nella comunicazione I2C, c’è un dispositivo principale chiamato “master” e uno o più dispositivi secondari chiamati “slave”. Il master inizia e controlla la comunicazione, mentre gli slave rispondono alle richieste del master.
- Indirizzamento: ogni dispositivo slave ha un indirizzo univoco sul bus I2C. Questo consente al master di selezionare il dispositivo con cui desidera comunicare. Non possono coesistere più dispositivi con lo stesso indirizzo sullo stesso bus.
- Half-Duplex: il bus I2C supporta la comunicazione half-duplex, il che significa che i dati possono fluire solo in una direzione alla volta. Tuttavia, è possibile invertire la direzione della comunicazione durante la trasmissione.
- Velocità variabile: il bus I2C supporta una varietà di velocità di comunicazione, consentendo di adattare la velocità in base alle esigenze specifiche dell’applicazione.
- Ampiamente utilizzato: il bus I2C è ampiamente utilizzato in una vasta gamma di dispositivi, tra cui sensori, dispositivi di memoria, schermi, microcontroller e molti altri. È una scelta comune nei dispositivi IoT e nei progetti embedded.
- Pull-Up resistori: per garantire una comunicazione affidabile, il bus I2C richiede l’uso di resistori di pull-up sugli sbocchi SDA e SCL. Questi resistori contribuiscono a stabilizzare il segnale e prevengono il rumore.
- Protocollo di alto livello: il bus I2C è un protocollo di alto livello che semplifica la comunicazione tra i dispositivi, consentendo agli sviluppatori di concentrarsi sulla logica dell’applicazione piuttosto che sulla gestione della comunicazione.
In sintesi, il bus I2C è un protocollo di comunicazione affidabile e flessibile che offre un modo conveniente per collegare dispositivi e sensori a un microcontroller o un microprocessore. La sua ampiezza di utilizzo e la sua facilità di implementazione lo rendono una scelta popolare nell’ambito dell’elettronica embedded e dell’IoT.
Il display TFT
Il display TFT da 1.8 pollici assume un ruolo cruciale all’interno di questo progetto radiofonico, fornendo una piattaforma visiva tecnologicamente avanzata. Dotato di una risoluzione nitida e colori vivaci, il display offre un’esperienza visiva ricca e dettagliata, ideale per visualizzare informazioni complesse come frequenze radio, dati RDS e impostazioni del sistema.
La sua tecnologia TFT (Thin Film Transistor) assicura una risposta rapida dei pixel e una gestione efficace dei livelli di luminosità, garantendo una chiarezza visiva ottimale in una varietà di condizioni ambientali. La retroilluminazione assicura una visualizzazione chiara anche in condizioni di luce non favorevole.
La dimensione fisica compatta del display, con una diagonale da 1.8 pollici, contribuisce a una costruzione leggera e discreta, facilitando l’integrazione nell’estetica generale del progetto. L’interfaccia utente intuitiva è arricchita da un’alta definizione grafica, facilitando la navigazione tra le diverse schermate e la gestione delle opzioni di ascolto.
Nel contesto del progetto radiofonico, il display TFT funge da elemento di controllo visivo, consentendo agli utenti di personalizzare e monitorare l’esperienza radiofonica in tempo reale. La sua integrazione senza soluzione di continuità con il sistema complessivo conferisce un tocco di sofisticatezza e funzionalità tecnologica all’intero progetto.
Abbiamo già utilizzato il display TFT negli articoli Misurazione della saturazione di ossigeno e della frequenza cardiaca con l’ESP8266 e il sensore MAX30101: guida completa e Stazione meteo con ESP8266 e display TFT: monitoraggio in tempo reale delle previsioni meteo
Il modulo LM386
Questo modulo si basa sul circuito integrato LM386, un amplificatore di potenza in bassa frequenza, in grado di erogare un paio di W su un carico di 4/8 Ω.
Operante con alimentazione singola da 4V a 12V, questo amplificatore IC è ideale per progetti audio di piccola scala come radio portatili, interfacce audio e progetti DIY. Alcune caratteristiche chiave includono:
- Guadagno regolabile: l’ LM386 offre la possibilità di regolare il guadagno attraverso l’uso di componenti esterni. Questa caratteristica consente una personalizzazione del livello di amplificazione in base alle esigenze del progetto.
- Basso consumo energetico: grazie al suo basso consumo energetico, l’ LM386 è adatto per applicazioni alimentate a batteria, contribuendo all’efficienza energetica dei dispositivi.
- Design semplice: l’LM386 semplifica il processo di progettazione con un numero limitato di componenti esterni richiesti per il suo funzionamento. Questo aspetto lo rende particolarmente adatto per progetti DIY e per coloro che si avvicinano per la prima volta alla progettazione di circuiti audio.
Il modulo che utilizzeremo in questo progetto contiene un LM386 e un trimmer e costituisce un amplificatore audio pre-assemblato, progettato per semplificare l’integrazione degli amplificatori audio in progetti elettronici. Le caratteristiche includono:
- LM386 integrato: il modulo monta l’LM386 come componente principale, fornendo un’ampia gamma di possibilità di amplificazione per segnali audio a bassa potenza.
- Guadagno regolabile: il trimmer presente sul modulo consente la regolazione del guadagno dell’LM386. Questa regolazione può essere adattata alle esigenze specifiche del progetto, consentendo un controllo preciso sulla potenza del segnale amplificato.
- Facilità d’uso: grazie al modulo pre-assemblato, gli utenti possono integrare facilmente la funzionalità di amplificazione audio nei loro progetti senza dover progettare il circuito da zero.
- Flessibilità di alimentazione: il modulo è progettato per funzionare con un’ampia gamma di tensioni di alimentazione, contribuendo alla sua versatilità.
Questo modulo è particolarmente adatto per progetti audio DIY, altoparlanti portatili, e altre applicazioni in cui è necessario amplificare segnali audio a bassa potenza.
Il telecomando a raggi infrarossi
Il telecomando a infrarossi è un dispositivo di controllo remoto che utilizza segnali infrarossi per comunicare con dispositivi elettronici. Composto da un trasmettitore e da pulsanti per la selezione delle funzioni, il telecomando a infrarossi è ampiamente utilizzato in dispositivi elettronici di consumo. Di seguito sono riportate alcune delle sue caratteristiche e componenti principali:
- LED trasmettitore: il telecomando è dotato di un LED a infrarossi come trasmettitore. Quando si preme un pulsante, il LED emette un segnale infrarosso contenente informazioni codificate sul comando selezionato.
- Pulsanti di controllo: il telecomando dispone di una serie di pulsanti che corrispondono a diverse funzioni del dispositivo controllato. Ogni pulsante invia un segnale specifico al dispositivo, attivando la rispettiva funzione.
- Circuito di controllo: all’interno del telecomando, un circuito di controllo gestisce la logica di funzionamento. Esso interpreta i segnali provenienti dai pulsanti e li traduce in segnali infrarossi inviati attraverso il LED.
Il ricevitore a infrarossi è il componente situato sul dispositivo destinato a ricevere e interpretare i segnali infrarossi trasmessi dal telecomando. Di seguito sono riportate le sue caratteristiche principali:
- Fotodiodo a infrarossi: il componente chiave del ricevitore è il fotodiodo a infrarossi, che è sensibile alla luce nell’intervallo degli infrarossi. Quando il segnale infrarosso colpisce il fotodiodo, genera una corrente elettrica proporzionale all’intensità della luce ricevuta.
- Demodulazione del segnale: dopo che il fotodiodo ha ricevuto il segnale, il ricevitore demodula il segnale infrarosso e il risultato è un segnale digitale che rappresenta il comando inviato dal telecomando.
- Decodifica del comando: il software di controllo all’interno del dispositivo che elaborerà il segnale ricevuto (nel nostro caso l’ESP8266) decodifica il segnale ricevuto, associandolo a una specifica funzione o comando. In base al comando ricevuto, il dispositivo attiverà la funzione corrispondente.
In sintesi, il telecomando a infrarossi e il suo ricevitore costituiscono un sistema che consente il controllo remoto di dispositivi elettronici, utilizzando segnali infrarossi per comunicare in modo efficiente e senza fili.
Di che componenti abbiamo bisogno per la nostra ESP8266 FM Radio?
La lista dei componenti non è particolarmente lunga:
- una breadboard per connettere la NodeMCU ESP8266 agli altri componenti
- alcuni fili DuPont (maschio – maschio, maschio – femmina, femmina – femmina)
- un display TFT 1.8″
- un modulo Si4703
- OPZIONALE: due moduli amplificatori con LM386
- un auricolare stereo con jack da 3.5mm maschio
- OPZIONALE: due altoparlanti da 4/8 Ω e almeno 2/3 W
- un telecomando a raggi infrarossi con relativo ricevitore
- e, ovviamente, una NodeMCU ESP8266 !
Vediamo ora le foto dei vari moduli impiegati nel progetto.
Ecco il display TFT utilizzato in questo progetto:
Le prossime due foto ci mostrano il modulo Si4703:
In genere il connettore viene fornito a parte quindi, se avessi necessità di saldarlo, ti consiglio di dare prima un’occhiata all’articolo Un altro tutorial su come saldare per vedere come fare.
È conveniente saldare il connettore dal lato della serigrafia in modo che quando il modulo è montato sulla breadboard le scritte risultino sotto e il jack femmina sopra.
I pin che dobbiamo considerare sono 3.3V, GND, SDIO, SCLK, RST negato (cioè col trattino in alto) e li collegheremo in questo modo
Si4703 | ESP8266 |
3.3V | 3V3 |
GND | GND |
SDIO | D2 |
SCLK | D1 |
RST negato | D8 |
Vediamo ora il telecomando utilizzato in questo progetto:
Questo è un telecomando abbastanza comune nei progetti con Arduino.
Vediamo il corrispondente ricevitore.
Il ricevitore IR consta di un modulo (a sinistra) su cui si deve montare il diodo ricevitore IR (a destra) sul connettore in basso. Il connettore in alto invece verrà connesso all’alimentazione e ad un GPIO dell’ESP8266.
Nelle prossime due foto vedremo il modulo ricevitore montato.
Il connettore in alto viene utilizzato per alimentare il modulo e per collegarlo all’ ESP8266:
Ricevitore IR (connettore in alto) | ESP8266 |
VCC | 3V3 |
GND | GND |
IN | D6 |
Il diodo ricevitore IR è montato sul connettore in basso in modo che la sua finestrella sia orientata in avanti (come nella foto successiva):
Vediamo ora il modulo amplificatore con LM386:
Il connettore superiore è l’ingresso dell’alimentazione e del segnale. Il terminale VDD va connesso al terminale Vin dell’ESP8266 (che è a tensione 5V), i terminali GND al GND del circuito mentre il terminale IN rappresenta l’ingresso del segnale di uno dei due canali (destro o sinistro).
Il connettore inferiore viene collegato all’altoparlante: il terminale GND al filo nero (o segnato con “-“) dell’altoparlante, il terminale OUT al filo rosso (o segnato con “+”).
Per poter creare una coppia di “casse” amplificate in questo modo ho utilizzato una vecchia coppia di auricolari stereo con jack 3.5mm non funzionanti. Ho tagliato gli auricolari e ho spellato le guaine di entrambi i fili che li collegavano. All’interno di ciascuna guaina ci sono due fili smaltati (in genere colorati diversamente): uno è collegato al terminale di massa del jack, l’altro ad uno dei due terminali rimanenti del jack (che rappresentano il canale sinistro e il canale destro). I fili sono smaltati quindi non vanno in corto circuito fra loro. È quindi necessario rimuovere un tratto di smalto per ciascuno con la semplice fiamma di un accendino. La fiamma elimina la smaltatura e rende il filo stagnabile. Quindi, spella la guaina plastica di ciascun canale, separa i fili all’interno per colore (sono colorati diversamente), per ciascuno elimina lo smalto con la fiamma dell’accendino e stagnali col saldatore. Questa operazione va fatta per entrambi i canali. A questo punto avrai un filo che porta la massa e il canale sinistro e un filo che porta la massa e il canale destro. Una coppia la colleghi ad uno dei due moduli nei terminali GND e IN del connettore superiore nella foto, stessa cosa farai con l’altra coppia di fili sull’altro modulo.
Alternativamente puoi usare una coppia di casse amplificate da computer o puoi anche ascoltare la radio tramite un semplice auricolare stereo.
Vediamo ora un insieme delle “casse” create per ascoltare la radio:
Quando inizierai ad usare la radio, dovrai regolare i trimmer presenti sui due moduli in modo da dosare il segnale e far sì che il suono esca pulito e non distorto.
Infine vediamo la radio completa e in funzione:
Realizzazione del progetto
Lo schema elettrico
Prima di realizzare il circuito vero e proprio diamo un’occhiata al pinout della board:
Vediamo anche il pinout del display:
Per il nostro progetto prenderemo in considerazione solo i pin sul lato sinistro (con connettore giallo su questa foto).
In genere questo display viene venduto già col connettore montato ma se il tuo non lo dovesse avere e avessi necessità di saldarlo, ti consiglio di dare prima un’occhiata all’articolo Un altro tutorial su come saldare per imparare a fare una perfetta saldatura.
Prima di passare allo schema elettrico dovrai effettuare una piccola operazione. Il display può funzionare sia a 5V che a 3.3V ma siccome i pin digitali dell’ESP8266 non tollerano tensioni più alte di 3.3V dovremo predisporre il display per funzionare con tale tensione.
Per fare ciò dovremo semplicemente cortocircuitare il jumper J1 cioè quello mostrato nella foto seguente:
Come vedi è un’operazione abbastanza semplice: è sufficiente una piccola goccia di stagno per mettere in cortocircuito le due piazzole indicate in rosso.
Vediamo ora lo schema elettrico del progetto, realizzato come al solito con Fritzing:
Per maggiore comodità riporterò di seguito la tabella dei collegamenti tra display e ESP8266:
Display TFT | ESP8266 |
1 | D4 |
2 | D3 |
3 | D0 |
4 | D7 |
5 | D5 |
6 | 3V3 |
7 | 3V3 |
8 | GND |
Lo sketch
Creiamo il progetto PlatformIO
Abbiamo già visto la procedura di creazione di un progetto PlatformIO nell’articolo Come creare un progetto per NodeMCU ESP8266 con PlatformIO.
Non installare le librerie indicate nell’articolo ma installa le seguenti librerie:
La libreria Adafruit GFX Library by Adafruit:
La libreria Adafruit ST7735 and ST7789 Library by Adafruit:
La libreria DIYables_IRcontroller by DIYables.io:
La libreria Radio by Matthias Hertel:
Ora modifica il file platformio.ini per aggiungere queste due righe:
monitor_speed = 115200
upload_speed = 921600
in modo che abbia un aspetto del genere:
[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
monitor_speed = 115200
upload_speed = 921600
framework = arduino
lib_deps = mathertel/Radio@^3.0.1
diyables/DIYables_IRcontroller@^1.0.0
adafruit/Adafruit GFX Library@^1.11.9
adafruit/Adafruit ST7735 and ST7789 Library@^1.10.3
Ovviamente puoi scaricare il progetto dal link seguente:
Sostituisci il file main.cpp del progetto che hai creato con quello presente nel file zip.
Per la realizzazione del seguente sketch mi sono basato su due esempi:
- il primo esempio è tratto dal sito http://www.mathertel.de/Arduino/RadioLibrary.aspx ed è relativo alla gestione del modulo radio. Nell’esempio di partenza la radio è comandata con comandi inviati tramite Serial Monitor
- il secondo esempio è tratto dal sito https://newbiely.com/tutorials/esp8266/esp8266-ir-remote-control e serve per la decodifica dei comandi inviati dal telecomando e ricevuti dal ricevitore IR
Vediamo ora come funziona lo sketch.
Prima di tutto bisogna fare una modifica alla libreria del modulo radio. Nel file .pio/libdeps/nodemcuv2/DIYables_IRcontroller/src/DIYables_IRcontroller.cpp, nella funzione DIYables_IRcontroller::begin() bisogna modificare la riga:
IrReceiver.begin(_pin, ENABLE_LED_FEEDBACK);
in:
IrReceiver.begin(_pin, DISABLE_LED_FEEDBACK);
In pratica il parametro ENABLE_LED_FEEDBACK diventa DISABLE_LED_FEEDBACK. Questa modifica si rende necessaria per far funzionare contemporaneamente il ricevitore IR e il display TFT.
Inizialmente vengono incluse le librerie necessarie per la gestione della EEPROM, del bus I2C, del display TFT, del modulo radio e del ricevitore IR. Inoltre vengono definiti i GPIO per il display e viene creato l’oggetto tft che gestisce il display:
#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 D4 // TFT RST pin is connected to NodeMCU pin D4 (GPIO2)
#define TFT_CS D3 // TFT CS pin is connected to NodeMCU pin D3 (GPIO0)
#define TFT_DC D0 // 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
Viene poi definito il GPIO per la ricezione dei comandi dal ricevitore IR e definito l’oggetto irController che decodificherà tali comandi:
#define IR_RECEIVER_PIN D6 // The ESP8266 pin D6 connected to IR controller
DIYables_IRcontroller_21 irController(IR_RECEIVER_PIN, 200); // debounce time is 200ms
Viene poi definito un array di indirizzi della EEPROM:
String currentChannelAddressEE[9] = {"2", "7", "12", "17", "22", "27", "32", "37", "42"};
Tali indirizzi sono 9 e sono destinati a memorizzare i preset della radio. Parleremo più in dettaglio in seguito di questa funzionalità.
Vengono poi definite delle variabili che servono per memorizzare alcuni stati di certe funzionalità (mute/unmute, stereo/mono, equalizzazione e così via) e la stringa che conterraà il comando da eseguire (cmdToExecute).
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 = "";
Successivamente vengono definiti alcuni GPIO che dipendono dall’architettura (nel nostro caso ESP8266):
// ===== 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
A questo punto viene definita la variabile saveCurrentStation che sarà usata nella operazione di memorizzazione di una stazione radio:
bool saveCurrentStation = false;
Vengono definiti gli oggetti radio (che gestisce il modulo Si4703) e rds che riceve informazioni aggiuntive dal canale (nel nostro caso il nome della stazione radio, se presente). Segue poi la definizione di alcune variabili di supporto:
SI4703 radio;
RDSParser rds;
String lastName = "";
int16_t kbValue;
bool lowLevelDebug = false;
La funzione DisplayFrequency mostra su Serial Monitor la frequenza corrente (è più che altro usata per debug):
void DisplayFrequency()
{
char s[12];
radio.formatFrequency(s, sizeof(s));
Serial.print("FREQ:");
Serial.println(s);
} // DisplayFrequency()
La funzione DisplayServiceName serve per stampare/aggiornare su display il nome della stazione radio (estratto dai dati RDS ricevuti dal modulo radio) se presente:
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()
La funzione RDS_process processa i dati RDS:
void RDS_process(uint16_t block1, uint16_t block2, uint16_t block3, uint16_t block4)
{
rds.processData(block1, block2, block3, block4);
}
La funzione runSerialCommand gestisce i comandi ricevuti e decodificati dal ricevitore IR:
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());
}
// toggle stereo mode
else if (cmd == 's')
{
radio.setMono(!radio.getMono());
}
// toggle bass boost
else if (cmd == 'b')
{
radio.setBassBoost(!radio.getBassBoost());
}
// ----- control the frequency -----
else if (cmd == 'f')
{
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()
In particolare:
- se riceve “+” o “-” aumenta o diminuisce il volume
- se riceve “u” commuta da MUTE a UNMUTE e viceversa
- se riceve “s” commuta da STEREO a MONO e viceversa
- se riceve “b” attiva o disattiva la funzione BASS BOOST
- se riceve il comando “f” seleziona la frequenza contenuta nella variabile value
- se riceve i comandi “.” o “:” va avanti nella ricerca (scan) di una frequenza occupata da una stazione radio
- se riceve i comandi “,” o “;” va indietro nella ricerca (scan) di una frequenza occupata da una stazione radio
- i comandi “!”, “i”, “x” e “*” non sono utilizzati
La funzione testfillrects disegna all’accensione del dispositivo una animazione a base di rettangoli (a puro scopo decorativo):
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);
}
}
La funzione selectChannel seleziona la stazione richiesta (requestChannel) precedentemente memorizzata nella EEPROM:
void selectChannel(int requestChannel) {
Serial.println(String(requestChannel));
indexArray = requestChannel - 1;
if(saveCurrentStation) {
channelToMemorize = String(requestChannel);
} else {
cmdToExecute = "f";
channelToReproduce = ""; // currentChannelAddressEE[9] = {"2", "7", "12", "17", "22", "27", "32", "37", "42"};
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);
}
}
Incontriamo ora la funzione setup:
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...");
EEPROM.begin(512); //Initialize EEPROM
#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
Questa funzione inizializza il display, il controller del ricevitore IR, la porta seriale, la EEPROM e il modulo Si4703.
La funzione loop inizia con questo blocco:
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();
}
Tale blocco, ogni measureDelayCycle ms (nel nostro caso 500 ms), aggiorna i dati sul display nel caso ci siano variazioni (la frequenza, il canale, lo stato mute/unmute, lo stato stereo/mono, lo stato con e senza equalizzazione (BASS BOOST)).
La funzione loop continua col seguente blocco:
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";
channelToReproduce = ""; // currentChannelAddressEE[9] = {"2", "7", "12", "17", "22", "27", "32", "37", "42"};
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";
channelToReproduce = ""; // currentChannelAddressEE[9] = {"2", "7", "12", "17", "22", "27", "32", "37", "42"};
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;
}
}
Tale blocco decodifica i comandi provenienti dal ricevitore IR (e quindi dal telecomando) ed espleta le corrispondenti funzioni.
- se il comando è “CH+” o “CH-” il ricevitore scorre avanti o indietro fra le stazioni memorizzate nella EEPROM (sale o scende di canale)
- se il comando è “CH” il ricevitore si predispone per la memorizzazione del canale sulla EEPROM (vedremo in seguito l’operazione più in dettaglio)
- se il comando è “<<” o “>>” il ricevitore effettua una scansione libera all’indietro o in avanti e si ferma sulla prima frequenza con segnale di ampiezza sufficiente
- se il comando è “>||” il ricevitore memorizza la frequenza corrente in una posizione della EEPROM (e quindi su un canale). Vedremo in seguito l’operazione più in dettaglio
- se il comando è “+” o “-” verrà alzato o diminuito il volume
- se il comando è “EQ” verrà attivata o disattivata la funzione BASS BOOST
- se il comando è “100+” viene attivata/disattivata la funzione mute
- se il comando è “200+” viene settata la modalità STEREO oppure MONO
- se il comando è una cifra compresa fra “1” e “9” verrà riprodotta la stazione precedentemente memorizzata su quella posizione
Infine incontriamo gli ultimi comandi:
kbValue = channelToReproduce.toInt();
runSerialCommand(cmdToExecute.charAt(0), kbValue);
// check for RDS data
radio.checkRDS();
La prima ricava la frequenza da riprodurre, la seconda esegue il comando ricevuto (con l’eventuale valore di frequenza), la terza controlla periodicamente i dati RDS.
Istruzioni per l’uso
All’accensione il ricevitore si predispone alla ricezione della frequenza di default 89.30 MHz.
Con i tasti seguenti puoi andare avanti o indietro con lo scan delle frequenze:
Supponi di arrivare ad una frequenza di tuo gradimento (per esempio 93.80 MHz) e di volerla memorizzare sulla posizione 1 (il tasto “1” del telecomando). Col ricevitore sintonizzato su tale frequenza prima premi il tasto “CH”:
poi premi il tasto “1”:
infine premi il tasto:
La frequenza 93.80 MHz verrà memorizzata nella EEPROM in corrispondenza del tasto “1” e sul display apparirà un messaggio di conferma.
Continua con lo scan delle frequenze e memorizza il tasto “2” e così via fino al 9. Si possono memorizzare 9 stazioni (sui tasti da 1 a 9).
Puoi anche sovrascrivere la frequenza memorizzata su un tasto. Se per esempio hai memorizzato per errore la frequenza 93.80 MHz sul tasto “1” ma invece volevi salvarci la frequenza 104.70 MHz devi usare i tasti scansione delle frequenze ( |◅◅ e ▻▻| ) fino ad arrivare alla frequenza 104.70 MHz e poi ripetere la procedura di memorizzazione (tasto CH, tasto 1, tasto ▻||).
Apriamo una piccola parentesi su come funziona il meccanismo.
Abbiamo visto in precedenza che è stato definito un array degli indirizzi sulla EEPROM:
String currentChannelAddressEE[9] = {"2", "7", "12", "17", "22", "27", "32", "37", "42"};
Al tasto “1” del telecomando corrisponde l’indirizzo “2” sulla EEPROM, al tasto “2” del telecomando corrisponde l’indirizzo “7” sulla EEPROM e così via fino al tasto “9” a cui corrisponde l’indirizzo “42” sulla EEPROM.
Noterai che ciascun numero (a parte il primo) dista 5 posizioni dal precedente. Questo perché i valori di frequenza vengono convertiti in modo da occupare sempre 5 posizioni esatte. Per esempio, la frequenza 98.60 MHz viene memorizzata come 09860 (5 posizioni) mentre la frequenza 102.30 MHz viene memorizzata come 10230 (5 posizioni).
Quindi se memorizzo la frequenza 98.60 MHz sul tasto 5 e la frequenza 102.30 MHz sul tasto 8, alla fine sulla EEPROM avrò memorizzati all’indirizzo 22 il valore 09860 e all’indirizzo 37 il valore 10230.
Quando viene premuto il tasto 5 per poter ascoltare la stazione memorizzata in quella posizione, il valore 09860 viene convertito in 9860 e mandato al bus I2C per dire al modulo radio di sintonizzarsi su quella frequenza e poi convertito in 98.60 e mandato al display per essere visualizzato.
Se invece viene premuto il tasto 8, il valore 10230 viene mandato direttamente al bus I2C per dire al modulo radio di sintonizzarsi su quella frequenza e poi convertito in 102.30 e mandato al display per essere visualizzato.
I tasti
e
ci consentono di scorrere i canali memorizzati.
Se desideri comprendere meglio il funzionamento della EEPROM ti invito a leggere l’articolo Come usare la memoria EEPROM sul NodeMCU ESP8266
NOTA BENE: alla prima accensione della ESP8266, gli indirizzi “2”, “7”, “12”, “17”, “22”, “27”, “32”, “37”, “42” saranno occupati da numeri e lettere casuali (a seconda dello stato in cui si trova la EEPROM in quel momento) quindi non corrispondenti ad alcuna stazione. È quindi opportuno procedere subito alla memorizzazione delle stazioni sui tasti da 1 a 9 per sovrascrivere tali valori casuali con stazioni radio realmente esistenti in modo da evitare comportamenti inattesi o eventuali blocchi del modulo radio che non riesce ad interpretare tali stringhe (questo controllo non è stato incluso per semplicità ma potresti provare ad implementarlo tu).
I tasti
regolano il volume.
Il tasto
fa passare dallo stato MUTE (radio muta) allo stato UNMUTE (radio non muta) e viceversa.
Il tasto
commuta fra la modalità STEREO e la modalità MONO e viceversa.
Il tasto
attiva/disattiva la modalità BASS BOOST.
Il tasto
non è associato ad alcuna funzione. Potresti provare ad aggiungere una funzione a questo tasto.
E finalmente un bel video del funzionamento della nostra EPS8266 FM radio
Newsletter
Se vuoi essere aggiornato sui nuovi articoli, iscriviti alla newsletter. Prima dell’iscrizione alla newsletter leggi la pagina Privacy Policy (UE)
Se ti vuoi disiscrivere dalla newsletter clicca sul link che troverai nella mail della newsletter.