Recents in Beach


Receba o meu conteúdo GRATUITAMENTE


Travou! E agora?



Um assunto que considero bastante importante é o Watchdog, palavra que, traduzida para o português, significa “cão de guarda”. Existe um problema que atinge muitos circuitos microcontrolados que é o fato deles travarem, isso por inúmeros motivos. O Watchdog, portanto, é um dispositivo independente do microcontrolador que, em caso de “congelamento”, irá reiniciar o sistema. Neste vídeo vamos, então, implementar um Watchdog no ESP32 utilizando interrupção por tempo e criar um exemplo para simular um travamento e acionar o dispositivo.



Introdução

O que fazer se seu programa travar no meio de uma execução e ficar “congelado”?
Como reiniciar o ESP32 sem precisar ir lá apertar o botão físico?
Para isso utilizamos o Watchdog, um “cão de guarda” que fica vigiando para verificar se seu programa travou e, caso isso ocorra, ele trata de reiniciá-lo automaticamente.


Mas, como funciona o Watchdog?

Imagine que você tem um cachorro que precisa comer de tempos em tempos para não começar a latir. Então, você passa a alimentá-lo com certa frequência para mantê-lo em estado normal.
É assim que o Watchdog funciona, ele fica aguardando ser “alimentado” (um sinal de que está tudo OK). Caso passe um tempo e nenhum sinal tenha sido recebido, ele reinicia o MCU.


Implementando um Watchdog

Implementaremos o Watchdog no ESP32 utilizando uma interrupção por tempo. Para isso vamos configurar um temporizador para disparar um sinal e acionar uma função callback (onde teremos o comando para reiniciar o ESP32), no caso de seu contador extrapolar o limite de tempo definido.
Não dá para você usar um circuito profissional microcontrolado sem utilizar o Watchdog porque ele é que impede que o sistema fiquei lá parado em caso de um travamento. Então, esse procedimento é bastante importante em inúmeras situações.


Interrupção por tempo

Utilizaremos os seguintes comandos para configurar e manipular nosso temporizador.


hw_timer_t *timer = NULL; //faz o controle do temporizador (interrupção por tempo)


//timerID 0, div 80 (clock do esp), contador progressivo
timer = timerBegin(0, 80, true); 


//instancia de timer, função callback, interrupção de borda
timerAttachInterrupt(timer, &resetModule, true);


// instancia de timer, tempo (us),3.000.000 us = 3 segundos , repetição
timerAlarmWrite(timer, 3000000, true);


timerAlarmEnable(timer); //habilita a interrupção


timerWrite(timer, 0); //reseta o temporizador (alimenta o watchdog) zera o contador 



WiFi NodeMCU-32S ESP-WROOM-32




Montagem




Programa

Nosso programa funcionará da seguinte forma: vamos configurar uma interrupção por tempo para 3 segundos. Na função Loop, a cada iteração, vamos resetar nosso temporizador. Teremos um botão para simular um travamento do programa. Enquanto ele estiver pressionado, o temporizador não será resetado, fazendo, assim, com que, ao extrapolar o limite de tempo pré-definido, a interrupção seja acionada e a função com o comando para reiniciar o ESP seja executada.


Variáveis

Aqui determinamos um pino do botão para simular um travamento. Um outro pino acenderá o Led vermelho. O mesmo ocorrerá com o Led verde toda vez que o ESP32 reiniciar. O controle é feito por um temporizador.

const int pinButton = 2; //pino do botão para simular um travamento
const int redLed = 23; //pino do led
const int greenLed = 4; //esse led acenderá toda vez que o ESP32 reiniciar

hw_timer_t *timer = NULL; //faz o controle do temporizador (interrupção por tempo)



Setup

Inicializamos os pinos e configuramos os timers. Na sequência, habilitamos a interrupção e colocamos o Led verde para piscar indicando que o programa reiniciou.

void setup() {
    Serial.begin(115200);
    Serial.println("running setup");
    
    pinMode(pinButton, INPUT_PULLUP);
    pinMode(redLed , OUTPUT);
    pinMode(greenLed , OUTPUT);   
    delay(500);  

    //hw_timer_t * timerBegin(uint8_t num, uint16_t divider, bool countUp)
    /*
       num: é a ordem do temporizador. Podemos ter quatro temporizadores, então a ordem pode ser [0,1,2,3].
      divider: É um prescaler (reduz a frequencia por fator). Para fazer um agendador de um segundo, 
      usaremos o divider como 80 (clock principal do ESP32 é 80MHz). Cada instante será T = 1/(80) = 1us
      countUp: True o contador será progressivo
    */
    timer = timerBegin(0, 80, true); //timerID 0, div 80
    //timer, callback, interrupção de borda
    timerAttachInterrupt(timer, &resetModule, true);
    //timer, tempo (us), repetição
    timerAlarmWrite(timer, 3000000, true);
    timerAlarmEnable(timer); //habilita a interrupção 
    digitalWrite(greenLed, HIGH);
    delay(1000);
    digitalWrite(greenLed, LOW);
}



Loop


void loop() {
    timerWrite(timer, 0); //reseta o temporizador (alimenta o watchdog) 
    long tme = millis(); //tempo inicial do loop
    //fica preso no loop enquanto o botão estiver pressionado
    while(digitalRead(pinButton))
    {
        Serial.println("botão pressionado: "+String(millis() - tme));
        digitalWrite(redLed, HIGH); //acende o led vermelho
        delay(500);
//        timerWrite(timer, 0); //se fizer esse reset no temporizador, o watchdog não será acionado
    }
    delay(1000);
    Serial.print("tempo passado dentro do loop (ms) = ");
    tme = millis() - tme; //calcula o tempo (atual - inicial)
    Serial.println(tme);
    //desliga o led vermelho
    digitalWrite(redLed, LOW);
}



resetModule

Temos aqui a função que o temporizador irá chamar para reiniciar o ESP32. Imprimimos o log e executamos o comando para reiniciar o chip.

//função que o temporizador irá chamar, para reiniciar o ESP32
void IRAM_ATTR resetModule(){
    ets_printf("(watchdog) reiniciar\n"); //imprime no log
    esp_restart_noos(); //reinicia o chip
}



Simulando

Segurando o botão por 3 segundos ou mais, o temporizador não foi resetado.
Logo, Watchdog entra em ação, reiniciando o ESP.


Segurando o botão por menos de 3 segundos.
Logo, não chegou ao limite do temporizador, e o Watchdog não será acionado.




Adicionando o Watchdog no ESP32 LoRa

É importante que vocês assistam este vídeo e entendam o código fonte que utilizei nele. Isso porque, a partir daí, você vai incluir a parte do Watchdog neste mesmo código.

Para implementarmos o Watchdog nesse projeto faremos poucas alterações como veremos a seguir.
No arquivo LoRaSendReceiveBME280.ino, adicione o seguinte trecho de código em qualquer lugar do arquivo.

//faz o controle do temporizador (interrupção por tempo)
hw_timer_t *timer = NULL; 

//função que o temporizador irá chamar, para reiniciar o ESP32
void IRAM_ATTR resetModule(){
ets_printf("(watchdog) reiniciar\n"); //imprime no log
esp_restart_noos(); //reinicia o chip
}

//função que o configura o temporizador
void configureWatchdog()
{
timer = timerBegin(0, 80, true); //timerID 0, div 80
//timer, callback, interrupção de borda
timerAttachInterrupt(timer, &resetModule, true);
//timer, tempo (us), repetição
timerAlarmWrite(timer, 5000000, true);
timerAlarmEnable(timer); //habilita a interrupção //enable interrupt
}


Nos outros arquivos Master.ino e Slave.ino temos duas linhas para adicionar, uma no Setup e outra no Loop.

void setup(){
    Serial.begin(115200);
    configureWatchdog();
    .....
}

void loop(){
    //reseta o temporizador (alimenta o watchdog)
    timerWrite(timer, 0); 
    .....
}



Faça o download dos arquivos:

INO

PDF


Postar um comentário

32 Comentários

  1. abraços de Portugal..
    O seu canal é mesmo giro..
    Parabens Fernando pelo seu canal..já aprendi bastante com ele..

    ResponderExcluir
  2. esou fazendo osket no arduino 1.8.5 e esdta dfando erro hw_timer_t *timer = NULL; 'hw_timer_t' does not name a type

    ResponderExcluir
    Respostas
    1. provavelmente na ide do arduino deve estar selecionado outra placa , no meu caso quando coloquei a heltec esp 32 lora (v2) funcionou normalmente.

      Excluir
  3. Boa tarde. Como eu posso colocar num mesmo programa watchdog e webupdate do esp32? Separados funcionam numa boa, porém, juntos, ao acessar o browser para carregar o arquivo de atualização, esse timer deve estourar e impedir que eu faça isso. Aí eu não consigo fazer a atualização de jeito nenhum!

    ResponderExcluir
    Respostas
    1. Bom dia, você pode criar uma "xTask" que faz esse trabalho de WebUpload separadamente do loop principal.

      Excluir
  4. Boa Tarde Fernando, em primeiro lugar quero lhe dizer que sou profundo admirador de seu trabalho,
    suas aulas são TOP, e seu modo de ensino é muito bacana, faz com que a gente fique pregado na tela sem querer piscar um só instante, enfim
    Parabéns.
    Sou um curioso, tenho 56 anos, sou da area de transportes, não tenho conhecimento de eletronica e nem da area de programação, o pouco que aprendi foi assistindo suas aulas, e também as aulas do Prof. Flávio do canal Brincando com Idéias, inclusive consegui desenvolver alguns projetos interessantes, baseados em ESP8266 com (WebServer), (DHT11),(LM35),(ACS712),(HC-SR04), dentre outros pequenos projetos também, enfim.
    Tenho um projeto com NodeMCU que está me dando uma dor de cabeça danada, (Projeto acender/apagar Lamp/Garagem com interruptor em paralelo com o relê e Sensor ACS712 para saber estado da lâmpada, e Abrir/Fechar Portão eletronico da Garagem com Sensor Fim de Curso, para saber se FECHADO ou ABERTO), tudo dentro do mesmo código WiFiServer com direcionamento de portas e DDNS para acessar remotamente fora da rede interna.
    Na verdade o projeto está funcionando até que bem, tando no Browser do Windows quanto no Browser do Celular Android, na página HTML que está dentro do código existe um Refresh onde atualiza a página em tempos em tempos, para saber o estado tanto da Lampada como a do Portão, ficou bem interessante, é lógico que isto não saiu da minha cabeça, fui juntando um pouco das suas aulas e também do Flávio,
    O Problema é o seguinte, estou fazendo um Aplicativo para Android no MIT APP INVENTOR, em que uso este código descrito, ficou legal também, porém ao usar o aplicativo, o ESP trava sempre nas mudanças de rede, tanto do WiFi para o 4G, quanto do 4G para WiFi, talvez seja motivado pelo GATILHO ou TRIGGER do Clock Timer do APP Inventor que tive que implantar para poder fazer Refresh no APP, enfim... trava que não te, jeito.
    Tentei implantar o Watch Dog no código para tentar resetar o ESP, mas não está funcionando, acho que só funciona do ESP32, vc pode me ajudar nisto, se houver outra maneira de resetar o NodeMcu,
    desde já agradeço.

    ResponderExcluir
    Respostas
    1. não parece ser travamento no ESP e sim no app do celular, por algum motivo ele pode travar ou nao reconectar direito quando vc muda de rede porém quando vc desliga e liga o ESP a conexao é perdida e reconectada dando impressão do problema resolvido. Ao meu ver oque esta ficando travado é o aplicativo que nao fechou a conexão quando mudou de rede wifi pra 4G, crie um botao de reconect, feche e reconect no ESP

      Excluir
  5. Olá! Como implementa o Watch Dog no ESP8266 NodeMcu ?

    ResponderExcluir
    Respostas
    1. Já vem implementado WD software reinicia com +- 3,5segundo e é realimentado com delay() ou no final de cada loop. e tem um WD hardware que reinicia com 8seg de travamento esse é realimentado quando o WD software realimenta.
      pode fazer um teste desabilitando o WD Software com o comando ESP.wdtDisable();
      e se quiser testar o WD software usa um delayMicroseconds(5000000); no loop ou setup que o esp vai reiniciar

      Excluir
  6. esp_restart_noos();

    DA ERRO E PONTO FINAL NA CONVEVRSA !!!

    ResponderExcluir
    Respostas
    1. acho que é a versao da IDE, to usando IDE do linux e pro restar uso a função esp_restart();

      Excluir
    2. boa noite chefe o meu deu o mesmo erro so retirei a parte para forcar o comando e funcionou com este esp_restart(); com o 32 deve kit e o nod 32s

      Excluir
  7. Bom dia
    Funciona no Nodemcu 12E?
    Grato.

    ResponderExcluir
    Respostas
    1. esse já tem WD programado, 3,5seg o via software e 8 seg via hardware

      Excluir
  8. update: tive que trocar o esp_restart_noos() por esp_restart(). Somente assim ele compilou.

    ResponderExcluir
  9. Olá boa noite, gostaria de saber se esse código funciona no ESP8266?

    ResponderExcluir
    Respostas
    1. esp8266 já tem WD programado por padrão, é realimentado sempre que aparece um delay() ou que reinicia o loop{}

      Excluir
  10. Este WDT por software não é seguro, ele não considera os problemas graves de programa que provocam inclusive o travamento das interrupções. Os WDT seguros são aqueles de hardware complementar, sejam externos ou dentro do próprio chip mas não dentro do core, isto sim irá monitorar se o core travou. Já vivenciei vários problemas de cores travando, em alguns casos não por falha de programa, mas por interferência eletromagnética ou disparos de cargas elétricas. Dependendo do chip, nem reset da CPU resolve, sendo necessário trabalhar com o WDT em conjunto com a fonte de alimentação.

    ResponderExcluir
    Respostas
    1. Henry, você sabe me dizer como implementar esse WD junto à fonte de alimentação? Estou desenvolvendo um aplicação na qual me comunico com um endpoint para enviar dados coletados pelo ESP8266 Nodemcu, porém houve um travamento nele após uns 3 dias funcionando, que não veio a se repetir desde então, mas que preciso me precaver disso. Não sei se houve travamento do ESP ou se foi algum problema de comunicação com o Endpoint, mas suspeito que tenha sido, pois não voltou a se repetir.

      Excluir
  11. Este watchdog não vai funcionar em caso de congelamento do processador.
    O ideal é um watchdog externo, que você desabilita frequentemento por I/O em um pino.
    Se o CPU travar, o circuito independente externo vai chegar em seu limite e resetar o microcontrolador.
    Pode-se fazer simples usando um temporizador RC, ou um gerenciador de watchdog dedicado.

    ResponderExcluir
  12. Obrigado Fernando! Funcionou perfeitamente.
    Apenas tive que substituir o "esp_restart_noos();" por "ESP.restart();" no meu caso.

    ResponderExcluir
  13. Olá boa tarde.
    Fernando uma dúvida minha, estou a programar o ESP32 para que haja interrupção via Timer0 e Timer1, no PIC sempre fiz de maneira facíl acessando e configurando os registradores pertinentes a cada Timer, já na ESP como não tenho acesso direto aos registradores isso está dando uma "bugada" na minha mente, mais precisamente na linha hw_timer_t * timer = NULL; pra fazer a interrupção do Timer0 e Timer1 eu criei 2 ponteiros distintos, seria isso mesmo?
    até funcionou aqui mas como eu não tenho acesso aos registradores diretamente não sei se fiz da maneira correta, segue meu código.

    /*Desenvolvido por:
    Eng. Wesley Dias
    31/03/2021 - Miguelópolis-SP */
    #define led0 14
    #define led1 12
    #define led2 2
    #include
    hw_timer_t * timer = NULL; // FIZ CERTO AQUI? PODERIA ME DIZER OQUE REALMENTE FAZ ESSES PONTEIROS
    hw_timer_t * timer1 = NULL; //


    LiquidCrystal lcd(15, 4, 19, 21, 22, 5); //Pinos do LCD

    void cb_timer(){ //Função interrupção Timer0
    static unsigned int counter = 0;
    lcd.clear();
    lcd.setCursor(1,0);
    lcd.print("cb_timer(): ");
    lcd.print(counter);
    lcd.setCursor(1,1);
    lcd.print(": ");
    lcd.print(millis()/1000);
    lcd.print(" segundos");

    if (counter == 5){ // A cada 5 segundos entro nesse if...


    digitalWrite(led0,!digitalRead(led0)); //Inverte o estado de led0

    lcd.setCursor(1,1);
    lcd.print("Pisca Led RED"); //Escrevo no LCD "Pisca Led0
    counter =0; //Reinicio a variável counter
    } //end if(counter ==5)
    counter++; //incrementa counter
    }

    void cb_timer1(){ //Função interrupção Timer1 (vale os contarios feitos acima)

    static unsigned int counter2 = 0;

    if (counter2 == 24){ //entro nesse if a cada 12 segundos



    digitalWrite(led2,!digitalRead(led2));
    lcd.clear();
    lcd.print("Pisca Led TMR1");
    delay(5000);
    counter2 =0;
    }
    counter2++;
    }


    void startTimer(){ //inicialização do timer. Parametros:


    /* 0 - seleção do timer a ser usado, de 0 a 3.
    80 - prescaler. O clock principal do ESP32 é 80MHz. Dividimos por 80 para ter 1us por tick.
    true - true para contador progressivo, false para regressivo
    */
    timer = timerBegin(0, 80, true); // Timer 0
    timer1 = timerBegin(1, 80, true); // Timer 1

    /*conecta à interrupção do timer
    - timer é a instância do hw_timer
    - endereço da função a ser chamada pelo timer
    - edge=true gera uma interrupção
    */
    timerAttachInterrupt(timer, &cb_timer, true); //Timer 0
    timerAttachInterrupt(timer1, &cb_timer1, true); //Timer 1

    /* - o timer instanciado no inicio
    - o valor em us para 1s
    - auto-reload. true para repetir o alarme
    */
    timerAlarmWrite(timer, 1000000, true); //Timer 0
    timerAlarmWrite(timer1, 500000, true); //Timer 1

    //ativa o alarme
    timerAlarmEnable(timer); //Timer 0
    timerAlarmEnable(timer1); //Timer 1
    }



    void setup(){ //Configurações do MCU
    lcd.begin(16,2); //Inicializa LCD 16x2
    lcd.clear(); //Limpa LCD
    lcd.print("Configurando"); //Escreve "Configurando" no LCD
    lcd.setCursor(0,1); //Seta Cursor na Linha 0 coluna 1

    pinMode(led0,OUTPUT); //Configuração das IO's
    pinMode(led1,OUTPUT);
    pinMode(led2,OUTPUT);

    digitalWrite(led0, HIGH);
    digitalWrite(led1,HIGH);
    digitalWrite(led2,HIGH);

    delay(500);
    lcd.clear();

    startTimer(); //Desvia para startTimer para configuração dos Timers
    }

    void loop(){
    }

    ResponderExcluir
    Respostas
    1. Olá bom dia, Eng Wesley.

      Gostaria de saber se conseguiu entender o que exatamente ela faz, eu gosto de entender o que estou fazendo, então estou procurando em todo lugar uma resposta, porem nem mesmo nos exemplos do arduino obtive uma resposta, pois não tem comentário.
      Eu estou em dúvida por que na linha é a declaração de um ponteiro, mas a sintaxe geralmente é "tipo da variável *nome_do_ponteiro = valor atribuído" sendo assim hw_timer_t seria um tipo de variável?!
      no Começo pensei que poderia ser uma "espécie de instanciação", que fosse apenas para nomear o "hw_timer_t" mas também não faz sentido pela sintaxe.

      Excluir
    2. Boa noite Eng Wesley.

      Eu e um amigos estávamos olhando uma documentação e descobrimos que que o hw_timer_t é uma struct, na primeira linha é criado um ponteiro para a struct, e na na função timerBegin ele acessa a struct e passa os parâmetros e nessa mesma função é retornado "timer" que provavelmente é o que usamos para o resto do código.

      Excluir
  14. Bom dia, fiz um contrlC controlV no seu programa mas esta dando erro ele não reconhece as funções é preciso fazer alguma configuração ou chamar algum comando antes?

    ResponderExcluir