Zum Inhalt springen

ESP32 + AC-Dimmer: Guru Meditation Error und IRAM_ATTR — Ursachen und Lösung

ESP32 AC-Dimmer stürzt mit WiFi ab? Die Ursache ist IRAM_ATTR — der Nulldurchgangs-ISR läuft aus dem Flash, der während WiFi-Aktivität deaktiviert wird. Behoben durch rbdimmerESP32 (automatisches IRAM_ATTR + Kern-Isolation) oder DimmerLink (kein ISR).

TL;DR: If your ESP32 with an AC dimmer crashes ("Guru Meditation Error" or "Cache disabled but cached memory region accessed"), the cause is that the zero-cross ISR handler is not marked with IRAM_ATTR and runs from flash memory. When WiFi accesses flash, ESP32 temporarily disables it — the ISR can't execute and the core panics. Fix for ESP32: use the ESP32 Dual-Core library — it handles IRAM_ATTR automatically and runs dimmer and WiFi/BLE tasks on separate cores. Note: the library only works on dual-core ESP32 (not the C/S/H series). The old RBDdimmer on ESP32 requires a manual patch.



Problembeschreibung

Sie haben einen AC-Dimmer an einen ESP32 angeschlossen und der Beispielcode läuft einwandfrei — stabil ohne WiFi. Aber sobald Sie WiFi aktivieren (WiFi.begin()) oder aktive Datenübertragung starten (MQTT, HTTP, OTA), friert der ESP32 ein oder startet mit einem Core Panic neu.

Typisches Muster: Ein Stack-Trace erscheint im Serial-Monitor, dann ein Neustart. Häufigste Auslöser:

  • WiFi nach dem Start des Dimmers aktivieren
  • Aktives MQTT (häufiges publish/subscribe)
  • OTA-Firmware-Update
  • Verwendung der alten RBDdimmer-Bibliothek auf ESP32

Typische Fehlermeldungen:

text
Guru Meditation Error: Core 0 panic'ed (LoadProhibited)
PC: 0x400xxxxx
Cache disabled but cached memory region accessed
Guru Meditation Error: Core 1 panic'ed (IllegalInstruction)

Typische Symptome:

  • Dimmer ist stabil ohne WiFi, stürzt innerhalb von Sekunden mit aktivem WiFi ab
  • Abstürze geschehen genau während WiFi-Aktivität (MQTT publish, HTTP-Request, OTA)
  • Stack-Trace-Adresse nahe 0x400Dxxxx (Flash-Bereich) statt 0x400Cxxxx (IRAM-Bereich)
  • Zufällige Neustarts unter normaler Last
  • Abstürze hören auf, wenn attachInterrupt() für den Nulldurchgang entfernt wird



Grundursache

ESP32 speichert Programmcode im externen SPI-Flash-Speicher. Im Normalbetrieb wird Flash über den CPU-Cache gelesen — schnell und transparent für den Programmierer.

Einige Operationen erfordern jedoch exklusiven Zugriff auf den Flash-Bus:

  • Flash-Schreiben/Löschen (NVS, SPIFFS, LittleFS)
  • OTA-Update
  • WiFi: bestimmte Stack-Operationen (Zertifikate laden, NVS)
  • Andere interne ESP-IDF-Operationen

Während dieser Operationen deaktiviert ESP32 vorübergehend den Flash-Cache und blockiert die Ausführung jedes Codes, der sich im Flash befindet. Wenn ein Interrupt auslöst (z.B. Nulldurchgang) und sein Handler (ISR) ebenfalls im Flash liegt — kann der Prozessor den ISR nicht ausführen und wirft eine „Cache disabled but cached memory region accessed"-Ausnahme.

ESP-IDF-Lösung: ISR-Code muss sich im IRAM (internes RAM, immer zugänglich) befinden. Setzen Sie das IRAM_ATTR-Attribut vor die Funktionsdeklaration:

cpp
void IRAM_ATTR zeroCrossISR() {
    // dieser Handler befindet sich im IRAM, nicht im Flash
    // wird auch bei deaktiviertem Flash ausgeführt
}

Zusätzlich sollte die Dimmer-Task für zuverlässiges Timing auf Core 0 laufen (xTaskCreatePinnedToCore(..., 0)). rbdimmerESP32 macht dies automatisch.

Problem mit der alten RBDdimmer: Sie wurde geschrieben, bevor die ESP32-spezifischen Anforderungen allgemein bekannt waren. Ihre ISR-Handler haben kein IRAM_ATTR und laufen aus dem Flash. Auf ESP32 ohne WiFi funktioniert das — Flash wird nicht blockiert. Mit aktiviertem WiFi führt es zu periodischen Abstürzen.



Lösungen



🟢 Für Einsteiger: DimmerLink — ganz ohne ISR

Möchten Sie sich nicht mit IRAM_ATTR, Interrupts und ESP32-Eigenheiten befassen? DimmerLink handhabt Nulldurchgang und TRIAC-Steuerung intern — Ihr ESP32 sendet nur einen Helligkeitslevel-Befehl. Funktioniert mit jedem ESP32-Modell.

Jeder ESP32 is a separate microcontroller that manages the AC dimmer via I2C or UART. In this setup the ESP32 doesn't handle zero-cross interrupts at all — it simply sends a level value. IRAM_ATTR crashes are impossible.

Wann diese Lösung wählen:

  • ☐ Verwendung von ESP32-S2, ESP32-C3 oder ESP32-H2 (Single-Core — keine
  • ISR-Bibliothek wird unterstützt)

  • ☐ WiFi/MQTT/ESP-NOW benötigt und Stabilität wichtiger als ISR-Steuerung
  • ☐ Raspberry Pi oder anderer SBC soll verwendet werden
  • ☐ Steuerung über LoRa-WAN, GSM/LTE, RS-232/485 via UART benötigt
  • ☐ Keine Lust, sich mit Interrupts zu befassen
  • Verdrahtung:

    • DimmerLink → ESP32: SDA → GPIO 21, SCL → GPIO 22, VCC → 3.3V, GND → GND
    • DimmerLink → AC-Dimmer: gemäß DimmerLink-Anschlussdiagramm

    Code (I2C, empfohlen):

    cpp
    // DimmerLink — AC-Dimmer-Steuerung über I2C
    // ESP32 verwendet keine Interrupts — kein Konflikt mit WiFi
    // Doku: https://www.rbdimmer.com/docs/dimmerlink-I2CCommunication
    #include <Wire.h>
    #include <WiFi.h>
    #include <PubSubClient.h>  // Beispiel: MQTT + Dimmer ohne Abstürze
    #define DIMMER_ADDR 0x50
    #define REG_LEVEL   0x10
    void setLevel(uint8_t level) {  // level: 0–100%
        Wire.beginTransmission(DIMMER_ADDR);
        Wire.write(REG_LEVEL);
        Wire.write(level);
        Wire.endTransmission();
    }
    void setup() {
        Wire.begin(21, 22);    // SDA, SCL
        WiFi.begin("ssid", "password");
        setLevel(50);           // 50% Helligkeit sofort
    }
    void loop() {
        // MQTT, HTTP, OTA — alles funktioniert ohne Konflikte mit dem Dimmer
    }

    Ergebnis: Dimmer + WiFi + MQTT funktionieren ohne Abstürze. Kein IRAM_ATTR, keine Interrupts auf dem ESP32.



    🔵 Für Fortgeschrittene: richtige Bibliothek für ESP32

    Möchten Sie ISR direkt auf ESP32 ohne DimmerLink verwenden? Sie brauchen die richtige Bibliothek.


    Option A: rbdimmerESP32 ✅ Empfohlen

    When to use: dual-core ESP32 (standard ESP32, not S2/C3/H2) Library: ESP32 Dual-Core

    rbdimmerESP32 wurde speziell für ESP32 geschrieben: sie platziert den ISR automatisch im IRAM und bindet die Dimmer-Task an Core 0. Kein manuelles IRAM_ATTR nötig.

    cpp
    // Plattform: Dual-Core ESP32
    // Bibliothek: rbdimmerESP32 — https://github.com/robotdyn-dimmer/rbdimmerESP32
    // IRAM_ATTR wird automatisch innerhalb der Bibliothek angewendet
    #include "rbdimmerESP32.h"
    #include <WiFi.h>
    #define ZC_PIN   18   // jeder GPIO am ESP32
    #define DIM_PIN  19   // jeder GPIO am ESP32
    rbdimmer dimmer;
    void setup() {
        Serial.begin(115200);
        dimmer.begin(ZC_PIN, DIM_PIN, 50);  // Netz 50 Hz
        dimmer.setPower(50);                 // 50% Helligkeit
        // WiFi kann nach oder vor der Dimmer-Initialisierung gestartet werden —
        // die Reihenfolge ist mit rbdimmerESP32 nicht kritisch, aber gute Praxis
        WiFi.begin("ssid", "password");
        while (WiFi.status() != WL_CONNECTED) delay(500);
        Serial.println("WiFi verbunden, Dimmer stabil");
    }
    void loop() {
        // Sanfter Helligkeitsverlauf — keine Abstürze bei aktivem WiFi
        for (int p = 10; p <= 95; p++) {
            dimmer.setPower(p);
            delay(30);
        }
        for (int p = 95; p >= 10; p--) {
            dimmer.setPower(p);
            delay(30);
        }
    }

    Erwartetes Ergebnis: Dimmer läuft stabil mit aktivem WiFi, MQTT, HTTP — keine Abstürze. WiFi und Dimmer arbeiten parallel ohne Konflikte.

    Häufige Fehler:

    • Bibliothek nicht installiert: rbdimmerESP32 ist eine separate Bibliothek — verwechseln Sie sie nicht mit der alten RBDdimmer. Installation über GitHub oder Arduino Library Manager.
    • Verwendung auf ESP32-S2/C3/H2: Funktioniert nicht. Single-Core ESP32-Varianten unterstützen ISR-Dimming mit keiner Bibliothek — verwenden Sie DimmerLink.
    • Falsche Netzfrequenz: dimmer.begin(ZC_PIN, DIM_PIN, 50) — der dritte Parameter ist die Netzfrequenz (50 oder 60 Hz). Falsche Frequenz = falsches Phasenanschnitt-Timing.


    Option B: manueller Patch für die alte RBDdimmer

    Wann verwenden: Sie nutzen bereits die alte RBDdimmer und möchten noch nicht migrieren.

    Dies ist eine temporäre Lösung. Migration zu rbdimmerESP32 wird empfohlen.

    Suchen Sie die Quelldatei RBDdimmer.cpp (oder .h), finden Sie die Nulldurchgangs-Interrupt-Handler-Funktion und fügen Sie IRAM_ATTR hinzu:

    cpp
    // In RBDdimmer.cpp — ISR finden und IRAM_ATTR hinzufügen:
    // VORHER (falsch für ESP32):
    void zeroCrossISR() {
        // ...
    }
    // NACHHER (korrekt):
    void IRAM_ATTR zeroCrossISR() {
        // ...
    }

    Wenn der ISR als Lambda oder Callback an attachInterrupt() übergeben wird, stellen Sie sicher, dass der Callback ebenfalls IRAM_ATTR hat. In RBDdimmer gibt es normalerweise einen Haupt-ISR — finden Sie ihn durch Suche nach attachInterrupt oder RISING.

    Wichtig: Der Patch geht bei einem Bibliotheks-Update verloren. Forken Sie das Repository oder migrieren Sie zu rbdimmerESP32.



    Option C: eigene Implementierung mit IRAM_ATTR

    ⚠️ Nur zu Lehrzwecken — nicht für den Produktiveinsatz. Zwei absichtliche Vereinfachungen im folgenden Code:

    1. delayMicroseconds() im ISR blockiert andere Interrupts.
    2. Die Timer-API ist für ESP32 Arduino Core 2.x geschrieben — kompiliert nicht auf Core 3.x (neue API: timerBegin(1000000)).

    Für den Produktiveinsatz verwenden Sie rbdimmerESP32 (Option A).

    Wann verwenden: Sie möchten das Prinzip verstehen, bevor Sie eine eigene Implementierung schreiben.

    cpp
    // ⚠️ NUR ZU LEHRZWECKEN — lesen Sie die Warnung oben
    // Plattform: Dual-Core ESP32, Arduino Core 2.x
    // IRAM_ATTR ist erforderlich für ALLE Funktionen, die aus einem ISR aufgerufen werden
    #define ZC_PIN  18
    #define DIM_PIN 19
    volatile int brightness = 50;
    volatile bool fired = false;
    hw_timer_t *timer = NULL;
    portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
    void IRAM_ATTR onTimer() {
        portENTER_CRITICAL_ISR(&timerMux);
        if (!fired) {
            // ⚠️ delayMicroseconds() im ISR — nur zur Demonstration
            // Im Produktivbetrieb: zweiten Timer verwenden, um das TRIAC-Gate zu schließen
            digitalWrite(DIM_PIN, HIGH);
            delayMicroseconds(100);
            digitalWrite(DIM_PIN, LOW);
            fired = true;
        }
        portEXIT_CRITICAL_ISR(&timerMux);
    }
    void IRAM_ATTR zeroCrossISR() {
        portENTER_CRITICAL_ISR(&timerMux);
        fired = false;
        uint32_t delay_us = (100 - brightness) * 78;  // 0–7800 µs
        // ⚠️ Core-2.x-API — auf Core 3.x timerAlarm() verwenden
        timerAlarmWrite(timer, delay_us, false);
        timerAlarmEnable(timer);
        portEXIT_CRITICAL_ISR(&timerMux);
    }
    void setup() {
        pinMode(DIM_PIN, OUTPUT);
        // ⚠️ Core 2.x: timerBegin(num, divider, countUp)
        // Core 3.x: timer = timerBegin(1000000);
        timer = timerBegin(0, 80, true);  // 1-MHz-Takt
        timerAttachInterrupt(timer, &onTimer, true);
        attachInterrupt(digitalPinToInterrupt(ZC_PIN), zeroCrossISR, RISING);
    }
    void loop() {
        brightness = 50;
        delay(100);
    }


    ⚠️ Häufige Fallstricke

    • „Funktioniert ohne WiFi, stürzt mit WiFi ab": Dies ist genau das IRAM_ATTR-Problem. Suchen Sie die Ursache nicht im Dimmer-Code — das Problem ist die ISR-Platzierung. Wechseln Sie zu rbdimmerESP32.

    • „IRAM_ATTR hinzugefügt — jetzt ein anderer Fehler": Wenn der ISR Funktionen ohne IRAM_ATTR aufruft (z.B. Serial.print()), verursachen diese den Absturz. Alle vom ISR aufgerufenen Funktionen müssen ebenfalls IRAM_ATTR haben oder sich im IRAM befinden.

    • „Hat funktioniert, nach SDK-Update kaputt": Nach dem Update des ESP32 Arduino Core oder ESP-IDF wurden die IRAM_ATTR-Anforderungen strenger. Wechseln Sie zu rbdimmerESP32 — sie verfolgt die Kompatibilität mit neuen SDK-Versionen.

    • „ESP32-S2 ausprobiert — rbdimmerESP32 funktioniert nicht": ESP32-S2, C3 und H2 sind Single-Core. ISR-Dimming ist auf ihnen mit keiner Bibliothek möglich. Die einzige Lösung ist DimmerLink.

    • portDISABLE_INTERRUPTS() ausprobiert — hat vorübergehend geholfen": Das Deaktivieren von Interrupts stört WiFi und andere Systemfunktionen. Das ist keine Lösung.




    Schnell-Check

    Vor dem Posten im Forum überprüfen Sie:

  • ☐ Welche Bibliothek verwenden Sie — alte `RBDdimmer` oder neue
  • rbdimmerESP32? Für ESP32 brauchen Sie die neue.

  • ☐ Welcher ESP32 — Standard (Dual-Core) oder S2/C3/H2?
  • Für S2/C3/H2 — nur DimmerLink.

  • ☐ Im Stack-Trace: Ist die ISR-Adresse im Flash (`0x400Dxxxx`) oder
  • IRAM (0x400Cxxxx)?

  • ☐ Stürzt es nur bei aktivem WiFi ab oder immer?
  • ☐ Gibt es Aufrufe von nicht-`IRAM_ATTR`-Funktionen im ISR (Serial.print usw.)?
  • etc.)?



    Kompatibilitätstabelle

    Plattform Plattform Funktioniert mit WiFi? RBDdimmer (alt)
    ESP32 ❌ nein ⚠️ Abstürze RBDdimmer + manueller Patch
    ESP32 ❌ nein ✅ ja rbdimmerESP32
    ESP32 Dual-Core ✅ automatisch ✅ ja rbdimmerESP32
    ESP32 Dual-Core DimmerLink
    Jeder ESP32 — (kein ISR) ✅ ja rbdimmerESP32
    Jeder ESP32 — (kein ISR) ✅ ja rbdimmerESP32
    Arduino Uno/Mega — (nicht nötig) — (kein WiFi) — (no WiFi)



    Verwandte Probleme

    • Nulldurchgang nicht erkannttroubleshooting/zero-cross-detection-errors.md
    • Trailing vs Leading Edgeload-types/trailing-vs-leading-edge.md
    • Trailing vs Leading Edgeload-types/trailing-vs-leading-edge.md



    Noch Fragen?

    Ask on forum.rbdimmer.com or open a GitHub Issue.

    Diesen Beitrag teilen
    Anmelden , um einen Kommentar zu hinterlassen