Skip to content

Commit 4524810

Browse files
authored
Simple ota example (#3629)
* Fix esp-bootloader-esp-idf * Use OTA enabled partition table for examples * Add simple OTA example * CHANGELOG.md * Create a dummy `ota_image` in CI * mkdir * Remove unnecessary details from CHANGELOG * Make non-Window's users life easier * Test ROM function in esp-bootloader-esp-idf * Fix
1 parent c15fc67 commit 4524810

File tree

11 files changed

+207
-3
lines changed

11 files changed

+207
-3
lines changed

esp-bootloader-esp-idf/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515

1616
### Fixed
1717

18+
- Fixed a problem with calculating the otadata checksum (#3629)
1819

1920
### Removed
2021

esp-bootloader-esp-idf/build.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use chrono::{TimeZone, Utc};
44
use esp_config::{ConfigOption, Validator, generate_config};
55

66
fn main() {
7+
println!("cargo::rustc-check-cfg=cfg(embedded_test)");
8+
79
let build_time = match env::var("SOURCE_DATE_EPOCH") {
810
Ok(val) => Utc.timestamp_opt(val.parse::<i64>().unwrap(), 0).unwrap(),
911
Err(_) => Utc::now(),

esp-bootloader-esp-idf/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ pub(crate) use rom as crypto;
3333

3434
#[cfg(feature = "std")]
3535
mod non_rom;
36+
#[cfg(embedded_test)]
37+
pub use crypto::Crc32 as Crc32ForTesting;
3638
#[cfg(feature = "std")]
3739
pub(crate) use non_rom as crypto;
3840

esp-bootloader-esp-idf/src/rom.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ impl Crc32 {
1010
fn esp_rom_crc32_le(crc: u32, buf: *const u8, len: u32) -> u32;
1111
}
1212

13-
unsafe { esp_rom_crc32_le(0, data.as_ptr(), data.len() as u32) }
13+
unsafe { esp_rom_crc32_le(u32::MAX, data.as_ptr(), data.len() as u32) }
1414
}
1515
}

examples/.cargo/config.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ esp32s2 = "run --release --features=esp32s2 --target=xtensa-esp32s2-none-elf"
88
esp32s3 = "run --release --features=esp32s3 --target=xtensa-esp32s3-none-elf"
99

1010
[target.'cfg(target_arch = "riscv32")']
11-
runner = "espflash flash --monitor"
11+
runner = "espflash flash --monitor --partition-table=partitions.csv"
1212
rustflags = [
1313
"-C", "link-arg=-Tlinkall.x",
1414
"-C", "force-frame-pointers",
1515
]
1616

1717
[target.'cfg(target_arch = "xtensa")']
18-
runner = "espflash flash --monitor"
18+
runner = "espflash flash --monitor --partition-table=partitions.csv"
1919
rustflags = [
2020
# GNU LD
2121
"-C", "link-arg=-Wl,-Tlinkall.x",

examples/partitions.csv

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# ESP-IDF Partition Table
2+
# Name, Type, SubType, Offset, Size, Flags
3+
nvs, data, nvs, 0x9000,0x4000,
4+
otadata, data, ota, 0xd000,0x2000,
5+
phy_init, data, phy, 0xf000,0x1000,
6+
factory, app, factory,0x10000,0x100000,
7+
ota_0, app, ota_0, 0x110000,0x100000,
8+
ota_1, app, ota_1, 0x210000,0x100000,

examples/src/bin/ota_update.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
//! OTA Update Example
2+
//!
3+
//! This shows the basics of dealing with partitions and changing the active partition.
4+
//! For simplicity it will flash an application image embedded into the binary.
5+
//! In a real world application you can get the image via HTTP(S), UART or from an sd-card etc.
6+
//!
7+
//! Adjust the target and the chip in the following commands according to the chip used!
8+
//!
9+
//! - `cargo xtask build examples examples esp32 --example=gpio_interrupt`
10+
//! - `espflash save-image --chip=esp32 examples/target/xtensa-esp32-none-elf/release/gpio_interrupt examples/target/ota_image`
11+
//! - `cargo xtask build examples examples esp32 --example=ota_update`
12+
//! - `espflash save-image --chip=esp32 examples/target/xtensa-esp32-none-elf/release/ota_update examples/target/ota_image`
13+
//! - erase whole flash via `espflash erase-flash` (this is to make sure otadata is cleared and no code is flashed to any partition)
14+
//! - run via `cargo xtask run example examples esp32 --example=ota_update`
15+
//!
16+
//! On first boot notice the firmware partition gets booted ("Loaded app from partition at offset 0x10000").
17+
//! Press the BOOT button, once finished press the RESET button.
18+
//!
19+
//! Notice OTA0 gets booted ("Loaded app from partition at offset 0x110000").
20+
//!
21+
//! Once again press BOOT, when finished press RESET.
22+
//! You will see the `gpio_interrupt` example gets booted from OTA1 ("Loaded app from partition at offset 0x210000")
23+
//!
24+
//! See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/ota.html
25+
26+
//% FEATURES: esp-storage esp-hal/unstable
27+
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
28+
29+
#![no_std]
30+
#![no_main]
31+
32+
use embedded_storage::Storage;
33+
use esp_backtrace as _;
34+
use esp_bootloader_esp_idf::{
35+
ota::Slot,
36+
partitions::{self, AppPartitionSubType, DataPartitionSubType},
37+
};
38+
use esp_hal::{
39+
gpio::{Input, InputConfig, Pull},
40+
main,
41+
};
42+
use esp_println::println;
43+
44+
esp_bootloader_esp_idf::esp_app_desc!();
45+
46+
static OTA_IMAGE: &[u8] = include_bytes!("../../target/ota_image");
47+
48+
#[main]
49+
fn main() -> ! {
50+
let peripherals = esp_hal::init(esp_hal::Config::default());
51+
52+
let mut storage = esp_storage::FlashStorage::new();
53+
54+
let mut buffer = [0u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN];
55+
let pt = esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut buffer)
56+
.unwrap();
57+
58+
// List all partitions - this is just FYI
59+
for i in 0..pt.len() {
60+
println!("{:?}", pt.get_partition(i));
61+
}
62+
63+
// Find the OTA-data partition and show the currently active partition
64+
let ota_part = pt
65+
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
66+
DataPartitionSubType::Ota,
67+
))
68+
.unwrap()
69+
.unwrap();
70+
let mut ota_part = ota_part.as_embedded_storage(&mut storage);
71+
println!("Found ota data");
72+
73+
let mut ota = esp_bootloader_esp_idf::ota::Ota::new(&mut ota_part).unwrap();
74+
let current = ota.current_slot().unwrap();
75+
println!(
76+
"current image state {:?} (only relevant if the bootloader was built with auto-rollback support)",
77+
ota.current_ota_state()
78+
);
79+
println!("current {:?} - next {:?}", current, current.next());
80+
81+
// Mark the current slot as VALID - this is only needed if the bootloader was built with auto-rollback support.
82+
// The default pre-compiled bootloader in espflash is NOT.
83+
if ota.current_slot().unwrap() != Slot::None
84+
&& (ota.current_ota_state().unwrap() == esp_bootloader_esp_idf::ota::OtaImageState::New
85+
|| ota.current_ota_state().unwrap()
86+
== esp_bootloader_esp_idf::ota::OtaImageState::PendingVerify)
87+
{
88+
println!("Changed state to VALID");
89+
ota.set_current_ota_state(esp_bootloader_esp_idf::ota::OtaImageState::Valid)
90+
.unwrap();
91+
}
92+
93+
cfg_if::cfg_if! {
94+
if #[cfg(any(feature = "esp32", feature = "esp32s2", feature = "esp32s3"))] {
95+
let button = peripherals.GPIO0;
96+
} else {
97+
let button = peripherals.GPIO9;
98+
}
99+
}
100+
101+
let boot_button = Input::new(button, InputConfig::default().with_pull(Pull::Up));
102+
103+
println!("Press boot button to flash and switch to the next OTA slot");
104+
let mut done = false;
105+
loop {
106+
if boot_button.is_low() && !done {
107+
done = true;
108+
109+
let next_slot = current.next();
110+
111+
println!("Flashing image to {:?}", next_slot);
112+
113+
// find the target app partition
114+
let next_app_partition = match next_slot {
115+
Slot::None => {
116+
// None is FACTORY if present, OTA0 otherwise
117+
pt.find_partition(partitions::PartitionType::App(AppPartitionSubType::Factory))
118+
.or_else(|_| {
119+
pt.find_partition(partitions::PartitionType::App(
120+
AppPartitionSubType::Ota0,
121+
))
122+
})
123+
.unwrap()
124+
}
125+
Slot::Slot0 => pt
126+
.find_partition(partitions::PartitionType::App(AppPartitionSubType::Ota0))
127+
.unwrap(),
128+
Slot::Slot1 => pt
129+
.find_partition(partitions::PartitionType::App(AppPartitionSubType::Ota1))
130+
.unwrap(),
131+
}
132+
.unwrap();
133+
println!("Found partition: {:?}", next_app_partition);
134+
let mut next_app_partition = next_app_partition.as_embedded_storage(&mut storage);
135+
136+
// write to the app partition
137+
for (sector, chunk) in OTA_IMAGE.chunks(4096).enumerate() {
138+
println!("Writing sector {sector}...");
139+
next_app_partition
140+
.write((sector * 4096) as u32, chunk)
141+
.unwrap();
142+
}
143+
144+
println!("Changing OTA slot and setting the state to NEW");
145+
let ota_part = pt
146+
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
147+
DataPartitionSubType::Ota,
148+
))
149+
.unwrap()
150+
.unwrap();
151+
let mut ota_part = ota_part.as_embedded_storage(&mut storage);
152+
let mut ota = esp_bootloader_esp_idf::ota::Ota::new(&mut ota_part).unwrap();
153+
ota.set_current_slot(next_slot).unwrap();
154+
ota.set_current_ota_state(esp_bootloader_esp_idf::ota::OtaImageState::New)
155+
.unwrap();
156+
}
157+
}
158+
}

hil-test/.cargo/config.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
[target.'cfg(target_arch = "riscv32")']
22
runner = "probe-rs run --preverify"
33
rustflags = [
4+
"--cfg", "embedded_test",
5+
46
"-C", "link-arg=-Tembedded-test.x",
57
"-C", "link-arg=-Tdefmt.x",
68
"-C", "link-arg=-Tlinkall.x",
@@ -10,6 +12,8 @@ rustflags = [
1012
[target.'cfg(target_arch = "xtensa")']
1113
runner = "probe-rs run --preverify"
1214
rustflags = [
15+
"--cfg", "embedded_test",
16+
1317
"-C", "link-arg=-nostartfiles",
1418
"-C", "link-arg=-Tembedded-test.x",
1519
"-C", "link-arg=-Tdefmt.x",

hil-test/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ name = "esp_wifi_init"
219219
harness = false
220220
required-features = ["esp-wifi", "esp-alloc"]
221221

222+
[[test]]
223+
name = "otadata"
224+
harness = false
225+
222226
[dependencies]
223227
allocator-api2 = { version = "0.3.0", default-features = false, features = ["alloc"] }
224228
cfg-if = "1.0.0"

hil-test/tests/otadata.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//! Tests parts of esp-bootloader-esp-idf's otadata related functionality not
2+
//! testable on the host
3+
4+
#![no_std]
5+
#![no_main]
6+
7+
use hil_test as _;
8+
9+
esp_bootloader_esp_idf::esp_app_desc!();
10+
11+
#[cfg(test)]
12+
#[embedded_test::tests(default_timeout = 3)]
13+
mod tests {
14+
#[test]
15+
fn test_crc_rom_function() {
16+
let crc = esp_bootloader_esp_idf::Crc32ForTesting::new();
17+
let res = crc.crc(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
18+
assert_eq!(res, 436745307);
19+
}
20+
}

0 commit comments

Comments
 (0)