Skip to Content

Iniziare con MaTouch AI ESP32S3 2.8″ TFT ST7789V

Iniziare con MaTouch AI ESP32S3 2.8″ TFT ST7789V

The MaTouch AI ESP32-S3 con display TFT ST7789V da 2,8 pollici di Makerfabs è una scheda di sviluppo compatta pensata per progetti che combinano connettività wireless, output grafico e machine learning integrato.

Costruita attorno al microcontrollore ESP32-S3, integra processori dual-core Xtensa LX7, Wi-Fi e Bluetooth Low Energy (BLE). L’aggiunta di un display TFT da 2,8 pollici 240×320 basato sul driver ST7789V consente la visualizzazione a colori completi, rendendo la scheda adatta a interfacce utente, visualizzazione dati e applicazioni embedded che richiedono interazione in tempo reale.

Makerfabs ha posizionato la scheda come una piattaforma versatile per sviluppatori che vogliono esplorare visione artificiale, riconoscimento audio e interfacce grafiche senza necessità di moduli esterni. In questo tutorial imparerai come iniziare con la MaTouch AI ESP32S3 2.8″ TFT ST7789V.

Parti Necessarie

Ti serviranno una scheda MaTouch AI ESP32S3 2.8″ TFT ST7789V e un cavo USB-C per programmare la scheda e provare gli esempi di codice.

MaTouch AI ESP32S3 2.8″ TFT ST7789V

Cavo USB C

Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.

Hardware della MaTouch AI ESP32S3 2.8″ TFT ST7789V

La MaTouch ESP32-S3 2.8″ TFT è basata sul microcontrollore ESP32-S3, che combina una CPU dual-core Xtensa® LX7 fino a 240 MHz con 16 MB di memoria flash e 8 MB di PSRAM.

Questa base hardware garantisce che la scheda possa gestire compiti complessi come elaborazione audio in tempo reale, rendering grafico e inferenza di machine learning direttamente sul dispositivo. La connettività wireless include Wi-Fi 802.11 b/g/n e Bluetooth 5.0 LE, rendendo la scheda versatile per applicazioni IoT e connesse.

La foto seguente mostra il retro della scheda con i singoli componenti etichettati:

Back of MaTouch AI ESP32S3 2.8" TFT ST7789V
Retro della MaTouch AI ESP32S3 2.8″ TFT ST7789V

Display e Touch

La scheda dispone di un display TFT LCD da 2,8 pollici pilotato dal controller ST7789V con risoluzione di 320 × 240 pixel (QVGA) e supporto per 65K colori. Si connette tramite un’interfaccia SPI. Un pannello multi-touch capacitivo (GT911) supporta fino a cinque punti di tocco simultanei, permettendo interfacce utente intuitive per pannelli di controllo o visualizzazione dati. L’immagine sotto mostra il fronte della scheda con display e fotocamera:

Front of Back of MaTouch AI ESP32S3 2.8" TFT ST7789V
Fronte e retro della MaTouch AI ESP32S3 2.8″ TFT ST7789V

Sottosistema Audio

L’ingresso audio è gestito da microfoni MEMS digitali INMP441 duali, che forniscono acquisizione audio stereo per applicazioni come riconoscimento vocale o rilevamento audio. Per l’uscita, la scheda integra un amplificatore I²S MAX98357, che eroga fino a 3,2 W su un altoparlante da 4 Ω, permettendo la riproduzione diretta di audio o risposte vocali senza bisogno di DAC o amplificatori esterni.

Memoria e Espansione

Per l’archiviazione e il logging dati, la scheda include uno slot per schede MicroSD che supporta schede fino a 32 GB. Sono disponibili header GPIO per periferiche aggiuntive come sensori o attuatori. L’immagine sotto mostra il pinout delle due porte GPIO (J1 e J3) accessibili sul retro della scheda:

Pinout of GPIO ports
Pinout delle porte GPIO

Supporto Fotocamera

La scheda integra un interfaccia per fotocamera compatibile con la fotocamera OV3660. Questo permette di implementare applicazioni di visione artificiale come classificazione immagini, rilevamento oggetti o semplice acquisizione video sfruttando le capacità di accelerazione AI dell’ESP32-S3.

Gestione Energetica

La scheda può essere alimentata tramite un interfaccia USB Type-C (4,0 V–5,25 V) e include un circuito di ricarica TP4056 per batterie al litio o polimeri di litio. Presenta inoltre un connettore per batteria, un interruttore hardware e un fuel gauge MAX17048, che permette al sistema di monitorare capacità e stato di carica della batteria.

Interfacce e Controllo

Per lo sviluppo, la scheda offre sia un interfaccia USB-to-UART (CH340K) che connettività USB nativa, offrendo flessibilità nella programmazione e nel debug. I pulsanti Boot e Reset consentono il controllo a basso livello durante il flashing del firmware o la risoluzione dei problemi.

LED e Orologio

Un LED RGB WS2812 fornisce indicazioni visive di stato e può essere programmato per notifiche o feedback utente. Un modulo RTC (PCF8563T) garantisce una precisa gestione del tempo.

Riepilogo delle Specifiche Tecniche

CaratteristicaSpecifiche
ControllerESP32-S3, CPU dual-core Xtensa LX7, fino a 240 MHz
Memoria16 MB Flash, 8 MB PSRAM
WirelessWi-Fi 802.11 b/g/n, Bluetooth 5.0 LE
DisplayTFT LCD 2,8″, 320 × 240 (QVGA), driver ST7789V, interfaccia SPI
Touch PanelCapacitivo, GT911, multi-touch a 5 punti
Ingresso AudioMicrofoni MEMS digitali INMP441 duali
Uscita AudioAmplificatore I²S MAX98357, 3,2 W @ 4 Ω
MemoriaSlot per scheda MicroSD (fino a 32 GB)
FotocameraSupporto interfaccia OV3660
LED RGB1 × LED programmabile WS2812
RTCOrologio in tempo reale PCF8563T
BatteriaPorta batteria con interruttore, caricatore TP4056, fuel gauge MAX17048
Interfacce USBUSB-to-UART (CH340K), USB nativo
PulsantiBoot e Reset
AlimentazioneUSB Type-C 5 V (4,0–5,25 V)
Espansione2 porte GPIO

Puoi trovare lo schema elettrico della scheda al seguente link:

Installazione del Core ESP32

Se è il tuo primo progetto con una scheda della serie ESP32, devi prima installare il core ESP32. Se le schede ESP32 sono già installate nel tuo Arduino IDE, puoi saltare questa sezione.

Inizia aprendo la finestra Preferenze selezionando “Preferences…” dal menu “File”. Si aprirà la finestra Preferenze mostrata sotto.

Nella scheda Settings troverai una casella di modifica in fondo alla finestra etichettata “Additional boards manager URLs“:

Additional boards manager URLs in Preferences
URL aggiuntive per il Boards Manager nelle Preferenze

In questo campo incolla il seguente URL:

https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json

Questo permetterà all’Arduino IDE di sapere dove trovare le librerie core ESP32. Successivamente installeremo le schede ESP32 tramite il Boards Manager.

Apri il Boards Manager tramite “Tools -> Boards -> Board Manager”. Vedrai il Boards Manager apparire nella barra laterale sinistra. Digita “ESP32” nel campo di ricerca in alto e dovresti vedere due tipi di schede ESP32; le “Arduino ESP32 Boards” e le schede “esp32 di Espressif”. Noi vogliamo le librerie “esp32 di Espressif”. Clicca sul pulsante INSTALL e attendi che il download e l’installazione siano completati.

Install ESP32 Core libraries
Installa le librerie Core ESP32

Selezione della Scheda

Infine dobbiamo selezionare una scheda ESP32. Nel caso della MaTouch AI ESP32S3, scegliamo il generico “ESP32S3 Dev Module”. Per farlo, clicca sul menu a tendina e poi su “Select other board and port…”:

Drop-down Menu for Board Selection
Menu a tendina per la selezione della scheda

Si aprirà una finestra dove puoi digitare “esp32s3 dev” nella barra di ricerca. Vedrai la scheda “ESP32S3 Dev Module” sotto Boards. Cliccaci sopra, seleziona la porta COM per attivarla e poi clicca OK:

Board Selection Dialog "ESP32S3 Dev Module" board
Finestra di selezione scheda “ESP32S3 Dev Module”

Nota che devi collegare la scheda al computer tramite cavo USB prima di poter selezionare una porta COM. La scheda ha due porte USB, una nativa e una per TTL/UART. Per la comunicazione seriale con la scheda devi usare la porta etichettata “USB TTL”, che è quella più vicina all’angolo:

USB TTL Port for Serial Communication
Porta USB TTL per comunicazione seriale

Impostazioni Strumenti

Nelle sezioni successive troverai esempi di codice per i vari componenti hardware della scheda. Alcuni richiedono memoria considerevole e avrai bisogno delle seguenti impostazioni per farli funzionare. Puoi trovare queste impostazioni nel menu Tools:

Settings for  MaTouch AI ESP32S3 2.8" TFT ST7789V
Impostazioni per MaTouch AI ESP32S3 2.8″ TFT ST7789V

Soprattutto, Flash Size è impostato a 16MB(128Mb), Partition Scheme è 16M Flash (3MB APP/9.9MB FATFS), PSRAM è impostato su OPI PSRAM. Le altre impostazioni dovrebbero essere quelle di default.

Esempio di Codice: Interfaccia Serial

Iniziamo testando la comunicazione Serial. Apri il tuo Arduino IDE, inserisci il seguente codice e caricalo sulla MaTouch AI ESP32S3.

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

void loop() {
  Serial.println("Makerguides");
  delay(2000);
}

Poi apri il Monitor Serial e dovresti vedere il testo “Makerguides” stampato ogni due secondi. In caso contrario, assicurati che il cavo USB sia collegato alla porta corretta della scheda.

Esempio di Codice: LED RGB

Proviamo ora il LED RGB integrato. Dovrai installare la Adafruit_NeoPixel libreria per usare questo esempio. Cambia semplicemente il colore del LED RGB da rosso, a verde, a blu ogni 200 millisecondi:

#include "Adafruit_NeoPixel.h"

const byte dataIn = 0;

Adafruit_NeoPixel pixels(1, dataIn, NEO_GRB + NEO_KHZ800);

void setup() {
  pixels.begin();
  pixels.clear();
  pixels.setBrightness(50);
  pixels.show();
}

void loop() {
  pixels.setPixelColor(0, 255, 0, 0);  // red
  pixels.show();
  delay(200);

  pixels.setPixelColor(0, 0, 255, 0);  // green
  pixels.show();
  delay(200);

  pixels.setPixelColor(0, 0, 0, 255);  // blue
  pixels.show();
  delay(200);
}

Se vuoi maggiori informazioni sul LED RGB WS2812 e su come usarlo, dai un’occhiata ai LED Ring Clock with WS2812 e ai Use WS2812B LED Strip with Arduino tutorial.

Esempio di Codice: GPIO

La scheda ha quattro pin GPIO (IO4, IO5, IO6, IO7). Per testare i GPIO colleghiamo un LED con una resistenza da 220 Ohm a GND e a uno dei pin GPIO. In questo esempio uso IO4:

Connecting LED to GND and IO4
Collegamento LED a GND e IO4

Ora possiamo usare il solito programma blink per accendere e spegnere il LED su GPIO4:

#define LED_PIN 4

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_PIN, HIGH);  
  delay(1000);                      
  digitalWrite(LED_PIN, LOW);   
  delay(1000);                      
}

Esempio di Codice: Monitoraggio Carica Batteria

La scheda MaTouch AI ESP32-S3 dispone di un connettore per batteria LiPo esterna e di un circuito di ricarica basato su TP4056:

Battery charging circuit
Circuito di ricarica batteria (source)

Inoltre è presente un MAX17048 fuel gauge che permette di monitorare la carica della batteria. È collegato via interfaccia I2C a SDA=39 e SCL=38:

MAX17048 fuel gauge
Fuel gauge MAX17048 (source)

Il seguente esempio di codice mostra come usare il fuel gauge integrato per misurare la tensione e la capacità residua della batteria. Nota che dovrai installare la MAX17048 Library perché questo codice funzioni.

#include "Wire.h"
#include "MAX17048.h"

#define SDA 39
#define SCL 38

MAX17048 power;

void setup() {
  Serial.begin(115200);
  Wire.begin(SDA, SCL);
  power.attatch(Wire);
}

void loop() {
  float volts = power.voltage();
  int pcnt = power.percent();
  Serial.printf("%.2fV (%d%%)\n", volts, pcnt);
  delay(3000);
}

Questo codice stamperà sul Monitor Serial la tensione attuale e la capacità residua (in percentuale) della batteria.

Esempio di Codice: Display e Touch Screen

Il prossimo esempio mostra come usare il display e il touch screen. Stampa il testo “Makerguides” al centro dello schermo e, se tocchi lo schermo, disegna un piccolo cerchio rosso nel punto di contatto. Vedi l’esempio sotto:

Prima di eseguire il codice, devi installare la Adafruit-GFX-Library, la Adafruit-ST7735-Library e la BitBank Capacitive Touch Sensor Library (bb_captouch). Tutte possono essere installate tramite il Library Manager dell’Arduino IDE.

Dai un’occhiata veloce al codice e poi discuteremo alcuni dettagli.

#include <Adafruit_GFX.h>      
#include <Adafruit_ST7789.h>   
#include <bb_captouch.h>

#define TFT_BLK 45
#define TFT_RES -1

#define TFT_SCLK  48
#define TFT_MISO 12
#define TFT_MOSI 13
#define TFT_SD_CS   47
#define TFT_CS 40
#define TFT_DC 21

#define TOUCH_INT 14
#define TOUCH_SDA 39
#define TOUCH_SCL 38
#define TOUCH_RST 18

#define SCREEN_H 320
#define SCREEN_W 240


Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RES);
BBCapTouch touch;


void setup() {
  pinMode(TFT_BLK, OUTPUT);
  digitalWrite(TFT_BLK, HIGH);

  tft.init(SCREEN_W, SCREEN_H);
  tft.setRotation(0);          
  tft.fillScreen(ST77XX_BLACK);

  tft.setTextColor(ST77XX_YELLOW);
  tft.setTextSize(2);
  tft.setCursor(50, 150);
  tft.println("Makerguides");

  touch.init(TOUCH_SDA, TOUCH_SCL, TOUCH_RST, TOUCH_INT);
}

void loop() {
  TOUCHINFO ti;
  if (touch.getSamples(&ti)) {
    int x = SCREEN_W - ti.x[0];
    int y = SCREEN_H - ti.y[0];
    tft.fillCircle(x, y, 5, ST77XX_RED);
  }
}

Librerie

Per prima cosa includiamo le librerie Adafruit per grafica e display, che forniscono funzioni di disegno per il controller ST7789, e l’header bb_captouch.h, che permette l’accesso al pannello touch capacitivo.

Costanti

Definiamo i pin usati per collegare il display TFT e il controller touch. Sono assegnate costanti per il pin della retroilluminazione, i pin di controllo del display e i pin I²C usati dal touch panel. Sono definite anche larghezza e altezza dello schermo per semplificare i calcoli successivi.

Oggetti

Viene creato un oggetto Adafruit_ST7789 chiamato tft, che rappresenta la connessione allo schermo TFT. Viene creato anche un oggetto BBCapTouch chiamato touch, che sarà usato per leggere gli eventi touch dal sensore capacitivo.

Setup

Nella funzione setup, prima il pin della retroilluminazione del display è configurato come output e acceso. Il display TFT viene inizializzato con risoluzione 240×320 pixel. La rotazione è impostata a zero, quindi lo schermo è in orientamento verticale. L’intero schermo viene pulito con il colore nero e il colore del testo impostato a giallo. Il programma imposta una dimensione del font pari a due, sposta il cursore alla posizione (50, 150) e stampa il testo “Makerguides” sullo schermo. Dopo aver configurato il display, il controller touch capacitivo viene inizializzato con i pin I²C e il pin di reset corretti.

Loop

Nella funzione loop controlliamo continuamente se è stato rilevato un nuovo evento touch. Se sono disponibili dati touch, il primo campione viene letto nelle variabili x e y. Queste coordinate vengono poi trasformate per corrispondere al sistema di coordinate dello schermo sottraendole dalla larghezza e altezza dello schermo. Infine, disegniamo un cerchio rosso con raggio di cinque pixel nel punto di contatto.

Esempio di Codice: Orologio in Tempo Reale

La scheda MaTouch AI ESP32-S3 contiene un PCF8563T per fornire un orologio in tempo reale (RTC). Vedi lo schema sotto:

Real Time Clock (RTC) circuit
Circuito Orologio in Tempo Reale (RTC) (source)

In questo esempio useremo il RTC per tenere il tempo e mostrare l’ora corrente sul display TFT. Avrà questo aspetto:

Dovrai installare la RTCLib perché il codice seguente funzioni. Dai un’occhiata veloce al codice e poi discuteremo i dettagli:

#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <RTClib.h>
#include <Wire.h>

#define TFT_BLK 45
#define TFT_RES -1

#define TFT_SCLK 48
#define TFT_MISO 12
#define TFT_MOSI 13
#define TFT_CS 40
#define TFT_DC 21

#define SCREEN_W 240
#define SCREEN_H 320

// RTC pins
#define RTC_SCL 38
#define RTC_SDA 39
#define RTC_INT 15

Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RES);
RTC_PCF8563 rtc_pcf;

void rtc_pcf_init() {
  if (!rtc_pcf.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    while (1) delay(10);
  }

  rtc_pcf.adjust(DateTime(F(__DATE__), F(__TIME__)));
}

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

  pinMode(TFT_BLK, OUTPUT);
  digitalWrite(TFT_BLK, HIGH);

  tft.init(SCREEN_W, SCREEN_H);
  tft.setRotation(0);
  tft.fillScreen(ST77XX_BLACK);
  tft.setTextColor(ST77XX_YELLOW, ST77XX_BLACK);
  tft.setTextSize(4);

  Wire.begin(RTC_SDA, RTC_SCL);
  rtc_pcf_init();
}

void loop() {
  static char buf[16];

  DateTime now = rtc_pcf.now();
  sprintf(buf, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());

  tft.setCursor(20, 130);
  tft.print(buf);

  delay(500);
}

Librerie

Iniziamo includendo le librerie Adafruit per grafica e display per disegnare sullo schermo, la libreria RTClib per accedere all’orologio in tempo reale, e la libreria Wire per la comunicazione I²C.

Costanti

Definiamo i pin per il display TFT, inclusi chip select, data/command e pin SPI. Sono definite anche larghezza e altezza dello schermo. Sono elencati anche i pin per il RTC, con RTC_SDA e RTC_SCL assegnati alle linee dati e clock I²C, e RTC_INT definito ma non usato in questo programma.

Oggetti

Creiamo un oggetto Adafruit_ST7789 chiamato tft per rappresentare il display. L’oggetto RTC_PCF8563 chiamato rtc_pcf è creato per comunicare con l’orologio in tempo reale.

rtc_pcf_init

La funzione rtc_pcf_init() tenta di inizializzare il RTC. Se il dispositivo non viene trovato sul bus I²C, il programma stampa un messaggio di errore e si ferma. Se il RTC è presente, l’ora viene impostata alla data e ora di compilazione del programma usando rtc_pcf.adjust(). Questo assicura che l’orologio parta da un punto di riferimento noto.

Setup

Nella funzione setup(), la comunicazione seriale viene avviata per il debug. Il pin della retroilluminazione TFT è abilitato e il display inizializzato a 240×320 pixel. La rotazione è impostata a zero, lo schermo viene pulito a nero e il colore del testo impostato a giallo su sfondo nero. La dimensione del testo è aumentata per una lettura più facile. L’interfaccia I²C è inizializzata con i pin SDA e SCL definiti, e il RTC è avviato con rtc_pcf_init().

Loop

La funzione loop() viene eseguita ripetutamente e recupera l’ora corrente dal RTC con rtc_pcf.now(). L’ora è formattata come stringa nel formato “HH:MM:SS” usando sprintf, e memorizzata in un buffer. Il cursore è posizionato alle coordinate (20, 130), vicino al centro dello schermo, e la stringa dell’ora è disegnata sul display in giallo. Il loop poi attende mezzo secondo prima di ripetere, così il display si aggiorna due volte al secondo.

In sintesi, questo codice inizializza un display TFT e un orologio in tempo reale PCF8563, imposta l’orologio all’ora di compilazione e poi mostra continuamente l’ora corrente in ore, minuti e secondi al centro dello schermo.

Esempio di Codice: Riproduzione File Audio

La scheda MaTouch AI ESP32-S3 ha un amplificatore di classe D integrato MAX98357A per pilotare un piccolo altoparlante (3,2 W @ 4 Ω).

Amplifier circuit
Circuito amplificatore (source)

Nel seguente esempio di codice riproduciamo un file WAV memorizzato sulla scheda SD:

#include <driver/i2s_std.h>
#include "FS.h"
#include "SD.h"
#include "SPI.h"

#define SCLK    48
#define MISO    12
#define MOSI    13
#define SD_CS   47

// Speaker pins
#define I2S_OUT_BCLK  20
#define I2S_OUT_LRC   1
#define I2S_OUT_DOUT  19

#define SAMPLE_RATE   16000U

static i2s_chan_handle_t tx_chan;

void SpeakerInit() {
  i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER);
  ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_chan, NULL));

  // Standard mode, 16-bit mono
  i2s_std_config_t std_cfg = {
    .clk_cfg  = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
    .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
                                                    I2S_SLOT_MODE_MONO),
    .gpio_cfg = {
      .mclk = I2S_GPIO_UNUSED,
      .bclk = (gpio_num_t)I2S_OUT_BCLK,
      .ws   = (gpio_num_t)I2S_OUT_LRC,
      .dout = (gpio_num_t)I2S_OUT_DOUT,
      .din  = I2S_GPIO_UNUSED,
    },
  };
  std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT;  // left channel only

  ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_chan, &std_cfg));
  ESP_ERROR_CHECK(i2s_channel_enable(tx_chan));
}

void playWav(const char *filename) {
  File file = SD.open(filename);
  if (!file || file.size() <= 44) {
    Serial.println("Invalid or missing WAV file.");
    return;
  }

  file.seek(44); // skip header
  uint8_t buffer[1024];
  size_t bytesRead, bytesWritten;

  while ((bytesRead = file.read(buffer, sizeof(buffer))) > 0) {
    // optional ×2 volume boost
    for (int i = 0; i < bytesRead; i += 2) {
      int16_t *s = (int16_t *)&buffer[i];
      *s <<= 1;
    }
    i2s_channel_write(tx_chan, buffer, bytesRead, &bytesWritten, portMAX_DELAY);
  }

  file.close();
  i2s_channel_disable(tx_chan);
  Serial.println("Playback finished.");
}

void setup() {
  Serial.begin(115200);
  SPI.begin(SCLK, MISO, MOSI);

  if (!SD.begin(SD_CS, SPI, 80'000'000)) {
    Serial.println(F("ERROR: SD mount failed!"));
    return;
  }

  SpeakerInit();
  delay(500);
  playWav("/LightMusic.wav");
}

void loop() {}

Librerie

All’inizio del codice sono incluse le librerie necessarie. Il driver I²S è usato per comunicare con un chip audio digitale esterno, le librerie SD e SPI forniscono accesso alla scheda SD, e la libreria FS definisce l’interfaccia del file system.

Costanti

Sono definiti i numeri dei pin per il bus SPI che collega la scheda SD e per i segnali I²S che collegano l’hardware audio di uscita. La frequenza di campionamento è impostata a 16.000 campioni al secondo, che definisce la velocità di riproduzione audio. È dichiarato un handle per il canale di trasmissione I²S per inviare i dati audio all’altoparlante.

SpeakerInit

La funzione SpeakerInit() configura e abilita l’uscita I²S. Prima crea una configurazione di canale predefinita per il canale I²S 1 in modalità master. Poi inizializza un nuovo canale, salvando l’handle di trasmissione in tx_chan.

Successivamente imposta una configurazione I²S standard: l’orologio è definito per la frequenza di 16 kHz, la configurazione dello slot specifica audio mono a 16 bit in formato Philips I²S, e la configurazione GPIO assegna i pin corretti per bit clock, word select e uscita dati. La maschera dello slot è limitata al canale sinistro, poiché si riproduce audio mono. Infine, il canale è inizializzato in modalità standard e abilitato, rendendo l’hardware pronto per la riproduzione audio.

playWav

La funzione playWav(const char *filename) apre un file WAV dalla scheda SD. Controlla se il file esiste ed è più grande di 44 byte, perché i primi 44 byte di un file WAV sono l’intestazione e non contengono dati audio veri.

Se il file è valido, il programma salta l’intestazione con file.seek(44). Poi legge ripetutamente blocchi di 1024 byte in un buffer. Prima di scrivere ogni blocco sull’interfaccia I²S, i campioni sono opzionalmente amplificati: ogni campione a 16 bit con segno è shiftato a sinistra di un bit, raddoppiandone l’ampiezza.

I dati audio processati sono poi scritti sul canale di trasmissione I²S usando i2s_channel_write(). Quando si raggiunge la fine del file, il file viene chiuso, il canale I²S disabilitato e viene stampato un messaggio che indica la fine della riproduzione.

Setup

Nella funzione setup(), la porta seriale è inizializzata per il debug. Il bus SPI è avviato sui pin specificati e la scheda SD è montata. Se il montaggio fallisce, viene stampato un errore e il programma si ferma. Altrimenti, l’altoparlante I²S è inizializzato, un breve ritardo assicura stabilità, e la funzione playWav("/LightMusic.wav") è chiamata per iniziare la riproduzione del file memorizzato sulla scheda SD.

Loop

La funzione loop() è lasciata vuota, poiché tutta l’azione avviene una sola volta in setup().

In sintesi, questo codice configura l’ESP32-S3 per riprodurre un file WAV memorizzato sulla scheda SD tramite un altoparlante collegato in I²S. Configura la scheda SD e l’uscita I²S, salta l’intestazione del file, trasmette i dati audio grezzi all’hardware I²S e produce suono udibile dalla traccia audio memorizzata.

Esempio di Codice: Registrazione Audio

La scheda MaTouch AI ESP32-S3 è dotata di due INMP441 microfoni a uscita digitale. Vedi lo schema sotto:

Microphone circuit
Circuito microfono (source)

Nel seguente esempio di codice registriamo 10 secondi di audio dal microfono e lo salviamo come file WAV sulla scheda SD:

#include <driver/i2s_std.h>
#include "FS.h"
#include "SD.h"
#include "SPI.h"

#define SCLK  48
#define MISO  12
#define MOSI  13
#define SD_CS 47

// Microphone pins
#define I2S_IN_BCLK  42
#define I2S_IN_LRC   2
#define I2S_IN_DIN   41

#define SAMPLE_RATE   16000U
#define SAMPLE_BITS   16
#define RECORD_TIME   10   // seconds

static i2s_chan_handle_t rx_chan;

void writeWavHeader(File &file, uint32_t dataSize) {
  uint32_t chunkSize = 36 + dataSize;
  uint16_t audioFormat = 1; // PCM
  uint16_t numChannels = 1;
  uint32_t sampleRate = SAMPLE_RATE;   // copy macro into variable
  uint16_t bitsPerSample = SAMPLE_BITS; // copy macro into variable
  uint32_t byteRate = sampleRate * numChannels * bitsPerSample / 8;
  uint16_t blockAlign = numChannels * bitsPerSample / 8;

  file.seek(0);
  file.write((const uint8_t *)"RIFF", 4);
  file.write((uint8_t *)&chunkSize, 4);
  file.write((const uint8_t *)"WAVE", 4);
  file.write((const uint8_t *)"fmt ", 4);

  uint32_t subChunk1Size = 16;
  file.write((uint8_t *)&subChunk1Size, 4);
  file.write((uint8_t *)&audioFormat, 2);
  file.write((uint8_t *)&numChannels, 2);
  file.write((uint8_t *)&sampleRate, 4);
  file.write((uint8_t *)&byteRate, 4);
  file.write((uint8_t *)&blockAlign, 2);
  file.write((uint8_t *)&bitsPerSample, 2);

  file.write((const uint8_t *)"data", 4);
  file.write((uint8_t *)&dataSize, 4);
}


void MicInit() {
  i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
  ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_chan));  // RX channel only

  i2s_std_config_t std_cfg = {
    .clk_cfg  = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
    .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
                                                    I2S_SLOT_MODE_MONO),
    .gpio_cfg = {
      .mclk = I2S_GPIO_UNUSED,
      .bclk = (gpio_num_t)I2S_IN_BCLK,
      .ws   = (gpio_num_t)I2S_IN_LRC,
      .dout = I2S_GPIO_UNUSED,
      .din  = (gpio_num_t)I2S_IN_DIN,
    },
  };
  std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_RIGHT;

  ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_chan, &std_cfg));
  ESP_ERROR_CHECK(i2s_channel_enable(rx_chan));
}

void recordWav(const char *filename) {
  File file = SD.open(filename, FILE_WRITE);
  if (!file) {
    Serial.println("Failed to open file for writing!");
    return;
  }

  // Reserve header space
  for (int i = 0; i < 44; i++) file.write((uint8_t)0);

  const size_t bufferSize = 1024;
  uint8_t buffer[bufferSize];
  size_t bytesRead;
  uint32_t totalBytes = 0;

  uint32_t startMs = millis();
  while ((millis() - startMs) < RECORD_TIME * 1000) {
    if (i2s_channel_read(rx_chan, buffer, bufferSize, &bytesRead, portMAX_DELAY) == ESP_OK) {
      file.write(buffer, bytesRead);
      totalBytes += bytesRead;
    }
  }

  // Write real header
  writeWavHeader(file, totalBytes);
  file.close();

  Serial.printf("Recording finished: %s (%lu bytes)\n", filename, (unsigned long)totalBytes);
}

void setup() {
  Serial.begin(115200);
  SPI.begin(SCLK, MISO, MOSI);

  if (!SD.begin(SD_CS, SPI, 80'000'000)) {
    Serial.println("SD mount failed!");
    return;
  }

  MicInit();
  delay(2000);
  Serial.println("Recording...");
  recordWav("/mic_record.wav");
}

void loop() {}

Librerie

All’inizio sono incluse le librerie necessarie. Il driver I²S fornisce accesso all’interfaccia hardware audio, le librerie FS e SD permettono di creare e scrivere file sulla scheda SD, e la libreria SPI gestisce la comunicazione con la scheda SD.

Costanti

Sono definite costanti per i pin SPI usati dalla scheda SD e per i pin I²S collegati al microfono digitale. La frequenza di campionamento è impostata a 16 kHz, la risoluzione a 16 bit per campione, e la durata della registrazione a dieci secondi. È dichiarato un handle per il canale di ricezione I²S per catturare i dati del microfono.

writeWavHeader

La funzione writeWavHeader() scrive una corretta intestazione WAV di 44 byte all’inizio del file. Costruisce il contenitore RIFF, specifica il formato audio come PCM, definisce un canale per audio mono, e imposta frequenza di campionamento, profondità bit e byte rate. Poi scrive l’intestazione della sezione “data” seguita dalla dimensione totale dei campioni audio. Questo garantisce che il file risultante possa essere riprodotto da qualsiasi software audio standard.

MicInit

La funzione MicInit() configura l’interfaccia I²S per ricevere audio dal microfono. Crea un nuovo canale I²S in modalità master con impostazioni predefinite. La configurazione standard specifica un clock per la frequenza di 16 kHz, formato slot Philips I²S con campioni a 16 bit, e modalità canale mono. La configurazione GPIO assegna i pin corretti per bit clock, word select e ingresso dati, lasciando inutilizzati master clock e uscita dati. Infine, il canale è inizializzato in modalità standard e abilitato, pronto per catturare campioni audio.

recordWav

La funzione recordWav(const char *filename) esegue la registrazione vera e propria. Apre un file sulla scheda SD per la scrittura. Se il file non può essere creato, segnala un errore ed esce. Per lasciare spazio all’intestazione WAV, scrive 44 byte zero all’inizio del file. Poi alloca un buffer di 1024 byte. Un ciclo gira per la durata definita da RECORD_TIME. Durante questo periodo, i dati audio sono letti dal canale I²S nel buffer e scritti direttamente nel file. Viene mantenuto un totale dei byte audio registrati. Dopo la fine della registrazione, la funzione torna all’inizio del file e scrive la vera intestazione WAV con le dimensioni corrette. Il file viene chiuso e viene stampato un messaggio con la dimensione finale del file.

Setup

Nella funzione setup(), la porta seriale è avviata per il debug. Il bus SPI è inizializzato sui pin indicati e la scheda SD è montata. Se il montaggio fallisce, il programma segnala un errore e non continua. Se ha successo, il microfono è inizializzato, un breve ritardo lascia tempo all’hardware di stabilizzarsi, e un messaggio annuncia l’inizio della registrazione. La funzione recordWav("/mic_record.wav") è poi chiamata per catturare e salvare dieci secondi di audio sulla scheda SD.

Loop

La funzione loop() è vuota, poiché la registrazione avviene una sola volta all’avvio.

In sintesi, questo programma inizializza la scheda SD e il microfono I²S, registra dieci secondi di audio a 16 kHz in un buffer, scrive i dati sulla scheda SD, aggiunge una corretta intestazione WAV e produce un file audio standard riproducibile su qualsiasi dispositivo.

Esempio di Codice: Fotocamera e Display

In quest’ultimo esempio catturiamo video in diretta dal modulo fotocamera collegato all’ESP32-S3 e lo visualizziamo direttamente sullo schermo TFT.

Per questo esempio dovrai installare la Arduino_GFX libreria di moononournation. Apri il LIBRARY MANAGER, digita “GFX Library for Arduino” nella barra di ricerca, trova “GFX Library for Arduino” di Moon e clicca sul pulsante INSTALL:

Installation of GFX Library for Arduino
Installazione della GFX Library for Arduino

Di seguito il codice completo per lo streaming video dalla fotocamera al display. Dai un’occhiata veloce e poi discuteremo i dettagli:

#include <Arduino_GFX_Library.h>
#include "esp_camera.h"

// === Camera pins ===
#define PWDN_GPIO_NUM    -1
#define RESET_GPIO_NUM   -1
#define XCLK_GPIO_NUM     9   // CSI_MCLK
#define SIOD_GPIO_NUM    39   // TWI_SDA
#define SIOC_GPIO_NUM    38   // TWI_SCK
#define Y9_GPIO_NUM      46   // CSI D7
#define Y8_GPIO_NUM       3   // CSI D6
#define Y7_GPIO_NUM       8   // CSI D5
#define Y6_GPIO_NUM      16   // CSI D4
#define Y5_GPIO_NUM       6   // CSI D3
#define Y4_GPIO_NUM       4   // CSI D2
#define Y3_GPIO_NUM       5   // CSI D1
#define Y2_GPIO_NUM       7   // CSI D0
#define VSYNC_GPIO_NUM   11   // CSI VSYNC
#define HREF_GPIO_NUM    10   // CSI HSYNC
#define PCLK_GPIO_NUM    17   // CSI PCLK

// === TFT pins ===
#define TFT_BLK          45
#define TFT_RES          -1
#define TFT_CS           40
#define TFT_DC           21
#define MOSI             13
#define MISO             12
#define SCLK             48


Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(
  TFT_DC, TFT_CS, SCLK, MOSI, MISO, HSPI, true
);
Arduino_GFX *gfx = new Arduino_ST7789(
  bus, TFT_RES, 1 , true 
);

// === Camera init ===
void camera_init_s3() {
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;

  config.frame_size   = FRAMESIZE_QVGA;    // 320x240
  config.pixel_format = PIXFORMAT_RGB565;  // direct TFT compatible
  config.grab_mode    = CAMERA_GRAB_WHEN_EMPTY;
  config.fb_location  = CAMERA_FB_IN_PSRAM;
  config.jpeg_quality = 12;
  config.fb_count     = 2;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x\n", err);
    return;
  }

  sensor_t *s = esp_camera_sensor_get();
  if (s->id.PID == OV3660_PID) {
    s->set_hmirror(s, 1); 
    s->set_vflip(s, 1);
  }
}

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

  pinMode(TFT_BLK, OUTPUT);
  digitalWrite(TFT_BLK, HIGH);

  gfx->begin();
  gfx->fillScreen(BLACK);

  camera_init_s3();
}

void loop() {
  camera_fb_t *fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed");
    delay(100);
    return;
  }

  gfx->draw16bitBeRGBBitmap(0, 0, (uint16_t *)fb->buf, fb->width, fb->height);
  esp_camera_fb_return(fb);
}

Librerie

All’inizio includiamo le librerie per il display (Arduino_GFX_Library.h) e per la fotocamera (esp_camera.h).

Costanti

La prima sezione definisce le connessioni hardware. I pin della fotocamera sono elencati per l’interfaccia parallela: otto pin dati (Y2–Y9), pin di clock e sincronizzazione (XCLK, PCLK, HREF, e VSYNC), e i pin I²C (SIOD e SIOC) che configurano il sensore della fotocamera. Sono definiti anche i pin del display TFT per chip select, data/command, linee di comunicazione SPI e retroilluminazione.

Oggetti

Vengono creati due oggetti per il display. Il primo, bus, rappresenta la connessione SPI al display, e il secondo, gfx, rappresenta il controller ST7789 stesso. Questi oggetti provengono dalla libreria Arduino GFX, che fornisce funzioni grafiche efficienti per molti display.

camera_init_s3

La funzione camera_init_s3() configura e inizializza la fotocamera. Una struttura camera_config_t è compilata con le assegnazioni pin e parametri corretti. Il clock esterno per la fotocamera è impostato a 20 MHz. La dimensione del frame è impostata a FRAMESIZE_QVGA, corrispondente a 320×240 pixel, in linea con la risoluzione TFT.

Il formato pixel è impostato a PIXFORMAT_RGB565, che produce un formato colore a 16 bit che il TFT può usare direttamente senza conversione. La modalità di acquisizione è impostata per catturare frame ogni volta che il buffer è vuoto, i buffer dei frame sono memorizzati in PSRAM, e sono usati due buffer per migliorare le prestazioni.

Dopo aver compilato la configurazione, esp_camera_init() avvia il driver della fotocamera. Se l’inizializzazione fallisce, viene stampato un codice di errore. Se il sensore è un OV3660, viene applicata una configurazione extra per regolare orientamento e luminosità.

Setup

Nella funzione setup(), la porta seriale è avviata per il debug. La retroilluminazione TFT è abilitata e il display inizializzato. Lo schermo è pulito a nero prima che la fotocamera venga avviata chiamando camera_init_s3().

Loop

La funzione loop() cattura e visualizza ripetutamente i frame. Ogni chiamata a esp_camera_fb_get() recupera un puntatore all’ultimo buffer frame. Se la cattura fallisce, viene stampato un messaggio e il programma attende brevemente prima di riprovare. Se ha successo, il buffer frame è disegnato sul TFT usando gfx->draw16bitBeRGBBitmap(). Questa funzione trasferisce direttamente i dati pixel RGB565 dal buffer della fotocamera al display in posizione (0,0). Una volta visualizzato il frame, esp_camera_fb_return(fb) è chiamato per rilasciare il buffer al driver affinché possa essere riutilizzato.

In sintesi, questo programma inizializza la fotocamera ESP32-S3 e il display TFT ST7789, cattura frame in formato RGB565 e li trasmette direttamente allo schermo. Il risultato è un’anteprima video in tempo reale visualizzata sul TFT.

Conclusioni

Questo tutorial ti ha fornito esempi di codice per iniziare con la MaTouch AI ESP32-S3 con display TFT ST7789V da 2,8 pollici.

Per altri esempi vedi Makerfabs’s Github repo for the Matouch display e il Wiki Page. Nota però che la maggior parte degli esempi lì usa la libreria lvgl, che ho evitato qui per mantenere bassa la complessità. Inoltre, alcuni esempi non funzionano con l’attuale core 3.x.

Se hai domande, sentiti libero di lasciarle nella sezione commenti.

Buon divertimento con il tinkering 😉