mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-27 12:20:56 +00:00
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
This commit is contained in:
parent
c15fc6773e
commit
45248100f4
@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed a problem with calculating the otadata checksum (#3629)
|
||||
|
||||
### Removed
|
||||
|
||||
|
@ -4,6 +4,8 @@ use chrono::{TimeZone, Utc};
|
||||
use esp_config::{ConfigOption, Validator, generate_config};
|
||||
|
||||
fn main() {
|
||||
println!("cargo::rustc-check-cfg=cfg(embedded_test)");
|
||||
|
||||
let build_time = match env::var("SOURCE_DATE_EPOCH") {
|
||||
Ok(val) => Utc.timestamp_opt(val.parse::<i64>().unwrap(), 0).unwrap(),
|
||||
Err(_) => Utc::now(),
|
||||
|
@ -33,6 +33,8 @@ pub(crate) use rom as crypto;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod non_rom;
|
||||
#[cfg(embedded_test)]
|
||||
pub use crypto::Crc32 as Crc32ForTesting;
|
||||
#[cfg(feature = "std")]
|
||||
pub(crate) use non_rom as crypto;
|
||||
|
||||
|
@ -10,6 +10,6 @@ impl Crc32 {
|
||||
fn esp_rom_crc32_le(crc: u32, buf: *const u8, len: u32) -> u32;
|
||||
}
|
||||
|
||||
unsafe { esp_rom_crc32_le(0, data.as_ptr(), data.len() as u32) }
|
||||
unsafe { esp_rom_crc32_le(u32::MAX, data.as_ptr(), data.len() as u32) }
|
||||
}
|
||||
}
|
||||
|
@ -8,14 +8,14 @@ esp32s2 = "run --release --features=esp32s2 --target=xtensa-esp32s2-none-elf"
|
||||
esp32s3 = "run --release --features=esp32s3 --target=xtensa-esp32s3-none-elf"
|
||||
|
||||
[target.'cfg(target_arch = "riscv32")']
|
||||
runner = "espflash flash --monitor"
|
||||
runner = "espflash flash --monitor --partition-table=partitions.csv"
|
||||
rustflags = [
|
||||
"-C", "link-arg=-Tlinkall.x",
|
||||
"-C", "force-frame-pointers",
|
||||
]
|
||||
|
||||
[target.'cfg(target_arch = "xtensa")']
|
||||
runner = "espflash flash --monitor"
|
||||
runner = "espflash flash --monitor --partition-table=partitions.csv"
|
||||
rustflags = [
|
||||
# GNU LD
|
||||
"-C", "link-arg=-Wl,-Tlinkall.x",
|
||||
|
8
examples/partitions.csv
Normal file
8
examples/partitions.csv
Normal file
@ -0,0 +1,8 @@
|
||||
# ESP-IDF Partition Table
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000,0x4000,
|
||||
otadata, data, ota, 0xd000,0x2000,
|
||||
phy_init, data, phy, 0xf000,0x1000,
|
||||
factory, app, factory,0x10000,0x100000,
|
||||
ota_0, app, ota_0, 0x110000,0x100000,
|
||||
ota_1, app, ota_1, 0x210000,0x100000,
|
|
158
examples/src/bin/ota_update.rs
Normal file
158
examples/src/bin/ota_update.rs
Normal file
@ -0,0 +1,158 @@
|
||||
//! OTA Update Example
|
||||
//!
|
||||
//! This shows the basics of dealing with partitions and changing the active partition.
|
||||
//! For simplicity it will flash an application image embedded into the binary.
|
||||
//! In a real world application you can get the image via HTTP(S), UART or from an sd-card etc.
|
||||
//!
|
||||
//! Adjust the target and the chip in the following commands according to the chip used!
|
||||
//!
|
||||
//! - `cargo xtask build examples examples esp32 --example=gpio_interrupt`
|
||||
//! - `espflash save-image --chip=esp32 examples/target/xtensa-esp32-none-elf/release/gpio_interrupt examples/target/ota_image`
|
||||
//! - `cargo xtask build examples examples esp32 --example=ota_update`
|
||||
//! - `espflash save-image --chip=esp32 examples/target/xtensa-esp32-none-elf/release/ota_update examples/target/ota_image`
|
||||
//! - erase whole flash via `espflash erase-flash` (this is to make sure otadata is cleared and no code is flashed to any partition)
|
||||
//! - run via `cargo xtask run example examples esp32 --example=ota_update`
|
||||
//!
|
||||
//! On first boot notice the firmware partition gets booted ("Loaded app from partition at offset 0x10000").
|
||||
//! Press the BOOT button, once finished press the RESET button.
|
||||
//!
|
||||
//! Notice OTA0 gets booted ("Loaded app from partition at offset 0x110000").
|
||||
//!
|
||||
//! Once again press BOOT, when finished press RESET.
|
||||
//! You will see the `gpio_interrupt` example gets booted from OTA1 ("Loaded app from partition at offset 0x210000")
|
||||
//!
|
||||
//! See https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/ota.html
|
||||
|
||||
//% FEATURES: esp-storage esp-hal/unstable
|
||||
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use embedded_storage::Storage;
|
||||
use esp_backtrace as _;
|
||||
use esp_bootloader_esp_idf::{
|
||||
ota::Slot,
|
||||
partitions::{self, AppPartitionSubType, DataPartitionSubType},
|
||||
};
|
||||
use esp_hal::{
|
||||
gpio::{Input, InputConfig, Pull},
|
||||
main,
|
||||
};
|
||||
use esp_println::println;
|
||||
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
static OTA_IMAGE: &[u8] = include_bytes!("../../target/ota_image");
|
||||
|
||||
#[main]
|
||||
fn main() -> ! {
|
||||
let peripherals = esp_hal::init(esp_hal::Config::default());
|
||||
|
||||
let mut storage = esp_storage::FlashStorage::new();
|
||||
|
||||
let mut buffer = [0u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN];
|
||||
let pt = esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut buffer)
|
||||
.unwrap();
|
||||
|
||||
// List all partitions - this is just FYI
|
||||
for i in 0..pt.len() {
|
||||
println!("{:?}", pt.get_partition(i));
|
||||
}
|
||||
|
||||
// Find the OTA-data partition and show the currently active partition
|
||||
let ota_part = pt
|
||||
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
||||
DataPartitionSubType::Ota,
|
||||
))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let mut ota_part = ota_part.as_embedded_storage(&mut storage);
|
||||
println!("Found ota data");
|
||||
|
||||
let mut ota = esp_bootloader_esp_idf::ota::Ota::new(&mut ota_part).unwrap();
|
||||
let current = ota.current_slot().unwrap();
|
||||
println!(
|
||||
"current image state {:?} (only relevant if the bootloader was built with auto-rollback support)",
|
||||
ota.current_ota_state()
|
||||
);
|
||||
println!("current {:?} - next {:?}", current, current.next());
|
||||
|
||||
// Mark the current slot as VALID - this is only needed if the bootloader was built with auto-rollback support.
|
||||
// The default pre-compiled bootloader in espflash is NOT.
|
||||
if ota.current_slot().unwrap() != Slot::None
|
||||
&& (ota.current_ota_state().unwrap() == esp_bootloader_esp_idf::ota::OtaImageState::New
|
||||
|| ota.current_ota_state().unwrap()
|
||||
== esp_bootloader_esp_idf::ota::OtaImageState::PendingVerify)
|
||||
{
|
||||
println!("Changed state to VALID");
|
||||
ota.set_current_ota_state(esp_bootloader_esp_idf::ota::OtaImageState::Valid)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(feature = "esp32", feature = "esp32s2", feature = "esp32s3"))] {
|
||||
let button = peripherals.GPIO0;
|
||||
} else {
|
||||
let button = peripherals.GPIO9;
|
||||
}
|
||||
}
|
||||
|
||||
let boot_button = Input::new(button, InputConfig::default().with_pull(Pull::Up));
|
||||
|
||||
println!("Press boot button to flash and switch to the next OTA slot");
|
||||
let mut done = false;
|
||||
loop {
|
||||
if boot_button.is_low() && !done {
|
||||
done = true;
|
||||
|
||||
let next_slot = current.next();
|
||||
|
||||
println!("Flashing image to {:?}", next_slot);
|
||||
|
||||
// find the target app partition
|
||||
let next_app_partition = match next_slot {
|
||||
Slot::None => {
|
||||
// None is FACTORY if present, OTA0 otherwise
|
||||
pt.find_partition(partitions::PartitionType::App(AppPartitionSubType::Factory))
|
||||
.or_else(|_| {
|
||||
pt.find_partition(partitions::PartitionType::App(
|
||||
AppPartitionSubType::Ota0,
|
||||
))
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
Slot::Slot0 => pt
|
||||
.find_partition(partitions::PartitionType::App(AppPartitionSubType::Ota0))
|
||||
.unwrap(),
|
||||
Slot::Slot1 => pt
|
||||
.find_partition(partitions::PartitionType::App(AppPartitionSubType::Ota1))
|
||||
.unwrap(),
|
||||
}
|
||||
.unwrap();
|
||||
println!("Found partition: {:?}", next_app_partition);
|
||||
let mut next_app_partition = next_app_partition.as_embedded_storage(&mut storage);
|
||||
|
||||
// write to the app partition
|
||||
for (sector, chunk) in OTA_IMAGE.chunks(4096).enumerate() {
|
||||
println!("Writing sector {sector}...");
|
||||
next_app_partition
|
||||
.write((sector * 4096) as u32, chunk)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
println!("Changing OTA slot and setting the state to NEW");
|
||||
let ota_part = pt
|
||||
.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
||||
DataPartitionSubType::Ota,
|
||||
))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let mut ota_part = ota_part.as_embedded_storage(&mut storage);
|
||||
let mut ota = esp_bootloader_esp_idf::ota::Ota::new(&mut ota_part).unwrap();
|
||||
ota.set_current_slot(next_slot).unwrap();
|
||||
ota.set_current_ota_state(esp_bootloader_esp_idf::ota::OtaImageState::New)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
[target.'cfg(target_arch = "riscv32")']
|
||||
runner = "probe-rs run --preverify"
|
||||
rustflags = [
|
||||
"--cfg", "embedded_test",
|
||||
|
||||
"-C", "link-arg=-Tembedded-test.x",
|
||||
"-C", "link-arg=-Tdefmt.x",
|
||||
"-C", "link-arg=-Tlinkall.x",
|
||||
@ -10,6 +12,8 @@ rustflags = [
|
||||
[target.'cfg(target_arch = "xtensa")']
|
||||
runner = "probe-rs run --preverify"
|
||||
rustflags = [
|
||||
"--cfg", "embedded_test",
|
||||
|
||||
"-C", "link-arg=-nostartfiles",
|
||||
"-C", "link-arg=-Tembedded-test.x",
|
||||
"-C", "link-arg=-Tdefmt.x",
|
||||
|
@ -219,6 +219,10 @@ name = "esp_wifi_init"
|
||||
harness = false
|
||||
required-features = ["esp-wifi", "esp-alloc"]
|
||||
|
||||
[[test]]
|
||||
name = "otadata"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
allocator-api2 = { version = "0.3.0", default-features = false, features = ["alloc"] }
|
||||
cfg-if = "1.0.0"
|
||||
|
20
hil-test/tests/otadata.rs
Normal file
20
hil-test/tests/otadata.rs
Normal file
@ -0,0 +1,20 @@
|
||||
//! Tests parts of esp-bootloader-esp-idf's otadata related functionality not
|
||||
//! testable on the host
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use hil_test as _;
|
||||
|
||||
esp_bootloader_esp_idf::esp_app_desc!();
|
||||
|
||||
#[cfg(test)]
|
||||
#[embedded_test::tests(default_timeout = 3)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_crc_rom_function() {
|
||||
let crc = esp_bootloader_esp_idf::Crc32ForTesting::new();
|
||||
let res = crc.crc(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
|
||||
assert_eq!(res, 436745307);
|
||||
}
|
||||
}
|
@ -428,6 +428,11 @@ fn run_ci_checks(workspace: &Path, args: CiArgs) -> Result<()> {
|
||||
|
||||
// Build (examples)
|
||||
println!("::group::Build examples");
|
||||
|
||||
// The `ota_example` expects a file named `examples/target/ota_image` - it doesn't care about the contents however
|
||||
std::fs::create_dir_all("./examples/target")?;
|
||||
std::fs::write("./examples/target/ota_image", "DUMMY")?;
|
||||
|
||||
examples(
|
||||
workspace,
|
||||
ExamplesArgs {
|
||||
|
Loading…
x
Reference in New Issue
Block a user