ESP32 Brownout Detector was triggered: cause e soluzioni
ESP32 Brownout detector was triggered e riavvii continui: alimentatore, cavo, condensatore, codici rst, Guru Meditation, Task WDT ed ESP Exception Decoder.
Il messaggio ESP32 Brownout detector was triggered significa che la tensione di alimentazione della scheda è scesa per un attimo sotto un limite sicuro e che il chip si è riavviato per proteggersi. Il problema quasi mai è nel codice, ma nell'alimentazione: una porta USB debole, un cavo sottile e lungo, un regolatore insufficiente oppure servo e strisce LED alimentati dalla stessa linea dell'ESP32. Il secondo grande motivo per cui un ESP32 continua a riavviarsi sono i crash del software; in questo caso il Monitor seriale mostra righe come Guru Meditation Error, task_wdt o Stack canary.
La buona notizia: l'ESP32 ti dice da solo perché si è riavviato, basta saper leggere i suoi messaggi. In questa guida imparerai prima a riconoscere i codici rst nelle righe di avvio e i messaggi di errore, poi risolverai il brownout una volta per tutte con l'alimentatore, il cavo e un condensatore. Dopo vedremo gli errori Guru Meditation come LoadProhibited e StoreProhibited, l'overflow dello stack, i puntatori nulli e i timeout del watchdog, e seguirai un crash fino alla riga di codice con ESP Exception Decoder. Infine daremo un'occhiata ai pin di strapping (GPIO0, 2, 12 e 15) che possono rovinare l'avvio.

Perché l'ESP32 continua a riavviarsi? Prima leggi il messaggio
Prima di iniziare la diagnosi, apri il Monitor seriale a 115200 baud e premi il pulsante EN della scheda. A ogni avvio il bootloader in ROM dell'ESP32 stampa prima una riga con una data come "ets Jun 8 2016 00:22:57" e poi una riga del tipo rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT). La parte rst indica perché la scheda si è riavviata, la parte boot in quale modalità è partita. Se la scheda è entrata in un ciclo, subito sopra queste righe trovi il messaggio di errore, le ultime parole dell'esecuzione precedente. La tabella riassume i messaggi che vedrai e da dove partire:
| Cosa vedi nel Monitor seriale | Significato | Dove guardare |
|---|---|---|
| Brownout detector was triggered | La tensione di alimentazione è scesa sotto la soglia | Alimentatore, cavo, condensatore |
| Guru Meditation Error: Core 1 panic'ed (LoadProhibited) | Il codice ha tentato di leggere da un indirizzo non valido | Puntatore nullo, limiti di un array |
| Guru Meditation Error: Core 1 panic'ed (StoreProhibited) | Il codice ha tentato di scrivere in un indirizzo non valido | Puntatore nullo, buffer che trabocca |
| Stack canary watchpoint triggered (loopTask) | Lo stack di un task si è riempito | Grandi array locali, ricorsione |
| task_wdt: Task watchdog got triggered | Un task non ha rilasciato la CPU per più di 5 secondi | Cicli senza pause, task bloccati |
| Interrupt wdt timeout on CPU0 / CPU1 | Un interrupt o gli interrupt disattivati hanno superato i 300 ms | ISR lunga, Serial dentro la ISR |
| boot:0x3 (DOWNLOAD_BOOT...) waiting for download | La scheda è partita in modalità programmazione | GPIO0 LOW all'avvio |
| invalid header o flash read err | Non è stato possibile leggere la memoria flash | GPIO12 HIGH all'avvio, caricamento interrotto |
Che cos'è "Brownout detector was triggered" e perché succede?
Un brownout è un breve calo di tensione senza una vera interruzione dell'alimentazione. L'ESP32 contiene un rilevatore di brownout che controlla di continuo l'alimentazione, e nel core Arduino la sua soglia è impostata al livello più basso, circa 2,43 V. Appena la tensione scende sotto questo valore, il chip mette al sicuro la radio e la memoria flash, scrive "Brownout detector was triggered" nel Monitor seriale e si riavvia. Il motivo è semplice: con una tensione bassa la flash può essere letta male, i dati salvati possono corrompersi e la CPU può eseguire istruzioni a caso. Il rilevatore fa ripartire la scheda in modo pulito invece di lasciarla comportarsi in modo imprevedibile. Il modulo ESP32-WROOM-32 è progettato per funzionare tra 3,0 e 3,6 V, quindi quando la linea arriva a 2,43 V è già da tempo fuori dalla zona sicura.
Dopo un messaggio di brownout, la riga di avvio mostra rst:0xc (SW_CPU_RESET) oppure rst:0x3 (SW_RESET) a seconda della versione del core Arduino, perché l'interrupt del rilevatore stampa il messaggio e poi riavvia il chip via software. Non farti ingannare: lo stesso codice annota nella memoria del chip che la causa è stata un brownout, e esp_reset_reason() restituisce ESP_RST_BROWNOUT. Quando interviene il reset per brownout dell'hardware stesso, la riga riporta invece rst:0xf (RTCWDT_BROWN_OUT_RESET). Nei core 3.x, basati su ESP-IDF 5, questa riga è diventata un messaggio di log di livello info (I BOD: Brownout detector was triggered). Poiché le librerie precompilate del core Arduino scrivono solo i log di livello errore, il messaggio di solito non compare affatto e la scheda si riavvia in silenzio; se all'avvio vedi un rst:0x3 o rst:0xc inspiegabile, controlla il motivo del reset con lo sketch di diagnosi più avanti. Per capire perché la tensione cala, bisogna guardare quanta corrente assorbe l'ESP32:
| Stato di funzionamento | Corrente tipica (chip ESP32) |
|---|---|
| Deep sleep (timer RTC attivo) | Circa 10 µA |
| Light sleep | Circa 0,8 mA |
| Wi-Fi spento, CPU a 240 MHz | 30–68 mA |
| Ricezione Wi-Fi (RX) | 95–100 mA |
| Trasmissione Wi-Fi, 802.11b, 19,5 dBm | Circa 240 mA |
| Calibrazione radio all'avvio e momento della connessione | Può superare i 400 mA in picchi di pochi microsecondi |
Questa differenza è il cuore del brownout: finché il tuo codice non accende il Wi-Fi, la scheda lavora tranquilla con circa 50 mA, ma alla riga WiFi.begin() la corrente si moltiplica per cinque o sei in pochi microsecondi. Se alimentatore, cavo e regolatore non riescono a stare dietro a questa richiesta improvvisa, la tensione crolla. Per questo Espressif consiglia per l'ESP32 un alimentatore in grado di fornire almeno 500 mA. Il sintomo è molto familiare: la scheda si avvia, scrive qualche riga nel Monitor seriale, si riavvia proprio quando parte il Wi-Fi o il Bluetooth e tutto diventa un ciclo infinito. Lo stesso può succedere quando si muove un servo, scatta un relè o una striscia LED diventa bianca.
Le cause più comuni del brownout
- Cavo USB sottile o lungo: i fili di alimentazione sottili dei cavi economici possono arrivare a 0,5 Ω tra andata e ritorno. Per la legge di Ohm, un picco di 400 mA perde 0,2 V in un cavo così.
- Porta USB debole o hub: le porte di tastiere e monitor, gli hub non alimentati e le porte frontali di alcuni portatili fanno calare la tensione nei picchi di corrente.
- Caduta del regolatore (dropout): l'AMS1117 presente sulla maggior parte delle schede ha bisogno di 1,0–1,3 V di differenza tra ingresso e uscita. Se la tensione sul pin 5V scende sotto circa 4,3 V, l'uscita non riesce a mantenere 3,3 V.
- Collegare direttamente una batteria: una batteria al litio da 3,7 V sul pin 5V porta l'uscita sotto i 3 V a causa del dropout del regolatore; due pile AA sotto carico scendono presto sotto il minimo dell'ESP32.
- Il pin 3.3V dell'Arduino Uno: da questo pin si consiglia di prelevare solo circa 50 mA; per un ESP32 con il Wi-Fi attivo non basta assolutamente.
- Carichi sulla stessa linea: se servo, relè, buzzer, motori DC o una striscia WS2812B sono alimentati dagli stessi 5 V dell'ESP32, ogni movimento trascina giù la tensione.
- Breadboard e cavetti jumper: contatti allentati e jumper sottili aggiungono un po' di resistenza a ogni collegamento; far passare l'alimentazione dalla breadboard peggiora il problema.
- ESP32-CAM: quando fotocamera, Wi-Fi e LED flash funzionano insieme, la corrente sale molto; questa scheda vuole 5 V e almeno 2 A. Trovi i dettagli nella guida ESP32-CAM.
Soluzione 1: migliora l'alimentatore e il cavo
Il primo passo, e il più efficace, è rinforzare l'alimentazione. Collega la scheda con un cavo dati corto, spesso e che sai essere buono, direttamente a una porta USB sul retro del computer oppure a un alimentatore da 5 V da almeno 1 A. Le porte USB 2.0 sono progettate per dare 500 mA e le USB 3.0 900 mA; i 500 mA consigliati da Espressif sono proprio il limite di una porta USB 2.0, e appena si aggiungono le perdite del cavo iniziano i guai. Se basta cambiare cavo per eliminare l'errore, la colpa era molto probabilmente della sua resistenza. Puoi calcolare quanta tensione perde un cavo con V = I × R nello strumento Legge di Ohm.

| Tipo di alimentazione | Rischio di brownout | Nota |
|---|---|---|
| Cavo corto e spesso e porta USB posteriore | Basso | La soluzione più semplice durante lo sviluppo |
| Cavo lungo e sottile, porta frontale o hub | Alto | La resistenza del cavo fa calare la tensione nei picchi |
| Alimentatore da 5 V e almeno 1 A sul pin 5V o VIN | Basso | Consigliato per i progetti definitivi |
| Sorgente da 3,3 V già pronta direttamente sul pin 3V3 | Dipende dalla sorgente | Deve fornire almeno 500 mA e non superare mai 3,6 V |
| Batteria al litio da 3,7 V tramite l'AMS1117 | Alto | Sotto circa 4 V sulla batteria, l'uscita scende sotto 3 V |
| Batteria al litio con regolatore 3,3 V a bassa caduta (LDO) o buck-boost | Basso | Fornisce 3,3 V stabili anche mentre la batteria si scarica |
| Due pile AA direttamente sul pin 3V3 | Molto alto | Sotto carico la tensione scende in fretta sotto il minimo dell'ESP32 |
Nei progetti a batteria serve attenzione. Una cella al litio come la 18650 dà 4,2 V carica e circa 3,0 V scarica. Questo intervallo non va bene né collegato direttamente al pin 3V3 (4,2 V superano il limite di 3,6 V) né al pin 5V con il suo AMS1117. La soluzione corretta è portare la batteria al pin 5V con un modulo elevatore (boost) da 5 V, oppure alimentare direttamente la linea 3V3 con un LDO a bassa caduta capace di erogare almeno 500 mA (per esempio ME6211 o HT7833) più un condensatore. Puoi stimare quanto durerà il tuo progetto a batteria con lo strumento Durata della batteria e conoscere il circuito di ricarica nella guida al modulo di ricarica TP4056.
Soluzione 2: aggiungi un condensatore tra 3V3 e GND
Un condensatore funziona come un piccolo serbatoio che fornisce energia durante un picco di corrente finché il regolatore e il cavo non recuperano. Metti tra i pin 3V3 e GND, il più vicino possibile alla scheda, un condensatore elettrolitico da 100–470 µF e accanto un condensatore ceramico da 100 nF. L'elettrolitico copre i cali dell'ordine dei millisecondi, il ceramico le oscillazioni brevissime ad alta frequenza. Puoi vederne l'effetto in modo approssimativo con ΔV = I × Δt / C: anche se un condensatore da 470 µF fornisse da solo 200 mA in più per 200 µs, la tensione calerebbe di appena 0,085 V. La scritta 104 su un condensatore ceramico significa 100 nF; puoi decifrare gli altri codici con lo strumento Codice dei condensatori.

Soluzione 3: alimenta separatamente servo, motori e strisce LED
Anche se l'ESP32 è alimentato bene, i carichi collegati alla stessa linea possono far calare la tensione. Perfino un piccolo servo come l'SG90 assorbe qualche centinaio di mA quando inizia a muoversi; un LED WS2812B consuma circa 60 mA a bianco pieno, quindi una striscia da 30 LED può arrivare a 1,8 A. Alimenta questi carichi non dal pin 5V o 3V3 dell'ESP32 ma da una sorgente da 5 V separata; per esempio puoi abbassare un alimentatore da 12 V a 5 V con un modulo step-down LM2596. La regola più importante è collegare tra loro i GND delle due sorgenti; senza una massa comune la linea del segnale non ha un riferimento e il servo o la striscia si comportano a caso. Ne parliamo in dettaglio in Arduino si resetta da solo? e GND e massa comune.

Disattivare il rilevatore di brownout è una soluzione?
La "soluzione" più proposta in rete è spegnere il rilevatore con una sola riga. Il codice qui sotto disattiva il rilevatore azzerando il registro di brownout dell'ESP32, e il messaggio di errore sparisce. Ma i cali di tensione continuano; semplicemente nessuno ti avvisa più. Usa questo codice solo per la diagnosi: se con il rilevatore spento la scheda funziona bene, hai la conferma che il problema è sicuramente l'alimentazione. Poi cancella la riga e sistema l'alimentazione.
1#include "soc/soc.h"
2#include "soc/rtc_cntl_reg.h"
3
4void setup() {
5 // YALNIZ TEŞHİS İÇİN: brownout dedektörünü kapat / DIAGNOSIS ONLY: disable the brownout detector
6 WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
7
8 Serial.begin(115200);
9 Serial.println("Brownout dedektoru kapali / brownout detector disabled");
10}
11
12void loop() {
13}Codici rst: l'ESP32 ti dice perché si è riavviato
Il valore rst: che la ROM stampa a ogni avvio indica come si è conclusa l'esecuzione precedente. In un avvio normale il valore boot è 0x13 (SPI_FAST_FLASH_BOOT), cioè la scheda avvia il programma dalla flash. La tabella riassume i codici che incontrerai più spesso. I codici della ROM non sempre bastano da soli: ESP.restart() e il riavvio dopo un Guru Meditation compaiono entrambi con il codice 0xc, mentre un brownout appare come 0xc o 0x3 a seconda della versione del core. Per distinguerli, usa lo sketch con esp_reset_reason() della prossima sezione.
| Codice rst | Nome | Che cosa significa? |
|---|---|---|
| 0x1 | POWERON_RESET | Alimentazione collegata per la prima volta o pulsante EN premuto |
| 0x3 | SW_RESET | Il software ha resettato l'intero sistema digitale; nelle versioni recenti del core compare anche dopo un brownout |
| 0x5 | DEEPSLEEP_RESET | La scheda si è svegliata dal deep sleep (normale) |
| 0x7 / 0x8 | TG0WDT_SYS_RESET / TG1WDT_SYS_RESET | Un watchdog di un gruppo timer ha resettato il sistema via hardware; gli interrupt sono rimasti disattivati troppo a lungo o non è riuscito a partire nemmeno il gestore di panic |
| 0x9 | RTCWDT_SYS_RESET | Il watchdog dell'RTC ha resettato il core digitale |
| 0xc | SW_CPU_RESET | Riavvio software: ESP.restart(), dopo un Guru Meditation o dopo un brownout nel core Arduino |
| 0xf | RTCWDT_BROWN_OUT_RESET | Reset hardware per brownout: tensione di alimentazione instabile |
| 0x10 | RTCWDT_RTC_RESET | Il watchdog dell'RTC ha resettato tutto il chip, RTC compreso; si vede soprattutto su schede che si bloccano durante l'avvio |
Sketch di diagnosi: stampa il motivo del reset, la memoria e lo stack
Carica il codice qui sotto come sketch separato oppure aggiungi le sue righe al setup() e al loop() del progetto che dà problemi. A ogni avvio la scheda scrive nel Monitor seriale il motivo del reset in testo leggibile, quante volte si è avviata e lo stato della memoria. Il contatore segnato con RTC_DATA_ATTR è salvato nella memoria RTC, quindi sopravvive ai reset software e al deep sleep e si azzera solo quando l'alimentazione viene tolta del tutto. Così, se il contatore sale ogni pochi secondi, capisci subito che la scheda è bloccata in un ciclo di reset.
1#include <esp_system.h>
2
3RTC_DATA_ATTR uint32_t bootCount = 0; // yazılım resetinde korunur / survives software resets
4
5// Reset nedenini okunabilir yazıya çevir / turn the reset reason into readable text
6const char* reasonText(esp_reset_reason_t r) {
7 switch (r) {
8 case ESP_RST_POWERON: return "POWERON: guc acilisi ya da EN / power-on or EN button";
9 case ESP_RST_SW: return "SW: ESP.restart()";
10 case ESP_RST_PANIC: return "PANIC: Guru Meditation";
11 case ESP_RST_INT_WDT: return "INT_WDT: kesme watchdog'u / interrupt watchdog";
12 case ESP_RST_TASK_WDT: return "TASK_WDT: gorev watchdog'u / task watchdog";
13 case ESP_RST_WDT: return "WDT: diger watchdog / other watchdog";
14 case ESP_RST_DEEPSLEEP: return "DEEPSLEEP: derin uykudan uyanma / deep sleep wake-up";
15 case ESP_RST_BROWNOUT: return "BROWNOUT: gerilim dusmesi / supply voltage dropped";
16 default: return "UNKNOWN: bilinmiyor / unknown";
17 }
18}
19
20void setup() {
21 Serial.begin(115200);
22 delay(1000); // Seri Monitör'ün açılmasını bekle / wait for the Serial Monitor
23 bootCount++;
24 Serial.printf("Acilis sayisi / boot count: %u\n", (unsigned)bootCount);
25 Serial.printf("Reset nedeni / reset reason: %s\n", reasonText(esp_reset_reason()));
26}
27
28void loop() {
29 // Bellek durumu / memory status
30 Serial.printf("Bos heap / free heap: %u B, en dusuk / minimum: %u B, en buyuk blok / largest block: %u B\n",
31 (unsigned)ESP.getFreeHeap(), (unsigned)ESP.getMinFreeHeap(), (unsigned)ESP.getMaxAllocHeap());
32 // loopTask yığınında hiç kullanılmamış en küçük alan / smallest unused space on the loopTask stack
33 Serial.printf("loopTask bos yigin / free stack: %u B\n", (unsigned)uxTaskGetStackHighWaterMark(NULL));
34 delay(5000);
35}I valori che il codice stampa nel ciclo rivelano anche problemi silenziosi. Se l'heap libero cala un po' a ogni giro, il tuo programma ha una perdita di memoria e prima o poi la scheda andrà in crash. Se il blocco più grande è molto più piccolo dell'heap libero, la memoria è frammentata; di solito dipende da oggetti String che crescono e si restringono di continuo nel ciclo. Se lo stack libero di loopTask scende sotto qualche centinaio di byte, l'overflow dello stack è dietro l'angolo. Se il motivo del reset è BROWNOUT, concentrati sulle sezioni sull'alimentazione; se è PANIC, TASK_WDT o INT_WDT, sulle sezioni software che seguono.
Che cos'è un Guru Meditation Error? Leggi il report del crash riga per riga
Un Guru Meditation Error è il report di crash che il gestore di panic stampa quando la CPU dell'ESP32 incontra un errore da cui non può riprendersi; il nome viene dalla schermata di errore dei vecchi computer Amiga. A prima vista fa paura, ma contano solo poche righe. Core 1 indica su quale core è avvenuto l'errore: con Arduino, setup() e loop() girano sul core 1, mentre lo stack Wi-Fi gira sul core 0. Il nome tra parentesi è il tipo di eccezione. Alla fine del report la scheda scrive "Rebooting..." e si riavvia; nella riga di avvio vedrai rst:0xc.
| Riga | Significato |
|---|---|
| Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled. | Il codice sul core 1 ha tentato di leggere da un indirizzo non valido e l'errore non è stato gestito |
| PC : 0x400d1a2c | Indirizzo dell'istruzione che ha causato il crash; se inizia con 0x400d, il tuo codice o una libreria girava dalla flash |
| EXCCAUSE: 0x0000001c | Codice dell'eccezione; 0x1c, cioè 28, significa LoadProhibited |
| EXCVADDR: 0x00000000 | Indirizzo a cui si è tentato di accedere; 0 o un numero molto piccolo indica un puntatore nullo |
| Backtrace: 0x400d1a29:0x3ffb2270 0x400d0f6e:0x3ffb2290 ... | Catena delle chiamate; ogni coppia ha la forma PC:SP e il decoder la traduce in una riga di codice |
| ELF file SHA256: ... | Impronta del programma andato in crash; il decoder ha bisogno esattamente della stessa compilazione |
| Rebooting... | Il gestore di panic riavvia il chip via software |
LoadProhibited, StoreProhibited e gli altri tipi di eccezione
| Eccezione | EXCCAUSE | Che cosa è successo? | Causa frequente |
|---|---|---|---|
| LoadProhibited | 28 (0x1c) | Lettura da un indirizzo non valido | Puntatore nullo o non inizializzato, oggetto eliminato |
| StoreProhibited | 29 (0x1d) | Scrittura in un indirizzo non valido | Scrittura tramite un puntatore nullo, buffer che esce dall'array |
| InstrFetchProhibited | 20 (0x14) | Lettura di un'istruzione da un indirizzo non valido | Puntatore a funzione o callback non assegnati |
| IllegalInstruction | 0 (0x0) | Istruzione non valida | Stack corrotto, return dimenticato in una funzione che deve restituire un valore |
| LoadStoreAlignment | 9 (0x9) | Accesso alla memoria non allineato | Convertire a forza un array di byte in un tipo come uint32_t* |
| IntegerDivideByZero | 6 (0x6) | Divisione intera per zero | Dividere per un contatore che può valere zero |
| Unhandled debug exception | - | Eccezione di debug | Stack canary watchpoint triggered: overflow dello stack |
| Interrupt wdt timeout on CPU0/CPU1 | - | Il watchdog degli interrupt è scaduto | ISR lunga, Serial o delay dentro la ISR |
| Cache disabled but cached memory region accessed | - | Accesso a codice in flash mentre la flash era occupata | Funzione di interrupt senza IRAM_ATTR |
Il più comune di questi errori è l'uso di un puntatore nullo. NULL corrisponde all'indirizzo 0 e sull'ESP32 non c'è memoria leggibile intorno all'indirizzo 0, quindi leggere dove punta un puntatore vuoto dà LoadProhibited e scriverci dà StoreProhibited. In questo caso EXCVADDR è un indizio perfetto: 0x00000000 indica il puntatore stesso, mentre valori piccoli come 0x00000004 o 0x00000010 indicano un campo dentro un puntatore vuoto (NULL più la posizione del campo). Nei progetti Arduino la causa più frequente è una funzione che restituisce NULL per dire "non trovato" e quel valore viene usato senza controllo. Nell'esempio qui sotto strstr() restituisce NULL se non trova il testo; senza controllo la scheda va in crash con EXCVADDR 0x00000005.
1const char* response = "temp=23.5;hum=40"; // sunucudan gelen yanıt / reply from a server
2
3void setup() {
4 Serial.begin(115200);
5 delay(500);
6
7 // YANLIŞ: "pres=" yoksa strstr NULL döner ve kart çöker (EXCVADDR 0x00000005)
8 // WRONG: if "pres=" is missing strstr returns NULL and the board crashes (EXCVADDR 0x00000005)
9 // float pressure = atof(strstr(response, "pres=") + 5);
10
11 // DOĞRU: işaretçiyi kullanmadan önce NULL kontrolü yap / RIGHT: check for NULL before using the pointer
12 const char* found = strstr(response, "pres=");
13 if (found != nullptr) {
14 float pressure = atof(found + 5);
15 Serial.println(pressure);
16 } else {
17 Serial.println("pres alani bulunamadi / pres field not found");
18 }
19}
20
21void loop() {
22}Overflow dello stack: l'errore "Stack canary watchpoint triggered"
Sull'ESP32 ogni task ha uno stack di dimensione fissa, dove vengono salvati le variabili locali, le chiamate di funzione e gli indirizzi di ritorno. Con Arduino, il task loopTask che esegue setup() e loop() ha uno stack di 8192 byte. Dichiarare un array locale da 10.000 byte dentro loop(), una funzione che richiama sé stessa (ricorsiva) o una funzione di libreria con molte variabili locali supera questo limite. Alla fine dello stack c'è una zona di guardia (canary); quando lo stack la raggiunge, la scheda scrive Guru Meditation Error: Core 1 panic'ed (Unhandled debug exception) e subito sotto Debug exception reason: Stack canary watchpoint triggered (loopTask). Il nome tra parentesi è il task il cui stack è traboccato. A volte lo stesso problema si presenta come ***ERROR*** A stack overflow in task loopTask has been detected. Ecco come risolverlo:
- Togli i grandi array dallo stack: dichiara l'array come globale o aggiungi static davanti; così si trova nella RAM permanente e non sullo stack.
- Ingrandisci lo stack di loopTask: scrivi SET_LOOP_TASK_STACK_SIZE(16 * 1024); in cima allo sketch per portare lo stack di loop() a 16 KB.
- Dai abbastanza spazio ai tuoi task: sull'ESP32 la dimensione dello stack passata a xTaskCreate e xTaskCreatePinnedToCore è in byte, non in word come in FreeRTOS su altre piattaforme. 1024 byte sono pochi perfino per un task che usa Serial.printf; parti da 4096.
- Misura: uxTaskGetStackHighWaterMark(NULL) restituisce in byte la quantità minima di stack che il task non ha mai usato finora; se scende sotto qualche centinaio di byte, ingrandisci lo stack.
- Evita la ricorsione: trasforma le funzioni che richiamano sé stesse in cicli o limitane la profondità.
Task watchdog: l'errore "task_wdt: Task watchdog got triggered"
Un watchdog (cane da guardia) è un timer che si accorge di un programma bloccato e salva la scheda. Il watchdog dei task (Task WDT) dell'ESP32 si aspetta che i task registrati diano segni di vita a intervalli regolari. Nel core Arduino controlla di default il task inattivo (IDLE) del core 0, con un tempo di 5 secondi. Se un task sul core 0 non rilascia la CPU per 5 secondi, il task IDLE non può girare e nel Monitor seriale compare un messaggio che inizia con E (10257) task_wdt: Task watchdog got triggered. Subito dopo arrivano l'elenco dei task che non hanno azzerato il watchdog in tempo (di solito - IDLE (CPU 0)) e, sotto Tasks currently running:, i task in esecuzione in quel momento. Nel core Arduino questo provoca un panic con la riga task_wdt: Aborting. e la scheda si riavvia.
Le cause più frequenti sono: un ciclo assegnato al core 0 con xTaskCreatePinnedToCore senza nemmeno un vTaskDelay(), un while che aspetta la risposta di un sensore senza timeout, oppure delay() e operazioni lunghe dentro le funzioni callback di librerie come AsyncWebServer. Nell'ultimo caso nell'elenco vedrai il task async_tcp al posto di IDLE. La soluzione è dividere i lavori lunghi in piccoli pezzi e aggiungere ai cicli brevi attese come vTaskDelay(1). La funzione loop() di per sé non è controllata di default; se vuoi, chiama enableLoopWDT() in setup() perché la scheda si riavvii anche quando loop() resta bloccato per più di 5 secondi. Spegnere il watchdog con disableCore0WDT() nasconde l'errore ma non sistema il codice bloccato. Per saperne di più su task e core, leggi FreeRTOS e dual core sull'ESP32. Il codice qui sotto mostra una struttura corretta dei task e le precauzioni contro l'overflow dello stack:
1SET_LOOP_TASK_STACK_SIZE(16 * 1024); // loop() yığını 8 KB yerine 16 KB / loop() stack 16 KB instead of 8 KB
2
3volatile uint32_t sampleCount = 0;
4
5// 0. çekirdekte çalışan görev / task running on core 0
6void sensorTask(void* parameter) {
7 for (;;) {
8 sampleCount = sampleCount + 1; // sensörünü burada oku / read your sensor here
9 vTaskDelay(pdMS_TO_TICKS(10)); // IDLE görevine sıra ver, task_wdt tetiklenmez / let IDLE run, no task_wdt
10 }
11}
12
13void setup() {
14 Serial.begin(115200);
15 // Yığın boyutu bayt cinsindendir: 4096 bayt / stack size is in bytes: 4096 bytes
16 xTaskCreatePinnedToCore(sensorTask, "sensorTask", 4096, NULL, 1, NULL, 0);
17}
18
19void loop() {
20 static uint8_t buffer[10000]; // static: yığında değil kalıcı RAM'de / static: in RAM, not on the stack
21 memset(buffer, 0, sizeof(buffer));
22
23 Serial.printf("Ornek / samples: %u, loopTask bos yigin / free stack: %u B\n",
24 (unsigned)sampleCount, (unsigned)uxTaskGetStackHighWaterMark(NULL));
25 delay(1000);
26}Interrupt wdt timeout e IRAM_ATTR: crash negli interrupt
Il watchdog degli interrupt (Interrupt WDT) è molto più impaziente: se una funzione di interrupt (ISR) o un pezzo di codice con gli interrupt disattivati supera i 300 ms, la scheda va in crash con Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1). Usare Serial.print, delay(), I2C o il Wi-Fi dentro una ISR è la causa più frequente. Un'altra trappola è la memoria flash: mentre Preferences, EEPROM o LittleFS scrivono in flash, la cache della flash viene disattivata per un istante. Se in quel momento arriva un interrupt la cui funzione si trova in flash, la scheda dà l'errore Cache disabled but cached memory region accessed. La soluzione è contrassegnare le ISR con IRAM_ATTR, così stanno in RAM, e limitarsi ad alzare un flag al loro interno; il lavoro vero lo fa loop(). Le variabili condivise tra la ISR e loop() devono essere volatile.
1const int BUTTON_PIN = 27; // buton GPIO 27 ile GND arasında / button between GPIO 27 and GND
2volatile bool buttonPressed = false; // ISR ile loop arasında paylaşılan bayrak / flag shared by the ISR and loop
3uint32_t pressCount = 0;
4
5void IRAM_ATTR onButton() { // IRAM_ATTR: fonksiyon flaşta değil RAM'de durur / the function lives in RAM, not flash
6 buttonPressed = true; // yalnızca bayrak kaldır, Serial yok / only set a flag, no Serial
7}
8
9void setup() {
10 Serial.begin(115200);
11 pinMode(BUTTON_PIN, INPUT_PULLUP);
12 attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), onButton, FALLING);
13}
14
15void loop() {
16 if (buttonPressed) {
17 buttonPressed = false;
18 pressCount++;
19 Serial.printf("Butona basildi / button pressed: %u\n", (unsigned)pressCount); // yavaş iş burada / slow work here
20 }
21}Trasformare il Backtrace in una riga di codice con ESP Exception Decoder
Gli indirizzi nella riga Backtrace da soli non dicono nulla; lo strumento che li traduce in file, funzione e numero di riga del tuo codice sorgente si chiama ESP Exception Decoder. Nel vecchio IDE Arduino 1.8 era un plugin aggiunto al menu Strumenti. Nell'IDE Arduino 2 si usa un'estensione della community con lo stesso nome:
- Scarica il file con estensione .vsix dalla sezione Releases del progetto dankeboy36/esp-exception-decoder su GitHub. Per l'IDE Arduino 2 scegli una versione 1.x; le nuove versioni 2.x sono pensate per VS Code.
- Chiudi l'IDE Arduino e copia il file in %UserProfile%\.arduinoIDE\plugins su Windows oppure in ~/.arduinoIDE/plugins su macOS e Linux; se la cartella non esiste, creala.
- Apri l'IDE e lo sketch che va in crash, compilalo con Verifica e caricalo sulla scheda.
- Apri il Monitor seriale e aspetta che il crash si ripeta.
- Apri la tavolozza dei comandi con Ctrl+Shift+P (Cmd+Shift+P su macOS) ed esegui ESP Exception Decoder: Show Decoder Terminal.
- Copia dal Monitor seriale il blocco Guru Meditation insieme alla riga Backtrace e incollalo nel terminale che si apre; lo strumento mostra ogni indirizzo con nome del file, funzione e numero di riga.
Se non riesci a installare l'estensione, puoi fare lo stesso a mano. Il menu Sketch > Export Compiled Binary lascia un file con estensione .elf nella cartella build all'interno della cartella dello sketch. Il comando xtensa-esp32-elf-addr2line -pfiaC -e sketch.ino.elf 0x400d1a29 0x400d0f6e della toolchain dell'ESP32 traduce questi indirizzi in nomi di funzione e numeri di riga. Se usi PlatformIO, basta aggiungere la riga monitor_filters = esp32_exception_decoder al file platformio.ini; il report del crash viene decodificato automaticamente nel monitor seriale.
Pin di strapping: come GPIO0, GPIO2, GPIO12 e GPIO15 rovinano l'avvio
All'avvio l'ESP32 guarda il livello di alcuni pin per decidere come partire. Si chiamano pin di strapping e conta solo il loro livello nel momento del reset; finito l'avvio, si possono usare come normali GPIO. Il problema nasce quando un sensore, un pulsante o un modulo collegato porta il pin al livello sbagliato durante l'avvio. A quel punto la scheda non avvia il programma, entra in modalità programmazione o non riesce a leggere la flash; da fuori anche questo sembra "l'ESP32 non si avvia" oppure "continua a resettarsi". Puoi vedere la piedinatura completa della tua scheda nello strumento Pinout.
| Pin | Resistenza interna | Ruolo all'avvio | Cosa succede con il livello sbagliato? |
|---|---|---|---|
| GPIO0 | Pull-up | LOW attiva la modalità programmazione (download), HIGH l'avvio normale | boot:0x3 (DOWNLOAD_BOOT) e waiting for download; il programma non parte |
| GPIO2 | Pull-down | Deve essere LOW o scollegato per entrare in programmazione; nell'avvio normale non conta | Un circuito che lo porta a HIGH impedisce il caricamento |
| GPIO12 (MTDI) | Pull-down | Sceglie la tensione della flash: LOW 3,3 V, HIGH 1,8 V | Se HIGH, una flash da 3,3 V non viene alimentata bene; la scheda non parte o entra in un ciclo di reset |
| GPIO15 (MTDO) | Pull-up | Se LOW silenzia i messaggi di avvio della ROM | Le righe di avvio non compaiono nel Monitor seriale e la diagnosi diventa più difficile |
| GPIO5 | Pull-up | Imposta il timing SDIO | Raramente crea problemi sulle schede di sviluppo |
Le schede più recenti hanno pin di strapping diversi: sull'ESP32-S3 influiscono sull'avvio GPIO0, GPIO3, GPIO45 e GPIO46, sull'ESP32-C3 GPIO2, GPIO8 e GPIO9. L'ESP32-S3 usa gli stessi nomi di eccezione Xtensa dell'ESP32 classico. Sull'ESP32-C3 con core RISC-V vedrai invece Load access fault al posto di LoadProhibited e Store access fault al posto di StoreProhibited, e il registro MTVAL prende il ruolo di EXCVADDR. Il messaggio di brownout è uguale su tutti i modelli.
ESP32 e Raspberry Pi Pico con MicroPython
Se programmi l'ESP32 con MicroPython, il brownout si presenta esattamente allo stesso modo, perché il problema è nell'hardware e non nel software. Gli errori nel codice Python invece compaiono come un Traceback leggibile al posto di un Guru Meditation, e la scheda va in crash di rado. Puoi leggere il motivo del reset con machine.reset_cause(), ma attenzione: la versione di MicroPython per ESP32 non restituisce un valore apposito per il brownout, lo segnala come PWRON_RESET insieme all'accensione. Se la scheda riparte di continuo con PWRON_RESET senza che tu la tocchi, sospetta di nuovo l'alimentazione. Il codice qui sotto è per l'ESP32; sul Raspberry Pi Pico il modulo machine conosce meno tipi di reset. Sul Pico il regolatore buck-boost dell'ingresso VSYS funziona con qualsiasi tensione tra 1,8 e 5,5 V, quindi i problemi di brownout sono molto più rari; per conoscere la scheda, leggi Che cos'è il Raspberry Pi Pico?
1import machine, gc, time
2
3# Reset nedenleri (ESP32 için) / reset causes (for ESP32)
4causes = {
5 machine.PWRON_RESET: "PWRON: guc acilisi ya da brownout / power-on or brownout",
6 machine.HARD_RESET: "HARD: cokme ya da machine.reset() / crash or machine.reset()",
7 machine.WDT_RESET: "WDT: watchdog",
8 machine.DEEPSLEEP_RESET: "DEEPSLEEP: derin uykudan uyanma / deep sleep wake-up",
9 machine.SOFT_RESET: "SOFT: Ctrl+D ile yazilim reseti / soft reset with Ctrl+D",
10}
11print("Reset nedeni / reset cause:", causes.get(machine.reset_cause(), "bilinmiyor / unknown"))
12
13while True:
14 gc.collect() # çöp toplayıcıyı çalıştır / run the garbage collector
15 print("Bos RAM / free RAM:", gc.mem_free(), "bayt / bytes")
16 time.sleep(5)Errori frequenti e soluzioni
| Sintomo | Causa probabile | Soluzione |
|---|---|---|
| Brownout detector was triggered all'avvio del Wi-Fi | Cavo, porta o regolatore non reggono il picco di corrente | Cavo corto e spesso, alimentatore da 1 A, 470 µF + 100 nF tra 3V3 e GND |
| Reset quando un servo gira o un relè scatta | Il carico condivide la linea di alimentazione dell'ESP32 | Alimenta il carico con una sorgente da 5 V separata e collega i GND |
| I reset iniziano quando la batteria si scarica un po' | Dropout dell'AMS1117 | Usa un LDO o un convertitore buck-boost |
| Guru Meditation, EXCVADDR 0x00000000 | Puntatore nullo | Controlla il puntatore prima di usarlo |
| Stack canary watchpoint triggered (loopTask) | Lo stack di loop() è pieno | Rendi static i grandi array o usa SET_LOOP_TASK_STACK_SIZE |
| task_wdt: - IDLE (CPU 0) | Ciclo senza pause sul core 0 | Aggiungi vTaskDelay al ciclo |
| Interrupt wdt timeout on CPU1 | ISR troppo lunga | Nella ISR alza solo un flag, usa IRAM_ATTR |
| Scheda muta, boot:0x3 e waiting for download | GPIO0 LOW all'avvio | Togli il circuito su GPIO0 e premi EN |
| La scheda non si avvia affatto, errore di lettura della flash | GPIO12 HIGH all'avvio | Sposta il pull-up o il modulo da GPIO12 a un altro pin |
| Failed to connect to ESP32 durante il caricamento | GPIO2 HIGH o la scheda non entra in programmazione | Libera GPIO2 e tieni premuto BOOT all'inizio del caricamento |
La tua scheda è stabile: e adesso?
- Dispositivo a batteria: con il deep sleep dell'ESP32 addormenta la scheda tra una misura e l'altra e scendi a pochi microampere.
- Aggiornamenti senza fili: carica il codice senza cavo USB con l'OTA dell'ESP32.
- Pannello in tempo reale: segui i dati dei sensori dal vivo nel browser con WebSocket sull'ESP32.
- Invia notifiche: con un bot Telegram con ESP32 ricevi sul telefono il motivo del reset ogni volta che la scheda si riavvia.
- Spettacolo di luci: pilota in sicurezza una striscia WS2812B NeoPixel con un'alimentazione separata.
La maggior parte dei problemi di riavvio dell'ESP32 è un problema di alimentazione oppure qualche riga di codice sbagliata; quando leggi bene il messaggio, entrambi si risolvono in fretta. Per ripassare le basi della scheda, dai un'occhiata a Che cos'è l'ESP32? Nell'app BlueGrays trovi progetti con ESP32 con LED, servo, strisce NeoPixel, connessione Wi-Fi, web server, aggiornamenti OTA e deep sleep, ognuno con schema del circuito e codice passo passo, così puoi provare in progetti reali le tecniche di alimentazione e diagnosi che hai imparato qui.
Domande frequenti
Come si risolve ESP32 Brownout detector was triggered?
Questo errore indica che la tensione di alimentazione dell'ESP32 è scesa per un attimo sotto circa 2,43 V. Usa prima un cavo USB corto e spesso con una sorgente da 5 V da almeno 1 A, poi aggiungi un condensatore elettrolitico da 100–470 µF e uno ceramico da 100 nF tra 3V3 e GND. Alimenta carichi come servo, relè e strisce LED con una sorgente separata e collega i GND tra loro.
Perché il mio ESP32 continua a riavviarsi?
Le cause più comuni sono un'alimentazione insufficiente (brownout), crash del software (Guru Meditation), timeout del watchdog e pin di strapping al livello sbagliato durante l'avvio. Apri il Monitor seriale a 115200 baud e premi EN; l'ultimo messaggio prima del reset e il codice rst all'avvio mostrano la causa. La funzione esp_reset_reason() ti permette anche di leggerla dal codice.
Cosa significa Guru Meditation Error LoadProhibited?
Significa che il codice sull'ESP32 ha tentato di leggere da un indirizzo di memoria non valido. Se EXCVADDR è 0 o un numero molto piccolo, quasi sicuramente è stato usato un puntatore nullo. Incolla la riga Backtrace in ESP Exception Decoder per trovare il file e la riga in cui è avvenuto l'errore.
Si può disattivare il rilevatore di brownout dell'ESP32?
Sì, con la riga WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0), ma così si zittisce solo l'avviso. Poiché i cali di tensione continuano, i dati in flash possono corrompersi e la scheda può andare in crash con altri errori. Usalo solo per poco tempo, per confermare che il problema dipende dall'alimentazione.
Come si risolve ESP32 Task watchdog got triggered?
Questo messaggio indica che un task controllato non ha rilasciato la CPU per 5 secondi; con Arduino l'elenco di solito mostra il task IDLE del core 0. Aggiungi vTaskDelay() ai cicli dei tuoi task, usa dei timeout quando aspetti un sensore ed evita delay() o operazioni lunghe nelle callback del web server. Spegnere il watchdog nasconde l'errore ma non ne corregge la causa.
Che alimentatore serve per un ESP32?
Espressif consiglia per l'ESP32 un alimentatore in grado di fornire almeno 500 mA; in pratica un alimentatore da 5 V e 1 A lascia un margine sicuro. Se usi un ESP32-CAM o aggiungi carichi come servo o strisce LED, scegli una sorgente da 2 A o più e, se possibile, alimenta quei carichi da una linea separata.
Progetti da realizzare dopo questo articolo
Vedi tutti i progetti
LED Lampeggiante con ESP32
Insegna l'uso di base delle uscite GPIO con l'ESP32. Otteniamo un effetto lampeggiante accendendo e spegnendo un LED dal pin digitale. Versione ESP32 del classico progetto blink su Arduino UNO; i comandi di base di blink nell'ambiente Arduino sono gli stessi, ma la disposizione dei pin e i livelli GPIO a 3,3 V dell'ESP32 sono diversi da quelli di Arduino UNO.

Connessione Wi-Fi STA con ESP32 (con display LCD)
In questo progetto colleghiamo l'ESP32 alla rete Wi-Fi domestica in modalità Station (STA) in modo sicuro e tollerante agli errori. Separiamo SSID e password in un file secrets.h per impedire fughe su Git, impostiamo un meccanismo di riconnessione automatica e visualizziamo il livello del segnale RSSI e l'indirizzo IP su un display LCD I2C. Il LED interno funge da indicatore di stato.

Servomotore ESP32 (controllo tramite potenziometro)
In questo progetto controlliamo un servomotore SG90 tramite un potenziometro. Regoliamo l'angolo del servomotore mappando il valore analogico letto dal potenziometro su un intervallo compreso tra 0 e 180 gradi. La libreria ESP32Servo genera segnali per servomotori sui GPIO adatti dell'ESP32 che possono funzionare come uscite; in questo progetto si utilizza GPIO 18. Si tratta di un metodo di controllo fondamentale per bracci robotici, gimbal per telecamere e meccanismi di porte automatiche.

Controllo della striscia LED NeoPixel WS2812B con ESP32
I LED indirizzabili WS2812B (NeoPixel) sono LED RGB intelligenti che possono essere controllati in cascata tramite un unico pin dati. In questo progetto controlleremo una striscia WS2812B da 8 LED utilizzando la libreria Adafruit_NeoPixel per creare un'animazione fluida dell'arcobaleno. Sebbene il livello logico a 3,3 V dell'ESP32 funzioni senza problemi con la maggior parte dei componenti WS2812B, in caso di instabilità mostreremo la soluzione con una resistenza in serie da 470 Ω e un condensat

Server web asincrono ESP32 (con indicazione dell'IP sul display LCD)
In questo progetto trasformiamo l'ESP32 in un server web asincrono che potrete controllare dal vostro telefono o computer collegato alla stessa rete Wi-Fi. Grazie alla libreria ESPAsyncWebServer, il server è in grado di elaborare più richieste contemporaneamente (a differenza del classico WebServer.h, che si blocca su una singola richiesta).

ESP32 Deep Sleep — Risparmio della batteria (OLED + contatore in RTC RAM)
La modalità Deep Sleep dell'ESP32 riduce il consumo di corrente a circa 10 µA spegnendo la CPU e la maggior parte dei dispositivi periferici ed è di fondamentale importanza per i dispositivi IoT alimentati a batteria. In questo progetto utilizziamo tre diverse fonti di risveglio: il timer (15 secondi), un pin esterno (EXT0 — pulsante GPIO 33) e il sensore tattile (T0/GPIO 4). Le variabili contrassegnate con RTC_DATA_ATTR vengono preservate durante la modalità Deep Sleep: il contatore di avvio,

Aggiornamento OTA dell'ESP32 — Installazione wireless del firmware (con display LCD)
In questo progetto imparerai a installare il nuovo firmware sull'ESP32 tramite Wi-Fi, senza bisogno di un cavo USB. Grazie alla libreria ArduinoOTA, l'ESP32 diventa visibile sulla rete e viene automaticamente elencato come bluegrays-ota nella scheda "Network port" dell'IDE di Arduino. Il primo caricamento avviene tramite USB, mentre tutti gli aggiornamenti successivi saranno wireless — una caratteristica fondamentale per i dispositivi sul campo. Sul display LCD I2C vengono visualizzati, sia

Sistema di Allarme Movimento ESP32 (PIR + Buzzer + LED + Telegram)
Un sistema di allarme di sicurezza intelligente che attiva buzzer e LED quando il sensore PIR rileva movimento, inviando contemporaneamente una notifica istantanea al proprietario tramite Telegram. Dispone di un tempo di attesa di 30 secondi per limitare la frequenza degli allarmi ripetuti; questo meccanismo non verifica la validità dei rilevamenti del sensore PIR né elimina le false attivazioni. Adatto a sicurezza domestica, monitoraggio di garage, magazzini o studi. Con l'ESP32-CAM è possibile
Altri articoli
App BlueGrays
Costruisci questo circuito passo passo sul telefono
Schema, codice ed elenco componenti in un’unica schermata. Oltre 200 progetti Arduino, ESP32 e Pico da realizzare.
Inquadra con il telefono
Punta la fotocamera sul codice: si apre lo store del tuo telefono.
oppure apri lo store

