ESP32 OTA status on TFT display
If you have your ESP32 connected to a display, wouldn’t it be nice to see the status of an OTA update in real time on this display?
I thought it is a nice little add on to my application. And as it is working fine within my ESP32 weather station, I share here as a stand alone code, which makes it easier to understand and integrate into your own application.
In this example I will use a 1.44″ TFT display from Elecrow. This display is equipped with a ILI9163 display driver IC. Bodmer’s TFT_eSPI library works fine on the ESP32 with the ILI9163. Depending on your display you might choose another library. In any way you need to adapt all calls to write to the display to your specific display and library.
The hardware connection is quite simple.
I am using the VSPI bus of the ESP32 to send commands and data to the display. The communication to ILI9163 is only from the ESP32 to the display, there is no data returning to the ESP32. So all we need to connect is the MOSI pin. In the above schematic you can see that I use the MISO pin as A0/CD line which is used to indicates to the display if the bytes sent are a command or data.
And here the software:
We need to include some libraries for the TFT display, the WiFi connection and for OTA.
1 2 3 4 5 |
#include <Arduino.h> #include <WiFi.h> #include <ESPmDNS.h> #include <ArduinoOTA.h> #include <TFT_eSPI.h> |
Then we need some variables and declarations. compileDate is the date and time the app was compiled, otaStatus will be used during the OTA update to remember the progress. tft is the pointer to the display class. activateOTA() is the declaration of the function where I put all the OTA initialization code.
1 2 3 4 5 6 7 8 9 |
/** Build time */ const char compileDate[] = __DATE__ " " __TIME__; /** OTA progress */ int otaStatus = 0; /** TFT_eSPI class for display */ TFT_eSPI tft = TFT_eSPI(); /** Function definition */ void activateOTA(); |
In setup() we setup the Serial connection, initialize the display and connect to the WiFi network. Of course SSID and PASSWD needs to be replaced with the credentials of your WiFi network. After the connection to the WiFi was successful activateOTA is called to initialize and activate the OTA function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
void setup() { // Start serial connection Serial.begin(115200); // Initialize TFT screen tft.init(); // Clear screen tft.fillScreen(TFT_BLACK); tft.setCursor(0, 40); tft.setTextColor(TFT_WHITE); // Put some information on the screen tft.println("Build: "); tft.setTextSize(1); tft.println(compileDate); // Connect to WiFi WiFi.mode(WIFI_STA); WiFi.begin("SSID", "PASSWD"); uint32_t startTime = millis(); while (WiFi.waitForConnectResult() != WL_CONNECTED) { if (millis()-startTime > 30000) { // wait maximum 30 seconds for a connection tft.println("Failed to connect to WiFI"); tft.println("Rebooting in 30 seconds"); delay(30000); esp_restart(); } } // WiFi connection successfull tft.println("Connected to "); tft.println(WiFi.SSID()); tft.println("with IP address "); tft.println(WiFi.localIP()); // Activate OTA activateOTA(); tft.println("\n\nWaiting for OTA to start"); } |
The main loop is only waiting for the OTA to start.
1 2 3 |
void loop() { ArduinoOTA.handle(); } |
And here is the main part of the software.
To make it easy to select the right device for an OTA update I give each module a name that is put together from the module type and his MAC address. This name is computed inside the application be reading out the MAC address and then use sprintf to create the module name. This name is then used when we initialize the OTA functionality
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/** * Activate OTA */ void activateOTA() { uint8_t baseMac[6]; // Get MAC address for WiFi station esp_read_mac(baseMac, ESP_MAC_WIFI_STA); char baseMacChr[19] = {0}; sprintf(baseMacChr, "ESP32-%02X%02X%02X%02X%02X%02X" , baseMac[0], baseMac[1], baseMac[2] , baseMac[3], baseMac[4], baseMac[5]); baseMacChr[18] = 0; ArduinoOTA .setHostname(baseMacChr) |
ArduinoOTA has several callback functions that will be called at different status during the OTA update.
- onStart is called when the update starts
- onEnd is called after the update has finished
- onError is called if an error occurred during the update
- onProgress is called frequently while the updated application is transfered to the ESP32
We use this four callbacks to display information on the TFT display.
Within the onStart callback we clear the display and write the static text “OTA Progress”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
.onStart([]() { /**********************************************************/ // Close app tasks and stop timers here /**********************************************************/ // Prepare LED for visual signal during OTA pinMode(16, OUTPUT); // Clear the screen and print OTA text tft.fillScreen(TFT_BLUE); tft.setTextDatum(MC_DATUM); tft.setTextColor(TFT_WHITE); tft.setTextSize(2); tft.drawString("OTA",64,50); tft.drawString("Progress:",64,75); }) |
While the update is ongoing, onProgress is called frequently. The call is made with two parameters, the current progress and the total bytes expected. Out of these two parameters we calculate the progress as percentage and display it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
.onProgress([](unsigned int progress, unsigned int total) { // Calculate progress unsigned int achieved = progress / (total / 100); // Update progress every 1 % if (otaStatus == 0 || achieved == otaStatus + 1) { // Toggle the LED digitalWrite(16, !digitalRead(16)); otaStatus = achieved; // Print the progress tft.setTextDatum(MC_DATUM); tft.setTextSize(2); tft.fillRect(32,91,64,28,TFT_BLUE); // String progVal = String(achieved) + "%"; String progVal = String(progress) + "%"; tft.drawString(progVal,64,105); } }) |
>
If the OTA update finished successful, onEnd is called. The ESP32 would reset immediately after returning from this call. So to give the user the chance to see the success message a small delay is added before continuing.
1 2 3 4 5 6 7 8 9 10 |
.onEnd([]() { // Clear the screen and print OTA finished text tft.fillScreen(TFT_GREEN); tft.setTextDatum(MC_DATUM); tft.setTextColor(TFT_BLACK); tft.setTextSize(2); tft.drawString("OTA",64,50); tft.drawString("FINISHED!",64,80); delay(10); }) |
If anything went wrong during the update, onError is called. Here we use the provided error code to display the reason of the failure on the display.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
.onError([](ota_error_t error) { // Clear the screen for error message tft.fillScreen(TFT_RED); tft.setTextDatum(MC_DATUM); tft.setTextColor(TFT_WHITE); // Print error message tft.setTextSize(2); tft.drawString("OTA",64,60); tft.drawString("ERROR:",64,90); // Get detailed error and print error reason if (error == OTA_AUTH_ERROR) { tft.drawString("Auth Failed",64,120); } if (error == OTA_BEGIN_ERROR) { tft.drawString("Begin Failed",64,120); } else if (error == OTA_CONNECT_ERROR) { tft.drawString("Connect Failed",64,120); } else if (error == OTA_RECEIVE_ERROR) { tft.drawString("Receive Failed",64,120); } else if (error == OTA_END_ERROR) { tft.drawString("End Failed",64,120); } otaStatus = 0; }); |
After defining the callbacks, we can call ArduinoOTA to activate the OTA functionality. And I added some more service text for the mDNS service to identify the module.
1 2 3 4 5 6 7 8 |
// Initialize OTA ArduinoOTA.begin(); // Add some extra service text to the mDNS service MDNS.addServiceTxt("_arduino", "_tcp", "service", "OTA-TEST"); MDNS.addServiceTxt("_arduino", "_tcp", "type", "ESP32"); MDNS.addServiceTxt("_arduino", "_tcp", "id", "BeeGee"); } |
And here how the whole OTA update process is shown on the display:
For convenient copying I put the whole code together below.
Or you can just pull the whole PlatformIO/Visual Studio Code project from my Github repo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
#include <Arduino.h> #include <WiFi.h> #include <ESPmDNS.h> #include <ArduinoOTA.h> #include <TFT_eSPI.h> /** Build time */ const char compileDate[] = __DATE__ " " __TIME__; /** OTA progress */ int otaStatus = 0; /** TFT_eSPI class for display */ TFT_eSPI tft = TFT_eSPI(); /** Function definition */ void activateOTA(); void setup() { // Start serial connection Serial.begin(115200); // Initialize TFT screen tft.init(); // Clear screen tft.fillScreen(TFT_BLACK); tft.setCursor(0, 40); tft.setTextColor(TFT_WHITE); // Put some information on the screen tft.println("Build: "); tft.setTextSize(1); tft.println(compileDate); // Connect to WiFi WiFi.mode(WIFI_STA); WiFi.begin("SSID", "PASSWD"); uint32_t startTime = millis(); while (WiFi.waitForConnectResult() != WL_CONNECTED) { if (millis()-startTime > 30000) { // wait maximum 30 seconds for a connection tft.println("Failed to connect to WiFI"); tft.println("Rebooting in 30 seconds"); delay(30000); esp_restart(); } } // WiFi connection successfull tft.println("Connected to "); tft.println(WiFi.SSID()); tft.println("with IP address "); tft.println(WiFi.localIP()); // Activate OTA activateOTA(); tft.println("\n\nWaiting for OTA to start"); } void loop() { ArduinoOTA.handle(); } /** * Activate OTA */ void activateOTA() { uint8_t baseMac[6]; // Get MAC address for WiFi station esp_read_mac(baseMac, ESP_MAC_WIFI_STA); char baseMacChr[19] = {0}; sprintf(baseMacChr, "ESP32-%02X%02X%02X%02X%02X%02X" , baseMac[0], baseMac[1], baseMac[2] , baseMac[3], baseMac[4], baseMac[5]); baseMacChr[18] = 0; ArduinoOTA .setHostname(baseMacChr) .onStart([]() { /**********************************************************/ // Close app tasks and stop timers here /**********************************************************/ // Prepare LED for visual signal during OTA pinMode(16, OUTPUT); // Clear the screen and print OTA text tft.fillScreen(TFT_BLUE); tft.setTextDatum(MC_DATUM); tft.setTextColor(TFT_WHITE); tft.setTextSize(2); tft.drawString("OTA",64,50); tft.drawString("Progress:",64,75); }) .onEnd([]() { // Clear the screen and print OTA finished text tft.fillScreen(TFT_GREEN); tft.setTextDatum(MC_DATUM); tft.setTextColor(TFT_BLACK); tft.setTextSize(2); tft.drawString("OTA",64,50); tft.drawString("FINISHED!",64,80); delay(10); }) .onProgress([](unsigned int progress, unsigned int total) { // Calculate progress unsigned int achieved = progress / (total / 100); // Update progress every 1 % if (otaStatus == 0 || achieved == otaStatus + 1) { // Toggle the LED digitalWrite(16, !digitalRead(16)); otaStatus = achieved; // Print the progress tft.setTextDatum(MC_DATUM); tft.setTextSize(2); tft.fillRect(32,91,64,28,TFT_BLUE); // String progVal = String(achieved) + "%"; String progVal = String(progress) + "%"; tft.drawString(progVal,64,105); } }) .onError([](ota_error_t error) { // Clear the screen for error message tft.fillScreen(TFT_RED); tft.setTextDatum(MC_DATUM); tft.setTextColor(TFT_WHITE); // Print error message tft.setTextSize(2); tft.drawString("OTA",64,60); tft.drawString("ERROR:",64,90); // Get detailed error and print error reason if (error == OTA_AUTH_ERROR) { tft.drawString("Auth Failed",64,120); } else if (error == OTA_BEGIN_ERROR) { tft.drawString("Begin Failed",64,120); } else if (error == OTA_CONNECT_ERROR) { tft.drawString("Connect Failed",64,120); } else if (error == OTA_RECEIVE_ERROR) { tft.drawString("Receive Failed",64,120); } else if (error == OTA_END_ERROR) { tft.drawString("End Failed",64,120); } otaStatus = 0; }); // Initialize OTA ArduinoOTA.begin(); // Add some extra service text to the mDNS service MDNS.addServiceTxt("_arduino", "_tcp", "service", "OTA-TEST"); MDNS.addServiceTxt("_arduino", "_tcp", "type", "ESP32"); MDNS.addServiceTxt("_arduino", "_tcp", "id", "BeeGee"); } |







00:21
Can you tell me what edit you do in library to make connection with display?
Thank you!
20:36
No changes in the library. Rest of the code is above and Github