ESP32-S3 + INMP441 + GC9A01 Spettroanalizzatore audio circolare fai-da-te | Tutorial completo I2S + FFT + SPI

Legge l'audio I2S dal microfono digitale INMP441 con ESP32-S3, analizza con FFT a 512 punti e disegna in tempo reale 16 barre spettrali arcobaleno sul display TFT circolare GC9A01. Include cablaggio completo, installazione librerie e commenti al codice.

Riassunto in una frase: ESP32-S3 + microfono INMP441 + schermo circolare GC9A01, costruisci uno spettroanalizzatore audio circolare che “balla”, tutorial completo I2S + FFT + SPI.

Tutorial completo: costruire uno spettroanalizzatore audio circolare che “balla” con ESP32-S3 + INMP441 + GC9A01 (I2S + FFT + SPI)

Difficoltà: ⭐⭐⭐☆☆ (accessibile con un minimo di esperienza Arduino) Tempo stimato: 45 minuti Ambiente di test: Arduino IDE 2.3.8 GFX Library for Arduino v1.6.5 arduinoFFT v2.0.4


TL;DR (versione senza preamboli):

  1. Cablaggio: INMP441 SD→GPIO4, WS→GPIO5, SCK→GPIO6, L/R deve essere collegato a GND
  2. Cablaggio: GC9A01 SCL→GPIO12, SDA→GPIO11, CS→GPIO9, DC→GPIO10, RST→GPIO18, BL→GPIO7
  3. Installare le librerie: GFX Library for Arduino (autore moononournation) + arduinoFFT (autore kosme)
  4. Incollare il codice, caricare, parlare davanti al microfono, le barre arcobaleno nel cerchio inizieranno a ballare

Introduzione

Da quando ho comprato uno schermo circolare da 1,28 pollici, mi sono divertito parecchio: la forma circolare offre possibilità molto diverse rispetto a quella quadrata. Ora, combinandolo con il modulo microfono INMP441, voglio realizzare qualcosa di davvero bello: visualizzazione spettrale audio in tempo reale.

Quando senti “spettroanalizzatore”, probabilmente ti viene in mente lo stile rétro di Winamp con le sue classiche barre verticali (lo avevo installato sul PC, potevo stare a guardare le barre che ballavano per un pomeriggio intero mentre ascoltavo musica). Ma uno spettro circolare è tutta un’altra cosa: 16 barre colorate che si irradiano dal centro verso l’esterno, più il volume è alto più le barre sono lunghe, e sulla sommità di ciascuna barra c’è un puntino bianco di picco che scende lentamente… a dire il vero, sono rimasto incantato a fissarlo per cinque minuti senza andare a mangiare.

Questo articolo ti guiderà passo dopo passo nell’uso di ESP32-S3 + microfono digitale INMP441 + schermo TFT circolare GC9A01, dal cablaggio al codice, per realizzare uno spettroanalizzatore arcobaleno circolare che risponde al suono in tempo reale. Un maker con un minimo di esperienza può vedere i risultati entro 45 minuti.


Effetto del progetto

  • Acquisizione audio in tempo reale dal microfono (44,1 kHz, 16 bit)
  • Analisi FFT a 512 punti, suddivisa in 16 bande di frequenza
  • Barre arcobaleno sul display circolare irradiate dall’interno verso l’esterno, con picchi bianchi a discesa lenta
  • Frequenza di aggiornamento di circa 20 fps, perfettamente fluida a occhio nudo


Descrizione dei componenti

Display TFT circolare GC9A01

Se lo schermo rettangolare normale è come un “cellulare con tastiera”, il GC9A01 è come il “quadrante di uno smartwatch”: LCD circolare da 1,28 pollici, il driver si chiama proprio GC9A01, interfaccia SPI, alimentazione a 3,3 V, si pilota con soli 8 fili.

ParametroValore
Dimensioni schermo1,28 pollici
Risoluzione240 × 240 pixel
InterfacciaSPI (4 fili)
Tensione di lavoro3,3 V
Driver ICGC9A01
Tipo pannelloIPS (visualizzazione completa)

Perché sceglierlo: è il piccolo schermo circolare più comune sul mercato, supportato nativamente dalla libreria Arduino_GFX, si inizializza con 5 righe di codice, con pochissimi problemi noti.


Microfono digitale MEMS INMP441

L’INMP441 è un microfono MEMS omnidirezionale digitale: in parole povere, genera direttamente un segnale digitale I2S, senza bisogno di ADC. È come avere un interprete simultaneo che traduce in tempo reale ciò che dici in un formato digitale comprensibile dal MCU, eliminando tutta la complessità dei segnali analogici.

ParametroValore
InterfacciaI2S (audio digitale)
Tensione di lavoro1,8 V ~ 3,3 V
Risposta in frequenza60 Hz ~ 15 kHz
Rapporto segnale/rumore61 dBA
Sensibilità-26 dBFS (valore tipico)
DirezionalitàOmnidirezionale

Perché sceglierlo: l’interfaccia I2S è pulita, non richiede ADC aggiuntivo, il rapporto segnale/rumore di 61 dBA è nettamente superiore rispetto alla maggior parte delle capsule microfoniche analogiche economiche, più che sufficiente per uno spettroanalizzatore.

Vale la pena notare che l’INMP441 era originariamente prodotto da InvenSense (poi acquisita da TDK), che lo ha ufficialmente dichiarato nello stato Obsolete (discontinuato/fuori produzione). Presso i principali distributori di componenti elettronici come Mouser e DigiKey, è già contrassegnato come fuori produzione. Tuttavia, sui mercati online si trovano ancora in abbondanza moduli INMP441 blu/neri a pochi yuan. Questo accade perché sul mercato continentale esistono ancora grandi scorte di giacenze di magazzino, oppure sono presenti sul mercato alcuni chip compatibili/ricondizionati di produzione nazionale che continuano a usare questo nome. Se lo scopo è un progetto DIY personale, un tutorial o un piccolo demo, i moduli attualmente in vendita funzionano ancora perfettamente.

Pertanto, se devi sviluppare un prodotto commerciale, questo modulo non è la scelta consigliata.


Lista componenti (BOM)

ComponenteModello / SpecificheQuantità
Scheda di sviluppoESP32-S3 (con USB-C)1
Display TFT circolareGC9A01, 1,28 pollici, 240×2401
Microfono digitaleModulo I2S INMP4411
Cavi dupontalcuni

Descrizione pin dei componenti

Pin del display GC9A01

PinDescrizione funzione
VCCAlimentazione positiva (collegare a 3,3 V)
GNDAlimentazione negativa (masse)
SCL / CLKClock SPI
SDA / MOSIDati SPI (trasmissione dal master)
CSChip select (attivo basso)
DCSelezione dati / comando
RSTReset (attivato con livello basso)
BLControllo retroilluminazione (collegare a 3,3 V per sempre accesa, oppure a un GPIO per regolazione PWM)

Pin del microfono INMP441

PinDescrizione funzione
VDDAlimentazione positiva (collegare a 3,3 V)
GNDAlimentazione negativa (masse)
SDUscita dati I2S (collegare all’ingresso dati ESP32)
WSWord clock / sincronizzazione frame (selezione canale sinistro/destro)
SCKBit clock
L/RSelezione canale: GND = canale sinistro, 3,3 V = canale destro, non lasciare flottante

Schema di cablaggio

Si consiglia di verificare ogni filo con la tabella subito dopo averlo collegato: si risparmia l’80% del tempo di troubleshooting.

Cablaggio display GC9A01

Pin moduloESP32-S3Colore filo (riferimento)
VCC3,3 VRosso
GNDGNDGrigio
SCL / CLKGPIO12Giallo
SDA / MOSIGPIO11Blu
CSGPIO9Verde
DCGPIO10Arancione
RSTGPIO18Viola
BLGPIO7 / 3,3 VCiano

Cablaggio microfono INMP441

Pin moduloESP32-S3Colore filo (riferimento)
VDD3,3 VRosso
GNDGNDGrigio
SDGPIO4Blu
WSGPIO5Verde
SCKGPIO6Giallo
L/RGND (canale sinistro)Grigio

Attenzione: il pin L/R deve essere collegato, non può essere lasciato flottante. Un pin L/R flottante provoca una selezione del canale indefinita, acquisendo solo rumore casuale; le barre dello spettro balleranno senza alcuna correlazione con il suono reale — non chiedetemi come lo so.

  • Assicurarsi di utilizzare l’alimentazione a 3,3 V, non collegare a 5 V
  • Il pin L/R dell’INMP441 collegato a GND = uscita canale sinistro
  • Collegare prima tutti i fili, verificare alimentazione e massa con un multimetro prima di accendere, per evitare cortocircuiti

Librerie da installare

In Arduino IDE → Strumenti → Gestore librerie cercare e installare:

LibreriaAutoreVersione testataUtilizzo
Arduino_GFX_Librarymoononournationv1.6.5Driver display GC9A01
arduinoFFTkosmev2.0.4Trasformata di Fourier veloce

Il driver I2S (driver/i2s.h) è una libreria integrata di ESP32, non necessita di installazione aggiuntiva.

Si raccomanda Arduino IDE versione 2.3.x o superiore; la versione precedente 1.x ha un supporto instabile per ESP32-S3.


Codice completo

#include <Arduino_GFX_Library.h>
#include <driver/i2s.h>
#include <arduinoFFT.h>

// ====== Passo 1: Definire i pin del display ======
#define TFT_SCK   12
#define TFT_MOSI  11
#define TFT_CS    9
#define TFT_DC    10
#define TFT_RST   18
#define TFT_BL    7

// ====== Passo 2: Definire i pin del microfono ======
#define I2S_WS    5
#define I2S_SD    4
#define I2S_SCK   6
#define I2S_PORT  I2S_NUM_0

// ====== Parametri FFT ======
#define SAMPLES   512
#define BANDS     16

// ====== Inizializzare il display GC9A01 ======
Arduino_DataBus *bus = new Arduino_ESP32SPI(
  TFT_DC, TFT_CS, TFT_SCK, TFT_MOSI, -1);
Arduino_GFX *gfx = new Arduino_GC9A01(
  bus, TFT_RST, 0, true);

// ====== Buffer FFT ======
double vReal[SAMPLES];
double vImag[SAMPLES];
ArduinoFFT<double> FFT = ArduinoFFT<double>(
  vReal, vImag, SAMPLES, 44100);

// ====== Energia per banda e picchi ======
float bandValues[BANDS];
float peakValues[BANDS];
int16_t sampleBuf[SAMPLES];

// ====== Utilità colore: HSL → RGB565 ======
uint16_t hslToRgb565(float h, float s, float l) {
  float c = (1.0f - fabsf(2.0f * l - 1.0f)) * s;
  float x = c * (1.0f - fabsf(fmodf(h / 60.0f, 2.0f) - 1.0f));
  float m = l - c / 2.0f;
  float r, g, b;
  if (h < 60)       { r=c; g=x; b=0; }
  else if (h < 120) { r=x; g=c; b=0; }
  else if (h < 180) { r=0; g=c; b=x; }
  else if (h < 240) { r=0; g=x; b=c; }
  else if (h < 300) { r=x; g=0; b=c; }
  else              { r=c; g=0; b=x; }
  uint8_t R = (uint8_t)((r + m) * 31);
  uint8_t G = (uint8_t)((g + m) * 63);
  uint8_t B = (uint8_t)((b + m) * 31);
  return (R << 11) | (G << 5) | B;
}

// ====== Passo 3: Inizializzare il microfono I2S ======
void setupMicrophone() {
  const i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = 44100,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = I2S_COMM_FORMAT_STAND_I2S,
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 8,
    .dma_buf_len = 64,
    .use_apll = false,
    .tx_desc_auto_clear = false,
    .fixed_mclk = 0
  };
  const i2s_pin_config_t pin_config = {
    .bck_io_num = I2S_SCK,
    .ws_io_num = I2S_WS,
    .data_out_num = -1,
    .data_in_num = I2S_SD
  };
  i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_PORT, &pin_config);
  i2s_start(I2S_PORT);
}

void setup() {
  Serial.begin(115200);

  // Passo 4: Accendere la retroilluminazione, inizializzare il display
  pinMode(TFT_BL, OUTPUT);
  digitalWrite(TFT_BL, HIGH);
  gfx->begin();
  gfx->fillScreen(0x0000);

  // Passo 5: Inizializzare il microfono
  setupMicrophone();

  memset(peakValues, 0, sizeof(peakValues));
}

// ====== Disegnare lo spettro circolare ======
void drawCircularSpectrum() {
  int cx = 120, cy = 120;
  int innerR = 25;
  int maxLen = 85;
  float angleStep = 2.0f * PI / BANDS;
  float barWidth = angleStep * 0.7f;

  gfx->fillScreen(0x0000);

  for (int i = 0; i < BANDS; i++) {
    float angle = i * angleStep - PI / 2.0f;
    float hue = (float)i / BANDS * 360.0f;
    float val = bandValues[i];
    int barLen = (int)(val * maxLen);

    for (int r = innerR; r < innerR + barLen; r += 2) {
      float t = (float)(r - innerR) / maxLen;
      uint16_t color = hslToRgb565(hue, 1.0f, 0.3f + t * 0.3f);
      float x1 = cx + cosf(angle - barWidth/2) * r;
      float y1 = cy + sinf(angle - barWidth/2) * r;
      float x2 = cx + cosf(angle + barWidth/2) * r;
      float y2 = cy + sinf(angle + barWidth/2) * r;
      gfx->drawLine(x1, y1, x2, y2, color);
    }

    if (peakValues[i] > 0.02f) {
      int peakR = innerR + (int)(peakValues[i] * maxLen) + 3;
      float px = cx + cosf(angle) * peakR;
      float py = cy + sinf(angle) * peakR;
      gfx->fillCircle(px, py, 2, 0xFFFF);
    }

    peakValues[i] *= 0.95f;
    if (bandValues[i] > peakValues[i]) {
      peakValues[i] = bandValues[i];
    }
  }
}

void loop() {
  // Passo 6: Leggere i dati I2S dal microfono
  size_t bytes_read = 0;
  i2s_read(I2S_PORT, sampleBuf, sizeof(sampleBuf),
           &bytes_read, portMAX_DELAY);

  // Passo 7: Riempire la parte reale della FFT con i campioni
  for (int i = 0; i < SAMPLES; i++) {
    vReal[i] = (double)sampleBuf[i];
    vImag[i] = 0.0;
  }

  // Passo 8: Eseguire la FFT
  FFT.windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.compute(FFT_FORWARD);
  FFT.complexToMagnitude();

  // Passo 9: Mappare i risultati FFT su 16 bande
  memset(bandValues, 0, sizeof(bandValues));
  int specLen = SAMPLES / 2;
  for (int i = 0; i < BANDS; i++) {
    int start = (int)(pow((float)i / BANDS, 1.8f) * specLen * 0.7f);
    int end   = (int)(pow((float)(i+1) / BANDS, 1.8f) * specLen * 0.7f);
    if (end <= start) end = start + 1;
    float sum = 0;
    for (int j = start; j < end && j < specLen; j++) {
      sum += (float)vReal[j];
    }
    float avg = sum / (end - start);
    bandValues[i] = constrain(avg / 5000.0f, 0.0f, 1.0f);
  }

  // Passo 10: Disegnare lo spettro circolare
  drawCircularSpectrum();
}

Spiegazione del codice

1. Perché SAMPLES = 512? 512 è una potenza di 2 e l’algoritmo FFT è più efficiente con questa lunghezza. Con una frequenza di campionamento di 44,1 kHz, una FFT a 512 punti offre una risoluzione in frequenza di circa 86 Hz, più che sufficiente. Con 256 sarebbe più veloce ma con meno dettaglio in frequenza; con 1024 sarebbe più fine ma il framerate diminuirebbe sensibilmente.

2. Perché la distribuzione delle bande usa pow(…, 1.8)? Una suddivisione lineare delle frequenze riempirebbe le bande alte di troppi dati, lasciando quelle basse vuote. La distribuzione esponenziale rende le bande di bassa frequenza più strette (più dettagliate) e quelle di alta frequenza più ampie (assorbendo il rumore), avvicinandosi alla curva di percezione uditiva umana e risultando più “naturale” visivamente.

3. Da dove deriva la normalizzazione dividendo per 5000? Questo valore dipende dalla distanza del microfono dalla sorgente sonora e dal volume ambientale: scenari diversi richiedono regolazioni manuali. Se le barre sono sempre al massimo (energia saturata), aumentare il valore 5000; se le barre sono troppo basse e appena visibili, diminuirlo.

*4. A cosa serve peakValues[i] = 0.95? È la tecnica classica del “peak hold + decay lento”: quando il suono si ferma improvvisamente, il punto bianco di picco non scompare istantaneamente, ma si riduce moltiplicando per 0,95 ad ogni fotogramma, scendendo gradualmente. L’effetto visivo è più fluido, simile a quello dei dispositivi audio professionali.


Risoluzione dei problemi più comuni

Niente panico, il 90% dei problemi deriva da questi punti:

Lo schermo è completamente nero, non mostra nulla Verificare prima che la retroilluminazione (pin BL) sia effettivamente alta (se il modulo non ha il pin BL si può ignorare), poi controllare che i quattro fili SPI (SCK / MOSI / CS / DC) siano collegati correttamente e non abbiano contatti deboli. Misurare con il multimetro che VCC abbia 3,3 V. Se la retroilluminazione è accesa ma lo schermo è nero, molto probabilmente CS o DC sono invertiti, provare a scambiarli.

Le barre dello spettro sono ferme oppure ballano senza correlazione con il suono Prima cosa: verificare che il pin L/R dell’INMP441 sia collegato a GND, è l’errore più frequente. Un pin L/R flottante provoca una selezione del canale anomala, acquisendo solo rumore casuale. Dopo aver collegato L/R, verificare i numeri GPIO dei tre fili SD / WS / SCK.

Tutte le barre dello spettro sono al massimo (energia sempre massima) Aumentare il valore 5000 nel codice bandValues[i] = constrain(avg / 5000.0f, ...), per esempio a 15000 o 30000. Anche un microfono troppo vicino alla sorgente sonora può causare questo problema: provare prima ad allontanare il microfono di 30 cm.

Le barre dello spettro reagiscono ma solo poche si muovono Probabilmente la sorgente sonora utilizzata per il test ha uno spettro di frequenza troppo stretto (per esempio solo un fischietto a tono singolo). Provare con un brano musicale a spettro completo (con bassi, voce e strumenti acuti) e verificare che tutte le bande rispondano.

Compilazione fallita: errore sulla classe template ArduinoFFT Verificare di aver installato arduinoFFT (versione kosme) v2.x. La sintassi v1.x è ArduinoFFT FFT (senza parametro template), mentre v2.x usa ArduinoFFT<double>; le API delle due versioni non sono compatibili. Aggiornare direttamente all’ultima versione dal Gestore librerie.


FAQ

D: Cosa succede se non collego il pin L/R dell’INMP441? R: La selezione del canale rimane flottante, il comportamento del microfono è indefinito; nella pratica si acquisisce quasi sempre rumore casuale e le barre dello spettro ballano in modo del tutto indipendente dal suono. Collegare a GND = canale sinistro, collegare a 3,3 V = canale destro; scegliere una delle due opzioni, ma non lasciare scollegato.

D: Posso cambiare SAMPLES a 1024? Quali sono le conseguenze? R: Sì, la risoluzione in frequenza passa da circa 86 Hz a circa 43 Hz, con maggior dettaglio sulle basse frequenze. Il compromesso è che il tempo di acquisizione e calcolo per ogni fotogramma raddoppia, e il framerate scende da circa 20 fps a circa 10 fps. Per la visualizzazione spettrale, 10 fps sono ancora accettabili a occhio nudo.

D: Con solo 3,3 V, l’INMP441 funziona correttamente? R: Assolutamente sì. L’INMP441 supporta alimentazione da 1,8 V a 3,3 V; 3,3 V è la tensione di lavoro più comune, non serve un modulo di regolazione di tensione aggiuntivo.

D: L’utilizzo della CPU di ESP32-S3 è elevato? Può interferire con altri task? R: Una FFT a 512 punti alla frequenza di 240 MHz di ESP32-S3 utilizza circa il 10%-15% del tempo CPU su un singolo core. Se è necessario eseguire anche Wi-Fi o Bluetooth, si consiglia di assegnare FFT + disegno al Core 0 e i task di rete al Core 1, in modo che non interferiscano tra loro.

D: Posso sostituire il GC9A01 con un ST7789 o un altro driver di display? R: Sì. Arduino_GFX_Library supporta decine di driver; basta sostituire Arduino_GC9A01 nel codice con la classe corrispondente (per esempio Arduino_ST7789), modificare i parametri di risoluzione e adattare il cablaggio secondo il datasheet del nuovo display. Nota: con un display non circolare è necessario ricalcolare le coordinate del centro.

D: Quando è tutto silenzioso c’è “rumore di fondo”, le barre non vanno a zero. Cosa fare? R: L’INMP441 ha un rumore intrinseco (SNR 61 dBA significa che una minima quantità di rumore ambientale viene sempre acquisita). Si può aggiungere una soglia di rumore: prima della mappatura aggiungere una riga if (avg < 200) avg = 0;, così in condizioni di silenzio le barre andranno completamente a zero. Anche aumentare il divisore di normalizzazione può aiutare.

D: Quale versione del driver I2S utilizza ESP32-S3? R: Questo articolo utilizza il driver I2S legacy in stile ESP-IDF v4.x (i2s_driver_install / i2s_read). ESP-IDF v5.x ha introdotto la nuova API I2S (i2s_new_channel ecc.); se il pacchetto di supporto della scheda ESP32-S3 è stato aggiornato alla versione 3.x, sarà necessario riscrivere la funzione setupMicrophone() secondo la nuova API.


Idee per sviluppi futuri

  • Passare a 32 bande di frequenza, abbinato a uno schermo circolare più grande (per esempio GC9A01A da 2,1 pollici), per uno spettro più dettagliato
  • Aggiungere pulsanti touch per cambiare modalità di visualizzazione (radiazione circolare / barre verticali / forma d’onda oscilloscopio)
  • Connettersi via Wi-Fi per inviare i dati spettrali a un browser e renderizzarli nuovamente in una pagina web
  • Utilizzare due moduli INMP441 per audio stereo, con canale sinistro e destro in colori diversi

Riferimenti