I am working on a project that requires OTA updates. I couldn't really find any help anywhere on how to run example code for the Arduino GIGA R1 board. I am posting an example here of how I got the updates working. I generated the .ota file from the binary using the CLI package via the command line: "arduino-cloud-cli ota header-encode --file xxxxxxx.bin --fqbn arduino:mbed_giga:giga". I encountered numerous issues, including problems with HTTP/HTTPS...
I hope that help other OTA-builder.....
/**
* Arduino Giga R1 — OTA loader
*
* Minimal sketch that runs once at power-on:
* 1. Connect to WiFi (credentials in password_ssid.h)
* 2. Download the .ota firmware package from the configured URL
* 3. Decompress LZSS payload and flash it via the Portenta OTA bootloader
* 4. Reset into the newly installed application sketch
*
* All user-tunable settings (WiFi, URLs, HTTPS, LAN vs remote) live in
* password_ssid.h so the same loader.ino can be reused across projects.
*
* Build the .ota package for the main application:
* lzss.py --encode sketch.bin sketch.lzss
* bin2ota.py GIGA sketch.lzss sketch.ota
*
* Or load arduino-cloud-cli tool and run
* "arduino-cloud-cli ota header-encode --file xxxxxxx.bin --fqbn arduino:mbed_giga:giga"
*
*
* QSPI layout note:
* MBR partition 2 hosts the user FAT filesystem (/fs). Arduino_Portenta_OTA
* also uses that partition during update. The loader must fully unmount and
* deinit the block devices before ota.begin(), then remount only on failure.
*
*
* Jari Ojala OH8LQ
*/
#include <Arduino_Portenta_OTA.h>
#include <ArduinoHttpClient.h>
#include <WiFi.h>
#include <WiFiSSLClient.h>
#include "QSPIFBlockDevice.h"
#include "MBRBlockDevice.h"
#include "FATFileSystem.h"
#include "password_ssid.h"
// --- QSPI block devices for user FAT on MBR partition 2 (/fs) ---
QSPIFBlockDevice root;
mbed::MBRBlockDevice part(&root, 2);
mbed::FATFileSystem fs("fs");
// =============================================================================
// QSPI / filesystem helpers
// =============================================================================
/**
* Release the user FAT mount so Arduino_Portenta_OTA can claim the same QSPI
* flash. fs.unmount() alone is often insufficient; root/part deinit is required
* to avoid OtaStorageInit (-3) errors.
*/
static void otaReleaseUserQspiBeforePortentaOtaBegin() {
int um = fs.unmount();
if (um != 0) {
Serial.print("OTA: fs.unmount returned ");
Serial.println(um);
}
delay(80);
part.deinit();
root.deinit();
delay(150);
}
/** Best-effort remount of user FAT after a failed OTA step (for diagnostics / retry). */
static void otaRemountUserFsAfterFail(const char* where) {
(void)fs.unmount();
part.deinit();
root.deinit();
delay(120);
root.init();
part.init();
int m = fs.mount(&part);
if (m != 0) {
Serial.print("OTA: fs.mount ");
Serial.print(m);
Serial.print(" @ ");
Serial.println(where);
}
}
// =============================================================================
// OTA library error reporting
// =============================================================================
/** Print human-readable hints for Arduino_Portenta_OTA::begin() failure codes. */
static void otaPrintBeginFailure(Arduino_Portenta_OTA::Error err) {
Serial.print("Arduino_Portenta_OTA::begin() failed: ");
Serial.println((int)err);
switch (err) {
case Arduino_Portenta_OTA::Error::NoCapableBootloader:
Serial.println("(-1) Update bootloader (OTA not supported).");
break;
case Arduino_Portenta_OTA::Error::CaStorageInit:
Serial.println("(-8) WLAN/certificate QSPI partition will not mount.");
Serial.println(" Run STM32H747_System -> WiFiFirmwareUpdater.");
break;
case Arduino_Portenta_OTA::Error::CaStorageOpen:
Serial.println("(-9) /wlan/cacert.pem missing or not readable.");
break;
case Arduino_Portenta_OTA::Error::OtaStorageInit:
Serial.println("(-3) OTA cannot mount update partition.");
break;
default:
break;
}
}
/** Decode negative return codes from download / decompress / mbed network layer. */
static void otaSerialExplainNegativeFwCode(int code) {
if (code == -3005) {
Serial.println("(-3005) NSAPI_ERROR_NO_SOCKET.");
}
if (code == static_cast<int>(Arduino_Portenta_OTA::Error::OtaHeaderLength)) {
Serial.println("(-5) OTA header/length — wrong .ota or bin2ota input.");
}
if (code == static_cast<int>(Arduino_Portenta_OTA::Error::OtaHeaderCrc)) {
Serial.println("(-6) OTA CRC mismatch.");
}
if (code == static_cast<int>(Arduino_Portenta_OTA::Error::OtaHeaterMagicNumber)) {
Serial.println("(-7) OTA magic — use bin2ota.py GIGA or CLI app.");
}
if (code == static_cast<int>(Arduino_Portenta_OTA::Error::OtaDownload)) {
Serial.println("(-12) OTA download layer error.");
}
}
// =============================================================================
// HTTP URL parsing and pre-download probe
// =============================================================================
/**
* Split "http(s)://host/path" into scheme flag, hostname, and path.
* Returns false if the URL does not start with http:// or https://.
*/
static bool otaParseHttpUrl(const char* url, bool* out_https, String* out_host, String* out_path) {
String u(url);
if (u.startsWith("https://")) {
*out_https = true;
u = u.substring(8);
} else if (u.startsWith("http://")) {
*out_https = false;
u = u.substring(7);
} else {
return false;
}
int const slash = u.indexOf('/');
if (slash < 0) {
*out_host = u;
*out_path = "/";
} else {
*out_host = u.substring(0, slash);
*out_path = u.substring(slash);
}
if (out_path->length() == 0) {
*out_path = "/";
}
return out_host->length() > 0;
}
/** Discard response body after a probe GET so the TCP socket is cleanly closed. */
static void otaProbeDrainResponse(HttpClient& http) {
uint8_t buf[256];
unsigned long const t0 = millis();
size_t total = 0;
const size_t kMaxBytes = 16384;
const unsigned long kMaxMs = 10000;
while (total < kMaxBytes && (millis() - t0) < kMaxMs) {
int n = http.read(buf, sizeof(buf));
if (n > 0) {
total += (size_t)n;
continue;
}
if (!http.connected()) {
break;
}
delay(2);
}
}
/** Issue GET, capture status code and Content-Length, then drain the body. */
static bool otaProbeHttpRun(HttpClient& http, const char* path, int* errOut, int* statusOut, long* lenOut) {
int err = http.get(path);
*errOut = err;
if (err != 0) {
return false;
}
*statusOut = http.responseStatusCode();
*lenOut = http.contentLength();
otaProbeDrainResponse(http);
return true;
}
/**
* Lightweight HEAD-like check: verify the firmware URL is reachable and returns
* HTTP 200 before starting a long download. Catches redirects and 404 early.
*/
static bool otaProbeFirmwareHead(const char* url, bool const want_https) {
bool url_https = false;
String host;
String path;
if (!otaParseHttpUrl(url, &url_https, &host, &path)) {
Serial.println("OTA probe: URL parse failed");
return false;
}
if (url_https != want_https) {
Serial.println("OTA probe: HTTPS setting does not match URL");
return false;
}
int err = 0;
int status = 0;
long contentLen = -1;
if (want_https) {
WiFiSSLClient ssl;
ssl.setTimeout(20000);
HttpClient http(ssl, host.c_str(), 443);
http.setHttpResponseTimeout(45000);
http.setHttpWaitForDataDelay(25);
(void)otaProbeHttpRun(http, path.c_str(), &err, &status, &contentLen);
http.stop();
} else {
WiFiClient wifi;
wifi.setTimeout(20000);
HttpClient http(wifi, host.c_str(), 80);
http.setHttpResponseTimeout(45000);
http.setHttpWaitForDataDelay(25);
(void)otaProbeHttpRun(http, path.c_str(), &err, &status, &contentLen);
http.stop();
}
if (err != 0) {
Serial.print("OTA probe: GET failed err=");
Serial.println(err);
return false;
}
Serial.print("OTA probe: HTTP status ");
Serial.println(status);
if (status >= 300 && status < 400) {
Serial.println("OTA: redirect — set OTA_FIRMWARE_USE_REMOTE_HTTPS to 1 in password_ssid.h.");
return false;
}
if (status != 200) {
Serial.println("OTA: need HTTP 200 for .ota");
return false;
}
return true;
}
// =============================================================================
// Downloaded file inspection
// =============================================================================
/** Return file size in bytes, or -1 on error. Path is mbed FAT style, e.g. "/fs/...". */
static long otaQspiFileSize(const char* path) {
FILE* f = fopen(path, "rb");
if (!f) {
return -1;
}
if (fseek(f, 0, SEEK_END) != 0) {
fclose(f);
return -1;
}
long const sz = ftell(f);
fclose(f);
return sz;
}
/** Detect HTML error pages masquerading as a binary .ota download. */
static bool otaHeaderLooksLikeHtml(uint8_t const* h, size_t n) {
if (n < 5) {
return false;
}
if (memcmp(h, "<!DOC", 5) == 0 || memcmp(h, "<!doc", 5) == 0) {
return true;
}
if (memcmp(h, "<html", 5) == 0 || memcmp(h, "<HTML", 5) == 0) {
return true;
}
if (n >= 2 && h[0] == '<' && h[1] == '!') {
return true;
}
return false;
}
/**
* Validate the bin2ota container written to /fs/UPDATE.BIN.LZSS.
* Expected layout: 4-byte little-endian payload length + 4-byte CRC + LZSS data.
* Total file size must equal 8 + length. Rejects gzip and HTML responses.
*/
static bool otaValidateDownloadedOtaContainer() {
long const fsz = otaQspiFileSize("/fs/UPDATE.BIN.LZSS");
if (fsz < 24) {
Serial.println("OTA: UPDATE.BIN.LZSS too small.");
return false;
}
FILE* f = fopen("/fs/UPDATE.BIN.LZSS", "rb");
if (!f) {
return false;
}
uint8_t h[16];
size_t const n = fread(h, 1, sizeof(h), f);
fclose(f);
if (n < 8) {
return false;
}
if (h[0] == 0x1f && h[1] == 0x8b) {
Serial.println("OTA: gzip signature — disable gzip for .ota on server.");
return false;
}
uint32_t const lenField =
(uint32_t)h[0] | ((uint32_t)h[1] << 8) | ((uint32_t)h[2] << 16) | ((uint32_t)h[3] << 24);
if (lenField >= 12 && lenField <= 32UL * 1024UL * 1024UL &&
(uint64_t)fsz == (uint64_t)lenField + 8ULL) {
return true;
}
if (otaHeaderLooksLikeHtml(h, n)) {
Serial.println("OTA: HTML response, not .ota binary.");
return false;
}
if (lenField < 12 || lenField > 32UL * 1024UL * 1024UL) {
Serial.println("OTA: invalid bin2ota length field.");
return false;
}
Serial.print("OTA: file size ");
Serial.print(fsz);
Serial.print(" != 8 + length ");
Serial.println((unsigned long)(lenField + 8));
return false;
}
// =============================================================================
// Firmware download
// =============================================================================
/**
* Stream the .ota file from HTTP(S) into /fs/UPDATE.BIN.LZSS using HttpClient.
* More reliable than mbed WiFi.download() on some Giga setups (which may return 0).
* Returns byte count on success, or a negative error code.
*/
static int otaDownloadLzssViaHttpClient(const char* url, bool want_https) {
static char const kDest[] = "/fs/UPDATE.BIN.LZSS";
bool url_https = false;
String host;
String path;
if (!otaParseHttpUrl(url, &url_https, &host, &path)) {
return -1;
}
if (url_https != want_https) {
return -2;
}
WiFiClient wifi;
WiFiSSLClient ssl;
Client& conn = want_https ? static_cast<Client&>(ssl) : static_cast<Client&>(wifi);
conn.setTimeout(30000);
HttpClient http(conn, host.c_str(), want_https ? 443 : 80);
http.setHttpResponseTimeout(120000);
http.setHttpWaitForDataDelay(25);
Serial.println("OTA: HttpClient GET...");
int const getErr = http.get(path.c_str());
if (getErr != 0) {
http.stop();
return getErr;
}
int const status = http.responseStatusCode();
int const len = http.contentLength();
if (status != 200) {
http.stop();
return -100;
}
bool const knownTotal = (len > 0);
if (knownTotal) {
Serial.print("OTA HttpClient Content-Length ");
Serial.println(len);
}
remove(kDest);
FILE* outf = fopen(kDest, "wb");
if (!outf) {
http.stop();
return -101;
}
uint8_t buffer[1024];
int downloaded = 0;
uint32_t idleStart = 0;
uint32_t const kIdleBreakMs = 180000U; // abort if no data for 3 minutes
for (;;) {
int ret = http.read(buffer, sizeof(buffer));
if (ret > 0) {
idleStart = 0;
int take = ret;
if (knownTotal && downloaded + take > len) {
take = len - downloaded;
}
if (take <= 0) {
break;
}
size_t const w = fwrite(buffer, 1, (size_t)take, outf);
if (w != (size_t)take) {
fclose(outf);
http.stop();
remove(kDest);
return -102;
}
downloaded += take;
if (knownTotal && downloaded >= len) {
break;
}
} else {
if (!http.connected()) {
break;
}
if (idleStart == 0) {
idleStart = millis();
}
if ((uint32_t)(millis() - idleStart) > kIdleBreakMs) {
fclose(outf);
http.stop();
remove(kDest);
return -103;
}
delay(2);
}
}
http.stop();
fclose(outf);
if (knownTotal && downloaded != len) {
remove(kDest);
return -104;
}
if (downloaded <= 0) {
remove(kDest);
return -105;
}
Serial.print("OTA HttpClient: wrote ");
Serial.print(downloaded);
Serial.println(" bytes");
return downloaded;
}
// =============================================================================
// WiFi and main OTA pipeline
// =============================================================================
/** Connect using WIFI_SSID / WIFI_PASSWORD from password_ssid.h. */
static bool connectWiFi() {
if (WiFi.status() == WL_NO_MODULE) {
Serial.println("ERROR: WiFi module not found.");
return false;
}
Serial.print("Connecting to ");
Serial.println(WIFI_SSID);
WiFi.disconnect();
delay(200);
int attempts = 0;
while (WiFi.begin(WIFI_SSID, WIFI_PASSWORD) != WL_CONNECTED && attempts < WIFI_CONNECT_ATTEMPTS) {
attempts++;
Serial.print('.');
delay(WIFI_CONNECT_DELAY_MS);
}
Serial.println();
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi: connect failed.");
return false;
}
Serial.print("WiFi connected, IP: ");
Serial.println(WiFi.localIP());
return true;
}
/**
* Full OTA sequence: begin storage → probe URL → download .ota → validate →
* decompress to UPDATE.BIN → ota.update() → ota.reset().
* Returns true only if reset is about to happen; false leaves the loader running.
*/
static bool performOtaUpdate() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("OTA: WiFi not connected.");
return false;
}
Arduino_Portenta_OTA_QSPI ota(QSPI_FLASH_FATFS_MBR, 2);
Arduino_Portenta_OTA::Error ota_err = Arduino_Portenta_OTA::Error::None;
if (!ota.isOtaCapable()) {
Serial.println("Bootloader does not support OTA.");
Serial.println("Update: Portenta_System -> PortentaH7_updateBootloader");
return false;
}
root.init();
part.init();
Serial.println("OTA: release user QSPI for Portenta OTA...");
otaReleaseUserQspiBeforePortentaOtaBegin();
if ((ota_err = ota.begin()) != Arduino_Portenta_OTA::Error::None) {
otaPrintBeginFailure(ota_err);
otaRemountUserFsAfterFail("begin");
return false;
}
uint32_t start = millis();
Serial.print("OTA firmware URL: ");
Serial.println(OTA_FILE_LOCATION_FIRMWARE);
if (!otaProbeFirmwareHead(OTA_FILE_LOCATION_FIRMWARE, OTA_FIRMWARE_IS_HTTPS != 0)) {
Serial.println("OTA probe failed.");
otaRemountUserFsAfterFail("probe");
return false;
}
int ota_download = 0;
#if OTA_FIRMWARE_USE_SPLIT_DL_DECOMPRESS
// Step 1: download compressed .ota container to QSPI FAT
int dlRaw =
otaDownloadLzssViaHttpClient(OTA_FILE_LOCATION_FIRMWARE, OTA_FIRMWARE_IS_HTTPS != 0);
if (dlRaw <= 0) {
Serial.println("OTA: HttpClient failed — trying mbed ota.download()...");
dlRaw = ota.download(OTA_FILE_LOCATION_FIRMWARE, OTA_FIRMWARE_IS_HTTPS != 0);
}
if (dlRaw <= 0) {
Serial.print("OTA: download failed, code ");
Serial.println(dlRaw);
otaSerialExplainNegativeFwCode(dlRaw);
otaRemountUserFsAfterFail("download");
return false;
}
// Step 2: sanity-check bin2ota header before decompress
if (!otaValidateDownloadedOtaContainer()) {
remove("/fs/UPDATE.BIN.LZSS");
otaRemountUserFsAfterFail("ota_validate");
return false;
}
// Step 3: LZSS decompress → /fs/UPDATE.BIN (raw flash image)
Serial.println("OTA: LZSS decompress...");
int const dec = ota.decompress();
if (dec <= 0) {
Serial.print("OTA decompress() ");
Serial.println(dec);
otaSerialExplainNegativeFwCode(dec);
otaRemountUserFsAfterFail("decompress");
return false;
}
ota_download = dec;
#else
// Single-call path: download + decompress in one mbed API call
ota_download = ota.downloadAndDecompress(OTA_FILE_LOCATION_FIRMWARE, OTA_FIRMWARE_IS_HTTPS != 0);
#endif
if (ota_download <= 0) {
Serial.print("OTA download/decompress failed: ");
Serial.println(ota_download);
otaSerialExplainNegativeFwCode(ota_download);
otaRemountUserFsAfterFail("download");
return false;
}
float const elapsed = (millis() - start) / 1000.0f;
float const speed = (ota_download / elapsed) / 1024.0f;
Serial.print(ota_download);
Serial.print(" bytes stored in ");
Serial.print(elapsed);
Serial.print(" s (");
Serial.print(speed);
Serial.println(" KB/s)");
// Step 4: hand off UPDATE.BIN to the bootloader and reboot
Serial.println("Applying firmware update...");
if ((ota_err = ota.update()) != Arduino_Portenta_OTA::Error::None) {
Serial.print("ota.update() failed: ");
Serial.println((int)ota_err);
otaRemountUserFsAfterFail("update");
return false;
}
delay(1000); // flush Serial before reset
Serial.println("Update OK — resetting...");
ota.reset();
return true; // not reached if reset succeeds
}
// =============================================================================
// Arduino entry points
// =============================================================================
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 3000) {
delay(10);
}
Serial.println();
Serial.println("=== Giga OTA loader ===");
if (!connectWiFi()) {
Serial.println("Loader halted — fix WiFi credentials in password_ssid.h");
return;
}
if (!performOtaUpdate()) {
Serial.println("OTA failed — retry in 30 s");
}
}
void loop() {
static uint32_t lastRetry = 0;
// Reconnect WiFi if the link dropped, then retry OTA immediately
if (WiFi.status() != WL_CONNECTED) {
if (connectWiFi()) {
performOtaUpdate();
}
return;
}
// Periodic retry while OTA keeps failing (e.g. server temporarily down)
if (millis() - lastRetry >= 30000UL) {
lastRetry = millis();
Serial.println("Retrying OTA...");
performOtaUpdate();
}
}
#ifndef PASSWORD_SSID_H
#define PASSWORD_SSID_H
// =============================================================================
// WiFi credentials
// =============================================================================
// Network name (SSID) the board joins before OTA download.
#define WIFI_SSID "xxxxxxxx"
// Pre-shared key (password) for the network above.
#define WIFI_PASSWORD "xxxxxxxx"
// Max connection attempts before setup() gives up (each attempt waits WIFI_CONNECT_DELAY_MS).
#define WIFI_CONNECT_ATTEMPTS 30
// Delay in milliseconds between WiFi.begin() retries.
#define WIFI_CONNECT_DELAY_MS 500
// =============================================================================
// OTA firmware source selection
// =============================================================================
// 1 = download from internet URLs below (OTA_FILE_LOCATION_REMOTE_*)
// 0 = download from local LAN URL (OTA_FILE_LOCATION_LAN)
#define OTA_FIRMWARE_USE_REMOTE 1
// 1 = use HTTPS URL (OTA_FILE_LOCATION_REMOTE_HTTPS, port 443)
// 0 = use HTTP URL (OTA_FILE_LOCATION_REMOTE_HTTP, port 80)
// Must match the scheme of the URL you actually use.
#define OTA_FIRMWARE_USE_REMOTE_HTTPS 0
// =============================================================================
// OTA file URLs and filename
// =============================================================================
// Public HTTP URL of the .ota firmware package on your update server.
// Typical path: /update/<sketch_name>.ota
#define OTA_FILE_LOCATION_REMOTE_HTTP "http://www.xxxxxx.com/xxxx/xxxxx.ota"
// Same file over HTTPS (use when the server redirects HTTP → HTTPS).
#define OTA_FILE_LOCATION_REMOTE_HTTPS "https://www.xxxxxx.com/xxxxx/xxxxx.ota"
// Local LAN URL for bench testing without internet (OTA_FIRMWARE_USE_REMOTE = 0).
// Host is usually a PC running a simple HTTP server serving the .ota file.
#define OTA_FILE_LOCATION_LAN "http://192.168.0.131/xxxxxx.ota"
// Basename of the firmware package on the server (informational; URLs above are authoritative).
#define OTA_FIRMWARE_FILENAME "xxxxxx.ota"
// =============================================================================
// OTA download / install behaviour
// =============================================================================
// 1 = download .ota via HttpClient, validate container, then LZSS decompress (recommended on Giga).
// 0 = single-step mbed ota.downloadAndDecompress().
#define OTA_FIRMWARE_USE_SPLIT_DL_DECOMPRESS 1
// =============================================================================
// Derived settings — do not edit unless you know what you are doing
// =============================================================================
#if OTA_FIRMWARE_USE_REMOTE
#if OTA_FIRMWARE_USE_REMOTE_HTTPS
#define OTA_FILE_LOCATION_FIRMWARE OTA_FILE_LOCATION_REMOTE_HTTPS
#define OTA_FIRMWARE_IS_HTTPS 1
#else
#define OTA_FILE_LOCATION_FIRMWARE OTA_FILE_LOCATION_REMOTE_HTTP
#define OTA_FIRMWARE_IS_HTTPS 0
#endif
#else
#define OTA_FILE_LOCATION_FIRMWARE OTA_FILE_LOCATION_LAN
#define OTA_FIRMWARE_IS_HTTPS 0
#endif
#endif