El ESP32 dual core y el watchdog

Llevo como 3 meses con este lío. Os explico.
Inicialmente, tenía un esp01 haciendo este trabajo y ha funcionado sin errores duarante bastante tiempo. Pero a veces se saltaba algún que otro "aviso" porque el pulso de uno de los pins es muy corto (entre 10 y 15ms), y si lo pillaba leyendo telegram (entre 1 y 2 segundos tarda).
Decidí entonces usar un ESP32 y usar los 2 cores. El core 1, el que usa por defecto el loop(), maneja el led y lee los 2 pins, El core 0 se usa para el wifi, el NTP y el control de telegram.
El programa funciona, pero todas las noches a las 03:11 se resetea el ESP32.

03:11:52.177 -> E (12865821) task_wdt: Task watchdog got triggered. The following tasks/users did not reset the watchdog in time:
03:11:52.177 -> E (12865821) task_wdt:  - IDLE0 (CPU 0)
03:11:52.177 -> E (12865821) task_wdt: Tasks currently running:
03:11:52.224 -> E (12865821) task_wdt: CPU 0: Task_2
03:11:52.224 -> E (12865821) task_wdt: CPU 1: loopTask
03:11:52.224 -> E (12865821) task_wdt: Aborting.
03:11:52.224 -> E (12865821) task_wdt: Print CPU 0 (current core) backtrace

He puesto vTaskDelay( pdMS_TO_TICKS( ) ) desde 30 hasta 50 en el core 0 en varios lugares para evitar que se bloquee, e incluso uno con 10ms en el loop, arriesgando que salte el pin durante ese delay. Nada funciona.
He leído foros donde ponen algunas sentencias para anular el watchdog, pero, o no consigo compilar o no funcionan tampoco.

Pero mirando desde lejos, da igual a la hora que encienda el ESP32, siempre cae a las 03:11. Curioso, muy curioso.
Uno de los pins solo mira si hay corriente en casa, con un alimentador de 230v a 3,3V. Aun así, le he puesto un divisor de tensión de 330 a positivo/1K a negativo.
El otro pin está conectado a una sirena de alarma. Es a 12V y para que no le haga daño al ESP32 le he puesto un TR BD139 con una resistencia 1K a positivo al emisor. Salta cuando da LOW. Esto ha estado funcionando durante algo más de 1 año con un ESP01s. El problema es que al conectar la alarma, en la sirena hay un solo pulso de 15ms a 12V positivo. No llega ni a sonar.

Estoy perdido. ¿Alguna idea?
Gracias y saludos.

Difícil adivinar sin ver el código.
Y si agregas un esquema con las conexiones, mejor que mejor.
Vamos que tu ya lo sabes.

Gracias por el interés, MaximoEsfuerzo.
Lo colgaré mañana. Come he dicho solo falla a las 03:11. Eso significa que debo limpiar y recomponer el programa, además de simplificarlo. En estos momentos y después de 3 meses desesperado, no lo entiendo ni yo. Y debo esperar a probarlo.
Lo que sí voy a subir en este post son las conexiones (muy simples).

Saludos

1 Like

Hola @bosoft ,

Mis experiencias complicadas con el watchdog han tenido origen en la prioridad de los tasks, y más aún con las funciones ejecutadas desde un task con prioridad alta, que heredan la prioridad del task que las invoca.

Mi mejor recomendación sería que verificaras las prioridades de las tareas vs la del watchdog kicker, en general mejor solución que "anular el watchdog" es "patearlo" desde dentro de la función, si es que es tan importante que se ejecute sin ser pausada y está suficientemente verificado el código para asegurar que no ha entrado en un loop interminable.

Al menos mi experiencia fue esa... ahora, qué función se ejecuta a las 3:11 y se queda dando vueltas a mayor prioridad que el kicker...?

Buena suerte!

Gaby.//

Mis disculpas @bosoft ,

También vale para las prioridades de las funciones de INT callbacks relacionadas con pines...

Saludos.

Gaby.//

No hay nada que se ejecute a una hora determinada. A lo sumo cada x milis. Eso es lo que me pierde.

y si no es molestia... ¿cómo se "patea"?
He explicado más o menos que hace el programa en cada núcleo. En el loop (core 1) lee 2 pins y se encarga de hacer que el led se encienda y se apague mediante millis.
En el core 0:

xTaskCreatePinnedToCore( loop2, "Task_2", 10000, NULL, 1, &Task2, 0);

Se dedica a enviar y recibir telegram y mirar la hora cuando se necesita (cuando no puede enviar un mensaje de telegram). En este core hay varios vTaskDelay( pdMS_TO_TICKS( ) ) de entre 30 y 50ms.
Ninguna función del core 0 se llama desde el core 1 (loop).

Como he dicho, he "limpiado" código y esta noche lo probaré. Si falla, como es habitual, mañana lo subo.

Gracias y saludos

Al principio (con un ESP01s) lo probé con interrupciones y no funcionó. Hay rebotes al activar la sirena. Es decir, varios LOWs y varios HIGHs y no sabía como controlarlo. Ya digo que como está funciona, y ha estado más de un año sin problemas.

Gracias de nuevo

Hola @bosoft ,

Patear el watchdog es desde ya muy poco recomendable, ya que debes estar seguro de no estar haciéndolo desde un loop infinito o cosas por el estilo, puedo recomendarte que leas el siguiente material (el navegador puede dar alerta de seguridad)

https://fjrg76.com/2021/04/27/hooks_utiles_y_un_watchdog/#watchdog

El código de creación del task tiene solo dos elementos que me llaman la atención:

  1. El tamaño del stack, aunque ya has mencionado que hace tareas complejas como interactuar con Telegram... casi 10KB de stack
  2. La configuración fija de la prioridad de la tarea, ya de tanto cambiar entre STM32 y ESP32 se me hace nudos, pero yo por las dudas en estos casos uso la macro configTIMER_TASK_PRIORITY, porque utilizo bastantes software timers. Si todo está bien la expansión al verla en el editor te debería dar ese mismo 1...

Por último, el watchdog es actualizado como parte del Idle Task, por lo que deberías asegurarte que cada tanto no haya ningún task de prioridad mayor al idle ejecutandose... y esa es la prioridad 0.

Suerte.
Gaby.//

Gracias por tus consejos. Intentaré implementar lo que me has dicho, pero el problema es que solo puedo hacer una prueba al día, lo cual hace interminable el asunto.
Ahora acabo de poner a probar el código "limpio" para subir mañana. Pero antes voy a cambiar a 0 la prioridad de loop2 por eso de hacer 2 pruebas en un día. Y le he subido la memoria a 20480.

xTaskCreatePinnedToCore( loop2, "Task_2", 20480, NULL, 0, &Task2, 0);

De todas formas, todo se basa en que ningún código se bloquee. En algo más de 1 año no he tenido ningún bloqueo, de hecho solo se ha desconectado para actualizaciones de la librería CTBot.

Mañana os cuento
Saludos

Probá desactivando lo referente a Telegram no sea cosa que a esa hora hagan alguna tarea en los servidores, la respuesta del bot tarde demasiado o no llegue y sea la causa del problema.

Hola @MaximoEsfuerzo ,

Pensé en eso y busqué en la web si había alguna publicación al respecto, pero al no encontrar nada y no conocer el tema lo dejé ahí, pero coincido contigo, demasiadas casualidades en estos temas no suelen existir!! Qué bueno sería que de eso se tratara!!

Saludos.
Gaby.//

Hola @bosoft ,

Error mío, no me refería a subirle la prioridad, que en este caso la estás poniendo en tskIDLE_PRIORITY, sino asegurarse que fuera un poco más baja, por lo menos la de los timers, configTIMER_TASK_PRIORITY.

Respecto del tamaño del stack sería conveniente poner alertas de nivel de espacio disponible (imagino que ya los conoces, las variantes de "high water mark"...

Pero la sugerencia que me pareció más interesante es la de @MaximoEsfuerzo y me generó la siguiente pregunta:
¿La parte referida a Telegram, está protegida como área crítica?
Me refiero a si tienes código protegido entre taskENTER_CRITICAL y taskEXIT_CRITICAL (en el caso de ESP-RTOS debes agregarle el (&mux) al final de cada uno y definir la variable antes, está en la documentación).
Incluso más: si se trata de una función llamada desde un task, esa llamada está entre taskENTER... y taskEXIT...? Si es así ahí tienes una buena posibiliad de problemas.

Suerte con las pruebas.

Saludos.
Gaby.//

Bueno, pues esta noche no ha reseteado.
No había pensado en lo de telegram, pero, como veréis en el código, solo mira si hay mensajes cada 30 segundos. No creo que sea eso, pero puedo implementar algo para evitar que lea entre las 03:10 y las 03:13 en las próximas pruebas.
Os paso el código.

// ESP32-WRRoOM-DA module
#ifndef ARDUINO_ARCH_ESP32
#error No es ESP32
#endif

bool MENSA_EN_USO=false;
String MENSA;

void loop2(void *parameter);
void envio(void);
String mi_IP(void);
void VER_MENSA(void);
String showTime(bool largo);
void HAY_LUZ(void);
bool hay_luz=true;

unsigned long LED_CON=4000UL; //tiempo apagada
#define LED_flash 30  //tiempo encendida  
unsigned long flash=0; // medida del tiempo del flash led
bool ENCEN=false;
#define ENCENDER LOW // como se enciende el led? LOW/HIGH

#define LED_BUILTIN 23 // led
#define CENTRAL 34 // pin conectado a la central divisor 10k/10k + bd139 + pull-up 1K
#define LUZ 33 // detectar si hay luz a 3.3v / divisor de 330+/1k- 
#define TELEGRAM 30000 // tiempo entre llamadas a telegram

String errors,IP_PUBLICA_ACTUAL;
bool ANTES=true, test=true;

unsigned long  t_wifi=0; // tiempo inicial -TELEGRAM para que entre la primera vez
bool wifi_ok=false;
bool init_time=false;

#include "time.h"
#include "esp_sntp.h"
#include <WiFi.h>
#include <HTTPClient.h>

String ssid  = ""; // REPLACE mySSID WITH YOUR WIFI SSID
String pass  = ""; // REPLACE myPassword YOUR WIFI PASSWORD, IF ANY 
String TOKEN = "";  // REPLACE myToken WITH YOUR TELEGRAM BOT TOKEN

#include "CTBot.h"
CTBot myBot;
TBMessage msg;

TaskHandle_t Task2;

void setup(){

  Serial.begin(115200);
  pinMode(CENTRAL,INPUT); //sirena
  pinMode(LUZ,INPUT);
  pinMode(LED_BUILTIN,OUTPUT);

  digitalWrite(LED_BUILTIN, HIGH);
  
  flash=millis(); // iniciar flash con 300ms
  LED_CON=300UL;
  myBot.setMaxConnectionRetries(20); // 20 * 500 milis = 10 seg
    // connect to the desired access point
  myBot.wifiConnect(ssid, pass);

  // set the telegram bot token
  myBot.setTelegramToken(TOKEN);
  
  // check if all things are ok
  if (myBot.testConnection())
    Serial.println("\ntestConnection OK");
  else
    Serial.println("\ntestConnection NOK");
  
  t_wifi=0-TELEGRAM;
 
	// iniciar NTP
  sntp_set_time_sync_notification_cb(timeavailable);
  const char *TZstr ="CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00";             
  configTzTime(TZstr,"es.pool.ntp.org","pool.ntp.org", "time.nist.gov");
  showTime(false); // primera cridada
   
  delay(1000);
  
  xTaskCreatePinnedToCore( loop2, "Task_2", 20480, NULL, 0, &Task2, 0);

  delay(2500);

  if(!init_time){ // ver si time esta ja good?
    do{
        Serial.print(".");
        delay(20);
    }while(!init_time);
    
    Serial.println();
    }
  
  mi_IP(); // ller IP publica: se usa pa saber si hay internet
  MENSA="ESP32D ha arrancado";
  MENSA+="\r\nIP: "+IP_PUBLICA_ACTUAL;
  envio();
  Serial.println("en marcha");
  }

//#define PROBA_RESET

void loop() {

  int resul;  
  
  test=digitalRead(CENTRAL);
  
  if(test!=ANTES){ //de inicio ANTES=true test=true
		// si no es pulso de setup, cambiar a pulso pin activo
		if(!test){
			if(LED_CON!=300) LED_CON=2500UL;
			VER_MENSA(); //mirar si mensa esta ocupado
			MENSA+=showTime(false); 
			MENSA+="La alarma se ha conectado";
			MENSA_EN_USO=false;
			// envio(); se envia desde loop2
			}
		ANTES=test;
        } // hay señal en sirena
 
  HAY_LUZ();
  wifi_flash();
  vTaskDelay( pdMS_TO_TICKS( 10 ) );
}

void wifi_flash()
{
	 if(!LED_CON) return; // no led
	 
     if(ENCEN)
        if(millis() - flash >LED_flash){
            digitalWrite(LED_BUILTIN, ENCENDER);
            flash=millis();
            ENCEN=false;
            }
     if(!ENCEN)
        if(millis() - flash >LED_CON){
              digitalWrite(LED_BUILTIN, !ENCENDER);
              flash=millis();
              ENCEN=true;
              }          
}		  

// espera a que se termine de usar MENSA
void VER_MENSA(void){
  while(MENSA_EN_USO) delay(10);
  MENSA_EN_USO=true;
}

void envio(void)
{
int32_t res=1;
String TEMP="";

 if(MENSA=="" && errors=="") return;
 if(MENSA!=""){
	VER_MENSA();
	TEMP=MENSA;
	MENSA="";
	MENSA_EN_USO=false;
	TEMP.trim();
	if(TEMP=="" && errors=="") return;
	}
  
  vTaskDelay( pdMS_TO_TICKS( 30 ) );
  
  if(TEMP!=""){      

      Serial.print("envio() running on core: ");
      Serial.println(xPortGetCoreID());
      Serial.println("TEMP ["+TEMP+"]");

      res=myBot.sendMessage(msg.sender.id,TEMP); // enviar
      }

  if(res<1){ // error de envio telegram

    if(LED_CON!=300) LED_CON=900UL; // de setup no cambia

    if(errors.length()) errors+="\r\n";
    errors+"["+String(res)+"]";
    errors+="<"+showTime(true)+">";
    errors+=TEMP;
	Serial.println("errores ["+errors+"]");
    }
  else{
	if(LED_CON!=300) LED_CON=5000UL; // de setup no cambia
    if(errors.length()){
        res=myBot.sendMessage(msg.sender.id,errors); // enviar?
        if(res>0){
            Serial.print("envio() running on core: ");
            Serial.println(xPortGetCoreID());
            Serial.println("Errores ["+errors+"]");
            errors=""; // sin error? borrar
            }
        }

    }
  TEMP="";
  vTaskDelay( pdMS_TO_TICKS( 30 ) );
}

// largo false=normal: true=largo
String showTime(bool largo)
{
  struct tm timeinfo;
  time_t nowSecs=time(nullptr);
  char time_output[60];

  if (!getLocalTime(&timeinfo)) return "";
  
  strftime(time_output, 56,"%d-%m-%y a las %T" , localtime(&nowSecs));

  return (String)time_output;  
  }

// Callback function (gets called when time adjusts via NTP)
void timeavailable(struct timeval *t) {

  Serial.println("Got time adjustment from NTP!");
  init_time=true;  
}
  
String mi_IP()
{ 
// ######## LEER IP PUBLICA ######## //
  WiFiClient client;

  HTTPClient http;
  String  new_ip="";
    http.begin(client, "http://ifconfig.me/ip");
    int httpCode = http.GET();
    if (httpCode == HTTP_CODE_OK)  new_ip=http.getString();
    else new_ip="";
    
  http.end();
  new_ip.trim();
  IP_PUBLICA_ACTUAL=new_ip;  
  return new_ip;
}

// core 1
void loop2(void *parameter){
  
  for(;;){
    
	envio(); // mirar si hay mensajes que enviar
      
    if( millis () - t_wifi >= TELEGRAM ){ // revisar si hay mensajes de entrada
      
		vTaskDelay( pdMS_TO_TICKS( 10 ) );
              
        while(myBot.getNewMessage(msg,true)==true){  // repetir mentres nian datos
                Serial.println("TEXT= "+msg.text);                    
                LED_CON=5000UL; // flash normal

                if(msg.text.indexOf("start")!=-1){
                        mi_IP();
                        VER_MENSA();
                        MENSA+="[ESP32D start] esperando\r\n";
                        MENSA+="\r\nIP: "+IP_PUBLICA_ACTUAL;                         
                        MENSA+="\r\n"+showTime(true);
                        MENSA+="\r\n";
                        MENSA+= hay_luz==true ? "SI" : "NO";
                        MENSA+=" hay luz";
                        MENSA_EN_USO=false;
                        envio();
                        }
				}
      t_wifi = millis(); // tiempo nuevo 
      }     
  vTaskDelay( pdMS_TO_TICKS( 50 ) );
  }
  
} // loop2

void HAY_LUZ(void){
  
    int luz=digitalRead(LUZ); // hay luz?

    if(hay_luz && luz) return; // SI
	
    if(!luz && hay_luz){// se ha ido
		    hay_luz=false;
		    VER_MENSA();
        MENSA+="Se ha ido la luz";
        MENSA_EN_USO=false;
        //envio(); //desde loop2
        }
      
    if(!hay_luz && luz){ //ya hay luz
		    VER_MENSA();
        MENSA +="Ya hay luz";
		    MENSA_EN_USO=false;
		    //envio();
        }
}

En cuanto a lo último de gabgold, si te soy sincero, no he entendido nada. Por eso te ruego que le des un vistazo al código en cuanto al watchdog se refiere.

Gracias a los 2. Ahora iré restituyendo el código poco a poco a ver si "aguanta". Serán muchos días de pruebas.
Saludos

Hola @bosoft ,

Respecto del código, no tengo posibilidad de verificarlo completamente, en un repaso rápido la única línea que me hace ruido a simple vista es:

Esa línea bloquea todo mientras MENSA_EN_USO == true... cuánto puede llegar a demorar esa variable en ponerse en false? Estás bloqueando todo el código durante todo ese tiempo.

Si con "lo úlitimo" te refieres a taskENTER_CRITICAL y taskEXIT_CRITICAL se refiere a los mecanismos existentes para evitar que una secuencia de instrucciones sean interumpidas por un cambio de tarea realizada por el scheduler... Pero ahora que veo tu código es una cosa para preocuparse más adelante porque al menos tú no los estás usando.

Lo único relevante que veo es que utilizas el loop() clásico de Arduino para ejecutar código en él. El loop() es parte de la configuración que hace Arduino para presentar una cara más "amable" del ESP-RTOS, que configura un task con su propia prioridad y con ciertas reglas de ejecución que funcionan de maravillas cuando lo utilizas en un entorno no multi-tareas, y si lo utilizas en multitareas puede traer algunas consecuencias importantes. No conoces la prioirdad asignada, el tamaño del stack asignado... (obviamente, lo puedees buscar en la red). Pero tú estás creando y ejecutando código desde otro task...

Es por eso que muchas veces los videos explicativos que ves en la red utilizan el loop() sólo para crear la auténtica tarea de control de tu aplicación, a la cual le asignan el stack de memoria y la prioridad que corresponda, e inmediatamente borran el task en el que se ejecuta el loop() mediante xTaskDelete(NULL);.

Todas estas son pistas sobre las cuales puedes seguir trabajando, porque la idea de evitar ejecutar tu aplicación en ciertos horarios es un blanco móvil, el día de mañana lo que sea que la está reiniciando lo cambian de horario...

Si quieres saber más sobre las características mencionadas del loop() puedes ingresar en Gemini la consulta Cuál es el nivel de prioridad de la tarea loop() en Arduino esp32-rtos?

Mucha Suerte.

G.//

Hola gabygold y muchas gracias por tu interés.
Respecto al código while(MENSA_EN_USO) delay(10); Cuando se intenta poner algo en MENSA se hace usando

			VER_MENSA(); //mirar si mensa esta ocupado
			MENSA+=showTime(false); 
			MENSA+="La alarma se ha conectado";
			MENSA_EN_USO=false;

Si te das cuenta, lo hago todo de un tirón, para evitar que este demasiado tiempo la variable MENSA "bloqueada". No creo que llegue a pasar más de 1 o 2ms. Es la única función que se llama desde los 2 loops.

Respecto al loop(). Si revisas el código, verás que se dedica a testear los 2 pins y hacer el flash del led. Es muy poco código y lo realiza muy rápido por lo corto que es. No hay más. La verdad es que, mientras no haga resets, no me importa la prioridad, o al menos eso creo, ya que entiendo que se usa el core 1 solo para eso. No tengo pensado insertar más código, aparte de depurarlo.
El core 0 (loop2) es el crítico en eso de la prioridad/watchdog, pero me da igual que sea lento o rápido. No hay prisa, Ya lo hará cuando tenga "un momento".

No hay más ni va a haber más. Este código ha estado funcionando con un ESP01s más de un año como ya he comentado y simplemente se ha cambiado a un ESP32 por un error de lectura del pin de alarma cada 2 o 3 semanas. Mientras se hacía la lectura del telegram (unos 2 segundos), no se enteraba de que el pin se había puesto LOW (10-15ms) y vuelto a HIGH. El pin de corriente tarda más en volver a HIGH.

Si así funciona, lo dejaré tal cual a ver el resultado. Si veo que a las 03:11 vuelve a resetear, le pondré un salto para que no entre a leer telegram a esa hora a ver que hace.

Ya os iré informando si funciona correctamente en días posteriores, de momento está funcionando perfecto desde ayer a las 21:15, con lo cual lleva 24h sin problemas.

Saludos y, de nuevo, muchas gracias

Hola a todos.
Segundo día sin reset. Entiendo, pues, que el watchdog está "reparado".
He hecho la rutina para el bloqueo de lectura de telegram, pero ahora no sé si continuará siendo a las 03:11 o también habrá cambiado de hora. De todas formas lo dejaré comentado por si alguna noche salta.
Voy a poner el código inicial a ver si el problema está ahí al tiempo que iré depurando.

Gracias por vuestra ayuda. Seguiré informando

1 Like

Pero qué pasó con el tema de la sirena (que por cierto no me quedó muy claro), suena como debe?
Porque ese era el problema principal, no?

Si, suena cuando debe y bien.
El tema es, que para saber si la alarma ha sido activada, sin entender el motivo, un relé la activa por unos 10-15 ms. A veces se oye un clic. Otras no te enteras.

Saludos

Hola,
El sistema lleva activo 3 noches y sin ningún error. Está todavía en modo provisional.
No he activado el "salto" de leer telegram a las 3:11.
Por lo que parece, al aumentar la memoria y poner prioridad 0 ha resuelto el problema del watchdog, Con otro ESP32D voy a intentar averiguar cuál de los 2 cambios es el "culpable".

Saludos y gracias por vuestras sugerencias y ayudas. Seguiré informando.

1 Like

Hola de nuevo
Pues nada, no hay forma de averiguar que es lo que hace que salte el watchdog.
El que está instalado sigue sin fallar desde hace 12 días sin parar.
Las pruebas las estoy haciendo con un ESP32 aparte y me está volviendo loco. Recuerdo que solo puedo hacer una prueba diaria.

Hace 3 días me salto el watchdog con el código de hace más de 1 mes. Para probar al día siguiente solo modifiqué el valor de una variable (la primera que en el código tenía valor) de 30000 a 40000. Esa variable controla el intervalo entre lecturas a telegram. Pues bien, para saber si era eso, devolví el valor a 30000 (30 seg) y lleva 2 días sin errores.
Voy a seguir haciendo pruebas, sobre todo quitando delays para hacer que el código sea lo más rápido posible, pero con las pruebas hechas no me aclara nada.

Saludos