summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlf Lilleengen <lulf@redhat.com>2022-04-20 13:49:59 +0200
committerUlf Lilleengen <ulf.lilleengen@gmail.com>2022-04-27 15:17:18 +0200
commit484e0acc638c27366e19275c32db9c8487ea8fba (patch)
tree5649591dad34cadcb28503f94c1bbca0bf5578a1
parent9c283cd44504d6d9d6f9e352e4c7a8d043bd673f (diff)
downloadembassy-484e0acc638c27366e19275c32db9c8487ea8fba.zip
Add stm32 flash + bootloader support
* Add flash drivers for L0, L1, L4, WB and WL. Not tested for WB, but should be similar to WL. * Add embassy-boot-stm32 for bootloading on STM32. * Add flash examples and bootloader examples * Update stm32-data
-rw-r--r--docs/modules/ROOT/nav.adoc1
-rw-r--r--docs/modules/ROOT/pages/bootloader.adoc32
-rw-r--r--embassy-boot/boot/Cargo.toml5
-rw-r--r--embassy-boot/boot/src/lib.rs219
-rw-r--r--embassy-boot/nrf/Cargo.toml2
-rw-r--r--embassy-boot/nrf/src/lib.rs30
-rw-r--r--embassy-boot/stm32/Cargo.toml65
-rw-r--r--embassy-boot/stm32/README.md11
-rw-r--r--embassy-boot/stm32/build.rs27
-rw-r--r--embassy-boot/stm32/memory.x18
-rw-r--r--embassy-boot/stm32/src/fmt.rs225
-rw-r--r--embassy-boot/stm32/src/lib.rs75
-rw-r--r--embassy-boot/stm32/src/main.rs62
-rw-r--r--embassy-stm32/Cargo.toml5
-rw-r--r--embassy-stm32/src/flash/mod.rs401
-rw-r--r--embassy-stm32/src/lib.rs2
-rw-r--r--embassy-stm32/src/rcc/wl.rs156
-rw-r--r--examples/boot/Cargo.toml19
-rw-r--r--examples/boot/nrf/Cargo.toml19
-rw-r--r--examples/boot/nrf/README.md (renamed from examples/boot/README.md)0
-rw-r--r--examples/boot/nrf/build.rs (renamed from examples/boot/build.rs)0
-rw-r--r--examples/boot/nrf/memory.x (renamed from examples/boot/memory.x)0
-rw-r--r--examples/boot/nrf/src/bin/a.rs (renamed from examples/boot/src/bin/a.rs)4
-rw-r--r--examples/boot/nrf/src/bin/b.rs (renamed from examples/boot/src/bin/b.rs)0
-rw-r--r--examples/boot/stm32l0/.cargo/config.toml6
-rw-r--r--examples/boot/stm32l0/Cargo.toml26
-rw-r--r--examples/boot/stm32l0/README.md29
-rw-r--r--examples/boot/stm32l0/build.rs37
-rw-r--r--examples/boot/stm32l0/memory.x15
-rw-r--r--examples/boot/stm32l0/src/bin/a.rs48
-rw-r--r--examples/boot/stm32l0/src/bin/b.rs25
-rw-r--r--examples/boot/stm32l1/.cargo/config.toml6
-rw-r--r--examples/boot/stm32l1/Cargo.toml26
-rw-r--r--examples/boot/stm32l1/README.md29
-rw-r--r--examples/boot/stm32l1/build.rs37
-rw-r--r--examples/boot/stm32l1/memory.x15
-rw-r--r--examples/boot/stm32l1/src/bin/a.rs48
-rw-r--r--examples/boot/stm32l1/src/bin/b.rs25
-rw-r--r--examples/boot/stm32l4/.cargo/config.toml6
-rw-r--r--examples/boot/stm32l4/Cargo.toml26
-rw-r--r--examples/boot/stm32l4/README.md29
-rw-r--r--examples/boot/stm32l4/build.rs37
-rw-r--r--examples/boot/stm32l4/memory.x15
-rw-r--r--examples/boot/stm32l4/src/bin/a.rs44
-rw-r--r--examples/boot/stm32l4/src/bin/b.rs25
-rw-r--r--examples/boot/stm32wl/.cargo/config.toml6
-rw-r--r--examples/boot/stm32wl/Cargo.toml26
-rw-r--r--examples/boot/stm32wl/README.md29
-rw-r--r--examples/boot/stm32wl/build.rs37
-rw-r--r--examples/boot/stm32wl/memory.x15
-rw-r--r--examples/boot/stm32wl/src/bin/a.rs45
-rw-r--r--examples/boot/stm32wl/src/bin/b.rs25
-rw-r--r--examples/stm32l0/Cargo.toml2
-rw-r--r--examples/stm32l0/src/bin/flash.rs43
-rw-r--r--examples/stm32l1/Cargo.toml1
-rw-r--r--examples/stm32l1/src/bin/flash.rs43
-rw-r--r--examples/stm32wl/Cargo.toml3
-rw-r--r--examples/stm32wl/src/bin/flash.rs43
m---------stm32-data0
59 files changed, 2114 insertions, 136 deletions
diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index 3aa2eb6b..a45da195 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -3,6 +3,7 @@
* xref:hal.adoc[Hardware Abstraction Layer]
** xref:nrf.adoc[nRF]
** xref:stm32.adoc[STM32]
+* xref:bootloader.adoc[Bootloader]
* xref:getting_started.adoc[Getting started]
** xref:basic_application.adoc[Basic application]
** xref:layer_by_layer.adoc[Layer by Layer]
diff --git a/docs/modules/ROOT/pages/bootloader.adoc b/docs/modules/ROOT/pages/bootloader.adoc
new file mode 100644
index 00000000..1a984d6d
--- /dev/null
+++ b/docs/modules/ROOT/pages/bootloader.adoc
@@ -0,0 +1,32 @@
+= Bootloader
+
+`embassy-boot` a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks.
+
+The bootloader can be used either as a library or be flashed directly if you are happy with the default configuration and capabilities.
+
+By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself.
+
+The bootloader supports both internal and external flash by relying on the `embedded-storage` traits.
+
+
+== Hardware support
+
+The bootloader supports
+
+* nRF52 with and without softdevice
+* STM32 L4, WB, WL, L1 and L0
+
+In general, the bootloader works on any platform that implements the `embedded-storage` traits for its internal flash, but may require custom initialization code to work.
+
+== Design
+
+The bootloader divides the storage into 4 main partitions, configured by a linker script:
+
+* BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash.
+* ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. This partition is only written to by the bootloader.
+* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application.
+* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. When the new firmware has been written to the DFU partition, a flag is set to instruct the bootloader that the partitions should be swapped.
+
+The partitions for ACTIVE (+BOOTLOADER), DFU and BOOTLOADER_STATE may be placed in separate flash, but they have to support compatible page sizes.
+
+The bootloader has a platform-agnostic part, which implements the power fail safe swapping algorithm given the boundaries set by the partitions. The platform-specific part is a minimal shim that provides additional functionality such as watchdogs or supporting the nRF52 softdevice.
diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/boot/Cargo.toml
index 0a3006ff..04deab30 100644
--- a/embassy-boot/boot/Cargo.toml
+++ b/embassy-boot/boot/Cargo.toml
@@ -21,3 +21,8 @@ log = "0.4"
env_logger = "0.9"
rand = "0.8"
futures = { version = "0.3", features = ["executor"] }
+
+[features]
+write-4 = []
+write-8 = []
+invert-erase = []
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs
index 0d33ad1a..080ea242 100644
--- a/embassy-boot/boot/src/lib.rs
+++ b/embassy-boot/boot/src/lib.rs
@@ -17,8 +17,23 @@ mod fmt;
use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
use embedded_storage_async::nor_flash::AsyncNorFlash;
-pub const BOOT_MAGIC: u32 = 0xD00DF00D;
-pub const SWAP_MAGIC: u32 = 0xF00FDAAD;
+#[cfg(not(any(feature = "write-4", feature = "write-8",)))]
+compile_error!("No write size/alignment specified. Must specify exactly one of the following features: write-4, write-8");
+
+const BOOT_MAGIC: u8 = 0xD0;
+const SWAP_MAGIC: u8 = 0xF0;
+
+#[cfg(feature = "write-4")]
+const WRITE_SIZE: usize = 4;
+
+#[cfg(feature = "write-8")]
+const WRITE_SIZE: usize = 8;
+
+#[cfg(feature = "invert-erase")]
+const ERASE_VALUE: u8 = 0x00;
+
+#[cfg(not(feature = "invert-erase"))]
+const ERASE_VALUE: u8 = 0xFF;
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@@ -80,12 +95,12 @@ pub trait FlashProvider {
}
/// BootLoader works with any flash implementing embedded_storage and can also work with
-/// different page sizes.
+/// different page sizes and flash write sizes.
pub struct BootLoader<const PAGE_SIZE: usize> {
// Page with current state of bootloader. The state partition has the following format:
- // | Range | Description |
- // | 0 - 4 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
- // | 4 - N | Progress index used while swapping or reverting |
+ // | Range | Description |
+ // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
+ // | WRITE_SIZE - N | Progress index used while swapping or reverting |
state: Partition,
// Location of the partition which will be booted from
active: Partition,
@@ -100,7 +115,7 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
// DFU partition must have an extra page
assert!(dfu.len() - active.len() >= PAGE_SIZE);
// Ensure we have enough progress pages to store copy progress
- assert!(active.len() / PAGE_SIZE >= (state.len() - 4) / PAGE_SIZE);
+ assert!(active.len() / PAGE_SIZE >= (state.len() - WRITE_SIZE) / PAGE_SIZE);
Self { active, dfu, state }
}
@@ -203,15 +218,18 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
if !self.is_swapped(p.state())? {
trace!("Swapping");
self.swap(p)?;
+ trace!("Swapping done");
} else {
trace!("Reverting");
self.revert(p)?;
// Overwrite magic and reset progress
let fstate = p.state().flash();
- fstate.write(self.state.from as u32, &[0, 0, 0, 0])?;
+ let aligned = Aligned([!ERASE_VALUE; WRITE_SIZE]);
+ fstate.write(self.state.from as u32, &aligned.0)?;
fstate.erase(self.state.from as u32, self.state.to as u32)?;
- fstate.write(self.state.from as u32, &BOOT_MAGIC.to_le_bytes())?;
+ let aligned = Aligned([BOOT_MAGIC; WRITE_SIZE]);
+ fstate.write(self.state.from as u32, &aligned.0)?;
}
}
_ => {}
@@ -227,12 +245,15 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
}
fn current_progress<P: FlashConfig>(&mut self, p: &mut P) -> Result<usize, BootError> {
- let max_index = ((self.state.len() - 4) / 4) - 1;
+ let max_index = ((self.state.len() - WRITE_SIZE) / WRITE_SIZE) - 1;
let flash = p.flash();
+ let mut aligned = Aligned([!ERASE_VALUE; WRITE_SIZE]);
for i in 0..max_index {
- let mut buf: [u8; 4] = [0; 4];
- flash.read((self.state.from + 4 + i * 4) as u32, &mut buf)?;
- if buf == [0xFF, 0xFF, 0xFF, 0xFF] {
+ flash.read(
+ (self.state.from + WRITE_SIZE + i * WRITE_SIZE) as u32,
+ &mut aligned.0,
+ )?;
+ if aligned.0 == [ERASE_VALUE; WRITE_SIZE] {
return Ok(i);
}
}
@@ -241,8 +262,9 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P) -> Result<(), BootError> {
let flash = p.flash();
- let w = self.state.from + 4 + idx * 4;
- flash.write(w as u32, &[0, 0, 0, 0])?;
+ let w = self.state.from + WRITE_SIZE + idx * WRITE_SIZE;
+ let aligned = Aligned([!ERASE_VALUE; WRITE_SIZE]);
+ flash.write(w as u32, &aligned.0)?;
Ok(())
}
@@ -314,21 +336,24 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
fn swap<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError> {
let page_count = self.active.len() / PAGE_SIZE;
- // trace!("Page count: {}", page_count);
+ trace!("Page count: {}", page_count);
for page in 0..page_count {
+ trace!("COPY PAGE {}", page);
// Copy active page to the 'next' DFU page.
let active_page = self.active_addr(page_count - 1 - page);
let dfu_page = self.dfu_addr(page_count - page);
- info!("Copy active {} to dfu {}", active_page, dfu_page);
+ //trace!("Copy active {} to dfu {}", active_page, dfu_page);
self.copy_page_once_to_dfu(page * 2, active_page, dfu_page, p)?;
// Copy DFU page to the active page
let active_page = self.active_addr(page_count - 1 - page);
let dfu_page = self.dfu_addr(page_count - 1 - page);
- info!("Copy dfy {} to active {}", dfu_page, active_page);
+ //trace!("Copy dfy {} to active {}", dfu_page, active_page);
self.copy_page_once_to_active(page * 2 + 1, dfu_page, active_page, p)?;
}
+ info!("DONE COPYING");
+
Ok(())
}
@@ -350,13 +375,15 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
}
fn read_state<P: FlashConfig>(&mut self, p: &mut P) -> Result<State, BootError> {
- let mut magic: [u8; 4] = [0; 4];
+ let mut magic: [u8; WRITE_SIZE] = [0; WRITE_SIZE];
let flash = p.flash();
flash.read(self.state.from as u32, &mut magic)?;
- match u32::from_le_bytes(magic) {
- SWAP_MAGIC => Ok(State::Swap),
- _ => Ok(State::Boot),
+ info!("Read magic: {:x}", magic);
+ if magic == [SWAP_MAGIC; WRITE_SIZE] {
+ Ok(State::Swap)
+ } else {
+ Ok(State::Boot)
}
}
}
@@ -424,6 +451,39 @@ pub struct FirmwareUpdater {
dfu: Partition,
}
+#[cfg(feature = "write-4")]
+#[repr(align(4))]
+pub struct Aligned([u8; 4]);
+
+#[cfg(feature = "write-8")]
+#[repr(align(8))]
+pub struct Aligned([u8; 8]);
+
+impl Default for FirmwareUpdater {
+ fn default() -> Self {
+ extern "C" {
+ static __bootloader_state_start: u32;
+ static __bootloader_state_end: u32;
+ static __bootloader_dfu_start: u32;
+ static __bootloader_dfu_end: u32;
+ }
+
+ let dfu = unsafe {
+ Partition::new(
+ &__bootloader_dfu_start as *const u32 as usize,
+ &__bootloader_dfu_end as *const u32 as usize,
+ )
+ };
+ let state = unsafe {
+ Partition::new(
+ &__bootloader_state_start as *const u32 as usize,
+ &__bootloader_state_end as *const u32 as usize,
+ )
+ };
+ FirmwareUpdater::new(dfu, state)
+ }
+}
+
impl FirmwareUpdater {
pub const fn new(dfu: Partition, state: Partition) -> Self {
Self { dfu, state }
@@ -435,53 +495,45 @@ impl FirmwareUpdater {
}
/// Instruct bootloader that DFU should commence at next boot.
+ /// Must be provided with an aligned buffer to use for reading and writing magic;
pub async fn mark_update<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> {
- #[repr(align(4))]
- struct Aligned([u8; 4]);
-
- let mut magic = Aligned([0; 4]);
- flash.read(self.state.from as u32, &mut magic.0).await?;
- let magic = u32::from_le_bytes(magic.0);
-
- if magic != SWAP_MAGIC {
- flash
- .write(self.state.from as u32, &Aligned([0; 4]).0)
- .await?;
- flash
- .erase(self.state.from as u32, self.state.to as u32)
- .await?;
- trace!(
- "Setting swap magic at {} to 0x{:x}, LE: 0x{:x}",
- self.state.from,
- &SWAP_MAGIC,
- &SWAP_MAGIC.to_le_bytes()
- );
- flash
- .write(self.state.from as u32, &SWAP_MAGIC.to_le_bytes())
- .await?;
- }
- Ok(())
+ let mut aligned = Aligned([0; WRITE_SIZE]);
+ self.set_magic(&mut aligned.0, SWAP_MAGIC, flash).await
}
/// Mark firmware boot successfully
pub async fn mark_booted<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> {
- #[repr(align(4))]
- struct Aligned([u8; 4]);
+ let mut aligned = Aligned([0; WRITE_SIZE]);
+ self.set_magic(&mut aligned.0, BOOT_MAGIC, flash).await
+ }
- let mut magic = Aligned([0; 4]);
- flash.read(self.state.from as u32, &mut magic.0).await?;
- let magic = u32::from_le_bytes(magic.0);
+ async fn set_magic<F: AsyncNorFlash>(
+ &mut self,
+ aligned: &mut [u8],
+ magic: u8,
+ flash: &mut F,
+ ) -> Result<(), F::Error> {
+ flash.read(self.state.from as u32, aligned).await?;
- if magic != BOOT_MAGIC {
- flash
- .write(self.state.from as u32, &Aligned([0; 4]).0)
- .await?;
+ let mut is_set = true;
+ for b in 0..aligned.len() {
+ if aligned[b] != magic {
+ is_set = false;
+ }
+ }
+ if !is_set {
+ for i in 0..aligned.len() {
+ aligned[i] = 0;
+ }
+ flash.write(self.state.from as u32, aligned).await?;
flash
.erase(self.state.from as u32, self.state.to as u32)
.await?;
- flash
- .write(self.state.from as u32, &BOOT_MAGIC.to_le_bytes())
- .await?;
+
+ for i in 0..aligned.len() {
+ aligned[i] = magic;
+ }
+ flash.write(self.state.from as u32, aligned).await?;
}
Ok(())
}
@@ -545,6 +597,7 @@ mod tests {
use super::*;
use core::convert::Infallible;
use core::future::Future;
+ use embedded_storage::nor_flash::ErrorType;
use embedded_storage_async::nor_flash::AsyncReadNorFlash;
use futures::executor::block_on;
@@ -552,9 +605,11 @@ mod tests {
const ACTIVE: Partition = Partition::new(4096, 61440);
const DFU: Partition = Partition::new(61440, 122880);
+ /*
#[test]
fn test_bad_magic() {
let mut flash = MemFlash([0xff; 131072]);
+ let mut flash = SingleFlashProvider::new(&mut flash);
let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE);
@@ -563,11 +618,13 @@ mod tests {
Err(BootError::BadMagic)
);
}
+ */
#[test]
fn test_boot_state() {
let mut flash = MemFlash([0xff; 131072]);
- flash.0[0..4].copy_from_slice(&BOOT_MAGIC.to_le_bytes());
+ flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]);
+ let mut flash = SingleFlashProvider::new(&mut flash);
let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE);
@@ -588,19 +645,19 @@ mod tests {
let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE);
let mut updater = FirmwareUpdater::new(DFU, STATE);
- for i in (DFU.from..DFU.to).step_by(4) {
- let base = i - DFU.from;
- let data: [u8; 4] = [
- update[base],
- update[base + 1],
- update[base + 2],
- update[base + 3],
- ];
- block_on(updater.write_firmware(i - DFU.from, &data, &mut flash)).unwrap();
+ let mut offset = 0;
+ for chunk in update.chunks(4096) {
+ block_on(updater.write_firmware(offset, &chunk, &mut flash, 4096)).unwrap();
+ offset += chunk.len();
}
block_on(updater.mark_update(&mut flash)).unwrap();
- assert_eq!(State::Swap, bootloader.prepare_boot(&mut flash).unwrap());
+ assert_eq!(
+ State::Swap,
+ bootloader
+ .prepare_boot(&mut SingleFlashProvider::new(&mut flash))
+ .unwrap()
+ );
for i in ACTIVE.from..ACTIVE.to {
assert_eq!(flash.0[i], update[i - ACTIVE.from], "Index {}", i);
@@ -612,7 +669,12 @@ mod tests {
}
// Running again should cause a revert
- assert_eq!(State::Swap, bootloader.prepare_boot(&mut flash).unwrap());
+ assert_eq!(
+ State::Swap,
+ bootloader
+ .prepare_boot(&mut SingleFlashProvider::new(&mut flash))
+ .unwrap()
+ );
for i in ACTIVE.from..ACTIVE.to {
assert_eq!(flash.0[i], original[i - ACTIVE.from], "Index {}", i);
@@ -625,7 +687,12 @@ mod tests {
// Mark as booted
block_on(updater.mark_booted(&mut flash)).unwrap();
- assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash).unwrap());
+ assert_eq!(
+ State::Boot,
+ bootloader
+ .prepare_boot(&mut SingleFlashProvider::new(&mut flash))
+ .unwrap()
+ );
}
struct MemFlash([u8; 131072]);
@@ -656,9 +723,12 @@ mod tests {
}
}
+ impl ErrorType for MemFlash {
+ type Error = Infallible;
+ }
+
impl ReadNorFlash for MemFlash {
const READ_SIZE: usize = 4;
- type Error = Infallible;
fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> {
let len = buf.len();
@@ -673,10 +743,9 @@ mod tests {
impl AsyncReadNorFlash for MemFlash {
const READ_SIZE: usize = 4;
- type Error = Infallible;
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a;
- fn read<'a>(&'a mut self, offset: usize, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
+ fn read<'a>(&'a mut self, offset: u32, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
async move {
let len = buf.len();
buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]);
diff --git a/embassy-boot/nrf/Cargo.toml b/embassy-boot/nrf/Cargo.toml
index 97207ac2..78157d24 100644
--- a/embassy-boot/nrf/Cargo.toml
+++ b/embassy-boot/nrf/Cargo.toml
@@ -13,7 +13,7 @@ defmt-rtt = { version = "0.3", optional = true }
embassy = { path = "../../embassy", default-features = false }
embassy-nrf = { path = "../../embassy-nrf", default-features = false, features = ["nightly"] }
-embassy-boot = { path = "../boot", default-features = false }
+embassy-boot = { path = "../boot", default-features = false, features = ["write-4"] }
cortex-m = { version = "0.7" }
cortex-m-rt = { version = "0.7" }
embedded-storage = "0.3.0"
diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs
index 785cb67e..500cae50 100644
--- a/embassy-boot/nrf/src/lib.rs
+++ b/embassy-boot/nrf/src/lib.rs
@@ -4,9 +4,7 @@
mod fmt;
-pub use embassy_boot::{
- FirmwareUpdater, FlashProvider, Partition, SingleFlashProvider, State, BOOT_MAGIC,
-};
+pub use embassy_boot::{FirmwareUpdater, FlashProvider, Partition, SingleFlashProvider};
use embassy_nrf::{
nvmc::{Nvmc, PAGE_SIZE},
peripherals::WDT,
@@ -184,29 +182,3 @@ impl<'d> ReadNorFlash for WatchdogFlash<'d> {
self.flash.capacity()
}
}
-
-pub mod updater {
- use super::*;
- pub fn new() -> embassy_boot::FirmwareUpdater {
- extern "C" {
- static __bootloader_state_start: u32;
- static __bootloader_state_end: u32;
- static __bootloader_dfu_start: u32;
- static __bootloader_dfu_end: u32;
- }
-
- let dfu = unsafe {
- Partition::new(
- &__bootloader_dfu_start as *const u32 as usize,
- &__bootloader_dfu_end as *const u32 as usize,
- )
- };
- let state = unsafe {
- Partition::new(
- &__bootloader_state_start as *const u32 as usize,
- &__bootloader_state_end as *const u32 as usize,
- )
- };
- embassy_boot::FirmwareUpdater::new(dfu, state)
- }
-}
diff --git a/embassy-boot/stm32/Cargo.toml b/embassy-boot/stm32/Cargo.toml
new file mode 100644
index 00000000..76bc480b
--- /dev/null
+++ b/embassy-boot/stm32/Cargo.toml
@@ -0,0 +1,65 @@
+[package]
+authors = [
+ "Ulf Lilleengen <lulf@redhat.com>",
+]
+edition = "2018"
+name = "embassy-boot-stm32"
+version = "0.1.0"
+description = "Bootloader for STM32 chips"
+
+[dependencies]
+defmt = { version = "0.3", optional = true }
+defmt-rtt = { version = "0.3", optional = true }
+
+embassy = { path = "../../embassy", default-features = false }
+embassy-stm32 = { path = "../../embassy-stm32", default-features = false, features = ["nightly"] }
+embassy-boot = { path = "../boot", default-features = false }
+cortex-m = { version = "0.7" }
+cortex-m-rt = { version = "0.7" }
+embedded-storage = "0.3.0"
+embedded-storage-async = "0.3.0"
+cfg-if = "1.0.0"
+
+[features]
+defmt = [
+ "dep:defmt",
+ "embassy-boot/defmt",
+ "embassy-stm32/defmt",
+]
+debug = ["defmt-rtt"]
+flash-2k = ["embassy-boot/write-8"]
+flash-128 = ["embassy-boot/write-4"]
+flash-256 = ["embassy-boot/write-4"]
+invert-erase = ["embassy-boot/invert-erase"]
+thumbv6 = []
+
+[profile.dev]
+debug = 2
+debug-assertions = true
+incremental = false
+opt-level = 'z'
+overflow-checks = true
+
+[profile.release]
+codegen-units = 1
+debug = 2
+debug-assertions = false
+incremental = false
+lto = 'fat'
+opt-level = 'z'
+overflow-checks = false
+
+# do not optimize proc-macro crates = faster builds from scratch
+[profile.dev.build-override]
+codegen-units = 8
+debug = false
+debug-assertions = false
+opt-level = 0
+overflow-checks = false
+
+[profile.release.build-override]
+codegen-units = 8
+debug = false
+debug-assertions = false
+opt-level = 0
+overflow-checks = false
diff --git a/embassy-boot/stm32/README.md b/embassy-boot/stm32/README.md
new file mode 100644
index 00000000..a82b730b
--- /dev/null
+++ b/embassy-boot/stm32/README.md
@@ -0,0 +1,11 @@
+# Bootloader for STM32
+
+The bootloader uses `embassy-boot` to interact with the flash.
+
+# Usage
+
+Flash the bootloader
+
+```
+cargo flash --features embassy-stm32/stm32wl55jc-cm4 --release --chip STM32WLE5JCIx
+```
diff --git a/embassy-boot/stm32/build.rs b/embassy-boot/stm32/build.rs
new file mode 100644
index 00000000..fd605991
--- /dev/null
+++ b/embassy-boot/stm32/build.rs
@@ -0,0 +1,27 @@
+use std::env;
+use std::fs::File;
+use std::io::Write;
+use std::path::PathBuf;
+
+fn main() {
+ // Put `memory.x` in our output directory and ensure it's
+ // on the linker search path.
+ let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
+ File::create(out.join("memory.x"))
+ .unwrap()
+ .write_all(include_bytes!("memory.x"))
+ .unwrap();
+ println!("cargo:rustc-link-search={}", out.display());
+
+ // By default, Cargo will re-run a build script whenever
+ // any file in the project changes. By specifying `memory.x`
+ // here, we ensure the build script is only re-run when
+ // `memory.x` is changed.
+ println!("cargo:rerun-if-changed=memory.x");
+
+ println!("cargo:rustc-link-arg-bins=--nmagic");
+ println!("cargo:rustc-link-arg-bins=-Tlink.x");
+ if env::var("CARGO_FEATURE_DEFMT").is_ok() {
+ println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
+ }
+}
diff --git a/embassy-boot/stm32/memory.x b/embassy-boot/stm32/memory.x
new file mode 100644
index 00000000..c5356ed3
--- /dev/null
+++ b/embassy-boot/stm32/memory.x
@@ -0,0 +1,18 @@
+MEMORY
+{
+ /* NOTE 1 K = 1 KiBi = 1024 bytes */
+ FLASH : ORIGIN = 0x08000000, LENGTH = 24K
+ BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K
+ ACTIVE : ORIGIN = 0x08008000, LENGTH = 32K
+ DFU : ORIGIN = 0x08010000, LENGTH = 36K
+ RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 16K
+}
+
+__bootloader_state_start = ORIGIN(BOOTLOADER_STATE);
+__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE);
+
+__bootloader_active_start = ORIGIN(ACTIVE);
+__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE);
+
+__bootloader_dfu_start = ORIGIN(DFU);
+__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU);
diff --git a/embassy-boot/stm32/src/fmt.rs b/embassy-boot/stm32/src/fmt.rs
new file mode 100644
index 00000000..06697081
--- /dev/null
+++ b/embassy-boot/stm32/src/fmt.rs
@@ -0,0 +1,225 @@
+#![macro_use]
+#![allow(unused_macros)]
+
+#[cfg(all(feature = "defmt", feature = "log"))]
+compile_error!("You may not enable both `defmt` and `log` features.");
+
+macro_rules! assert {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::assert!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::assert!($($x)*);
+ }
+ };
+}
+
+macro_rules! assert_eq {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::assert_eq!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::assert_eq!($($x)*);
+ }
+ };
+}
+
+macro_rules! assert_ne {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::assert_ne!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::assert_ne!($($x)*);
+ }
+ };
+}
+
+macro_rules! debug_assert {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::debug_assert!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::debug_assert!($($x)*);
+ }
+ };
+}
+
+macro_rules! debug_assert_eq {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::debug_assert_eq!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::debug_assert_eq!($($x)*);
+ }
+ };
+}
+
+macro_rules! debug_assert_ne {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::debug_assert_ne!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::debug_assert_ne!($($x)*);
+ }
+ };
+}
+
+macro_rules! todo {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::todo!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::todo!($($x)*);
+ }
+ };
+}
+
+macro_rules! unreachable {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::unreachable!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::unreachable!($($x)*);
+ }
+ };
+}
+
+macro_rules! panic {
+ ($($x:tt)*) => {
+ {
+ #[cfg(not(feature = "defmt"))]
+ ::core::panic!($($x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::panic!($($x)*);
+ }
+ };
+}
+
+macro_rules! trace {
+ ($s:literal $(, $x:expr)* $(,)?) => {
+ {
+ #[cfg(feature = "log")]
+ ::log::trace!($s $(, $x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::trace!($s $(, $x)*);
+ #[cfg(not(any(feature = "log", feature="defmt")))]
+ let _ = ($( & $x ),*);
+ }
+ };
+}
+
+macro_rules! debug {
+ ($s:literal $(, $x:expr)* $(,)?) => {
+ {
+ #[cfg(feature = "log")]
+ ::log::debug!($s $(, $x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::debug!($s $(, $x)*);
+ #[cfg(not(any(feature = "log", feature="defmt")))]
+ let _ = ($( & $x ),*);
+ }
+ };
+}
+
+macro_rules! info {
+ ($s:literal $(, $x:expr)* $(,)?) => {
+ {
+ #[cfg(feature = "log")]
+ ::log::info!($s $(, $x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::info!($s $(, $x)*);
+ #[cfg(not(any(feature = "log", feature="defmt")))]
+ let _ = ($( & $x ),*);
+ }
+ };
+}
+
+macro_rules! warn {
+ ($s:literal $(, $x:expr)* $(,)?) => {
+ {
+ #[cfg(feature = "log")]
+ ::log::warn!($s $(, $x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::warn!($s $(, $x)*);
+ #[cfg(not(any(feature = "log", feature="defmt")))]
+ let _ = ($( & $x ),*);
+ }
+ };
+}
+
+macro_rules! error {
+ ($s:literal $(, $x:expr)* $(,)?) => {
+ {
+ #[cfg(feature = "log")]
+ ::log::error!($s $(, $x)*);
+ #[cfg(feature = "defmt")]
+ ::defmt::error!($s $(, $x)*);
+ #[cfg(not(any(feature = "log", feature="defmt")))]
+ let _ = ($( & $x ),*);
+ }
+ };
+}
+
+#[cfg(feature = "defmt")]
+macro_rules! unwrap {
+ ($($x:tt)*) => {
+ ::defmt::unwrap!($($x)*)
+ };
+}
+
+#[cfg(not(feature = "defmt"))]
+macro_rules! unwrap {
+ ($arg:expr) => {
+ match $crate::fmt::Try::into_result($arg) {
+ ::core::result::Result::Ok(t) => t,
+ ::core::result::Result::Err(e) => {
+ ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
+ }
+ }
+ };
+ ($arg:expr, $($msg:expr),+ $(,)? ) => {
+ match $crate::fmt::Try::into_result($arg) {
+ ::core::result::Result::Ok(t) => t,
+ ::core::result::Result::Err(e) => {
+ ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
+ }
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub struct NoneError;
+
+pub trait Try {
+ type Ok;
+ type Error;
+ fn into_result(self) -> Result<Self::Ok, Self::Error>;
+}
+
+impl<T> Try for Option<T> {
+ type Ok = T;
+ type Error = NoneError;
+
+ #[inline]
+ fn into_result(self) -> Result<T, NoneError> {
+ self.ok_or(NoneError)
+ }
+}
+
+impl<T, E> Try for Result<T, E> {
+ type Ok = T;
+ type Error = E;
+
+ #[inline]
+ fn into_result(self) -> Self {
+ self
+ }
+}
diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs
new file mode 100644
index 00000000..d1754a31
--- /dev/null
+++ b/embassy-boot/stm32/src/lib.rs
@@ -0,0 +1,75 @@
+#![no_std]
+#![feature(generic_associated_types)]
+#![feature(type_alias_impl_trait)]
+
+mod fmt;
+
+pub use embassy_boot::{FirmwareUpdater, FlashProvider, Partition, SingleFlashProvider, State};
+
+pub struct BootLoader<const PAGE_SIZE: usize> {
+ boot: embassy_boot::BootLoader<PAGE_SIZE>,
+}
+
+impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
+ /// Create a new bootloader instance using parameters from linker script
+ pub fn default() -> Self {
+ extern "C" {
+ static __bootloader_state_start: u32;
+ static __bootloader_state_end: u32;
+ static __bootloader_active_start: u32;
+ static __bootloader_active_end: u32;
+ static __bootloader_dfu_start: u32;
+ static __bootloader_dfu_end: u32;
+ }
+
+ let active = unsafe {
+ Partition::new(
+ &__bootloader_active_start as *const u32 as usize,
+ &__bootloader_active_end as *const u32 as usize,
+ )
+ };
+ let dfu = unsafe {
+ Partition::new(
+ &__bootloader_dfu_start as *const u32 as usize,
+ &__bootloader_dfu_end as *const u32 as usize,
+ )
+ };
+ let state = unsafe {
+ Partition::new(
+ &__bootloader_state_start as *const u32 as usize,
+ &__bootloader_state_end as *const u32 as usize,
+ )
+ };
+
+ trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to);
+ trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
+ trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
+
+ Self::new(active, dfu, state)
+ }
+
+ /// Create a new bootloader instance using the supplied partitions for active, dfu and state.
+ pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
+ Self {
+ boot: embassy_boot::BootLoader::new(active, dfu, state),
+ }
+ }
+
+ /// Boots the application
+ pub fn prepare<F: FlashProvider>(&mut self, flash: &mut F) -> usize {
+ match self.boot.prepare_boot(flash) {
+ Ok(_) => self.boot.boot_address(),
+ Err(_) => panic!("boot prepare error!"),
+ }
+ }
+
+ pub unsafe fn load(&mut self, start: usize) -> ! {
+ trace!("Loading app at 0x{:x}", start);
+ let mut p = cortex_m::Peripherals::steal();
+ #[cfg(not(feature = "thumbv6"))]
+ p.SCB.invalidate_icache();
+ p.SCB.vtor.write(start as u32);
+
+ cortex_m::asm::bootload(start as *const u32)
+ }
+}
diff --git a/embassy-boot/stm32/src/main.rs b/embassy-boot/stm32/src/main.rs
new file mode 100644
index 00000000..6fe0fb66
--- /dev/null
+++ b/embassy-boot/stm32/src/main.rs
@@ -0,0 +1,62 @@
+#![no_std]
+#![no_main]
+
+use cortex_m_rt::{entry, exception};
+
+#[cfg(feature = "defmt")]
+use defmt_rtt as _;
+
+use embassy_boot_stm32::*;
+use embassy_stm32::flash::Flash;
+
+#[cfg(not(any(feature = "flash-2k", feature = "flash-256", feature = "flash-128")))]
+compile_error!("No flash size specified. Must specify exactly one of the following features: flash-2k, flash-256, flash-128");
+
+#[entry]
+fn main() -> ! {
+ let p = embassy_stm32::init(Default::default());
+
+ // Uncomment this if you are debugging the bootloader with debugger/RTT attached,
+ // as it prevents a hard fault when accessing flash 'too early' after boot.
+ /*
+ for i in 0..10000000 {
+ cortex_m::asm::nop();
+ }
+ */
+
+ #[cfg(feature = "flash-2k")]
+ let mut bl: BootLoader<2048> = BootLoader::default();
+
+ #[cfg(feature = "flash-256")]
+ let mut bl: BootLoader<256> = BootLoader::default();
+
+ #[cfg(feature = "flash-128")]
+ let mut bl: BootLoader<128> = BootLoader::default();
+
+ let mut flash = Flash::unlock(p.FLASH);
+ let start = bl.prepare(&mut SingleFlashProvider::new(&mut flash));
+ core::mem::drop(flash);
+ unsafe { bl.load(start) }
+}
+
+#[no_mangle]
+#[cfg_attr(target_os = "none", link_section = ".HardFault.user")]
+unsafe extern "C" fn HardFault() {
+ cortex_m::peripheral::SCB::sys_reset();
+}
+
+#[exception]
+unsafe fn DefaultHandler(_: i16) -> ! {
+ const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32;
+ let irqn = core::ptr::read_volatile(SCB_ICSR) as u8 as i16 - 16;
+
+ panic!("DefaultHandler #{:?}", irqn);
+}
+
+#[panic_handler]
+fn panic(_info: &core::panic::PanicInfo) -> ! {
+ unsafe {
+ cortex_m::asm::udf();
+ core::hint::unreachable_unchecked();
+ }
+}
diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml
index 1c9616b7..8152b07d 100644
--- a/embassy-stm32/Cargo.toml
+++ b/embassy-stm32/Cargo.toml
@@ -42,6 +42,9 @@ embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["un
embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8", optional = true}
embedded-hal-async = { version = "0.1.0-alpha.0", optional = true}
+embedded-storage = "0.3.0"
+embedded-storage-async = { version = "0.3.0", optional = true }
+
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
cortex-m-rt = ">=0.6.15,<0.8"
@@ -87,7 +90,7 @@ time-driver-tim12 = ["_time-driver"]
time-driver-tim15 = ["_time-driver"]
# Enable nightly-only features
-nightly = ["embassy/nightly", "embedded-hal-1", "embedded-hal-async"]
+nightly = ["embassy/nightly", "embedded-hal-1", "embedded-hal-async", "embedded-storage-async"]
# Reexport stm32-metapac at `embassy_stm32::pac`.
# This is unstable because semver-minor (non-breaking) releases of embassy-stm32 may major-bump (breaking) the stm32-metapac version.
diff --git a/embassy-stm32/src/flash/mod.rs b/embassy-stm32/src/flash/mod.rs
new file mode 100644
index 00000000..7a282497
--- /dev/null
+++ b/embassy-stm32/src/flash/mod.rs
@@ -0,0 +1,401 @@
+use crate::pac;
+use crate::peripherals::FLASH;
+use core::convert::TryInto;
+use core::marker::PhantomData;
+use core::ptr::write_volatile;
+use embassy::util::Unborrow;
+use embassy_hal_common::unborrow;
+
+use embedded_storage::nor_flash::{
+ ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash,
+};
+
+const FLASH_BASE: usize = 0x8000000;
+
+#[cfg(flash_l4)]
+mod config {
+ use super::*;
+ pub(crate) const FLASH_SIZE: usize = 0x100000;
+ pub(crate) const FLASH_START: usize = FLASH_BASE;
+ pub(crate) const FLASH_END: usize = FLASH_START + FLASH_SIZE;
+ pub(crate) const PAGE_SIZE: usize = 2048;
+ pub(crate) const WORD_SIZE: usize = 8;
+}
+
+#[cfg(flash_wb)]
+mod config {
+ use super::*;
+ pub(crate) const FLASH_SIZE: usize = 0x100000;
+ pub(crate) const FLASH_START: usize = FLASH_BASE;
+ pub(crate) const FLASH_END: usize = FLASH_START + FLASH_SIZE;
+ pub(crate) const PAGE_SIZE: usize = 4096;
+ pub(crate) const WORD_SIZE: usize = 8;
+}
+
+#[cfg(flash_wl)]
+mod config {
+ use super::*;
+ pub(crate) const FLASH_SIZE: usize = 0x40000;
+ pub(crate) const FLASH_START: usize = FLASH_BASE;
+ pub(crate) const FLASH_END: usize = FLASH_START + FLASH_SIZE;
+ pub(crate) const PAGE_SIZE: usize = 2048;
+ pub(crate) const WORD_SIZE: usize = 8;
+}
+
+#[cfg(flash_l0)]
+mod config {
+ use super::*;
+ pub(crate) const FLASH_SIZE: usize = 0x30000;
+ pub(crate) const FLASH_START: usize = FLASH_BASE;
+ pub(crate) const FLASH_END: usize = FLASH_START + FLASH_SIZE;
+ pub(crate) const PAGE_SIZE: usize = 128;
+ pub(crate) const WORD_SIZE: usize = 4;
+}
+
+#[cfg(flash_l1)]
+mod config {
+ use super::*;
+ pub(crate) const FLASH_SIZE: usize = 0x80000;
+ pub(crate) const FLASH_START: usize = FLASH_BASE;
+ pub(crate) const FLASH_END: usize = FLASH_START + FLASH_SIZE;
+ pub(crate) const PAGE_SIZE: usize = 256;
+ pub(crate) const WORD_SIZE: usize = 4;
+}
+
+use config::*;
+
+pub struct Flash<'d> {
+ _inner: FLASH,
+ _phantom: PhantomData<&'d mut FLASH>,
+}
+
+impl<'d> Flash<'d> {
+ pub fn new(p: impl Unborrow<Target = FLASH>) -> Self {
+ unborrow!(p);
+ Self {
+ _inner: p,
+ _phantom: PhantomData,
+ }
+ }
+
+ pub fn unlock(p: impl Unborrow<Target = FLASH>) -> Self {
+ let flash = Self::new(p);
+ #[cfg(any(flash_wl, flash_wb, flash_l4))]
+ unsafe {
+ pac::FLASH.keyr().write(|w| w.set_keyr(0x4567_0123));
+ pac::FLASH.keyr().write(|w| w.set_keyr(0xCDEF_89AB));
+ }
+
+ #[cfg(any(flash_l0))]
+ unsafe {
+ pac::FLASH.pekeyr().write(|w| w.set_pekeyr(0x89ABCDEF));
+ pac::FLASH.pekeyr().write(|w| w.set_pekeyr(0x02030405));
+
+ pac::FLASH.prgkeyr().write(|w| w.set_prgkeyr(0x8C9DAEBF));
+ pac::FLASH.prgkeyr().write(|w| w.set_prgkeyr(0x13141516));
+ }
+ flash
+ }
+
+ pub fn lock(&mut self) {
+ #[cfg(any(flash_wl, flash_wb, flash_l4))]
+ unsafe {
+ pac::FLASH.cr().modify(|w| w.set_lock(true));
+ }
+
+ #[cfg(any(flash_l0))]
+ unsafe {
+ pac::FLASH.pecr().modify(|w| {
+ w.set_optlock(true);
+ w.set_prglock(true);
+ w.set_pelock(true);
+ });
+ }
+ }
+
+ pub fn blocking_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> {
+ if offset as usize >= FLASH_END || offset as usize + bytes.len() > FLASH_END {
+ return Err(Error::Size);
+ }
+
+ let flash_data = unsafe { core::slice::from_raw_parts(offset as *const u8, bytes.len()) };
+ bytes.copy_from_slice(flash_data);
+ Ok(())
+ }
+
+ pub fn blocking_write(&mut self, offset: u32, buf: &[u8]) -> Result<(), Error> {
+ if offset as usize + buf.len() > FLASH_END {
+ return Err(Error::Size);
+ }
+ if offset as usize % WORD_SIZE != 0 || buf.len() as usize % WORD_SIZE != 0 {
+ return Err(Error::Unaligned);
+ }
+
+ self.clear_all_err();
+
+ #[cfg(any(flash_wl, flash_wb, flash_l4))]
+ unsafe {
+ pac::FLASH.cr().write(|w| w.set_pg(true));
+ }
+
+ let mut ret: Result<(), Error> = Ok(());
+ let mut offset = offset;
+ for chunk in buf.chunks(WORD_SIZE) {
+ for val in chunk.chunks(4) {
+ unsafe {
+ write_volatile(
+ offset as *mut u32,
+ u32::from_le_bytes(val[0..4].try_into().unwrap()),
+ );
+ }
+ offset += val.len() as u32;
+ }
+
+ ret = self.blocking_wait_ready();
+ if ret.is_err() {
+ break;
+ }
+ }
+
+ #[cfg(any(flash_wl, flash_wb, flash_l4))]
+ unsafe {
+ pac::FLASH.cr().write(|w| w.set_pg(false));
+ }
+
+ ret
+ }
+
+ pub fn blocking_erase(&mut self, from: u32, to: u32) -> Result<(), Error> {
+ if to < from || to as usize > FLASH_END {
+ return Err(Error::Size);
+ }
+ if from as usize % PAGE_SIZE != 0 || to as usize % PAGE_SIZE != 0 {
+ return Err(Error::Unaligned);
+ }
+
+ self.clear_all_err();
+
+ for page in (from..to).step_by(PAGE_SIZE) {
+ #[cfg(any(flash_l0, flash_l1))]
+ unsafe {
+ pac::FLASH.pecr().modify(|w| {
+ w.set_erase(true);
+ w.set_prog(true);
+ });
+
+ write_volatile(page as *mut u32, 0xFFFFFFFF);
+ }
+
+ #[cfg(any(flash_wl, flash_wb, flash_l4))]
+ unsafe {
+ let idx = page / PAGE_SIZE as u32;
+
+ pac::FLASH.cr().modify(|w| {
+ w.set_per(true);
+ w.set_pnb(idx as u8);
+ #[cfg(any(flash_wl, flash_wb))]
+ w.set_strt(true);
+ #[cfg(any(flash_l4))]
+ w.set_start(true);
+ });
+ }
+
+ let ret: Result<(), Error> = self.blocking_wait_ready();
+
+ #[cfg(any(flash_wl, flash_wb, flash_l4))]
+ unsafe {
+ pac::FLASH.cr().modify(|w| w.set_per(false));
+ }
+
+ #[cfg(any(flash_l0, flash_l1))]
+ unsafe {
+ pac::FLASH.pecr().modify(|w| {
+ w.set_erase(false);
+ w.set_prog(false);
+ });
+ }
+
+ self.clear_all_err();
+ if ret.is_err() {
+ return ret;
+ }
+ }
+
+ Ok(())
+ }
+
+ fn blocking_wait_ready(&self) -> Result<(), Error> {
+ loop {
+ let sr = unsafe { pac::FLASH.sr().read() };
+
+ if !sr.bsy() {
+ #[cfg(any(flash_wl, flash_wb, flash_l4))]
+ if sr.progerr() {
+ return Err(Error::Prog);
+ }
+
+ if sr.wrperr() {
+ return Err(Error::Protected);
+ }
+
+ if sr.pgaerr() {
+ return Err(Error::Unaligned);
+ }
+
+ if sr.sizerr() {
+ return Err(Error::Size);
+ }
+
+ #[cfg(any(flash_wl, flash_wb, flash_l4))]
+ if sr.miserr() {
+ return Err(Error::Miss);
+ }
+
+ #[cfg(any(flash_wl, flash_wb, flash_l4))]
+ if sr.pgserr() {
+ return Err(Error::Seq);
+ }
+ return Ok(());
+ }
+ }
+ }
+
+ fn clear_all_err(&mut self) {
+ unsafe {
+ pac::FLASH.sr().modify(|w| {
+ #[cfg(any(flash_wl, flash_wb, flash_l4, flash_l0))]
+ if w.rderr() {
+ w.set_rderr(false);
+ }
+ #[cfg(any(flash_wl, flash_wb, flash_l4))]
+ if w.fasterr() {
+ w.set_fasterr(false);
+ }
+ #[cfg(any(flash_wl, flash_wb, flash_l4))]
+ if w.miserr() {
+ w.set_miserr(false);
+ }
+ #[cfg(any(flash_wl, flash_wb, flash_l4))]
+ if w.pgserr() {
+ w.set_pgserr(false);
+ }
+ if w.sizerr() {
+ w.set_sizerr(false);
+ }
+ if w.pgaerr() {
+ w.set_pgaerr(false);
+ }
+ if w.wrperr() {
+ w.set_wrperr(false);
+ }
+ #[cfg(any(flash_wl, flash_wb, flash_l4))]
+ if w.progerr() {
+ w.set_progerr(false);
+ }
+ #[cfg(any(flash_wl, flash_wb, flash_l4))]
+ if w.operr() {
+ w.set_operr(false);
+ }
+ });
+ }
+ }
+}
+
+impl Drop for Flash<'_> {
+ fn drop(&mut self) {
+ self.lock();
+ }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+pub enum Error {
+ Prog,
+ Size,
+ Miss,
+ Seq,
+ Protected,
+ Unaligned,
+}
+
+impl<'d> ErrorType for Flash<'d> {
+ type Error = Error;
+}
+
+impl NorFlashError for Error {
+ fn kind(&self) -> NorFlashErrorKind {
+ match self {
+ Self::Size => NorFlashErrorKind::OutOfBounds,
+ Self::Unaligned => NorFlashErrorKind::NotAligned,
+ _ => NorFlashErrorKind::Other,
+ }
+ }
+}
+
+impl<'d> ReadNorFlash for Flash<'d> {
+ const READ_SIZE: usize = WORD_SIZE;
+
+ fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
+ self.blocking_read(offset, bytes)
+ }
+
+ fn capacity(&self) -> usize {
+ FLASH_SIZE
+ }
+}
+
+impl<'d> NorFlash for Flash<'d> {
+ const WRITE_SIZE: usize = WORD_SIZE;
+ const ERASE_SIZE: usize = PAGE_SIZE;
+
+ fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
+ self.blocking_erase(from, to)
+ }
+
+ fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
+ self.blocking_write(offset, bytes)
+ }
+}
+
+/*
+cfg_if::cfg_if! {
+ if #[cfg(feature = "nightly")]
+ {
+ use embedded_storage_async::nor_flash::{AsyncNorFlash, AsyncReadNorFlash};
+ use core::future::Future;
+
+ impl<'d> AsyncNorFlash for Flash<'d> {
+ const WRITE_SIZE: usize = <Self as NorFlash>::WRITE_SIZE;
+ const ERASE_SIZE: usize = <Self as NorFlash>::ERASE_SIZE;
+
+ type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+ fn write<'a>(&'a mut self, offset: u32, data: &'a [u8]) -> Self::WriteFuture<'a> {
+ async move {
+ todo!()
+ }
+ }
+
+ type EraseFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+ fn erase<'a>(&'a mut self, from: u32, to: u32) -> Self::EraseFuture<'a> {
+ async move {
+ todo!()
+ }
+ }
+ }
+
+ impl<'d> AsyncReadNorFlash for Flash<'d> {
+ const READ_SIZE: usize = <Self as ReadNorFlash>::READ_SIZE;
+ type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a;
+ fn read<'a>(&'a mut self, address: u32, data: &'a mut [u8]) -> Self::ReadFuture<'a> {
+ async move {
+ todo!()
+ }
+ }
+
+ fn capacity(&self) -> usize {
+ FLASH_SIZE
+ }
+ }
+ }
+}
+*/
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs
index 3417c5d9..ba426128 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -50,6 +50,8 @@ pub mod i2c;
#[cfg(crc)]
pub mod crc;
+#[cfg(any(flash_l0, flash_l1, flash_wl, flash_wb, flash_l4))]
+pub mod flash;
pub mod pwm;
#[cfg(rng)]
pub mod rng;
diff --git a/embassy-stm32/src/rcc/wl.rs b/embassy-stm32/src/rcc/wl.rs
index fb2dd998..1f2fbaf8 100644
--- a/embassy-stm32/src/rcc/wl.rs
+++ b/embassy-stm32/src/rcc/wl.rs
@@ -1,4 +1,4 @@
-use crate::pac::RCC;
+use crate::pac::{FLASH, RCC};
use crate::rcc::{set_freqs, Clocks};
use crate::time::U32Ext;
@@ -15,10 +15,101 @@ pub const HSE32_FREQ: u32 = 32_000_000;
/// System clock mux source
#[derive(Clone, Copy)]
pub enum ClockSrc {
+ MSI(MSIRange),
HSE32,
HSI16,
}
+#[derive(Clone, Copy, PartialOrd, PartialEq)]
+pub enum MSIRange {
+ /// Around 100 kHz
+ Range0,
+ /// Around 200 kHz
+ Range1,
+ /// Around 400 kHz
+ Range2,
+ /// Around 800 kHz
+ Range3,
+ /// Around 1 MHz
+ Range4,
+ /// Around 2 MHz
+ Range5,
+ /// Around 4 MHz (reset value)
+ Range6,
+ /// Around 8 MHz
+ Range7,
+ /// Around 16 MHz
+ Range8,
+ /// Around 24 MHz
+ Range9,
+ /// Around 32 MHz
+ Range10,
+ /// Around 48 MHz
+ Range11,
+}
+
+impl MSIRange {
+ fn freq(&self) -> u32 {
+ match self {
+ MSIRange::Range0 => 100_000,
+ MSIRange::Range1 => 200_000,
+ MSIRange::Range2 => 400_000,
+ MSIRange::Range3 => 800_000,
+ MSIRange::Range4 => 1_000_000,
+ MSIRange::Range5 => 2_000_000,
+ MSIRange::Range6 => 4_000_000,
+ MSIRange::Range7 => 8_000_000,
+ MSIRange::Range8 => 16_000_000,
+ MSIRange::Range9 => 24_000_000,
+ MSIRange::Range10 => 32_000_000,
+ MSIRange::Range11 => 48_000_000,
+ }
+ }
+
+ fn vos(&self) -> VoltageScale {
+ if self > &MSIRange::Range8 {
+ VoltageScale::Range1
+ } else {
+ VoltageScale::Range2
+ }
+ }
+}
+
+impl Default for MSIRange {
+ fn default() -> MSIRange {
+ MSIRange::Range6
+ }
+}
+
+impl Into<u8> for MSIRange {
+ fn into(self) -> u8 {
+ match self {
+ MSIRange::Range0 => 0b0000,
+ MSIRange::Range1 => 0b0001,
+ MSIRange::Range2 => 0b0010,
+ MSIRange::Range3 => 0b0011,
+ MSIRange::Range4 => 0b0100,
+ MSIRange::Range5 => 0b0101,
+ MSIRange::Range6 => 0b0110,
+ MSIRange::Range7 => 0b0111,
+ MSIRange::Range8 => 0b1000,
+ MSIRange::Range9 => 0b1001,
+ MSIRange::Range10 => 0b1010,
+ MSIRange::Range11 => 0b1011,
+ }
+ }
+}
+
+/// Voltage Scale
+///
+/// Represents the voltage range feeding the CPU core. The maximum core
+/// clock frequency depends on this value.
+#[derive(Copy, Clone, PartialEq)]
+pub enum VoltageScale {
+ Range1,
+ Range2,
+}
+
/// AHB prescaler
#[derive(Clone, Copy, PartialEq)]
pub enum AHBPrescaler {
@@ -85,6 +176,7 @@ impl Into<u8> for AHBPrescaler {
pub struct Config {
pub mux: ClockSrc,
pub ahb_pre: AHBPrescaler,
+ pub shd_ahb_pre: AHBPrescaler,
pub apb1_pre: APBPrescaler,
pub apb2_pre: APBPrescaler,
pub enable_lsi: bool,
@@ -94,8 +186,9 @@ impl Default for Config {
#[inline]
fn default() -> Config {
Config {
- mux: ClockSrc::HSI16,
+ mux: ClockSrc::MSI(MSIRange::default()),
ahb_pre: AHBPrescaler::NotDivided,
+ shd_ahb_pre: AHBPrescaler::NotDivided,
apb1_pre: APBPrescaler::NotDivided,
apb2_pre: APBPrescaler::NotDivided,
enable_lsi: false,
@@ -104,13 +197,13 @@ impl Default for Config {
}
pub(crate) unsafe fn init(config: Config) {
- let (sys_clk, sw) = match config.mux {
+ let (sys_clk, sw, vos) = match config.mux {
ClockSrc::HSI16 => {
// Enable HSI16
RCC.cr().write(|w| w.set_hsion(true));
while !RCC.cr().read().hsirdy() {}
- (HSI_FREQ, 0x01)
+ (HSI_FREQ, 0x01, VoltageScale::Range2)
}
ClockSrc::HSE32 => {
// Enable HSE32
@@ -120,7 +213,17 @@ pub(crate) unsafe fn init(config: Config) {
});
while !RCC.cr().read().hserdy() {}
- (HSE32_FREQ, 0x02)
+ (HSE32_FREQ, 0x02, VoltageScale::Range1)
+ }
+ ClockSrc::MSI(range) => {
+ RCC.cr().write(|w| {
+ w.set_msirange(range.into());
+ w.set_msion(true);
+ });
+
+ while !RCC.cr().read().msirdy() {}
+
+ (range.freq(), 0x00, range.vos())
}
};
@@ -135,6 +238,14 @@ pub(crate) unsafe fn init(config: Config) {
w.set_ppre2(config.apb2_pre.into());
});
+ RCC.extcfgr().modify(|w| {
+ if config.shd_ahb_pre == AHBPrescaler::NotDivided {
+ w.set_shdhpre(0);
+ } else {
+ w.set_shdhpre(config.shd_ahb_pre.into());
+ }
+ });
+
let ahb_freq: u32 = match config.ahb_pre {
AHBPrescaler::NotDivided => sys_clk,
pre => {
@@ -144,6 +255,15 @@ pub(crate) unsafe fn init(config: Config) {
}
};
+ let shd_ahb_freq: u32 = match config.shd_ahb_pre {
+ AHBPrescaler::NotDivided => sys_clk,
+ pre => {
+ let pre: u8 = pre.into();
+ let pre = 1 << (pre as u32 - 7);
+ sys_clk / pre
+ }
+ };
+
let (apb1_freq, apb1_tim_freq) = match config.apb1_pre {
APBPrescaler::NotDivided => (ahb_freq, ahb_freq),
pre => {
@@ -164,8 +284,7 @@ pub(crate) unsafe fn init(config: Config) {
}
};
- // TODO: completely untested
- let apb3_freq = ahb_freq;
+ let apb3_freq = shd_ahb_freq;
if config.enable_lsi {
let csr = RCC.csr().read();
@@ -175,11 +294,32 @@ pub(crate) unsafe fn init(config: Config) {
}
}
+ // Adjust flash latency
+ let flash_clk_src_freq: u32 = shd_ahb_freq;
+ let ws = match vos {
+ VoltageScale::Range1 => match flash_clk_src_freq {
+ 0..=18_000_000 => 0b000,
+ 18_000_001..=36_000_000 => 0b001,
+ _ => 0b010,
+ },
+ VoltageScale::Range2 => match flash_clk_src_freq {
+ 0..=6_000_000 => 0b000,
+ 6_000_001..=12_000_000 => 0b001,
+ _ => 0b010,
+ },
+ };
+
+ FLASH.acr().modify(|w| {
+ w.set_latency(ws);
+ });
+
+ while FLASH.acr().read().latency() != ws {}
+
set_freqs(Clocks {
sys: sys_clk.hz(),
ahb1: ahb_freq.hz(),
ahb2: ahb_freq.hz(),
- ahb3: ahb_freq.hz(),
+ ahb3: shd_ahb_freq.hz(),
apb1: apb1_freq.hz(),
apb2: apb2_freq.hz(),
apb3: apb3_freq.hz(),
diff --git a/examples/boot/Cargo.toml b/examples/boot/Cargo.toml
deleted file mode 100644
index 2da65947..00000000
--- a/examples/boot/Cargo.toml
+++ /dev/null
@@ -1,19 +0,0 @@
-[package]
-authors = ["Ulf Lilleengen <lulf@redhat.com>"]
-edition = "2018"
-name = "embassy-boot-examples"
-version = "0.1.0"
-
-[dependencies]
-embassy = { version = "0.1.0", path = "../../embassy", features = ["nightly"] }
-embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", "nightly"] }
-embassy-boot-nrf = { version = "0.1.0", path = "../../embassy-boot/nrf" }
-embassy-traits = { version = "0.1.0", path = "../../embassy-traits" }
-
-defmt = { version = "0.3", optional = true }
-defmt-rtt = { version = "0.3", optional = true }
-panic-reset = { version = "0.1.1" }
-embedded-hal = { version = "0.2.6" }
-
-cortex-m = "0.7.3"
-cortex-m-rt = "0.7.0"
diff --git a/examples/boot/nrf/Cargo.toml b/examples/boot/nrf/Cargo.toml
new file mode 100644
index 00000000..0a5bb8f9
--- /dev/null
+++ b/examples/boot/nrf/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+authors = ["Ulf Lilleengen <lulf@redhat.com>"]
+edition = "2018"
+name = "embassy-boot-nrf-examples"
+version = "0.1.0"
+
+[dependencies]
+embassy = { version = "0.1.0", path = "../../../embassy", features = ["nightly"] }
+embassy-nrf = { version = "0.1.0", path = "../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", "nightly"] }
+embassy-boot-nrf = { version = "0.1.0", path = "../../../embassy-boot/nrf" }
+embassy-traits = { version = "0.1.0", path = "../../../embassy-traits" }
+
+defmt = { version = "0.3", optional = true }
+defmt-rtt = { version = "0.3", optional = true }
+panic-reset = { version = "0.1.1" }
+embedded-hal = { version = "0.2.6" }
+
+cortex-m = "0.7.3"
+cortex-m-rt = "0.7.0"
diff --git a/examples/boot/README.md b/examples/boot/nrf/README.md
index b97513a9..b97513a9 100644
--- a/examples/boot/README.md
+++ b/examples/boot/nrf/README.md
diff --git a/examples/boot/build.rs b/examples/boot/nrf/build.rs
index cd1a264c..cd1a264c 100644
--- a/examples/boot/build.rs
+++ b/examples/boot/nrf/build.rs
diff --git a/examples/boot/memory.x b/examples/boot/nrf/memory.x
index dfb72103..dfb72103 100644
--- a/examples/boot/memory.x
+++ b/examples/boot/nrf/memory.x
diff --git a/examples/boot/src/bin/a.rs b/examples/boot/nrf/src/bin/a.rs
index d18b508c..caf8140d 100644
--- a/examples/boot/src/bin/a.rs
+++ b/examples/boot/nrf/src/bin/a.rs
@@ -4,7 +4,7 @@
#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]
-use embassy_boot_nrf::updater;
+use embassy_boot_nrf::FirmwareUpdater;
use embassy_nrf::{
gpio::{Input, Pull},
gpio::{Level, Output, OutputDrive},
@@ -26,10 +26,10 @@ async fn main(_s: embassy::executor::Spawner, p: Peripherals) {
let nvmc = Nvmc::new(p.NVMC);
let mut nvmc = BlockingAsync::new(nvmc);
+ let mut updater = FirmwareUpdater::default();
loop {
button.wait_for_any_edge().await;
if button.is_low() {
- let mut updater = updater::new();
let mut offset = 0;
for chunk in APP_B.chunks(4096) {
let mut buf: [u8; 4096] = [0; 4096];
diff --git a/examples/boot/src/bin/b.rs b/examples/boot/nrf/src/bin/b.rs
index 18bb6330..18bb6330 100644
--- a/examples/boot/src/bin/b.rs
+++ b/examples/boot/nrf/src/bin/b.rs
diff --git a/examples/boot/stm32l0/.cargo/config.toml b/examples/boot/stm32l0/.cargo/config.toml
new file mode 100644
index 00000000..840faa62
--- /dev/null
+++ b/examples/boot/stm32l0/.cargo/config.toml
@@ -0,0 +1,6 @@
+[target.'cfg(all(target_arch = "arm", target_os = "none"))']
+# replace your chip as listed in `probe-run --list-chips`
+runner = "probe-run --chip STM32L072CZTx"
+
+[build]
+target = "thumbv6m-none-eabi"
diff --git a/examples/boot/stm32l0/Cargo.toml b/examples/boot/stm32l0/Cargo.toml
new file mode 100644
index 00000000..2e093d77
--- /dev/null
+++ b/examples/boot/stm32l0/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+authors = ["Ulf Lilleengen <lulf@redhat.com>"]
+edition = "2018"
+name = "embassy-boot-stm32l0-examples"
+version = "0.1.0"
+
+[dependencies]
+embassy = { version = "0.1.0", path = "../../../embassy", features = ["nightly"] }
+embassy-stm32 = { version = "0.1.0", path = "../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32l072cz", "time-driver-any", "exti", "memory-x"] }
+embassy-boot-stm32 = { version = "0.1.0", path = "../../../embassy-boot/stm32", features = ["flash-128", "invert-erase", "thumbv6"] }
+embassy-traits = { version = "0.1.0", path = "../../../embassy-traits" }
+
+defmt = { version = "0.3", optional = true }
+defmt-rtt = { version = "0.3", optional = true }
+panic-reset = { version = "0.1.1" }
+embedded-hal = { version = "0.2.6" }
+
+cortex-m = "0.7.3"
+cortex-m-rt = "0.7.0"
+
+[features]
+defmt = [
+ "dep:defmt",
+ "embassy-stm32/defmt",
+ "embassy-boot-stm32/defmt",
+]
diff --git a/examples/boot/stm32l0/README.md b/examples/boot/stm32l0/README.md
new file mode 100644
index 00000000..9c866082
--- /dev/null
+++ b/examples/boot/stm32l0/README.md
@@ -0,0 +1,29 @@
+# Examples using bootloader
+
+Example for STM32L0 demonstrating the bootloader. The example consists of application binaries, 'a'
+which allows you to press a button to start the DFU process, and 'b' which is the updated
+application.
+
+
+## Prerequisites
+
+* `cargo-binutils`
+* `cargo-flash`
+* `embassy-boot-stm32`
+
+## Usage
+
+```
+# Flash bootloader
+cargo flash --manifest-path ../../../embassy-boot/stm32/Cargo.toml --release --features embassy-stm32/stm32l072cz,flash-128,invert-erase,thumbv6 --chip STM32L072CZTx
+# Build 'b'
+cargo build --release --bin b
+# Generate binary for 'b'
+cargo objcopy --release --bin b -- -O binary b.bin
+```
+
+# Flash `a` (which includes b.bin)
+
+```
+cargo flash --release --bin a --chip STM32L072CZTx
+```
diff --git a/examples/boot/stm32l0/build.rs b/examples/boot/stm32l0/build.rs
new file mode 100644
index 00000000..e1da6932
--- /dev/null
+++ b/examples/boot/stm32l0/build.rs
@@ -0,0 +1,37 @@
+//! This build script copies the `memory.x` file from the crate root into
+//! a directory where the linker can always find it at build time.
+//! For many projects this is optional, as the linker always searches the
+//! project root directory -- wherever `Cargo.toml` is. However, if you
+//! are using a workspace or have a more complicated build setup, this
+//! build script becomes required. Additionally, by requesting that
+//! Cargo re-run the build script whenever `memory.x` is changed,
+//! updating `memory.x` ensures a rebuild of the application with the
+//! new memory settings.
+
+use std::env;
+use std::fs::File;
+use std::io::Write;
+use std::path::PathBuf;
+
+fn main() {
+ // Put `memory.x` in our output directory and ensure it's
+ // on the linker search path.
+ let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
+ File::create(out.join("memory.x"))
+ .unwrap()
+ .write_all(include_bytes!("memory.x"))
+ .unwrap();
+ println!("cargo:rustc-link-search={}", out.display());
+
+ // By default, Cargo will re-run a build script whenever
+ // any file in the project changes. By specifying `memory.x`
+ // here, we ensure the build script is only re-run when
+ // `memory.x` is changed.
+ println!("cargo:rerun-if-changed=memory.x");
+
+ println!("cargo:rustc-link-arg-bins=--nmagic");
+ println!("cargo:rustc-link-arg-bins=-Tlink.x");
+ if env::var("CARGO_FEATURE_DEFMT").is_ok() {
+ println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
+ }
+}
diff --git a/examples/boot/stm32l0/memory.x b/examples/boot/stm32l0/memory.x
new file mode 100644
index 00000000..fd5bf1a5
--- /dev/null
+++ b/examples/boot/stm32l0/memory.x
@@ -0,0 +1,15 @@
+MEMORY
+{
+ /* NOTE 1 K = 1 KiBi = 1024 bytes */
+ BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K
+ BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K
+ FLASH : ORIGIN = 0x08008000, LENGTH = 32K
+ DFU : ORIGIN = 0x08010000, LENGTH = 36K
+ RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 16K
+}
+
+__bootloader_state_start = ORIGIN(BOOTLOADER_STATE);
+__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE);
+
+__bootloader_dfu_start = ORIGIN(DFU);
+__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU);
diff --git a/examples/boot/stm32l0/src/bin/a.rs b/examples/boot/stm32l0/src/bin/a.rs
new file mode 100644
index 00000000..7b9000c9
--- /dev/null
+++ b/examples/boot/stm32l0/src/bin/a.rs
@@ -0,0 +1,48 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use embassy::time::{Duration, Timer};
+use embassy_boot_stm32::FirmwareUpdater;
+use embassy_stm32::exti::ExtiInput;
+use embassy_stm32::flash::Flash;
+use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed};
+use embassy_stm32::Peripherals;
+use embassy_traits::adapter::BlockingAsync;
+use panic_reset as _;
+
+#[cfg(feature = "defmt-rtt")]
+use defmt_rtt::*;
+
+static APP_B: &[u8] = include_bytes!("../../b.bin");
+
+#[embassy::main]
+async fn main(_s: embassy::executor::Spawner, p: Peripherals) {
+ let flash = Flash::unlock(p.FLASH);
+ let mut flash = BlockingAsync::new(flash);
+
+ let button = Input::new(p.PB2, Pull::Up);
+ let mut button = ExtiInput::new(button, p.EXTI2);
+
+ let mut led = Output::new(p.PB5, Level::Low, Speed::Low);
+
+ led.set_high();
+
+ let mut updater = FirmwareUpdater::default();
+ button.wait_for_falling_edge().await;
+ let mut offset = 0;
+ for chunk in APP_B.chunks(128) {
+ let mut buf: [u8; 128] = [0; 128];
+ buf[..chunk.len()].copy_from_slice(chunk);
+ updater
+ .write_firmware(offset, &buf, &mut flash, 128)
+ .await
+ .unwrap();
+ offset += chunk.len();
+ }
+
+ updater.mark_update(&mut flash).await.unwrap();
+ led.set_low();
+ Timer::after(Duration::from_secs(1)).await;
+ cortex_m::peripheral::SCB::sys_reset();
+}
diff --git a/examples/boot/stm32l0/src/bin/b.rs b/examples/boot/stm32l0/src/bin/b.rs
new file mode 100644
index 00000000..ed774fd7
--- /dev/null
+++ b/examples/boot/stm32l0/src/bin/b.rs
@@ -0,0 +1,25 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use embassy::executor::Spawner;
+use embassy::time::{Duration, Timer};
+use embassy_stm32::gpio::{Level, Output, Speed};
+use embassy_stm32::Peripherals;
+use panic_reset as _;
+
+#[cfg(feature = "defmt-rtt")]
+use defmt_rtt::*;
+
+#[embassy::main]
+async fn main(_spawner: Spawner, p: Peripherals) {
+ let mut led = Output::new(p.PB6, Level::High, Speed::Low);
+
+ loop {
+ led.set_high();
+ Timer::after(Duration::from_millis(500)).await;
+
+ led.set_low();
+ Timer::after(Duration::from_millis(500)).await;
+ }
+}
diff --git a/examples/boot/stm32l1/.cargo/config.toml b/examples/boot/stm32l1/.cargo/config.toml
new file mode 100644
index 00000000..04985720
--- /dev/null
+++ b/examples/boot/stm32l1/.cargo/config.toml
@@ -0,0 +1,6 @@
+[target.'cfg(all(target_arch = "arm", target_os = "none"))']
+# replace your chip as listed in `probe-run --list-chips`
+runner = "probe-run --chip STM32L151CBxxA"
+
+[build]
+target = "thumbv7m-none-eabi"
diff --git a/examples/boot/stm32l1/Cargo.toml b/examples/boot/stm32l1/Cargo.toml
new file mode 100644
index 00000000..ec396bef
--- /dev/null
+++ b/examples/boot/stm32l1/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+authors = ["Ulf Lilleengen <lulf@redhat.com>"]
+edition = "2018"
+name = "embassy-boot-stm32l1-examples"
+version = "0.1.0"
+
+[dependencies]
+embassy = { version = "0.1.0", path = "../../../embassy", features = ["nightly"] }
+embassy-stm32 = { version = "0.1.0", path = "../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32l151cb-a", "time-driver-any", "exti"] }
+embassy-boot-stm32 = { version = "0.1.0", path = "../../../embassy-boot/stm32", features = ["flash-256", "invert-erase"] }
+embassy-traits = { version = "0.1.0", path = "../../../embassy-traits" }
+
+defmt = { version = "0.3", optional = true }
+defmt-rtt = { version = "0.3", optional = true }
+panic-reset = { version = "0.1.1" }
+embedded-hal = { version = "0.2.6" }
+
+cortex-m = "0.7.3"
+cortex-m-rt = "0.7.0"
+
+[features]
+defmt = [
+ "dep:defmt",
+ "embassy-stm32/defmt",
+ "embassy-boot-stm32/defmt",
+]
diff --git a/examples/boot/stm32l1/README.md b/examples/boot/stm32l1/README.md
new file mode 100644
index 00000000..1a9e85a7
--- /dev/null
+++ b/examples/boot/stm32l1/README.md
@@ -0,0 +1,29 @@
+# Examples using bootloader
+
+Example for STM32L1 demonstrating the bootloader. The example consists of application binaries, 'a'
+which allows you to press a button to start the DFU process, and 'b' which is the updated
+application.
+
+
+## Prerequisites
+
+* `cargo-binutils`
+* `cargo-flash`
+* `embassy-boot-stm32`
+
+## Usage
+
+```
+# Flash bootloader
+cargo flash --manifest-path ../../../embassy-boot/stm32/Cargo.toml --release --features embassy-stm32/stm32l151cb-a,flash-256,invert-erase --chip STM32L151CBxxA
+# Build 'b'
+cargo build --release --bin b
+# Generate binary for 'b'
+cargo objcopy --release --bin b -- -O binary b.bin
+```
+
+# Flash `a` (which includes b.bin)
+
+```
+cargo flash --release --bin a --chip STM32L151CBxxA
+```
diff --git a/examples/boot/stm32l1/build.rs b/examples/boot/stm32l1/build.rs
new file mode 100644
index 00000000..e1da6932
--- /dev/null
+++ b/examples/boot/stm32l1/build.rs
@@ -0,0 +1,37 @@
+//! This build script copies the `memory.x` file from the crate root into
+//! a directory where the linker can always find it at build time.
+//! For many projects this is optional, as the linker always searches the
+//! project root directory -- wherever `Cargo.toml` is. However, if you
+//! are using a workspace or have a more complicated build setup, this
+//! build script becomes required. Additionally, by requesting that
+//! Cargo re-run the build script whenever `memory.x` is changed,
+//! updating `memory.x` ensures a rebuild of the application with the
+//! new memory settings.
+
+use std::env;
+use std::fs::File;
+use std::io::Write;
+use std::path::PathBuf;
+
+fn main() {
+ // Put `memory.x` in our output directory and ensure it's
+ // on the linker search path.
+ let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
+ File::create(out.join("memory.x"))
+ .unwrap()
+ .write_all(include_bytes!("memory.x"))
+ .unwrap();
+ println!("cargo:rustc-link-search={}", out.display());
+
+ // By default, Cargo will re-run a build script whenever
+ // any file in the project changes. By specifying `memory.x`
+ // here, we ensure the build script is only re-run when
+ // `memory.x` is changed.
+ println!("cargo:rerun-if-changed=memory.x");
+
+ println!("cargo:rustc-link-arg-bins=--nmagic");
+ println!("cargo:rustc-link-arg-bins=-Tlink.x");
+ if env::var("CARGO_FEATURE_DEFMT").is_ok() {
+ println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
+ }
+}
diff --git a/examples/boot/stm32l1/memory.x b/examples/boot/stm32l1/memory.x
new file mode 100644
index 00000000..fd5bf1a5
--- /dev/null
+++ b/examples/boot/stm32l1/memory.x
@@ -0,0 +1,15 @@
+MEMORY
+{
+ /* NOTE 1 K = 1 KiBi = 1024 bytes */
+ BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K
+ BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K
+ FLASH : ORIGIN = 0x08008000, LENGTH = 32K
+ DFU : ORIGIN = 0x08010000, LENGTH = 36K
+ RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 16K
+}
+
+__bootloader_state_start = ORIGIN(BOOTLOADER_STATE);
+__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE);
+
+__bootloader_dfu_start = ORIGIN(DFU);
+__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU);
diff --git a/examples/boot/stm32l1/src/bin/a.rs b/examples/boot/stm32l1/src/bin/a.rs
new file mode 100644
index 00000000..7b9000c9
--- /dev/null
+++ b/examples/boot/stm32l1/src/bin/a.rs
@@ -0,0 +1,48 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use embassy::time::{Duration, Timer};
+use embassy_boot_stm32::FirmwareUpdater;
+use embassy_stm32::exti::ExtiInput;
+use embassy_stm32::flash::Flash;
+use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed};
+use embassy_stm32::Peripherals;
+use embassy_traits::adapter::BlockingAsync;
+use panic_reset as _;
+
+#[cfg(feature = "defmt-rtt")]
+use defmt_rtt::*;
+
+static APP_B: &[u8] = include_bytes!("../../b.bin");
+
+#[embassy::main]
+async fn main(_s: embassy::executor::Spawner, p: Peripherals) {
+ let flash = Flash::unlock(p.FLASH);
+ let mut flash = BlockingAsync::new(flash);
+
+ let button = Input::new(p.PB2, Pull::Up);
+ let mut button = ExtiInput::new(button, p.EXTI2);
+
+ let mut led = Output::new(p.PB5, Level::Low, Speed::Low);
+
+ led.set_high();
+
+ let mut updater = FirmwareUpdater::default();
+ button.wait_for_falling_edge().await;
+ let mut offset = 0;
+ for chunk in APP_B.chunks(128) {
+ let mut buf: [u8; 128] = [0; 128];
+ buf[..chunk.len()].copy_from_slice(chunk);
+ updater
+ .write_firmware(offset, &buf, &mut flash, 128)
+ .await
+ .unwrap();
+ offset += chunk.len();
+ }
+
+ updater.mark_update(&mut flash).await.unwrap();
+ led.set_low();
+ Timer::after(Duration::from_secs(1)).await;
+ cortex_m::peripheral::SCB::sys_reset();
+}
diff --git a/examples/boot/stm32l1/src/bin/b.rs b/examples/boot/stm32l1/src/bin/b.rs
new file mode 100644
index 00000000..ed774fd7
--- /dev/null
+++ b/examples/boot/stm32l1/src/bin/b.rs
@@ -0,0 +1,25 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use embassy::executor::Spawner;
+use embassy::time::{Duration, Timer};
+use embassy_stm32::gpio::{Level, Output, Speed};
+use embassy_stm32::Peripherals;
+use panic_reset as _;
+
+#[cfg(feature = "defmt-rtt")]
+use defmt_rtt::*;
+
+#[embassy::main]
+async fn main(_spawner: Spawner, p: Peripherals) {
+ let mut led = Output::new(p.PB6, Level::High, Speed::Low);
+
+ loop {
+ led.set_high();
+ Timer::after(Duration::from_millis(500)).await;
+
+ led.set_low();
+ Timer::after(Duration::from_millis(500)).await;
+ }
+}
diff --git a/examples/boot/stm32l4/.cargo/config.toml b/examples/boot/stm32l4/.cargo/config.toml
new file mode 100644
index 00000000..7b6c4c0a
--- /dev/null
+++ b/examples/boot/stm32l4/.cargo/config.toml
@@ -0,0 +1,6 @@
+[target.'cfg(all(target_arch = "arm", target_os = "none"))']
+# replace your chip as listed in `probe-run --list-chips`
+runner = "probe-run --chip STM32L475VG"
+
+[build]
+target = "thumbv7em-none-eabihf"
diff --git a/examples/boot/stm32l4/Cargo.toml b/examples/boot/stm32l4/Cargo.toml
new file mode 100644
index 00000000..394f26a1
--- /dev/null
+++ b/examples/boot/stm32l4/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+authors = ["Ulf Lilleengen <lulf@redhat.com>"]
+edition = "2018"
+name = "embassy-boot-stm32l4-examples"
+version = "0.1.0"
+
+[dependencies]
+embassy = { version = "0.1.0", path = "../../../embassy", features = ["nightly"] }
+embassy-stm32 = { version = "0.1.0", path = "../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32l475vg", "time-driver-any", "exti"] }
+embassy-boot-stm32 = { version = "0.1.0", path = "../../../embassy-boot/stm32", features = ["flash-2k"] }
+embassy-traits = { version = "0.1.0", path = "../../../embassy-traits" }
+
+defmt = { version = "0.3", optional = true }
+defmt-rtt = { version = "0.3", optional = true }
+panic-reset = { version = "0.1.1" }
+embedded-hal = { version = "0.2.6" }
+
+cortex-m = "0.7.3"
+cortex-m-rt = "0.7.0"
+
+[features]
+defmt = [
+ "dep:defmt",
+ "embassy-stm32/defmt",
+ "embassy-boot-stm32/defmt",
+]
diff --git a/examples/boot/stm32l4/README.md b/examples/boot/stm32l4/README.md
new file mode 100644
index 00000000..09e09d6e
--- /dev/null
+++ b/examples/boot/stm32l4/README.md
@@ -0,0 +1,29 @@
+# Examples using bootloader
+
+Example for STM32L4 demonstrating the bootloader. The example consists of application binaries, 'a'
+which allows you to press a button to start the DFU process, and 'b' which is the updated
+application.
+
+
+## Prerequisites
+
+* `cargo-binutils`
+* `cargo-flash`
+* `embassy-boot-stm32`
+
+## Usage
+
+```
+# Flash bootloader
+cargo flash --manifest-path ../../../embassy-boot/stm32/Cargo.toml --release --features embassy-stm32/stm32l475vg,flash-2k --chip STM32L475VG
+# Build 'b'
+cargo build --release --bin b
+# Generate binary for 'b'
+cargo objcopy --release --bin b -- -O binary b.bin
+```
+
+# Flash `a` (which includes b.bin)
+
+```
+cargo flash --release --bin a --chip STM32L475VG
+```
diff --git a/examples/boot/stm32l4/build.rs b/examples/boot/stm32l4/build.rs
new file mode 100644
index 00000000..e1da6932
--- /dev/null
+++ b/examples/boot/stm32l4/build.rs
@@ -0,0 +1,37 @@
+//! This build script copies the `memory.x` file from the crate root into
+//! a directory where the linker can always find it at build time.
+//! For many projects this is optional, as the linker always searches the
+//! project root directory -- wherever `Cargo.toml` is. However, if you
+//! are using a workspace or have a more complicated build setup, this
+//! build script becomes required. Additionally, by requesting that
+//! Cargo re-run the build script whenever `memory.x` is changed,
+//! updating `memory.x` ensures a rebuild of the application with the
+//! new memory settings.
+
+use std::env;
+use std::fs::File;
+use std::io::Write;
+use std::path::PathBuf;
+
+fn main() {
+ // Put `memory.x` in our output directory and ensure it's
+ // on the linker search path.
+ let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
+ File::create(out.join("memory.x"))
+ .unwrap()
+ .write_all(include_bytes!("memory.x"))
+ .unwrap();
+ println!("cargo:rustc-link-search={}", out.display());
+
+ // By default, Cargo will re-run a build script whenever
+ // any file in the project changes. By specifying `memory.x`
+ // here, we ensure the build script is only re-run when
+ // `memory.x` is changed.
+ println!("cargo:rerun-if-changed=memory.x");
+
+ println!("cargo:rustc-link-arg-bins=--nmagic");
+ println!("cargo:rustc-link-arg-bins=-Tlink.x");
+ if env::var("CARGO_FEATURE_DEFMT").is_ok() {
+ println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
+ }
+}
diff --git a/examples/boot/stm32l4/memory.x b/examples/boot/stm32l4/memory.x
new file mode 100644
index 00000000..fd5bf1a5
--- /dev/null
+++ b/examples/boot/stm32l4/memory.x
@@ -0,0 +1,15 @@
+MEMORY
+{
+ /* NOTE 1 K = 1 KiBi = 1024 bytes */
+ BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K
+ BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K
+ FLASH : ORIGIN = 0x08008000, LENGTH = 32K
+ DFU : ORIGIN = 0x08010000, LENGTH = 36K
+ RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 16K
+}
+
+__bootloader_state_start = ORIGIN(BOOTLOADER_STATE);
+__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE);
+
+__bootloader_dfu_start = ORIGIN(DFU);
+__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU);
diff --git a/examples/boot/stm32l4/src/bin/a.rs b/examples/boot/stm32l4/src/bin/a.rs
new file mode 100644
index 00000000..a5a9e230
--- /dev/null
+++ b/examples/boot/stm32l4/src/bin/a.rs
@@ -0,0 +1,44 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use embassy_boot_stm32::FirmwareUpdater;
+use embassy_stm32::exti::ExtiInput;
+use embassy_stm32::flash::Flash;
+use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed};
+use embassy_stm32::Peripherals;
+use embassy_traits::adapter::BlockingAsync;
+use panic_reset as _;
+
+#[cfg(feature = "defmt-rtt")]
+use defmt_rtt::*;
+
+static APP_B: &[u8] = include_bytes!("../../b.bin");
+
+#[embassy::main]
+async fn main(_s: embassy::executor::Spawner, p: Peripherals) {
+ let flash = Flash::unlock(p.FLASH);
+ let mut flash = BlockingAsync::new(flash);
+
+ let button = Input::new(p.PC13, Pull::Up);
+ let mut button = ExtiInput::new(button, p.EXTI13);
+
+ let mut led = Output::new(p.PB14, Level::Low, Speed::Low);
+ led.set_high();
+
+ let mut updater = FirmwareUpdater::default();
+ button.wait_for_falling_edge().await;
+ let mut offset = 0;
+ for chunk in APP_B.chunks(2048) {
+ let mut buf: [u8; 2048] = [0; 2048];
+ buf[..chunk.len()].copy_from_slice(chunk);
+ updater
+ .write_firmware(offset, &buf, &mut flash, 2048)
+ .await
+ .unwrap();
+ offset += chunk.len();
+ }
+ updater.mark_update(&mut flash).await.unwrap();
+ led.set_low();
+ cortex_m::peripheral::SCB::sys_reset();
+}
diff --git a/examples/boot/stm32l4/src/bin/b.rs b/examples/boot/stm32l4/src/bin/b.rs
new file mode 100644
index 00000000..81427598
--- /dev/null
+++ b/examples/boot/stm32l4/src/bin/b.rs
@@ -0,0 +1,25 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use embassy::executor::Spawner;
+use embassy::time::{Duration, Timer};
+use embassy_stm32::gpio::{Level, Output, Speed};
+use embassy_stm32::Peripherals;
+use panic_reset as _;
+
+#[cfg(feature = "defmt-rtt")]
+use defmt_rtt::*;
+
+#[embassy::main]
+async fn main(_spawner: Spawner, p: Peripherals) {
+ let mut led = Output::new(p.PA5, Level::High, Speed::Low);
+
+ loop {
+ led.set_high();
+ Timer::after(Duration::from_millis(500)).await;
+
+ led.set_low();
+ Timer::after(Duration::from_millis(500)).await;
+ }
+}
diff --git a/examples/boot/stm32wl/.cargo/config.toml b/examples/boot/stm32wl/.cargo/config.toml
new file mode 100644
index 00000000..60076e06
--- /dev/null
+++ b/examples/boot/stm32wl/.cargo/config.toml
@@ -0,0 +1,6 @@
+[target.'cfg(all(target_arch = "arm", target_os = "none"))']
+# replace your chip as listed in `probe-run --list-chips`
+runner = "probe-run --chip STM32WLE5JCIx"
+
+[build]
+target = "thumbv7em-none-eabihf"
diff --git a/examples/boot/stm32wl/Cargo.toml b/examples/boot/stm32wl/Cargo.toml
new file mode 100644
index 00000000..9c69f4a6
--- /dev/null
+++ b/examples/boot/stm32wl/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+authors = ["Ulf Lilleengen <lulf@redhat.com>"]
+edition = "2018"
+name = "embassy-boot-stm32wl-examples"
+version = "0.1.0"
+
+[dependencies]
+embassy = { version = "0.1.0", path = "../../../embassy", features = ["nightly"] }
+embassy-stm32 = { version = "0.1.0", path = "../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32wl55jc-cm4", "time-driver-any", "exti"] }
+embassy-boot-stm32 = { version = "0.1.0", path = "../../../embassy-boot/stm32", features = ["flash-2k"] }
+embassy-traits = { version = "0.1.0", path = "../../../embassy-traits" }
+
+defmt = { version = "0.3", optional = true }
+defmt-rtt = { version = "0.3", optional = true }
+panic-reset = { version = "0.1.1" }
+embedded-hal = { version = "0.2.6" }
+
+cortex-m = "0.7.3"
+cortex-m-rt = "0.7.0"
+
+[features]
+defmt = [
+ "dep:defmt",
+ "embassy-stm32/defmt",
+ "embassy-boot-stm32/defmt",
+]
diff --git a/examples/boot/stm32wl/README.md b/examples/boot/stm32wl/README.md
new file mode 100644
index 00000000..a26a2385
--- /dev/null
+++ b/examples/boot/stm32wl/README.md
@@ -0,0 +1,29 @@
+# Examples using bootloader
+
+Example for STM32WL demonstrating the bootloader. The example consists of application binaries, 'a'
+which allows you to press a button to start the DFU process, and 'b' which is the updated
+application.
+
+
+## Prerequisites
+
+* `cargo-binutils`
+* `cargo-flash`
+* `embassy-boot-stm32`
+
+## Usage
+
+```
+# Flash bootloader
+cargo flash --manifest-path ../../../embassy-boot/stm32/Cargo.toml --release --features embassy-stm32/stm32wl55jc-cm4,flash-2k --chip STM32WLE5JCIx
+# Build 'b'
+cargo build --release --bin b
+# Generate binary for 'b'
+cargo objcopy --release --bin b -- -O binary b.bin
+```
+
+# Flash `a` (which includes b.bin)
+
+```
+cargo flash --release --bin a --chip STM32WLE5JCIx
+```
diff --git a/examples/boot/stm32wl/build.rs b/examples/boot/stm32wl/build.rs
new file mode 100644
index 00000000..e1da6932
--- /dev/null
+++ b/examples/boot/stm32wl/build.rs
@@ -0,0 +1,37 @@
+//! This build script copies the `memory.x` file from the crate root into
+//! a directory where the linker can always find it at build time.
+//! For many projects this is optional, as the linker always searches the
+//! project root directory -- wherever `Cargo.toml` is. However, if you
+//! are using a workspace or have a more complicated build setup, this
+//! build script becomes required. Additionally, by requesting that
+//! Cargo re-run the build script whenever `memory.x` is changed,
+//! updating `memory.x` ensures a rebuild of the application with the
+//! new memory settings.
+
+use std::env;
+use std::fs::File;
+use std::io::Write;
+use std::path::PathBuf;
+
+fn main() {
+ // Put `memory.x` in our output directory and ensure it's
+ // on the linker search path.
+ let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
+ File::create(out.join("memory.x"))
+ .unwrap()
+ .write_all(include_bytes!("memory.x"))
+ .unwrap();
+ println!("cargo:rustc-link-search={}", out.display());
+
+ // By default, Cargo will re-run a build script whenever
+ // any file in the project changes. By specifying `memory.x`
+ // here, we ensure the build script is only re-run when
+ // `memory.x` is changed.
+ println!("cargo:rerun-if-changed=memory.x");
+
+ println!("cargo:rustc-link-arg-bins=--nmagic");
+ println!("cargo:rustc-link-arg-bins=-Tlink.x");
+ if env::var("CARGO_FEATURE_DEFMT").is_ok() {
+ println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
+ }
+}
diff --git a/examples/boot/stm32wl/memory.x b/examples/boot/stm32wl/memory.x
new file mode 100644
index 00000000..78dd69c3
--- /dev/null
+++ b/examples/boot/stm32wl/memory.x
@@ -0,0 +1,15 @@
+MEMORY
+{
+ /* NOTE 1 K = 1 KiBi = 1024 bytes */
+ BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K
+ BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K
+ FLASH : ORIGIN = 0x08008000, LENGTH = 32K
+ DFU : ORIGIN = 0x08010000, LENGTH = 36K
+ RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 32K
+}
+
+__bootloader_state_start = ORIGIN(BOOTLOADER_STATE);
+__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE);
+
+__bootloader_dfu_start = ORIGIN(DFU);
+__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU);
diff --git a/examples/boot/stm32wl/src/bin/a.rs b/examples/boot/stm32wl/src/bin/a.rs
new file mode 100644
index 00000000..b1f4a4a0
--- /dev/null
+++ b/examples/boot/stm32wl/src/bin/a.rs
@@ -0,0 +1,45 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use embassy_boot_stm32::FirmwareUpdater;
+use embassy_stm32::exti::ExtiInput;
+use embassy_stm32::flash::Flash;
+use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed};
+use embassy_stm32::Peripherals;
+use embassy_traits::adapter::BlockingAsync;
+use panic_reset as _;
+
+#[cfg(feature = "defmt-rtt")]
+use defmt_rtt::*;
+
+static APP_B: &[u8] = include_bytes!("../../b.bin");
+
+#[embassy::main]
+async fn main(_s: embassy::executor::Spawner, p: Peripherals) {
+ let flash = Flash::new(p.FLASH);
+ let mut flash = BlockingAsync::new(flash);
+
+ let button = Input::new(p.PA0, Pull::Up);
+ let mut button = ExtiInput::new(button, p.EXTI0);
+
+ let mut led = Output::new(p.PB9, Level::Low, Speed::Low);
+
+ let mut updater = FirmwareUpdater::default();
+ button.wait_for_falling_edge().await;
+ let mut offset = 0;
+ for chunk in APP_B.chunks(2048) {
+ let mut buf: [u8; 2048] = [0; 2048];
+ buf[..chunk.len()].copy_from_slice(chunk);
+ // defmt::info!("Writing chunk at 0x{:x}", offset);
+ updater
+ .write_firmware(offset, &buf, &mut flash, 2048)
+ .await
+ .unwrap();
+ offset += chunk.len();
+ }
+ updater.mark_update(&mut flash).await.unwrap();
+ // defmt::info!("Marked as updated");
+ led.set_high();
+ cortex_m::peripheral::SCB::sys_reset();
+}
diff --git a/examples/boot/stm32wl/src/bin/b.rs b/examples/boot/stm32wl/src/bin/b.rs
new file mode 100644
index 00000000..ffe15b66
--- /dev/null
+++ b/examples/boot/stm32wl/src/bin/b.rs
@@ -0,0 +1,25 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use embassy::executor::Spawner;
+use embassy::time::{Duration, Timer};
+use embassy_stm32::gpio::{Level, Output, Speed};
+use embassy_stm32::Peripherals;
+use panic_reset as _;
+
+#[cfg(feature = "defmt-rtt")]
+use defmt_rtt::*;
+
+#[embassy::main]
+async fn main(_spawner: Spawner, p: Peripherals) {
+ let mut led = Output::new(p.PB15, Level::High, Speed::Low);
+
+ loop {
+ led.set_high();
+ Timer::after(Duration::from_millis(500)).await;
+
+ led.set_low();
+ Timer::after(Duration::from_millis(500)).await;
+ }
+}
diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml
index 0b21f674..1cdb2f37 100644
--- a/examples/stm32l0/Cargo.toml
+++ b/examples/stm32l0/Cargo.toml
@@ -21,6 +21,8 @@ lorawan = { version = "0.7.1", default-features = false, features = ["default-cr
defmt = "0.3"
defmt-rtt = "0.3"
+embedded-storage = "0.3.0"
+
cortex-m = "0.7.3"
cortex-m-rt = "0.7.0"
panic-probe = { version = "0.3", features = ["print-defmt"] }
diff --git a/examples/stm32l0/src/bin/flash.rs b/examples/stm32l0/src/bin/flash.rs
new file mode 100644
index 00000000..0ab7b133
--- /dev/null
+++ b/examples/stm32l0/src/bin/flash.rs
@@ -0,0 +1,43 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::{info, unwrap};
+use embassy::executor::Spawner;
+use embassy_stm32::flash::Flash;
+use embassy_stm32::Peripherals;
+use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
+
+use defmt_rtt as _; // global logger
+use panic_probe as _;
+
+#[embassy::main]
+async fn main(_spawner: Spawner, p: Peripherals) {
+ info!("Hello Flash!");
+
+ const ADDR: u32 = 0x8026000;
+
+ let mut f = Flash::unlock(p.FLASH);
+
+ info!("Reading...");
+ let mut buf = [0u8; 8];
+ unwrap!(f.read(ADDR, &mut buf));
+ info!("Read: {=[u8]:x}", buf);
+
+ info!("Erasing...");
+ unwrap!(f.erase(ADDR, ADDR + 128));
+
+ info!("Reading...");
+ let mut buf = [0u8; 8];
+ unwrap!(f.read(ADDR, &mut buf));
+ info!("Read after erase: {=[u8]:x}", buf);
+
+ info!("Writing...");
+ unwrap!(f.write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8]));
+
+ info!("Reading...");
+ let mut buf = [0u8; 8];
+ unwrap!(f.read(ADDR, &mut buf));
+ info!("Read: {=[u8]:x}", buf);
+ assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]);
+}
diff --git a/examples/stm32l1/Cargo.toml b/examples/stm32l1/Cargo.toml
index a7a6c228..ce6b0772 100644
--- a/examples/stm32l1/Cargo.toml
+++ b/examples/stm32l1/Cargo.toml
@@ -18,3 +18,4 @@ embedded-hal = "0.2.6"
panic-probe = { version = "0.3", features = ["print-defmt"] }
futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
heapless = { version = "0.7.5", default-features = false }
+embedded-storage = "0.3.0"
diff --git a/examples/stm32l1/src/bin/flash.rs b/examples/stm32l1/src/bin/flash.rs
new file mode 100644
index 00000000..b234289a
--- /dev/null
+++ b/examples/stm32l1/src/bin/flash.rs
@@ -0,0 +1,43 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::{info, unwrap};
+use embassy::executor::Spawner;
+use embassy_stm32::flash::Flash;
+use embassy_stm32::Peripherals;
+use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
+
+use defmt_rtt as _; // global logger
+use panic_probe as _;
+
+#[embassy::main]
+async fn main(_spawner: Spawner, p: Peripherals) {
+ info!("Hello Flash!");
+
+ const ADDR: u32 = 0x8026000;
+
+ let mut f = Flash::unlock(p.FLASH);
+
+ info!("Reading...");
+ let mut buf = [0u8; 8];
+ unwrap!(f.read(ADDR, &mut buf));
+ info!("Read: {=[u8]:x}", buf);
+
+ info!("Erasing...");
+ unwrap!(f.erase(ADDR, ADDR + 256));
+
+ info!("Reading...");
+ let mut buf = [0u8; 8];
+ unwrap!(f.read(ADDR, &mut buf));
+ info!("Read after erase: {=[u8]:x}", buf);
+
+ info!("Writing...");
+ unwrap!(f.write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8]));
+
+ info!("Reading...");
+ let mut buf = [0u8; 8];
+ unwrap!(f.read(ADDR, &mut buf));
+ info!("Read: {=[u8]:x}", buf);
+ assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]);
+}
diff --git a/examples/stm32wl/Cargo.toml b/examples/stm32wl/Cargo.toml
index 919104b0..01f31703 100644
--- a/examples/stm32wl/Cargo.toml
+++ b/examples/stm32wl/Cargo.toml
@@ -8,7 +8,7 @@ resolver = "2"
[dependencies]
embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] }
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "subghz", "unstable-pac", "exti"] }
-embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time"] }
+embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time", "defmt"] }
lorawan-device = { version = "0.7.1", default-features = false, features = ["async"] }
lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"] }
@@ -19,6 +19,7 @@ defmt-rtt = "0.3"
cortex-m = "0.7.3"
cortex-m-rt = "0.7.0"
embedded-hal = "0.2.6"
+embedded-storage = "0.3.0"
panic-probe = { version = "0.3", features = ["print-defmt"] }
futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
heapless = { version = "0.7.5", default-features = false }
diff --git a/examples/stm32wl/src/bin/flash.rs b/examples/stm32wl/src/bin/flash.rs
new file mode 100644
index 00000000..9e13c702
--- /dev/null
+++ b/examples/stm32wl/src/bin/flash.rs
@@ -0,0 +1,43 @@
+#![no_std]
+#![no_main]
+#![feature(type_alias_impl_trait)]
+
+use defmt::{info, unwrap};
+use embassy::executor::Spawner;
+use embassy_stm32::flash::Flash;
+use embassy_stm32::Peripherals;
+use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
+
+use defmt_rtt as _; // global logger
+use panic_probe as _;
+
+#[embassy::main]
+async fn main(_spawner: Spawner, p: Peripherals) {
+ info!("Hello Flash!");
+
+ const ADDR: u32 = 0x8036000;
+
+ let mut f = Flash::unlock(p.FLASH);
+
+ info!("Reading...");
+ let mut buf = [0u8; 8];
+ unwrap!(f.read(ADDR, &mut buf));
+ info!("Read: {=[u8]:x}", buf);
+
+ info!("Erasing...");
+ unwrap!(f.erase(ADDR, ADDR + 2048));
+
+ info!("Reading...");
+ let mut buf = [0u8; 8];
+ unwrap!(f.read(ADDR, &mut buf));
+ info!("Read: {=[u8]:x}", buf);
+
+ info!("Writing...");
+ unwrap!(f.write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8]));
+
+ info!("Reading...");
+ let mut buf = [0u8; 8];
+ unwrap!(f.read(ADDR, &mut buf));
+ info!("Read: {=[u8]:x}", buf);
+ assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]);
+}
diff --git a/stm32-data b/stm32-data
-Subproject 472ee98e8fdb11312392e47b16568c9d02fe654
+Subproject 419701c835dd0da3c37d8de02c95115f500dfa6