Start / Maker Projects / ESP32-Soundmaschine in einem IKEA-Bilderrahmen

ESP32-Soundmaschine in einem IKEA-Bilderrahmen

Ein Freund von mir hat einen ganz besonderen Musikgeschmack. Als unsere Gruppe also beschloss, Geschenke zu machen, die alle – im wahrsten Sinne des Wortes – in einen gemeinsamen Rahmen passen, kam mir eine Idee. Warum nicht eine echte Soundmaschine in einen IKEA-Bilderrahmen bauen? Gesamtbudget: unter 30 €, ein Wochenende und ein KI-Programmierassistent. Los geht’s.

 

Die Hardware

Ich hatte nicht viel Zeit, also habe ich einfach alles auf einmal bei AliExpress bestellt:

  • Ai-Thinker ESP32-Audio-Kit (ESP32-A1S V2.2) – ESP32 mit integriertem ES8388-Audio-Codec, zwei NS4150-Class-D-Verstärkern, SD-Kartensteckplatz, Bluetooth, sechs Hardware-Tasten, alles auf einer Platine. Etwa 17 €.
  • WS2812B-LED-Streifen, 30 LEDs – einzeln adressierbares RGB, angeschlossen an GPIO22. Etwa 4 €.
  • Zwei kleine Lautsprecher aus meinem Bastelvorrat.
  • Ein 5×7"-Bilderrahmen von IKEA – 5 €.
  • Ein USB-Stromkabel für 5-V-Versorgung.
  • Das war’s. Keine selbstbestückte Leiterplatte, kein Wirrwarr auf dem Steckbrett. Die Platine hat zwei USB-Anschlüsse – einen zum Flashen, einen für die Stromversorgung –, was die Sache noch übersichtlicher macht.

    Die Lautsprecher sitzen im Rahmen und sind auf die Kunststoffrückwand geklebt. Wenn ich das noch einmal bauen würde, würde ich sie direkt am Holzrahmen befestigen, damit dieser als Resonanzkörper fungiert – der 6-W-Verstärker ist da, die 4-Ω-Treiber sind da, aber im Moment entweicht der Klang durch den Kunststoff. Für 5-Euro-Lautsprecher klingt es immer noch überraschend gut. Mit dem Rahmen als Resonanzkörper wäre es wirklich hervorragend.

    Der Bau

    Alles wird mit doppelseitigem Klebeband und einer Heißklebepistole zusammengefügt. Das einzige, was gelötet werden muss, ist:

    1. Lautsprecherkabel an die JST-Anschlusspads der Platine
    2. Strom- und Signalkabel des LED-Streifens an die Platine
    3. Die Platine ist leicht in die Rückseite des Rahmens eingelassen, sodass nichts über die Rahmentiefe hinausragt. Du kannst das direkt an die Wand hängen.

      Eine Kleinigkeit, die ich beim nächsten Mal hinzufügen würde: einen externen Netzschalter. Momentan zieht man einfach den Stecker. Funktioniert zwar gut, aber trotzdem.

      Was es kann

      Der Funktionsumfang ist letztendlich ziemlich komplett geworden:

      • Spielt alle .mp3 Dateien aus dem Stammverzeichnis der SD-Karte in alphabetischer Reihenfolge der Dateinamen ab, in einer Endlosschleife
      • _welcome.mp3 wird beim Hochfahren immer als Erstes abgespielt (Unterstriche werden vor Buchstaben sortiert) – das Gerät begrüßt dich mit einem violetten Lichtrahmen
      • KEY1 kurz drücken: Pause/Weiter
      • KEY1 lang drücken (2 s): Bluetooth-A2DP-Empfangsmodus umschalten – LEDs blinken während des Pairings blau, dann streamt dein Handy Audio über denselben Codec und die gleiche LED-Anzeige
      • Taste 3/Taste 4: Vorheriger/Nächster Titel
      • Taste 5/Taste 6: Lautstärke runter/rauf
      • Bluetooth-Gerätename: Dietmars-Soundbox (natürlich ein personalisiertes Geschenk)
      • Die LED-Show

        Hier wird es technisch interessant. Die LED-Task führt auf Core 0 eine aus 5 Ebenen zusammengesetzte Show aus:

        1. Ambient – langsames Farbwechseln basierend auf der Gesamtlautstärke
        2. Beat-Burst – Blinken bei Kick-Erkennung
        3. Bass-Blob – auf die Helligkeit abgebildete tieffrequente Energie
        4. Sparkles – zufällige hochfrequente Glitzereffekte
        5. Picture Frame – die vier Seiten des Rahmens erhalten jeweils einen eigenen Farbton, gesteuert durch die Lautstärkestufe
        6. Die Audio-Pipeline funktioniert so: PCM-Samples werden aus dem I2S-Write-Callback abgegriffen (sowohl im SD- als auch im Bluetooth-Modus), von Stereo int16 in Mono float konvertiert und in einen Stream-Puffer geschoben. Die LED-Task leert diesen Puffer, führt eine 512-Punkt-FFT mit einem Hann-Fenster durch, teilt das Ergebnis in 8 logarithmische Bänder von 60 Hz bis 20 kHz auf, wendet bandweise AGC mit einer Zeitkonstante von ~10 s an und führt die Beat-Erkennung über ein Dual-EMA-Subbass-Verhältnis durch: kick_fast / kick_slow > 1.3, mit einem Mindestabstand von 200 ms zwischen den Schlägen.

          Bei leisen Passagen atmet der Rahmen in ruhigen Farben bei geringer Sättigung. Laute und schnelle Musik löst Disco-Snaps mit voller Sättigung aus, bei denen jede Seite des Rahmens einen zufällig zugewiesenen Farbton mit einer erzwungenen Trennung von ~80–120° erhält – so kommt es nie vor, dass zwei Seiten versehentlich dieselbe Farbe haben. Lautstärkestufen (leise / mittel / laut) steuern, wie viele Rahmenseiten gleichzeitig beleuchtet werden.

          Das sorgt wirklich für die richtige Stimmung im Raum. Es war toll zu sehen, wie dieser Teil funktioniert.

          Entwicklung mit Claude Code

          Ich hatte einen Docker-Container aus einem früheren Projekt – Ubuntu-Basis, Node.js, @anthropic-ai/claude-code installiert, USB-Seriell-Passthrough über docker-compose.yml. Ich habe alle AliExpress-Datenblätter und Platinen-Schaltpläne, die ich finden konnte, in einen external-docs/ Ordner, schrieb ein grobes requirements.mdund ließ Claude loslegen.

          Die Konfiguration ist es wert, beschrieben zu werden: Das Repo enthält eine CLAUDE.md , das Claude anweist, requirements.md, alle skills/ Dateien (Arbeitskonventionen pro Thema) und alle knowledge/ Dateien (validierte Konfigurationsnotizen pro Komponente) zu Beginn jeder Sitzung zu lesen. Auf diese Weise verfügt Claude über eine kontextübergreifende Persistenz über Sitzungen hinweg, ohne auf den Konversationsverlauf angewiesen zu sein. Jedes Mal, wenn Claude eine funktionierende Konfiguration bestätigt, schreibt er eine Notiz in knowledge/ — so beginnt die nächste Sitzung mit dem, was sich bereits bewährt hat.

          Die erste Herausforderung bestand darin, überhaupt Audioausgabe zu erhalten. Der ES8388-Codec reagiert empfindlich auf die Initialisierungsreihenfolge: Die SD-Karte muss vor der I2S-Initialisierung gemountet werden (GPIO25/26-Konflikt), MCLK muss vor der Codec-Initialisierung 100 ms lang stabil sein, und du musst I2S_CLK_SRC_DEFAULT — APLL verursacht breitbandiges weißes Rauschen auf dem ESP32. Claude hat all das gemeistert, indem er es versuchte, scheiterte, den Fehler auslas und Anpassungen vornahm. Der richtige Weg war, espressif/esp_codec_dev und niemals direkt in die ES8388-Register zu schreiben.

          Es gab auch einen fiesen Bug, bei dem jeder Song mit eingebettetem Cover-Art am Anfang knisterte. Die Ursache: Der ID3v2-Tag kann ein mehrere hundert KB großes JPEG enthalten, und der Helix-MP3-Decoder durchsuchte es Byte für Byte auf der Suche nach einem Sync-Frame, wodurch der I2S-DMA-Puffer leergefegt wurde. Die Lösung bestand darin, die Syncsafe-Größe aus dem ID3v2-Header zu parsen und fseek() das gesamte Tag zu überspringen, bevor die Datei an den Decoder übergeben wurde. Claude hat das selbstständig herausgefunden, nachdem ich ihm das Knackgeräusch beschrieben hatte.

          Bluetooth benötigte eine benutzerdefinierte 3-MB-Partitionstabelle, da der BT-Stack die Binärdatei auf ~1,1 MB aufbläht. Der A2DP-Daten-Callback muss streng nicht-blockierend sein – übertrage ihn StreamBufferHandle_t, Drain in einer separaten Task auf Core 1 – sonst bricht die BT-Verbindung bei jeder Belastung ab.

          Ich bin ein passabler Programmierer. Ich hätte zwei oder drei Wochenenden gebraucht, um das alles alleine zum Laufen zu bringen. Mit Claude Code war es eins.

          Stückliste

          TeilKosten
          ESP32-Audio-Kit (A1S V2.2, ES8388)17
          WS2812B LED-Streifen (30 LEDs)4
          IKEA-Bilderrahmen5
          Sonstiges (USB-Kabel, Klebeband, Kleber)~3
          Gesamt~29

          Lautsprecher aus dem Bastelvorrat, SD-Karte aus der Schublade. Rechne noch 5 € dazu, wenn du diese Teile kaufst.

          Was kommt als Nächstes

          Die naheliegende Erweiterung ist ein kleiner 5-V-LiPo-Akku unter dem Rahmen für die kabellose Wandmontage. Der LED-Streifen benötigt ohnehin 5 V, daher ist kein Aufwärtswandler erforderlich – nur ein winziger LiPo-Akku mit einer TP4056-Ladekarte unter dem Rahmen. Und ein ordentlicher externer Netzschalter.

          Der vollständige Quellcode und das Devcontainer-Setup sind auf GitHub.


          Ein paar Erkenntnisse, die ich daraus gewonnen habe: Günstige AliExpress-Hardware ist wirklich leistungsfähig, wenn du weißt, was du kaufst. Der ES8388-Codec ist ein echter Audio-Chip – er klingt gut. Und Claude Code ist ein wirklich nützliches Tool für Embedded-Projekte, nicht nur für Web-Sachen, solange du ihm einen strukturierten Kontext gibst. Der knowledge/ Ordneransatz – bei dem sich bestätigte Hardware-Fakten über mehrere Sitzungen hinweg ansammeln – hat einen echten Unterschied gemacht. Es lohnt sich, das richtig einzurichten.


          GitHub: happychriss/ESP32-A1S-sound-machine

          Markiert: