ESP32-S3 Tutorial: Dual-Core, 8 MB PSRAM & USB-HID in der Praxis – das N16R8 Dev Kit
Wenn der klassische ESP32 an seine Grenzen stößt, kommt der ESP32-S3 ins Spiel: zwei Xtensa-LX7-Kerne mit 240 MHz, beim N16R8-Kit satte 16 MB Flash und 8 MB PSRAM, natives USB-OTG und Vektorbefehle für KI-Anwendungen. Das ist die Hardware, auf der Kamera-Streams, Audio-Verarbeitung und TinyML-Modelle laufen – und gleichzeitig ein herrlich unkompliziertes Arduino-Board. In diesem Tutorial holst du alles einmal raus: Systemcheck, echtes Dual-Core-Multitasking mit FreeRTOS, ein Messwert-Puffer mit einer Million Einträgen im PSRAM und ein Sketch, der den S3 in eine USB-Tastatur verwandelt.
Was macht den ESP32-S3 besonders?
Der S3 ist Espressifs Flaggschiff für rechenintensive Projekte. Gegenüber dem klassischen ESP32 bekommst du nicht nur mehr Speicher, sondern auch SIMD-Vektorbefehle, die neuronale Netze (Keyword-Spotting, Gesichtserkennung mit ESP-WHO, TensorFlow Lite Micro) um ein Vielfaches beschleunigen – und endlich natives USB.
| Merkmal | ESP32 (Classic) | ESP32-S3 N16R8 |
|---|---|---|
| CPU | 2x Xtensa LX6, 240 MHz | 2x Xtensa LX7, 240 MHz + Vektorbefehle |
| SRAM intern | 520 KB | 512 KB |
| PSRAM | meist keiner | 8 MB (Octal-SPI, schnell) |
| Flash | 4 MB typisch | 16 MB |
| USB | über USB-Seriell-Chip | nativ, OTG & HID-fähig (Dual USB-C) |
| Bluetooth | BLE 4.2 + Classic | BLE 5.0 |
| KI-Eignung | eingeschränkt | SIMD-beschleunigt (ESP-NN, TFLite Micro) |
ℹ️ Wofür braucht man 8 MB PSRAM? Kamera-Framebuffer, Audio-Aufnahmen, große JSON-Antworten, Displays mit hoher Auflösung, Messwert-Historien – alles, was nicht in die 512 KB internen RAM passt. Der Octal-PSRAM des N16R8 ist dabei doppelt so schnell angebunden wie der Quad-PSRAM älterer S3-Boards.
Was du brauchst
Arduino IDE einrichten
-
1Boardpaket
esp32 by Espressif Systems(Version 3.x) installieren undESP32S3 Dev Moduleauswählen. - 2PSRAM: OPI PSRAM einstellen – sonst bleiben die 8 MB unsichtbar!
- 3Flash Size: 16MB und USB CDC On Boot: Enabled setzen.
- 4Das Board hat zwei USB-C-Ports: COM/UART zum Flashen und Debuggen, USB/OTG für HID-Projekte wie Code 4.
Verkabelung
OLED und MPU-6050 teilen sich den I2C-Bus – zwei Geräte, zwei Adressen (0x3C und 0x68), vier Kabel.
| Modul-Pin | ESP32-S3 | Hinweis |
|---|---|---|
| OLED VCC / MPU VCC | 3V3 | beide Module 3,3V-fähig |
| OLED GND / MPU GND | GND | |
| OLED SDA / MPU SDA | GPIO 8 | gemeinsamer I2C-Bus |
| OLED SCL / MPU SCL | GPIO 9 | gemeinsamer I2C-Bus |
Code 1: Systemcheck – was steckt in deinem Board?
Der erste Sketch verrät dir sofort, ob PSRAM und Flash korrekt erkannt werden – dein wichtigstes Diagnose-Werkzeug bei S3-Boards.
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println("=== ESP32-S3 Systemcheck ===");
Serial.printf("Chip: %s, Rev. %d\n", ESP.getChipModel(), ESP.getChipRevision());
Serial.printf("Kerne: %d @ %d MHz\n", ESP.getChipCores(), ESP.getCpuFreqMHz());
Serial.printf("Flash: %.1f MB\n", ESP.getFlashChipSize() / 1048576.0);
Serial.printf("Freier Heap (intern): %.1f KB\n", ESP.getFreeHeap() / 1024.0);
if (psramFound()) {
Serial.printf("PSRAM: %.1f MB, davon frei: %.1f MB\n",
ESP.getPsramSize() / 1048576.0,
ESP.getFreePsram() / 1048576.0);
} else {
Serial.println("KEIN PSRAM erkannt! Board-Einstellung 'OPI PSRAM' pruefen.");
}
}
void loop() {}
Erwartete Ausgabe beim N16R8: 2 Kerne, 16 MB Flash und rund 8 MB PSRAM. Steht dort „KEIN PSRAM", wirf einen Blick in die Board-Einstellungen (Schritt 2 oben).
Code 2: Echtes Multitasking – zwei Kerne, zwei Aufgaben
Mit FreeRTOS verteilst du Arbeit gezielt auf beide Kerne: Kern 0 liest den MPU-6050 mit hoher Rate, Kern 1 kümmert sich ungestört um das OLED. So ruckelt nichts, egal wie aufwendig die Anzeige wird. Benötigte Bibliotheken: Adafruit SSD1306, Adafruit GFX und Adafruit MPU6050.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_MPU6050.h>
Adafruit_SSD1306 oled(128, 64, &Wire, -1);
Adafruit_MPU6050 mpu;
volatile float beschlG = 0; // Gesamtbeschleunigung in g
volatile int messungenProSek = 0;
// Task auf Kern 0: Sensor so schnell wie moeglich lesen
void sensorTask(void* param) {
int zaehler = 0;
unsigned long letzteSek = millis();
for (;;) {
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);
float ax = a.acceleration.x, ay = a.acceleration.y, az = a.acceleration.z;
beschlG = sqrtf(ax*ax + ay*ay + az*az) / 9.81f;
zaehler++;
if (millis() - letzteSek >= 1000) {
messungenProSek = zaehler;
zaehler = 0;
letzteSek = millis();
}
vTaskDelay(1); // Watchdog fuettern
}
}
// Task auf Kern 1: Anzeige aktualisieren
void displayTask(void* param) {
for (;;) {
oled.clearDisplay();
oled.setTextColor(SSD1306_WHITE);
oled.setTextSize(1);
oled.setCursor(0, 0);
oled.printf("Kern 0: %d Mess./s", messungenProSek);
oled.setTextSize(2);
oled.setCursor(0, 24);
oled.printf("%.2f g", beschlG);
oled.setTextSize(1);
oled.setCursor(0, 54);
oled.printf("Kern 1: Anzeige");
oled.display();
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
void setup() {
Serial.begin(115200);
Wire.begin(8, 9); // SDA = 8, SCL = 9
oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
mpu.begin();
mpu.setAccelerometerRange(MPU6050_RANGE_8_G);
xTaskCreatePinnedToCore(sensorTask, "Sensor", 4096, NULL, 2, NULL, 0);
xTaskCreatePinnedToCore(displayTask, "Display", 4096, NULL, 1, NULL, 1);
}
void loop() { vTaskDelay(1000); }
Schüttle das Breadboard – die g-Anzeige reagiert sofort, während die Messrate konstant hoch bleibt. Genau dafür sind zwei Kerne da.
Code 3: Eine Million Messwerte im PSRAM
Jetzt spielt der N16R8 seinen Trumpf aus: Mit ps_malloc() legst du Puffer im externen PSRAM an – hier eine Historie mit 1.000.000 Messwerten (4 MB), undenkbar auf jedem anderen Arduino-Board.
#include <Wire.h>
#include <Adafruit_MPU6050.h>
Adafruit_MPU6050 mpu;
const size_t PUFFER_GROESSE = 1000000; // 1 Mio. floats = 4 MB
float* historie = nullptr;
size_t schreibIndex = 0;
void setup() {
Serial.begin(115200);
Wire.begin(8, 9);
mpu.begin();
// Puffer im PSRAM anlegen
historie = (float*) ps_malloc(PUFFER_GROESSE * sizeof(float));
if (historie == nullptr) {
Serial.println("PSRAM-Allokation fehlgeschlagen!");
while (true) delay(1000);
}
Serial.printf("4 MB Ringpuffer im PSRAM angelegt. Frei: %.1f MB\n",
ESP.getFreePsram() / 1048576.0);
}
void loop() {
sensors_event_t a, g, temp;
mpu.getEvent(&a, &g, &temp);
historie[schreibIndex] = a.acceleration.z;
schreibIndex = (schreibIndex + 1) % PUFFER_GROESSE;
// Alle 10.000 Messungen: Statistik ueber den gesamten Puffer
if (schreibIndex % 10000 == 0) {
float minW = historie[0], maxW = historie[0];
for (size_t i = 1; i < PUFFER_GROESSE; i++) {
if (historie[i] < minW) minW = historie[i];
if (historie[i] > maxW) maxW = historie[i];
}
Serial.printf("Puffer-Statistik: min %.2f / max %.2f m/s2 (Index %u)\n",
minW, maxW, schreibIndex);
}
delay(2);
}
ℹ️ Gut zu wissen: PSRAM ist etwas langsamer als interner RAM. Für Puffer, Historien und Framebuffer ist das egal – zeitkritische Variablen (z. B. in Interrupts) gehören aber weiterhin in den internen RAM.
Code 4: Der S3 als USB-Tastatur – Makro-Pad in 30 Zeilen
Dank nativem USB-OTG kann sich der S3 als HID-Gerät ausgeben. Dieser Sketch macht aus der BOOT-Taste eine Makro-Taste, die einen Text tippt – die Basis für eigene Makro-Pads, Passwort-Eingaben oder Shortcut-Boxen. Wichtig: Nutze den USB/OTG-Port des Boards und stelle USB Mode: USB-OTG (TinyUSB) ein.
#include "USB.h"
#include "USBHIDKeyboard.h"
USBHIDKeyboard tastatur;
#define BOOT_TASTE 0
void setup() {
pinMode(BOOT_TASTE, INPUT_PULLUP);
tastatur.begin();
USB.begin();
}
void loop() {
if (digitalRead(BOOT_TASTE) == LOW) {
delay(50); // Entprellen
if (digitalRead(BOOT_TASTE) == LOW) {
tastatur.println("Hallo von MAKEROO - getippt vom ESP32-S3!");
// Warten bis Taste losgelassen
while (digitalRead(BOOT_TASTE) == LOW) delay(10);
}
}
}
⚠️ Wichtig fürs Zurückflashen: Im USB-OTG-Modus verschwindet der serielle Upload-Port. Zum erneuten Flashen: BOOT-Taste halten, RESET drücken, BOOT loslassen – oder einfach den COM/UART-Port des Boards verwenden.
Praxistipps
🧠 KI-Ausblick: Mit TensorFlow Lite Micro und Espressifs ESP-NN-Bibliothek laufen Keyword-Spotting oder Gestenerkennung (z. B. mit den MPU-6050-Daten aus Code 2!) dank LX7-Vektorbefehlen um ein Vielfaches schneller als auf dem klassischen ESP32.
📷 Kamera-Projekte: Der S3 ist das Herz vieler aktueller Kamera-Boards. Der große PSRAM nimmt dabei die Framebuffer auf – dasselbe Prinzip wie in Code 3.
⚡ Stromspitzen: WiFi-Bursts + OLED + Sensor ziehen kurzzeitig viel Strom. Bei sporadischen Resets hilft ein kurzes, hochwertiges USB-Kabel – Ladekabel ohne Datenadern verursachen ohnehin nur Frust.
Troubleshooting
⚠️ PSRAM wird nicht erkannt: In den Board-Einstellungen PSRAM: OPI PSRAM wählen (nicht QSPI!). Beim N16R8 ist der Speicher octal angebunden.
⚠️ Serieller Monitor leer: USB CDC On Boot: Enabled setzen und prüfen, an welchem der beiden USB-C-Ports das Kabel steckt.
⚠️ I2C-Geräte antworten nicht: Beim S3 sind SDA/SCL frei wählbar – Wire.begin(8, 9) muss zu deiner Verkabelung passen. Ein I2C-Scanner-Sketch zeigt dir, ob 0x3C (OLED) und 0x68 (MPU) gefunden werden.
⚠️ „Guru Meditation Error" bei großen Arrays: Statische Arrays landen im internen RAM und sprengen ihn schnell. Große Puffer immer mit ps_malloc() in den PSRAM legen wie in Code 3.
🚀 Bereit für mehr Rechenpower?
Alle Bauteile für dieses Tutorial findest du bei MAKEROO.
Fazit
Der ESP32-S3 N16R8 ist das Board für alle Projekte, bei denen „normale" Mikrocontroller passen müssen: Zwei schnelle Kerne teilen sich die Arbeit sauber auf, 8 MB PSRAM schlucken Datenmengen, an denen selbst ein UNO R4 scheitert, und das native USB macht aus dem Board im Handumdrehen eine Tastatur, Maus oder Soundkarte. Dabei bleibt alles so zugänglich wie gewohnt – vier Sketches, ein Nachmittag, und du hast Multitasking, Megabyte-Puffer und USB-HID im Werkzeugkasten. Wenn dein nächstes Projekt nach mehr Speicher, mehr Takt oder KI riecht: Das hier ist dein Board.