Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions boards.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ giga.debug.server.openocd.scripts.2=target/stm32h7x_dual_bank.cfg
giga.debug.cortex-debug.custom.request=attach
giga.debug.svd_file={runtime.platform.path}/svd/STM32H747_CM7.svd

giga.build.ota.magic=0x23410266
giga.build.ota.sketch_offset={bootloader.maximum_size}

##########################################################################################

nano33ble.name=Arduino Nano 33 BLE
Expand Down Expand Up @@ -256,6 +259,9 @@ portentah7.bootloader.interface=0
portentah7.bootloader.file=zephyr-{build.variant}.bin
portentah7.bootloader.address=0x08040000

portentah7.build.ota.magic=0x2341025B
portentah7.build.ota.sketch_offset={bootloader.maximum_size}

portentah7.debug.tool=gdb
portentah7.debug.server.openocd.scripts.0=interface/{programmer.protocol}.cfg
portentah7.debug.server.openocd.scripts.1={programmer.transport_script}
Expand Down Expand Up @@ -308,6 +314,9 @@ nicla_vision.bootloader.interface=0
nicla_vision.bootloader.file=zephyr-{build.variant}.bin
nicla_vision.bootloader.address=0x08040000

nicla_vision.build.ota.magic=0x2341025F
nicla_vision.build.ota.sketch_offset={bootloader.maximum_size}

nicla_vision.debug.tool=gdb
nicla_vision.debug.server.openocd.scripts.0=interface/{programmer.protocol}.cfg
nicla_vision.debug.server.openocd.scripts.1={programmer.transport_script}
Expand Down Expand Up @@ -458,6 +467,9 @@ portentac33.leave.dfuse={empty} -Q
portentac33.loader.args.with_sketch={empty} -w
portentac33.sketch.args.without_loader={empty} -w

portentac33.build.ota.magic=0x23410068
portentac33.build.ota.sketch_offset={bootloader.maximum_size}

##########################################################################################

opta.name=Arduino Opta
Expand Down Expand Up @@ -510,6 +522,9 @@ opta.debug.server.openocd.scripts.2=target/stm32h7x_dual_bank.cfg
opta.debug.cortex-debug.custom.request=attach
opta.debug.svd_file={runtime.platform.path}/svd/STM32H747_CM7.svd

opta.build.ota.magic=0x23410064
opta.build.ota.sketch_offset={bootloader.maximum_size}

##########################################################################################

nano_matter.name=Arduino Nano Matter
Expand Down
2 changes: 2 additions & 0 deletions cores/arduino/llext_wrappers.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ W2(char *, strstr, const char *, const char *)
W2(int, strcmp, const char *, const char *)
W3(int, strncmp, const char *, const char *, size_t)
W2(int, strcasecmp, const char *, const char *)
W3(int, strncasecmp, const char *, const char *, size_t)
W3(char *, strncpy, char *, const char *, size_t)
W2(char *, strcat, char *, const char *)
W2(char *, strcpy, char *, const char *)
Expand Down Expand Up @@ -216,6 +217,7 @@ VN(__aeabi_fcmpun)
VN(__aeabi_d2iz)
VN(__aeabi_d2uiz)
VN(__aeabi_d2lz)
VN(__aeabi_d2ulz)
VN(__aeabi_i2d)
VN(__aeabi_ui2d)
VN(__aeabi_l2d)
Expand Down
1 change: 1 addition & 0 deletions extra/artifacts/zephyr_main.only
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
# Files listed here are included by this package only, and automatically
# excluded by all other artifacts.

libraries/OTA/
libraries/PDM/
3 changes: 3 additions & 0 deletions extra/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ if [ ! -z "$board" ]; then
fi
update_local_field "upload.address" "$UPLOAD_ADDR" "$UPLOAD_ADDR_COMMENT"

LOADER_MAX_SIZE=$(get_value_from_text_file variants/${variant}/syms-static.ld '_loader_max_size')
update_local_field "bootloader.maximum_size" $LOADER_MAX_SIZE

# maximum sketch size: size of sketch partition, decimal (exact limit)
CODE_SIZE=$(( $(get_value_from_text_file variants/${variant}/syms-static.ld '_sketch_max_size') ))
update_local_field "upload.maximum_size" $CODE_SIZE
Expand Down
45 changes: 45 additions & 0 deletions libraries/OTA/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# OTA

Downloads and installs firmware updates on an Arduino board running the
Zephyr core.

**Sketch only** — the examples fetch a sketch binary over plain HTTP and
write it to the loader's staging area. On the next boot the Zephyr loader
installs it in place of the running sketch.

**Loader + sketch** — the examples fetch a full binary (loader + sketch)
and write it to the OTA partition on QSPI. On the next boot the mbed
bootloader writes the entire payload to internal flash, updating both the
loader and the sketch in one shot.

## Requirements

- The loader must be built with `CONFIG_OTA=y`.
- Run `FlashFormat` (in `libraries/Storage/examples/`) once before using OTA.
It creates the MBR partition table and formats the FAT filesystems on QSPI
that both the Zephyr loader and the mbed bootloader depend on.

## Producing the update images

**Sketch only** — take the `.elf-zsk.bin` from the sketch build.

**Loader + sketch** — take the `.elf-zsk.bin-bundle.bin` from the sketch
build. The build system produces it automatically; it is the raw loader
binary + 0xFF padding + sketch, without the OTA header. The mbed
bootloader validates the file by checking that the first word is a valid
ARM initial stack pointer; an OTA header would fail that check.

Serve the file as `UPDATE.bin` with any HTTP server:

```
cp <sketch>.elf-zsk.bin UPDATE.bin # sketch only
cp <sketch>.elf-zsk.bin-bundle.bin UPDATE.bin # loader + sketch
python3 -m http.server 8000
```

## Examples

- **OTAEthernet** — sketch update over Ethernet (DHCP).
- **OTAWiFi** — sketch update over WiFi (edit `arduino_secrets.h`).
- **OTAFullEthernet** — loader + sketch update over Ethernet.
- **OTAFullWiFi** — loader + sketch update over WiFi.
191 changes: 191 additions & 0 deletions libraries/OTA/examples/OTAEthernet/OTAEthernet.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/*
* OTA sketch update over Ethernet.
*
* Downloads a raw .bin sketch image over plain HTTP and installs it on
* the next boot. No .ota container — the server hosts the plain
* "<sketch>.elf-zsk.bin" artifact produced by the build. Serve it with:
*
* python3 -m http.server 8000
*
* Copyright (c) Arduino s.r.l. and/or its affiliated companies
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <ota.h>
#include <ZephyrClient.h>
#include <ZephyrEthernet.h>
#include <zephyr/fs/fs.h>
#include <zephyr/autoconf.h>

#ifndef CONFIG_OTA
#error "This variant's loader was built without CONFIG_OTA."
#endif

#define STAGING_PATH CONFIG_OTA_SKETCH_UPDATE_PATH CONFIG_OTA_SKETCH_TEMP_PATH_POSTFIX

const char *server = "10.10.10.10";
const uint16_t port = 8000;
const char *path = "/UPDATE.bin";

ZephyrClient client;

static long httpGet(Client &c, const char *host, uint16_t port,
const char *path, struct fs_file_t *out) {
if (!c.connect(host, port)) {
Serial.println("Connection failed");
return -1;
}

c.print("GET ");
c.print(path);
c.print(" HTTP/1.1\r\nHost: ");
c.print(host);
c.print("\r\nConnection: close\r\n\r\n");

char line[128];
bool first = true;
long content_length = -1;
uint32_t last = millis();

while (true) {
if (!c.available()) {
if (!c.connected() || millis() - last > 10000) {
c.stop();
return -1;
}
continue;
}
last = millis();

size_t n = c.readBytesUntil('\n', line, sizeof(line) - 1);
line[n] = '\0';
if (n > 0 && line[n - 1] == '\r') {
line[n - 1] = '\0';
}

if (first) {
if (strncmp(line, "HTTP/1.", 7) != 0 || atoi(line + 9) != 200) {
Serial.print("HTTP error: ");
Serial.println(line);
c.stop();
return -1;
}
first = false;
continue;
}

if (line[0] == '\0') {
break;
}

if (strncasecmp(line, "Content-Length:", 15) == 0) {
content_length = atol(line + 15);
}
}

if (content_length <= 0) {
Serial.println("Missing or zero Content-Length");
c.stop();
return -1;
}

uint8_t buf[512];
long received = 0;
last = millis();

while (received < content_length) {
int avail = c.available();
if (avail <= 0) {
if (!c.connected() || millis() - last > 10000) {
break;
}
continue;
}

size_t want = (size_t)(content_length - received);
if (want > sizeof(buf)) {
want = sizeof(buf);
}
int got = c.read(buf, want);
if (got <= 0) {
continue;
}

if (fs_write(out, buf, got) != got) {
Serial.println("Filesystem write failed");
c.stop();
return -1;
}
received += got;
last = millis();
}

c.stop();
return received == content_length ? received : -1;
}

void setup() {
Serial.begin(115200);
while (!Serial) {
delay(10);
}

Serial.println("OTA update over Ethernet");

while (Ethernet.linkStatus() != LinkON) {
Serial.println("Waiting for link...");
delay(100);
}

if (Ethernet.begin() == 0) {
Serial.println("DHCP failed");
return;
}

Serial.print("IP: ");
Serial.println(Ethernet.localIP());

fs_unlink(STAGING_PATH);

struct fs_file_t file;
fs_file_t_init(&file);
if (fs_open(&file, STAGING_PATH, FS_O_CREATE | FS_O_WRITE) < 0) {
Serial.println("Cannot open staging file");
return;
}

Serial.print("Downloading http://");
Serial.print(server);
Serial.print(":");
Serial.print(port);
Serial.println(path);

long len = httpGet(client, server, port, path, &file);
fs_close(&file);

if (len < 0) {
Serial.println("Download failed");
fs_unlink(STAGING_PATH);
return;
}

Serial.print("Downloaded ");
Serial.print(len);
Serial.println(" bytes");

if (ota_sketch_ready() < 0) {
Serial.println("Failed to mark update as ready");
return;
}

Serial.println("Rebooting...");
Serial.flush();
delay(100);

ota_sketch_start();
}

void loop() {
delay(1000);
}
Loading
Loading