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_ATTRand 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 handlesIRAM_ATTRautomatically 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 oldRBDdimmeron ESP32 requires a manual patch.
Описание проблемы
Вы подключили AC-диммер к ESP32, и демонстрационный код работает нормально —
стабильно без WiFi. Но как только вы включаете WiFi (WiFi.begin()) или
начинаете активную передачу данных (MQTT, HTTP, OTA), ESP32 зависает или
перезагружается с паникой ядра.
Типичный паттерн: в Serial-мониторе появляется стек-трейс, затем перезагрузка. Наиболее частые триггеры:
- включение WiFi после запуска диммера
- активная работа MQTT (частые publish/subscribe)
- OTA-обновление прошивки
- использование старой библиотеки
RBDdimmerна ESP32
Типичные сообщения об ошибках:
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 перед объявлением функции:
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.
Когда выбрать это решение:
библиотека ISR-диммирования не поддерживается)
Подключение:
- DimmerLink → ESP32: SDA → GPIO 21, SCL → GPIO 22, VCC → 3.3V, GND → GND
- DimmerLink → AC-диммер: по схеме подключения DimmerLink
Код (I2C, рекомендуется):
// 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 не нужен.
// Платформа: двухъядерный 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:
// В RBDdimmer.cpp — найдите ISR и добавьте IRAM_ATTR:
// БЫЛО (неправильно для ESP32):
void zeroCrossISR() {
// ...
}
// СТАЛО (правильно):
void IRAM_ATTR zeroCrossISR() {
// ...
}Если ISR передаётся в attachInterrupt() как лямбда или колбэк,
убедитесь, что колбэк тоже помечен IRAM_ATTR. В RBDdimmer обычно
один основной ISR — найдите его поиском по attachInterrupt или RISING.
Важно: патч теряется при обновлении библиотеки. Сделайте форк
репозитория или мигрируйте на rbdimmerESP32.
Вариант C: собственная реализация с IRAM_ATTR
⚠️ Только в образовательных целях — не для продакшена. Два намеренных упрощения в коде ниже:
delayMicroseconds()внутри ISR блокирует другие прерывания.- Timer API написан для ESP32 Arduino core 2.x — не компилируется на core 3.x (новый API:
timerBegin(1000000)).Для продакшена используйте
rbdimmerESP32(вариант A).
Когда использовать: вы хотите понять принцип перед написанием собственной реализации.
// ⚠️ ТОЛЬКО В ОБРАЗОВАТЕЛЬНЫХ ЦЕЛЯХ — прочитайте предупреждение выше
// Платформа: двухъядерный 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 и других системных функций. Это не решение.
Экспресс-проверка
Перед публикацией на форуме проверьте:
rbdimmerESP32? Для ESP32 нужна новая.
Для S2/C3/H2 — только DimmerLink.
IRAM (0x400Cxxxx)?
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 Edge →
load-types/trailing-vs-leading-edge.md - Trailing vs Leading Edge →
load-types/trailing-vs-leading-edge.md
Остались вопросы?
Ask on forum.rbdimmer.com or open a GitHub Issue.