INMP441 MEMS-Mikrofon mit ESP32 auslesen (I2S): Schallpegel messen, Klatsch-Schalter & WAV-Aufnahme
Analoge Mikrofonmodule wie das KY-038 sind für Messungen kaum zu gebrauchen: Sie rauschen, driften mit der Temperatur und hängen an einem Poti, das jeder anders einstellt. Das INMP441 macht es anders: Es liefert fertig digitalisierte 24-Bit-Samples über I2S – ohne Verstärker, ohne ADC, ohne Kalibrier-Poti. In diesem Tutorial schließt du es an den ESP32 an, misst echte Schallpegel in Dezibel, baust einen Klatsch-Schalter, der nicht bei jedem Türknall auslöst, und nimmst am Ende eine abspielbare WAV-Datei auf die SD-Karte auf.
Warum I2S und nicht der ADC?
Bei einem analogen Mikrofonmodul passiert die ganze Arbeit außerhalb des Chips: Die Mikrofonkapsel liefert wenige Millivolt, ein Operationsverstärker hebt das an, und dein Mikrocontroller misst das Ergebnis mit analogRead(). Jede Stufe fügt Rauschen hinzu – und beim ESP32 kommt ein ADC dazu, der ohnehin nicht besonders linear ist (Details dazu im Artikel ESP32 ADC richtig auslesen).
Das INMP441 verlegt die komplette Kette in ein 4,7 × 3,8 mm großes MEMS-Gehäuse. Heraus kommen digitale Zahlen. Was du am Pin siehst, ist bereits das Endergebnis – kein Kabel, kein Steckbrett und keine Netzteilbrummschleife kann daran noch etwas verschlechtern.
| Merkmal | Analoges Modul (KY-038 & Co.) | INMP441 (I2S) |
|---|---|---|
| Ausgang | Analogspannung + Digital-Schwelle | 24-Bit-Samples, seriell |
| Signal-Rausch-Abstand | ca. 40–50 dB | 61 dB(A) |
| Frequenzgang | undefiniert | 60 Hz – 15 kHz |
| Empfindlichkeit | Poti, nicht reproduzierbar | −26 dBFS bei 94 dB SPL (fest) |
| Max. Schalldruck | je nach Einstellung | 120 dB SPL ohne Clipping |
| Betriebsspannung | 3,3 / 5 V | nur 1,8 – 3,3 V |
| Stromaufnahme | ca. 1–5 mA | ca. 1,4 mA |
| Pins am Controller | 1 analog (+1 digital) | 3 (BCLK, WS, SD) |
💡 Der entscheidende Satz aus dem Datenblatt: −26 dBFS bei 94 dB SPL. Weil diese Empfindlichkeit fest im Chip verdrahtet ist, kannst du aus dem Zahlenwert zurückrechnen, wie laut es tatsächlich war. Genau das macht ein analoges Modul unmöglich – dort verstellst du mit dem Poti die Skala, ohne zu wissen, wohin. Deshalb sind Schallpegelmessungen erst mit dem INMP441 überhaupt sinnvoll.
Das brauchst du
1Verkabelung – und die eine Brücke, die alle vergessen
Das INMP441-Modul hat sechs Pins. Fünf davon sind schnell erklärt, der sechste ist die häufigste Fehlerquelle überhaupt:
| INMP441 | Bedeutung | ESP32 (Vorschlag) | ESP32-S3 |
|---|---|---|---|
| VDD | Versorgung, max. 3,3 V | 3V3 | 3V3 |
| GND | Masse | GND | GND |
| SCK | Bit-Clock (BCLK) | GPIO14 | GPIO5 |
| WS | Word-Select (LRCLK) | GPIO15 | GPIO6 |
| SD | Serial Data (Ausgang) | GPIO32 | GPIO7 |
| L/R | Kanalwahl | GND = links | GND = links |
⚠️ L/R darf nicht offen bleiben. Der Pin entscheidet, in welcher Hälfte des I2S-Frames das Mikrofon seine Daten ablegt. Lässt du ihn in der Luft hängen, ist der Zustand undefiniert – du bekommst entweder gar keine Daten oder Werte, die zufällig mal da sind und mal nicht. Lege L/R fest auf GND (Kanal links) und lies im Code entsprechend den linken Kanal. Wer zwei Mikrofone für Stereo betreiben will, legt das zweite auf VDD und teilt sich SCK, WS und SD.
⚠️ Keine 5 V an VDD. Das INMP441 verträgt maximal 3,63 V. Am ESP32 ist das kein Thema, weil dort ohnehin 3,3 V anliegen – aber wer aus Gewohnheit den 5V-Pin nimmt, zerstört den Chip sofort und dauerhaft.
ℹ️ Warum diese GPIOs? Die I2S-Peripherie des ESP32 lässt sich über die GPIO-Matrix auf fast beliebige Pins legen. Vermeide aber: GPIO6–11 (interner Flash), GPIO34–39 (nur Eingang – für SD zwar erlaubt, für BCLK/WS nicht) und die Strapping-Pins GPIO0, GPIO2, GPIO12 und GPIO15. GPIO15 als WS funktioniert, weil das Mikrofon den Pin nicht treibt; würdest du dort etwas gegen GND ziehen, käme der ESP32 im falschen Flash-Modus hoch.
2Erster Sketch: Rohdaten sehen
Bevor du irgendetwas berechnest, willst du wissen, ob überhaupt Daten ankommen. Dieser Sketch nutzt die klassische driver/i2s.h-API, die in allen ESP32-Core-Versionen ab 2.x funktioniert:
#include <driver/i2s.h>
#define I2S_PORT I2S_NUM_0
#define PIN_I2S_BCLK 14
#define PIN_I2S_WS 15
#define PIN_I2S_SD 32
#define SAMPLE_RATE 16000
#define BUFFER_LEN 512
int32_t rohPuffer[BUFFER_LEN];
void i2sInit() {
const i2s_config_t cfg = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
.sample_rate = SAMPLE_RATE,
.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // INMP441 sendet 24 Bit in 32-Bit-Slots
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, // L/R liegt auf GND
.communication_format = I2S_COMM_FORMAT_STAND_I2S,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = 8,
.dma_buf_len = BUFFER_LEN,
.use_apll = false,
.tx_desc_auto_clear = false,
.fixed_mclk = 0
};
const i2s_pin_config_t pins = {
.bck_io_num = PIN_I2S_BCLK,
.ws_io_num = PIN_I2S_WS,
.data_out_num = I2S_PIN_NO_CHANGE,
.data_in_num = PIN_I2S_SD
};
i2s_driver_install(I2S_PORT, &cfg, 0, NULL);
i2s_set_pin(I2S_PORT, &pins);
i2s_zero_dma_buffer(I2S_PORT);
}
void setup() {
Serial.begin(115200);
delay(300);
i2sInit();
Serial.println(F("I2S laeuft. Sprich mal ins Mikrofon..."));
}
void loop() {
size_t gelesen = 0;
esp_err_t err = i2s_read(I2S_PORT, rohPuffer, sizeof(rohPuffer),
&gelesen, portMAX_DELAY);
if (err != ESP_OK) return;
uint16_t n = gelesen / sizeof(int32_t);
int32_t mn = 2147483647L;
int32_t mx = -2147483647L;
for (uint16_t i = 0; i < n; i++) {
// Die oberen 24 Bit sind das Signal - 8 Bit nach rechts schieben
int32_t s = rohPuffer[i] >> 8;
if (s < mn) mn = s;
if (s > mx) mx = s;
}
Serial.print(F("min=")); Serial.print(mn);
Serial.print(F(" max=")); Serial.print(mx);
Serial.print(F(" spitze-spitze=")); Serial.println(mx - mn);
delay(100);
}
💡 Das >> 8 ist kein Schönheitsfehler, sondern Pflicht. Das INMP441 liefert 24 Bit Nutzdaten, die linksbündig in einem 32-Bit-Slot sitzen. Ohne das Schieben arbeitest du mit Zahlen im Millionenbereich und die untersten acht Bit sind Müll. Nach dem Schieben liegt der Wertebereich bei ±8.388.607 – das ist die Zahl, mit der du weiterrechnest.
Bei Stille solltest du eine Spitze-Spitze-Amplitude von wenigen Tausend sehen, beim Sprechen aus 30 cm Entfernung mehrere Hunderttausend. Bleibt der Wert konstant bei 0 oder bei −1, springe direkt ins Troubleshooting am Ende.
3Vom Zahlenwert zum echten Dezibel-Wert
Jetzt wird es interessant. Weil die Empfindlichkeit des INMP441 im Datenblatt festgeschrieben ist, lässt sich aus dem RMS-Wert der Samples der tatsächliche Schalldruckpegel berechnen. Der Rechenweg in drei Schritten:
- Gleichanteil abziehen. MEMS-Mikrofone haben einen kleinen DC-Offset. Ohne Abzug misst du den Offset statt des Schalls.
- RMS bilden. Der Effektivwert – Wurzel aus dem Mittel der Quadrate – ist das, was unser Gehör als Lautstärke wahrnimmt, nicht die Spitzenamplitude.
- In dB SPL umrechnen. Über die bekannte Empfindlichkeit von −26 dBFS bei 94 dB SPL.
#include <driver/i2s.h>
#include <math.h>
#define I2S_PORT I2S_NUM_0
#define PIN_I2S_BCLK 14
#define PIN_I2S_WS 15
#define PIN_I2S_SD 32
#define SAMPLE_RATE 16000
#define BUFFER_LEN 512
// Datenblatt INMP441: -26 dBFS bei 94 dB SPL
const float MIC_EMPFINDLICHKEIT_DBFS = -26.0f;
const float MIC_REFERENZ_SPL = 94.0f;
const float VOLLAUSSCHLAG = 8388608.0f; // 2^23
int32_t rohPuffer[BUFFER_LEN];
void i2sInit(); // wie in Schritt 2
// Liefert den Schalldruckpegel in dB SPL
float leseSchallpegel() {
size_t gelesen = 0;
i2s_read(I2S_PORT, rohPuffer, sizeof(rohPuffer), &gelesen, portMAX_DELAY);
uint16_t n = gelesen / sizeof(int32_t);
if (n == 0) return 0.0f;
// Schritt 1: Mittelwert (DC-Offset) bestimmen
double summe = 0.0;
for (uint16_t i = 0; i < n; i++) summe += (double)(rohPuffer[i] >> 8);
double mittel = summe / n;
// Schritt 2: RMS um den Mittelwert herum
double quadratSumme = 0.0;
for (uint16_t i = 0; i < n; i++) {
double x = (double)(rohPuffer[i] >> 8) - mittel;
quadratSumme += x * x;
}
double rms = sqrt(quadratSumme / n);
if (rms < 1.0) rms = 1.0; // log(0) vermeiden
// Schritt 3: in dBFS, dann in dB SPL
float dbfs = 20.0f * log10f((float)(rms / VOLLAUSSCHLAG));
float spl = dbfs - MIC_EMPFINDLICHKEIT_DBFS + MIC_REFERENZ_SPL;
return spl;
}
void setup() {
Serial.begin(115200);
delay(300);
i2sInit();
}
void loop() {
float spl = leseSchallpegel();
Serial.print(spl, 1);
Serial.print(F(" dB SPL "));
// kleine Textbalkenanzeige: 30 dB = leer, 90 dB = voll
int balken = (int)((spl - 30.0f) / 2.0f);
if (balken < 0) balken = 0;
if (balken > 30) balken = 30;
for (int i = 0; i < balken; i++) Serial.print('#');
Serial.println();
delay(100);
}
| Situation | Typischer Messwert | Einordnung |
|---|---|---|
| Ruhiges Zimmer nachts | 30 – 35 dB | nahe der Rauschgrenze des Sensors |
| Wohnzimmer, Kühlschrank läuft | 40 – 45 dB | Grundpegel für die Kalibrierung |
| Normales Gespräch, 1 m | 55 – 65 dB | Sprache klar erkennbar |
| Staubsauger, 2 m | 70 – 78 dB | dauerhaft unangenehm |
| Händeklatschen, 1 m | 85 – 100 dB (kurz) | Trigger für Schritt 4 |
ℹ️ Wie genau ist das? Ohne Referenzgerät liegst du typischerweise ±3 bis ±5 dB daneben – die Serienstreuung des Mikrofons und der fehlende A-Bewertungsfilter machen den Unterschied. Für Trends („Ist es heute lauter als gestern?") ist das völlig ausreichend. Wer es genauer braucht, vergleicht einmal mit einer Schallpegel-App und addiert die Differenz als Konstante zum Ergebnis. Für amtliche Lärmmessungen taugt kein Bastelaufbau, egal wie gut der Sensor ist.
4Klatsch-Schalter, der nicht bei jeder Tür auslöst
Die naive Variante – „wenn laut, dann schalten" – reagiert auf zufallende Türen, Husten und den Hund. Ein brauchbarer Klatsch-Schalter braucht drei Zutaten: einen gleitenden Grundpegel, eine Anstiegsflanke statt eines absoluten Schwellwerts und ein Zeitfenster für die Doppelklatsch-Erkennung.
#include <driver/i2s.h>
#include <math.h>
#define I2S_PORT I2S_NUM_0
#define PIN_I2S_BCLK 14
#define PIN_I2S_WS 15
#define PIN_I2S_SD 32
#define PIN_RELAIS 26
#define SAMPLE_RATE 16000
#define BUFFER_LEN 256 // ca. 16 ms pro Block
int32_t rohPuffer[BUFFER_LEN];
// --- Erkennungsparameter ---
const float ANSTIEG_DB = 14.0f; // so viel ueber Grundpegel = Klatschen
const float GRUNDPEGEL_ALPHA = 0.02f; // wie schnell sich der Grundpegel anpasst
const uint16_t TOTZEIT_MS = 180; // Nachhall ignorieren
const uint16_t FENSTER_MIN_MS = 200; // fruehestes zweites Klatschen
const uint16_t FENSTER_MAX_MS = 900; // spaetestes zweites Klatschen
float grundpegel = 45.0f;
uint32_t letzterKlatsch = 0;
uint8_t klatschZaehler = 0;
bool relaisAn = false;
void i2sInit(); // wie in Schritt 2
float leseSchallpegel(); // wie in Schritt 3
void relaisSchalten(bool an) {
relaisAn = an;
digitalWrite(PIN_RELAIS, an ? HIGH : LOW);
Serial.println(an ? F("-> Relais AN") : F("-> Relais AUS"));
}
void setup() {
Serial.begin(115200);
pinMode(PIN_RELAIS, OUTPUT);
digitalWrite(PIN_RELAIS, LOW);
delay(300);
i2sInit();
// Grundpegel einmal grob einmessen
Serial.println(F("Kalibriere Raumpegel - bitte 2 Sekunden still sein..."));
float summe = 0;
for (uint8_t i = 0; i < 40; i++) summe += leseSchallpegel();
grundpegel = summe / 40.0f;
Serial.print(F("Grundpegel: "));
Serial.print(grundpegel, 1);
Serial.println(F(" dB"));
}
void loop() {
float spl = leseSchallpegel();
uint32_t jetzt = millis();
bool istKlatsch = (spl > grundpegel + ANSTIEG_DB) &&
(jetzt - letzterKlatsch > TOTZEIT_MS);
if (istKlatsch) {
uint32_t abstand = jetzt - letzterKlatsch;
if (klatschZaehler == 1 &&
abstand >= FENSTER_MIN_MS && abstand <= FENSTER_MAX_MS) {
// Zweites Klatschen im Fenster -> umschalten
relaisSchalten(!relaisAn);
klatschZaehler = 0;
} else {
klatschZaehler = 1;
}
letzterKlatsch = jetzt;
Serial.print(F("Klatsch bei "));
Serial.print(spl, 1);
Serial.println(F(" dB"));
} else {
// Grundpegel nur bei Ruhe langsam nachfuehren
if (spl < grundpegel + 6.0f) {
grundpegel = grundpegel * (1.0f - GRUNDPEGEL_ALPHA)
+ spl * GRUNDPEGEL_ALPHA;
}
}
// Fenster abgelaufen -> Zaehler zuruecksetzen
if (klatschZaehler == 1 && jetzt - letzterKlatsch > FENSTER_MAX_MS) {
klatschZaehler = 0;
}
}
💡 Warum die Doppelklatsch-Logik so viel besser ist: Ein einzelnes lautes Geräusch passiert im Alltag ständig. Zwei laute Geräusche mit exakt 200–900 ms Abstand passieren praktisch nie zufällig. Das Fenster nach unten zu begrenzen ist genauso wichtig wie nach oben – ohne die 200 ms zählt der Nachhall des ersten Klatschens als zweites Klatschen, und das Relais flattert.
⚠️ Das KY-019-Modul mit High-Level-Trigger will 5 V an VCC, aber der Steuereingang verträgt 3,3 V vom ESP32. Schalte damit keine Netzspannung, solange du dir nicht absolut sicher bist, was du tust – 230 V auf einem Breadboard sind lebensgefährlich. Für Tests und für Kleinspannungslasten wie LED-Streifen oder 12-V-Lüfter ist das Modul genau richtig. Hintergründe im Artikel Relais-Modul KY-019 mit Arduino & ESP32 schalten.
5WAV-Datei auf die SD-Karte aufnehmen
Eine WAV-Datei ist erfreulich simpel: 44 Byte Header, danach die rohen Samples. Der einzige Haken ist, dass im Header die Dateigröße steht – die kennst du erst am Ende. Deshalb schreibst du den Header zweimal: einmal als Platzhalter, einmal korrekt beim Schließen.
#include <driver/i2s.h>
#include <SPI.h>
#include <SD.h>
#define I2S_PORT I2S_NUM_0
#define PIN_I2S_BCLK 14
#define PIN_I2S_WS 15
#define PIN_I2S_SD 32
#define PIN_SD_CS 5 // SD-Modul: MOSI 23, MISO 19, SCK 18
#define PIN_TASTER 4
#define SAMPLE_RATE 16000
#define BUFFER_LEN 512
#define AUFNAHME_S 10
int32_t rohPuffer[BUFFER_LEN];
int16_t wavPuffer[BUFFER_LEN];
void i2sInit(); // wie in Schritt 2
// --- 44-Byte WAV-Header, 16 Bit mono ---
void schreibeWavHeader(File &f, uint32_t datenBytes) {
uint32_t byteRate = SAMPLE_RATE * 2; // 1 Kanal * 2 Byte
uint32_t riffGroesse = datenBytes + 36;
f.write((const uint8_t*)"RIFF", 4);
f.write((uint8_t*)&riffGroesse, 4);
f.write((const uint8_t*)"WAVE", 4);
f.write((const uint8_t*)"fmt ", 4);
uint32_t fmtGroesse = 16; f.write((uint8_t*)&fmtGroesse, 4);
uint16_t audioFormat = 1; f.write((uint8_t*)&audioFormat, 2); // PCM
uint16_t kanaele = 1; f.write((uint8_t*)&kanaele, 2);
uint32_t rate = SAMPLE_RATE; f.write((uint8_t*)&rate, 4);
f.write((uint8_t*)&byteRate, 4);
uint16_t blockAlign = 2; f.write((uint8_t*)&blockAlign, 2);
uint16_t bits = 16; f.write((uint8_t*)&bits, 2);
f.write((const uint8_t*)"data", 4);
f.write((uint8_t*)&datenBytes, 4);
}
void aufnehmen(const char* dateiname) {
File f = SD.open(dateiname, FILE_WRITE);
if (!f) {
Serial.println(F("Datei konnte nicht angelegt werden"));
return;
}
schreibeWavHeader(f, 0); // Platzhalter
uint32_t zielSamples = (uint32_t)SAMPLE_RATE * AUFNAHME_S;
uint32_t geschrieben = 0;
Serial.println(F("Aufnahme laeuft..."));
while (geschrieben < zielSamples) {
size_t gelesen = 0;
i2s_read(I2S_PORT, rohPuffer, sizeof(rohPuffer), &gelesen, portMAX_DELAY);
uint16_t n = gelesen / sizeof(int32_t);
for (uint16_t i = 0; i < n; i++) {
// 24 Bit -> 16 Bit: erst um 8 nach rechts (Nutzdaten),
// dann um 8 weitere (Aufloesung reduzieren) = insgesamt 16
int32_t s = rohPuffer[i] >> 16;
// Sanfte Verstaerkung, damit die Aufnahme nicht zu leise ist
s = s * 4;
if (s > 32767) s = 32767; // sauber begrenzen
if (s < -32768) s = -32768;
wavPuffer[i] = (int16_t)s;
}
f.write((uint8_t*)wavPuffer, n * sizeof(int16_t));
geschrieben += n;
}
uint32_t datenBytes = geschrieben * 2;
f.seek(0);
schreibeWavHeader(f, datenBytes); // jetzt mit echter Groesse
f.close();
Serial.print(F("Fertig: "));
Serial.print(dateiname);
Serial.print(F(" ("));
Serial.print(datenBytes / 1024);
Serial.println(F(" KB)"));
}
uint16_t dateiNr = 0;
void setup() {
Serial.begin(115200);
pinMode(PIN_TASTER, INPUT_PULLUP);
delay(300);
if (!SD.begin(PIN_SD_CS)) {
Serial.println(F("SD-Karte nicht gefunden"));
while (true) delay(1000);
}
i2sInit();
Serial.println(F("Bereit. Taster an GPIO4 startet die Aufnahme."));
}
void loop() {
if (digitalRead(PIN_TASTER) == LOW) {
char name[20];
snprintf(name, sizeof(name), "/rec%03u.wav", dateiNr++);
aufnehmen(name);
while (digitalRead(PIN_TASTER) == LOW) delay(10); // entprellen
delay(200);
}
}
ℹ️ 16 kHz mono braucht 32 kB pro Sekunde. Zehn Sekunden sind also gut 320 kB – für jede SD-Karte eine Lappalie. Der Engpass ist nicht die Kapazität, sondern die Schreibgeschwindigkeit: Wenn die Karte während eines Blockschreibvorgangs länger als der DMA-Puffer reicht beschäftigt ist, verlierst du Samples und hörst ein Knacken. Größere dma_buf_count-Werte (12 statt 8) und eine Karte der Klasse 10 lösen das zuverlässig. Grundlagen zum SD-Modul im Artikel Micro-SD-Karten-Modul mit Arduino (SPI).
6VU-Meter auf dem OLED
Zum Abschluss ein kleines Dashboard: aktueller Pegel als Balken, Spitzenwert-Markierung mit Nachlauf und ein Verlaufsdiagramm der letzten Sekunden auf dem 0,96 Zoll OLED:
#include <Wire.h>
#include <U8g2lib.h>
#include <driver/i2s.h>
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
float leseSchallpegel(); // wie in Schritt 3
void i2sInit(); // wie in Schritt 2
const float SKALA_MIN = 30.0f;
const float SKALA_MAX = 95.0f;
float spitzenwert = SKALA_MIN;
uint32_t spitzenZeit = 0;
const uint16_t SPITZE_HALTEN_MS = 1200;
#define VERLAUF_N 128
uint8_t verlauf[VERLAUF_N];
uint8_t verlaufIdx = 0;
uint8_t splZuPixel(float spl, uint8_t breite) {
if (spl < SKALA_MIN) spl = SKALA_MIN;
if (spl > SKALA_MAX) spl = SKALA_MAX;
return (uint8_t)((spl - SKALA_MIN) / (SKALA_MAX - SKALA_MIN) * breite);
}
void setup() {
Serial.begin(115200);
u8g2.begin();
u8g2.enableUTF8Print();
i2sInit();
for (uint8_t i = 0; i < VERLAUF_N; i++) verlauf[i] = 0;
}
void loop() {
float spl = leseSchallpegel();
uint32_t jetzt = millis();
if (spl > spitzenwert) { spitzenwert = spl; spitzenZeit = jetzt; }
else if (jetzt - spitzenZeit > SPITZE_HALTEN_MS) {
spitzenwert -= 0.6f; // langsam absinken lassen
if (spitzenwert < SKALA_MIN) spitzenwert = SKALA_MIN;
}
verlauf[verlaufIdx] = splZuPixel(spl, 24);
verlaufIdx = (verlaufIdx + 1) % VERLAUF_N;
u8g2.clearBuffer();
// Kopfzeile mit Zahlenwert
u8g2.setFont(u8g2_font_logisoso20_tn);
char t[10];
snprintf(t, sizeof(t), "%.0f", spl);
u8g2.drawStr(0, 21, t);
u8g2.setFont(u8g2_font_6x10_te);
u8g2.drawStr(46, 21, "dB SPL");
snprintf(t, sizeof(t), "max %.0f", spitzenwert);
u8g2.drawStr(46, 10, t);
// Pegelbalken
u8g2.drawFrame(0, 26, 128, 12);
uint8_t b = splZuPixel(spl, 124);
if (b > 0) u8g2.drawBox(2, 28, b, 8);
// Spitzenwert-Markierung
uint8_t p = splZuPixel(spitzenwert, 124);
u8g2.drawVLine(2 + p, 26, 12);
// Verlauf unten
for (uint8_t i = 0; i < VERLAUF_N; i++) {
uint8_t idx = (verlaufIdx + i) % VERLAUF_N;
uint8_t h = verlauf[idx];
if (h > 0) u8g2.drawVLine(i, 64 - h, h);
}
u8g2.sendBuffer();
}
💡 Der Spitzenwert mit Nachlauf ist die halbe Miete. Ein reiner Momentanbalken zappelt so schnell, dass man nichts ablesen kann. Die Markierung, die den Höchstwert 1,2 Sekunden hält und dann langsam absinkt, ist genau das, was analoge VU-Meter mechanisch schon immer gemacht haben – und der Grund, warum sie sich gut ablesen lassen.
Troubleshooting
⚠️ Alle Samples sind 0 oder −1: Fast immer der offene L/R-Pin oder ein vertauschtes SCK/WS. Lege L/R fest auf GND und prüfe, ob im Code I2S_CHANNEL_FMT_ONLY_LEFT steht. Steht dort ONLY_RIGHT, liest du die leere Frame-Hälfte aus.
⚠️ Werte in Millionenhöhe, Rauschen extrem: Das >> 8 fehlt. Die untersten acht Bit des 32-Bit-Slots sind beim INMP441 nicht definiert und erzeugen Zufallszahlen.
⚠️ Der Pegel ist konstant hoch, auch bei Stille: Der DC-Offset wird nicht abgezogen. Ohne die Mittelwertbildung aus Schritt 3 misst du den Ruhepegel des Wandlers statt des Schalls.
⚠️ Regelmäßiges Brummen oder ein Ton bei genau der halben Abtastrate: Zu lange Kabel. I2S läuft hier mit rund 1 MHz Bit-Clock – ab etwa 15 cm Dupont-Kabel koppelt die Clock in die Datenleitung. Kürze die Kabel oder verdrille jede Signalleitung mit einer eigenen GND-Ader.
⚠️ Das Mikrofon reagiert nach dem Flashen erst nach einem Reset: Bekanntes Verhalten, wenn i2s_driver_install() zu früh nach dem Boot läuft. Ein delay(300) vor der Initialisierung räumt das aus.
⚠️ Die WAV-Datei ist stumm oder wird nicht abgespielt: Der Header wurde nicht mit der finalen Größe überschrieben. Prüfe, ob f.seek(0) vor dem zweiten schreibeWavHeader() steht – ohne Rücksprung hängst du den Header hinten an die Daten.
⚠️ Der ESP32 startet nicht mehr, seit das Mikrofon dran ist: Du hast ein Strapping-Pin erwischt. GPIO12 darf beim Boot nicht auf HIGH liegen, GPIO0 nicht auf LOW. Weiche auf GPIO14/15/32 aus wie in der Tabelle oben.
Praxistipps
- 16 kHz reichen fast immer. Sprache liegt unterhalb von 8 kHz, und die Abtastrate bestimmt direkt, wie viel Rechenzeit, RAM und Speicherplatz du brauchst. 44,1 kHz ist nur nötig, wenn du Musik in Hi-Fi-Qualität aufnehmen willst – und dafür ist ein einzelnes MEMS-Mikrofon ohnehin das falsche Werkzeug.
- Die Schallöffnung nicht zukleben. Das kleine Loch auf der Platine ist der Schalleintritt. Im Gehäuse brauchst du davor eine Bohrung von 1,5–2 mm; ein Stück akustisch offener Schaumstoff hält Staub ab, ohne den Frequenzgang nennenswert zu verbiegen.
-
Rechne in
double, speichere inint16_t. Die RMS-Berechnung mitfloatläuft bei großen Puffern in Rundungsfehler. Für die eigentliche Aufnahme reichen 16 Bit dagegen locker. -
Beide I2S-Ports nutzen. Der ESP32 hat
I2S_NUM_0undI2S_NUM_1. Wenn du gleichzeitig aufnehmen und über einen Verstärker ausgeben willst, nimm für jede Richtung einen eigenen Port statt eines Voll-Duplex-Ports. - Messwerte glätten, nicht mitteln. Ein exponentiell gleitender Mittelwert über die dB-Werte sieht ruhiger aus, ohne echte Ereignisse zu verschlucken. Die verschiedenen Verfahren vergleicht der Artikel Sensorwerte glätten mit Arduino.
-
Für Dauerbetrieb an Batterie: I2S abschalten.
i2s_driver_uninstall()vor dem Deep Sleep spart die 1,4 mA des Mikrofons plus die Peripherie im Chip. Wie das Aufwachen sauber funktioniert, steht im Artikel ESP32 Deep Sleep Tutorial. - Der Klassiker als Erweiterung: Lärmampel. Grün, gelb, rot aus dem LED-Sortiment, Schwellen bei 55 und 70 dB – in Klassenzimmern und Großraumbüros erstaunlich wirkungsvoll und in zwanzig Zeilen Code erledigt.
🚀 Bauteile für dein Audio-Projekt
- INMP441 MEMS-Mikrofon Modul I2S – digital, rauscharm, kalibriert ab Werk
- ESP32 DEV KIT WROOM32 – zwei I2S-Peripherien in Hardware
- ESP32-S3 N16R8 Dev Kit – 8 MB PSRAM für lange Aufnahmen
- Micro-SD-Karten-Modul (SPI) – Ziel für die WAV-Dateien
- 0,96 Zoll OLED Display SSD1306 – VU-Meter und Zahlenwert
- Relais-Modul KY-019 – der Klatsch-Schalter braucht etwas zum Schalten
- 3 mm LED Sortiment – für die Lärmampel
- Breadboard + Dupont-Kabel Set – kurze Wege für die I2S-Clock
Fazit
Das INMP441 ist eines der wenigen Bauteile, bei denen der Umstieg von analog auf digital sofort und unübersehbar besser ist. Kein Poti, das nach jedem Transport neu eingestellt werden muss, kein Rauschen aus dem Verstärker, keine Diskussion über den ADC. Stattdessen fest definierte Empfindlichkeit, mit der sich echte Dezibel-Werte berechnen lassen, und 24 Bit Dynamik, die vom Flüstern bis zum Presslufthammer alles ohne Umschalten abdeckt.
Die zwei Dinge, die dich am Anfang Zeit kosten, sind der L/R-Pin (fest auf GND) und das >> 8 beim Auslesen. Sind die beiden erledigt, liegt der Rest offen: Schallpegelmessung, Klatsch-Schalter, WAV-Recorder und VU-Meter sind alles nur unterschiedliche Auswertungen desselben Datenstroms – und alle vier bekommst du mit dem Code aus diesem Artikel an einem Nachmittag zum Laufen.