O processo de upload Over-the-Air (OTA) permite atualizar o firmware de um dispositivo sem a necessidade de conexão física. Para que o upload OTA funcione, a memória do dispositivo precisa ser organizada de forma a permitir a atualização do firmware sem corromper o código em execução. Neste artigo iremos explorar esse recurso voltado para os chips: Esp32 e Esp8266.
Etapa 1: O New Sketch será gravado na área de memória flash disponível entre o Current Sketch e a partição SPIFF.
[Current Sketch] <------> [New Sketch] <------> [SPIFF]
Note
SPIFF (Single Page In File File System) é um sistema de arquivos de página única, projetado para ser usado em dispositivos com memória flash limitada, como microcontroladores.
Etapa 2: Após a próxima reinicialização, o bootloader "eboot" processa os comandos recebidos. Em seguida, o novo sketch é gravado sobre o antigo e o dispositivo é iniciado com o novo firmware.
[New Sketch] <----------------------> [SPIFF]
Antes de fazer o upload de novos sketches para o seu dispositivo com a funcionalidade OTA (Over-the-Air), é fundamental que você configure o sketch principal com as funcionalidades OTA propriamente ditas.
// C++
/* Inclusão de biblioteca e constantes */
#ifdef ESP8266
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#else
#include <WiFi.h>
#include <ESPmDNS.h>
#endif
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
const char* ssid = "..........";
const char* password = "..........";
/* Configurações iniciais */
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();
}
/* Funções de Callback */
ArduinoOTA.onStart([]()
{
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
{
type = "sketch";
} else
{
type = "filesystem";
}
Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]()
{
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total)
{
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.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());
}
/* Tarefa principal */
void loop()
{
ArduinoOTA.handle();
}
Inclusão de bibliotecas
#ifdef ESP8266
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#else
#include <WiFi.h>
#include <ESPmDNS.h> #endif
#ifdef ESP8266
: Esta diretiva verifica se a constanteESP8266
está definida. Essa constante é geralmente definida pelo compilador quando você está compilando o código para um dispositivo ESP8266.#include <ESP8266WiFi.h>
: Se a constanteESP8266
estiver definida, essa linha inclui a bibliotecaESP8266WiFi.h
, que fornece as funções necessárias para conectar o dispositivo ESP8266 a uma rede Wi-Fi.#include <ESP8266mDNS.h>
: Se a constanteESP8266
estiver definida, essa linha inclui a bibliotecaESP8266mDNS.h
, que fornece as funções necessárias para usar o mDNS (Multicast DNS) no ESP8266.#else
: Esta diretiva indica que o código a seguir será executado caso a constanteESP8266
não esteja definida.#include <WiFi.h>
: Se a constanteESP8266
não estiver definida, essa linha inclui a bibliotecaWiFi.h
, que fornece as funções necessárias para conectar o dispositivo a uma rede Wi-Fi. *#include <ESPmDNS.h>
: Se a constanteESP8266
não estiver definida, essa linha inclui a bibliotecaESPmDNS.h
, que fornece as funções necessárias para usar o mDNS (Multicast DNS).
Constantes
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();
}
//Continua...
-
void setup()
: Esta função é executada uma vez no início do programa, antes do loop principal (loop()
). Ela configura as configurações iniciais do dispositivo. -
Serial.begin(115200);
: Inicializa a comunicação serial com a taxa de baud de 115200 bps. Isso permite que o dispositivo se comunique com um computador ou outro dispositivo serial, enviando e recebendo dados. -
Serial.println("Booting");
: Imprime a mensagem "Booting" na serial para indicar que o dispositivo está iniciando. Isso ajuda a monitorar o processo de inicialização. -
WiFi.mode(WIFI_STA);
: Define o modo de operação da interface Wi-Fi como estação (cliente). Isso significa que o dispositivo irá se conectar a uma rede Wi-Fi existente, em vez de criar sua própria rede. -
WiFi.begin(ssid, password);
: Inicia a conexão com a rede Wi-Fi usando o SSID e a senha definidos anteriormente nas constantesssid
epassword
. O dispositivo tenta se conectar à rede Wi-Fi especificada. -
while (WiFi.waitForConnectResult() != WL_CONNECTED) { ... }
: Este loop aguarda até que o dispositivo seja conectado à rede Wi-Fi. A funçãoWiFi.waitForConnectResult()
retorna o estado da conexão Wi-Fi:WL_CONNECTED
: Indica que o dispositivo está conectado à rede Wi-Fi.- Outros valores: Indicam que a conexão falhou.
-
Serial.println("Connection Failed! Rebooting...");
: Se a conexão com a rede Wi-Fi falhar, essa linha imprime uma mensagem de erro na serial. -
delay(5000);
: Espera 5 segundos antes de reiniciar o dispositivo. Isso dá tempo para visualizar a mensagem de erro na serial. -
ESP.restart();
: Reinicia o dispositivo ESP. Isso permite que o dispositivo tente se conectar à rede Wi-Fi novamente.
Uma função de callback
é uma função fornecida como argumento a outra função. Essas funções, que são chamadas quando a eventos específicos, ocorrem durante o processo de atualização de firmware.
//Continua...
/* Funções de Callback */
ArduinoOTA.onStart([]()
{
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
{
type = "sketch";
} else
{
type = "filesystem";
}
Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]()
{
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total)
{
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.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());
}
ArduinoOTA.onStart([]() { ... });
: Define um callback que é chamado quando o processo de atualização começa. A função anônima dentro do callback define o que acontece quando a atualização é iniciada.ArduinoOTA.onEnd([]() { ... });
: Define um callback que é chamado quando o processo de atualização termina. A função anônima dentro do callback define o que acontece quando a atualização termina.ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { ... });
: Define um callback que é chamado durante o processo de atualização para mostrar o progresso. A função anônima dentro do callback recebe dois parâmetros:progress
etotal
, que representam o progresso atual e o tamanho total da atualização, respectivamente.ArduinoOTA.onError([](ota_error_t error) { ... });
: Define um callback que é chamado caso ocorra algum erro durante o processo de atualização. A função anônima dentro do callback recebe um parâmetroerror
, que representa o tipo de erro ocorrido.ArduinoOTA.begin();
: Inicia o servidor ArduinoOTA para receber atualizações de firmware.Serial.println("Ready");
: Imprime uma mensagem na serial informando que o dispositivo está pronto para receber atualizações.Serial.print("IP address: ");
: Imprime uma mensagem na serial informando que o endereço IP do dispositivo será mostrado a seguir.Serial.println(WiFi.localIP());
: Imprime o endereço IP do dispositivo na serial.
void loop()
{
ArduinoOTA.handle();
}
void loop()
: Esta função é executada repetidamente após a funçãosetup()
ser concluída. Ela contém o código principal do programa que é executado em loop.ArduinoOTA.handle();
: Esta linha de código é a chave para o funcionamento do ArduinoOTA. Ela processa todas as solicitações de atualização de firmware que podem estar pendentes.
Note
Sketch carregado, agora você vai notar que ao visitar a aba Port
na IDE Arduino estará um endereço IP. Este corresponde ao seu ESP que aguarda para receber novos uploads!
Note que o sketch não muda muito. Isso se deve ao fato de que desejamos manter a funcionalidade OTA sempre disponível, não é mesmo?!
LED de prova: Tente fazer uploads de algoritmos simples via OTA. E com o tempo, avance para algoritmos mais complexos.
#ifdef ESP8266
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#else
#include <WiFi.h>
#include <ESPmDNS.h>
#endif
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#define LED_PROVA 2
const char* ssid = "..........";
const char* password = "..........";
void setup()
{
pinMode(LED_PROVA, OUTPUT);
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();
}
ArduinoOTA.onStart([]()
{
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
{
type = "sketch";
} else
{
type = "filesystem";
}
Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total)
{
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.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()
{
digitalWrite(LED_PROVA, HIGH);
delay(500);
digitalWrite(LED_PROVA, LOW);
delay(500);
ArduinoOTA.handle(); //Não esqueça de mim!
}
Caution
Não esqueça de incluir a função ArduinoOTA.handle()
no fim de seus projetos.
🐈⬛ @leonardoalvessousa
- Conte a outras pessoas sobre este projeto 📢;
- Pague uma cerveja para o autor 🍺;