Programación OTA en ESP32


Una de las mejores cosas de ESP32 es que su firmware se puede actualizar de forma inalámbrica. Este tipo de programación se llama «Over-The-Air» (OTA) permitiendo actualizar/cargar un nuevo programa al ESP32 a través de Wi-Fi sin tener que conectar el ESP32 a un ordenador a través de USB.

La funcionalidad OTA es útil cuando no hay acceso físico al módulo ESP pero además, reduce el tiempo necesario para actualizar cada módulo ESP durante el mantenimiento.

Una ventaja clave de OTA es que una única ubicación central puede enviar una actualización a varios ESP en la misma red.

La única desventaja es que debe incluir un código OTA con cada boceto que cargue para poder usar OTA en la próxima actualización.

Hay dos formas de implementar la funcionalidad OTA en el ESP32:

  • OTA básico : las actualizaciones se envían mediante el IDE de Arduino.
  • Web Updater OTA : las actualizaciones se entregan a través de un navegador web.

Cada uno tiene sus propios beneficios, por lo que puede usar el que funcione mejor para su proyecto.

OTA Básico

En este primer apartado veremos el proceso de implementación de OTA básico.  En resumen son 3 pasos simples para usar OTA básico con el ESP32

  1. Instalación de la serie Python 2.7.x: el primer paso es instalar la serie Python 2.7.x en su computadora.
  2. Carga de firmware OTA básico en serie: cargue el boceto que contiene el firmware OTA en serie. Este es un paso necesario para realizar las actualizaciones posteriores de forma inalámbrica.
  3. Carga de nuevos bocetos por aire: ahora puede cargar nuevos bocetos al ESP32 desde Arduino IDE por aire.

1: Instalación de Python 2.7.x

Para usar la funcionalidad OTA, primero debe instalar Python 2.7.x, si aún no está instalado en su máquina. Descargue Python 2.7.x para Windows (instalador MSI) desde el sitio web oficial de Python .

Inicie el instalador y continúe con el asistente de instalación

Asegúrese de que la opción «Agregar python.exe a la ruta» esté habilitada en la sección Personalizar Python 2.7.X.

2: Carga del firmware OTA básico en serie

Debido a que la imagen de fábrica del ESP32 carece de la capacidad de actualización OTA, primero debe cargar el firmware OTA en el ESP32 a través de la interfaz serial.

Es necesario actualizar primero el firmware para poder realizar actualizaciones inalámbricas posteriores.

El complemento ESP32 para Arduino IDE incluye una biblioteca OTA, así como un ejemplo de BasicOTA. Simplemente navegue a Archivo > Ejemplos > ArduinoOTA > BasicOTA .

Antes de comenzar a cargar el boceto, debe modificar las siguientes dos variables con sus credenciales de red para que el ESP32 pueda conectarse a una red existente.

const char* ssid = "..........";
const char* password = "..........";

Cuando haya terminado, siga adelante y cargue el boceto.

#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

const char* ssid = "..........";
const char* password = "..........";

void setup() {
  Serial.begin(115200);
  Serial.println("Booting");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }

  // Port defaults to 3232
  // ArduinoOTA.setPort(3232);

  // Hostname defaults to esp3232-[MAC]
  // ArduinoOTA.setHostname("myesp32");

  // No authentication by default
  // ArduinoOTA.setPassword("admin");

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");

  ArduinoOTA
    .onStart([]() {
      String type;
      if (ArduinoOTA.getCommand() == U_FLASH)
        type = "sketch";
      else // U_SPIFFS
        type = "filesystem";

      // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
      Serial.println("Start updating " + type);
    })
    .onEnd([]() {
      Serial.println("\nEnd");
    })
    .onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
    })
    .onError([](ota_error_t error) {
      Serial.printf("Error[%u]: ", error);
      if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
      else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
      else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
      else if (error == OTA_END_ERROR) Serial.println("End Failed");
    });

  ArduinoOTA.begin();

  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  ArduinoOTA.handle();
}

Ahora, abra el Serial Monitor a 115200 baudios y presione el botón EN en el ESP32. Si todo está bien, debería ver la dirección IP dinámica asignada por su enrutador. Tome nota de ello.

Paso 3: Carga del nuevo boceto por aire

Ahora, subamos un nuevo boceto por aire. Recuerde que debes incluir el código OTA en cada sketch que subas. De lo contrario, perderá la capacidad OTA y no podrá realizar la próxima carga inalámbrica. Por lo tanto, se recomienda que modifique el código anterior para incluir su nuevo código.

Como ejemplo, incluiremos un boceto Blink simple en el código OTA básico. Recuerda modificar las variables SSID y contraseña con sus credenciales de red.

Los cambios en el programa Basic OTA están resaltados con comentarios //******.

#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

const char* ssid = "..........";
const char* password = "..........";


//******codigo programa*******
//variabls for blinking an LED with Millis
const int led = 2; // ESP32 Pin to which onboard LED is connected
unsigned long previousMillis = 0;  // will store last time LED was updated
const long interval = 1000;  // interval at which to blink (milliseconds)
int ledState = LOW;  // ledState used to set the LED

//******fin codigo programa*******

void setup() {

//******codigo programa*******
pinMode(led, OUTPUT);
//******fin codigo programa*******
  
  Serial.begin(115200);
  Serial.println("Booting");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Connection Failed! Rebooting...");
    delay(5000);
    ESP.restart();
  }

  // Port defaults to 3232
  // ArduinoOTA.setPort(3232);

  // Hostname defaults to esp3232-[MAC]
  // ArduinoOTA.setHostname("myesp32");

  // No authentication by default
  // ArduinoOTA.setPassword("admin");

  // Password can be set with it's md5 value as well
  // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3
  // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3");

  ArduinoOTA
    .onStart([]() {
      String type;
      if (ArduinoOTA.getCommand() == U_FLASH)
        type = "sketch";
      else // U_SPIFFS
        type = "filesystem";

      // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
      Serial.println("Start updating " + type);
    })
    .onEnd([]() {
      Serial.println("\nEnd");
    })
    .onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
    })
    .onError([](ota_error_t error) {
      Serial.printf("Error[%u]: ", error);
      if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
      else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
      else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
      else if (error == OTA_END_ERROR) Serial.println("End Failed");
    });

  ArduinoOTA.begin();

  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  ArduinoOTA.handle();  

//******codigo programa*******
//loop to blink without delay
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
  // save the last time you blinked the LED
  previousMillis = currentMillis;
  // if the LED is off turn it on and vice-versa:
  ledState = not(ledState);
  // set the LED with the ledState of the variable:
  digitalWrite(led,  ledState);
 //******fin codigo programa*******
  }

}

Tenga en cuenta que no hemos utilizado la delay()función para hacer que el LED parpadee. Esto se debe a que la delay()función pausa el programa. Si se genera la siguiente solicitud OTA mientras el ESP32 está en pausa esperando a delay()que se complete, su programa perderá esa solicitud.

Después de copiar el boceto anterior en su IDE de Arduino, navegue hasta la opción Herramientas > Puerto . Busque algo como: esp32-xxxxxx en your_esp_ip_address . Si no puede localizarlo, es posible que deba reiniciar su IDE.

Elija el puerto y presione el botón Cargar. El nuevo boceto se cargará en cuestión de segundos. El LED integrado debería comenzar a parpadear.

Actualizador OTA via web

La programación OTA es útil cuando necesita actualizar el código de las placas ESP32 a las que no se puede acceder fácilmente. El ejemplo que mostraremos aquí funciona cuando el ESP32 y su navegador están en su red local.

La única desventaja de OTA Web Updater es que debe agregar el código para OTA en cada boceto que cargue, para que pueda usar OTA en el futuro.

¿Cómo funciona el Actualizador web OTA?

  • El primer boceto debe cargarse a través del puerto serie. Este boceto debe contener el código para crear el actualizador web OTA, de modo que pueda cargar el código más tarde con su navegador.
  • El boceto de OTA Web Updater crea un servidor web al que puede acceder para cargar un nuevo boceto a través del navegador web.
  • Luego, debe implementar rutinas OTA en cada boceto que cargue, de modo que pueda realizar las próximas actualizaciones/cargas por aire.
  • Si carga un código sin una rutina OTA, ya no podrá acceder al servidor web y cargar un nuevo boceto por aire.

Antes de continuar , debe tener instalado el complemento ESP32 en su IDE de Arduino. Veamos los pasos a seguir

1-Carga de OTAWebUpdater

Cuando instala el complemento ESP32 para Arduino IDE, instalará automáticamente la biblioteca ArduinoOTA. Vaya a Archivo > Ejemplos > ArduinoOTA > OTAWebUpdater .

otawebupdater-arduino-ide

Debería cargarse el siguiente código.

/*
 * OTAWebUpdater.ino Example from ArduinoOTA Library
 * Rui Santos 
 * Complete Project Details https://randomnerdtutorials.com
 */

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>

const char* host = "esp32";
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

WebServer server(80);

/*
 * Login page
 */
const char* loginIndex = 
 "<form name='loginForm'>"
    "<table width='20%' bgcolor='A09F9F' align='center'>"
        "<tr>"
            "<td colspan=2>"
                "<center><font size=4><b>ESP32 Login Page</b></font></center>"
                "<br>"
            "</td>"
            "<br>"
            "<br>"
        "</tr>"
        "<td>Username:</td>"
        "<td><input type='text' size=25 name='userid'><br></td>"
        "</tr>"
        "<br>"
        "<br>"
        "<tr>"
            "<td>Password:</td>"
            "<td><input type='Password' size=25 name='pwd'><br></td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
            "<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
        "</tr>"
    "</table>"
"</form>"
"<script>"
    "function check(form)"
    "{"
    "if(form.userid.value=='admin' && form.pwd.value=='admin')"
    "{"
    "window.open('/serverIndex')"
    "}"
    "else"
    "{"
    " alert('Error Password or Username')/*displays error message*/"
    "}"
    "}"
"</script>";
 
/*
 * Server Index Page
 */
 
const char* serverIndex = 
"https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
   "<input type='file' name='update'>"
        "<input type='submit' value='Update'>"
    "</form>"
 "<div id='prg'>progress: 0%</div>"
 "<script>"
  "$('form').submit(function(e){"
  "e.preventDefault();"
  "var form = $('#upload_form')[0];"
  "var data = new FormData(form);"
  " $.ajax({"
  "url: '/update',"
  "type: 'POST',"
  "data: data,"
  "contentType: false,"
  "processData:false,"
  "xhr: function() {"
  "var xhr = new window.XMLHttpRequest();"
  "xhr.upload.addEventListener('progress', function(evt) {"
  "if (evt.lengthComputable) {"
  "var per = evt.loaded / evt.total;"
  "$('#prg').html('progress: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('success!')" 
 "},"
 "error: function (a, b, c) {"
 "}"
 "});"
 "});"
 "</script>";

/*
 * setup function
 */
void setup(void) {
  Serial.begin(115200);

  // Connect to WiFi network
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  /*use mdns for host name resolution*/
  if (!MDNS.begin(host)) { //http://esp32.local
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
  /*return index page which is stored in serverIndex */
  server.on("/", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", loginIndex);
  });
  server.on("/serverIndex", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });
  /*handling uploading firmware file */
  server.on("/update", HTTP_POST, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
    ESP.restart();
  }, []() {
    HTTPUpload& upload = server.upload();
    if (upload.status == UPLOAD_FILE_START) {
      Serial.printf("Update: %s\n", upload.filename.c_str());
      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_WRITE) {
      /* flashing firmware to ESP*/
      if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_END) {
      if (Update.end(true)) { //true to set the size to the current progress
        Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
      } else {
        Update.printError(Serial);
      }
    }
  });
  server.begin();
}

void loop(void) {
  server.handleClient();
  delay(1);
}

Debe cambiar las siguientes líneas en el código para incluir sus propias credenciales de red:

const char* ssid = "";
const char* contraseña = "";

El ejemplo de OTAWebUpdater para ESP32 crea un servidor web asíncrono donde puede cargar código nuevo en su placa sin necesidad de una conexión en serie.Suba el código anterior a tu placa ESP32. No olvide ingresar sus credenciales de red y seleccionar la placa y el puerto serial correctos.

Después de cargar el código, abra el monitor serie a una velocidad de transmisión de 115200, presione el botón de activación de ESP32 y obtendrá la dirección IP de ESP32:

Ahora, puede cargar el código a su ESP32 por aire usando un navegador en su red local.

Para probar el Actualizador web OTA, puede desconectar el ESP32 de su computadora y encenderlo con un banco de energía, por ejemplo (esto es opcional, lo sugerimos para imitar una situación en la que el ESP32 no está conectado a su computadora).

2-Actualización del nuevo código usando el actualizador web OTA

Abra un navegador en su red e ingrese la dirección IP ESP32. Deberías obtener lo siguiente:

otawebupdater-usuario-contraseña

Introduzca el nombre de usuario y la contraseña:

  • Nombre de usuario : admin
  • contraseña : admin

Puede cambiar el nombre de usuario y la contraseña en el código.

Nota:  Después de ingresar el nombre de usuario y la contraseña, será redirigido a la URL /serverIndex . No necesita ingresar el nombre de usuario y la contraseña para acceder a la URL /serverIndex . Por lo tanto, si alguien conoce la URL para cargar el nuevo código, el nombre de usuario y la contraseña no protegen la página web para que otros no puedan acceder a ella.

Debería abrirse una nueva pestaña en la URL /serverIndex . Esta página le permite cargar un nuevo código a su ESP32. Debería cargar archivos .bin (veremos cómo hacerlo en un momento).

ota-web-updater-esp32

3-Preparación del nuevo sketch

Al cargar un nuevo boceto por aire, debe tener en cuenta que debe agregar código para OTA en su nuevo boceto, de modo que siempre pueda sobrescribir cualquier boceto con uno nuevo en el futuro. Por lo tanto, le recomendamos que modifique el boceto de OTAWebUpdater para incluir su propio código.

Con fines de aprendizaje, subamos un nuevo código que parpadee un LED (sin demora). Copie el siguiente código en su IDE de Arduino.

/*
 * Rui Santos 
 * Complete Project Details https://randomnerdtutorials.com
 */

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>

const char* host = "esp32";
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

//variabls to blink without delay:
const int led = 2;
unsigned long previousMillis = 0;        // will store last time LED was updated
const long interval = 1000;           // interval at which to blink (milliseconds)
int ledState = LOW;             // ledState used to set the LED

WebServer server(80);

/*
 * Login page
 */

const char* loginIndex = 
 "<form name='loginForm'>"
    "<table width='20%' bgcolor='A09F9F' align='center'>"
        "<tr>"
            "<td colspan=2>"
                "<center><font size=4><b>ESP32 Login Page</b></font></center>"
                "<br>"
            "</td>"
            "<br>"
            "<br>"
        "</tr>"
        "<td>Username:</td>"
        "<td><input type='text' size=25 name='userid'><br></td>"
        "</tr>"
        "<br>"
        "<br>"
        "<tr>"
            "<td>Password:</td>"
            "<td><input type='Password' size=25 name='pwd'><br></td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
            "<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
        "</tr>"
    "</table>"
"</form>"
"<script>"
    "function check(form)"
    "{"
    "if(form.userid.value=='admin' && form.pwd.value=='admin')"
    "{"
    "window.open('/serverIndex')"
    "}"
    "else"
    "{"
    " alert('Error Password or Username')/*displays error message*/"
    "}"
    "}"
"</script>";
 
/*
 * Server Index Page
 */
 
const char* serverIndex = 
"https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
   "<input type='file' name='update'>"
        "<input type='submit' value='Update'>"
    "</form>"
 "<div id='prg'>progress: 0%</div>"
 "<script>"
  "$('form').submit(function(e){"
  "e.preventDefault();"
  "var form = $('#upload_form')[0];"
  "var data = new FormData(form);"
  " $.ajax({"
  "url: '/update',"
  "type: 'POST',"
  "data: data,"
  "contentType: false,"
  "processData:false,"
  "xhr: function() {"
  "var xhr = new window.XMLHttpRequest();"
  "xhr.upload.addEventListener('progress', function(evt) {"
  "if (evt.lengthComputable) {"
  "var per = evt.loaded / evt.total;"
  "$('#prg').html('progress: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('success!')" 
 "},"
 "error: function (a, b, c) {"
 "}"
 "});"
 "});"
 "</script>";

/*
 * setup function
 */
void setup(void) {
  pinMode(led, OUTPUT);
  
  Serial.begin(115200);

  // Connect to WiFi network
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  /*use mdns for host name resolution*/
  if (!MDNS.begin(host)) { //http://esp32.local
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
  /*return index page which is stored in serverIndex */
  server.on("/", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", loginIndex);
  });
  server.on("/serverIndex", HTTP_GET, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", serverIndex);
  });
  /*handling uploading firmware file */
  server.on("/update", HTTP_POST, []() {
    server.sendHeader("Connection", "close");
    server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
    ESP.restart();
  }, []() {
    HTTPUpload& upload = server.upload();
    if (upload.status == UPLOAD_FILE_START) {
      Serial.printf("Update: %s\n", upload.filename.c_str());
      if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_WRITE) {
      /* flashing firmware to ESP*/
      if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
        Update.printError(Serial);
      }
    } else if (upload.status == UPLOAD_FILE_END) {
      if (Update.end(true)) { //true to set the size to the current progress
        Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
      } else {
        Update.printError(Serial);
      }
    }
  });
  server.begin();
}

void loop(void) {
  server.handleClient();
  delay(1);

  //loop to blink without delay
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;

    // if the LED is off turn it on and vice-versa:
    ledState = not(ledState);

    // set the LED with the ledState of the variable:
    digitalWrite(led, ledState);
  }
}

Como puede ver, hemos agregado el código «parpadeo sin demora» al código OTAWebUpdater, para que podamos realizar actualizaciones más adelante.

Después de copiar el código en su IDE de Arduino, debe generar un archivo .bin .

4-Generación del archivo .bin en Arduino IDE

Guarde su boceto como LED_Web_Updater .

Para generar un archivo .bin a partir de su boceto, vaya a Boceto > Exportar binario compilado

exportar-archivo-bin-arduino-ide

Se debe crear un nuevo archivo en el boceto de la carpeta. Vaya a Bosquejo > Mostrar carpeta de bosquejo . Debe tener dos archivos en su carpeta Sketch: el archivo .ino y el archivo .bin . Debe cargar el archivo .bin mediante el actualizador web de OTA.

5-Carga del nuevo sketch por aire al ESP32

En su navegador, en la página ESP32 OTA Web Updater, haga clic en el botón Elegir archivo . Seleccione el archivo .bin generado anteriormente y luego haga clic en Actualizar .

Después de unos segundos, el código debería cargarse correctamente.

El LED incorporado ESP32 debe estar parpadeando.¡Felicidades! Ha subido un nuevo código a tu ESP32 por aire.

Las actualizaciones inalámbricas hemos visto que son útiles para cargar un nuevo código en su placa ESP32 cuando no es fácilmente accesible. En este segundo caso hemos visto el código OTA Web Updater como crea un servidor web al que puede acceder para cargar un nuevo código en su placa ESP32 utilizando un navegador web en su red local.

Fuentes

https://lastminuteengineers.com/esp32-ota-updates-arduino-ide/

ESP32 Over-the-air (OTA) Programming – Web Updater Arduino IDE

Ejecutando código en paralelo con el ESP32


El objetivo de esta post es en primer lugar comprender como se puede ejecutar la multitarea en el ESP32 para después ver como se implementa un algoritmo de cálculo de potencia simple en el ESP32 y probar la aceleración ejecutándolo en los dos núcleos del microcontrolador.

El ESP32 viene con 2 microprocesadores Xtensa LX6 de 32 bits: core 0 y core 1. Entonces, es dual core. Cuando ejecutamos código en Arduino IDE, de forma predeterminada, se ejecuta en el núcleo 1. En esta publicación, le mostraremos cómo ejecutar código en el segundo núcleo ESP32 mediante la creación de tareas. Puede ejecutar piezas de código simultáneamente en ambos núcleos y hacer que su ESP32 sea multitarea. Como nota imporante  no necesariamente necesita ejecutar doble núcleo para lograr la multitarea.

El ESP32 por tanto tiene dos núcleos Tensilica LX6 [1] que podemos usar para ejecutar código. Al momento de escribir, la forma más fácil de controlar la ejecución de código en los diferentes núcleos del ESP32 es usando FreeRTOS y asignar una tarea a cada CPU

Aunque de forma genérica son muchos los beneficios de tener más de un núcleo disponible para ejecutar código, uno de los más importantes es aumentar el rendimiento de nuestros programas. Entonces, aunque veremos en este post cómo ejecutar código en los dos núcleos del ESP32, también comprobaremos el aumento de rendimiento que podemos obtener de eso mediante una aplicación simple que sea capaz de calcular una potencia de los números de una matriz. Lo ejecutaremos en un solo núcleo y luego dividiremos el procesamiento por los dos núcleos y verificaremos si hay una ganancia en el tiempo de ejecución. Finalmente, solo para comparar, también dividiremos la ejecución entre cuatro tareas (dos asignadas a cada núcleo) solo para verificar si hay alguna aceleración para generar más tareas que núcleos.

Introducción

El ESP32 viene con 2 microprocesadores Xtensa LX6 de 32 bits, por lo que es de doble núcleo:

  • Núcleo 0
  • Núcleo 1

Cuando subimos el código al ESP32 usando el IDE de Arduino, simplemente se ejecuta; no tenemos que preocuparnos de qué núcleo ejecuta el código.

Hay una función que puede usar para identificar en qué núcleo se está ejecutando el código:

xPortGetCoreID()

Si usa esa función en un boceto de Arduino, verá que tanto setup() para la configuración() como el loopo()se ejecutan en el núcleo 1. Pruébelo usted mismo cargando el siguiente boceto en su ESP32.


void setup() {
  Serial.begin(115200);
  Serial.print("setup() running on core ");
  Serial.println(xPortGetCoreID());
}

void loop() {
  Serial.print("loop() running on core ");
  Serial.println(xPortGetCoreID());
}

Abra el Serial Monitor a una velocidad de transmisión de 115200 y verifique el núcleo en el que se ejecuta el boceto de Arduino.

Crear tareas

Arduino IDE es compatible con FreeRTOS para ESP32, que es un sistema operativo en tiempo real. Esto nos permite manejar varias tareas en paralelo que se ejecutan de forma independiente. Las tareas son fragmentos de código que ejecutan algo. Por ejemplo, puede hacer parpadear un LED, realizar una solicitud de red, medir lecturas de sensores, publicar lecturas de sensores, etc.

Para asignar partes específicas de código a un núcleo específico, debe crear tareas. Al crear una tarea, puede elegir en qué núcleo se ejecutará, así como su prioridad. Los valores de prioridad comienzan en 0, en el que 0 es la prioridad más baja. El procesador ejecutará primero las tareas con mayor prioridad.

Para crear tareas necesita seguir los siguientes pasos:

1. Crear un identificador de tarea. Un ejemplo para Task1:

TaskHandle_t Task1;

2. En la configuración()crear una tarea asignada a un núcleo específico usando elxTaskCreatePinnedToCorefunción. Esa función toma varios argumentos, incluida la prioridad y el núcleo donde se debe ejecutar la tarea (el último parámetro).

xTaskCreatePinnedToCore(
      Task1code, /* Function to implement the task */
      "Task1", /* Name of the task */
      10000,  /* Stack size in words */
      NULL,  /* Task input parameter */
      0,  /* Priority of the task */
      &Task1,  /* Task handle. */
      0); /* Core where the task should run */

3. Después de crear la tarea, debe crear una función que contenga el código para la tarea creada. En este ejemplo, debe crear la tarea e1código()función. Así es como se ve la función de tarea:

Void Task1code( void * parameter) {
  for(;;) {
    Code for task 1 - infinite loop
    (...)
  }
}

los for(;;)crean un bucle infinito. Por lo tanto, esta función se ejecuta de manera similar a la función loop(). Puede usarlo como un segundo ciclo en su código, por ejemplo.

Si durante la ejecución de su código desea eliminar la tarea creada, puede utilizar la función vTareaEliminar(), que acepta el identificador de tareas (Tarea 1) como argumento:

vTaskDelete(Task1);

Veamos cómo funcionan estos conceptos con un ejemplo sencillo.

Crear tareas en diferentes núcleos: ejemplo

Para seguir este ejemplo usaremos las siguientes partes:

Para crear diferentes tareas que se ejecuten en diferentes núcleos, crearemos dos tareas que parpadeen los LED con diferentes tiempos de retraso . Conectaremos mediante dos resistencias en serie de 330 ohmios el cátodo de un led rojo al puerto 4 (GPIO4) y el otro cátodo del led verde al GPIO2 uniendo ambas masas y conectando estas al ping GND del ESP32 .

El plano resultante seria similar al siguiente diagrama:

Crearemos dos tareas ejecutándose en diferentes núcleos:

  • Task1 se ejecuta en el núcleo 0;
  • Task2 se ejecuta en el núcleo 1;

Cargue el siguiente boceto en su ESP32 para hacer parpadear cada LED en un núcleo diferente:



TaskHandle_t Task1;
TaskHandle_t Task2;

// LED pins
const int led1 = 2;
const int led2 = 4;

void setup() {
  Serial.begin(115200); 
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);

  //create a task that will be executed in the Task1code() function, with priority 1 and executed on core 0
  xTaskCreatePinnedToCore(
                    Task1code,   /* Task function. */
                    "Task1",     /* name of task. */
                    10000,       /* Stack size of task */
                    NULL,        /* parameter of the task */
                    1,           /* priority of the task */
                    &Task1,      /* Task handle to keep track of created task */
                    0);          /* pin task to core 0 */                  
  delay(500); 

  //create a task that will be executed in the Task2code() function, with priority 1 and executed on core 1
  xTaskCreatePinnedToCore(
                    Task2code,   /* Task function. */
                    "Task2",     /* name of task. */
                    10000,       /* Stack size of task */
                    NULL,        /* parameter of the task */
                    1,           /* priority of the task */
                    &Task2,      /* Task handle to keep track of created task */
                    1);          /* pin task to core 1 */
    delay(500); 
}

//Task1code: blinks an LED every 1000 ms
void Task1code( void * pvParameters ){
  Serial.print("Task1 running on core ");
  Serial.println(xPortGetCoreID());

  for(;;){
    digitalWrite(led1, HIGH);
    delay(1000);
    digitalWrite(led1, LOW);
    delay(1000);
  } 
}

//Task2code: blinks an LED every 700 ms
void Task2code( void * pvParameters ){
  Serial.print("Task2 running on core ");
  Serial.println(xPortGetCoreID());

  for(;;){
    digitalWrite(led2, HIGH);
    delay(700);
    digitalWrite(led2, LOW);
    delay(700);
  }
}

void loop() {
  
}

Cómo funciona el código

Nota: en el código creamos dos tareas y asignamos una tarea al núcleo 0 y otra al núcleo 1. Los bocetos de Arduino se ejecutan en el núcleo 1 de forma predeterminada. Por lo tanto, podría escribir el código para Task2 en el loop()(no hubo necesidad de crear otra tarea). En este caso, creamos dos tareas diferentes con fines de aprendizaje.

Sin embargo, según los requisitos de su proyecto, puede ser más práctico organizar su código en tareas como se muestra en este ejemplo.

El código comienza creando un identificador de tarea para Task1 y Task2 llamadoTarea 1yTarea 2.

TaskHandle_t Task1;
TaskHandle_t Task2;

Asigne GPIO 2 y GPIO 4 a los LED:

const int led1 = 2; 
const int led2 = 4;

En la configuración(), inicialice el monitor serie a una velocidad en baudios de 115200:

Serial.begin(115200);

Declare los LED como salidas:

pinMode(led1, OUTPUT); 
pinMode(led2, OUTPUT);

Luego, cree Task1 usando la función  xTaskCreatePinnedToCore() :

xTaskCreatePinnedToCore(
             Task1code, /* Task function. */
             "Task1",   /* name of task. */
             10000,     /* Stack size of task */
             NULL,      /* parameter of the task */
             1,         /* priority of the task */
             &Task1,    /* Task handle to keep track of created task */
             0);        /* pin task to core 0 */

Task1 se implementará con eltarea1código()función. Entonces, necesitamos crear esa función más adelante en el código. Le damos a la tarea la prioridad 1 y la anclamos al núcleo 0.

Creamos Task2 usando el mismo método:

xTaskCreatePinnedToCore(
             Task2code,  /* Task function. */
             "Task2",    /* name of task. */
             10000,      /* Stack size of task */
             NULL,       /* parameter of the task */
             1,          /* priority of the task */
             &Task2,     /* Task handle to keep track of created task */
             1);         /* pin task to core 0 */

Después de crear las tareas, necesitamos crear las funciones que ejecutarán esas tareas.

void Task1code( void * pvParameters ){
  Serial.print("Task1 running on core ");
  Serial.println(xPortGetCoreID());

  for(;;){
    digitalWrite(led1, HIGH);
    delay(1000);
    digitalWrite(led1, LOW);
    delay(1000);
  }
}

La función para Task1 se llamatarea1código()(Puedes llamarlo como quieras). Para fines de depuración, primero imprimimos el núcleo en el que se ejecuta la tarea:

Serial.print("Task1 running on core ");
Serial.println(xPortGetCoreID());

Entonces, tenemos un ciclo infinito similar alcírculo()en el boceto de Arduino. En ese ciclo, parpadeamos el LED1 cada segundo.

Lo mismo sucede con Task2, pero parpadeamos el LED con un tiempo de retraso diferente.

void Task2code( void * pvParameters ){
  Serial.print("Task2 running on core ");
  Serial.println(xPortGetCoreID());

  for(;;){
    digitalWrite(led2, HIGH);
    delay(700);
    digitalWrite(led2, LOW);
    delay(700);
  }
}

Finalmente, el bucle loop tiene ()la función está vacía:

void loop() { }

Nota: como se mencionó anteriormente, el bucle loop ()se ejecuta en el núcleo 1. Entonces, en lugar de crear una tarea para ejecutar en el núcleo 1, simplemente puede escribir su código dentro del loop().

Demostración

Suba el código a su ESP32. Asegúrese de tener la placa y el puerto COM correctos seleccionados.

Abra el monitor serie a una velocidad en baudios de 115200. Debería recibir los siguientes mensajes:

Como era de esperar, Task1 se ejecuta en el núcleo 0, mientras que Task2 se ejecuta en el núcleo 1.

En su circuito, un LED debe parpadear cada 1 segundo y el otro debe parpadear cada 700 milisegundos.

En resumen:

  • El ESP32 es de doble núcleo;
  • Los bocetos de Arduino se ejecutan en el núcleo 1 de forma predeterminada;
  • Para usar core 0 necesitas crear tareas;
  • Puede usar la función TaskCreatePinnedToCore() para fijar una tarea específica a un núcleo específico;
  • Con este método, puede ejecutar dos tareas diferentes de forma independiente y simultánea utilizando los dos núcleos.

Hemos visto un ejemplo simple con LED. La idea es utilizar este método con proyectos más avanzados con aplicaciones del mundo real. Por ejemplo, puede ser útil usar un núcleo para tomar lecturas de sensores y otro para publicar esas lecturas en un sistema de automatización del hogar. Ahora a continuación usando la multitarea vamos a calcular como aumenta la aceleración usando ambos núcleos


Cálculo de aceleración

Básicamente, lo que queremos comprobar es cuánto aumenta la velocidad de ejecución de nuestro programa cuando pasamos de una ejecución de un solo núcleo a una ejecución de doble núcleo. Entonces, una de las formas más fáciles de hacerlo es calcular la relación entre el tiempo de ejecución del programa que se ejecuta en un solo núcleo y el que se ejecuta en los dos núcleos del ESP32 [2].

Mediremos el tiempo de ejecución para cada enfoque utilizando la función Arduino micros , que devuelve la cantidad de microsegundos desde que la placa comenzó a ejecutar el programa [3].

Entonces, para medir un bloque de ejecución de código, haremos algo similar a lo que se indica a continuación.

start = micros();
//Run code
end = micros();
execTime = end - start;

Podríamos haber usado la función FreeRTOS xTaskGetTickCount  para una mayor precisión, pero la función micros será suficiente para lo que queremos mostrar y es una función muy conocida de Arduino.

Variables globales

Comenzaremos nuestro código declarando algunas variables globales auxiliares. No vamos a necesitar ningún include.

Como vamos a comparar aceleraciones en diferentes situaciones, vamos a especificar una matriz con múltiples exponentes para probar. Además, vamos a tener una variable con el tamaño de la matriz, para que podamos iterarla. Primero vamos a probar valores pequeños para el exponente y luego comenzaremos a aumentarlos mucho.

int n[10] = {2, 3, 4, 5, 10, 50, 100, 1000, 2000, 10000 };
int nArraySize = 10;

También vamos a tener algunas variables para almacenar el tiempo de ejecución del código en los diferentes casos de uso. Reutilizaremos las  variables de inicio y finalización  de la ejecución , pero usaremos una variable diferente para cada caso de uso: ejecución de una tarea, ejecución de dos tareas y ejecución de cuatro tareas. De esta forma, almacenaremos los valores para una última comparación.

unsigned long start;
unsigned long end;
unsigned long execTimeOneTask, execTimeTwoTask, execTimeFourTask ;

Luego, declararemos un semáforo de conteo para poder sincronizar la función de configuración (que se ejecuta en una tarea de FreeRTOS) con las tareas que vamos a ejecutar. Consulte esta publicación para obtener una explicación detallada sobre cómo lograr este tipo de sincronización.

Tenga en cuenta que la función xSemaphoreCreateCounting recibe como entrada el recuento máximo y el recuento inicial del semáforo. Como tendremos como máximo 4 tareas para sincronizar, su valor máximo será 4.

SemaphoreHandle_t barrierSemaphore = xSemaphoreCreateCounting( 4, 0 );

Finalmente, declararemos dos arreglos: uno con los valores iniciales (llamado bigArray ) y otro para almacenar los resultados (llamado resultArray ). Haremos sus tallas grandes.

int bigArray[10000], resultArray[10000];


La tarea de FreeRTOS

Definiremos la función que implementará nuestro algoritmo de cálculo de potencia, para ser lanzada como tarea. Si necesita ayuda con los detalles sobre cómo definir una tarea de FreeRTOS, consulte este tutorial anterior.

Lo primero que debemos tener en cuenta es que nuestra función recibirá algunos parámetros de configuración. Dado que en algunos de los casos de uso vamos a dividir la ejecución del algoritmo en varias tareas, necesitamos controlar la parte de la matriz que cubrirá cada tarea.

Para crear solo una tarea genérica que pueda responder a todos nuestros casos de uso, pasaremos como parámetro los índices de la matriz de la que será responsable la tarea. Además, vamos a pasar el exponente, así que ahora sabemos cuántas multiplicaciones necesitamos realizar. Puede consultar con más detalle cómo pasar un argumento a una tarea de FreeRTOS en este tutorial anterior.

Entonces, primero definiremos una estructura con estos 3 parámetros, para poder pasarla a nuestra función. Puedes leer más sobre estructuras aquí . Tenga en cuenta que esta estructura se declara fuera de cualquier función. Entonces, el código se puede colocar cerca de la declaración de variables globales.

struct argsStruct {
  int arrayStart;
  int arrayEnd;
  int n;
};

Como ya tenemos declarada nuestra estructura, dentro de la función declararemos una variable del tipo de esta estructura. Luego, asignaremos el parámetro de entrada de la función a esta variable. Recuerde que los parámetros se pasan a las funciones de FreeRTOS como un puntero a nulo ( void * ) y es nuestra responsabilidad devolverlo al tipo original.

Tenga en cuenta también que le pasamos a la función un puntero a la variable original y no a la variable real, por lo que necesitamos usar el puntero para acceder al valor. 

	
argsStruct myArgs = *((argsStruct*)parameters);

Ahora, implementaremos la función de cálculo de potencia. Tenga en cuenta que podríamos haber usado la función Arduino pow , pero luego explicaré por qué no lo hicimos. Entonces, implementaremos la función con un ciclo donde multiplicaremos un valor por sí mismo n veces.

Comenzamos declarando una variable para contener el producto parcial. Luego, haremos un ciclo for para iterar todos los elementos de la matriz asignada a la tarea. Recuerda que este era nuestro objetivo inicial. Luego, para cada elemento, calcularemos su potencia y al final le asignaremos el valor a la matriz de resultados. Esto asegura que usaremos los mismos datos en todas nuestras pruebas y no cambiaremos la matriz original.

Para un código más limpio, podríamos haber implementado el algoritmo pow en una función dedicada, pero se intenta minimizar las llamadas a funciones auxiliares para un código más compacto.

Verifique el código a continuación. Dejo ahí comentada una línea de código para calcular la potencia usando la función pow , así que puedes probarlo si quieres. Tenga en cuenta que los resultados de aceleración serán considerablemente diferentes. Además, para acceder a un elemento de una variable de estructura, usamos el nombre de la variable punto («.») el nombre del elemento.

int product;
for (int i = myArgs.arrayStart; i < myArgs.arrayEnd; i++) {
 
    product = 1;
    for (int j = 0; j < myArgs.n; j++) {
      product =  product * bigArray[i];
    }
    resultArray[i]=product;
    //resultArray [i] = pow(bigArray[i], myArgs.n);
}

Compruebe el código de función completo a continuación. Tenga en cuenta que al final del código estamos aumentando el semáforo de conteo global en una unidad. Esto se hace para asegurar la sincronización con la función de configuración, ya que contaremos el tiempo de ejecución a partir de ahí.  Además, al final, estamos eliminando la tarea.

void powerTask( void * parameters ) {
 
  argsStruct  myArgs = *((argsStruct*)parameters);
 
  int product;
  for (int i = myArgs.arrayStart; i < myArgs.arrayEnd; i++) {
 
    product = 1;
    for (int j = 0; j < myArgs.n; j++) {
      product =  product * bigArray[i];
    }
    resultArray[i]=product;
    //resultArray [i] = pow(bigArray[i], myArgs.n);
  }
 
  xSemaphoreGive(barrierSemaphore);
 
  vTaskDelete(NULL);
 
}


La función de configuración

Vamos a hacer todo el código restante en la función de configuración, por lo que nuestro ciclo principal estará vacío. Comenzaremos abriendo una conexión en serie para generar los resultados de nuestras pruebas.

Serial.begin(115200);
Serial.println();

Ahora, inicializaremos nuestra matriz con algunos valores para aplicar el cálculo. Tenga en cuenta que no nos preocupa el contenido de la matriz ni el resultao real, sino los tiempos de ejecución. Entonces, vamos a inicializar la matriz con valores aleatorios solo para mostrar esas funciones, ya que no vamos a imprimir la matriz que contendrá los resultados.

Primero vamos a llamar a la función randomSeed , por lo que los valores aleatorios generados diferirán en diferentes ejecuciones del programa [4]. Si el pin analógico está desconectado, devolverá un valor correspondiente a ruido aleatorio, lo cual es ideal para pasar como entrada de la función randomSeed .

Después de eso, podemos simplemente llamar a la función aleatoria  , pasando como argumentos los valores mínimo y máximo que se pueden devolver [4]. Nuevamente, estamos haciendo esto solo con fines ilustrativos, ya que no vamos a imprimir el contenido de las matrices.

randomSeed(analogRead(0));
for (int i = 0; i < 10000; i++) {
  bigArray[i] = random(1, 10);
}

Ahora, vamos a definir las variables que se utilizarán como argumentos para nuestras tareas. Recuerda la estructura declarada anteriormente, que contendrá los índices del arreglo que procesará cada tarea y el exponente.

Entonces, el primer elemento de la estructura es el índice inicial, el segundo es el índice final y el tercero es el exponente. Declararemos estructuras para cada caso de uso (una tarea, dos tareas y cuatro tareas) como se puede ver en el código a continuación. Tenga en cuenta que en el último elemento de la estructura estamos pasando un elemento de la matriz global que declaramos con los exponentes. Entonces, como veremos en el código final, iteraremos toda la matriz, pero por ahora mantengamos las cosas simples.

argsStruct oneTask = { 0 , 1000 , n[i] };
 
argsStruct twoTasks1 = { 0 , 1000 / 2 , n[i] };
argsStruct twoTasks2 = { 1000 / 2 , 1000 , n[i] };
 
argsStruct fourTasks1 = { 0 , 1000 / 4 , n[i] };
argsStruct fourTasks2 = { 1000 / 4 , 1000 / 4 * 2, n[i]};
argsStruct fourTasks3 = { 1000 / 4 * 2, 1000 / 4 * 3, n[i]};
argsStruct fourTasks4 = { 1000 / 4 * 3 , 1000, n[i]};

Ahora, vamos a hacer la prueba usando una sola tarea. Empezamos por obtener el tiempo de ejecución con la función micros. Luego, usaremos la función  xTaskCreatePinnedToCore para crear una tarea de FreeRTOS anclada a uno de los núcleos. Elegiremos el núcleo 1.

Vamos a pasar como parámetro la dirección de la variable de estructura oneTask , que contiene los argumentos necesarios para que se ejecute la función. No olvides el yeso al vacío* .

Una cosa muy importante a tener en cuenta es que aún no hemos analizado qué tareas pueden haber sido lanzadas por el núcleo de Arduino y que pueden influir en el tiempo de ejecución. Entonces, para garantizar que nuestra tarea se ejecutará con mayor prioridad, le asignaremos un valor de 20. Recuerde, los números más altos significan una mayor prioridad de ejecución para el programador de FreeRTOS.

Después de iniciar la tarea, solicitaremos una unidad del semáforo, asegurándonos de que la función de configuración se mantendrá hasta que la nueva tarea termine de ejecutarse. Finalmente, imprimiremos el tiempo de ejecución.

Serial.println("");
Serial.println("------One task-------");
 
start = micros();
 
xTaskCreatePinnedToCore(
  powerTask,               /* Function to implement the task */
  "powerTask",              /* Name of the task */
  10000,                   /* Stack size in words */
  (void*)&oneTask,         /* Task input parameter */
  20,                      /* Priority of the task */
  NULL,                    /* Task handle. */
  1);                      /* Core where the task should run */
 
xSemaphoreTake(barrierSemaphore, portMAX_DELAY);
 
end = micros();
execTimeOneTask = end - start;
Serial.print("Exec time: ");
Serial.println(execTimeOneTask);
Serial.print("Start: ");
Serial.println(start);
Serial.print("end: ");
Serial.println(end);

Esto es más o menos lo que vamos a hacer para el resto de los casos de uso. La única diferencia es que vamos a lanzar más tareas y vamos a intentar sacar más unidades del semáforo (tantas como tareas lanzadas para ese caso de uso).

Consulte el código fuente completo a continuación, que ya incluye los casos de uso para ejecutar el código con dos tareas (una por núcleo ESP32) y cuatro tareas (dos por núcleo ESP32). Además, al final de la función de configuración, incluye la impresión de los resultados de aceleración para cada iteración.

Tenga en cuenta que el parámetro extra de la función Serial.println indica el número de lugares decimales para el número de punto flotante.

int n[10] = {2, 3, 4, 5, 10, 50, 100, 1000, 2000, 10000  };
int nArraySize = 10;
 
struct argsStruct {
  int arrayStart;
  int arrayEnd;
  int n;
};
 
unsigned long start;
unsigned long end;
unsigned long execTimeOneTask, execTimeTwoTask, execTimeFourTask ;
 
SemaphoreHandle_t barrierSemaphore = xSemaphoreCreateCounting( 4, 0 );
 
int bigArray[10000], resultArray[10000];
 
void setup() {
 
  Serial.begin(115200);
  Serial.println();
 
  randomSeed(analogRead(0));
 
  for (int i = 0; i < 10000; i++) {
    bigArray[i] = random(1, 10);
  }
 
  for (int i = 0; i < nArraySize; i++) {
 
    Serial.println("#############################");
    Serial.print("Starting test for n= ");
    Serial.println(n[i]);
 
    argsStruct oneTask = { 0 , 1000 , n[i] };
 
    argsStruct twoTasks1 = { 0 , 1000 / 2 , n[i] };
    argsStruct twoTasks2 = { 1000 / 2 , 1000 , n[i] };
 
    argsStruct fourTasks1 = { 0 , 1000 / 4 , n[i] };
    argsStruct fourTasks2 = { 1000 / 4 , 1000 / 4 * 2,   n[i]};
    argsStruct fourTasks3 = { 1000 / 4 * 2, 1000 / 4 * 3, n[i]};
    argsStruct fourTasks4 = { 1000 / 4 * 3 , 1000,     n[i]};
 
    Serial.println("");
    Serial.println("------One task-------");
 
    start = micros();
 
    xTaskCreatePinnedToCore(
      powerTask,               /* Function to implement the task */
      "powerTask",              /* Name of the task */
      10000,                   /* Stack size in words */
      (void*)&oneTask,         /* Task input parameter */
      20,                      /* Priority of the task */
      NULL,                    /* Task handle. */
      1);                      /* Core where the task should run */
 
    xSemaphoreTake(barrierSemaphore, portMAX_DELAY);
 
    end = micros();
    execTimeOneTask = end - start;
    Serial.print("Exec time: ");
    Serial.println(execTimeOneTask);
    Serial.print("Start: ");
    Serial.println(start);
    Serial.print("end: ");
    Serial.println(end);
 
    Serial.println("");
    Serial.println("------Two tasks-------");
 
    start = micros();
 
    xTaskCreatePinnedToCore(
      powerTask,                /* Function to implement the task */
      "powerTask",              /* Name of the task */
      10000,                    /* Stack size in words */
      (void*)&twoTasks1,        /* Task input parameter */
      20,                       /* Priority of the task */
      NULL,                     /* Task handle. */
      0);                       /* Core where the task should run */
 
    xTaskCreatePinnedToCore(
      powerTask,               /* Function to implement the task */
      "coreTask",              /* Name of the task */
      10000,                   /* Stack size in words */
      (void*)&twoTasks2,       /* Task input parameter */
      20,                      /* Priority of the task */
      NULL,                    /* Task handle. */
      1);                      /* Core where the task should run */
 
    for (int i = 0; i < 2; i++) {
      xSemaphoreTake(barrierSemaphore, portMAX_DELAY);
    }
 
    end = micros();
    execTimeTwoTask = end - start;
    Serial.print("Exec time: ");
    Serial.println(execTimeTwoTask);
    Serial.print("Start: ");
    Serial.println(start);
    Serial.print("end: ");
    Serial.println(end);
 
    Serial.println("");
    Serial.println("------Four tasks-------");
 
    start = micros();
 
    xTaskCreatePinnedToCore(
      powerTask,                /* Function to implement the task */
      "powerTask",              /* Name of the task */
      10000,                    /* Stack size in words */
      (void*)&fourTasks1,       /* Task input parameter */
      20,                       /* Priority of the task */
      NULL,                     /* Task handle. */
      0);                       /* Core where the task should run */
 
    xTaskCreatePinnedToCore(
      powerTask,                /* Function to implement the task */
      "powerTask",              /* Name of the task */
      10000,                    /* Stack size in words */
      (void*)&fourTasks2,       /* Task input parameter */
      20,                       /* Priority of the task */
      NULL,                     /* Task handle. */
      0);                       /* Core where the task should run */
 
    xTaskCreatePinnedToCore(
      powerTask,                /* Function to implement the task */
      "powerTask",              /* Name of the task */
      10000,                    /* Stack size in words */
      (void*)&fourTasks3,       /* Task input parameter */
      20,                       /* Priority of the task */
      NULL,                     /* Task handle. */
      1);                       /* Core where the task should run */
 
    xTaskCreatePinnedToCore(
      powerTask,                /* Function to implement the task */
      "powerTask",              /* Name of the task */
      10000,                    /* Stack size in words */
      (void*)&fourTasks4,       /* Task input parameter */
      20,                       /* Priority of the task */
      NULL,                     /* Task handle. */
      1);                       /* Core where the task should run */
 
    for (int i = 0; i < 4; i++) {
      xSemaphoreTake(barrierSemaphore, portMAX_DELAY);
    }
 
    end = micros();
    execTimeFourTask = end - start;
    Serial.print("Exec time: ");
    Serial.println(execTimeFourTask);
    Serial.print("Start: ");
    Serial.println(start);
    Serial.print("end: ");
    Serial.println(end);
 
    Serial.println();
    Serial.println("------Results-------");
 
    Serial.print("Speedup two tasks: ");
    Serial.println((float) execTimeOneTask / execTimeTwoTask, 4 );
 
    Serial.print("Speedup four tasks: ");
    Serial.println((float)execTimeOneTask / execTimeFourTask, 4 );
 
    Serial.print("Speedup four tasks vs two tasks: ");
    Serial.println((float)execTimeTwoTask / execTimeFourTask, 4 );
 
    Serial.println("#############################");
    Serial.println();
  }
 
}
 
void loop() {
 
}
 
void powerTask( void * parameters ) {
 
  argsStruct  myArgs = *((argsStruct*)parameters);
 
  int product;
  for (int i = myArgs.arrayStart; i < myArgs.arrayEnd; i++) {
 
    product = 1;
    for (int j = 0; j < myArgs.n; j++) {
      product =  product * bigArray[i];
    }
 
    resultArray[i]=product;
    //resultArray [i] = pow(bigArray[i], myArgs.n);
  }
 
  xSemaphoreGive(barrierSemaphore);
 
  vTaskDelete(NULL);
 
}


Probando el código

Para probar el código, simplemente cárguelo con el IDE de Arduino y abra el monitor serie. Debería obtener un resultado similar a la figura 1. Naturalmente, los tiempos de ejecución pueden variar.

Análisis de aceleración de doble núcleo ESP32

Figura 1 : Salida del programa de prueba de aceleración.

Los resultados para cada exponente se muestran en la siguiente tabla, en la tabla 1.

Exponente1 tarea [µs]2 tareas [µs]4 tareas [µs]Acelerar 1 tarea frente a 2 tareasAcelerar 1 tarea frente a 4 tareasAcelerar 2 tareas vs 4 tareas
22291832961.25140.77360.6182
32712073251.30920.83380.6369
43122243401.39290.91760.6588
53542493671.42170.96460.6785
105563474511.60231.23280.7694
502235118813051.88131.71260.9103
1004331223423431.93871.84850.9535
10004207221108212121.99321.98340.9951
20008399242138421901.99331.99080.9988
100004194002102832103101.99451.99420.9999

Tabla 1 – Resultados de aceleración para los exponentes definidos en el código.


Analizando los resultados

Para comprender los resultados, primero debemos tener en cuenta que no se puede paralelizar completamente todo el programa. Entonces, siempre habrá partes del código que se ejecuten en un solo núcleo, como el lanzamiento de las tareas o los mecanismos de sincronización.

Aunque no teníamos este caso de uso, muchos algoritmos de paralelización también tienen una parte en la que cada resultado parcial se agrega secuencialmente (por ejemplo, si tenemos varias tareas calculando el valor máximo de su parte de la matriz y luego la tarea principal calcula el valor máximo entre todos los resultados parciales).

Entonces, hagamos lo que hagamos, teóricamente no podemos lograr una aceleración igual a la cantidad de núcleos (hay algunas excepciones en, por ejemplo, algoritmos que buscan un valor específico y salen cuando lo encuentran, pero no nos compliquemos).

Entonces, mientras más cómputos ejecutemos en paralelo versus la porción que ejecutamos en secuencia, más aceleración tendremos. Y eso es precisamente lo que vemos en nuestros resultados. Si comenzamos con un exponente de 2 , nuestra aceleración de la ejecución de una tarea a dos tareas es solo de  1.2514 .

Pero a medida que aumentamos el valor del exponente, hacemos más iteraciones del ciclo más interno y, por lo tanto, más cálculos podemos realizar en paralelo. Así, a medida que aumentamos el exponente, vemos un aumento de la aceleración. Por ejemplo, con un exponente de 1000 , nuestra aceleración es  1.9933 . Lo cual es un valor muy alto en comparación con el anterior.

Entonces, esto significa que la parte secuencial se diluye y para grandes exponentes la paralelización compensa. Tenga en cuenta que podemos lograr este tipo de valores altos porque el algoritmo es muy simple y fácil de paralelizar. Además, debido a la forma en que FreeRTOS maneja las prioridades y los cambios de contexto de ejecución de tareas, no hay mucha sobrecarga después de que se ejecutan las tareas. Una computadora normal tiende a tener más cambios de contexto entre subprocesos y, por lo tanto, la sobrecarga secuencial es mayor.

Tenga en cuenta que no usamos la función pow para mostrar esta progresión en los valores de aceleración. La función pow usa flotantes, lo que significa que su implementación es más intensiva en computación. Entonces, obtendríamos aceleraciones mucho mejores en exponentes más bajos porque la parte paralela sería mucho más relevante que la secuencial. Puede comentar nuestro algoritmo implementado y usar la función pow  para comparar los resultados.

Además, es importante tener en cuenta que lanzar más tareas que la cantidad de núcleos no aumenta la velocidad. De hecho, tiene el efecto contrario. Como podemos ver en los resultados, la aceleración de ejecutar con 4 tareas siempre es menor que la de ejecutar con 2 tareas. De hecho, para exponentes bajos, en realidad es más lento ejecutar el código con 4 tareas (aunque estén divididas entre los dos núcleos del ESP32) que ejecutar con una tarea en un solo núcleo.

Esto es normal porque cada CPU solo puede ejecutar una tarea en un momento dado y, por lo tanto, lanzar más tareas que CPU significa más tiempo secuencial para manejarlas y más sobrecarga en los puntos de sincronización.

Sin embargo, tenga en cuenta que esto no siempre es blanco y negro. Si las tareas tuvieran algún tipo de punto de rendimiento en el que dejarían de esperar algo, entonces tener más tareas que núcleos podría ser beneficioso para usar los ciclos de CPU libres. Sin embargo, en nuestro caso, las CPU nunca rinden y siempre se están ejecutando, por lo que no hay ningún beneficio en lanzar más de 2 tareas, en cuanto a la aceleración.


La ley de Amdhal

Para complementar los resultados, veremos la ley de Amdahl . Entonces, vamos a aplicar algunas transformaciones a la fórmula de aceleración que estábamos usando. La fórmula inicial era que la aceleración es igual al tiempo de ejecución secuencial (lo llamaremos T ) dividido por el tiempo de ejecución en paralelo (lo llamaremos T Paralelo ).

fórmula de aceleración

Pero, como vimos, hay una parte del tiempo de ejecución que siempre es secuencial y otra que puede ser paralelizada. Llamemos p a la fracción del programa que podemos ejecutar en paralelo. Como es una fracción, su valor estará entre 0 y 1.

Entonces, podemos representar el tiempo de ejecución secuencial T como la porción que puede ejecutarse en paralelo ( p*T ) más la porción que no puede (1-p)*T .

ejecución secuencial

Como solo podemos dividir la parte paralela entre los núcleos de nuestra máquina, el tiempo T Parallel es similar a la fórmula anterior, excepto que la parte paralela aparece dividida por la cantidad de núcleos que tenemos disponibles.

ejecución en paralelo

Entonces, nuestra fórmula de aceleración se convierte en:

Fórmula modificada de aceleración

Ahora tenemos la aceleración escrita en función de T , el tiempo de ejecución original sin mejoras. Entonces, podemos dividir cada término por T :

acelerar la ley de amdahl

Entonces, ahora tenemos la aceleración escrita en función de la parte que puede ejecutarse en paralelo y la cantidad de núcleos. Para terminar, supongamos que tenemos una cantidad infinita de núcleos disponibles para ejecutar (el caso de uso real sería un número muy grande, pero analizaremos matemáticamente la fórmula).

Acelera un número infinito de núcleos

Como una constante dividida por infinito es igual a 0, terminamos con:

Acelerar un número infinito de núcleos expresión final

Así que, aunque la aceleración aumenta con la cantidad de recursos disponibles para la paralelización (la cantidad de núcleos, en nuestro caso), lo cierto es que la máxima aceleración teórica posible está limitada por la parte no paralela, que no podemos optimizar.

Esta es una conclusión interesante para que decidamos si vamos o no a la paralelización. Algunas veces, los algoritmos pueden paralelizarse fácilmente y tenemos una tremenda aceleración, otras el esfuerzo necesario no justifica la ganancia.

Además, otra cosa importante a tener en cuenta es que el mejor algoritmo secuencial no siempre es el mejor después de la paralelización. Entonces, a veces, es mejor usar un algoritmo que tiene menos rendimiento en la ejecución secuencial, pero termina siendo mucho mejor que el mejor secuencial en la paralelización.


Resumen

Con este post, confirmamos que las funciones disponibles para la ejecución multinúcleo funcionan bien y podemos aprovecharlas para obtener beneficios de rendimiento.

La última parte más teórica tenía el objetivo de mostrar que la paralelización no es un tema trivial, y uno no puede saltar directamente a un enfoque paralelo sin un análisis previo y esperar una aceleración igual a la cantidad de núcleos disponibles. Este razonamiento se extiende más allá del alcance del ESP32 y se aplica a la computación paralela en general.

Entonces, para aquellos que van a comenzar con la computación paralela utilizando el ESP32 para mejorar el rendimiento, es un buen comienzo para aprender primero algunos de los conceptos más teóricos.

Finalmente, tenga en cuenta que el código está orientado a mostrar los resultados, por lo que se podrían haber hecho muchas optimizaciones, como lanzar las tareas en un ciclo en lugar de repetir el código o condensar el algoritmo en una función.

Además, no buscábamos los resultados de la ejecución para confirmar que la matriz de salida era la misma porque ese no era nuestro enfoque principal. Naturalmente, pasar de secuencial a paralelo requiere una implementación cuidadosa del código y la ejecución de muchas pruebas para garantizar que el resultado sea el mismo.

Mas información en

[1] https://espressif.com/en/products/hardware/esp32/overview

[2] http://www.dcc.fc.up.pt/~fds/aulas/PPD/1112/metrics_en.pdf

[3] https://www.arduino.cc/en/reference/micros

[4] https://www.arduino.cc/en/reference/random

[5] https://techtutorialsx.com/2017/05/16/esp32-dual-core-execution-speedup/

Primeros pasos con ESP32


El famoso fabricante aleman AZDelivery en efecto nos pone disponible el ESP32 ESP-WROOM-32 NodeMCU Modulo WiFi + Bluetooth Dev Kit C Placa de Desarrollo 2.4 GHz Dual Core con Chip CP2102 , lo cual para muchos expertos es el sucesor del ESP8266 a un precio muy parecido ( unos 10€ en Amazon)

Este módulo AZ-Delivery , desarrollado por la empresa Espressif tiene el potente Microcontrolador ESP32 instalado siendo por tanto ideal para prototipos rápidos ya que esta placa de desarrollo ESP32 permite la dinámica creación de prototipos con una sencilla programación a través de un script Lua o en la construcción compatible con Arduino-IDE y Breadboard. Integra funciones Wi-Fi y Bluetooth.

Destaca además el consumo ultra bajo de energía ideal para poderlo alimentarlo con baterías. Asimismo cuenta con chips Bluetooth Wi-Fi de modo dual de 2,4 GHz y TSMC, así como tecnología de bajo consumo de 40 nm.

Esta versión tiene 38 pines y ofrece más funciones que un módulo de 30 pines y es más pequeño y más conveniente de usar.

De igual manera que en el modelo predecesor ESP8266, la funcionalidad WLAN está implementada directamente en el SoC, pero con funcionalidad Bluetooth adicional (incl. BLE).


El procesador ESP32 es mucho más potente que el ESP8266 pues combina una CPU con 2 núcleos Tensilica LX6, con una frecuencia de hasta 240 MHz, y 512 Kilobytes de SRAM en un único chip microcontrolador. Además, integra una unidad de radio para WLAN (según 802.11bgn) y Bluetooth (Classic y LE) , esta conectividad que por cierto no posee el ESP8266.

La función WLAN soporta todos los métodos de encriptación habituales, como WPA2. También puede actuar en la WLAN como punto de acceso o Sniffer en modo pasivo.

A través de los 32 pines están disponibles, entre otros, UART, I2C, SPI, DAC, ADC (12 bits) y todos los pines GPIO pueden utilizarse como entrada o salida.

Información Técnica

Voltaje de alimentación5V
Voltaje de entrada / salida3.3V
Corriente de Funcionamientomin. 500mA
SoCESP32-WROOM 32
Frecuencia de Reloj80MHz / 240MHz
RAM512kB
Memoria Flash externa4MB
Pines I / O34
InterfacesSPI, I2C, I2S, CAN, UART
Protocolos Wi-Fi802.11 b/g/n (802.11n hasta 150 Mbps)
Frecuencia Wi-Fi2.4 GHz – 2.5 GHz
BluetoothV4.2 – BLE y Bluetooth clásico
Antena inalámbricaPCB
Dimensiones56x28x13mm


Hay un complemento para el IDE de Arduino que le permite programar el ESP32 utilizando el IDE de Arduino y su lenguaje de programación. En este post, le mostraremos cómo instalar la placa ESP32 en Arduino IDE, ya sea que esté usando Windows, Mac OS X o Linux..

Antes de comenzar este procedimiento de instalación, asegúrese de tener instalada la última versión del IDE de Arduino en su computadora. Si no lo hace, desinstálelo e instálelo de nuevo. De lo contrario, es posible que no funcione.

Con el último software Arduino IDE instalado desde arduino.cc/en/Main/Software , continúe con este post.

Para instalar la placa ESP32 en su Arduino IDE, siga las siguientes instrucciones:

  1. En tu IDE de Arduino, ve a Archivo > Preferencias Instalación del complemento ESP32 en Arduino IDE Preferencias abiertas de Windows, Mac OS X y Linux
  2. Ingrese lo siguiente en el campo «URL adicionales del administrador de la junta»:https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json .
  3. Luego, haga clic en el botón «Aceptar»:Instalación del complemento ESP32 en Arduino IDE Windows, Mac OS X, Linux ingrese las URLNota: si ya tiene la URL de los tableros ESP8266, puede separar las URL con una coma de la siguiente manera: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json, http://arduino.esp8266.com/stable/package_esp8266com_index.json
  4. Abra el Administrador de tableros. Vaya a Herramientas > Tablero > Administrador de tableros…Instalación del complemento ESP32 en Arduino IDE Windows, Mac OS X, administrador de tableros abiertos de Linux
  5. Busque ESP32 y presione el botón de instalación para » ESP32 by Espressif Systems «:Complemento ESP32 en Arduino IDE Windows, Mac OS X, Linux instalado
  6. Eso es todo. Debe instalarse después de unos segundos.

Prueba de la instalación

Conecte la placa ESP32 a su ordenador mediante un cable USB. Lo más sencillo seria probar a hacer parpadear el led interno , pero realmente si queremos probar la conectividad wifi es mejor probar esto con un codigo mas elaborado. Afortunadamente no tenemos que buscar mucho porque el propio IDE de Arduino incluye los ejemplos .

Con su Arduino IDE abierto, siga estos pasos:

1. Seleccione su placa en el   menú  Herramientas  >  Placa (en el ejemplo es un  DOIT ESP32 DEVKIT V1 pero si compra el AZDelivery ESP32 ESP-WROOM-32 NodeMCU Modulo WiFi + Bluetooth Dev Kit C Placa de Desarrollo 2.4 GHz Dual Core con Chip CP2102 deberemos poner ESP32Dev Module )

Arduino IDE seleccione la placa ESP32 en el menú Herramientas

2. Seleccione el puerto (si no ve el puerto COM en su IDE de Arduino, debe instalar los  controladores CP210x USB to UART Bridge VCP ):

Arduino IDE seleccione el puerto ESP32 en el menú Herramientas

3. Abra el siguiente ejemplo en Archivo > Ejemplos > WiFi (ESP32) > WiFiScan

Ejemplo de Arduino IDE open WiFiScan para ESP32

4. Se abre un nuevo boceto en su IDE de Arduino:

Arduino IDE cargando WiFiScan ejemplo a ESP32

5. Presione el  botón Cargar  en el IDE de Arduino. Espere unos segundos mientras el código se compila y carga en su placa.

6. Si todo salió como se esperaba, debería ver un mensaje » Terminó de cargar». » mensaje.

Arduino IDE terminó de cargar ESP32 WiFiScan sketch

7. Abra el monitor serie Arduino IDE a una velocidad de transmisión de 115200:

8. Presione el botón Habilitar integrado de ESP32   y debería ver las redes disponibles cerca de su ESP32:

Instalación del complemento de prueba ESP32 en PC con Windows, Max OS X y computadora con Linux

Solución de problemas más comunes

Si intenta cargar un nuevo boceto a su ESP32 y recibe este mensaje de error » Se produjo un error fatal: no se pudo conectar a ESP32: se agotó el tiempo de espera… Conectando… «. Significa que su ESP32 no está en modo de carga/parpadeo.

Con el nombre de la placa y el puerto COM seleccionados, siga estos pasos:

  • Mantenga presionado el botón » BOOT » en su tablero ESP32
Resuelto Ocurrió un error fatal: No se pudo conectar a ESP32: Se agotó el tiempo de espera... Conectando...
  • Presione el botón » Cargar » en el IDE de Arduino para cargar su boceto:
  • Después de ver el mensaje “ Conectando…. ” en su IDE de Arduino, suelte el dedo del botón “ BOOT ”:
Arduino IDE terminó de cargar ESP32 WiFiScan sketch
  • Después de eso, debería ver el mensaje » Terminó de cargar «.

Eso es todo. Su ESP32 debería tener el nuevo boceto ejecutándose. Presione el botón » HABILITAR » para reiniciar el ESP32 y ejecutar el nuevo boceto cargado.

Solución de otros problemas

Los problemas de parpadeo pueden ser complicados de solucionar. Pruebe las sugerencias aquí si tiene problemas:

El gestor de arranque no responde

Si ve errores como «Error al conectar», es probable que su chip no esté ingresando correctamente al gestor de arranque:

  • Compruebe que está pasando el puerto serie correcto en la línea de comandos.
  • Verifique que tenga permisos para acceder al puerto serie y que otro software (como el administrador de módem en Linux) no esté tratando de interactuar con él. Un error común es dejar un terminal serial accediendo a este puerto abierto en otra ventana y olvidarse de él.
  • Compruebe que el chip esté recibiendo 3,3 V de una fuente de alimentación estable (consulte Alimentación insuficiente para obtener más detalles).
  • Verifique que todos los pines estén conectados como se describe en Selección del modo de inicio . Verifique los voltajes en cada pin con un multímetro, los pines «altos» deben estar cerca de 3.3V y los pines «bajos» deben estar cerca de 0V.
  • Si ha conectado otros dispositivos a los pines GPIO, intente eliminarlos y vea si esptool comienza a funcionar.
  • Intente usar una tasa de baudios más lenta ( es un valor muy lento que puede usar para verificar que no es un problema de tasa de baudios).-b 9600

Escribir en Flash falla a mitad de camino

Si el parpadeo falla con errores aleatorios en la mitad, vuelva a intentarlo con una velocidad en baudios más baja.

Los problemas de estabilidad de energía también pueden causar esto (consulte Energía insuficiente ).

La escritura en Flash se realiza correctamente, pero el programa no se ejecuta

Si esptool puede actualizar su módulo write_flashpero su programa no se ejecuta, verifique lo siguiente:

Modo de flash incorrecto

Algunos dispositivos solo admiten el diomodo flash. Escribir en flash con qioel modo tendrá éxito, pero el chip no puede volver a leer el flash para ejecutarlo, por lo que no sucede nada en el arranque. Prueba a pasar la opción a .-fm diowrite_flash

Consulte la página Modos de flash SPI para obtener una descripción completa de los modos de flash y cómo determinar cuáles son compatibles con su dispositivo.

Poder insuficiente

La fuente de alimentación de 3,3 V para el chip ESP tiene que suministrar grandes cantidades de corriente (hasta 70 mA continuos, pico de 200-300 mA, puede ser un poco más alto). También necesita suficiente capacitancia en el circuito de alimentación para satisfacer grandes picos de demanda de energía.

Capacitancia insuficiente

Si está utilizando una placa o módulo de desarrollo prefabricado, el regulador de potencia y los condensadores incorporados suelen ser lo suficientemente buenos, siempre que la fuente de alimentación de entrada sea adecuada.

Nota

Esto no es cierto para algunos módulos de ruptura de pines muy simples, similar a este . Estos desgloses no integran suficiente capacitancia para funcionar de manera confiable sin componentes adicionales. Los módulos OEM de montaje en superficie como ESP-WROOM02 y ESP-WROOM32 requieren un condensador volumétrico externo en la PCB para ser confiables, consulte la hoja de datos del módulo.

Clasificación de la fuente de alimentación

Es posible tener una fuente de alimentación que suministre suficiente corriente para la etapa del cargador de arranque en serie con esptool, pero no lo suficiente para el funcionamiento normal del firmware. Es posible que vea que el voltaje VCC de 3,3 V cae si lo mide con un multímetro, pero puede tener problemas incluso si esto no sucede.

Intente cambiar un suministro de 3,3 V con una clasificación de corriente más alta, agregue condensadores a la línea de alimentación y/o acorte los cables de alimentación de 3,3 V.

La salida de 3,3 V de los chips/adaptadores FTDI FT232R o las placas Arduino no suministran suficiente corriente para alimentar un chip ESP (a veces puede parecer que funciona, pero no funcionará de manera confiable). Otros adaptadores USB TTL/serie también pueden ser marginales.

Falta el cargador de arranque

ESP-IDF y utiliza un pequeño programa cargador de arranque de firmware. El cargador de arranque de hardware en ROM carga este cargador de arranque de firmware desde flash y luego ejecuta el programa. En ESP32, la imagen del cargador de arranque debe ser flasheada por ESP-IDF en el desplazamiento 0x1000.

Consulte la documentación de ESP-IDF para obtener detalles sobre qué binarios deben actualizarse en qué compensaciones.

Pines SPI que deben desconectarse

En comparación con el cargador de arranque ROM con el que habla esptool, un firmware en ejecución usa más pines del chip para acceder al flash SPI.

Si configura el modo «Quad I/O» ( , el valor predeterminado de esptool), los GPIO 7, 8, 9 y 10 se utilizan para leer el flash SPI y, de lo contrario, deben desconectarse.-fm qio

Si configura el modo «Dual I/O» ( ), los GPIO 7 y 8 se utilizan para leer el flash SPI y, de lo contrario, deben desconectarse.-fm dio

Intente desconectar cualquier cosa de esos pines (y/o cambie al modo de E/S dual si anteriormente estaba usando el modo de E/S cuádruple pero desea conectar cosas a los GPIO 9 y 10). Tenga en cuenta que si los GPIO 9 y 10 también están conectados a los pines de entrada en el chip flash SPI, aún pueden no ser adecuados para su uso como E/S de propósito general.

Además de estos pines, los GPIO 6 y 11 también se utilizan para acceder al flash SPI (en todos los modos). Sin embargo, el parpadeo generalmente fallará por completo si estos pines están conectados incorrectamente.

Accidente de etapa temprana

Utilice cualquiera de los programas de terminal serie para ver el registro de arranque. (La tasa de baudios ESP32 es 115200bps). Vea si el programa se bloquea durante el inicio temprano o si muestra un mensaje de error.

Programas de terminal serie

Hay muchos programas de terminal en serie adecuados para la depuración y la interacción en serie. El módulo pySerial (que se requiere para esptool) incluye uno de esos programas de terminal de línea de comandos: miniterm.py. Para obtener más detalles, consulte la documentación relacionada con pySerial o ejecute . Para conocer los valores exactos de configuración del puerto serie, consulte Configuración del puerto serie .miniterm -h

Seguimiento de las interacciones de Esptool

La ejecución volcará todas las interacciones en serie a la salida estándar (esto es una gran cantidad de salida). Esto puede ser útil al depurar problemas con la conexión en serie o al proporcionar información para informes de errores.esptool.py --trace

Errores comunes

Esta es una lista no exhaustiva de los errores de esptool más comunes junto con explicaciones de posibles causas y soluciones. Antes de leer cualquier consejo específico de error, se recomienda encarecidamente que revise primero toda la sección de Solución de problemas.

No se recibieron datos seriales.

Esptool no recibió ningún byte de datos o un paquete de deslizamiento exitoso . Este error generalmente implica algún tipo de problema de hardware. Esto puede deberse a que el hardware no funciona correctamente, las líneas seriales RX/TX no están conectadas o porque hay algún problema al restablecer el modo de descarga .

¡Modo de arranque incorrecto detectado (0xXX)! El chip debe estar en modo de descarga.

La comunicación con el chip funciona (se detecta el registro de arranque de la ROM), pero no se restablece automáticamente al modo de descarga.

Para resolver esto, verifique el circuito de restablecimiento automático (si su placa lo tiene), o intente restablecer el modo de descarga manualmente. Consulte Cargador de arranque manual para obtener instrucciones.

El modo de descarga se detectó correctamente, pero no se obtuvo respuesta de sincronización: la ruta de transmisión en serie parece estar inactiva.

El chip se restablece con éxito en el modo de descarga y envía datos a la computadora host, pero no recibe ninguna respuesta enviada por correo electrónico esptool. Esto implica un problema con la línea TX que se ejecuta desde el host hasta el dispositivo ESP. Verifique dos veces su placa o circuito de placa de prueba para ver si hay algún problema.

Encabezado de paquete no válido (0xXX): posible corrupción o ruido en serie.

Este error suele ser causado por una de las siguientes razones:

  • Usando un cable USB de mala calidad.
  • A veces, las placas de prueba pueden acortar los pines flash SPI en la placa y causar este tipo de problema. Intente quitar su placa de desarrollo de la placa de pruebas.
  • El chip podría estar oscureciéndose durante el flasheo. El regulador interno de 3,3 V de los chips FTDI no es suficiente para alimentar un ESP, consulte Alimentación insuficiente .

Otras cosas para probar:

  • Intente sincronizar y comunicarse a una velocidad en baudios mucho más baja, p .esptool.py --baud 9600 ...
  • Intente rastrear las interacciones en curso y vea si se recibe algo.esptool.py --trace ...
  • Intente omitir la detección automática de chips especificando el tipo de chip, ejecute .esptool.py --chip ESP32 ...

Si ninguna de las soluciones mencionadas anteriormente ayuda y su problema persiste, abra un nuevo problema .

Se produjo un error de excepción en serie

esptool.pyutiliza el módulo pySerial Python para acceder al puerto serie. Si pySerial no puede funcionar normalmente, genera un error y finaliza. Algunas de las causas de error de pySerial más comunes son:

  • No tienes permiso para acceder al puerto.
  • El puerto ya está siendo utilizado por otro software.
  • El puerto no existe.
  • El dispositivo se desconecta inesperadamente.
  • Los controladores de puerto serie necesarios no están instalados o están defectuosos.

Un ejemplo de un error pySerial:

A serial exception error occurred: read failed: [Errno 6] Device not configured

Los errores que se originan en pySerial, por lo tanto, no son un problema con esptool.py, pero generalmente son causados ​​por un problema con el hardware o los controladores.

Mas información en https://docs.espressif.com/projects/esptool/en/latest/esp32/troubleshooting.html

Por cierto, una forma rapida de conseguir el ESP32 es Amazon porque por unos 10€ podemos tenerlo en casa y empezar a experimentar porque además regalan un ebook