Manchmal braucht es gar nicht viel, um aus einem kleinen Bastelprojekt etwas richtig Nützliches zu machen. Ein Mikrofon, ein ESP32-S3 und eine SD-Karte – schon entsteht ein kompakter WAV-Rekorder, der ohne großen Zusatzaufwand funktioniert.
In diesem Beitrag zeige ich dir Schritt für Schritt, wie du mit dem INMP441 Mikrofon und dem ESP32-S3 digitale Audiodaten direkt aufzeichnen kannst. Das Besondere: Wir verzichten auf externe Audio-Codecs und nutzen die I²S-Schnittstelle des ESP32, um die Daten sauber und verlustfrei als WAV-Datei zu speichern.
So entsteht ein Projekt, das nicht nur für Einsteiger spannend ist, sondern auch für alle, die verstehen wollen, wie digitale Audioverarbeitung im Mikrocontroller-Umfeld funktioniert. Am Ende hast du einen funktionierenden Rekorder, den du mit einem einfachen Knopfdruck starten und stoppen kannst – und natürlich den Quellcode zum Nachbauen.

Funktionsweise des Mikrofon:
Der INMP441 ist ein digitales Mikro das schon einen Analog Digital Converter mitbringt
- Typ: Omnidirektionales MEMS-Mikrofon
- Signalweg intern:
- MEMS-Sensor nimmt analoge Schallwellen auf
- Signalaufbereitung (Gain, Filterung)
- Integrierter Analog-Digital-Wandler (ADC) wandelt das Signal in digitale Werte
- Anti-Aliasing-Filter glätten das Signal. Verhindert Störungen durch zu hohe Frequenzen
- I²S-Schnittstelle (24-bit) gibt die digitalen Audiodaten direkt aus
- Vorteil:
- Kein externer ADC oder Audio-Codec nötig
- Direkte Verbindung zu Mikrocontrollern wie ESP32 über I²S.
I²S ist eine serielle Schnittstelle speziell für Audiodaten, die von vielen Mikrocontrollern unterstützt wird - Spart Platz und reduziert Komplexität im Aufbau
Funktionsweise des ESP32-S3:
Der ESP32-S3 empfängt die digitalen 24-bit Audiodaten des INMP441-Mikrofons über die I²S-Schnittstelle – ein serielles Protokoll speziell für Audiosignale.
Diese Daten werden linksbündig in ein 32-bit Datenwort geschrieben. Da WAV-Dateien typischerweise 16-bit Samples verwenden, konvertiert der ESP32 die Daten per Bitverschiebung in das passende Format. Anschließend werden die Samples direkt auf der SD-Karte gespeichert – inklusive korrekt aufgebautem WAV-Header, sodass die Datei sofort abspielbar ist.
Funktionsweise des SD-Kartenmodul:
Das SD-Kartenmodul ermöglicht dem ESP32-S3 die Speicherung der aufgenommenen Audiodaten als WAV-Datei auf einer microSD-Karte. (meist mit FAT32-formatiert).
Signalweg intern:
- Der ESP32-S3 kommuniziert über die SPI-Schnittstelle mit dem SD-Modul
- Initialisierung der Karte und Erstellung einer neuen Datei (
record.wav) - Schreiben eines WAV-Headers (44 Byte) mit Platzhalter für die spätere Dateigröße
- Während der Aufnahme: fortlaufendes Schreiben der 16-bit Audiodaten in die Datei
- Nach Aufnahmeende: Aktualisierung des Headers mit korrekter Dateigröße und Schließen der Datei
Hardware:
ESP32-S3 mit Arduino-Library 3.3.3 –> Belegungsplan ESP32-S3
INMP441 (I2S-Mikrofon)
MicroSD-Kartenmodul mit <=32 GB MicroSD-Karte, FAT32 formatiert
Einfacher Taster
Verdrahtung:

- ESP32‑S3 Board bereitlegen
- INMP441 Mikrofon anschließen (I²S: WS → GPIO5, SD → GPIO4, SCK → GPIO6, VCC → 3V3, GND → GND, L/R → GND)
- MicroSD‑Kartenmodul verbinden (SPI: CS → GPIO9, SCK → GPIO12, MISO → GPIO13, MOSI → GPIO11, VCC → 3V3, GND → GND)
- Taster an GPIO7 gegen GND anschließen
Software/Setup
- In Arduino IDE 2.x in der Boardverwaltung die ESP32 Librarie ab Vers. 3.3.3 installieren
- Libraries:
Arduino.h,driver/i2s.h,SD.h,SPI.hsind standardmäßig schon eingebunden - Beispielcode hochladen
- Serielle Konsole öffnen (115200 Baud) ob Aufnahme bereit ist

Quellcode:
/*
WAV Aufnahme Rekorder V1.0 prilchen.de
Board:ESP32-s3 mit Library 3.3.3
SD Card Modul
-CS 9
-SCK 12
-MISO 13
-MOSI 11
-VCC 3V3
-GND GND
Mikro: INMP441
-I2S_WS 5
-I2S_SD 4
-I2S_SCK 6
-VCC 3V3
-GND und L/R an GND
SD-Karte 2GB mit FAT32
Taster an GPIO 7
*/
#include <Arduino.h>
#include "driver/i2s.h"
#include <SD.h>
#include <SPI.h>
//
// ─── PIN BELEGUNG ─────────────────────────────────────────────
//
#define I2S_WS 5 // LRCLK
#define I2S_SD 4 // DATA IN
#define I2S_SCK 6 // BCLK
#define BUTTON_PIN 7 // Taster → GND (INPUT_PULLUP)
#define SD_CS 9
//
// ─── AUFNAHME PARAMETER ───────────────────────────────────────
//
const int sampleRate = 16000;
File wavFile;
bool isRecording = false;
uint32_t dataLength = 0;
uint32_t lastButtonState = HIGH;
uint32_t lastDebounce = 0;
//
// ─── WAV HEADER FUNKTION ───────────────────────────────────────
//
void writeWavHeader(File file, int sampleRate, int totalDataLen) {
uint8_t header[44];
uint32_t byteRate = sampleRate * 2; // 16-bit mono
uint16_t blockAlign = 2;
memcpy(header, "RIFF", 4);
*(uint32_t*)&header[4] = totalDataLen + 36;
memcpy(&header[8], "WAVE", 4);
memcpy(&header[12], "fmt ", 4);
*(uint32_t*)&header[16] = 16;
*(uint16_t*)&header[20] = 1;
*(uint16_t*)&header[22] = 1;
*(uint32_t*)&header[24] = sampleRate;
*(uint32_t*)&header[28] = byteRate;
*(uint16_t*)&header[32] = blockAlign;
*(uint16_t*)&header[34] = 16;
memcpy(&header[36], "data", 4);
*(uint32_t*)&header[40] = totalDataLen;
file.seek(0);
file.write(header, 44);
}
//
// ─── SETUP ─────────────────────────────────────────────────────
//
void setup() {
Serial.begin(115200);
delay(500);
// SD-KARTE
if (!SD.begin(SD_CS)) {
Serial.println("SD Fehler!");
while (1);
}
// TASTER
pinMode(BUTTON_PIN, INPUT_PULLUP);
// I2S EINRICHTEN
i2s_config_t config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = sampleRate,
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
.communication_format = I2S_COMM_FORMAT_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 4,
.dma_buf_len = 1024
};
i2s_pin_config_t pin_config = {
.bck_io_num = I2S_SCK,
.ws_io_num = I2S_WS,
.data_out_num = -1,
.data_in_num = I2S_SD
};
i2s_driver_install(I2S_NUM_0, &config, 0, NULL);
i2s_set_pin(I2S_NUM_0, &pin_config);
Serial.println("Bereit. Drücke Taste zum Starten.");
}
//
// ─── LOOP ──────────────────────────────────────────────────────
//
void loop() {
// ── Taster entprellen ──────────────────────────────
int reading = digitalRead(BUTTON_PIN);
if (reading != lastButtonState) {
lastDebounce = millis();
lastButtonState = reading;
}
if ((millis() - lastDebounce) > 50) {
// ── Taste gedrückt (LOW wegen Pullup) ──────────
if (reading == LOW) {
if (!isRecording) {
// ► Aufnahme starten
wavFile = SD.open("/aufnahme.wav", FILE_WRITE);
uint8_t emptyHeader[44] = {0};
wavFile.write(emptyHeader, 44);
dataLength = 0;
isRecording = true;
Serial.println("Recording...");
delay(300);
} else {
// ■ Aufnahme stoppen
isRecording = false;
writeWavHeader(wavFile, sampleRate, dataLength);
wavFile.close();
Serial.println("Saved!");
delay(300);
}
}
}
// ── Wenn nicht in Aufnahme: Fertig ────────────────
if (!isRecording) return;
// ── Während Aufnahme: Daten lesen & umwandeln ──────
static uint8_t buffer[1024];
size_t bytes_read;
i2s_read(I2S_NUM_0, buffer, sizeof(buffer), &bytes_read, portMAX_DELAY);
for (int i = 0; i < bytes_read; i += 4) {
int32_t sample = ((int32_t*)buffer)[i/4];
int16_t s16 = sample >> 14;
wavFile.write((uint8_t*)&s16, 2);
dataLength += 2;
}
}
Mit dem ESP32-S3, dem INMP441-Mikrofon und dem SD-Kartenmodul entsteht so ein kompakter und leicht verständlicher WAV-Rekorder. Die Aufnahme lässt sich bequem über den Taster starten und stoppen, wobei der Status zusätzlich im seriellen Monitor angezeigt wird. So behältst du jederzeit die Kontrolle über den Aufnahmeprozess.

Die erzeugte Datei wird als standardkonforme WAV-Datei auf der SD-Karte gespeichert. Dank des korrekt geschriebenen Headers kann sie direkt am PC geöffnet und mit jedem gängigen Medienwiedergabeprogramm abgespielt werden – ganz ohne Nachbearbeitung oder Konvertierung.
Ich finde die Testaufnahme klingt doch wirklich natürlich, oder.
Damit zeigt das Projekt deutlich, wie sich mit wenigen Bauteilen ein vollständiger Audio-Rekorder realisieren lässt, der sowohl für Lernzwecke als auch für praktische Anwendungen geeignet ist.
Optional:
ESP32-S3 prüfen und korrekt in der Arduino IDE konfigurieren
Bei diesem Projekt konnte ich gut lernen, dass das erstellen von Audio Datei viel Speicher voraussetzt. Deshalb habe ich einen ESP32-S3 mit N16R8 eingesetzt, der aber erst nach der richtigen Konfiguration optimal lief.
Das Problem liegt darin, dass die Arduino IDE aus Sicherheitsgründen fast immer auf den kleinsten gemeinsamen Nenner zurückgreift (meist 4MB Flash, kein PSRAM). Wenn man Einstellungen wählt, die die Hardware nicht hat, bootet der Controller oft gar nicht mehr (Bootloop). Daher ist „konservativ“ die Standardeinstellung.
Hier mal meine Notizen, wie du dein ESP32‑S3 Board prüfst und richtig die IDE-Einstellungen konfigurierst.
Gehen Sie in der Arduino IDE auf Werkzeuge (Tools). Stellen Sie sicher, dass die folgenden Punkte jedes Mal geprüft werden, wenn Sie ein neues Board anschließen:
Board: Wählen Sie nicht nur „ESP32 Dev Module“, sondern wenn möglich das spezifische Board (z.B. „ESP32S3 Dev Module“). Das schaltet oft schon die richtigen Optionen frei.
- Board-Bezeichnung verstehen und einstellen
ESP32‑S3 Boards tragen Kürzel (siehe Metallgehäuse) wie N16R8. Diese geben direkt Auskunft über den Speicher:
- N = Flash-Größe (z. B. N16 → 16 MB Flash)
- R = PSRAM-Größe (z. B. R8 → 8 MB PSRAM)
Beispiele:
- N4R0 → 4 MB Flash, kein PSRAM
- N8R2 → 8 MB Flash, 2 MB PSRAM
- N16R8 → 16 MB Flash, 8 MB PSRAM
Falls nichts draufsteht: Schaue auf die Rechnung oder in das Datenblatt des Verkäufers. Ohne dieses Wissen ist es ein Ratespiel.
Flash Size: Hier musste ich manuell 16MB (128Mb) auswählen.
PSRAM (Wichtig!): Beim S3 musste ich aktiv einschalten.
Partition Scheme (Der häufigste Fehler):
• Auch wenn nun 16MB Flash einstellt wurde, nutzt die IDE standardmäßig oft nur ein Partitionsschema für 4MB („Default 4MB with spiffs“). Der Rest bleibt leer und ungenutzt!
• Lösung: Wähle ein Schema wie 16M Flash (3MB APP/9.9MB FATFS) oder RainMaker, je nachdem, ob mehr Platz für Programmcode oder für Dateien (Audio) gebraucht wird.
- QIO oder OPI? – Flash-Modus erkennen
Der Speicher kann über QIO (Quad I/O) oder OPI (Octal I/O) angebunden sein.
- N16R8 Boards sind meist OPI
- N8R2 Boards sind meist QIO
- N4R0 Boards laufen standardmäßig mit QIO
Wenn du unsicher bist: In der Arduino IDE den Modus wechseln und mit einem Testsketch prüfen. Nur eine Einstellung läuft stabil und zeigt den vollen PSRAM.
Flash Mode: QIO oder OPI (in diesem Fall habe ich OPI gewählt)
- Vergleich der Standardvarianten
| Variante | Flash | PSRAM | Typischer Modus | Geeignet für |
|---|---|---|---|---|
| N4R0 | 4 MB | ❌ | QIO | Sensoren, LED, kleine Webserver |
| N8R2 | 8 MB | 2 MB | QIO | Audio, BLE, einfache Grafik |
| N16R8 | 16 MB | 8 MB | OPI | GUI, Kamera, komplexe Audio-Projekte |
- Speicher prüfen mit Testsketch
Vor jedem Projekt solltest du dieden Speicher testen:
void setup() {
Serial.begin(115200);
while (!Serial); // Warten auf Monitor
delay(1000);
Serial.println("\n--- HARDWARE CHECK ---");
// 1. Chip Modell
Serial.printf("Chip Modell: %s\n", ESP.getChipModel());
Serial.printf("Chip Revision: %d\n", ESP.getChipRevision());
// 2. Flash Speicher (Programmspeicher + Dateisystem)
uint32_t flashSize = ESP.getFlashChipSize();
Serial.printf("Flash Größe (Real): %d MB\n", flashSize / (1024 * 1024));
// Prüfen ob Flash Mode korrekt (Geschwindigkeit)
Serial.printf("Flash Speed: %d MHz\n", ESP.getFlashChipSpeed() / 1000000);
// 3. Interner RAM (Schnell, aber klein)
Serial.printf("Interner Heap (RAM): %d KB\n", ESP.getHeapSize() / 1024);
Serial.printf("Freier Interner Heap: %d KB\n", ESP.getFreeHeap() / 1024);
// 4. PSRAM (Externer RAM - Wichtig für Audio!)
if (psramFound()) {
Serial.printf("PSRAM Größe: %d MB\n", ESP.getPsramSize() / (1024 * 1024));
Serial.printf("Freier PSRAM: %d MB\n", ESP.getFreePsram() / (1024 * 1024));
} else {
Serial.println("ACHTUNG: Kein PSRAM gefunden oder nicht aktiviert!");
}
Serial.println("----------------------");
}
void loop() {}
Ergebnis zeigt sofort, ob deine IDE-Einstellungen zur Hardware passen.
Wenn z.B. „Kein PSRAM gefunden“ erscheint, ist die Option im Menü aus oder der falsche Typ (OPI vs QSPI) gewählt.
5. Programmierung optimieren (PSRAM nutzen)
Nur weil PSRAM da ist, nutzt der ESP32 ihn nicht automatisch für alles.
• Automatisch: Wenn du malloc() für sehr große Blöcke aufrufst, versucht der ESP32-S3 oft automatisch, in den PSRAM auszuweichen (je nach CONFIG_SPIRAM_USE_MALLOC Einstellung).
• Manuell (Besser für Audio): Für Audio-Buffer solltest du den Speicher explizit im PSRAM anfordern, um den schnellen internen RAM für den Prozessor freizuhalten.
Nutze dafür ps_malloc() statt malloc():
// Beispiel: Einen 1MB Audio Buffer erstellen
uint8_t *audioBuffer = (uint8_t *) ps_malloc(1024 * 1024);
if (audioBuffer == NULL) {
Serial.println("Nicht genug PSRAM für Buffer!");
} else {
Serial.println("Buffer erfolgreich im PSRAM angelegt.");
}
Ich hoffe so vermeidest du Resets durch Speicherüberlastung und stellst sicher, dass dein ESP32-S3 sein volles Potenzial entfaltet.
…und jetzt noch ein Tipp zum optimieren:
Standard-Partitionstabellen sind für Audio-Projekte oft nutzlos, weil sie entweder zu viel Platz für den Programmcode (app0) reservieren oder den Flash-Speicher nicht voll ausnutzen.
Für Audio brauchen wir meistens: Wenig Platz für Code (Audio-Player-Code ist klein) und maximalen Platz für das Dateisystem (WAV/MP3 Dateien sind groß).
Hier ist der einfachste Weg, dies zu tun, ohne tief in die Systemdateien der IDE eingreifen zu müssen. Wir nutzen die „Lokale Partitionstabelle“.
Die Methode: partitions.csv im Projektordner
Anstatt die Arduino-Dateien zu hacken, legen wir einfach eine Konfigurationsdatei direkt in Ihren Sketch-Ordner. Der Compiler erkennt diese automatisch und überschreibt die Standardeinstellungen.
Schritt 1: Eine neue Datei erstellen
- Öffne deinen Sketch-Ordner (Dort, wo deine .ino Datei liegt).
- Erstelle eine neue Textdatei und nenne sie exakt: partitions.csv
- Öffne diese Datei mit einem Texteditor (Notepad, TextEdit, VS Code).
Schritt 2: Das Schema definieren (Das 16MB Audio-Layout)
Hier ist ein optimiertes Layout für Ihren 16MB Flash ESP32-S3.
Es reserviert ca. 3 MB für das Programm (inkl. Update-Fähigkeit)
und gibt satte ~12 MB für Audio-Dateien.
Kopiere diesen Block in Ihre partitions.csv:
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, , 0x5000,
otadata, data, ota, , 0x2000,
app0, app, ota_0, , 0x140000,
app1, app, ota_1, , 0x140000,
spiffs, data, spiffs, , 0xC00000,
Erklärung der Werte (Hexadezimal):
- nvs (20KB): Speichert WLAN-Passwörter und kleine Einstellungen.
- otadata (8KB): Sagt dem ESP, welche App (app0 oder app1) gestartet werden soll.
- app0 (1.25 MB): Ihr eigentlicher Programmcode. 1.25 MB ist für reinen Audio-Code meist mehr als genug.
- app1 (1.25 MB): Platzhalter für OTA-Updates (Over The Air). Wenn Updates über WLAN nicht geplant sind, kannst du das löschen und app0 vergrößern.
- spiffs (12 MB): Hier liegt das Gold. Der Speicherplatz für die Audio-Dateien.
- Hinweis: Die Spalte „Offset“ habe ich leer gelassen. Der ESP32-Compiler berechnet die Startadressen dann automatisch nacheinander. Das vermeidet Rechenfehler!
Schritt 3: In der Arduino IDE einstellen
Damit das nun funktioniert, musst Du in der IDE trotzdem eine Einstellung wählen, die dem Compiler sagt: „Ich habe 16MB Platz“. - Starte die Arduino IDE neu (damit er die .csv Datei bemerkt).
- Gehe zu Werkzeuge -> Partition Scheme.
- Wähle ruhig „Default 4MB with spiffs“ oder eine beliebige Einstellung, ABER stelle sicher, dass unter Werkzeuge -> Flash Size weiterhin 16MB eingestellt ist.
- Der Trick: Solange die partitions.csv im Ordner liegt, wird das Partitionsschema aus dem Menü ignoriert. Die IDE nutzt deine csv-Datei. Die Einstellung im Menü dient oft nur dazu, dem Compiler die maximale Größe mitzuteilen, damit er keine Warnung wirft.
Wichtiger Zusatztipp: LittleFS vs. SPIFFS
In der Tabelle oben steht spiffs. SPIFFS ist das alte Dateisystem. Für Audio-Projekte auf dem ESP32 wird heute LittleFS empfohlen. Es ist schneller, stabiler und unterstützt Ordnerstrukturen (was SPIFFS nicht kann).
Wie man auf LittleFS umstellt: - Ändere in der partitions.csv die letzte Zeile von spiffs zu spiffs (nö, nicht verlesen, der Name spiffs als Label wird oft aus Kompatibilität beibehalten, aber der Typ entscheidet). Eigentlich reicht es, im Code LittleFS zu nutzen.
- Korrekter wäre die Zeile:
frk, data, spiffs, , 0xC00000,
(Der Name vorne ist egal, der SubType spiffs (0x82) ist entscheidend. LittleFS nutzt oft denselben Partitionstyp, wird aber im Code anders angesprochen).
Im Arduino Code nutzen Sie dann:
#include <LittleFS.h>
void setup() {
// Statt SPIFFS.begin() nutzen Sie:
if(!LittleFS.begin(true)){ // true = formatieren, falls fehlgeschlagen
Serial.println("LittleFS Mount Failed");
return;
}
// Gesamten Platz prüfen
Serial.printf("Dateisystem Größe: %d MB\n", LittleFS.totalBytes() / (1024 * 1024));
}
Checkliste zum Erfolg:
- partitions.csv erstellt und gespeichert?
- IDE neu gestartet?
- Code hochgeladen?
- Zeigt LittleFS.totalBytes() nun ca. 12 MB an?
Wenn das klappt, hast du das Maximum aus deinem 16MB/8MB Monster herausgeholt!
Jetzt bin ich aber wirklich fertig 🙂
