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:

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:

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:

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
| Merkmal | Spezifikation |
|---|---|
| Controller | ESP32-S3, Xtensa LX7 Dual-Core CPU, bis zu 240 MHz |
| Speicher | 16 MB Flash, 8 MB PSRAM |
| Drahtlos | Wi-Fi 802.11 b/g/n, Bluetooth 5.0 LE |
| Display | 2,8″ TFT LCD, 320 × 240 (QVGA), ST7789V Treiber, SPI Schnittstelle |
| Touch Panel | Kapazitiv, GT911, 5-Punkt Multi-Touch |
| Audio Eingang | Dual INMP441 digitale MEMS Mikrofone |
| Audio Ausgang | MAX98357 I²S Verstärker, 3,2 W @ 4 Ω |
| Speicher | MicroSD-Kartensteckplatz (bis zu 32 GB) |
| Kamera | OV3660 Schnittstelle unterstützt |
| RGB LED | 1 × WS2812 programmierbare LED |
| RTC | PCF8563T Echtzeituhr |
| Batterie | Batterieanschluss mit Schalter, TP4056 Ladegerät, MAX17048 Fuel Gauge |
| USB Schnittstellen | USB-zu-UART (CH340K), native USB |
| Tasten | Boot und Reset |
| Stromversorgung | USB Type-C 5 V (4,0–5,25 V) |
| Erweiterung | 2x 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“:

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.

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…“:

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:

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:

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:

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:

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:

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:

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:

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.

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:

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:

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 😉

