Skip to Content

Erste Schritte mit MaTouch AI ESP32S3 2,8″ TFT ST7789V

Erste Schritte mit MaTouch AI ESP32S3 2,8″ TFT ST7789V

Die MaTouch AI ESP32-S3 mit 2,8-Zoll TFT ST7789V Display von Makerfabs ist ein kompaktes Entwicklungsboard, das für Projekte entwickelt wurde, die drahtlose Konnektivität, Grafikdarstellung und Onboard-Maschinelles Lernen kombinieren.

Basierend auf dem ESP32-S3 Mikrocontroller integriert es Dual-Core Xtensa LX7 Prozessoren, Wi-Fi und Bluetooth Low Energy (BLE). Das hinzugefügte 2,8-Zoll 240×320 TFT Display mit dem ST7789V Treiber ermöglicht Vollfarb-Grafiken, wodurch das Board für Benutzeroberflächen, Datenvisualisierung und eingebettete Anwendungen mit Echtzeit-Interaktion geeignet ist.

Makerfabs positioniert das Board als vielseitige Plattform für Entwickler, die Computer Vision, Spracherkennung und grafische Schnittstellen erkunden möchten, ohne externe Module zu benötigen. In diesem Tutorial lernst du, wie du mit dem MaTouch AI ESP32S3 2,8″ TFT ST7789V Display startest.

Benötigte Teile

Du benötigst ein MaTouch AI ESP32S3 2,8″ TFT ST7789V Board und ein USB-C-Kabel, um das Board zu programmieren und die Codebeispiele auszuprobieren.

MaTouch AI ESP32S3 2,8″ TFT ST7789V

USB-C Kabel

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 des MaTouch AI ESP32S3 2,8″ TFT ST7789V

Das MaTouch ESP32-S3 2,8″ TFT basiert auf dem ESP32-S3 Mikrocontroller, der eine Dual-Core Xtensa® LX7 CPU mit bis zu 240 MHz mit 16 MB Flash-Speicher und 8 MB PSRAM kombiniert.

Diese Hardwarebasis stellt sicher, dass das Board komplexe Aufgaben wie Echtzeit-Audiobearbeitung, grafische Darstellung und maschinelles Lernen direkt vor Ort bewältigen kann. Die drahtlose Konnektivität umfasst Wi-Fi 802.11 b/g/n und Bluetooth 5.0 LE, was das Board vielseitig für IoT- und vernetzte Anwendungen macht.

Das folgende Foto zeigt die Rückseite des Boards mit beschrifteten Einzelkomponenten:

Back of MaTouch AI ESP32S3 2.8" TFT ST7789V
Rückseite des MaTouch AI ESP32S3 2,8″ TFT ST7789V

Display und Touch

Das Board verfügt über ein 2,8-Zoll TFT LCD, das vom ST7789V Controller mit einer Auflösung von 320 × 240 Pixel (QVGA) und Unterstützung für 65K Farben gesteuert wird. Es ist über eine SPI-Schnittstelle angeschlossen. Ein kapazitiver Multi-Touch-Sensor (GT911) unterstützt bis zu fünf gleichzeitige Berührungspunkte und ermöglicht intuitive Benutzeroberflächen für Steuerungen oder Datenvisualisierung. Das Bild unten zeigt die Vorderseite des Boards mit Display und Kamera:

Front of Back of MaTouch AI ESP32S3 2.8" TFT ST7789V
Vorderseite des MaTouch AI ESP32S3 2,8″ TFT ST7789V

Audio-Subsystem

Die Audioeingabe erfolgt über zwei INMP441 digitale MEMS-Mikrofone, die Stereo-Klangaufnahme für Anwendungen wie Spracherkennung oder Audioerkennung ermöglichen. Für die Ausgabe ist ein MAX98357 I²S Verstärker integriert, der bis zu 3,2 W an einem 4 Ω Lautsprecher liefert und so die direkte Wiedergabe von Audio oder Sprachantworten ohne externen DAC oder Verstärker ermöglicht.

Speicher und Erweiterung

Für Datenspeicherung und Logging verfügt das Board über einen MicroSD-Kartensteckplatz, der Karten bis zu 32 GB unterstützt. GPIO-Header sind für zusätzliche Peripheriegeräte wie Sensoren oder Aktuatoren verfügbar. Das Bild unten zeigt das Pinout der zwei GPIO-Ports (J1 und J3) auf der Rückseite des Boards:

Pinout of GPIO ports
Pinbelegung der GPIO-Ports

Kamera-Unterstützung

Das Board integriert eine Kamera-Schnittstelle, die mit der OV3660 Kamera kompatibel ist. Damit lassen sich Computer-Vision-Anwendungen wie Bildklassifikation, Objekterkennung oder einfache Videoaufnahmen in Kombination mit den KI-Beschleunigungsfunktionen des ESP32-S3 realisieren.

Stromversorgung und Management

Das Board kann über eine USB Type-C Schnittstelle (4,0 V–5,25 V) mit Strom versorgt werden und enthält einen TP4056 Ladecontroller für Lithium-Ionen- oder Lithium-Polymer-Akkus. Außerdem gibt es einen Batterieanschluss, einen Hardware-Schalter und einen MAX17048 Fuel Gauge, mit dem der Batteriestatus und Ladezustand überwacht werden können.

Schnittstellen und Steuerung

Für die Entwicklung bietet das Board sowohl eine USB-zu-UART Schnittstelle (CH340K) als auch native USB-Konnektivität, was Flexibilität beim Programmieren und Debuggen ermöglicht. Physische Boot- und Reset-Tasten erlauben eine Steuerung auf niedriger Ebene beim Flashen der Firmware oder bei der Fehlersuche.

LED und Uhr

Eine WS2812 RGB LED bietet eine visuelle Statusanzeige und kann für Benachrichtigungen oder Nutzerfeedback programmiert werden. Ein RTC-Modul (PCF8563T) sorgt für genaue Zeitmessung.

Zusammenfassung der technischen Spezifikationen

MerkmalSpezifikation
ControllerESP32-S3, Xtensa LX7 Dual-Core CPU, bis zu 240 MHz
Speicher16 MB Flash, 8 MB PSRAM
DrahtlosWi-Fi 802.11 b/g/n, Bluetooth 5.0 LE
Display2,8″ TFT LCD, 320 × 240 (QVGA), ST7789V Treiber, SPI Schnittstelle
Touch PanelKapazitiv, GT911, 5-Punkt Multi-Touch
Audio EingangDual INMP441 digitale MEMS Mikrofone
Audio AusgangMAX98357 I²S Verstärker, 3,2 W @ 4 Ω
SpeicherMicroSD-Kartensteckplatz (bis zu 32 GB)
KameraOV3660 Schnittstelle unterstützt
RGB LED1 × WS2812 programmierbare LED
RTCPCF8563T Echtzeituhr
BatterieBatterieanschluss mit Schalter, TP4056 Ladegerät, MAX17048 Fuel Gauge
USB SchnittstellenUSB-zu-UART (CH340K), native USB
TastenBoot und Reset
StromversorgungUSB Type-C 5 V (4,0–5,25 V)
Erweiterung2x GPIO Ports

Die Schaltpläne des Boards findest du unter folgendem Link:

Installation des ESP32 Cores

Wenn dies dein erstes Projekt mit einem ESP32-Board ist, musst du zuerst den ESP32 Core installieren. Falls ESP32 Boards bereits in deiner Arduino IDE installiert sind, kannst du diesen Abschnitt überspringen.

Öffne zunächst den Einstellungen-Dialog über „Preferences…“ im „File“-Menü. Es öffnet sich der unten gezeigte Dialog.

Unter dem Reiter „Settings“ findest du unten ein Eingabefeld mit der Bezeichnung „Additional boards manager URLs“:

Additional boards manager URLs in Preferences
Additional boards manager URLs in Preferences

Kopiere in dieses Feld folgende URL:

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

Damit weiß die Arduino IDE, wo sie die ESP32 Core Bibliotheken findet. Anschließend installieren wir die ESP32 Boards über den Boards Manager.

Öffne den Boards Manager über „Tools -> Boards -> Board Manager“. Im linken Sidebar erscheint der Boards Manager. Gib oben im Suchfeld „ESP32“ ein, und du solltest zwei Arten von ESP32 Boards sehen: die „Arduino ESP32 Boards“ und die „esp32 von Espressif“. Wir wollen die „esp32 Bibliotheken von Espressif“. Klicke auf den INSTALL Button und warte, bis der Download und die Installation abgeschlossen sind.

Install ESP32 Core libraries
ESP32 Core Bibliotheken installieren

Board auswählen

Zum Schluss wählen wir ein ESP32 Board aus. Für das MaTouch AI ESP32S3 Board wählen wir das generische „ESP32S3 Dev Module“. Klicke dazu auf das Dropdown-Menü und dann auf „Select other board and port…“:

Drop-down Menu for Board Selection
Dropdown-Menü zur Board-Auswahl

Es öffnet sich ein Dialog, in dem du „esp32s3 dev“ in die Suchleiste eingibst. Du siehst das Board „ESP32S3 Dev Module“ unter Boards. Klicke darauf, wähle den COM-Port aus, um es zu aktivieren, und bestätige mit OK:

Board Selection Dialog "ESP32S3 Dev Module" board
Board-Auswahl-Dialog „ESP32S3 Dev Module“

Beachte, dass du das Board per USB-Kabel mit deinem Computer verbinden musst, bevor du einen COM-Port auswählen kannst. Das Board hat zwei USB-Ports, einen nativen und einen für TTL/UART. Für die serielle Kommunikation mit dem Board musst du den Port mit der Bezeichnung „USB TTL“ verwenden, der näher an der Ecke liegt:

USB TTL Port for Serial Communication
USB TTL Port für serielle Kommunikation

Tool-Einstellungen

In den nächsten Abschnitten findest du Codebeispiele für die verschiedenen Hardware-Komponenten des Boards. Einige benötigen viel Speicher, daher brauchst du folgende Einstellungen, die du im Tools-Menü findest:

Settings for  MaTouch AI ESP32S3 2.8" TFT ST7789V
Einstellungen für MaTouch AI ESP32S3 2,8″ TFT ST7789V

Wichtig ist, dass die Flash-Größe auf 16MB(128MB) gesetzt ist, das Partition Scheme auf 16M Flash (3MB APP/9.9MB FATFS), und PSRAM auf OPI PSRAM. Die anderen Einstellungen sollten Standardwerte sein.

Codebeispiel: Serielle Schnittstelle

Wir beginnen mit dem Test der seriellen Kommunikation. Öffne deine Arduino IDE, gib den folgenden Code ein und lade ihn auf das MaTouch AI ESP32S3 hoch.

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

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

Öffne dann den Serial Monitor, und du solltest alle zwei Sekunden den Text „Makerguides“ sehen. Falls nicht, überprüfe, ob dein USB-Kabel am richtigen Port des Boards angeschlossen ist.

Codebeispiel: RGB LED

Als nächstes testen wir die onboard RGB LED. Du musst die Adafruit_NeoPixel Bibliothek installieren, um dieses Beispiel zu nutzen. Sie ändert einfach alle 200 Millisekunden die Farbe der RGB LED von Rot über Grün zu Blau:

#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);
}

Wenn du mehr Informationen zur WS2812 RGB LED und deren Nutzung brauchst, schau dir die LED Ring Clock with WS2812 und die Use WS2812B LED Strip with Arduino Tutorials an.

Codebeispiel: GPIO

Das Board hat vier GPIO Pins (IO4, IO5, IO6, IO7). Um die GPIOs zu testen, verbinden wir eine LED mit einem 220-Ohm-Widerstand mit GND und einem der GPIO Pins. In diesem Beispiel nutze ich IO4:

Connecting LED to GND and IO4
LED an GND und IO4 anschließen

Jetzt können wir das übliche Blinkprogramm verwenden, um die LED an GPIO4 ein- und auszuschalten:

#define LED_PIN 4

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

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

Codebeispiel: Batterie-Ladestatus überwachen

Das MaTouch AI ESP32-S3 Board verfügt über einen Anschluss für eine externe LiPo-Batterie und einen Ladecontroller basierend auf dem TP4056:

Battery charging circuit
Batterieladekreis (source)

Zusätzlich gibt es einen MAX17048 fuel gauge, mit dem du den Batterieladestand überwachen kannst. Er ist über eine I2C-Schnittstelle an SDA=39 und SCL=38 angeschlossen:

MAX17048 fuel gauge
MAX17048 Fuel Gauge (source)

Das folgende Codebeispiel zeigt, wie du den eingebauten Fuel Gauge nutzt, um Spannung und verbleibende Kapazität der Batterie zu messen. Beachte, dass du die MAX17048 Library installieren musst, damit der Code funktioniert.

#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);
}

Dieser Code gibt die aktuelle Spannung und die verbleibende Kapazität (in Prozent) der Batterie im Serial Monitor aus.

Codebeispiel: Display und Touchscreen

Das nächste Beispiel zeigt, wie du Display und Touchscreen nutzt. Es wird der Text „Makerguides“ in der Bildschirmmitte angezeigt, und bei Berührung wird an der Stelle ein kleiner roter Kreis gezeichnet. Siehe Beispiel unten:

Bevor du den folgenden Code ausführst, musst du die Adafruit-GFX-Library, die Adafruit-ST7735-Library und die BitBank Capacitive Touch Sensor Library (bb_captouch) installieren. Alle sind über den Library Manager der Arduino IDE verfügbar.

Schau dir den Code kurz an, dann besprechen wir einige Details.

#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);
  }
}

Bibliotheken

Zuerst binden wir die Adafruit Grafik- und Display-Bibliotheken ein, die Zeichenfunktionen für den ST7789 Controller bereitstellen, sowie die bb_captouch.h Headerdatei, die Zugriff auf das kapazitive Touchpanel ermöglicht.

Konstanten

Dann definieren wir die Pins für das TFT Display und den Touch-Controller. Konstanten werden für den Backlight-Pin, die Display-Steuerpins und die I²C-Pins des Touchpanels vergeben. Die Bildschirmbreite und -höhe werden ebenfalls definiert, um spätere Berechnungen zu erleichtern.

Objekte

Anschließend wird ein Adafruit_ST7789 Objekt namens tft erstellt, das die Verbindung zum TFT Bildschirm repräsentiert. Ein BBCapTouch Objekt namens touch wird ebenfalls erstellt, um Touch-Events vom kapazitiven Sensor auszulesen.

Setup

In der setup Funktion wird zuerst der Backlight-Pin als Ausgang konfiguriert und eingeschaltet. Das TFT Display wird auf eine Auflösung von 240 × 320 Pixel initialisiert. Die Rotation wird auf Null gesetzt, was die Hochformat-Nutzung bedeutet. Der gesamte Bildschirm wird schwarz gelöscht, und die Textfarbe auf Gelb gesetzt. Die Schriftgröße wird auf zwei gesetzt, der Cursor auf Position (50, 150) bewegt, und der Text „Makerguides“ wird angezeigt. Danach wird der kapazitive Touch-Controller mit den korrekten I²C- und Reset-Pins initialisiert.

Loop

In der loop Funktion prüfen wir kontinuierlich, ob ein neuer Touch-Event erkannt wurde. Wenn Touch-Daten vorliegen, werden die ersten Koordinaten in die Variablen x und y eingelesen. Diese Koordinaten werden so transformiert, dass sie zum Bildschirmkoordinatensystem passen, indem sie von der Bildschirmbreite und -höhe subtrahiert werden. Schließlich wird an der Berührungsstelle ein roter Kreis mit Radius fünf Pixel gezeichnet.

Codebeispiel: Echtzeituhr

Das MaTouch AI ESP32-S3 Board enthält eine PCF8563T zur Bereitstellung einer Echtzeituhr (RTC). Siehe Schaltpläne unten:

Real Time Clock (RTC) circuit
Echtzeituhr (RTC) Schaltung (source)

In diesem Beispiel verwenden wir die RTC, um die Zeit zu halten und die aktuelle Uhrzeit auf dem TFT Display anzuzeigen. Es sieht so aus:

Für den folgenden Code musst du die RTCLib installieren. Schau dir den Code kurz an, dann besprechen wir Details:

#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);
}

Bibliotheken

Wir binden die Adafruit Grafik- und Display-Bibliotheken zum Zeichnen ein, die RTClib Bibliothek für den Zugriff auf die Echtzeituhr und die Wire-Bibliothek für die I²C-Kommunikation.

Konstanten

Dann definieren wir die Pins für das TFT Display, inklusive Chip Select, Daten/Befehl und SPI Pins. Bildschirmbreite und -höhe werden ebenfalls definiert. Die Pins für die RTC sind ebenfalls aufgelistet, mit RTC_SDA und RTC_SCL für die I²C Daten- und Taktleitungen, sowie RTC_INT, der im Programm nicht verwendet wird.

Objekte

Wir erstellen ein Adafruit_ST7789 Objekt namens tft für das Display. Das RTC_PCF8563 Objekt namens rtc_pcf wird für die Kommunikation mit der Echtzeituhr erstellt.

rtc_pcf_init

Die Funktion rtc_pcf_init() versucht, die RTC zu initialisieren. Wenn das Gerät auf dem I²C-Bus nicht gefunden wird, gibt das Programm eine Fehlermeldung aus und stoppt. Ist die RTC vorhanden, wird die Zeit auf das Kompilierdatum und die Kompilierzeit des Programms gesetzt mit rtc_pcf.adjust(). So startet die Uhr mit einem bekannten Referenzpunkt.

Setup

In der setup() Funktion wird die serielle Kommunikation zum Debuggen gestartet. Der TFT Backlight-Pin wird aktiviert, und das Display auf 240 × 320 Pixel initialisiert. Die Rotation wird auf Null gesetzt, der Bildschirm schwarz gelöscht, und die Textfarbe auf Gelb mit schwarzem Hintergrund gesetzt. Die Textgröße wird vergrößert für bessere Lesbarkeit. Die I²C-Schnittstelle wird mit den definierten SDA- und SCL-Pins initialisiert, und die RTC mit rtc_pcf_init() gestartet.

Loop

Die loop() Funktion läuft wiederholt und liest die aktuelle Zeit von der RTC mit rtc_pcf.now() aus. Die Zeit wird als String im Format „HH:MM:SS“ mit sprintf formatiert und in einem Puffer gespeichert. Der Cursor wird auf die Koordinaten (20, 130) gesetzt, nahe der Bildschirmmitte, und die Zeit in Gelb angezeigt. Die Schleife wartet dann eine halbe Sekunde, bevor sie erneut läuft, sodass die Anzeige zweimal pro Sekunde aktualisiert wird.

Zusammenfassend initialisiert dieser Code ein TFT Display und eine PCF8563 Echtzeituhr, setzt die Uhr auf die Kompilierzeit und zeigt dann kontinuierlich die aktuelle Uhrzeit in Stunden, Minuten und Sekunden in der Bildschirmmitte an.

Codebeispiel: Audio-Datei abspielen

Das MaTouch AI ESP32-S3 Board verfügt über einen eingebauten MAX98357A Class D Verstärker, um einen kleinen Lautsprecher (3,2 W @ 4 Ω) anzutreiben.

Amplifier circuit
Verstärker-Schaltung (source)

Im folgenden Codebeispiel spielen wir eine WAV-Datei ab, die auf der SD-Karte gespeichert ist:

#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() {}

Bibliotheken

Oben im Code werden die benötigten Bibliotheken eingebunden. Der I²S-Treiber wird für die Kommunikation mit dem externen digitalen Audioprozessor verwendet, die SD- und SPI-Bibliotheken ermöglichen den Zugriff auf die SD-Karte, und die FS-Bibliothek definiert die Dateisystem-Schnittstelle.

Konstanten

Es werden Pin-Nummern für den SPI-Bus definiert, der die SD-Karte verbindet, sowie für die I²S-Signale, die mit der Audioausgabe-Hardware verbunden sind. Die Abtastrate wird auf 16.000 Samples pro Sekunde gesetzt, was die Wiedergabegeschwindigkeit definiert. Ein Handle für den I²S-Sende-Kanal wird deklariert, um Audiodaten an den Lautsprecher zu senden.

SpeakerInit

Die Funktion SpeakerInit() konfiguriert und aktiviert die I²S-Ausgabe. Zuerst wird eine Standardkanalkonfiguration für I²S Kanal 1 im Master-Modus erstellt. Dann wird ein neuer Kanal initialisiert, und das Sendhandle in tx_chan gespeichert.

Anschließend wird eine Standard-I²S-Konfiguration eingerichtet: Die Clock wird für 16 kHz Abtastrate definiert, die Slot-Konfiguration spezifiziert 16-Bit Mono-Audio im Philips I²S Format, und die GPIO-Konfiguration weist die korrekten Pins für Bit Clock, Word Select und Daten-Ausgang zu. Die Slot-Maske ist auf den linken Kanal beschränkt, da Mono-Audio abgespielt wird. Schließlich wird der Kanal im Standardmodus initialisiert und aktiviert, sodass die Hardware für die Audiowiedergabe bereit ist.

playWav

Die Funktion playWav(const char *filename) öffnet eine WAV-Datei von der SD-Karte. Sie prüft, ob die Datei existiert und größer als 44 Bytes ist, da die ersten 44 Bytes einer WAV-Datei den Header enthalten und keine Klangdaten.

Ist die Datei gültig, überspringt das Programm den Header mit file.seek(44). Dann liest es wiederholt 1024-Byte-Blöcke in einen Puffer ein. Vor dem Schreiben jedes Blocks in die I²S-Schnittstelle werden die Samples optional verstärkt: Jedes 16-Bit signierte Sample wird um ein Bit nach links verschoben, was die Amplitude verdoppelt.

Die verarbeiteten Audiodaten werden dann mit i2s_channel_write() an den I²S-Sende-Kanal geschrieben. Am Ende der Datei wird die Datei geschlossen, der I²S-Kanal deaktiviert, und eine Meldung ausgegeben, dass die Wiedergabe beendet ist.

Setup

In der setup() Funktion wird der serielle Port zum Debuggen initialisiert. Der SPI-Bus wird auf den angegebenen Pins gestartet, und die SD-Karte gemountet. Wenn das Mounten fehlschlägt, wird ein Fehler ausgegeben und das Programm stoppt. Andernfalls wird der I²S-Lautsprecher initialisiert, eine kurze Verzögerung sorgt für Stabilität, und die Funktion playWav("/LightMusic.wav") wird aufgerufen, um die Wiedergabe der Datei von der SD-Karte zu starten.

Loop

Die loop() Funktion bleibt leer, da alle Aktionen einmalig in setup() ausgeführt werden.

Zusammenfassend konfiguriert dieser Code den ESP32-S3, um eine WAV-Datei von der SD-Karte über einen I²S-Lautsprecher abzuspielen. Er richtet die SD-Karte und die I²S-Ausgabe ein, überspringt den Dateikopf, streamt die Roh-Audiodaten an die I²S-Hardware und erzeugt hörbaren Ton aus der gespeicherten Audiodatei.

Codebeispiel: Audio aufnehmen

Das MaTouch AI ESP32-S3 Board verfügt über zwei INMP441 digitale Ausgangs-Mikrofone. Siehe Schaltpläne unten:

Microphone circuit
Mikrofon-Schaltung (source)

Im folgenden Codebeispiel nehmen wir 10 Sekunden Audio vom Mikrofon auf und speichern es als WAV-Datei auf der SD-Karte:

#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() {}

Bibliotheken

Zu Beginn werden die benötigten Bibliotheken eingebunden. Der I²S-Treiber ermöglicht den Zugriff auf die Audio-Hardware, die FS- und SD-Bibliotheken erlauben das Erstellen und Schreiben von Dateien auf der SD-Karte, und die SPI-Bibliothek steuert die Kommunikation mit der SD-Karte.

Konstanten

Es werden Konstanten für die SPI-Pins der SD-Karte und die I²S-Pins der digitalen Mikrofone definiert. Die Abtastrate wird auf 16 kHz, die Auflösung auf 16 Bit pro Sample und die Aufnahmezeit auf zehn Sekunden gesetzt. Ein Handle für den I²S-Empfangskanal wird deklariert, um Mikrofon-Daten zu erfassen.

writeWavHeader

Die Funktion writeWavHeader() schreibt einen korrekten 44-Byte WAV-Header an den Anfang der Datei. Sie erstellt den RIFF-Container, definiert das Audioformat als PCM, legt einen Kanal für Mono-Audio fest und setzt Abtastrate, Bit-Tiefe und Byte-Rate. Danach wird der „data“-Abschnitt mit der Gesamtgröße der Audiodaten geschrieben. So kann die Datei von jeder Standard-Audio-Software abgespielt werden.

MicInit

Die Funktion MicInit() konfiguriert die I²S-Schnittstelle, um Audio vom Mikrofon zu empfangen. Sie erstellt einen neuen I²S-Kanal im Master-Modus mit Standard-Einstellungen. Die Standardkonfiguration definiert eine Clock für 16 kHz Abtastrate, Philips I²S Slot-Format mit 16-Bit Samples und Mono-Kanalmodus. Die GPIO-Konfiguration weist die korrekten Pins für Bit Clock, Word Select und Daten-Eingang zu, während Master Clock und Daten-Ausgang ungenutzt bleiben. Schließlich wird der Kanal im Standardmodus initialisiert und aktiviert, sodass er bereit ist, Audiosamples zu erfassen.

recordWav

Die Funktion recordWav(const char *filename) führt die eigentliche Aufnahme durch. Sie öffnet eine Datei auf der SD-Karte zum Schreiben. Wenn die Datei nicht erstellt werden kann, wird ein Fehler gemeldet und die Funktion beendet. Um Platz für den WAV-Header zu lassen, werden am Anfang 44 Null-Bytes geschrieben. Dann wird ein 1024-Byte-Puffer alloziert. Eine Schleife läuft für die Dauer, die in RECORD_TIME definiert ist. Währenddessen werden Audiodaten vom I²S-Kanal in den Puffer gelesen und direkt in die Datei geschrieben. Die Gesamtzahl der aufgenommenen Bytes wird mitgezählt. Nach der Aufnahme springt die Funktion zum Dateianfang zurück und schreibt den echten WAV-Header mit den korrekten Größen. Die Datei wird geschlossen, und eine Meldung mit der finalen Dateigröße ausgegeben.

Setup

In der setup() Funktion wird der serielle Port zum Debuggen gestartet. Der SPI-Bus wird auf den angegebenen Pins initialisiert, und die SD-Karte gemountet. Wenn das Mounten fehlschlägt, wird ein Fehler gemeldet und das Programm stoppt. Bei Erfolg wird das Mikrofon initialisiert, eine kurze Verzögerung gibt der Hardware Zeit zum Stabilisieren, und eine Meldung kündigt den Beginn der Aufnahme an. Die Funktion recordWav("/mic_record.wav") wird aufgerufen, um zehn Sekunden Audio auf der SD-Karte aufzunehmen und zu speichern.

Loop

Die loop() Funktion bleibt leer, da die Aufnahme nur einmal beim Start erfolgt.

Zusammenfassend initialisiert dieses Programm die SD-Karte und das I²S-Mikrofon, nimmt zehn Sekunden Audio mit 16 kHz in einen Puffer auf, schreibt die Daten auf die SD-Karte, fügt einen korrekten WAV-Header hinzu und erzeugt so eine Standard-Audiodatei, die auf jedem Gerät abgespielt werden kann.

Codebeispiel: Kamera und Display

Im letzten Beispiel erfassen wir Live-Video vom Kameramodul, das an den ESP32-S3 angeschlossen ist, und zeigen es direkt auf dem TFT-Bildschirm an.

Für dieses Beispiel musst du die Arduino_GFX Bibliothek von moononournation installieren. Öffne den LIBRARY MANAGER, tippe „GFX Library for Arduino“ in die Suchleiste, finde die „GFX Library for Arduino“ von Moon und klicke auf INSTALLIEREN:

Installation of GFX Library for Arduino
Installation der GFX Library for Arduino

Unten findest du den kompletten Code, um Video von der Kamera zum Display zu streamen. Schau ihn dir kurz an, dann besprechen wir die Details:

#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);
}

Bibliotheken

Zu Beginn binden wir die Bibliotheken für das Display (Arduino_GFX_Library.h) und die Kamera (esp_camera.h) ein.

Konstanten

Der erste Abschnitt definiert die Hardware-Verbindungen. Die Kamerapins für die parallele Schnittstelle sind aufgelistet: acht Datenpins (Y2–Y9), Clock- und Synchronisationspins (XCLK, PCLK, HREF und VSYNC), sowie die I²C-Pins (SIOD und SIOC), die den Kamerasensor konfigurieren. Die TFT-Display-Pins sind ebenfalls für Chip Select, Daten/Befehl, SPI-Kommunikationsleitungen und Backlight definiert.

Objekte

Es werden zwei Objekte für das Display erstellt. Das erste, bus, repräsentiert die SPI-Verbindung zum Display, und das zweite, gfx, den ST7789 Controller selbst. Diese Objekte stammen aus der Arduino GFX Bibliothek, die effiziente Grafikfunktionen für viele Displays bereitstellt.

camera_init_s3

Die Funktion camera_init_s3() konfiguriert und initialisiert die Kamera. Eine camera_config_t Struktur wird mit den korrekten Pinbelegungen und Parametern gefüllt. Der externe Takt für die Kamera wird auf 20 MHz gesetzt. Die Bildgröße wird auf FRAMESIZE_QVGA eingestellt, was 320×240 Pixel entspricht und zur TFT-Auflösung passt.

Das Pixelformat wird auf PIXFORMAT_RGB565 gesetzt, was ein 16-Bit Farbformat erzeugt, das das TFT direkt ohne Umwandlung nutzen kann. Der Grab-Modus ist so eingestellt, dass Frames erfasst werden, sobald der Puffer leer ist, die Frame-Puffer werden im PSRAM gespeichert, und zwei Frame-Puffer werden verwendet, um die Leistung zu verbessern.

Nach dem Ausfüllen der Konfiguration startet esp_camera_init() den Kameratreiber. Wenn die Initialisierung fehlschlägt, wird ein Fehlercode ausgegeben. Handelt es sich um einen OV3660 Sensor, wird eine zusätzliche Konfiguration vorgenommen, um Ausrichtung und Helligkeit anzupassen.

Setup

In der setup() Funktion wird der serielle Port zum Debuggen gestartet. Das TFT Backlight wird aktiviert, und das Display initialisiert. Der Bildschirm wird schwarz gelöscht, bevor die Kamera mit camera_init_s3() gestartet wird.

Loop

Die loop() Funktion erfasst und zeigt wiederholt Frames an. Jeder Aufruf von esp_camera_fb_get() holt einen Zeiger auf den neuesten Frame-Puffer. Wenn die Aufnahme fehlschlägt, wird eine Meldung ausgegeben und das Programm wartet kurz, bevor es erneut versucht. Bei Erfolg wird der Frame-Puffer mit gfx->draw16bitBeRGBBitmap() auf das TFT gezeichnet. Diese Funktion überträgt die RGB565-Pixeldaten direkt vom Kamerapuffer an die Anzeige an Position (0,0). Nach der Anzeige wird esp_camera_fb_return(fb) aufgerufen, um den Puffer an den Treiber zurückzugeben, damit er wiederverwendet werden kann.

Zusammenfassend initialisiert dieses Programm die ESP32-S3 Kamera und das ST7789 TFT Display, erfasst Frames im RGB565 Format und streamt sie direkt auf den Bildschirm. Das Ergebnis ist eine Live-Kamera-Vorschau in Echtzeit auf dem TFT.

Fazit

Dieses Tutorial hat dir Codebeispiele gezeigt, um mit dem MaTouch AI ESP32-S3 mit 2,8-Zoll TFT ST7789V Display zu starten.

Für weitere Beispiele siehe Makerfabs’s Github repo for the Matouch display und die Wiki Page. Beachte jedoch, dass die meisten dortigen Beispiele die lvgl Bibliothek verwenden, die ich hier vermieden habe, um die Komplexität gering zu halten. Außerdem funktionieren einige Beispiele nicht mit dem aktuellen 3.x Core.

Bei Fragen kannst du sie gerne im Kommentarbereich stellen.

Viel Spaß beim Tüfteln 😉