iiOTESP32 Brasil
Modo de contingencia: las páginas públicas y las descargas siguen disponibles; el inicio de sesión, la comunidad y los datos del servidor pueden no estar disponibles.
Volver al catálogo de tutoriales
Ilustração: três placas ESP32 em sequência, funcionando normalmente, travada com alerta e cronômetro, e recuperada após o reinício, ligadas por uma seta circular
Imagem ilustrativa gerada por IA
IntermedioSeguridadLectura: 13 min

Watchdog Timer y recuperación automática de fallos

Reiniciar solo es fácil. Lo difícil es reiniciar únicamente cuando hace falta, saber por qué ocurrió y evitar ciclos, con Task WDT, causa del reset y backoff.

Tecnologías:#ESP32#Task WDT#FreeRTOS#esp_reset_reason#RTC memory

Requisitos de hardware:

  • Placa ESP32 ou ESP32-S3
  • Cabo USB com transmissão de dados

Watchdog no es reiniciar cada cierto tiempo

Hay una práctica que aparece en muchos proyectos embebidos y que no es un watchdog: programar un reinicio cada seis horas o cada mil ciclos esperando que limpie lo que se esté acumulando.

Eso es esconder el problema. Si hay una fuga de memoria, el reinicio periódico oculta la causa hasta que un día deja de ser suficiente. Y, peor aún, el dispositivo se reinicia cuando funciona bien, en mitad de una lectura, una escritura o una transmisión.

Un watchdog es lo contrario: un temporizador que solo actúa cuando el firmware deja de dar señales de vida. Mientras todo funciona no se dispara. Cuando algo se bloquea, reinicia en segundos en vez de esperar seis horas.

La diferencia práctica es grande. El reinicio periódico es indiscriminado; el watchdog es quirúrgico.

Los dos watchdogs del ESP32

El ESP32 tiene dos mecanismos y confundirlos causa la mitad de los problemas.

El Task Watchdog Timer, o TWDT, vigila tareas concretas. Registras una tarea, debe dar señales periódicas y, si permanece en silencio más allá del plazo, el sistema se reinicia. Es el que configuras y utilizas día a día.

El Interrupt Watchdog vigila si se atienden las interrupciones. Detecta una interrupción atascada o un tramo de código que desactivó las interrupciones demasiado tiempo. No lo configuras; existe y se dispara solo cuando ocurre algo grave.

En Arduino, la tarea loopTask, que ejecuta tu setup y loop, viene registrada en el Task Watchdog en muchas versiones del core. Por eso un bucle bloqueante largo puede reiniciar la placa aunque nadie haya configurado ningún watchdog.

Registrar la tarea y alimentarla

La configuración es corta. En Arduino, con un core reciente:

code.cpp
cpp
1#include "esp_task_wdt.h"
2static const uint32_t WDT_SEGUNDOS = 15;
3void setup() {
4 esp_task_wdt_config_t cfg = {
5 .timeout_ms = WDT_SEGUNDOS * 1000,
6 .idle_core_mask = 0,
7 .trigger_panic = true
8 };
9 esp_task_wdt_reconfigure(&cfg);
10 esp_task_wdt_add(NULL);
11}
12void loop() {
13 esp_task_wdt_reset();
14 fazerTrabalho();
15}

esp_task_wdt_add(NULL) registra la tarea actual. reset es la señal de vida, lo que se suele llamar alimentar al perro. trigger_panic hace que el sistema entre en pánico y reinicie en vez de limitarse a registrar la alerta, que es lo que quieres en campo.

En las versiones antiguas del core la firma era esp_task_wdt_init(segundos, panic). Si el compilador se queja de la struct, ese es el caso.

El error que anula el mecanismo

Es posible tener el watchdog configurado y ninguna protección: alimentarlo en el lugar equivocado.

Si llamas a reset al principio del bucle y el trabajo real viene después, está bien. Pero si repartes llamadas a reset dentro de funciones largas, dentro del bucle que espera una respuesta Modbus o del que espera una conexión MQTT, el watchdog nunca se dispara porque sigues dando señal de vida aunque el firmware esté bloqueado.

La regla es: aliméntalo en un único punto, en el bucle principal, y nunca dentro de rutinas que puedan bloquearse. El objetivo no es impedir el reinicio; es impedir que ocurra sin motivo y garantizar que ocurra cuando haga falta.

El corolario incómodo es que, si tu firmware necesita resets repartidos para no reiniciarse, el problema no es el watchdog. Hay una operación bloqueante demasiado larga y debe hacerse no bloqueante, como en el patrón de guardas del artículo MQTT.

Elegir el plazo

Un plazo demasiado corto reinicia por error. Uno demasiado largo deja el dispositivo bloqueado demasiado tiempo antes de actuar.

Mide el peor caso del bucle y dale un margen generoso. Si la vuelta más lenta tarda dos segundos, quince es cómodo. Si haces una petición HTTP con timeout de diez segundos dentro del bucle, el plazo debe ser mayor; o, mejor, la petición debe salir del bucle.

Empieza con margen y ajústalo después de observar. Un watchdog que se dispara durante la operación normal es peor que ninguno: enseña al equipo a ignorar reinicios.

Por qué se reinició: la pregunta que nadie puede responder

Aquí se detiene la mayoría de las implementaciones y es lo que separa un firmware que se recupera de uno que solo reinicia.

Un dispositivo que reinicia solo y no explica por qué es un problema de diagnóstico permanente. Puede ser watchdog, pérdida de alimentación, pánico por acceso inválido a memoria, un comando de software o el botón de reset. Cada caso requiere una acción distinta y sin información solo puedes adivinar.

El ESP32 guarda el motivo y leerlo cuesta tres líneas:

code.cpp
cpp
1#include "esp_system.h"
2const char *descreverMotivoReset(esp_reset_reason_t r) {
3 switch (r) {
4 case ESP_RST_POWERON: return "energia aplicada: partida normal";
5 case ESP_RST_SW: return "reinicio por software: esperado";
6 case ESP_RST_PANIC: return "PANICO: excecao no firmware";
7 case ESP_RST_TASK_WDT: return "TASK WATCHDOG: uma tarefa travou";
8 case ESP_RST_INT_WDT: return "INTERRUPT WATCHDOG: interrupcao presa";
9 case ESP_RST_BROWNOUT: return "BROWNOUT: tensao caiu abaixo do minimo";
10 case ESP_RST_DEEPSLEEP: return "acordou de sono profundo";
11 case ESP_RST_EXT: return "reset externo pelo pino";
12 default: return "motivo desconhecido";
13 }
14}

Imprímelo en el arranque y, mejor aún, publícalo junto con la telemetría. La primera vez que un dispositivo en campo informe de brownout ahorrarás un día entero de investigación de software para un problema que estaba en la fuente de alimentación.

El brownout merece atención especial. Es el síntoma clásico de una fuente insuficiente o de un cable USB malo y se confunde con un bloqueo del firmware con una frecuencia sorprendente. Si el motivo del reset dice brownout, deja de mirar el código.

Contar reinicios sin perder la cuenta al cortar la energía

Conocer el motivo del último reset ayuda. Saber que hubo nueve reinicios en la última hora cambia por completo el diagnóstico.

La memoria RTC del ESP32 sobrevive a los reinicios por software y por watchdog, por lo que es el lugar adecuado para este contador:

code.cpp
cpp
1RTC_DATA_ATTR static uint32_t contadorResets = 0;
2RTC_DATA_ATTR static uint32_t marcaValidade = 0;
3static const uint32_t MARCA = 0xC0FFEE01;
4void inicializarContador() {
5 if (marcaValidade != MARCA) {
6 marcaValidade = MARCA;
7 contadorResets = 0;
8 } else {
9 contadorResets++;
10 }
11}

La marca de validez existe porque la memoria RTC no se borra de forma fiable al energizar por primera vez; sin ella el contador empieza con basura. Compararla con un valor conocido lo resuelve.

Observa que la memoria RTC se pierde al cortar la energía. Es una característica útil, no una limitación: un contador que se pone a cero al perder alimentación distingue naturalmente un reinicio por fallo de uno de mantenimiento.

No entrar en un ciclo de reinicios

Este es el escenario que convierte la protección en un problema.

Supón que el firmware se bloquea siempre que intenta hablar con un sensor averiado. El watchdog reinicia. El firmware arranca, intenta el sensor, se bloquea y reinicia. En ciclo para siempre, consumiendo energía y sin permanecer en línea el tiempo suficiente para recibir un comando o una actualización OTA.

El watchdog hizo su trabajo, pero el resultado es peor que el bloqueo original porque ahora ni siquiera hay tiempo para diagnosticar.

Utiliza el contador para degradar en vez de insistir:

code.cpp
cpp
1void aplicarModoSeguro() {
2 if (contadorResets >= 5) {
3 Serial.println("[wdt] cinco reinicios seguidos. Entrando em modo seguro.");
4 modoSeguro = true;
5 }
6}

En modo seguro, el firmware desactiva lo que probablemente causa el bloqueo, la lectura del sensor, el bus Modbus o lo que sea, y conserva solo lo esencial: Wi-Fi, servidor de configuración y OTA.

El dispositivo funciona mal, pero funciona y sigue siendo accesible. Lo arreglas de forma remota en vez de desplazarte.

También conviene añadir un backoff progresivo: espera unos segundos antes de volver a intentarlo y aumenta la espera en cada fallo. Así el ciclo no consume energía y un problema transitorio tiene tiempo de desaparecer.

Tutorial: de cero a un watchdog en el que confías

Paso 1: incluir y configurar

Empieza registrando el watchdog en setup con un plazo amplio. Quince segundos es un buen punto de partida para la mayoría de los bucles.

Añade también la lectura del motivo del reset; es la parte que ofrece una respuesta inmediata.

Paso 2: grabar y observar el arranque normal

Compila, graba y abre el monitor:

code.cpp
cpp
1pio run -t upload
2pio device monitor -b 115200

En el primer arranque el motivo debería ser energía aplicada. Pulsa el botón de reset y observa: el motivo cambia a reset externo y el contador aumenta.

Paso 3: provocar un bloqueo

Esta es la prueba que da confianza. Añade un bloqueo intencionado activado por un comando serie:

code.cpp
cpp
1if (comando == "travar") {
2 Serial.println("Travando de proposito...");
3 while (true) {
4 // sem alimentar o watchdog
5 }
6}

Envía el comando y cronometra. En unos quince segundos la placa debe reiniciarse y el motivo debe aparecer como TASK WATCHDOG.

Si no reinicia, el registro de la tarea no funciona; comprueba que se llamó a add y que trigger_panic está activado.

Paso 4: comprobar el contador

Provoca tres bloqueos seguidos y confirma que el contador llega a tres. Después corta la alimentación y vuelve a encender: el contador debe volver a cero y el motivo debe ser energía aplicada.

Esta diferencia entre reinicio por fallo y por energía es la base del modo seguro.

Paso 5: probar el modo seguro

Provoca bloqueos hasta superar el límite configurado. El firmware debe anunciar la entrada en modo seguro y dejar de ejecutar la parte problemática, manteniendo la red activa.

Confirma que puedes alcanzarlo por la red en ese estado. Si no puedes, el modo seguro está desactivando demasiado.

Paso 6: ajustar el plazo con datos

Con todo funcionando, mide cuánto tarda la vuelta más lenta del bucle durante la operación normal. Ajusta el plazo a unas cinco veces ese valor.

Nunca lo ajustes hasta el límite. Un watchdog que se dispara durante la operación normal enseña al equipo a ignorar reinicios y deja de cumplir su función.

Errores comunes y cómo resolverlos

Se reinicia solo y no sé por qué. Implementa la lectura del motivo del reset antes que cualquier otra cosa. Es la información que cambia el diagnóstico de adivinanza a deducción.

El motivo dice brownout. No es firmware. Es una fuente insuficiente, un cable USB malo o un pico de consumo al activar la radio. Mide la tensión.

Configuré el watchdog y nunca se dispara. Probablemente hay resets repartidos dentro de rutinas largas. Aliméntalo en un solo punto, en el bucle principal.

Se dispara durante la operación normal. El plazo es corto para el peor caso del bucle o existe una operación bloqueante que debería ser no bloqueante.

Entró en un ciclo de reinicios. El modo seguro resuelve ese escenario. Sin él, la recuperación exige acceso físico.

El contador de resets empieza con basura. Falta la marca de validez: la memoria RTC no se borra de forma fiable al energizar por primera vez.

El error se queja de la struct de configuración. Es la versión del core. En las antiguas la llamada era esp_task_wdt_init con dos argumentos en lugar de la struct.

Ver más tutoriales técnicosIIoT ESP32 Brasil — Documentación práctica