Перейти к содержимому

ESP32 + AC-диммер: Guru Meditation Error и IRAM_ATTR — причины и решение

ESP32 AC-диммер падает с WiFi? Причина — IRAM_ATTR: обработчик прерывания перехода через ноль выполняется из flash, которая отключается во время работы WiFi. Решение: rbdimmerESP32 (автоматический IRAM_ATTR + изоляция ядер) или DimmerLink (ISR на ESP32 не используется).

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 двухъядерный 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.



Описание проблемы

Вы подключили AC-диммер к ESP32, и демонстрационный код работает нормально — стабильно без WiFi. Но как только вы включаете WiFi (WiFi.begin()) или начинаете активную передачу данных (MQTT, HTTP, OTA), ESP32 зависает или перезагружается с паникой ядра.

Типичный паттерн: в Serial-мониторе появляется стек-трейс, затем перезагрузка. Наиболее частые триггеры:

  • включение WiFi после запуска диммера
  • активная работа MQTT (частые publish/subscribe)
  • OTA-обновление прошивки
  • использование старой библиотеки RBDdimmer на ESP32

Типичные сообщения об ошибках:

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)

Типичные симптомы:

  • Диммер стабилен без WiFi, падает в течение секунд при активном WiFi
  • Сбои происходят именно во время WiFi-активности (MQTT publish, HTTP-запрос, OTA)
  • Адрес в стек-трейсе около 0x400Dxxxx (область flash) вместо 0x400Cxxxx (область IRAM)
  • Случайные перезагрузки при обычной нагрузке
  • Сбои прекращаются при удалении attachInterrupt() для перехода через ноль



Причина проблемы

ESP32 хранит программный код во внешней SPI flash-памяти. В нормальном режиме flash читается через кэш процессора — быстро и прозрачно для программиста.

Однако некоторые операции требуют эксклюзивного доступа к шине flash:

  • Запись/стирание flash (NVS, SPIFFS, LittleFS)
  • OTA-обновление
  • WiFi: некоторые операции стека (загрузка сертификатов, NVS)
  • Другие внутренние операции ESP-IDF

Во время этих операций ESP32 временно отключает кэш flash и блокирует выполнение любого кода, расположенного во flash. Если в этот момент срабатывает прерывание (например, переход через ноль), а его обработчик (ISR) тоже находится во flash — процессор не может выполнить ISR и выбрасывает исключение «Cache disabled but cached memory region accessed».

Решение ESP-IDF: код ISR должен находиться в IRAM (внутренняя RAM, всегда доступна). Поставьте атрибут IRAM_ATTR перед объявлением функции:

cpp
void IRAM_ATTR zeroCrossISR() {
    // этот обработчик находится в IRAM, а не во flash
    // выполняется даже при отключённой flash
}

Дополнительно, для надёжной работы тайминга задача диммера должна быть привязана к Core 0 (xTaskCreatePinnedToCore(..., 0)). rbdimmerESP32 делает это автоматически.

Проблема со старой RBDdimmer: она была написана до того, как специфические требования ESP32 стали широко известны. Её обработчики ISR не имеют IRAM_ATTR и выполняются из flash. На ESP32 без WiFi это работает — flash не блокируется. При включённом WiFi это приводит к периодическим сбоям.



Решения



🟢 Для начинающих: DimmerLink — вообще без ISR

Не хотите разбираться с IRAM_ATTR, прерываниями и особенностями ESP32? DimmerLink обрабатывает переход через ноль и управление TRIAC внутри себя — ваш ESP32 просто отправляет команду уровня яркости. Работает на любой модели ESP32.

Любой 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.

Когда выбрать это решение:

  • ☐ Используете ESP32-S2, ESP32-C3 или ESP32-H2 (одноядерные — ни одна
  • библиотека ISR-диммирования не поддерживается)

  • ☐ Нужен WiFi/MQTT/ESP-NOW, и стабильность важнее прямого ISR-управления
  • ☐ Хотите использовать Raspberry Pi или другой одноплатник
  • ☐ Нужно управление через LoRa-WAN, GSM/LTE, RS-232/485 по UART
  • ☐ Не хотите разбираться с прерываниями
  • Подключение:

    • DimmerLink → ESP32: SDA → GPIO 21, SCL → GPIO 22, VCC → 3.3V, GND → GND
    • DimmerLink → AC-диммер: по схеме подключения DimmerLink

    Код (I2C, рекомендуется):

    cpp
    // DimmerLink — управление AC-диммером через I2C
    // ESP32 не использует прерывания — конфликт с WiFi исключён
    // Документация: https://www.rbdimmer.com/docs/dimmerlink-I2CCommunication
    #include <Wire.h>
    #include <WiFi.h>
    #include <PubSubClient.h>  // пример: MQTT + диммер без сбоев
    #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% сразу
    }
    void loop() {
        // MQTT, HTTP, OTA — всё работает без конфликтов с диммером
    }

    Результат: Диммер + WiFi + MQTT работают без сбоев. Никаких IRAM_ATTR, никаких прерываний на ESP32.



    🔵 Для продвинутых: правильная библиотека для ESP32

    Хотите использовать ISR напрямую на ESP32 без DimmerLink? Нужна правильная библиотека.


    Вариант A: rbdimmerESP32 ✅ Рекомендуется

    When to use: dual-core ESP32 (standard ESP32, not S2/C3/H2) Library: ESP32 двухъядерный

    rbdimmerESP32 написана специально для ESP32: она автоматически размещает ISR в IRAM и привязывает задачу диммера к Core 0. Ручной IRAM_ATTR не нужен.

    cpp
    // Платформа: двухъядерный ESP32
    // Библиотека: rbdimmerESP32 — https://github.com/robotdyn-dimmer/rbdimmerESP32
    // IRAM_ATTR применяется автоматически внутри библиотеки
    #include "rbdimmerESP32.h"
    #include <WiFi.h>
    #define ZC_PIN   18   // любой GPIO на ESP32
    #define DIM_PIN  19   // любой GPIO на ESP32
    rbdimmer dimmer;
    void setup() {
        Serial.begin(115200);
        dimmer.begin(ZC_PIN, DIM_PIN, 50);  // сеть 50 Гц
        dimmer.setPower(50);                 // яркость 50%
        // WiFi можно запустить после или до инициализации диммера — порядок
        // не критичен с rbdimmerESP32, но это хорошая практика
        WiFi.begin("ssid", "password");
        while (WiFi.status() != WL_CONNECTED) delay(500);
        Serial.println("WiFi подключён, диммер стабилен");
    }
    void loop() {
        // Плавная развёртка яркости — без сбоев при активном 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);
        }
    }

    Ожидаемый результат: Диммер работает стабильно при активном WiFi, MQTT, HTTP — без сбоев. WiFi и диммер работают параллельно без конфликтов.

    Типичные ошибки:

    • Библиотека не установлена: rbdimmerESP32 — отдельная библиотека, не путайте её со старой RBDdimmer. Установите с GitHub или через Arduino Library Manager.
    • Использование на ESP32-S2/C3/H2: Не будет работать. Одноядерные варианты ESP32 не поддерживают ISR-диммирование ни одной библиотекой — используйте DimmerLink.
    • Неправильная частота сети: dimmer.begin(ZC_PIN, DIM_PIN, 50) — третий параметр — частота сети (50 или 60 Гц). Неправильная частота = неправильный тайминг фазовой отсечки.


    Вариант B: ручной патч для старой RBDdimmer

    Когда использовать: вы уже используете старую RBDdimmer и не хотите мигрировать прямо сейчас.

    Это временное решение. Рекомендуется миграция на rbdimmerESP32.

    Найдите исходный файл RBDdimmer.cpp (или .h), найдите функцию-обработчик прерывания перехода через ноль и добавьте IRAM_ATTR:

    cpp
    // В RBDdimmer.cpp — найдите ISR и добавьте IRAM_ATTR:
    // БЫЛО (неправильно для ESP32):
    void zeroCrossISR() {
        // ...
    }
    // СТАЛО (правильно):
    void IRAM_ATTR zeroCrossISR() {
        // ...
    }

    Если ISR передаётся в attachInterrupt() как лямбда или колбэк, убедитесь, что колбэк тоже помечен IRAM_ATTR. В RBDdimmer обычно один основной ISR — найдите его поиском по attachInterrupt или RISING.

    Важно: патч теряется при обновлении библиотеки. Сделайте форк репозитория или мигрируйте на rbdimmerESP32.



    Вариант C: собственная реализация с IRAM_ATTR

    ⚠️ Только в образовательных целях — не для продакшена. Два намеренных упрощения в коде ниже:

    1. delayMicroseconds() внутри ISR блокирует другие прерывания.
    2. Timer API написан для ESP32 Arduino core 2.x — не компилируется на core 3.x (новый API: timerBegin(1000000)).

    Для продакшена используйте rbdimmerESP32 (вариант A).

    Когда использовать: вы хотите понять принцип перед написанием собственной реализации.

    cpp
    // ⚠️ ТОЛЬКО В ОБРАЗОВАТЕЛЬНЫХ ЦЕЛЯХ — прочитайте предупреждение выше
    // Платформа: двухъядерный ESP32, Arduino core 2.x
    // IRAM_ATTR обязателен для ВСЕХ функций, вызываемых из ISR
    #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() в ISR — только для демонстрации
            // В продакшене: используйте второй таймер для закрытия затвора TRIAC
            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 мкс
        // ⚠️ API core 2.x — на core 3.x используйте timerAlarm()
        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 МГц
        timerAttachInterrupt(timer, &onTimer, true);
        attachInterrupt(digitalPinToInterrupt(ZC_PIN), zeroCrossISR, RISING);
    }
    void loop() {
        brightness = 50;
        delay(100);
    }


    ⚠️ Частые ошибки

    • «Работает без WiFi, падает с WiFi»: Это именно проблема IRAM_ATTR. Не ищите причину в коде диммера — проблема в размещении ISR. Переходите на rbdimmerESP32.

    • «Добавил IRAM_ATTR — теперь другая ошибка»: Если ISR вызывает функции без IRAM_ATTR (например, Serial.print()), они вызовут сбой. Все функции, вызываемые из ISR, тоже должны быть IRAM_ATTR или располагаться в IRAM.

    • «Работало, сломалось после обновления SDK»: После обновления ESP32 Arduino core или ESP-IDF требования к IRAM_ATTR стали строже. Переходите на rbdimmerESP32 — она отслеживает совместимость с новыми версиями SDK.

    • «Попробовал ESP32-S2 — rbdimmerESP32 не работает»: ESP32-S2, C3 и H2 — одноядерные. ISR-диммирование на них невозможно ни с одной библиотекой. Единственное решение — DimmerLink.

    • «Попробовал portDISABLE_INTERRUPTS() — помогло временно»: Отключение прерываний нарушает работу WiFi и других системных функций. Это не решение.




    Экспресс-проверка

    Перед публикацией на форуме проверьте:

  • ☐ Какую библиотеку вы используете — старую `RBDdimmer` или новую
  • rbdimmerESP32? Для ESP32 нужна новая.

  • ☐ Какой ESP32 — стандартный (двухъядерный) или S2/C3/H2?
  • Для S2/C3/H2 — только DimmerLink.

  • ☐ В стек-трейсе адрес ISR во flash (`0x400Dxxxx`) или
  • IRAM (0x400Cxxxx)?

  • ☐ Сбой происходит только при активном WiFi или всегда?
  • ☐ Есть ли вызовы функций без `IRAM_ATTR` в ISR (Serial.print и др.)?
  • etc.)?



    Таблица совместимости

    Платформа IRAM_ATTR Работает с WiFi? RBDdimmer (старая)
    ESP32 ❌ нет ⚠️ сбои RBDdimmer + ручной патч
    ESP32 ❌ нет ✅ да rbdimmerESP32
    ESP32 двухъядерный ✅ автоматически ✅ да rbdimmerESP32
    ESP32 двухъядерный DimmerLink
    Любой ESP32 — (без ISR) ✅ да rbdimmerESP32
    Любой ESP32 — (без ISR) ✅ да rbdimmerESP32
    Arduino Uno/Mega — (не требуется) — (нет WiFi) — (no WiFi)



    Связанные проблемы

    • Переход через ноль не определяетсяtroubleshooting/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



    Остались вопросы?

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

    Поделиться этой записью
    Войти оставить комментарий