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 rbdimmerESP32 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.
Descrizione del problema
Hai collegato un dimmer AC a un ESP32 e il codice di esempio funziona bene —
stabile senza WiFi. Ma appena abiliti il WiFi (WiFi.begin()) o avvii un
trasferimento dati attivo (MQTT, HTTP, OTA), l'ESP32 si blocca o si riavvia
con un core panic.
Schema tipico: uno stack trace appare nel monitor seriale, poi un riavvio. Cause scatenanti più comuni:
- abilitazione del WiFi dopo l'avvio del dimmer
- MQTT attivo (publish/subscribe frequenti)
- aggiornamento OTA del firmware
- utilizzo della vecchia libreria
RBDdimmersu ESP32
Messaggi di errore tipici:
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)Sintomi tipici:
- Il dimmer è stabile senza WiFi, va in crash in pochi secondi con il WiFi attivo
- I crash avvengono esattamente durante l'attività WiFi (MQTT publish, richiesta HTTP, OTA)
- L'indirizzo nello stack trace è vicino a
0x400Dxxxx(zona flash) invece di0x400Cxxxx(zona IRAM) - Riavvii casuali sotto carico normale
- I crash cessano quando
attachInterrupt()per il passaggio per lo zero viene rimosso
Causa profonda
L'ESP32 memorizza il codice del programma nella memoria flash SPI esterna. In condizioni normali la flash viene letta tramite la cache della CPU — velocemente e in modo trasparente per il programmatore.
Tuttavia, alcune operazioni richiedono accesso esclusivo al bus flash:
- Scrittura/cancellazione flash (NVS, SPIFFS, LittleFS)
- Aggiornamento OTA
- WiFi: alcune operazioni dello stack (caricamento certificati, NVS)
- Altre operazioni interne ESP-IDF
Durante queste operazioni l'ESP32 disabilita temporaneamente la cache flash
e blocca l'esecuzione di qualsiasi codice che risiede nella flash. Se un
interrupt scatta (ad es., passaggio per lo zero) e il suo gestore (ISR) è
anch'esso nella flash — il processore non può eseguire l'ISR e genera
l'eccezione «Cache disabled but cached memory region accessed».
Soluzione ESP-IDF: il codice ISR deve trovarsi in IRAM (RAM interna,
sempre accessibile). Metti l'attributo IRAM_ATTR prima della dichiarazione
della funzione:
void IRAM_ATTR zeroCrossISR() {
// questo gestore risiede in IRAM, non nella flash
// viene eseguito anche quando la flash è disabilitata
}Inoltre, per un timing affidabile il task del dimmer dovrebbe essere eseguito
sul Core 0 (xTaskCreatePinnedToCore(..., 0)). rbdimmerESP32 lo fa
automaticamente.
Problema con la vecchia RBDdimmer: è stata scritta prima che i requisiti
specifici dell'ESP32 fossero ampiamente conosciuti. I suoi gestori ISR
mancano di IRAM_ATTR e vengono eseguiti dalla flash. Su ESP32 senza WiFi
questo funziona — la flash non è bloccata. Con il WiFi abilitato, provoca
crash periodici.
Soluzioni
🟢 Per principianti: DimmerLink — nessun ISR
Non vuoi occuparti di IRAM_ATTR, interrupt e particolarità dell'ESP32? DimmerLink gestisce il passaggio per lo zero e il controllo del TRIAC internamente — il tuo ESP32 invia semplicemente un comando di livello di luminosità. Funziona su qualsiasi modello di ESP32.
DimmerLink 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.
Quando scegliere questa soluzione:
ISR è supportata)
Cablaggio:
- DimmerLink → ESP32: SDA → GPIO 21, SCL → GPIO 22, VCC → 3.3V, GND → GND
- DimmerLink → dimmer AC: secondo lo schema di collegamento DimmerLink
Codice (I2C, raccomandato):
// DimmerLink — controllo dimmer AC tramite I2C
// L'ESP32 non usa interrupt — nessun conflitto con il WiFi
// Documentazione: https://www.rbdimmer.com/docs/dimmerlink-I2CCommunication
#include <Wire.h>
#include <WiFi.h>
#include <PubSubClient.h> // esempio: MQTT + dimmer senza crash
#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% di luminosità immediatamente
}
void loop() {
// MQTT, HTTP, OTA — tutto funziona senza conflitti con il dimmer
}Risultato: Dimmer + WiFi + MQTT funzionano senza crash. Nessun IRAM_ATTR, nessun interrupt sull'ESP32.
🔵 Per utenti avanzati: la libreria corretta per ESP32
Vuoi usare gli ISR direttamente su ESP32 senza DimmerLink? Hai bisogno della libreria giusta.
Opzione A: rbdimmerESP32 ✅ Raccomandato
When to use: dual-core ESP32 (standard ESP32, not S2/C3/H2) Library: rbdimmerESP32
rbdimmerESP32 è scritta specificamente per ESP32: posiziona l'ISR in IRAM
automaticamente e assegna il task del dimmer al Core 0. Nessun IRAM_ATTR
manuale necessario.
// Piattaforma: ESP32 dual-core
// Libreria: rbdimmerESP32 — https://github.com/robotdyn-dimmer/rbdimmerESP32
// IRAM_ATTR viene applicato automaticamente all'interno della libreria
#include "rbdimmerESP32.h"
#include <WiFi.h>
#define ZC_PIN 18 // qualsiasi GPIO su ESP32
#define DIM_PIN 19 // qualsiasi GPIO su ESP32
rbdimmer dimmer;
void setup() {
Serial.begin(115200);
dimmer.begin(ZC_PIN, DIM_PIN, 50); // rete 50 Hz
dimmer.setPower(50); // 50% di luminosità
// Il WiFi può essere avviato dopo o prima dell'inizializzazione del dimmer —
// l'ordine non è critico con rbdimmerESP32, ma è buona pratica
WiFi.begin("ssid", "password");
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.println("WiFi connesso, dimmer stabile");
}
void loop() {
// Sweep graduale della luminosità — nessun crash con WiFi attivo
for (int p = 10; p <= 95; p++) {
dimmer.setPower(p);
delay(30);
}
for (int p = 95; p >= 10; p--) {
dimmer.setPower(p);
delay(30);
}
}Risultato atteso: Il dimmer funziona stabilmente con WiFi, MQTT, HTTP attivi — nessun crash. WiFi e dimmer funzionano in parallelo senza conflitti.
Errori comuni:
- Libreria non installata:
rbdimmerESP32è una libreria separata — non confonderla con la vecchiaRBDdimmer. Installa da GitHub o dal gestore delle librerie Arduino. - Utilizzo su ESP32-S2/C3/H2: Non funzionerà. Le varianti single-core di ESP32 non supportano la dimmerizzazione ISR con nessuna libreria — usa DimmerLink.
- Frequenza di rete errata:
dimmer.begin(ZC_PIN, DIM_PIN, 50)— il terzo parametro è la frequenza di rete (50 o 60 Hz). Frequenza errata = temporizzazione di taglio di fase errata.
Opzione B: patch manuale per la vecchia RBDdimmer
Quando usare: stai già utilizzando la vecchia RBDdimmer e non vuoi
migrare subito.
Questa è una soluzione temporanea. Si raccomanda la migrazione a
rbdimmerESP32.
Trova il file sorgente RBDdimmer.cpp (o .h), individua la funzione
gestore dell'interrupt di passaggio per lo zero e aggiungi IRAM_ATTR:
// In RBDdimmer.cpp — trova l'ISR e aggiungi IRAM_ATTR:
// PRIMA (errato per ESP32):
void zeroCrossISR() {
// ...
}
// DOPO (corretto):
void IRAM_ATTR zeroCrossISR() {
// ...
}Se l'ISR viene passato ad attachInterrupt() come lambda o callback,
assicurati che anche il callback sia IRAM_ATTR. In RBDdimmer di solito
c'è un ISR principale — trovalo cercando attachInterrupt o RISING.
Importante: la patch viene persa all'aggiornamento della libreria. Fai un
fork del repository o migra a rbdimmerESP32.
Opzione C: implementazione personalizzata con IRAM_ATTR
⚠️ Solo a scopo educativo — non per la produzione. Due semplificazioni intenzionali nel codice seguente:
delayMicroseconds()dentro l'ISR blocca gli altri interrupt.- L'API Timer è scritta per ESP32 Arduino core 2.x — non compila su core 3.x (nuova API:
timerBegin(1000000)).Per la produzione, usa
rbdimmerESP32(Opzione A).
Quando usare: vuoi capire il principio prima di scrivere la tua implementazione.
// ⚠️ SOLO A SCOPO EDUCATIVO — leggi l'avviso sopra
// Piattaforma: ESP32 dual-core, Arduino core 2.x
// IRAM_ATTR è obbligatorio per TUTTE le funzioni chiamate da un 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() nell'ISR — solo per dimostrazione
// In produzione: usa un secondo timer per chiudere il gate 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 — su 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); // clock 1 MHz
timerAttachInterrupt(timer, &onTimer, true);
attachInterrupt(digitalPinToInterrupt(ZC_PIN), zeroCrossISR, RISING);
}
void loop() {
brightness = 50;
delay(100);
}⚠️ Errori comuni
-
«Funziona senza WiFi, va in crash con WiFi»: Questo è esattamente il problema IRAM_ATTR. Non cercare la causa nel codice del dimmer — il problema è il posizionamento dell'ISR. Passa a
rbdimmerESP32. -
«Aggiunto IRAM_ATTR — ora un errore diverso»: Se l'ISR chiama funzioni senza
IRAM_ATTR(ad es.,Serial.print()), quelle chiamate causeranno il crash. Tutte le funzioni chiamate da un ISR devono anch'esse avereIRAM_ATTRo risiedere in IRAM. -
«Funzionava, si è rotto dopo l'aggiornamento del SDK»: Dopo l'aggiornamento dell'Arduino core ESP32 o ESP-IDF, i requisiti di IRAM_ATTR sono diventati più stringenti. Passa a
rbdimmerESP32— segue la compatibilità con le nuove versioni del SDK. -
«Provato ESP32-S2 — rbdimmerESP32 non funziona»: ESP32-S2, C3 e H2 sono single-core. La dimmerizzazione ISR è impossibile su di essi con qualsiasi libreria. L'unica soluzione è DimmerLink.
-
«Provato
portDISABLE_INTERRUPTS()— ha aiutato temporaneamente»: Disabilitare gli interrupt compromette il WiFi e le altre funzioni di sistema. Non è una soluzione.
Controllo rapido
Prima di pubblicare sul forum, verifica:
rbdimmerESP32? Per ESP32 serve la nuova.
Per S2/C3/H2 — solo DimmerLink.
in IRAM (0x400Cxxxx)?
ecc.)?
Tabella di compatibilità
| Libreria / metodo | Piattaforma | IRAM_ATTR | Funziona con WiFi? |
|---|---|---|---|
| RBDdimmer (vecchia) | ESP32 | ❌ no | ⚠️ crash |
| RBDdimmer + patch manuale | ESP32 | ✅ manualmente | ✅ sì |
| rbdimmerESP32 | ESP32 dual-core | ✅ automatico | ✅ sì |
| rbdimmerESP32 | ESP32-S2/C3/H2 | — | ❌ non supportato |
| DimmerLink | Qualsiasi ESP32 | — (nessun ISR) | ✅ sì |
| DimmerLink | Raspberry Pi | — (nessun ISR) | ✅ sì |
| RBDdimmer | Arduino Uno/Mega | — (non necessario) | — (no WiFi) |
Problemi correlati
- Passaggio per lo zero non rilevato →
troubleshooting/zero-cross-detection-errors.md - Sfarfallio LED con dimmer →
load-types/led-flicker-triac-dimmer.md - Trailing vs Leading Edge →
load-types/trailing-vs-leading-edge.md
Hai ancora domande?
Ask on forum.rbdimmer.com or open a GitHub Issue.