
Watchdog Timer e recuperação automática de falhas
Reiniciar sozinho é fácil. Difícil é reiniciar só quando precisa, saber por que reiniciou e não entrar em ciclo — com Task WDT, motivo do reset e backoff.
Requisitos de hardware:
- Placa ESP32 ou ESP32-S3
- Cabo USB com transmissão de dados
Baixe o material deste tutorial:
- Projeto completo (ZIP)143 KBFirmware Arduino com Task Watchdog, leitura do motivo do reset, contador em memória RTC e modo seguro. Comando de travamento proposital para testar. README e manual em PDF.
- Manual em PDF (8 páginas)173 KBO erro que anula o mecanismo, tabela dos motivos de reset, contador RTC e modo seguro, roteiro de sete testes e checklist de catorze itens.
Quer receber novos manuais e esquemas industriais? Entre no nosso canal do Telegram / WhatsApp
Watchdog não é reiniciar de vez em quando
Existe uma prática que aparece em muito projeto embarcado e que não é watchdog: agendar um reinício a cada seis horas, ou a cada mil ciclos, na esperança de que isso limpe o que estiver acumulando.
Isso é varrer o problema para debaixo do tapete. Se há vazamento de memória, o reinício periódico esconde a causa e um dia deixa de bastar. E, pior, o dispositivo reinicia quando está funcionando bem — no meio de uma leitura, de uma gravação, de uma transmissão.
Watchdog é o oposto: um temporizador que só age quando o firmware para de dar sinal de vida. Enquanto tudo anda, ele nunca dispara. Quando algo trava, ele reinicia em segundos, sem esperar seis horas.
A diferença prática é grande. O reinício periódico é indiscriminado; o watchdog é cirúrgico.
Os dois watchdogs do ESP32
O ESP32 tem dois mecanismos, e confundi-los é a origem de metade dos problemas.
O Task Watchdog Timer, ou TWDT, vigia tarefas específicas. Você registra uma tarefa, ela precisa dar sinal periodicamente, e se ficar em silêncio além do prazo o sistema é reiniciado. É o que você configura e usa no dia a dia.
O Interrupt Watchdog vigia se as interrupções estão sendo atendidas. Ele detecta uma interrupção que ficou presa ou um trecho de código que desabilitou interrupções por tempo demais. Você não o configura — ele existe e dispara sozinho quando algo grave acontece.
No Arduino, a tarefa loopTask, que executa o seu setup e loop, vem registrada no Task Watchdog em muitas versões do core. É por isso que um laço bloqueante longo às vezes produz reinício sem que ninguém tenha configurado watchdog nenhum.
Registrar a tarefa e alimentar
A configuração é curta. Em Arduino, com o core recente:
| 1 | #include "esp_task_wdt.h" |
| 2 | static const uint32_t WDT_SEGUNDOS = 15; |
| 3 | void 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 | } |
| 12 | void loop() { |
| 13 | esp_task_wdt_reset(); |
| 14 | fazerTrabalho(); |
| 15 | } |
O esp_task_wdt_add(NULL) registra a tarefa atual. O reset é o sinal de vida, popularmente chamado de alimentar o cão. E o trigger_panic faz o sistema entrar em pânico e reiniciar em vez de apenas registrar o alerta — que é o que você quer em campo.
Em versões mais antigas do core, a assinatura era esp_task_wdt_init(segundos, panic). Se o seu compilador reclamar da struct, é esse o caso.
O erro que anula o mecanismo
Existe uma forma de ter watchdog configurado e proteção nenhuma: alimentar em lugar errado.
Se você chama o reset no início do laço e o trabalho de verdade vem depois, tudo bem. Mas se você espalhar chamadas de reset dentro das funções demoradas — dentro do laço que espera resposta Modbus, dentro do que aguarda conexão MQTT — o watchdog nunca vai disparar, porque você está garantindo o sinal de vida mesmo quando o firmware está travado.
A regra: alimente num ponto só, no laço principal, e não dentro das rotinas que podem travar. O objetivo não é evitar o reinício; é evitar que ele aconteça sem motivo e garantir que aconteça quando houver.
Corolário incômodo: se o seu firmware precisa de resets espalhados para não reiniciar, o problema não é o watchdog. É que existe uma operação bloqueante longa demais, e ela precisa virar não-bloqueante — como no padrão de guarda do artigo de MQTT.
Escolher o prazo
Prazo curto demais reinicia por engano. Prazo longo demais deixa o dispositivo travado por tempo demais antes de agir.
O critério é medir o pior caso do seu laço e dar folga generosa. Se a volta mais lenta leva dois segundos, quinze é um prazo confortável. Se você faz requisição HTTP com tempo limite de dez segundos dentro do laço, o prazo precisa ser maior que isso — ou, melhor, a requisição precisa sair do laço.
Comece com folga e aperte depois de observar. Um watchdog que dispara em operação normal é pior que nenhum: ensina a equipe a ignorar reinícios.
Por que reiniciou: a pergunta que ninguém consegue responder
Este é o ponto onde a maioria das implementações para, e é o que separa firmware que se recupera de firmware que só reinicia.
Um dispositivo que reinicia sozinho e não diz por quê é um problema de diagnóstico permanente. Pode ter sido watchdog, queda de alimentação, pânico por acesso inválido de memória, comando de software, ou o botão de reset. Cada um exige uma ação diferente, e sem a informação você fica adivinhando.
O ESP32 guarda o motivo, e ler custa três linhas:
| 1 | #include "esp_system.h" |
| 2 | const 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 | } |
Imprima isso no boot, e melhor ainda: publique junto da telemetria. A primeira vez que um dispositivo em campo avisar brownout você vai economizar um dia inteiro de investigação em software para um problema que era da fonte.
Aliás, brownout merece atenção especial. É o sintoma clássico de fonte subdimensionada ou cabo USB ruim, e é confundido com travamento de firmware com uma frequência impressionante. Se o motivo do reset diz brownout, pare de olhar o código.
Contar reinícios sem perder na energia
Saber o motivo do último reset ajuda. Saber que houve nove reinícios na última hora muda o diagnóstico completamente.
A memória RTC do ESP32 sobrevive a reinício por software e por watchdog, e é o lugar certo para esse contador:
| 1 | RTC_DATA_ATTR static uint32_t contadorResets = 0; |
| 2 | RTC_DATA_ATTR static uint32_t marcaValidade = 0; |
| 3 | static const uint32_t MARCA = 0xC0FFEE01; |
| 4 | void inicializarContador() { |
| 5 | if (marcaValidade != MARCA) { |
| 6 | marcaValidade = MARCA; |
| 7 | contadorResets = 0; |
| 8 | } else { |
| 9 | contadorResets++; |
| 10 | } |
| 11 | } |
A marca de validade existe porque a memória RTC não é zerada de forma confiável na primeira energização — sem ela, o contador começa com lixo. Comparar contra um valor conhecido resolve.
Perceba que a memória RTC se perde ao desligar a energia. Isso é uma característica útil, não uma limitação: um contador que zera na queda de energia distingue naturalmente reinício por falha de reinício por manutenção.
Não entrar em ciclo de reinício
Aqui está o cenário que transforma proteção em problema.
Suponha que o firmware trave sempre que tenta falar com um sensor que queimou. O watchdog reinicia. O firmware sobe, tenta o sensor, trava, reinicia. Em ciclo, para sempre, consumindo energia e sem nunca ficar tempo suficiente no ar para receber um comando ou uma atualização OTA.
O watchdog fez seu trabalho e o resultado é pior que o travamento original, porque agora nem para diagnosticar dá.
A saída é usar o contador para degradar em vez de insistir:
| 1 | void aplicarModoSeguro() { |
| 2 | if (contadorResets >= 5) { |
| 3 | Serial.println("[wdt] cinco reinicios seguidos. Entrando em modo seguro."); |
| 4 | modoSeguro = true; |
| 5 | } |
| 6 | } |
No modo seguro, o firmware desliga o que provavelmente está causando o travamento — a leitura do sensor, o barramento Modbus, o que for — e mantém apenas o essencial: Wi-Fi, servidor de configuração e OTA.
O dispositivo passa a funcionar mal, mas funciona, e permanece alcançável. Você conserta remotamente em vez de ir até lá.
Vale acrescentar um recuo progressivo: esperar alguns segundos antes de tentar de novo, aumentando a espera a cada falha. Isso evita que o ciclo consuma energia e dá tempo de o problema transitório passar sozinho.
Tutorial: do zero ao watchdog que você confia
Passo 1: incluir e configurar
Comece registrando o watchdog no setup, com prazo folgado. Quinze segundos é um bom ponto de partida para a maioria dos laços.
Adicione também a leitura do motivo do reset — é a parte que dá retorno imediato.
Passo 2: gravar e observar a partida normal
Compile, grave e abra o monitor:
| 1 | pio run -t upload |
| 2 | pio device monitor -b 115200 |
No primeiro boot, o motivo deve ser energia aplicada. Aperte o botão de reset e observe: o motivo muda para reset externo, e o contador sobe.
Passo 3: provocar um travamento
Este é o teste que dá confiança. Acrescente uma trava proposital, acionada por comando serial:
| 1 | if (comando == "travar") { |
| 2 | Serial.println("Travando de proposito..."); |
| 3 | while (true) { |
| 4 | // sem alimentar o watchdog |
| 5 | } |
| 6 | } |
Envie o comando e cronometre. Em cerca de quinze segundos a placa deve reiniciar, e o motivo do reset deve aparecer como TASK WATCHDOG.
Se não reiniciar, o registro da tarefa não está funcionando — verifique se o add foi chamado e se o trigger_panic está ligado.
Passo 4: verificar o contador
Provoque três travamentos seguidos e confirme que o contador chega a três. Depois desligue a energia e ligue de novo: o contador deve voltar a zero, e o motivo deve ser energia aplicada.
Essa diferença entre reinício por falha e por energia é a base do modo seguro.
Passo 5: testar o modo seguro
Provoque travamentos até passar do limite configurado. O firmware deve anunciar a entrada em modo seguro e parar de executar a parte problemática, mantendo a rede de pé.
Confirme que consegue alcançá-lo pela rede nesse estado. Se não conseguir, o modo seguro está desligando coisa demais.
Passo 6: apertar o prazo com dados
Com tudo funcionando, meça quanto tempo leva a volta mais lenta do seu laço em operação normal. Ajuste o prazo para algo em torno de cinco vezes esse valor.
Nunca aperte até o limite. Um watchdog que dispara em operação normal ensina a equipe a ignorar reinícios, e aí ele deixou de servir.
Erros comuns e como resolver
Reinicia sozinho e não sei por quê. Implemente a leitura do motivo do reset antes de qualquer outra coisa. É a informação que muda o diagnóstico de adivinhação para dedução.
O motivo diz brownout. Não é firmware. É fonte subdimensionada, cabo USB ruim ou pico de consumo no acionamento de rádio. Meça a tensão.
Configurei o watchdog e ele nunca dispara. Provavelmente há resets espalhados dentro das rotinas demoradas. Alimente num ponto só, no laço principal.
Dispara em operação normal. Prazo curto para o pior caso do laço, ou existe uma operação bloqueante que deveria ser não-bloqueante.
Entrou em ciclo de reinício. É o cenário que o modo seguro resolve. Sem ele, a recuperação exige acesso físico.
O contador de resets começa com lixo. Falta a marca de validade: a memória RTC não é zerada de forma confiável na primeira energização.
O erro reclama da struct de configuração. Versão do core. Nas antigas a chamada era esp_task_wdt_init com dois argumentos, em vez da struct.
