ESP32 Brownout Detector was triggered y Guru Meditation: solución
ESP32 Brownout detector was triggered y reinicios constantes: fuente, cable y condensador, códigos rst, Guru Meditation, Task WDT y ESP Exception Decoder.
El mensaje ESP32 Brownout detector was triggered indica que la tensión de alimentación de la placa cayó por un instante por debajo de un límite seguro y que el chip se reinició para protegerse. El problema casi nunca está en el código, sino en la alimentación: un puerto USB débil, un cable fino y largo, un regulador insuficiente o servos y tiras LED alimentados desde la misma línea que el ESP32. La segunda gran razón por la que un ESP32 se reinicia una y otra vez son los fallos de software; en ese caso el Monitor Serie muestra líneas como Guru Meditation Error, task_wdt o Stack canary.
La buena noticia: el ESP32 te dice por qué se reinició, solo tienes que saber leer sus mensajes. En esta guía primero aprenderás a reconocer los códigos rst de las líneas de arranque y los mensajes de error, y después resolverás el brownout de forma definitiva con la fuente, el cable y un condensador. Luego veremos los errores Guru Meditation como LoadProhibited y StoreProhibited, el desbordamiento de pila, los punteros nulos y los tiempos de espera del watchdog, y seguirás un fallo hasta la línea exacta de código con ESP Exception Decoder. Por último revisaremos los pines de arranque (strapping) GPIO0, 2, 12 y 15, que pueden estropear el arranque.

¿Por qué el ESP32 se reinicia constantemente? Lee primero el mensaje
Antes de empezar el diagnóstico, abre el Monitor Serie a 115200 baudios y pulsa el botón EN de la placa. En cada arranque, el cargador de arranque de la ROM del ESP32 imprime primero una línea con una fecha como "ets Jun 8 2016 00:22:57" y luego una línea del tipo rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT). La parte rst indica por qué se reinició la placa y la parte boot, en qué modo arrancó. Si la placa entró en un bucle, justo encima de estas líneas encontrarás el mensaje de error, las últimas palabras de la ejecución anterior. La tabla resume los mensajes que verás y por dónde empezar:
| Lo que ves en el Monitor Serie | Significado | Dónde buscar |
|---|---|---|
| Brownout detector was triggered | La tensión de alimentación cayó por debajo del umbral | Fuente, cable, condensador |
| Guru Meditation Error: Core 1 panic'ed (LoadProhibited) | El código intentó leer de una dirección no válida | Puntero nulo, límites de un array |
| Guru Meditation Error: Core 1 panic'ed (StoreProhibited) | El código intentó escribir en una dirección no válida | Puntero nulo, búfer desbordado |
| Stack canary watchpoint triggered (loopTask) | La pila (stack) de una tarea se llenó | Arrays locales grandes, recursión |
| task_wdt: Task watchdog got triggered | Una tarea no liberó la CPU durante más de 5 segundos | Bucles sin pausas, tareas bloqueadas |
| Interrupt wdt timeout on CPU0 / CPU1 | Una interrupción o las interrupciones desactivadas superaron los 300 ms | ISR larga, Serial dentro de la ISR |
| boot:0x3 (DOWNLOAD_BOOT...) waiting for download | La placa arrancó en modo programación | GPIO0 en LOW al arrancar |
| invalid header o flash read err | No se pudo leer la memoria flash | GPIO12 en HIGH al arrancar, carga interrumpida |
¿Qué es "Brownout detector was triggered" y por qué ocurre?
Un brownout es una caída breve de la tensión sin llegar a un corte total. El ESP32 tiene un detector de brownout que vigila continuamente la alimentación, y en el núcleo de Arduino su umbral está en el nivel más bajo, unos 2,43 V. En cuanto la tensión baja de ese valor, el chip protege la radio y la memoria flash, escribe "Brownout detector was triggered" en el Monitor Serie y se reinicia. La razón es sencilla: con poca tensión la flash puede leerse mal, los datos guardados pueden corromperse y la CPU puede ejecutar instrucciones aleatorias. El detector hace que la placa se reinicie limpiamente en lugar de comportarse de forma impredecible. El módulo ESP32-WROOM-32 está diseñado para funcionar entre 3,0 y 3,6 V, así que cuando la línea llega a 2,43 V ya hace rato que salió de la zona segura.
Tras un mensaje de brownout, la línea de arranque muestra rst:0xc (SW_CPU_RESET) o rst:0x3 (SW_RESET) según la versión del núcleo de Arduino, porque la interrupción del detector imprime el mensaje y luego reinicia el chip por software. Que no te confunda: ese mismo código anota en la memoria del chip que la causa fue un brownout, y esp_reset_reason() devuelve ESP_RST_BROWNOUT. Cuando actúa el reinicio por brownout del propio hardware, la línea dice rst:0xf (RTCWDT_BROWN_OUT_RESET). En los núcleos 3.x, basados en ESP-IDF 5, esta línea pasó a ser un mensaje de registro (log) de nivel informativo (I BOD: Brownout detector was triggered). Como las bibliotecas precompiladas de Arduino solo escriben los mensajes de nivel error, casi nunca aparece y la placa se reinicia en silencio; si ves un rst:0x3 o rst:0xc sin explicación al arrancar, revisa el motivo de reinicio con el sketch de diagnóstico más abajo. Para entender por qué cae la tensión, hay que ver cuánta corriente consume el ESP32:
| Estado de funcionamiento | Corriente típica (chip ESP32) |
|---|---|
| Sueño profundo (temporizador RTC activo) | Unos 10 µA |
| Sueño ligero (light sleep) | Unos 0,8 mA |
| Wi-Fi apagado, CPU a 240 MHz | 30–68 mA |
| Recepción Wi-Fi (RX) | 95–100 mA |
| Transmisión Wi-Fi, 802.11b, 19,5 dBm | Unos 240 mA |
| Calibración de radio al arrancar y momento de conexión | Puede superar los 400 mA en picos de microsegundos |
Esta diferencia es el núcleo del brownout: hasta que tu código enciende el Wi-Fi, la placa funciona tranquila con unos 50 mA, pero en la línea WiFi.begin() la corriente se multiplica por cinco o seis en pocos microsegundos. Si la fuente, el cable y el regulador no alcanzan esa demanda repentina, la tensión se desploma. Por eso Espressif recomienda para el ESP32 una fuente capaz de entregar al menos 500 mA. El síntoma es muy conocido: la placa arranca, escribe unas líneas en el Monitor Serie, se reinicia justo cuando arranca el Wi-Fi o el Bluetooth y todo se convierte en un bucle infinito. Lo mismo puede pasar cuando se mueve un servo, conmuta un relé o una tira LED se pone en blanco.
Las causas más comunes del brownout
- Cable USB fino o largo: los hilos de alimentación finos de los cables baratos pueden sumar hasta 0,5 Ω entre ida y vuelta. Según la ley de Ohm, un pico de 400 mA pierde 0,2 V en ese cable.
- Puerto USB débil o hub: los puertos de teclados y monitores, los hubs sin alimentación y los puertos frontales de algunos portátiles dejan caer la tensión en los picos de corriente.
- Caída del regulador (dropout): el AMS1117 de la mayoría de las placas necesita 1,0–1,3 V de diferencia entre entrada y salida. Si la tensión del pin 5V baja de unos 4,3 V, la salida no puede mantener 3,3 V.
- Conectar una batería directamente: una batería de litio de 3,7 V en el pin 5V deja la salida por debajo de 3 V por la caída del regulador; dos pilas AA bajan enseguida del mínimo del ESP32 con carga.
- El pin 3.3V del Arduino Uno: de este pin solo se recomiendan unos 50 mA; no basta en absoluto para un ESP32 con el Wi-Fi activo.
- Cargas en la misma línea: si servos, relés, zumbadores, motores DC o una tira WS2812B se alimentan de los mismos 5 V que el ESP32, cada movimiento hace caer la tensión.
- Protoboard y cables jumper: los contactos flojos y los jumpers finos añaden algo de resistencia en cada conexión; llevar la alimentación a través de la protoboard empeora el problema.
- ESP32-CAM: cuando la cámara, el Wi-Fi y el LED de flash funcionan a la vez, la corriente sube mucho; esta placa necesita 5 V y al menos 2 A. Más detalles en la guía ESP32-CAM.
Solución 1: mejora la fuente de alimentación y el cable
El primer paso, y el más eficaz, es reforzar la alimentación. Conecta la placa con un cable de datos corto, grueso y que sepas que funciona bien, directamente a un puerto USB trasero del ordenador o a un cargador de 5 V de al menos 1 A. Los puertos USB 2.0 están pensados para dar 500 mA y los USB 3.0, 900 mA; los 500 mA que recomienda Espressif son justo el límite de un puerto USB 2.0, y al sumar las pérdidas del cable empiezan los problemas. Si cambiar solo el cable soluciona el error, lo más probable es que la culpa fuera de su resistencia. Puedes calcular cuánta tensión pierde un cable con V = I × R en la herramienta Ley de Ohm.

| Esquema de alimentación | Riesgo de brownout | Nota |
|---|---|---|
| Cable corto y grueso y puerto USB trasero | Bajo | La solución más fácil durante el desarrollo |
| Cable largo y fino, puerto frontal o hub | Alto | La resistencia del cable hace caer la tensión en los picos |
| Cargador de 5 V de al menos 1 A en el pin 5V o VIN | Bajo | Recomendado para proyectos permanentes |
| Fuente de 3,3 V directamente al pin 3V3 | Depende de la fuente | Debe dar al menos 500 mA y nunca superar 3,6 V |
| Batería de litio de 3,7 V a través del AMS1117 | Alto | Por debajo de unos 4 V en la batería, la salida baja de 3 V |
| Batería de litio con regulador de baja caída (LDO) de 3,3 V o buck-boost | Bajo | Da 3,3 V estables aunque la batería se descargue |
| Dos pilas AA directamente al pin 3V3 | Muy alto | Con carga, la tensión baja rápido del mínimo del ESP32 |
En los proyectos con batería hay que tener cuidado. Una celda de litio como la 18650 da 4,2 V llena y unos 3,0 V descargada. Ese rango no sirve ni para conectarla directamente al pin 3V3 (4,2 V superan el límite de 3,6 V) ni al pin 5V con su AMS1117. La solución correcta es llevar la batería al pin 5V mediante un módulo elevador (boost) de 5 V, o alimentar directamente la línea de 3V3 con un LDO de baja caída capaz de dar al menos 500 mA (por ejemplo ME6211 o HT7833) más un condensador. Puedes calcular cuánto aguantará tu proyecto con batería en la herramienta Duración de la batería y conocer el circuito de carga en la guía del módulo de carga TP4056.
Solución 2: añade un condensador entre 3V3 y GND
Un condensador funciona como un pequeño depósito que aporta energía durante un pico de corriente hasta que el regulador y el cable reaccionan. Coloca entre los pines 3V3 y GND, lo más cerca posible de la placa, un condensador electrolítico de 100–470 µF y, a su lado, uno cerámico de 100 nF. El electrolítico cubre las caídas de milisegundos y el cerámico, las fluctuaciones muy cortas y de alta frecuencia. Puedes ver su efecto de forma aproximada con ΔV = I × Δt / C: aunque un condensador de 470 µF aporte él solo 200 mA extra durante 200 µs, la tensión cae solo 0,085 V. La marca 104 de un condensador cerámico significa 100 nF; puedes descifrar otros códigos con la herramienta Código de condensadores.

Solución 3: alimenta por separado servos, motores y tiras LED
Aunque el ESP32 esté bien alimentado, las cargas conectadas a la misma línea pueden hacer caer la tensión. Incluso un servo pequeño como el SG90 consume varios cientos de mA al empezar a moverse; un LED WS2812B gasta unos 60 mA en blanco total, así que una tira de 30 LED puede llegar a 1,8 A. Alimenta estas cargas no desde el pin 5V o 3V3 del ESP32, sino desde una fuente de 5 V aparte; por ejemplo, puedes bajar un cargador de 12 V a 5 V con un módulo reductor LM2596. La regla más importante es unir los GND de ambas fuentes; sin una masa común la línea de señal no tiene referencia y el servo o la tira se comportan de forma aleatoria. Lo explicamos en detalle en ¿Tu Arduino se reinicia solo? y GND y tierra común.

¿Desactivar el detector de brownout es una solución?
La "solución" más repetida en internet es desactivar el detector con una sola línea. El código siguiente desactiva el detector poniendo a cero el registro de brownout del ESP32, y el mensaje de error desaparece. Pero las caídas de tensión siguen; simplemente ya nadie te avisa. Usa este código solo para diagnosticar: si con el detector apagado la placa funciona bien, habrás confirmado que el problema es sin duda la alimentación. Después borra la línea y arregla la alimentación.
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}Códigos rst: el ESP32 te dice por qué se reinició
El valor rst: que imprime la ROM en cada arranque indica cómo terminó la ejecución anterior. En un arranque normal el valor boot es 0x13 (SPI_FAST_FLASH_BOOT), es decir, la placa inicia su programa desde la flash. La tabla resume los códigos que verás con más frecuencia. Los códigos de la ROM no siempre bastan por sí solos: ESP.restart() y el reinicio tras un Guru Meditation aparecen con el mismo código 0xc, y un brownout sale como 0xc o 0x3 según la versión del núcleo. Para distinguirlos, usa el sketch con esp_reset_reason() de la siguiente sección.
| Código rst | Nombre | ¿Qué significa? |
|---|---|---|
| 0x1 | POWERON_RESET | Se conectó la alimentación por primera vez o se pulsó el botón EN |
| 0x3 | SW_RESET | El software reinició todo el sistema digital; en versiones recientes del núcleo también aparece tras un brownout |
| 0x5 | DEEPSLEEP_RESET | La placa despertó del sueño profundo (normal) |
| 0x7 / 0x8 | TG0WDT_SYS_RESET / TG1WDT_SYS_RESET | Un watchdog de grupo de temporizadores reinició el sistema por hardware; las interrupciones estuvieron desactivadas demasiado tiempo o ni siquiera pudo actuar el manejador de pánico |
| 0x9 | RTCWDT_SYS_RESET | El watchdog del RTC reinició el núcleo digital |
| 0xc | SW_CPU_RESET | Reinicio por software: ESP.restart(), tras un Guru Meditation o tras un brownout en el núcleo de Arduino |
| 0xf | RTCWDT_BROWN_OUT_RESET | Reinicio por brownout del hardware: tensión de alimentación inestable |
| 0x10 | RTCWDT_RTC_RESET | El watchdog del RTC reinició todo el chip, RTC incluido; suele verse en placas que se cuelgan durante el arranque |
Sketch de diagnóstico: imprime el motivo de reinicio, la memoria y la pila
Sube el código siguiente como un sketch aparte o añade sus líneas al setup() y al loop() de tu proyecto con problemas. Cada vez que la placa arranca, escribe en el Monitor Serie el motivo de reinicio en texto legible, cuántas veces ha arrancado y el estado de la memoria. El contador marcado con RTC_DATA_ATTR se guarda en la memoria RTC, por lo que sobrevive a los reinicios por software y al sueño profundo y solo se borra cuando se corta del todo la alimentación. Así, si el contador sube cada pocos segundos, sabrás al instante que la placa está en un bucle de reinicios.
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}Los valores que el código imprime en el bucle también destapan problemas silenciosos. Si el heap libre baja un poco en cada vuelta, tu programa tiene una fuga de memoria y la placa acabará fallando. Si el bloque más grande es mucho menor que el heap libre, la memoria está fragmentada; suele deberse a objetos String que crecen y encogen sin parar dentro del bucle. Si la pila libre de loopTask baja de unos cientos de bytes, el desbordamiento de pila está a la vuelta de la esquina. Si el motivo de reinicio es BROWNOUT, céntrate en las secciones de alimentación; si es PANIC, TASK_WDT o INT_WDT, en las secciones de software que siguen.
¿Qué es un Guru Meditation Error? Lee el informe del fallo línea a línea
Un Guru Meditation Error es el informe de fallo que imprime el manejador de pánico cuando la CPU del ESP32 encuentra un error del que no puede recuperarse; el nombre viene de la pantalla de error de los antiguos ordenadores Amiga. A primera vista asusta, pero solo importan unas pocas líneas. Core 1 indica en qué núcleo ocurrió el error: en Arduino, setup() y loop() se ejecutan en el núcleo 1 y la pila Wi-Fi en el núcleo 0. El nombre entre paréntesis es el tipo de excepción. Al terminar el informe, la placa escribe "Rebooting..." y se reinicia; en la línea de arranque verás rst:0xc.
| Línea | Significado |
|---|---|
| Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled. | El código del núcleo 1 intentó leer de una dirección no válida y el error no se gestionó |
| PC : 0x400d1a2c | Dirección de la instrucción donde ocurrió el fallo; si empieza por 0x400d, tu código o una librería se ejecutaba desde la flash |
| EXCCAUSE: 0x0000001c | Código de la excepción; 0x1c, es decir 28, significa LoadProhibited |
| EXCVADDR: 0x00000000 | Dirección a la que se intentó acceder; 0 o un número muy pequeño señala un puntero nulo |
| Backtrace: 0x400d1a29:0x3ffb2270 0x400d0f6e:0x3ffb2290 ... | Cadena de llamadas; cada par tiene la forma PC:SP y el decoder lo convierte en una línea de código |
| ELF file SHA256: ... | Huella del programa que falló; el decoder necesita exactamente la misma compilación |
| Rebooting... | El manejador de pánico reinicia el chip por software |
LoadProhibited, StoreProhibited y otros tipos de excepción
| Excepción | EXCCAUSE | ¿Qué pasó? | Causa habitual |
|---|---|---|---|
| LoadProhibited | 28 (0x1c) | Lectura desde una dirección no válida | Puntero nulo o sin inicializar, objeto borrado |
| StoreProhibited | 29 (0x1d) | Escritura en una dirección no válida | Escribir a través de un puntero nulo, búfer que se sale del array |
| InstrFetchProhibited | 20 (0x14) | Lectura de instrucción desde una dirección no válida | Puntero a función o callback sin asignar |
| IllegalInstruction | 0 (0x0) | Instrucción no válida | Pila corrompida, return olvidado en una función que debe devolver un valor |
| LoadStoreAlignment | 9 (0x9) | Acceso a memoria no alineado | Convertir a la fuerza un array de bytes en un tipo como uint32_t* |
| IntegerDivideByZero | 6 (0x6) | División entera entre cero | Dividir entre un contador que puede valer cero |
| Unhandled debug exception | - | Excepción de depuración | Stack canary watchpoint triggered: desbordamiento de pila |
| Interrupt wdt timeout on CPU0/CPU1 | - | Venció el watchdog de interrupciones | ISR larga, Serial o delay dentro de la ISR |
| Cache disabled but cached memory region accessed | - | Acceso a código en la flash mientras la flash estaba ocupada | Función de interrupción sin IRAM_ATTR |
El más frecuente de estos errores es usar un puntero nulo. NULL es la dirección 0 y en el ESP32 no hay memoria legible alrededor de la dirección 0, así que leer de donde apunta un puntero vacío da LoadProhibited y escribir ahí da StoreProhibited. En este caso EXCVADDR es una pista perfecta: 0x00000000 señala el propio puntero, y valores pequeños como 0x00000004 o 0x00000010 señalan un campo dentro de un puntero vacío (NULL más la posición del campo). En los proyectos de Arduino la causa más común es una función que devuelve NULL para decir "no lo encontré" y que ese valor se use sin comprobarlo. En el ejemplo siguiente, strstr() devuelve NULL si no encuentra el texto; sin comprobación, la placa falla 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}Desbordamiento de pila: el error "Stack canary watchpoint triggered"
En el ESP32 cada tarea (task) tiene una pila (stack) de tamaño fijo donde se guardan las variables locales, las llamadas a funciones y las direcciones de retorno. En Arduino, la tarea loopTask, que ejecuta setup() y loop(), tiene una pila de 8192 bytes. Declarar un array local de 10 000 bytes dentro de loop(), una función que se llama a sí misma (recursiva) o una función de librería con muchas variables locales supera ese límite. Al final de la pila hay una zona de vigilancia (canary); cuando la pila la alcanza, la placa escribe Guru Meditation Error: Core 1 panic'ed (Unhandled debug exception) y justo debajo Debug exception reason: Stack canary watchpoint triggered (loopTask). El nombre entre paréntesis es la tarea cuya pila se desbordó. A veces el mismo problema aparece como ***ERROR*** A stack overflow in task loopTask has been detected. Así se soluciona:
- Saca los arrays grandes de la pila: declara el array como global o ponle static delante; así vive en la RAM permanente y no en la pila.
- Agranda la pila de loopTask: escribe SET_LOOP_TASK_STACK_SIZE(16 * 1024); al principio del sketch para subir la pila de loop() a 16 KB.
- Da espacio suficiente a tus tareas: en el ESP32, el tamaño de pila que se pasa a xTaskCreate y xTaskCreatePinnedToCore está en bytes, no en palabras (words) como en FreeRTOS en otras plataformas. 1024 bytes se quedan cortos incluso para una tarea que use Serial.printf; empieza con 4096.
- Mide: uxTaskGetStackHighWaterMark(NULL) devuelve en bytes la menor cantidad de pila que la tarea nunca ha usado hasta ahora; si baja de unos cientos de bytes, agranda la pila.
- Evita la recursión: convierte las funciones que se llaman a sí mismas en bucles o limita su profundidad.
Watchdog de tareas: el error "task_wdt: Task watchdog got triggered"
Un watchdog (perro guardián) es un temporizador que detecta un programa colgado y rescata la placa. El watchdog de tareas (Task WDT) del ESP32 espera que las tareas registradas den señales de vida a intervalos regulares. En el núcleo de Arduino vigila por defecto la tarea inactiva (IDLE) del núcleo 0, con un tiempo de 5 segundos. Si una tarea del núcleo 0 no libera la CPU durante 5 segundos, la tarea IDLE no puede ejecutarse y en el Monitor Serie aparece un mensaje que empieza por E (10257) task_wdt: Task watchdog got triggered. A continuación vienen la lista de tareas que no reiniciaron el watchdog a tiempo (normalmente - IDLE (CPU 0)) y, bajo Tasks currently running:, las tareas que se ejecutaban en ese momento. En el núcleo de Arduino esto provoca un pánico con la línea task_wdt: Aborting. y la placa se reinicia.
Las causas más comunes son: un bucle fijado al núcleo 0 con xTaskCreatePinnedToCore que nunca llama a vTaskDelay(), un while que espera la respuesta de un sensor sin tiempo límite, o delay() y operaciones largas dentro de las funciones callback de librerías como AsyncWebServer. En este último caso verás en la lista la tarea async_tcp en lugar de IDLE. La solución es dividir los trabajos largos en partes pequeñas y añadir esperas cortas como vTaskDelay(1) a los bucles. La propia función loop() no se vigila por defecto; si quieres, llama a enableLoopWDT() en setup() para que la placa también se reinicie cuando loop() se quede colgado más de 5 segundos. Desactivar el watchdog con disableCore0WDT() oculta el error pero no arregla el código bloqueado. Para saber más sobre tareas y núcleos, consulta FreeRTOS y doble núcleo en el ESP32. El código siguiente muestra una estructura de tarea correcta y las precauciones contra el desbordamiento de pila:
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: fallos en las interrupciones
El watchdog de interrupciones (Interrupt WDT) es mucho más impaciente: si una función de interrupción (ISR) o un fragmento de código con las interrupciones desactivadas supera los 300 ms, la placa falla con Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1). Usar Serial.print, delay(), I2C o Wi-Fi dentro de una ISR es la causa más habitual. Otra trampa es la memoria flash: mientras Preferences, EEPROM o LittleFS escriben en la flash, la caché de la flash se desactiva un instante. Si en ese momento llega una interrupción cuya función está en la flash, la placa da el error Cache disabled but cached memory region accessed. La solución es marcar las ISR con IRAM_ATTR para que vivan en la RAM y limitarse a activar una bandera dentro de ellas; el trabajo real lo hace loop(). Las variables compartidas entre la ISR y loop() deben ser 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}Convierte el Backtrace en una línea de código con ESP Exception Decoder
Las direcciones de la línea Backtrace no dicen nada por sí solas; la herramienta que las traduce al archivo, la función y el número de línea de tu código fuente se llama ESP Exception Decoder. En el antiguo IDE de Arduino 1.8 era un complemento que se añadía al menú Herramientas. En el IDE de Arduino 2 se usa una extensión de la comunidad con el mismo nombre:
- Descarga el archivo con extensión .vsix desde la sección Releases del proyecto dankeboy36/esp-exception-decoder en GitHub. Para el IDE de Arduino 2 elige una versión 1.x; las nuevas versiones 2.x están pensadas para VS Code.
- Cierra el IDE de Arduino y copia el archivo en %UserProfile%\.arduinoIDE\plugins en Windows o en ~/.arduinoIDE/plugins en macOS y Linux; si la carpeta no existe, créala.
- Abre el IDE y el sketch que falla, compílalo con Verificar y súbelo a la placa.
- Abre el Monitor Serie y espera a que el fallo vuelva a producirse.
- Abre la paleta de comandos con Ctrl+Shift+P (Cmd+Shift+P en macOS) y ejecuta ESP Exception Decoder: Show Decoder Terminal.
- Copia del Monitor Serie el bloque Guru Meditation junto con la línea Backtrace y pégalo en el terminal que se abre; la herramienta muestra cada dirección con el nombre del archivo, la función y el número de línea.
Si no puedes instalar la extensión, puedes hacer lo mismo a mano. El menú Sketch > Export Compiled Binary deja un archivo con extensión .elf en la carpeta build, dentro de la carpeta del sketch. El comando xtensa-esp32-elf-addr2line -pfiaC -e sketch.ino.elf 0x400d1a29 0x400d0f6e de la cadena de herramientas del ESP32 convierte esas direcciones en nombres de función y números de línea. Si usas PlatformIO, basta con añadir la línea monitor_filters = esp32_exception_decoder al archivo platformio.ini; el informe del fallo se decodifica solo en el monitor serie.
Pines de arranque (strapping): cómo GPIO0, GPIO2, GPIO12 y GPIO15 estropean el arranque
Al arrancar, el ESP32 mira el nivel de ciertos pines para decidir cómo iniciar. Se llaman pines de strapping y solo importa su nivel en el momento del reinicio; una vez terminado el arranque, se pueden usar como GPIO normales. El problema aparece cuando un sensor, un pulsador o un módulo conectado a ellos tira del pin hacia el nivel equivocado al arrancar. Entonces la placa no inicia su programa, entra en modo programación o no puede leer la flash; desde fuera, esto también parece que "el ESP32 no arranca" o que "se reinicia sin parar". Puedes ver el mapa completo de pines de tu placa en la herramienta Pinout.
| Pin | Resistencia interna | Función al arrancar | ¿Qué pasa con el nivel equivocado? |
|---|---|---|---|
| GPIO0 | Pull-up | LOW activa el modo programación (download), HIGH el arranque normal | boot:0x3 (DOWNLOAD_BOOT) y waiting for download; el programa no se ejecuta |
| GPIO2 | Pull-down | Debe estar en LOW o al aire para entrar en modo programación; no influye en el arranque normal | Un circuito que lo lleve a HIGH impide subir código |
| GPIO12 (MTDI) | Pull-down | Elige la tensión de la flash: LOW 3,3 V, HIGH 1,8 V | En HIGH, una flash de 3,3 V no se alimenta bien; la placa no arranca o entra en un bucle de reinicios |
| GPIO15 (MTDO) | Pull-up | En LOW silencia los mensajes de arranque de la ROM | Las líneas de arranque no aparecen en el Monitor Serie y el diagnóstico se complica |
| GPIO5 | Pull-up | Define la temporización SDIO | Rara vez da problemas en placas de desarrollo |
Las placas más nuevas tienen otros pines de strapping: en el ESP32-S3 influyen en el arranque GPIO0, GPIO3, GPIO45 y GPIO46, y en el ESP32-C3 GPIO2, GPIO8 y GPIO9. El ESP32-S3 usa los mismos nombres de excepción Xtensa que el ESP32 clásico. En cambio, en el ESP32-C3 con núcleo RISC-V verás Load access fault en lugar de LoadProhibited y Store access fault en lugar de StoreProhibited, y el registro MTVAL asume el papel de EXCVADDR. El mensaje de brownout es igual en todos los modelos.
ESP32 y Raspberry Pi Pico con MicroPython
Si programas el ESP32 con MicroPython, el brownout ocurre exactamente igual, porque el problema está en el hardware y no en el software. Los errores del código Python, en cambio, aparecen como un Traceback legible en lugar de un Guru Meditation, y la placa rara vez falla. Puedes leer el motivo de reinicio con machine.reset_cause(), pero ojo: la versión de MicroPython para ESP32 no devuelve el brownout como un valor propio, sino que lo informa como PWRON_RESET junto con el encendido. Si la placa arranca una y otra vez con PWRON_RESET sin que la toques, vuelve a sospechar de la alimentación. El código siguiente es para el ESP32; en la Raspberry Pi Pico el módulo machine conoce menos tipos de reinicio. En la Pico, el regulador buck-boost de la entrada VSYS funciona con cualquier tensión entre 1,8 y 5,5 V, así que los problemas de brownout son mucho más raros; para conocer la placa, consulta ¿Qué es la 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)Errores frecuentes y soluciones
| Síntoma | Causa probable | Solución |
|---|---|---|
| Brownout detector was triggered al arrancar el Wi-Fi | El cable, el puerto o el regulador no alcanzan el pico de corriente | Cable corto y grueso, cargador de 1 A, 470 µF + 100 nF entre 3V3 y GND |
| Reinicio al girar un servo o conmutar un relé | La carga comparte la línea de alimentación del ESP32 | Alimenta la carga con una fuente de 5 V aparte y une los GND |
| Los reinicios empiezan cuando la batería se descarga un poco | Caída del AMS1117 | Usa un LDO o un convertidor buck-boost |
| Guru Meditation, EXCVADDR 0x00000000 | Puntero nulo | Comprueba el puntero antes de usarlo |
| Stack canary watchpoint triggered (loopTask) | La pila de loop() se llenó | Haz static los arrays grandes o usa SET_LOOP_TASK_STACK_SIZE |
| task_wdt: - IDLE (CPU 0) | Bucle sin pausas en el núcleo 0 | Añade vTaskDelay al bucle |
| Interrupt wdt timeout on CPU1 | La ISR es demasiado larga | En la ISR solo activa una bandera, usa IRAM_ATTR |
| Placa muda, boot:0x3 y waiting for download | GPIO0 en LOW al arrancar | Quita el circuito de GPIO0 y pulsa EN |
| La placa no arranca nada, error al leer la flash | GPIO12 en HIGH al arrancar | Pasa el pull-up o el módulo de GPIO12 a otro pin |
| Failed to connect to ESP32 al subir el código | GPIO2 en HIGH o la placa no entra en modo programación | Libera GPIO2 y mantén pulsado BOOT al empezar la subida |
Tu placa ya funciona estable: ¿y ahora qué?
- Dispositivo a batería: con el deep sleep del ESP32 duerme la placa entre mediciones y baja el consumo a microamperios.
- Actualizaciones inalámbricas: sube código sin cable USB con OTA en el ESP32.
- Panel en tiempo real: sigue los datos de los sensores en directo en el navegador con WebSocket en el ESP32.
- Envía notificaciones: con un bot de Telegram con ESP32, recibe en el móvil el motivo de reinicio cada vez que la placa se reinicie.
- Espectáculo de luces: controla con seguridad una tira WS2812B NeoPixel con alimentación separada.
La mayoría de los problemas de reinicio del ESP32 son o un problema de alimentación o unas pocas líneas de código con errores; cuando lees bien el mensaje, ambos se resuelven rápido. Para repasar los fundamentos de la placa, echa un vistazo a ¿Qué es el ESP32? En la app BlueGrays encontrarás proyectos con ESP32 de LED, servos, tiras NeoPixel, conexión Wi-Fi, servidor web, actualizaciones OTA y deep sleep, cada uno con su esquema de circuito y su código paso a paso, para que pruebes en proyectos reales las técnicas de alimentación y diagnóstico que has aprendido aquí.
Preguntas frecuentes
¿Cómo soluciono ESP32 Brownout detector was triggered?
Este error indica que la tensión de alimentación del ESP32 cayó por un instante por debajo de unos 2,43 V. Primero usa un cable USB corto y grueso con una fuente de 5 V de al menos 1 A y después añade un condensador electrolítico de 100–470 µF y uno cerámico de 100 nF entre 3V3 y GND. Alimenta cargas como servos, relés y tiras LED con una fuente aparte y une los GND.
¿Por qué mi ESP32 se reinicia constantemente?
Las causas más comunes son una alimentación insuficiente (brownout), fallos de software (Guru Meditation), tiempos de espera del watchdog y pines de strapping en un nivel equivocado al arrancar. Abre el Monitor Serie a 115200 baudios y pulsa EN; el último mensaje antes del reinicio y el código rst del arranque muestran la causa. La función esp_reset_reason() también te permite leerla desde el código.
¿Qué significa Guru Meditation Error LoadProhibited?
Significa que el código del ESP32 intentó leer de una dirección de memoria no válida. Si EXCVADDR es 0 o un número muy pequeño, casi seguro que se usó un puntero nulo. Pega la línea Backtrace en ESP Exception Decoder para encontrar el archivo y la línea donde se produjo el error.
¿Se puede desactivar el detector de brownout del ESP32?
Sí, con la línea WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0), pero eso solo silencia el aviso. Como las caídas de tensión siguen, los datos de la flash pueden corromperse y la placa puede fallar con otros errores. Úsalo solo un momento para confirmar que el problema viene de la alimentación.
¿Cómo soluciono ESP32 Task watchdog got triggered?
Este mensaje indica que una tarea vigilada no liberó la CPU durante 5 segundos; en Arduino la lista suele mostrar la tarea IDLE del núcleo 0. Añade vTaskDelay() a los bucles de tus propias tareas, usa tiempos límite al esperar a los sensores y evita delay() u operaciones largas en los callbacks del servidor web. Desactivar el watchdog oculta el error, pero no corrige la causa.
¿Qué fuente de alimentación necesita un ESP32?
Espressif recomienda para el ESP32 una fuente capaz de dar al menos 500 mA; en la práctica, un cargador de 5 V y 1 A deja un margen seguro. Si usas un ESP32-CAM o añades cargas como servos o tiras LED, elige una fuente de 2 A o más y, si es posible, alimenta esas cargas por una línea aparte.
Proyectos para hacer después de leer esto
Ver todos los proyectos
LED Parpadeante con ESP32
Enseña el uso básico de la salida GPIO con ESP32. Conseguimos un efecto de parpadeo encendiendo y apagando un LED desde el pin digital. Versión para ESP32 del clásico proyecto blink en Arduino UNO; los comandos básicos de blink en el entorno Arduino son los mismos, pero la distribución de pines y los niveles GPIO de 3,3 V del ESP32 difieren de los del Arduino UNO.

Conexión Wi-Fi STA con ESP32 (con pantalla LCD)
En este proyecto, conectamos el ESP32 a la red Wi-Fi doméstica en modo Station (STA) de forma segura y tolerante a fallos. Separamos el SSID y la contraseña en el archivo secrets.h para evitar que se filtren a Git, configuramos un mecanismo de reconexión automática y mostramos el nivel de señal RSSI y la dirección IP en una pantalla LCD I2C. El LED interno actúa como indicador de estado.

Servomotor ESP32 (control mediante potenciómetro)
En este proyecto controlamos un servomotor SG90 mediante un potenciómetro. Ajustamos el ángulo del servo asignando el valor analógico leído en el potenciómetro al rango de 0 a 180 grados. La biblioteca ESP32Servo genera señales de servo en los GPIO adecuados del ESP32 que admiten salida; en este proyecto se utiliza GPIO 18. Este es un método de control básico para brazos robóticos, cardanes de cámara y mecanismos de puertas automáticas.

Control de la tira NeoPixel WS2812B con ESP32
Los LED direccionables WS2812B (NeoPixel) son LED RGB inteligentes que se pueden controlar en cadena a través de un único pin de datos. En este proyecto, controlaremos una tira de 8 LED WS2812B con la biblioteca Adafruit_NeoPixel para crear una animación fluida de arcoíris. Aunque el nivel lógico de 3,3 V del ESP32 funciona sin problemas en la mayoría de los componentes WS2812B, mostraremos la solución de una resistencia en serie de 470 Ω y un condensador de 1000 µF en caso de inestabilidad. La

Servidor web asíncrono ESP32 (con indicación de IP en la pantalla LCD)
En este proyecto, convertimos el ESP32 en un servidor web asíncrono que puedes controlar desde tu teléfono o tu ordenador, siempre que estén conectados a la misma red Wi-Fi. Gracias a la biblioteca ESPAsyncWebServer, el servidor puede procesar varias solicitudes al mismo tiempo (a diferencia del clásico WebServer.h, que se bloquea con una sola solicitud).

Modo de suspensión profunda del ESP32 — Ahorro de batería (OLED + contador en RTC RAM)
El modo «Deep Sleep» del ESP32 reduce el consumo de corriente a unos 10 µA al desactivar la CPU y la mayoría de los periféricos, lo cual es de vital importancia para los dispositivos IoT que funcionan con pilas. En este proyecto utilizamos tres fuentes de activación diferentes: el temporizador (15 s), un pin externo (EXT0 — botón GPIO 33) y el sensor táctil (T0/GPIO 4). Las variables marcadas con RTC_DATA_ATTR se conservan durante el modo Deep Sleep: el contador de arranque, el contador de act

Actualización OTA del ESP32 — Instalación inalámbrica del firmware (con pantalla LCD)
En este proyecto aprenderás a instalar un nuevo firmware en el ESP32 a través de Wi-Fi, sin necesidad de un cable USB. Con la biblioteca ArduinoOTA, el ESP32 se hace visible en la red y aparece automáticamente como bluegrays-ota en la pestaña «Puerto de red» del IDE de Arduino. La primera instalación se realiza mediante USB; a partir de ahí, todas las actualizaciones se realizan de forma inalámbrica, lo cual es una característica fundamental para los dispositivos de campo. En la pantalla LCD

Sistema de Alarma de Movimiento ESP32 (PIR + Buzzer + LED + Telegram)
Un sistema inteligente de alarma de seguridad que activa el buzzer y el LED cuando el sensor PIR detecta movimiento, y envía simultáneamente una notificación instantánea al propietario vía Telegram. Cuenta con un tiempo de espera de 30 segundos para limitar la frecuencia de las alarmas repetidas; este mecanismo no comprueba la validez de las detecciones del sensor PIR ni elimina las falsas detecciones. Ideal para seguridad del hogar, vigilancia de garajes, almacenes o salas de trabajo. Con ESP32
Más artículos
App BlueGrays
Monta este circuito paso a paso en tu teléfono
Esquema, código y lista de piezas en una sola pantalla. Más de 200 proyectos de Arduino, ESP32 y Pico listos para montar.
Escanea con tu teléfono
Apunta la cámara al código y se abrirá la tienda de apps de tu teléfono.
o ábrela en la tienda

