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 doble núcleo 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.
Descripción del problema
Conectaste un dimmer AC a un ESP32 y el código de ejemplo funciona bien —
estable sin WiFi. Pero en cuanto activas WiFi (WiFi.begin()) o inicias
transferencia activa de datos (MQTT, HTTP, OTA), el ESP32 se congela o
reinicia con un core panic.
Patrón típico: aparece un stack trace en el monitor serie, luego un reinicio. Desencadenantes más comunes:
- activar WiFi después de iniciar el dimmer
- MQTT activo (publish/subscribe frecuentes)
- actualización OTA de firmware
- uso de la biblioteca antigua
RBDdimmeren ESP32
Mensajes de error típicos:
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)Síntomas típicos:
- El dimmer es estable sin WiFi, se cuelga en segundos con WiFi activo
- Los fallos ocurren exactamente durante actividad WiFi (MQTT publish, solicitud HTTP, OTA)
- La dirección del stack trace está cerca de
0x400Dxxxx(zona flash) en vez de0x400Cxxxx(zona IRAM) - Reinicios aleatorios bajo carga normal
- Los fallos cesan al eliminar
attachInterrupt()para el cruce por cero
Causa raíz
El ESP32 almacena el código del programa en memoria flash SPI externa. En operación normal, la flash se lee a través del caché de la CPU — rápido y transparente para el programador.
Sin embargo, algunas operaciones requieren acceso exclusivo al bus flash:
- Escritura/borrado de flash (NVS, SPIFFS, LittleFS)
- Actualización OTA
- WiFi: ciertas operaciones del stack (carga de certificados, NVS)
- Otras operaciones internas de ESP-IDF
Durante estas operaciones, el ESP32 desactiva temporalmente el caché flash
y bloquea la ejecución de cualquier código que resida en flash. Si una
interrupción se dispara (por ej., cruce por cero) y su manejador (ISR)
también está en flash — el procesador no puede ejecutar la ISR y lanza una
excepción «Cache disabled but cached memory region accessed».
Solución ESP-IDF: el código ISR debe estar en IRAM (RAM interna,
siempre accesible). Coloque el atributo IRAM_ATTR antes de la declaración
de la función:
void IRAM_ATTR zeroCrossISR() {
// este manejador reside en IRAM, no en flash
// se ejecuta incluso cuando flash está desactivada
}Adicionalmente, para un timing confiable, la tarea del dimmer debe ejecutarse
en el Core 0 (xTaskCreatePinnedToCore(..., 0)). rbdimmerESP32 lo hace
automáticamente.
Problema con la antigua RBDdimmer: fue escrita antes de que los
requisitos específicos del ESP32 fueran ampliamente conocidos. Sus manejadores
ISR carecen de IRAM_ATTR y se ejecutan desde flash. En ESP32 sin WiFi
esto funciona — la flash no se bloquea. Con WiFi activado, provoca fallos
periódicos.
Soluciones
🟢 Para principiantes: DimmerLink — sin ISR
¿No quieres lidiar con IRAM_ATTR, interrupciones y particularidades del ESP32? DimmerLink maneja el cruce por cero y el control del TRIAC internamente — tu ESP32 solo envía un comando de nivel de brillo. Funciona en cualquier modelo de ESP32.
Cualquier 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.
Cuándo elegir esta solución:
ISR es compatible)
Cableado:
- DimmerLink → ESP32: SDA → GPIO 21, SCL → GPIO 22, VCC → 3.3V, GND → GND
- DimmerLink → dimmer AC: según el diagrama de conexión de DimmerLink
Código (I2C, recomendado):
// DimmerLink — control de dimmer AC vía I2C
// ESP32 no usa interrupciones — sin conflicto con WiFi
// Documentación: https://www.rbdimmer.com/docs/dimmerlink-I2CCommunication
#include <Wire.h>
#include <WiFi.h>
#include <PubSubClient.h> // ejemplo: MQTT + dimmer sin fallos
#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% de brillo inmediatamente
}
void loop() {
// MQTT, HTTP, OTA — todo funciona sin conflictos con el dimmer
}Resultado: Dimmer + WiFi + MQTT funcionan sin fallos. Sin IRAM_ATTR, sin interrupciones en el ESP32.
🔵 Para usuarios avanzados: la biblioteca correcta para ESP32
¿Quieres usar ISR directamente en ESP32 sin DimmerLink? Necesitas la biblioteca correcta.
Opción A: rbdimmerESP32 ✅ Recomendado
When to use: dual-core ESP32 (standard ESP32, not S2/C3/H2) Library: ESP32 doble núcleo
rbdimmerESP32 está escrita específicamente para ESP32: ubica la ISR en IRAM
automáticamente y fija la tarea del dimmer al Core 0. No se necesita
IRAM_ATTR manual.
// Plataforma: ESP32 de doble núcleo
// Biblioteca: rbdimmerESP32 — https://github.com/robotdyn-dimmer/rbdimmerESP32
// IRAM_ATTR se aplica automáticamente dentro de la biblioteca
#include "rbdimmerESP32.h"
#include <WiFi.h>
#define ZC_PIN 18 // cualquier GPIO en ESP32
#define DIM_PIN 19 // cualquier GPIO en ESP32
rbdimmer dimmer;
void setup() {
Serial.begin(115200);
dimmer.begin(ZC_PIN, DIM_PIN, 50); // red eléctrica 50 Hz
dimmer.setPower(50); // 50% de brillo
// WiFi puede iniciarse después o antes de la inicialización del dimmer —
// el orden no es crítico con rbdimmerESP32, pero es buena práctica
WiFi.begin("ssid", "password");
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.println("WiFi conectado, dimmer estable");
}
void loop() {
// Barrido suave de brillo — sin fallos con WiFi activo
for (int p = 10; p <= 95; p++) {
dimmer.setPower(p);
delay(30);
}
for (int p = 95; p >= 10; p--) {
dimmer.setPower(p);
delay(30);
}
}Resultado esperado: El dimmer funciona de forma estable con WiFi, MQTT, HTTP activos — sin fallos. WiFi y dimmer funcionan en paralelo sin conflictos.
Errores comunes:
- Biblioteca no instalada:
rbdimmerESP32es una biblioteca separada — no la confundas con la antiguaRBDdimmer. Instala desde GitHub o el gestor de bibliotecas de Arduino. - Uso en ESP32-S2/C3/H2: No funcionará. Las variantes de núcleo único de ESP32 no soportan atenuación por ISR con ninguna biblioteca — usa DimmerLink.
- Frecuencia de red incorrecta:
dimmer.begin(ZC_PIN, DIM_PIN, 50)— el tercer parámetro es la frecuencia de red (50 o 60 Hz). Frecuencia incorrecta = temporización de corte de fase incorrecta.
Opción B: parche manual para la antigua RBDdimmer
Cuándo usar: ya estás usando la antigua RBDdimmer y no quieres migrar
ahora mismo.
Esta es una solución temporal. Se recomienda migrar a rbdimmerESP32.
Busca el archivo fuente RBDdimmer.cpp (o .h), localiza la función
manejadora de interrupción de cruce por cero y agrega IRAM_ATTR:
// En RBDdimmer.cpp — busca la ISR y agrega IRAM_ATTR:
// ANTES (incorrecto para ESP32):
void zeroCrossISR() {
// ...
}
// DESPUÉS (correcto):
void IRAM_ATTR zeroCrossISR() {
// ...
}Si la ISR se pasa a attachInterrupt() como lambda o callback, asegúrate de
que el callback también tenga IRAM_ATTR. En RBDdimmer generalmente hay una
ISR principal — encuéntrala buscando attachInterrupt o RISING.
Importante: el parche se pierde al actualizar la biblioteca. Haz un fork
del repositorio o migra a rbdimmerESP32.
Opción C: implementación personalizada con IRAM_ATTR
⚠️ Solo con fines educativos — no para producción. Dos simplificaciones intencionales en el código siguiente:
delayMicroseconds()dentro de la ISR bloquea otras interrupciones.- La API de Timer está escrita para ESP32 Arduino core 2.x — no compila en core 3.x (nueva API:
timerBegin(1000000)).Para producción, usa
rbdimmerESP32(Opción A).
Cuándo usar: quieres entender el principio antes de escribir tu propia implementación.
// ⚠️ SOLO CON FINES EDUCATIVOS — lee la advertencia anterior
// Plataforma: ESP32 de doble núcleo, Arduino core 2.x
// IRAM_ATTR es obligatorio para TODAS las funciones llamadas desde una 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() en ISR — solo para demostración
// En producción: usa un segundo timer para cerrar la compuerta del 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 µs
// ⚠️ API core 2.x — en core 3.x usa 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); // reloj 1 MHz
timerAttachInterrupt(timer, &onTimer, true);
attachInterrupt(digitalPinToInterrupt(ZC_PIN), zeroCrossISR, RISING);
}
void loop() {
brightness = 50;
delay(100);
}⚠️ Errores comunes
-
«Funciona sin WiFi, se cuelga con WiFi»: Esto es exactamente el problema de IRAM_ATTR. No busques la causa en tu código del dimmer — el problema es la ubicación de la ISR. Cambia a
rbdimmerESP32. -
«Agregué IRAM_ATTR — ahora un error diferente»: Si la ISR llama funciones sin
IRAM_ATTR(por ej.,Serial.print()), esas llamadas provocarán el fallo. Todas las funciones llamadas desde una ISR también deben serIRAM_ATTRo estar ubicadas en IRAM. -
«Funcionaba, se rompió después de actualizar el SDK»: Después de actualizar el core Arduino ESP32 o ESP-IDF, los requisitos de IRAM_ATTR se volvieron más estrictos. Cambia a
rbdimmerESP32— sigue la compatibilidad con nuevas versiones del SDK. -
«Probé ESP32-S2 — rbdimmerESP32 no funciona»: ESP32-S2, C3 y H2 son de núcleo único. La atenuación por ISR es imposible con cualquier biblioteca. La única solución es DimmerLink.
-
«Probé
portDISABLE_INTERRUPTS()— ayudó temporalmente»: Desactivar interrupciones rompe WiFi y otras funciones del sistema. No es una solución.
Verificación rápida
Antes de publicar en el foro, verifica:
Para ESP32 necesitas la nueva.
Para S2/C3/H2 — solo DimmerLink.
IRAM (0x400Cxxxx)?
etc.)?
Tabla de compatibilidad
| Plataforma | Plataforma | ¿Funciona con WiFi? | RBDdimmer (antigua) |
|---|---|---|---|
| ESP32 | ❌ no | ⚠️ fallos | RBDdimmer + parche manual |
| ESP32 | ❌ no | ✅ sí | rbdimmerESP32 |
| ESP32 doble núcleo | ✅ automático | ✅ sí | rbdimmerESP32 |
| ESP32 doble núcleo | — | — | DimmerLink |
| Cualquier ESP32 | — (sin ISR) | ✅ sí | rbdimmerESP32 |
| Cualquier ESP32 | — (sin ISR) | ✅ sí | rbdimmerESP32 |
| Arduino Uno/Mega | — (no necesario) | — (sin WiFi) | — (no WiFi) |
Problemas relacionados
- Cruce por cero no detectado →
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
¿Todavía tienes preguntas?
Ask on forum.rbdimmer.com or open a GitHub Issue.