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?
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); ..... }
32 Comentários
Obrigado mestre!
ResponderExcluirabraços de Portugal..
ResponderExcluirO seu canal é mesmo giro..
Parabens Fernando pelo seu canal..já aprendi bastante com ele..
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
ResponderExcluirEstou com o mesmo problema
Excluirtambem estou igual
Excluirprovavelmente na ide do arduino deve estar selecionado outra placa , no meu caso quando coloquei a heltec esp 32 lora (v2) funcionou normalmente.
ExcluirBoa 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!
ResponderExcluirBom dia, você pode criar uma "xTask" que faz esse trabalho de WebUpload separadamente do loop principal.
ExcluirBoa Tarde Fernando, em primeiro lugar quero lhe dizer que sou profundo admirador de seu trabalho,
ResponderExcluirsuas 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.
DIFICIL DE TER UMA RESPOSTA...
Excluirnã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
Excluirnao consigo baixar
ResponderExcluirOlá! Como implementa o Watch Dog no ESP8266 NodeMcu ?
ResponderExcluirJá 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.
Excluirpode 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
esp_restart_noos();
ResponderExcluirDA ERRO E PONTO FINAL NA CONVEVRSA !!!
acho que é a versao da IDE, to usando IDE do linux e pro restar uso a função esp_restart();
Excluirboa 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
ExcluirBom dia
ResponderExcluirFunciona no Nodemcu 12E?
Grato.
esse já tem WD programado, 3,5seg o via software e 8 seg via hardware
Excluirupdate: tive que trocar o esp_restart_noos() por esp_restart(). Somente assim ele compilou.
ResponderExcluirisso mesmo, aqui foi assim tb vlw
ExcluirNo esp8266 funciona?
ResponderExcluirOlá boa noite, gostaria de saber se esse código funciona no ESP8266?
ResponderExcluiresp8266 já tem WD programado por padrão, é realimentado sempre que aparece um delay() ou que reinicia o loop{}
ExcluirEste 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.
ResponderExcluirHenry, 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.
ExcluirEste watchdog não vai funcionar em caso de congelamento do processador.
ResponderExcluirO 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.
Obrigado Fernando! Funcionou perfeitamente.
ResponderExcluirApenas tive que substituir o "esp_restart_noos();" por "ESP.restart();" no meu caso.
Olá boa tarde.
ResponderExcluirFernando 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(){
}
Olá bom dia, Eng Wesley.
ExcluirGostaria 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.
Boa noite Eng Wesley.
ExcluirEu 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.
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