diff options
59 files changed, 2114 insertions, 136 deletions
diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 3aa2eb6bc..a45da1958 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | * xref:hal.adoc[Hardware Abstraction Layer] | 3 | * xref:hal.adoc[Hardware Abstraction Layer] |
| 4 | ** xref:nrf.adoc[nRF] | 4 | ** xref:nrf.adoc[nRF] |
| 5 | ** xref:stm32.adoc[STM32] | 5 | ** xref:stm32.adoc[STM32] |
| 6 | * xref:bootloader.adoc[Bootloader] | ||
| 6 | * xref:getting_started.adoc[Getting started] | 7 | * xref:getting_started.adoc[Getting started] |
| 7 | ** xref:basic_application.adoc[Basic application] | 8 | ** xref:basic_application.adoc[Basic application] |
| 8 | ** xref:layer_by_layer.adoc[Layer by Layer] | 9 | ** 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 000000000..1a984d6dc --- /dev/null +++ b/docs/modules/ROOT/pages/bootloader.adoc | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | = Bootloader | ||
| 2 | |||
| 3 | `embassy-boot` a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks. | ||
| 4 | |||
| 5 | The bootloader can be used either as a library or be flashed directly if you are happy with the default configuration and capabilities. | ||
| 6 | |||
| 7 | 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. | ||
| 8 | |||
| 9 | The bootloader supports both internal and external flash by relying on the `embedded-storage` traits. | ||
| 10 | |||
| 11 | |||
| 12 | == Hardware support | ||
| 13 | |||
| 14 | The bootloader supports | ||
| 15 | |||
| 16 | * nRF52 with and without softdevice | ||
| 17 | * STM32 L4, WB, WL, L1 and L0 | ||
| 18 | |||
| 19 | 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. | ||
| 20 | |||
| 21 | == Design | ||
| 22 | |||
| 23 | The bootloader divides the storage into 4 main partitions, configured by a linker script: | ||
| 24 | |||
| 25 | * BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash. | ||
| 26 | * 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. | ||
| 27 | * DFU - Where the application-to-be-swapped is placed. This partition is written to by the application. | ||
| 28 | * 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. | ||
| 29 | |||
| 30 | The partitions for ACTIVE (+BOOTLOADER), DFU and BOOTLOADER_STATE may be placed in separate flash, but they have to support compatible page sizes. | ||
| 31 | |||
| 32 | 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 0a3006ffc..04deab30b 100644 --- a/embassy-boot/boot/Cargo.toml +++ b/embassy-boot/boot/Cargo.toml | |||
| @@ -21,3 +21,8 @@ log = "0.4" | |||
| 21 | env_logger = "0.9" | 21 | env_logger = "0.9" |
| 22 | rand = "0.8" | 22 | rand = "0.8" |
| 23 | futures = { version = "0.3", features = ["executor"] } | 23 | futures = { version = "0.3", features = ["executor"] } |
| 24 | |||
| 25 | [features] | ||
| 26 | write-4 = [] | ||
| 27 | write-8 = [] | ||
| 28 | invert-erase = [] | ||
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 0d33ad1a6..080ea2426 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs | |||
| @@ -17,8 +17,23 @@ mod fmt; | |||
| 17 | use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; | 17 | use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; |
| 18 | use embedded_storage_async::nor_flash::AsyncNorFlash; | 18 | use embedded_storage_async::nor_flash::AsyncNorFlash; |
| 19 | 19 | ||
| 20 | pub const BOOT_MAGIC: u32 = 0xD00DF00D; | 20 | #[cfg(not(any(feature = "write-4", feature = "write-8",)))] |
| 21 | pub const SWAP_MAGIC: u32 = 0xF00FDAAD; | 21 | compile_error!("No write size/alignment specified. Must specify exactly one of the following features: write-4, write-8"); |
| 22 | |||
| 23 | const BOOT_MAGIC: u8 = 0xD0; | ||
| 24 | const SWAP_MAGIC: u8 = 0xF0; | ||
| 25 | |||
| 26 | #[cfg(feature = "write-4")] | ||
| 27 | const WRITE_SIZE: usize = 4; | ||
| 28 | |||
| 29 | #[cfg(feature = "write-8")] | ||
| 30 | const WRITE_SIZE: usize = 8; | ||
| 31 | |||
| 32 | #[cfg(feature = "invert-erase")] | ||
| 33 | const ERASE_VALUE: u8 = 0x00; | ||
| 34 | |||
| 35 | #[cfg(not(feature = "invert-erase"))] | ||
| 36 | const ERASE_VALUE: u8 = 0xFF; | ||
| 22 | 37 | ||
| 23 | #[derive(Copy, Clone, Debug)] | 38 | #[derive(Copy, Clone, Debug)] |
| 24 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 39 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| @@ -80,12 +95,12 @@ pub trait FlashProvider { | |||
| 80 | } | 95 | } |
| 81 | 96 | ||
| 82 | /// BootLoader works with any flash implementing embedded_storage and can also work with | 97 | /// BootLoader works with any flash implementing embedded_storage and can also work with |
| 83 | /// different page sizes. | 98 | /// different page sizes and flash write sizes. |
| 84 | pub struct BootLoader<const PAGE_SIZE: usize> { | 99 | pub struct BootLoader<const PAGE_SIZE: usize> { |
| 85 | // Page with current state of bootloader. The state partition has the following format: | 100 | // Page with current state of bootloader. The state partition has the following format: |
| 86 | // | Range | Description | | 101 | // | Range | Description | |
| 87 | // | 0 - 4 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | | 102 | // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | |
| 88 | // | 4 - N | Progress index used while swapping or reverting | | 103 | // | WRITE_SIZE - N | Progress index used while swapping or reverting | |
| 89 | state: Partition, | 104 | state: Partition, |
| 90 | // Location of the partition which will be booted from | 105 | // Location of the partition which will be booted from |
| 91 | active: Partition, | 106 | active: Partition, |
| @@ -100,7 +115,7 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> { | |||
| 100 | // DFU partition must have an extra page | 115 | // DFU partition must have an extra page |
| 101 | assert!(dfu.len() - active.len() >= PAGE_SIZE); | 116 | assert!(dfu.len() - active.len() >= PAGE_SIZE); |
| 102 | // Ensure we have enough progress pages to store copy progress | 117 | // Ensure we have enough progress pages to store copy progress |
| 103 | assert!(active.len() / PAGE_SIZE >= (state.len() - 4) / PAGE_SIZE); | 118 | assert!(active.len() / PAGE_SIZE >= (state.len() - WRITE_SIZE) / PAGE_SIZE); |
| 104 | Self { active, dfu, state } | 119 | Self { active, dfu, state } |
| 105 | } | 120 | } |
| 106 | 121 | ||
| @@ -203,15 +218,18 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> { | |||
| 203 | if !self.is_swapped(p.state())? { | 218 | if !self.is_swapped(p.state())? { |
| 204 | trace!("Swapping"); | 219 | trace!("Swapping"); |
| 205 | self.swap(p)?; | 220 | self.swap(p)?; |
| 221 | trace!("Swapping done"); | ||
| 206 | } else { | 222 | } else { |
| 207 | trace!("Reverting"); | 223 | trace!("Reverting"); |
| 208 | self.revert(p)?; | 224 | self.revert(p)?; |
| 209 | 225 | ||
| 210 | // Overwrite magic and reset progress | 226 | // Overwrite magic and reset progress |
| 211 | let fstate = p.state().flash(); | 227 | let fstate = p.state().flash(); |
| 212 | fstate.write(self.state.from as u32, &[0, 0, 0, 0])?; | 228 | let aligned = Aligned([!ERASE_VALUE; WRITE_SIZE]); |
| 229 | fstate.write(self.state.from as u32, &aligned.0)?; | ||
| 213 | fstate.erase(self.state.from as u32, self.state.to as u32)?; | 230 | fstate.erase(self.state.from as u32, self.state.to as u32)?; |
| 214 | fstate.write(self.state.from as u32, &BOOT_MAGIC.to_le_bytes())?; | 231 | let aligned = Aligned([BOOT_MAGIC; WRITE_SIZE]); |
| 232 | fstate.write(self.state.from as u32, &aligned.0)?; | ||
| 215 | } | 233 | } |
| 216 | } | 234 | } |
| 217 | _ => {} | 235 | _ => {} |
| @@ -227,12 +245,15 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> { | |||
| 227 | } | 245 | } |
| 228 | 246 | ||
| 229 | fn current_progress<P: FlashConfig>(&mut self, p: &mut P) -> Result<usize, BootError> { | 247 | fn current_progress<P: FlashConfig>(&mut self, p: &mut P) -> Result<usize, BootError> { |
| 230 | let max_index = ((self.state.len() - 4) / 4) - 1; | 248 | let max_index = ((self.state.len() - WRITE_SIZE) / WRITE_SIZE) - 1; |
| 231 | let flash = p.flash(); | 249 | let flash = p.flash(); |
| 250 | let mut aligned = Aligned([!ERASE_VALUE; WRITE_SIZE]); | ||
| 232 | for i in 0..max_index { | 251 | for i in 0..max_index { |
| 233 | let mut buf: [u8; 4] = [0; 4]; | 252 | flash.read( |
| 234 | flash.read((self.state.from + 4 + i * 4) as u32, &mut buf)?; | 253 | (self.state.from + WRITE_SIZE + i * WRITE_SIZE) as u32, |
| 235 | if buf == [0xFF, 0xFF, 0xFF, 0xFF] { | 254 | &mut aligned.0, |
| 255 | )?; | ||
| 256 | if aligned.0 == [ERASE_VALUE; WRITE_SIZE] { | ||
| 236 | return Ok(i); | 257 | return Ok(i); |
| 237 | } | 258 | } |
| 238 | } | 259 | } |
| @@ -241,8 +262,9 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> { | |||
| 241 | 262 | ||
| 242 | fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P) -> Result<(), BootError> { | 263 | fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P) -> Result<(), BootError> { |
| 243 | let flash = p.flash(); | 264 | let flash = p.flash(); |
| 244 | let w = self.state.from + 4 + idx * 4; | 265 | let w = self.state.from + WRITE_SIZE + idx * WRITE_SIZE; |
| 245 | flash.write(w as u32, &[0, 0, 0, 0])?; | 266 | let aligned = Aligned([!ERASE_VALUE; WRITE_SIZE]); |
| 267 | flash.write(w as u32, &aligned.0)?; | ||
| 246 | Ok(()) | 268 | Ok(()) |
| 247 | } | 269 | } |
| 248 | 270 | ||
| @@ -314,21 +336,24 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> { | |||
| 314 | 336 | ||
| 315 | fn swap<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError> { | 337 | fn swap<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError> { |
| 316 | let page_count = self.active.len() / PAGE_SIZE; | 338 | let page_count = self.active.len() / PAGE_SIZE; |
| 317 | // trace!("Page count: {}", page_count); | 339 | trace!("Page count: {}", page_count); |
| 318 | for page in 0..page_count { | 340 | for page in 0..page_count { |
| 341 | trace!("COPY PAGE {}", page); | ||
| 319 | // Copy active page to the 'next' DFU page. | 342 | // Copy active page to the 'next' DFU page. |
| 320 | let active_page = self.active_addr(page_count - 1 - page); | 343 | let active_page = self.active_addr(page_count - 1 - page); |
| 321 | let dfu_page = self.dfu_addr(page_count - page); | 344 | let dfu_page = self.dfu_addr(page_count - page); |
| 322 | info!("Copy active {} to dfu {}", active_page, dfu_page); | 345 | //trace!("Copy active {} to dfu {}", active_page, dfu_page); |
| 323 | self.copy_page_once_to_dfu(page * 2, active_page, dfu_page, p)?; | 346 | self.copy_page_once_to_dfu(page * 2, active_page, dfu_page, p)?; |
| 324 | 347 | ||
| 325 | // Copy DFU page to the active page | 348 | // Copy DFU page to the active page |
| 326 | let active_page = self.active_addr(page_count - 1 - page); | 349 | let active_page = self.active_addr(page_count - 1 - page); |
| 327 | let dfu_page = self.dfu_addr(page_count - 1 - page); | 350 | let dfu_page = self.dfu_addr(page_count - 1 - page); |
| 328 | info!("Copy dfy {} to active {}", dfu_page, active_page); | 351 | //trace!("Copy dfy {} to active {}", dfu_page, active_page); |
| 329 | self.copy_page_once_to_active(page * 2 + 1, dfu_page, active_page, p)?; | 352 | self.copy_page_once_to_active(page * 2 + 1, dfu_page, active_page, p)?; |
| 330 | } | 353 | } |
| 331 | 354 | ||
| 355 | info!("DONE COPYING"); | ||
| 356 | |||
| 332 | Ok(()) | 357 | Ok(()) |
| 333 | } | 358 | } |
| 334 | 359 | ||
| @@ -350,13 +375,15 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> { | |||
| 350 | } | 375 | } |
| 351 | 376 | ||
| 352 | fn read_state<P: FlashConfig>(&mut self, p: &mut P) -> Result<State, BootError> { | 377 | fn read_state<P: FlashConfig>(&mut self, p: &mut P) -> Result<State, BootError> { |
| 353 | let mut magic: [u8; 4] = [0; 4]; | 378 | let mut magic: [u8; WRITE_SIZE] = [0; WRITE_SIZE]; |
| 354 | let flash = p.flash(); | 379 | let flash = p.flash(); |
| 355 | flash.read(self.state.from as u32, &mut magic)?; | 380 | flash.read(self.state.from as u32, &mut magic)?; |
| 356 | 381 | ||
| 357 | match u32::from_le_bytes(magic) { | 382 | info!("Read magic: {:x}", magic); |
| 358 | SWAP_MAGIC => Ok(State::Swap), | 383 | if magic == [SWAP_MAGIC; WRITE_SIZE] { |
| 359 | _ => Ok(State::Boot), | 384 | Ok(State::Swap) |
| 385 | } else { | ||
| 386 | Ok(State::Boot) | ||
| 360 | } | 387 | } |
| 361 | } | 388 | } |
| 362 | } | 389 | } |
| @@ -424,6 +451,39 @@ pub struct FirmwareUpdater { | |||
| 424 | dfu: Partition, | 451 | dfu: Partition, |
| 425 | } | 452 | } |
| 426 | 453 | ||
| 454 | #[cfg(feature = "write-4")] | ||
| 455 | #[repr(align(4))] | ||
| 456 | pub struct Aligned([u8; 4]); | ||
| 457 | |||
| 458 | #[cfg(feature = "write-8")] | ||
| 459 | #[repr(align(8))] | ||
| 460 | pub struct Aligned([u8; 8]); | ||
| 461 | |||
| 462 | impl Default for FirmwareUpdater { | ||
| 463 | fn default() -> Self { | ||
| 464 | extern "C" { | ||
| 465 | static __bootloader_state_start: u32; | ||
| 466 | static __bootloader_state_end: u32; | ||
| 467 | static __bootloader_dfu_start: u32; | ||
| 468 | static __bootloader_dfu_end: u32; | ||
| 469 | } | ||
| 470 | |||
| 471 | let dfu = unsafe { | ||
| 472 | Partition::new( | ||
| 473 | &__bootloader_dfu_start as *const u32 as usize, | ||
| 474 | &__bootloader_dfu_end as *const u32 as usize, | ||
| 475 | ) | ||
| 476 | }; | ||
| 477 | let state = unsafe { | ||
| 478 | Partition::new( | ||
| 479 | &__bootloader_state_start as *const u32 as usize, | ||
| 480 | &__bootloader_state_end as *const u32 as usize, | ||
| 481 | ) | ||
| 482 | }; | ||
| 483 | FirmwareUpdater::new(dfu, state) | ||
| 484 | } | ||
| 485 | } | ||
| 486 | |||
| 427 | impl FirmwareUpdater { | 487 | impl FirmwareUpdater { |
| 428 | pub const fn new(dfu: Partition, state: Partition) -> Self { | 488 | pub const fn new(dfu: Partition, state: Partition) -> Self { |
| 429 | Self { dfu, state } | 489 | Self { dfu, state } |
| @@ -435,53 +495,45 @@ impl FirmwareUpdater { | |||
| 435 | } | 495 | } |
| 436 | 496 | ||
| 437 | /// Instruct bootloader that DFU should commence at next boot. | 497 | /// Instruct bootloader that DFU should commence at next boot. |
| 498 | /// Must be provided with an aligned buffer to use for reading and writing magic; | ||
| 438 | pub async fn mark_update<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> { | 499 | pub async fn mark_update<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> { |
| 439 | #[repr(align(4))] | 500 | let mut aligned = Aligned([0; WRITE_SIZE]); |
| 440 | struct Aligned([u8; 4]); | 501 | self.set_magic(&mut aligned.0, SWAP_MAGIC, flash).await |
| 441 | |||
| 442 | let mut magic = Aligned([0; 4]); | ||
| 443 | flash.read(self.state.from as u32, &mut magic.0).await?; | ||
| 444 | let magic = u32::from_le_bytes(magic.0); | ||
| 445 | |||
| 446 | if magic != SWAP_MAGIC { | ||
| 447 | flash | ||
| 448 | .write(self.state.from as u32, &Aligned([0; 4]).0) | ||
| 449 | .await?; | ||
| 450 | flash | ||
| 451 | .erase(self.state.from as u32, self.state.to as u32) | ||
| 452 | .await?; | ||
| 453 | trace!( | ||
| 454 | "Setting swap magic at {} to 0x{:x}, LE: 0x{:x}", | ||
| 455 | self.state.from, | ||
| 456 | &SWAP_MAGIC, | ||
| 457 | &SWAP_MAGIC.to_le_bytes() | ||
| 458 | ); | ||
| 459 | flash | ||
| 460 | .write(self.state.from as u32, &SWAP_MAGIC.to_le_bytes()) | ||
| 461 | .await?; | ||
| 462 | } | ||
| 463 | Ok(()) | ||
| 464 | } | 502 | } |
| 465 | 503 | ||
| 466 | /// Mark firmware boot successfully | 504 | /// Mark firmware boot successfully |
| 467 | pub async fn mark_booted<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> { | 505 | pub async fn mark_booted<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> { |
| 468 | #[repr(align(4))] | 506 | let mut aligned = Aligned([0; WRITE_SIZE]); |
| 469 | struct Aligned([u8; 4]); | 507 | self.set_magic(&mut aligned.0, BOOT_MAGIC, flash).await |
| 508 | } | ||
| 470 | 509 | ||
| 471 | let mut magic = Aligned([0; 4]); | 510 | async fn set_magic<F: AsyncNorFlash>( |
| 472 | flash.read(self.state.from as u32, &mut magic.0).await?; | 511 | &mut self, |
| 473 | let magic = u32::from_le_bytes(magic.0); | 512 | aligned: &mut [u8], |
| 513 | magic: u8, | ||
| 514 | flash: &mut F, | ||
| 515 | ) -> Result<(), F::Error> { | ||
| 516 | flash.read(self.state.from as u32, aligned).await?; | ||
| 474 | 517 | ||
| 475 | if magic != BOOT_MAGIC { | 518 | let mut is_set = true; |
| 476 | flash | 519 | for b in 0..aligned.len() { |
| 477 | .write(self.state.from as u32, &Aligned([0; 4]).0) | 520 | if aligned[b] != magic { |
| 478 | .await?; | 521 | is_set = false; |
| 522 | } | ||
| 523 | } | ||
| 524 | if !is_set { | ||
| 525 | for i in 0..aligned.len() { | ||
| 526 | aligned[i] = 0; | ||
| 527 | } | ||
| 528 | flash.write(self.state.from as u32, aligned).await?; | ||
| 479 | flash | 529 | flash |
| 480 | .erase(self.state.from as u32, self.state.to as u32) | 530 | .erase(self.state.from as u32, self.state.to as u32) |
| 481 | .await?; | 531 | .await?; |
| 482 | flash | 532 | |
| 483 | .write(self.state.from as u32, &BOOT_MAGIC.to_le_bytes()) | 533 | for i in 0..aligned.len() { |
| 484 | .await?; | 534 | aligned[i] = magic; |
| 535 | } | ||
| 536 | flash.write(self.state.from as u32, aligned).await?; | ||
| 485 | } | 537 | } |
| 486 | Ok(()) | 538 | Ok(()) |
| 487 | } | 539 | } |
| @@ -545,6 +597,7 @@ mod tests { | |||
| 545 | use super::*; | 597 | use super::*; |
| 546 | use core::convert::Infallible; | 598 | use core::convert::Infallible; |
| 547 | use core::future::Future; | 599 | use core::future::Future; |
| 600 | use embedded_storage::nor_flash::ErrorType; | ||
| 548 | use embedded_storage_async::nor_flash::AsyncReadNorFlash; | 601 | use embedded_storage_async::nor_flash::AsyncReadNorFlash; |
| 549 | use futures::executor::block_on; | 602 | use futures::executor::block_on; |
| 550 | 603 | ||
| @@ -552,9 +605,11 @@ mod tests { | |||
| 552 | const ACTIVE: Partition = Partition::new(4096, 61440); | 605 | const ACTIVE: Partition = Partition::new(4096, 61440); |
| 553 | const DFU: Partition = Partition::new(61440, 122880); | 606 | const DFU: Partition = Partition::new(61440, 122880); |
| 554 | 607 | ||
| 608 | /* | ||
| 555 | #[test] | 609 | #[test] |
| 556 | fn test_bad_magic() { | 610 | fn test_bad_magic() { |
| 557 | let mut flash = MemFlash([0xff; 131072]); | 611 | let mut flash = MemFlash([0xff; 131072]); |
| 612 | let mut flash = SingleFlashProvider::new(&mut flash); | ||
| 558 | 613 | ||
| 559 | let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); | 614 | let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); |
| 560 | 615 | ||
| @@ -563,11 +618,13 @@ mod tests { | |||
| 563 | Err(BootError::BadMagic) | 618 | Err(BootError::BadMagic) |
| 564 | ); | 619 | ); |
| 565 | } | 620 | } |
| 621 | */ | ||
| 566 | 622 | ||
| 567 | #[test] | 623 | #[test] |
| 568 | fn test_boot_state() { | 624 | fn test_boot_state() { |
| 569 | let mut flash = MemFlash([0xff; 131072]); | 625 | let mut flash = MemFlash([0xff; 131072]); |
| 570 | flash.0[0..4].copy_from_slice(&BOOT_MAGIC.to_le_bytes()); | 626 | flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]); |
| 627 | let mut flash = SingleFlashProvider::new(&mut flash); | ||
| 571 | 628 | ||
| 572 | let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); | 629 | let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); |
| 573 | 630 | ||
| @@ -588,19 +645,19 @@ mod tests { | |||
| 588 | 645 | ||
| 589 | let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); | 646 | let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); |
| 590 | let mut updater = FirmwareUpdater::new(DFU, STATE); | 647 | let mut updater = FirmwareUpdater::new(DFU, STATE); |
| 591 | for i in (DFU.from..DFU.to).step_by(4) { | 648 | let mut offset = 0; |
| 592 | let base = i - DFU.from; | 649 | for chunk in update.chunks(4096) { |
| 593 | let data: [u8; 4] = [ | 650 | block_on(updater.write_firmware(offset, &chunk, &mut flash, 4096)).unwrap(); |
| 594 | update[base], | 651 | offset += chunk.len(); |
| 595 | update[base + 1], | ||
| 596 | update[base + 2], | ||
| 597 | update[base + 3], | ||
| 598 | ]; | ||
| 599 | block_on(updater.write_firmware(i - DFU.from, &data, &mut flash)).unwrap(); | ||
| 600 | } | 652 | } |
| 601 | block_on(updater.mark_update(&mut flash)).unwrap(); | 653 | block_on(updater.mark_update(&mut flash)).unwrap(); |
| 602 | 654 | ||
| 603 | assert_eq!(State::Swap, bootloader.prepare_boot(&mut flash).unwrap()); | 655 | assert_eq!( |
| 656 | State::Swap, | ||
| 657 | bootloader | ||
| 658 | .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) | ||
| 659 | .unwrap() | ||
| 660 | ); | ||
| 604 | 661 | ||
| 605 | for i in ACTIVE.from..ACTIVE.to { | 662 | for i in ACTIVE.from..ACTIVE.to { |
| 606 | assert_eq!(flash.0[i], update[i - ACTIVE.from], "Index {}", i); | 663 | assert_eq!(flash.0[i], update[i - ACTIVE.from], "Index {}", i); |
| @@ -612,7 +669,12 @@ mod tests { | |||
| 612 | } | 669 | } |
| 613 | 670 | ||
| 614 | // Running again should cause a revert | 671 | // Running again should cause a revert |
| 615 | assert_eq!(State::Swap, bootloader.prepare_boot(&mut flash).unwrap()); | 672 | assert_eq!( |
| 673 | State::Swap, | ||
| 674 | bootloader | ||
| 675 | .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) | ||
| 676 | .unwrap() | ||
| 677 | ); | ||
| 616 | 678 | ||
| 617 | for i in ACTIVE.from..ACTIVE.to { | 679 | for i in ACTIVE.from..ACTIVE.to { |
| 618 | assert_eq!(flash.0[i], original[i - ACTIVE.from], "Index {}", i); | 680 | assert_eq!(flash.0[i], original[i - ACTIVE.from], "Index {}", i); |
| @@ -625,7 +687,12 @@ mod tests { | |||
| 625 | 687 | ||
| 626 | // Mark as booted | 688 | // Mark as booted |
| 627 | block_on(updater.mark_booted(&mut flash)).unwrap(); | 689 | block_on(updater.mark_booted(&mut flash)).unwrap(); |
| 628 | assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash).unwrap()); | 690 | assert_eq!( |
| 691 | State::Boot, | ||
| 692 | bootloader | ||
| 693 | .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) | ||
| 694 | .unwrap() | ||
| 695 | ); | ||
| 629 | } | 696 | } |
| 630 | 697 | ||
| 631 | struct MemFlash([u8; 131072]); | 698 | struct MemFlash([u8; 131072]); |
| @@ -656,9 +723,12 @@ mod tests { | |||
| 656 | } | 723 | } |
| 657 | } | 724 | } |
| 658 | 725 | ||
| 726 | impl ErrorType for MemFlash { | ||
| 727 | type Error = Infallible; | ||
| 728 | } | ||
| 729 | |||
| 659 | impl ReadNorFlash for MemFlash { | 730 | impl ReadNorFlash for MemFlash { |
| 660 | const READ_SIZE: usize = 4; | 731 | const READ_SIZE: usize = 4; |
| 661 | type Error = Infallible; | ||
| 662 | 732 | ||
| 663 | fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> { | 733 | fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> { |
| 664 | let len = buf.len(); | 734 | let len = buf.len(); |
| @@ -673,10 +743,9 @@ mod tests { | |||
| 673 | 743 | ||
| 674 | impl AsyncReadNorFlash for MemFlash { | 744 | impl AsyncReadNorFlash for MemFlash { |
| 675 | const READ_SIZE: usize = 4; | 745 | const READ_SIZE: usize = 4; |
| 676 | type Error = Infallible; | ||
| 677 | 746 | ||
| 678 | type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a; | 747 | type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a; |
| 679 | fn read<'a>(&'a mut self, offset: usize, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { | 748 | fn read<'a>(&'a mut self, offset: u32, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { |
| 680 | async move { | 749 | async move { |
| 681 | let len = buf.len(); | 750 | let len = buf.len(); |
| 682 | buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]); | 751 | 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 97207ac29..78157d246 100644 --- a/embassy-boot/nrf/Cargo.toml +++ b/embassy-boot/nrf/Cargo.toml | |||
| @@ -13,7 +13,7 @@ defmt-rtt = { version = "0.3", optional = true } | |||
| 13 | 13 | ||
| 14 | embassy = { path = "../../embassy", default-features = false } | 14 | embassy = { path = "../../embassy", default-features = false } |
| 15 | embassy-nrf = { path = "../../embassy-nrf", default-features = false, features = ["nightly"] } | 15 | embassy-nrf = { path = "../../embassy-nrf", default-features = false, features = ["nightly"] } |
| 16 | embassy-boot = { path = "../boot", default-features = false } | 16 | embassy-boot = { path = "../boot", default-features = false, features = ["write-4"] } |
| 17 | cortex-m = { version = "0.7" } | 17 | cortex-m = { version = "0.7" } |
| 18 | cortex-m-rt = { version = "0.7" } | 18 | cortex-m-rt = { version = "0.7" } |
| 19 | embedded-storage = "0.3.0" | 19 | embedded-storage = "0.3.0" |
diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs index 785cb67e8..500cae500 100644 --- a/embassy-boot/nrf/src/lib.rs +++ b/embassy-boot/nrf/src/lib.rs | |||
| @@ -4,9 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | mod fmt; | 5 | mod fmt; |
| 6 | 6 | ||
| 7 | pub use embassy_boot::{ | 7 | pub use embassy_boot::{FirmwareUpdater, FlashProvider, Partition, SingleFlashProvider}; |
| 8 | FirmwareUpdater, FlashProvider, Partition, SingleFlashProvider, State, BOOT_MAGIC, | ||
| 9 | }; | ||
| 10 | use embassy_nrf::{ | 8 | use embassy_nrf::{ |
| 11 | nvmc::{Nvmc, PAGE_SIZE}, | 9 | nvmc::{Nvmc, PAGE_SIZE}, |
| 12 | peripherals::WDT, | 10 | peripherals::WDT, |
| @@ -184,29 +182,3 @@ impl<'d> ReadNorFlash for WatchdogFlash<'d> { | |||
| 184 | self.flash.capacity() | 182 | self.flash.capacity() |
| 185 | } | 183 | } |
| 186 | } | 184 | } |
| 187 | |||
| 188 | pub mod updater { | ||
| 189 | use super::*; | ||
| 190 | pub fn new() -> embassy_boot::FirmwareUpdater { | ||
| 191 | extern "C" { | ||
| 192 | static __bootloader_state_start: u32; | ||
| 193 | static __bootloader_state_end: u32; | ||
| 194 | static __bootloader_dfu_start: u32; | ||
| 195 | static __bootloader_dfu_end: u32; | ||
| 196 | } | ||
| 197 | |||
| 198 | let dfu = unsafe { | ||
| 199 | Partition::new( | ||
| 200 | &__bootloader_dfu_start as *const u32 as usize, | ||
| 201 | &__bootloader_dfu_end as *const u32 as usize, | ||
| 202 | ) | ||
| 203 | }; | ||
| 204 | let state = unsafe { | ||
| 205 | Partition::new( | ||
| 206 | &__bootloader_state_start as *const u32 as usize, | ||
| 207 | &__bootloader_state_end as *const u32 as usize, | ||
| 208 | ) | ||
| 209 | }; | ||
| 210 | embassy_boot::FirmwareUpdater::new(dfu, state) | ||
| 211 | } | ||
| 212 | } | ||
diff --git a/embassy-boot/stm32/Cargo.toml b/embassy-boot/stm32/Cargo.toml new file mode 100644 index 000000000..76bc480bf --- /dev/null +++ b/embassy-boot/stm32/Cargo.toml | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | [package] | ||
| 2 | authors = [ | ||
| 3 | "Ulf Lilleengen <[email protected]>", | ||
| 4 | ] | ||
| 5 | edition = "2018" | ||
| 6 | name = "embassy-boot-stm32" | ||
| 7 | version = "0.1.0" | ||
| 8 | description = "Bootloader for STM32 chips" | ||
| 9 | |||
| 10 | [dependencies] | ||
| 11 | defmt = { version = "0.3", optional = true } | ||
| 12 | defmt-rtt = { version = "0.3", optional = true } | ||
| 13 | |||
| 14 | embassy = { path = "../../embassy", default-features = false } | ||
| 15 | embassy-stm32 = { path = "../../embassy-stm32", default-features = false, features = ["nightly"] } | ||
| 16 | embassy-boot = { path = "../boot", default-features = false } | ||
| 17 | cortex-m = { version = "0.7" } | ||
| 18 | cortex-m-rt = { version = "0.7" } | ||
| 19 | embedded-storage = "0.3.0" | ||
| 20 | embedded-storage-async = "0.3.0" | ||
| 21 | cfg-if = "1.0.0" | ||
| 22 | |||
| 23 | [features] | ||
| 24 | defmt = [ | ||
| 25 | "dep:defmt", | ||
| 26 | "embassy-boot/defmt", | ||
| 27 | "embassy-stm32/defmt", | ||
| 28 | ] | ||
| 29 | debug = ["defmt-rtt"] | ||
| 30 | flash-2k = ["embassy-boot/write-8"] | ||
| 31 | flash-128 = ["embassy-boot/write-4"] | ||
| 32 | flash-256 = ["embassy-boot/write-4"] | ||
| 33 | invert-erase = ["embassy-boot/invert-erase"] | ||
| 34 | thumbv6 = [] | ||
| 35 | |||
| 36 | [profile.dev] | ||
| 37 | debug = 2 | ||
| 38 | debug-assertions = true | ||
| 39 | incremental = false | ||
| 40 | opt-level = 'z' | ||
| 41 | overflow-checks = true | ||
| 42 | |||
| 43 | [profile.release] | ||
| 44 | codegen-units = 1 | ||
| 45 | debug = 2 | ||
| 46 | debug-assertions = false | ||
| 47 | incremental = false | ||
| 48 | lto = 'fat' | ||
| 49 | opt-level = 'z' | ||
| 50 | overflow-checks = false | ||
| 51 | |||
| 52 | # do not optimize proc-macro crates = faster builds from scratch | ||
| 53 | [profile.dev.build-override] | ||
| 54 | codegen-units = 8 | ||
| 55 | debug = false | ||
| 56 | debug-assertions = false | ||
| 57 | opt-level = 0 | ||
| 58 | overflow-checks = false | ||
| 59 | |||
| 60 | [profile.release.build-override] | ||
| 61 | codegen-units = 8 | ||
| 62 | debug = false | ||
| 63 | debug-assertions = false | ||
| 64 | opt-level = 0 | ||
| 65 | overflow-checks = false | ||
diff --git a/embassy-boot/stm32/README.md b/embassy-boot/stm32/README.md new file mode 100644 index 000000000..a82b730b9 --- /dev/null +++ b/embassy-boot/stm32/README.md | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | # Bootloader for STM32 | ||
| 2 | |||
| 3 | The bootloader uses `embassy-boot` to interact with the flash. | ||
| 4 | |||
| 5 | # Usage | ||
| 6 | |||
| 7 | Flash the bootloader | ||
| 8 | |||
| 9 | ``` | ||
| 10 | cargo flash --features embassy-stm32/stm32wl55jc-cm4 --release --chip STM32WLE5JCIx | ||
| 11 | ``` | ||
diff --git a/embassy-boot/stm32/build.rs b/embassy-boot/stm32/build.rs new file mode 100644 index 000000000..fd605991f --- /dev/null +++ b/embassy-boot/stm32/build.rs | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | use std::env; | ||
| 2 | use std::fs::File; | ||
| 3 | use std::io::Write; | ||
| 4 | use std::path::PathBuf; | ||
| 5 | |||
| 6 | fn main() { | ||
| 7 | // Put `memory.x` in our output directory and ensure it's | ||
| 8 | // on the linker search path. | ||
| 9 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); | ||
| 10 | File::create(out.join("memory.x")) | ||
| 11 | .unwrap() | ||
| 12 | .write_all(include_bytes!("memory.x")) | ||
| 13 | .unwrap(); | ||
| 14 | println!("cargo:rustc-link-search={}", out.display()); | ||
| 15 | |||
| 16 | // By default, Cargo will re-run a build script whenever | ||
| 17 | // any file in the project changes. By specifying `memory.x` | ||
| 18 | // here, we ensure the build script is only re-run when | ||
| 19 | // `memory.x` is changed. | ||
| 20 | println!("cargo:rerun-if-changed=memory.x"); | ||
| 21 | |||
| 22 | println!("cargo:rustc-link-arg-bins=--nmagic"); | ||
| 23 | println!("cargo:rustc-link-arg-bins=-Tlink.x"); | ||
| 24 | if env::var("CARGO_FEATURE_DEFMT").is_ok() { | ||
| 25 | println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); | ||
| 26 | } | ||
| 27 | } | ||
diff --git a/embassy-boot/stm32/memory.x b/embassy-boot/stm32/memory.x new file mode 100644 index 000000000..c5356ed37 --- /dev/null +++ b/embassy-boot/stm32/memory.x | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | MEMORY | ||
| 2 | { | ||
| 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ | ||
| 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 24K | ||
| 5 | BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K | ||
| 6 | ACTIVE : ORIGIN = 0x08008000, LENGTH = 32K | ||
| 7 | DFU : ORIGIN = 0x08010000, LENGTH = 36K | ||
| 8 | RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 16K | ||
| 9 | } | ||
| 10 | |||
| 11 | __bootloader_state_start = ORIGIN(BOOTLOADER_STATE); | ||
| 12 | __bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE); | ||
| 13 | |||
| 14 | __bootloader_active_start = ORIGIN(ACTIVE); | ||
| 15 | __bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE); | ||
| 16 | |||
| 17 | __bootloader_dfu_start = ORIGIN(DFU); | ||
| 18 | __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 000000000..066970813 --- /dev/null +++ b/embassy-boot/stm32/src/fmt.rs | |||
| @@ -0,0 +1,225 @@ | |||
| 1 | #![macro_use] | ||
| 2 | #![allow(unused_macros)] | ||
| 3 | |||
| 4 | #[cfg(all(feature = "defmt", feature = "log"))] | ||
| 5 | compile_error!("You may not enable both `defmt` and `log` features."); | ||
| 6 | |||
| 7 | macro_rules! assert { | ||
| 8 | ($($x:tt)*) => { | ||
| 9 | { | ||
| 10 | #[cfg(not(feature = "defmt"))] | ||
| 11 | ::core::assert!($($x)*); | ||
| 12 | #[cfg(feature = "defmt")] | ||
| 13 | ::defmt::assert!($($x)*); | ||
| 14 | } | ||
| 15 | }; | ||
| 16 | } | ||
| 17 | |||
| 18 | macro_rules! assert_eq { | ||
| 19 | ($($x:tt)*) => { | ||
| 20 | { | ||
| 21 | #[cfg(not(feature = "defmt"))] | ||
| 22 | ::core::assert_eq!($($x)*); | ||
| 23 | #[cfg(feature = "defmt")] | ||
| 24 | ::defmt::assert_eq!($($x)*); | ||
| 25 | } | ||
| 26 | }; | ||
| 27 | } | ||
| 28 | |||
| 29 | macro_rules! assert_ne { | ||
| 30 | ($($x:tt)*) => { | ||
| 31 | { | ||
| 32 | #[cfg(not(feature = "defmt"))] | ||
| 33 | ::core::assert_ne!($($x)*); | ||
| 34 | #[cfg(feature = "defmt")] | ||
| 35 | ::defmt::assert_ne!($($x)*); | ||
| 36 | } | ||
| 37 | }; | ||
| 38 | } | ||
| 39 | |||
| 40 | macro_rules! debug_assert { | ||
| 41 | ($($x:tt)*) => { | ||
| 42 | { | ||
| 43 | #[cfg(not(feature = "defmt"))] | ||
| 44 | ::core::debug_assert!($($x)*); | ||
| 45 | #[cfg(feature = "defmt")] | ||
| 46 | ::defmt::debug_assert!($($x)*); | ||
| 47 | } | ||
| 48 | }; | ||
| 49 | } | ||
| 50 | |||
| 51 | macro_rules! debug_assert_eq { | ||
| 52 | ($($x:tt)*) => { | ||
| 53 | { | ||
| 54 | #[cfg(not(feature = "defmt"))] | ||
| 55 | ::core::debug_assert_eq!($($x)*); | ||
| 56 | #[cfg(feature = "defmt")] | ||
| 57 | ::defmt::debug_assert_eq!($($x)*); | ||
| 58 | } | ||
| 59 | }; | ||
| 60 | } | ||
| 61 | |||
| 62 | macro_rules! debug_assert_ne { | ||
| 63 | ($($x:tt)*) => { | ||
| 64 | { | ||
| 65 | #[cfg(not(feature = "defmt"))] | ||
| 66 | ::core::debug_assert_ne!($($x)*); | ||
| 67 | #[cfg(feature = "defmt")] | ||
| 68 | ::defmt::debug_assert_ne!($($x)*); | ||
| 69 | } | ||
| 70 | }; | ||
| 71 | } | ||
| 72 | |||
| 73 | macro_rules! todo { | ||
| 74 | ($($x:tt)*) => { | ||
| 75 | { | ||
| 76 | #[cfg(not(feature = "defmt"))] | ||
| 77 | ::core::todo!($($x)*); | ||
| 78 | #[cfg(feature = "defmt")] | ||
| 79 | ::defmt::todo!($($x)*); | ||
| 80 | } | ||
| 81 | }; | ||
| 82 | } | ||
| 83 | |||
| 84 | macro_rules! unreachable { | ||
| 85 | ($($x:tt)*) => { | ||
| 86 | { | ||
| 87 | #[cfg(not(feature = "defmt"))] | ||
| 88 | ::core::unreachable!($($x)*); | ||
| 89 | #[cfg(feature = "defmt")] | ||
| 90 | ::defmt::unreachable!($($x)*); | ||
| 91 | } | ||
| 92 | }; | ||
| 93 | } | ||
| 94 | |||
| 95 | macro_rules! panic { | ||
| 96 | ($($x:tt)*) => { | ||
| 97 | { | ||
| 98 | #[cfg(not(feature = "defmt"))] | ||
| 99 | ::core::panic!($($x)*); | ||
| 100 | #[cfg(feature = "defmt")] | ||
| 101 | ::defmt::panic!($($x)*); | ||
| 102 | } | ||
| 103 | }; | ||
| 104 | } | ||
| 105 | |||
| 106 | macro_rules! trace { | ||
| 107 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 108 | { | ||
| 109 | #[cfg(feature = "log")] | ||
| 110 | ::log::trace!($s $(, $x)*); | ||
| 111 | #[cfg(feature = "defmt")] | ||
| 112 | ::defmt::trace!($s $(, $x)*); | ||
| 113 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 114 | let _ = ($( & $x ),*); | ||
| 115 | } | ||
| 116 | }; | ||
| 117 | } | ||
| 118 | |||
| 119 | macro_rules! debug { | ||
| 120 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 121 | { | ||
| 122 | #[cfg(feature = "log")] | ||
| 123 | ::log::debug!($s $(, $x)*); | ||
| 124 | #[cfg(feature = "defmt")] | ||
| 125 | ::defmt::debug!($s $(, $x)*); | ||
| 126 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 127 | let _ = ($( & $x ),*); | ||
| 128 | } | ||
| 129 | }; | ||
| 130 | } | ||
| 131 | |||
| 132 | macro_rules! info { | ||
| 133 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 134 | { | ||
| 135 | #[cfg(feature = "log")] | ||
| 136 | ::log::info!($s $(, $x)*); | ||
| 137 | #[cfg(feature = "defmt")] | ||
| 138 | ::defmt::info!($s $(, $x)*); | ||
| 139 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 140 | let _ = ($( & $x ),*); | ||
| 141 | } | ||
| 142 | }; | ||
| 143 | } | ||
| 144 | |||
| 145 | macro_rules! warn { | ||
| 146 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 147 | { | ||
| 148 | #[cfg(feature = "log")] | ||
| 149 | ::log::warn!($s $(, $x)*); | ||
| 150 | #[cfg(feature = "defmt")] | ||
| 151 | ::defmt::warn!($s $(, $x)*); | ||
| 152 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 153 | let _ = ($( & $x ),*); | ||
| 154 | } | ||
| 155 | }; | ||
| 156 | } | ||
| 157 | |||
| 158 | macro_rules! error { | ||
| 159 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 160 | { | ||
| 161 | #[cfg(feature = "log")] | ||
| 162 | ::log::error!($s $(, $x)*); | ||
| 163 | #[cfg(feature = "defmt")] | ||
| 164 | ::defmt::error!($s $(, $x)*); | ||
| 165 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 166 | let _ = ($( & $x ),*); | ||
| 167 | } | ||
| 168 | }; | ||
| 169 | } | ||
| 170 | |||
| 171 | #[cfg(feature = "defmt")] | ||
| 172 | macro_rules! unwrap { | ||
| 173 | ($($x:tt)*) => { | ||
| 174 | ::defmt::unwrap!($($x)*) | ||
| 175 | }; | ||
| 176 | } | ||
| 177 | |||
| 178 | #[cfg(not(feature = "defmt"))] | ||
| 179 | macro_rules! unwrap { | ||
| 180 | ($arg:expr) => { | ||
| 181 | match $crate::fmt::Try::into_result($arg) { | ||
| 182 | ::core::result::Result::Ok(t) => t, | ||
| 183 | ::core::result::Result::Err(e) => { | ||
| 184 | ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); | ||
| 185 | } | ||
| 186 | } | ||
| 187 | }; | ||
| 188 | ($arg:expr, $($msg:expr),+ $(,)? ) => { | ||
| 189 | match $crate::fmt::Try::into_result($arg) { | ||
| 190 | ::core::result::Result::Ok(t) => t, | ||
| 191 | ::core::result::Result::Err(e) => { | ||
| 192 | ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); | ||
| 193 | } | ||
| 194 | } | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 199 | pub struct NoneError; | ||
| 200 | |||
| 201 | pub trait Try { | ||
| 202 | type Ok; | ||
| 203 | type Error; | ||
| 204 | fn into_result(self) -> Result<Self::Ok, Self::Error>; | ||
| 205 | } | ||
| 206 | |||
| 207 | impl<T> Try for Option<T> { | ||
| 208 | type Ok = T; | ||
| 209 | type Error = NoneError; | ||
| 210 | |||
| 211 | #[inline] | ||
| 212 | fn into_result(self) -> Result<T, NoneError> { | ||
| 213 | self.ok_or(NoneError) | ||
| 214 | } | ||
| 215 | } | ||
| 216 | |||
| 217 | impl<T, E> Try for Result<T, E> { | ||
| 218 | type Ok = T; | ||
| 219 | type Error = E; | ||
| 220 | |||
| 221 | #[inline] | ||
| 222 | fn into_result(self) -> Self { | ||
| 223 | self | ||
| 224 | } | ||
| 225 | } | ||
diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs new file mode 100644 index 000000000..d1754a310 --- /dev/null +++ b/embassy-boot/stm32/src/lib.rs | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![feature(generic_associated_types)] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | mod fmt; | ||
| 6 | |||
| 7 | pub use embassy_boot::{FirmwareUpdater, FlashProvider, Partition, SingleFlashProvider, State}; | ||
| 8 | |||
| 9 | pub struct BootLoader<const PAGE_SIZE: usize> { | ||
| 10 | boot: embassy_boot::BootLoader<PAGE_SIZE>, | ||
| 11 | } | ||
| 12 | |||
| 13 | impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> { | ||
| 14 | /// Create a new bootloader instance using parameters from linker script | ||
| 15 | pub fn default() -> Self { | ||
| 16 | extern "C" { | ||
| 17 | static __bootloader_state_start: u32; | ||
| 18 | static __bootloader_state_end: u32; | ||
| 19 | static __bootloader_active_start: u32; | ||
| 20 | static __bootloader_active_end: u32; | ||
| 21 | static __bootloader_dfu_start: u32; | ||
| 22 | static __bootloader_dfu_end: u32; | ||
| 23 | } | ||
| 24 | |||
| 25 | let active = unsafe { | ||
| 26 | Partition::new( | ||
| 27 | &__bootloader_active_start as *const u32 as usize, | ||
| 28 | &__bootloader_active_end as *const u32 as usize, | ||
| 29 | ) | ||
| 30 | }; | ||
| 31 | let dfu = unsafe { | ||
| 32 | Partition::new( | ||
| 33 | &__bootloader_dfu_start as *const u32 as usize, | ||
| 34 | &__bootloader_dfu_end as *const u32 as usize, | ||
| 35 | ) | ||
| 36 | }; | ||
| 37 | let state = unsafe { | ||
| 38 | Partition::new( | ||
| 39 | &__bootloader_state_start as *const u32 as usize, | ||
| 40 | &__bootloader_state_end as *const u32 as usize, | ||
| 41 | ) | ||
| 42 | }; | ||
| 43 | |||
| 44 | trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); | ||
| 45 | trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); | ||
| 46 | trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); | ||
| 47 | |||
| 48 | Self::new(active, dfu, state) | ||
| 49 | } | ||
| 50 | |||
| 51 | /// Create a new bootloader instance using the supplied partitions for active, dfu and state. | ||
| 52 | pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { | ||
| 53 | Self { | ||
| 54 | boot: embassy_boot::BootLoader::new(active, dfu, state), | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | /// Boots the application | ||
| 59 | pub fn prepare<F: FlashProvider>(&mut self, flash: &mut F) -> usize { | ||
| 60 | match self.boot.prepare_boot(flash) { | ||
| 61 | Ok(_) => self.boot.boot_address(), | ||
| 62 | Err(_) => panic!("boot prepare error!"), | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | pub unsafe fn load(&mut self, start: usize) -> ! { | ||
| 67 | trace!("Loading app at 0x{:x}", start); | ||
| 68 | let mut p = cortex_m::Peripherals::steal(); | ||
| 69 | #[cfg(not(feature = "thumbv6"))] | ||
| 70 | p.SCB.invalidate_icache(); | ||
| 71 | p.SCB.vtor.write(start as u32); | ||
| 72 | |||
| 73 | cortex_m::asm::bootload(start as *const u32) | ||
| 74 | } | ||
| 75 | } | ||
diff --git a/embassy-boot/stm32/src/main.rs b/embassy-boot/stm32/src/main.rs new file mode 100644 index 000000000..6fe0fb66d --- /dev/null +++ b/embassy-boot/stm32/src/main.rs | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use cortex_m_rt::{entry, exception}; | ||
| 5 | |||
| 6 | #[cfg(feature = "defmt")] | ||
| 7 | use defmt_rtt as _; | ||
| 8 | |||
| 9 | use embassy_boot_stm32::*; | ||
| 10 | use embassy_stm32::flash::Flash; | ||
| 11 | |||
| 12 | #[cfg(not(any(feature = "flash-2k", feature = "flash-256", feature = "flash-128")))] | ||
| 13 | compile_error!("No flash size specified. Must specify exactly one of the following features: flash-2k, flash-256, flash-128"); | ||
| 14 | |||
| 15 | #[entry] | ||
| 16 | fn main() -> ! { | ||
| 17 | let p = embassy_stm32::init(Default::default()); | ||
| 18 | |||
| 19 | // Uncomment this if you are debugging the bootloader with debugger/RTT attached, | ||
| 20 | // as it prevents a hard fault when accessing flash 'too early' after boot. | ||
| 21 | /* | ||
| 22 | for i in 0..10000000 { | ||
| 23 | cortex_m::asm::nop(); | ||
| 24 | } | ||
| 25 | */ | ||
| 26 | |||
| 27 | #[cfg(feature = "flash-2k")] | ||
| 28 | let mut bl: BootLoader<2048> = BootLoader::default(); | ||
| 29 | |||
| 30 | #[cfg(feature = "flash-256")] | ||
| 31 | let mut bl: BootLoader<256> = BootLoader::default(); | ||
| 32 | |||
| 33 | #[cfg(feature = "flash-128")] | ||
| 34 | let mut bl: BootLoader<128> = BootLoader::default(); | ||
| 35 | |||
| 36 | let mut flash = Flash::unlock(p.FLASH); | ||
| 37 | let start = bl.prepare(&mut SingleFlashProvider::new(&mut flash)); | ||
| 38 | core::mem::drop(flash); | ||
| 39 | unsafe { bl.load(start) } | ||
| 40 | } | ||
| 41 | |||
| 42 | #[no_mangle] | ||
| 43 | #[cfg_attr(target_os = "none", link_section = ".HardFault.user")] | ||
| 44 | unsafe extern "C" fn HardFault() { | ||
| 45 | cortex_m::peripheral::SCB::sys_reset(); | ||
| 46 | } | ||
| 47 | |||
| 48 | #[exception] | ||
| 49 | unsafe fn DefaultHandler(_: i16) -> ! { | ||
| 50 | const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32; | ||
| 51 | let irqn = core::ptr::read_volatile(SCB_ICSR) as u8 as i16 - 16; | ||
| 52 | |||
| 53 | panic!("DefaultHandler #{:?}", irqn); | ||
| 54 | } | ||
| 55 | |||
| 56 | #[panic_handler] | ||
| 57 | fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| 58 | unsafe { | ||
| 59 | cortex_m::asm::udf(); | ||
| 60 | core::hint::unreachable_unchecked(); | ||
| 61 | } | ||
| 62 | } | ||
diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 1c9616b71..8152b07d4 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 | |||
| 42 | embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8", optional = true} | 42 | embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8", optional = true} |
| 43 | embedded-hal-async = { version = "0.1.0-alpha.0", optional = true} | 43 | embedded-hal-async = { version = "0.1.0-alpha.0", optional = true} |
| 44 | 44 | ||
| 45 | embedded-storage = "0.3.0" | ||
| 46 | embedded-storage-async = { version = "0.3.0", optional = true } | ||
| 47 | |||
| 45 | defmt = { version = "0.3", optional = true } | 48 | defmt = { version = "0.3", optional = true } |
| 46 | log = { version = "0.4.14", optional = true } | 49 | log = { version = "0.4.14", optional = true } |
| 47 | cortex-m-rt = ">=0.6.15,<0.8" | 50 | cortex-m-rt = ">=0.6.15,<0.8" |
| @@ -87,7 +90,7 @@ time-driver-tim12 = ["_time-driver"] | |||
| 87 | time-driver-tim15 = ["_time-driver"] | 90 | time-driver-tim15 = ["_time-driver"] |
| 88 | 91 | ||
| 89 | # Enable nightly-only features | 92 | # Enable nightly-only features |
| 90 | nightly = ["embassy/nightly", "embedded-hal-1", "embedded-hal-async"] | 93 | nightly = ["embassy/nightly", "embedded-hal-1", "embedded-hal-async", "embedded-storage-async"] |
| 91 | 94 | ||
| 92 | # Reexport stm32-metapac at `embassy_stm32::pac`. | 95 | # Reexport stm32-metapac at `embassy_stm32::pac`. |
| 93 | # This is unstable because semver-minor (non-breaking) releases of embassy-stm32 may major-bump (breaking) the stm32-metapac version. | 96 | # 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 000000000..7a282497f --- /dev/null +++ b/embassy-stm32/src/flash/mod.rs | |||
| @@ -0,0 +1,401 @@ | |||
| 1 | use crate::pac; | ||
| 2 | use crate::peripherals::FLASH; | ||
| 3 | use core::convert::TryInto; | ||
| 4 | use core::marker::PhantomData; | ||
| 5 | use core::ptr::write_volatile; | ||
| 6 | use embassy::util::Unborrow; | ||
| 7 | use embassy_hal_common::unborrow; | ||
| 8 | |||
| 9 | use embedded_storage::nor_flash::{ | ||
| 10 | ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash, | ||
| 11 | }; | ||
| 12 | |||
| 13 | const FLASH_BASE: usize = 0x8000000; | ||
| 14 | |||
| 15 | #[cfg(flash_l4)] | ||
| 16 | mod config { | ||
| 17 | use super::*; | ||
| 18 | pub(crate) const FLASH_SIZE: usize = 0x100000; | ||
| 19 | pub(crate) const FLASH_START: usize = FLASH_BASE; | ||
| 20 | pub(crate) const FLASH_END: usize = FLASH_START + FLASH_SIZE; | ||
| 21 | pub(crate) const PAGE_SIZE: usize = 2048; | ||
| 22 | pub(crate) const WORD_SIZE: usize = 8; | ||
| 23 | } | ||
| 24 | |||
| 25 | #[cfg(flash_wb)] | ||
| 26 | mod config { | ||
| 27 | use super::*; | ||
| 28 | pub(crate) const FLASH_SIZE: usize = 0x100000; | ||
| 29 | pub(crate) const FLASH_START: usize = FLASH_BASE; | ||
| 30 | pub(crate) const FLASH_END: usize = FLASH_START + FLASH_SIZE; | ||
| 31 | pub(crate) const PAGE_SIZE: usize = 4096; | ||
| 32 | pub(crate) const WORD_SIZE: usize = 8; | ||
| 33 | } | ||
| 34 | |||
| 35 | #[cfg(flash_wl)] | ||
| 36 | mod config { | ||
| 37 | use super::*; | ||
| 38 | pub(crate) const FLASH_SIZE: usize = 0x40000; | ||
| 39 | pub(crate) const FLASH_START: usize = FLASH_BASE; | ||
| 40 | pub(crate) const FLASH_END: usize = FLASH_START + FLASH_SIZE; | ||
| 41 | pub(crate) const PAGE_SIZE: usize = 2048; | ||
| 42 | pub(crate) const WORD_SIZE: usize = 8; | ||
| 43 | } | ||
| 44 | |||
| 45 | #[cfg(flash_l0)] | ||
| 46 | mod config { | ||
| 47 | use super::*; | ||
| 48 | pub(crate) const FLASH_SIZE: usize = 0x30000; | ||
| 49 | pub(crate) const FLASH_START: usize = FLASH_BASE; | ||
| 50 | pub(crate) const FLASH_END: usize = FLASH_START + FLASH_SIZE; | ||
| 51 | pub(crate) const PAGE_SIZE: usize = 128; | ||
| 52 | pub(crate) const WORD_SIZE: usize = 4; | ||
| 53 | } | ||
| 54 | |||
| 55 | #[cfg(flash_l1)] | ||
| 56 | mod config { | ||
| 57 | use super::*; | ||
| 58 | pub(crate) const FLASH_SIZE: usize = 0x80000; | ||
| 59 | pub(crate) const FLASH_START: usize = FLASH_BASE; | ||
| 60 | pub(crate) const FLASH_END: usize = FLASH_START + FLASH_SIZE; | ||
| 61 | pub(crate) const PAGE_SIZE: usize = 256; | ||
| 62 | pub(crate) const WORD_SIZE: usize = 4; | ||
| 63 | } | ||
| 64 | |||
| 65 | use config::*; | ||
| 66 | |||
| 67 | pub struct Flash<'d> { | ||
| 68 | _inner: FLASH, | ||
| 69 | _phantom: PhantomData<&'d mut FLASH>, | ||
| 70 | } | ||
| 71 | |||
| 72 | impl<'d> Flash<'d> { | ||
| 73 | pub fn new(p: impl Unborrow<Target = FLASH>) -> Self { | ||
| 74 | unborrow!(p); | ||
| 75 | Self { | ||
| 76 | _inner: p, | ||
| 77 | _phantom: PhantomData, | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | pub fn unlock(p: impl Unborrow<Target = FLASH>) -> Self { | ||
| 82 | let flash = Self::new(p); | ||
| 83 | #[cfg(any(flash_wl, flash_wb, flash_l4))] | ||
| 84 | unsafe { | ||
| 85 | pac::FLASH.keyr().write(|w| w.set_keyr(0x4567_0123)); | ||
| 86 | pac::FLASH.keyr().write(|w| w.set_keyr(0xCDEF_89AB)); | ||
| 87 | } | ||
| 88 | |||
| 89 | #[cfg(any(flash_l0))] | ||
| 90 | unsafe { | ||
| 91 | pac::FLASH.pekeyr().write(|w| w.set_pekeyr(0x89ABCDEF)); | ||
| 92 | pac::FLASH.pekeyr().write(|w| w.set_pekeyr(0x02030405)); | ||
| 93 | |||
| 94 | pac::FLASH.prgkeyr().write(|w| w.set_prgkeyr(0x8C9DAEBF)); | ||
| 95 | pac::FLASH.prgkeyr().write(|w| w.set_prgkeyr(0x13141516)); | ||
| 96 | } | ||
| 97 | flash | ||
| 98 | } | ||
| 99 | |||
| 100 | pub fn lock(&mut self) { | ||
| 101 | #[cfg(any(flash_wl, flash_wb, flash_l4))] | ||
| 102 | unsafe { | ||
| 103 | pac::FLASH.cr().modify(|w| w.set_lock(true)); | ||
| 104 | } | ||
| 105 | |||
| 106 | #[cfg(any(flash_l0))] | ||
| 107 | unsafe { | ||
| 108 | pac::FLASH.pecr().modify(|w| { | ||
| 109 | w.set_optlock(true); | ||
| 110 | w.set_prglock(true); | ||
| 111 | w.set_pelock(true); | ||
| 112 | }); | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | pub fn blocking_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { | ||
| 117 | if offset as usize >= FLASH_END || offset as usize + bytes.len() > FLASH_END { | ||
| 118 | return Err(Error::Size); | ||
| 119 | } | ||
| 120 | |||
| 121 | let flash_data = unsafe { core::slice::from_raw_parts(offset as *const u8, bytes.len()) }; | ||
| 122 | bytes.copy_from_slice(flash_data); | ||
| 123 | Ok(()) | ||
| 124 | } | ||
| 125 | |||
| 126 | pub fn blocking_write(&mut self, offset: u32, buf: &[u8]) -> Result<(), Error> { | ||
| 127 | if offset as usize + buf.len() > FLASH_END { | ||
| 128 | return Err(Error::Size); | ||
| 129 | } | ||
| 130 | if offset as usize % WORD_SIZE != 0 || buf.len() as usize % WORD_SIZE != 0 { | ||
| 131 | return Err(Error::Unaligned); | ||
| 132 | } | ||
| 133 | |||
| 134 | self.clear_all_err(); | ||
| 135 | |||
| 136 | #[cfg(any(flash_wl, flash_wb, flash_l4))] | ||
| 137 | unsafe { | ||
| 138 | pac::FLASH.cr().write(|w| w.set_pg(true)); | ||
| 139 | } | ||
| 140 | |||
| 141 | let mut ret: Result<(), Error> = Ok(()); | ||
| 142 | let mut offset = offset; | ||
| 143 | for chunk in buf.chunks(WORD_SIZE) { | ||
| 144 | for val in chunk.chunks(4) { | ||
| 145 | unsafe { | ||
| 146 | write_volatile( | ||
| 147 | offset as *mut u32, | ||
| 148 | u32::from_le_bytes(val[0..4].try_into().unwrap()), | ||
| 149 | ); | ||
| 150 | } | ||
| 151 | offset += val.len() as u32; | ||
| 152 | } | ||
| 153 | |||
| 154 | ret = self.blocking_wait_ready(); | ||
| 155 | if ret.is_err() { | ||
| 156 | break; | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | #[cfg(any(flash_wl, flash_wb, flash_l4))] | ||
| 161 | unsafe { | ||
| 162 | pac::FLASH.cr().write(|w| w.set_pg(false)); | ||
| 163 | } | ||
| 164 | |||
| 165 | ret | ||
| 166 | } | ||
| 167 | |||
| 168 | pub fn blocking_erase(&mut self, from: u32, to: u32) -> Result<(), Error> { | ||
| 169 | if to < from || to as usize > FLASH_END { | ||
| 170 | return Err(Error::Size); | ||
| 171 | } | ||
| 172 | if from as usize % PAGE_SIZE != 0 || to as usize % PAGE_SIZE != 0 { | ||
| 173 | return Err(Error::Unaligned); | ||
| 174 | } | ||
| 175 | |||
| 176 | self.clear_all_err(); | ||
| 177 | |||
| 178 | for page in (from..to).step_by(PAGE_SIZE) { | ||
| 179 | #[cfg(any(flash_l0, flash_l1))] | ||
| 180 | unsafe { | ||
| 181 | pac::FLASH.pecr().modify(|w| { | ||
| 182 | w.set_erase(true); | ||
| 183 | w.set_prog(true); | ||
| 184 | }); | ||
| 185 | |||
| 186 | write_volatile(page as *mut u32, 0xFFFFFFFF); | ||
| 187 | } | ||
| 188 | |||
| 189 | #[cfg(any(flash_wl, flash_wb, flash_l4))] | ||
| 190 | unsafe { | ||
| 191 | let idx = page / PAGE_SIZE as u32; | ||
| 192 | |||
| 193 | pac::FLASH.cr().modify(|w| { | ||
| 194 | w.set_per(true); | ||
| 195 | w.set_pnb(idx as u8); | ||
| 196 | #[cfg(any(flash_wl, flash_wb))] | ||
| 197 | w.set_strt(true); | ||
| 198 | #[cfg(any(flash_l4))] | ||
| 199 | w.set_start(true); | ||
| 200 | }); | ||
| 201 | } | ||
| 202 | |||
| 203 | let ret: Result<(), Error> = self.blocking_wait_ready(); | ||
| 204 | |||
| 205 | #[cfg(any(flash_wl, flash_wb, flash_l4))] | ||
| 206 | unsafe { | ||
| 207 | pac::FLASH.cr().modify(|w| w.set_per(false)); | ||
| 208 | } | ||
| 209 | |||
| 210 | #[cfg(any(flash_l0, flash_l1))] | ||
| 211 | unsafe { | ||
| 212 | pac::FLASH.pecr().modify(|w| { | ||
| 213 | w.set_erase(false); | ||
| 214 | w.set_prog(false); | ||
| 215 | }); | ||
| 216 | } | ||
| 217 | |||
| 218 | self.clear_all_err(); | ||
| 219 | if ret.is_err() { | ||
| 220 | return ret; | ||
| 221 | } | ||
| 222 | } | ||
| 223 | |||
| 224 | Ok(()) | ||
| 225 | } | ||
| 226 | |||
| 227 | fn blocking_wait_ready(&self) -> Result<(), Error> { | ||
| 228 | loop { | ||
| 229 | let sr = unsafe { pac::FLASH.sr().read() }; | ||
| 230 | |||
| 231 | if !sr.bsy() { | ||
| 232 | #[cfg(any(flash_wl, flash_wb, flash_l4))] | ||
| 233 | if sr.progerr() { | ||
| 234 | return Err(Error::Prog); | ||
| 235 | } | ||
| 236 | |||
| 237 | if sr.wrperr() { | ||
| 238 | return Err(Error::Protected); | ||
| 239 | } | ||
| 240 | |||
| 241 | if sr.pgaerr() { | ||
| 242 | return Err(Error::Unaligned); | ||
| 243 | } | ||
| 244 | |||
| 245 | if sr.sizerr() { | ||
| 246 | return Err(Error::Size); | ||
| 247 | } | ||
| 248 | |||
| 249 | #[cfg(any(flash_wl, flash_wb, flash_l4))] | ||
| 250 | if sr.miserr() { | ||
| 251 | return Err(Error::Miss); | ||
| 252 | } | ||
| 253 | |||
| 254 | #[cfg(any(flash_wl, flash_wb, flash_l4))] | ||
| 255 | if sr.pgserr() { | ||
| 256 | return Err(Error::Seq); | ||
| 257 | } | ||
| 258 | return Ok(()); | ||
| 259 | } | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | fn clear_all_err(&mut self) { | ||
| 264 | unsafe { | ||
| 265 | pac::FLASH.sr().modify(|w| { | ||
| 266 | #[cfg(any(flash_wl, flash_wb, flash_l4, flash_l0))] | ||
| 267 | if w.rderr() { | ||
| 268 | w.set_rderr(false); | ||
| 269 | } | ||
| 270 | #[cfg(any(flash_wl, flash_wb, flash_l4))] | ||
| 271 | if w.fasterr() { | ||
| 272 | w.set_fasterr(false); | ||
| 273 | } | ||
| 274 | #[cfg(any(flash_wl, flash_wb, flash_l4))] | ||
| 275 | if w.miserr() { | ||
| 276 | w.set_miserr(false); | ||
| 277 | } | ||
| 278 | #[cfg(any(flash_wl, flash_wb, flash_l4))] | ||
| 279 | if w.pgserr() { | ||
| 280 | w.set_pgserr(false); | ||
| 281 | } | ||
| 282 | if w.sizerr() { | ||
| 283 | w.set_sizerr(false); | ||
| 284 | } | ||
| 285 | if w.pgaerr() { | ||
| 286 | w.set_pgaerr(false); | ||
| 287 | } | ||
| 288 | if w.wrperr() { | ||
| 289 | w.set_wrperr(false); | ||
| 290 | } | ||
| 291 | #[cfg(any(flash_wl, flash_wb, flash_l4))] | ||
| 292 | if w.progerr() { | ||
| 293 | w.set_progerr(false); | ||
| 294 | } | ||
| 295 | #[cfg(any(flash_wl, flash_wb, flash_l4))] | ||
| 296 | if w.operr() { | ||
| 297 | w.set_operr(false); | ||
| 298 | } | ||
| 299 | }); | ||
| 300 | } | ||
| 301 | } | ||
| 302 | } | ||
| 303 | |||
| 304 | impl Drop for Flash<'_> { | ||
| 305 | fn drop(&mut self) { | ||
| 306 | self.lock(); | ||
| 307 | } | ||
| 308 | } | ||
| 309 | |||
| 310 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] | ||
| 311 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 312 | pub enum Error { | ||
| 313 | Prog, | ||
| 314 | Size, | ||
| 315 | Miss, | ||
| 316 | Seq, | ||
| 317 | Protected, | ||
| 318 | Unaligned, | ||
| 319 | } | ||
| 320 | |||
| 321 | impl<'d> ErrorType for Flash<'d> { | ||
| 322 | type Error = Error; | ||
| 323 | } | ||
| 324 | |||
| 325 | impl NorFlashError for Error { | ||
| 326 | fn kind(&self) -> NorFlashErrorKind { | ||
| 327 | match self { | ||
| 328 | Self::Size => NorFlashErrorKind::OutOfBounds, | ||
| 329 | Self::Unaligned => NorFlashErrorKind::NotAligned, | ||
| 330 | _ => NorFlashErrorKind::Other, | ||
| 331 | } | ||
| 332 | } | ||
| 333 | } | ||
| 334 | |||
| 335 | impl<'d> ReadNorFlash for Flash<'d> { | ||
| 336 | const READ_SIZE: usize = WORD_SIZE; | ||
| 337 | |||
| 338 | fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { | ||
| 339 | self.blocking_read(offset, bytes) | ||
| 340 | } | ||
| 341 | |||
| 342 | fn capacity(&self) -> usize { | ||
| 343 | FLASH_SIZE | ||
| 344 | } | ||
| 345 | } | ||
| 346 | |||
| 347 | impl<'d> NorFlash for Flash<'d> { | ||
| 348 | const WRITE_SIZE: usize = WORD_SIZE; | ||
| 349 | const ERASE_SIZE: usize = PAGE_SIZE; | ||
| 350 | |||
| 351 | fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||
| 352 | self.blocking_erase(from, to) | ||
| 353 | } | ||
| 354 | |||
| 355 | fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { | ||
| 356 | self.blocking_write(offset, bytes) | ||
| 357 | } | ||
| 358 | } | ||
| 359 | |||
| 360 | /* | ||
| 361 | cfg_if::cfg_if! { | ||
| 362 | if #[cfg(feature = "nightly")] | ||
| 363 | { | ||
| 364 | use embedded_storage_async::nor_flash::{AsyncNorFlash, AsyncReadNorFlash}; | ||
| 365 | use core::future::Future; | ||
| 366 | |||
| 367 | impl<'d> AsyncNorFlash for Flash<'d> { | ||
| 368 | const WRITE_SIZE: usize = <Self as NorFlash>::WRITE_SIZE; | ||
| 369 | const ERASE_SIZE: usize = <Self as NorFlash>::ERASE_SIZE; | ||
| 370 | |||
| 371 | type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a; | ||
| 372 | fn write<'a>(&'a mut self, offset: u32, data: &'a [u8]) -> Self::WriteFuture<'a> { | ||
| 373 | async move { | ||
| 374 | todo!() | ||
| 375 | } | ||
| 376 | } | ||
| 377 | |||
| 378 | type EraseFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a; | ||
| 379 | fn erase<'a>(&'a mut self, from: u32, to: u32) -> Self::EraseFuture<'a> { | ||
| 380 | async move { | ||
| 381 | todo!() | ||
| 382 | } | ||
| 383 | } | ||
| 384 | } | ||
| 385 | |||
| 386 | impl<'d> AsyncReadNorFlash for Flash<'d> { | ||
| 387 | const READ_SIZE: usize = <Self as ReadNorFlash>::READ_SIZE; | ||
| 388 | type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a where Self: 'a; | ||
| 389 | fn read<'a>(&'a mut self, address: u32, data: &'a mut [u8]) -> Self::ReadFuture<'a> { | ||
| 390 | async move { | ||
| 391 | todo!() | ||
| 392 | } | ||
| 393 | } | ||
| 394 | |||
| 395 | fn capacity(&self) -> usize { | ||
| 396 | FLASH_SIZE | ||
| 397 | } | ||
| 398 | } | ||
| 399 | } | ||
| 400 | } | ||
| 401 | */ | ||
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 3417c5d9b..ba426128f 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs | |||
| @@ -50,6 +50,8 @@ pub mod i2c; | |||
| 50 | 50 | ||
| 51 | #[cfg(crc)] | 51 | #[cfg(crc)] |
| 52 | pub mod crc; | 52 | pub mod crc; |
| 53 | #[cfg(any(flash_l0, flash_l1, flash_wl, flash_wb, flash_l4))] | ||
| 54 | pub mod flash; | ||
| 53 | pub mod pwm; | 55 | pub mod pwm; |
| 54 | #[cfg(rng)] | 56 | #[cfg(rng)] |
| 55 | pub mod rng; | 57 | pub mod rng; |
diff --git a/embassy-stm32/src/rcc/wl.rs b/embassy-stm32/src/rcc/wl.rs index fb2dd9986..1f2fbaf87 100644 --- a/embassy-stm32/src/rcc/wl.rs +++ b/embassy-stm32/src/rcc/wl.rs | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | use crate::pac::RCC; | 1 | use crate::pac::{FLASH, RCC}; |
| 2 | use crate::rcc::{set_freqs, Clocks}; | 2 | use crate::rcc::{set_freqs, Clocks}; |
| 3 | use crate::time::U32Ext; | 3 | use crate::time::U32Ext; |
| 4 | 4 | ||
| @@ -15,10 +15,101 @@ pub const HSE32_FREQ: u32 = 32_000_000; | |||
| 15 | /// System clock mux source | 15 | /// System clock mux source |
| 16 | #[derive(Clone, Copy)] | 16 | #[derive(Clone, Copy)] |
| 17 | pub enum ClockSrc { | 17 | pub enum ClockSrc { |
| 18 | MSI(MSIRange), | ||
| 18 | HSE32, | 19 | HSE32, |
| 19 | HSI16, | 20 | HSI16, |
| 20 | } | 21 | } |
| 21 | 22 | ||
| 23 | #[derive(Clone, Copy, PartialOrd, PartialEq)] | ||
| 24 | pub enum MSIRange { | ||
| 25 | /// Around 100 kHz | ||
| 26 | Range0, | ||
| 27 | /// Around 200 kHz | ||
| 28 | Range1, | ||
| 29 | /// Around 400 kHz | ||
| 30 | Range2, | ||
| 31 | /// Around 800 kHz | ||
| 32 | Range3, | ||
| 33 | /// Around 1 MHz | ||
| 34 | Range4, | ||
| 35 | /// Around 2 MHz | ||
| 36 | Range5, | ||
| 37 | /// Around 4 MHz (reset value) | ||
| 38 | Range6, | ||
| 39 | /// Around 8 MHz | ||
| 40 | Range7, | ||
| 41 | /// Around 16 MHz | ||
| 42 | Range8, | ||
| 43 | /// Around 24 MHz | ||
| 44 | Range9, | ||
| 45 | /// Around 32 MHz | ||
| 46 | Range10, | ||
| 47 | /// Around 48 MHz | ||
| 48 | Range11, | ||
| 49 | } | ||
| 50 | |||
| 51 | impl MSIRange { | ||
| 52 | fn freq(&self) -> u32 { | ||
| 53 | match self { | ||
| 54 | MSIRange::Range0 => 100_000, | ||
| 55 | MSIRange::Range1 => 200_000, | ||
| 56 | MSIRange::Range2 => 400_000, | ||
| 57 | MSIRange::Range3 => 800_000, | ||
| 58 | MSIRange::Range4 => 1_000_000, | ||
| 59 | MSIRange::Range5 => 2_000_000, | ||
| 60 | MSIRange::Range6 => 4_000_000, | ||
| 61 | MSIRange::Range7 => 8_000_000, | ||
| 62 | MSIRange::Range8 => 16_000_000, | ||
| 63 | MSIRange::Range9 => 24_000_000, | ||
| 64 | MSIRange::Range10 => 32_000_000, | ||
| 65 | MSIRange::Range11 => 48_000_000, | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | fn vos(&self) -> VoltageScale { | ||
| 70 | if self > &MSIRange::Range8 { | ||
| 71 | VoltageScale::Range1 | ||
| 72 | } else { | ||
| 73 | VoltageScale::Range2 | ||
| 74 | } | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | impl Default for MSIRange { | ||
| 79 | fn default() -> MSIRange { | ||
| 80 | MSIRange::Range6 | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | impl Into<u8> for MSIRange { | ||
| 85 | fn into(self) -> u8 { | ||
| 86 | match self { | ||
| 87 | MSIRange::Range0 => 0b0000, | ||
| 88 | MSIRange::Range1 => 0b0001, | ||
| 89 | MSIRange::Range2 => 0b0010, | ||
| 90 | MSIRange::Range3 => 0b0011, | ||
| 91 | MSIRange::Range4 => 0b0100, | ||
| 92 | MSIRange::Range5 => 0b0101, | ||
| 93 | MSIRange::Range6 => 0b0110, | ||
| 94 | MSIRange::Range7 => 0b0111, | ||
| 95 | MSIRange::Range8 => 0b1000, | ||
| 96 | MSIRange::Range9 => 0b1001, | ||
| 97 | MSIRange::Range10 => 0b1010, | ||
| 98 | MSIRange::Range11 => 0b1011, | ||
| 99 | } | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | /// Voltage Scale | ||
| 104 | /// | ||
| 105 | /// Represents the voltage range feeding the CPU core. The maximum core | ||
| 106 | /// clock frequency depends on this value. | ||
| 107 | #[derive(Copy, Clone, PartialEq)] | ||
| 108 | pub enum VoltageScale { | ||
| 109 | Range1, | ||
| 110 | Range2, | ||
| 111 | } | ||
| 112 | |||
| 22 | /// AHB prescaler | 113 | /// AHB prescaler |
| 23 | #[derive(Clone, Copy, PartialEq)] | 114 | #[derive(Clone, Copy, PartialEq)] |
| 24 | pub enum AHBPrescaler { | 115 | pub enum AHBPrescaler { |
| @@ -85,6 +176,7 @@ impl Into<u8> for AHBPrescaler { | |||
| 85 | pub struct Config { | 176 | pub struct Config { |
| 86 | pub mux: ClockSrc, | 177 | pub mux: ClockSrc, |
| 87 | pub ahb_pre: AHBPrescaler, | 178 | pub ahb_pre: AHBPrescaler, |
| 179 | pub shd_ahb_pre: AHBPrescaler, | ||
| 88 | pub apb1_pre: APBPrescaler, | 180 | pub apb1_pre: APBPrescaler, |
| 89 | pub apb2_pre: APBPrescaler, | 181 | pub apb2_pre: APBPrescaler, |
| 90 | pub enable_lsi: bool, | 182 | pub enable_lsi: bool, |
| @@ -94,8 +186,9 @@ impl Default for Config { | |||
| 94 | #[inline] | 186 | #[inline] |
| 95 | fn default() -> Config { | 187 | fn default() -> Config { |
| 96 | Config { | 188 | Config { |
| 97 | mux: ClockSrc::HSI16, | 189 | mux: ClockSrc::MSI(MSIRange::default()), |
| 98 | ahb_pre: AHBPrescaler::NotDivided, | 190 | ahb_pre: AHBPrescaler::NotDivided, |
| 191 | shd_ahb_pre: AHBPrescaler::NotDivided, | ||
| 99 | apb1_pre: APBPrescaler::NotDivided, | 192 | apb1_pre: APBPrescaler::NotDivided, |
| 100 | apb2_pre: APBPrescaler::NotDivided, | 193 | apb2_pre: APBPrescaler::NotDivided, |
| 101 | enable_lsi: false, | 194 | enable_lsi: false, |
| @@ -104,13 +197,13 @@ impl Default for Config { | |||
| 104 | } | 197 | } |
| 105 | 198 | ||
| 106 | pub(crate) unsafe fn init(config: Config) { | 199 | pub(crate) unsafe fn init(config: Config) { |
| 107 | let (sys_clk, sw) = match config.mux { | 200 | let (sys_clk, sw, vos) = match config.mux { |
| 108 | ClockSrc::HSI16 => { | 201 | ClockSrc::HSI16 => { |
| 109 | // Enable HSI16 | 202 | // Enable HSI16 |
| 110 | RCC.cr().write(|w| w.set_hsion(true)); | 203 | RCC.cr().write(|w| w.set_hsion(true)); |
| 111 | while !RCC.cr().read().hsirdy() {} | 204 | while !RCC.cr().read().hsirdy() {} |
| 112 | 205 | ||
| 113 | (HSI_FREQ, 0x01) | 206 | (HSI_FREQ, 0x01, VoltageScale::Range2) |
| 114 | } | 207 | } |
| 115 | ClockSrc::HSE32 => { | 208 | ClockSrc::HSE32 => { |
| 116 | // Enable HSE32 | 209 | // Enable HSE32 |
| @@ -120,7 +213,17 @@ pub(crate) unsafe fn init(config: Config) { | |||
| 120 | }); | 213 | }); |
| 121 | while !RCC.cr().read().hserdy() {} | 214 | while !RCC.cr().read().hserdy() {} |
| 122 | 215 | ||
| 123 | (HSE32_FREQ, 0x02) | 216 | (HSE32_FREQ, 0x02, VoltageScale::Range1) |
| 217 | } | ||
| 218 | ClockSrc::MSI(range) => { | ||
| 219 | RCC.cr().write(|w| { | ||
| 220 | w.set_msirange(range.into()); | ||
| 221 | w.set_msion(true); | ||
| 222 | }); | ||
| 223 | |||
| 224 | while !RCC.cr().read().msirdy() {} | ||
| 225 | |||
| 226 | (range.freq(), 0x00, range.vos()) | ||
| 124 | } | 227 | } |
| 125 | }; | 228 | }; |
| 126 | 229 | ||
| @@ -135,6 +238,14 @@ pub(crate) unsafe fn init(config: Config) { | |||
| 135 | w.set_ppre2(config.apb2_pre.into()); | 238 | w.set_ppre2(config.apb2_pre.into()); |
| 136 | }); | 239 | }); |
| 137 | 240 | ||
| 241 | RCC.extcfgr().modify(|w| { | ||
| 242 | if config.shd_ahb_pre == AHBPrescaler::NotDivided { | ||
| 243 | w.set_shdhpre(0); | ||
| 244 | } else { | ||
| 245 | w.set_shdhpre(config.shd_ahb_pre.into()); | ||
| 246 | } | ||
| 247 | }); | ||
| 248 | |||
| 138 | let ahb_freq: u32 = match config.ahb_pre { | 249 | let ahb_freq: u32 = match config.ahb_pre { |
| 139 | AHBPrescaler::NotDivided => sys_clk, | 250 | AHBPrescaler::NotDivided => sys_clk, |
| 140 | pre => { | 251 | pre => { |
| @@ -144,6 +255,15 @@ pub(crate) unsafe fn init(config: Config) { | |||
| 144 | } | 255 | } |
| 145 | }; | 256 | }; |
| 146 | 257 | ||
| 258 | let shd_ahb_freq: u32 = match config.shd_ahb_pre { | ||
| 259 | AHBPrescaler::NotDivided => sys_clk, | ||
| 260 | pre => { | ||
| 261 | let pre: u8 = pre.into(); | ||
| 262 | let pre = 1 << (pre as u32 - 7); | ||
| 263 | sys_clk / pre | ||
| 264 | } | ||
| 265 | }; | ||
| 266 | |||
| 147 | let (apb1_freq, apb1_tim_freq) = match config.apb1_pre { | 267 | let (apb1_freq, apb1_tim_freq) = match config.apb1_pre { |
| 148 | APBPrescaler::NotDivided => (ahb_freq, ahb_freq), | 268 | APBPrescaler::NotDivided => (ahb_freq, ahb_freq), |
| 149 | pre => { | 269 | pre => { |
| @@ -164,8 +284,7 @@ pub(crate) unsafe fn init(config: Config) { | |||
| 164 | } | 284 | } |
| 165 | }; | 285 | }; |
| 166 | 286 | ||
| 167 | // TODO: completely untested | 287 | let apb3_freq = shd_ahb_freq; |
| 168 | let apb3_freq = ahb_freq; | ||
| 169 | 288 | ||
| 170 | if config.enable_lsi { | 289 | if config.enable_lsi { |
| 171 | let csr = RCC.csr().read(); | 290 | let csr = RCC.csr().read(); |
| @@ -175,11 +294,32 @@ pub(crate) unsafe fn init(config: Config) { | |||
| 175 | } | 294 | } |
| 176 | } | 295 | } |
| 177 | 296 | ||
| 297 | // Adjust flash latency | ||
| 298 | let flash_clk_src_freq: u32 = shd_ahb_freq; | ||
| 299 | let ws = match vos { | ||
| 300 | VoltageScale::Range1 => match flash_clk_src_freq { | ||
| 301 | 0..=18_000_000 => 0b000, | ||
| 302 | 18_000_001..=36_000_000 => 0b001, | ||
| 303 | _ => 0b010, | ||
| 304 | }, | ||
| 305 | VoltageScale::Range2 => match flash_clk_src_freq { | ||
| 306 | 0..=6_000_000 => 0b000, | ||
| 307 | 6_000_001..=12_000_000 => 0b001, | ||
| 308 | _ => 0b010, | ||
| 309 | }, | ||
| 310 | }; | ||
| 311 | |||
| 312 | FLASH.acr().modify(|w| { | ||
| 313 | w.set_latency(ws); | ||
| 314 | }); | ||
| 315 | |||
| 316 | while FLASH.acr().read().latency() != ws {} | ||
| 317 | |||
| 178 | set_freqs(Clocks { | 318 | set_freqs(Clocks { |
| 179 | sys: sys_clk.hz(), | 319 | sys: sys_clk.hz(), |
| 180 | ahb1: ahb_freq.hz(), | 320 | ahb1: ahb_freq.hz(), |
| 181 | ahb2: ahb_freq.hz(), | 321 | ahb2: ahb_freq.hz(), |
| 182 | ahb3: ahb_freq.hz(), | 322 | ahb3: shd_ahb_freq.hz(), |
| 183 | apb1: apb1_freq.hz(), | 323 | apb1: apb1_freq.hz(), |
| 184 | apb2: apb2_freq.hz(), | 324 | apb2: apb2_freq.hz(), |
| 185 | apb3: apb3_freq.hz(), | 325 | apb3: apb3_freq.hz(), |
diff --git a/examples/boot/Cargo.toml b/examples/boot/Cargo.toml deleted file mode 100644 index 2da659478..000000000 --- a/examples/boot/Cargo.toml +++ /dev/null | |||
| @@ -1,19 +0,0 @@ | |||
| 1 | [package] | ||
| 2 | authors = ["Ulf Lilleengen <[email protected]>"] | ||
| 3 | edition = "2018" | ||
| 4 | name = "embassy-boot-examples" | ||
| 5 | version = "0.1.0" | ||
| 6 | |||
| 7 | [dependencies] | ||
| 8 | embassy = { version = "0.1.0", path = "../../embassy", features = ["nightly"] } | ||
| 9 | embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", "nightly"] } | ||
| 10 | embassy-boot-nrf = { version = "0.1.0", path = "../../embassy-boot/nrf" } | ||
| 11 | embassy-traits = { version = "0.1.0", path = "../../embassy-traits" } | ||
| 12 | |||
| 13 | defmt = { version = "0.3", optional = true } | ||
| 14 | defmt-rtt = { version = "0.3", optional = true } | ||
| 15 | panic-reset = { version = "0.1.1" } | ||
| 16 | embedded-hal = { version = "0.2.6" } | ||
| 17 | |||
| 18 | cortex-m = "0.7.3" | ||
| 19 | 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 000000000..0a5bb8f9d --- /dev/null +++ b/examples/boot/nrf/Cargo.toml | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | [package] | ||
| 2 | authors = ["Ulf Lilleengen <[email protected]>"] | ||
| 3 | edition = "2018" | ||
| 4 | name = "embassy-boot-nrf-examples" | ||
| 5 | version = "0.1.0" | ||
| 6 | |||
| 7 | [dependencies] | ||
| 8 | embassy = { version = "0.1.0", path = "../../../embassy", features = ["nightly"] } | ||
| 9 | embassy-nrf = { version = "0.1.0", path = "../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", "nightly"] } | ||
| 10 | embassy-boot-nrf = { version = "0.1.0", path = "../../../embassy-boot/nrf" } | ||
| 11 | embassy-traits = { version = "0.1.0", path = "../../../embassy-traits" } | ||
| 12 | |||
| 13 | defmt = { version = "0.3", optional = true } | ||
| 14 | defmt-rtt = { version = "0.3", optional = true } | ||
| 15 | panic-reset = { version = "0.1.1" } | ||
| 16 | embedded-hal = { version = "0.2.6" } | ||
| 17 | |||
| 18 | cortex-m = "0.7.3" | ||
| 19 | cortex-m-rt = "0.7.0" | ||
diff --git a/examples/boot/README.md b/examples/boot/nrf/README.md index b97513a9d..b97513a9d 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 cd1a264c4..cd1a264c4 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 dfb72103f..dfb72103f 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 d18b508cc..caf8140d8 100644 --- a/examples/boot/src/bin/a.rs +++ b/examples/boot/nrf/src/bin/a.rs | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | #![feature(generic_associated_types)] | 4 | #![feature(generic_associated_types)] |
| 5 | #![feature(type_alias_impl_trait)] | 5 | #![feature(type_alias_impl_trait)] |
| 6 | 6 | ||
| 7 | use embassy_boot_nrf::updater; | 7 | use embassy_boot_nrf::FirmwareUpdater; |
| 8 | use embassy_nrf::{ | 8 | use embassy_nrf::{ |
| 9 | gpio::{Input, Pull}, | 9 | gpio::{Input, Pull}, |
| 10 | gpio::{Level, Output, OutputDrive}, | 10 | gpio::{Level, Output, OutputDrive}, |
| @@ -26,10 +26,10 @@ async fn main(_s: embassy::executor::Spawner, p: Peripherals) { | |||
| 26 | let nvmc = Nvmc::new(p.NVMC); | 26 | let nvmc = Nvmc::new(p.NVMC); |
| 27 | let mut nvmc = BlockingAsync::new(nvmc); | 27 | let mut nvmc = BlockingAsync::new(nvmc); |
| 28 | 28 | ||
| 29 | let mut updater = FirmwareUpdater::default(); | ||
| 29 | loop { | 30 | loop { |
| 30 | button.wait_for_any_edge().await; | 31 | button.wait_for_any_edge().await; |
| 31 | if button.is_low() { | 32 | if button.is_low() { |
| 32 | let mut updater = updater::new(); | ||
| 33 | let mut offset = 0; | 33 | let mut offset = 0; |
| 34 | for chunk in APP_B.chunks(4096) { | 34 | for chunk in APP_B.chunks(4096) { |
| 35 | let mut buf: [u8; 4096] = [0; 4096]; | 35 | 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 18bb6330c..18bb6330c 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 000000000..840faa62e --- /dev/null +++ b/examples/boot/stm32l0/.cargo/config.toml | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] | ||
| 2 | # replace your chip as listed in `probe-run --list-chips` | ||
| 3 | runner = "probe-run --chip STM32L072CZTx" | ||
| 4 | |||
| 5 | [build] | ||
| 6 | target = "thumbv6m-none-eabi" | ||
diff --git a/examples/boot/stm32l0/Cargo.toml b/examples/boot/stm32l0/Cargo.toml new file mode 100644 index 000000000..2e093d771 --- /dev/null +++ b/examples/boot/stm32l0/Cargo.toml | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | [package] | ||
| 2 | authors = ["Ulf Lilleengen <[email protected]>"] | ||
| 3 | edition = "2018" | ||
| 4 | name = "embassy-boot-stm32l0-examples" | ||
| 5 | version = "0.1.0" | ||
| 6 | |||
| 7 | [dependencies] | ||
| 8 | embassy = { version = "0.1.0", path = "../../../embassy", features = ["nightly"] } | ||
| 9 | embassy-stm32 = { version = "0.1.0", path = "../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32l072cz", "time-driver-any", "exti", "memory-x"] } | ||
| 10 | embassy-boot-stm32 = { version = "0.1.0", path = "../../../embassy-boot/stm32", features = ["flash-128", "invert-erase", "thumbv6"] } | ||
| 11 | embassy-traits = { version = "0.1.0", path = "../../../embassy-traits" } | ||
| 12 | |||
| 13 | defmt = { version = "0.3", optional = true } | ||
| 14 | defmt-rtt = { version = "0.3", optional = true } | ||
| 15 | panic-reset = { version = "0.1.1" } | ||
| 16 | embedded-hal = { version = "0.2.6" } | ||
| 17 | |||
| 18 | cortex-m = "0.7.3" | ||
| 19 | cortex-m-rt = "0.7.0" | ||
| 20 | |||
| 21 | [features] | ||
| 22 | defmt = [ | ||
| 23 | "dep:defmt", | ||
| 24 | "embassy-stm32/defmt", | ||
| 25 | "embassy-boot-stm32/defmt", | ||
| 26 | ] | ||
diff --git a/examples/boot/stm32l0/README.md b/examples/boot/stm32l0/README.md new file mode 100644 index 000000000..9c8660821 --- /dev/null +++ b/examples/boot/stm32l0/README.md | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | # Examples using bootloader | ||
| 2 | |||
| 3 | Example for STM32L0 demonstrating the bootloader. The example consists of application binaries, 'a' | ||
| 4 | which allows you to press a button to start the DFU process, and 'b' which is the updated | ||
| 5 | application. | ||
| 6 | |||
| 7 | |||
| 8 | ## Prerequisites | ||
| 9 | |||
| 10 | * `cargo-binutils` | ||
| 11 | * `cargo-flash` | ||
| 12 | * `embassy-boot-stm32` | ||
| 13 | |||
| 14 | ## Usage | ||
| 15 | |||
| 16 | ``` | ||
| 17 | # Flash bootloader | ||
| 18 | cargo flash --manifest-path ../../../embassy-boot/stm32/Cargo.toml --release --features embassy-stm32/stm32l072cz,flash-128,invert-erase,thumbv6 --chip STM32L072CZTx | ||
| 19 | # Build 'b' | ||
| 20 | cargo build --release --bin b | ||
| 21 | # Generate binary for 'b' | ||
| 22 | cargo objcopy --release --bin b -- -O binary b.bin | ||
| 23 | ``` | ||
| 24 | |||
| 25 | # Flash `a` (which includes b.bin) | ||
| 26 | |||
| 27 | ``` | ||
| 28 | cargo flash --release --bin a --chip STM32L072CZTx | ||
| 29 | ``` | ||
diff --git a/examples/boot/stm32l0/build.rs b/examples/boot/stm32l0/build.rs new file mode 100644 index 000000000..e1da69328 --- /dev/null +++ b/examples/boot/stm32l0/build.rs | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | //! This build script copies the `memory.x` file from the crate root into | ||
| 2 | //! a directory where the linker can always find it at build time. | ||
| 3 | //! For many projects this is optional, as the linker always searches the | ||
| 4 | //! project root directory -- wherever `Cargo.toml` is. However, if you | ||
| 5 | //! are using a workspace or have a more complicated build setup, this | ||
| 6 | //! build script becomes required. Additionally, by requesting that | ||
| 7 | //! Cargo re-run the build script whenever `memory.x` is changed, | ||
| 8 | //! updating `memory.x` ensures a rebuild of the application with the | ||
| 9 | //! new memory settings. | ||
| 10 | |||
| 11 | use std::env; | ||
| 12 | use std::fs::File; | ||
| 13 | use std::io::Write; | ||
| 14 | use std::path::PathBuf; | ||
| 15 | |||
| 16 | fn main() { | ||
| 17 | // Put `memory.x` in our output directory and ensure it's | ||
| 18 | // on the linker search path. | ||
| 19 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); | ||
| 20 | File::create(out.join("memory.x")) | ||
| 21 | .unwrap() | ||
| 22 | .write_all(include_bytes!("memory.x")) | ||
| 23 | .unwrap(); | ||
| 24 | println!("cargo:rustc-link-search={}", out.display()); | ||
| 25 | |||
| 26 | // By default, Cargo will re-run a build script whenever | ||
| 27 | // any file in the project changes. By specifying `memory.x` | ||
| 28 | // here, we ensure the build script is only re-run when | ||
| 29 | // `memory.x` is changed. | ||
| 30 | println!("cargo:rerun-if-changed=memory.x"); | ||
| 31 | |||
| 32 | println!("cargo:rustc-link-arg-bins=--nmagic"); | ||
| 33 | println!("cargo:rustc-link-arg-bins=-Tlink.x"); | ||
| 34 | if env::var("CARGO_FEATURE_DEFMT").is_ok() { | ||
| 35 | println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); | ||
| 36 | } | ||
| 37 | } | ||
diff --git a/examples/boot/stm32l0/memory.x b/examples/boot/stm32l0/memory.x new file mode 100644 index 000000000..fd5bf1a5d --- /dev/null +++ b/examples/boot/stm32l0/memory.x | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | MEMORY | ||
| 2 | { | ||
| 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ | ||
| 4 | BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K | ||
| 5 | BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K | ||
| 6 | FLASH : ORIGIN = 0x08008000, LENGTH = 32K | ||
| 7 | DFU : ORIGIN = 0x08010000, LENGTH = 36K | ||
| 8 | RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 16K | ||
| 9 | } | ||
| 10 | |||
| 11 | __bootloader_state_start = ORIGIN(BOOTLOADER_STATE); | ||
| 12 | __bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE); | ||
| 13 | |||
| 14 | __bootloader_dfu_start = ORIGIN(DFU); | ||
| 15 | __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 000000000..7b9000c91 --- /dev/null +++ b/examples/boot/stm32l0/src/bin/a.rs | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use embassy::time::{Duration, Timer}; | ||
| 6 | use embassy_boot_stm32::FirmwareUpdater; | ||
| 7 | use embassy_stm32::exti::ExtiInput; | ||
| 8 | use embassy_stm32::flash::Flash; | ||
| 9 | use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; | ||
| 10 | use embassy_stm32::Peripherals; | ||
| 11 | use embassy_traits::adapter::BlockingAsync; | ||
| 12 | use panic_reset as _; | ||
| 13 | |||
| 14 | #[cfg(feature = "defmt-rtt")] | ||
| 15 | use defmt_rtt::*; | ||
| 16 | |||
| 17 | static APP_B: &[u8] = include_bytes!("../../b.bin"); | ||
| 18 | |||
| 19 | #[embassy::main] | ||
| 20 | async fn main(_s: embassy::executor::Spawner, p: Peripherals) { | ||
| 21 | let flash = Flash::unlock(p.FLASH); | ||
| 22 | let mut flash = BlockingAsync::new(flash); | ||
| 23 | |||
| 24 | let button = Input::new(p.PB2, Pull::Up); | ||
| 25 | let mut button = ExtiInput::new(button, p.EXTI2); | ||
| 26 | |||
| 27 | let mut led = Output::new(p.PB5, Level::Low, Speed::Low); | ||
| 28 | |||
| 29 | led.set_high(); | ||
| 30 | |||
| 31 | let mut updater = FirmwareUpdater::default(); | ||
| 32 | button.wait_for_falling_edge().await; | ||
| 33 | let mut offset = 0; | ||
| 34 | for chunk in APP_B.chunks(128) { | ||
| 35 | let mut buf: [u8; 128] = [0; 128]; | ||
| 36 | buf[..chunk.len()].copy_from_slice(chunk); | ||
| 37 | updater | ||
| 38 | .write_firmware(offset, &buf, &mut flash, 128) | ||
| 39 | .await | ||
| 40 | .unwrap(); | ||
| 41 | offset += chunk.len(); | ||
| 42 | } | ||
| 43 | |||
| 44 | updater.mark_update(&mut flash).await.unwrap(); | ||
| 45 | led.set_low(); | ||
| 46 | Timer::after(Duration::from_secs(1)).await; | ||
| 47 | cortex_m::peripheral::SCB::sys_reset(); | ||
| 48 | } | ||
diff --git a/examples/boot/stm32l0/src/bin/b.rs b/examples/boot/stm32l0/src/bin/b.rs new file mode 100644 index 000000000..ed774fd70 --- /dev/null +++ b/examples/boot/stm32l0/src/bin/b.rs | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use embassy::executor::Spawner; | ||
| 6 | use embassy::time::{Duration, Timer}; | ||
| 7 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 8 | use embassy_stm32::Peripherals; | ||
| 9 | use panic_reset as _; | ||
| 10 | |||
| 11 | #[cfg(feature = "defmt-rtt")] | ||
| 12 | use defmt_rtt::*; | ||
| 13 | |||
| 14 | #[embassy::main] | ||
| 15 | async fn main(_spawner: Spawner, p: Peripherals) { | ||
| 16 | let mut led = Output::new(p.PB6, Level::High, Speed::Low); | ||
| 17 | |||
| 18 | loop { | ||
| 19 | led.set_high(); | ||
| 20 | Timer::after(Duration::from_millis(500)).await; | ||
| 21 | |||
| 22 | led.set_low(); | ||
| 23 | Timer::after(Duration::from_millis(500)).await; | ||
| 24 | } | ||
| 25 | } | ||
diff --git a/examples/boot/stm32l1/.cargo/config.toml b/examples/boot/stm32l1/.cargo/config.toml new file mode 100644 index 000000000..04985720b --- /dev/null +++ b/examples/boot/stm32l1/.cargo/config.toml | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] | ||
| 2 | # replace your chip as listed in `probe-run --list-chips` | ||
| 3 | runner = "probe-run --chip STM32L151CBxxA" | ||
| 4 | |||
| 5 | [build] | ||
| 6 | target = "thumbv7m-none-eabi" | ||
diff --git a/examples/boot/stm32l1/Cargo.toml b/examples/boot/stm32l1/Cargo.toml new file mode 100644 index 000000000..ec396bef2 --- /dev/null +++ b/examples/boot/stm32l1/Cargo.toml | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | [package] | ||
| 2 | authors = ["Ulf Lilleengen <[email protected]>"] | ||
| 3 | edition = "2018" | ||
| 4 | name = "embassy-boot-stm32l1-examples" | ||
| 5 | version = "0.1.0" | ||
| 6 | |||
| 7 | [dependencies] | ||
| 8 | embassy = { version = "0.1.0", path = "../../../embassy", features = ["nightly"] } | ||
| 9 | embassy-stm32 = { version = "0.1.0", path = "../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32l151cb-a", "time-driver-any", "exti"] } | ||
| 10 | embassy-boot-stm32 = { version = "0.1.0", path = "../../../embassy-boot/stm32", features = ["flash-256", "invert-erase"] } | ||
| 11 | embassy-traits = { version = "0.1.0", path = "../../../embassy-traits" } | ||
| 12 | |||
| 13 | defmt = { version = "0.3", optional = true } | ||
| 14 | defmt-rtt = { version = "0.3", optional = true } | ||
| 15 | panic-reset = { version = "0.1.1" } | ||
| 16 | embedded-hal = { version = "0.2.6" } | ||
| 17 | |||
| 18 | cortex-m = "0.7.3" | ||
| 19 | cortex-m-rt = "0.7.0" | ||
| 20 | |||
| 21 | [features] | ||
| 22 | defmt = [ | ||
| 23 | "dep:defmt", | ||
| 24 | "embassy-stm32/defmt", | ||
| 25 | "embassy-boot-stm32/defmt", | ||
| 26 | ] | ||
diff --git a/examples/boot/stm32l1/README.md b/examples/boot/stm32l1/README.md new file mode 100644 index 000000000..1a9e85a75 --- /dev/null +++ b/examples/boot/stm32l1/README.md | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | # Examples using bootloader | ||
| 2 | |||
| 3 | Example for STM32L1 demonstrating the bootloader. The example consists of application binaries, 'a' | ||
| 4 | which allows you to press a button to start the DFU process, and 'b' which is the updated | ||
| 5 | application. | ||
| 6 | |||
| 7 | |||
| 8 | ## Prerequisites | ||
| 9 | |||
| 10 | * `cargo-binutils` | ||
| 11 | * `cargo-flash` | ||
| 12 | * `embassy-boot-stm32` | ||
| 13 | |||
| 14 | ## Usage | ||
| 15 | |||
| 16 | ``` | ||
| 17 | # Flash bootloader | ||
| 18 | cargo flash --manifest-path ../../../embassy-boot/stm32/Cargo.toml --release --features embassy-stm32/stm32l151cb-a,flash-256,invert-erase --chip STM32L151CBxxA | ||
| 19 | # Build 'b' | ||
| 20 | cargo build --release --bin b | ||
| 21 | # Generate binary for 'b' | ||
| 22 | cargo objcopy --release --bin b -- -O binary b.bin | ||
| 23 | ``` | ||
| 24 | |||
| 25 | # Flash `a` (which includes b.bin) | ||
| 26 | |||
| 27 | ``` | ||
| 28 | cargo flash --release --bin a --chip STM32L151CBxxA | ||
| 29 | ``` | ||
diff --git a/examples/boot/stm32l1/build.rs b/examples/boot/stm32l1/build.rs new file mode 100644 index 000000000..e1da69328 --- /dev/null +++ b/examples/boot/stm32l1/build.rs | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | //! This build script copies the `memory.x` file from the crate root into | ||
| 2 | //! a directory where the linker can always find it at build time. | ||
| 3 | //! For many projects this is optional, as the linker always searches the | ||
| 4 | //! project root directory -- wherever `Cargo.toml` is. However, if you | ||
| 5 | //! are using a workspace or have a more complicated build setup, this | ||
| 6 | //! build script becomes required. Additionally, by requesting that | ||
| 7 | //! Cargo re-run the build script whenever `memory.x` is changed, | ||
| 8 | //! updating `memory.x` ensures a rebuild of the application with the | ||
| 9 | //! new memory settings. | ||
| 10 | |||
| 11 | use std::env; | ||
| 12 | use std::fs::File; | ||
| 13 | use std::io::Write; | ||
| 14 | use std::path::PathBuf; | ||
| 15 | |||
| 16 | fn main() { | ||
| 17 | // Put `memory.x` in our output directory and ensure it's | ||
| 18 | // on the linker search path. | ||
| 19 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); | ||
| 20 | File::create(out.join("memory.x")) | ||
| 21 | .unwrap() | ||
| 22 | .write_all(include_bytes!("memory.x")) | ||
| 23 | .unwrap(); | ||
| 24 | println!("cargo:rustc-link-search={}", out.display()); | ||
| 25 | |||
| 26 | // By default, Cargo will re-run a build script whenever | ||
| 27 | // any file in the project changes. By specifying `memory.x` | ||
| 28 | // here, we ensure the build script is only re-run when | ||
| 29 | // `memory.x` is changed. | ||
| 30 | println!("cargo:rerun-if-changed=memory.x"); | ||
| 31 | |||
| 32 | println!("cargo:rustc-link-arg-bins=--nmagic"); | ||
| 33 | println!("cargo:rustc-link-arg-bins=-Tlink.x"); | ||
| 34 | if env::var("CARGO_FEATURE_DEFMT").is_ok() { | ||
| 35 | println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); | ||
| 36 | } | ||
| 37 | } | ||
diff --git a/examples/boot/stm32l1/memory.x b/examples/boot/stm32l1/memory.x new file mode 100644 index 000000000..fd5bf1a5d --- /dev/null +++ b/examples/boot/stm32l1/memory.x | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | MEMORY | ||
| 2 | { | ||
| 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ | ||
| 4 | BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K | ||
| 5 | BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K | ||
| 6 | FLASH : ORIGIN = 0x08008000, LENGTH = 32K | ||
| 7 | DFU : ORIGIN = 0x08010000, LENGTH = 36K | ||
| 8 | RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 16K | ||
| 9 | } | ||
| 10 | |||
| 11 | __bootloader_state_start = ORIGIN(BOOTLOADER_STATE); | ||
| 12 | __bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE); | ||
| 13 | |||
| 14 | __bootloader_dfu_start = ORIGIN(DFU); | ||
| 15 | __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 000000000..7b9000c91 --- /dev/null +++ b/examples/boot/stm32l1/src/bin/a.rs | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use embassy::time::{Duration, Timer}; | ||
| 6 | use embassy_boot_stm32::FirmwareUpdater; | ||
| 7 | use embassy_stm32::exti::ExtiInput; | ||
| 8 | use embassy_stm32::flash::Flash; | ||
| 9 | use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; | ||
| 10 | use embassy_stm32::Peripherals; | ||
| 11 | use embassy_traits::adapter::BlockingAsync; | ||
| 12 | use panic_reset as _; | ||
| 13 | |||
| 14 | #[cfg(feature = "defmt-rtt")] | ||
| 15 | use defmt_rtt::*; | ||
| 16 | |||
| 17 | static APP_B: &[u8] = include_bytes!("../../b.bin"); | ||
| 18 | |||
| 19 | #[embassy::main] | ||
| 20 | async fn main(_s: embassy::executor::Spawner, p: Peripherals) { | ||
| 21 | let flash = Flash::unlock(p.FLASH); | ||
| 22 | let mut flash = BlockingAsync::new(flash); | ||
| 23 | |||
| 24 | let button = Input::new(p.PB2, Pull::Up); | ||
| 25 | let mut button = ExtiInput::new(button, p.EXTI2); | ||
| 26 | |||
| 27 | let mut led = Output::new(p.PB5, Level::Low, Speed::Low); | ||
| 28 | |||
| 29 | led.set_high(); | ||
| 30 | |||
| 31 | let mut updater = FirmwareUpdater::default(); | ||
| 32 | button.wait_for_falling_edge().await; | ||
| 33 | let mut offset = 0; | ||
| 34 | for chunk in APP_B.chunks(128) { | ||
| 35 | let mut buf: [u8; 128] = [0; 128]; | ||
| 36 | buf[..chunk.len()].copy_from_slice(chunk); | ||
| 37 | updater | ||
| 38 | .write_firmware(offset, &buf, &mut flash, 128) | ||
| 39 | .await | ||
| 40 | .unwrap(); | ||
| 41 | offset += chunk.len(); | ||
| 42 | } | ||
| 43 | |||
| 44 | updater.mark_update(&mut flash).await.unwrap(); | ||
| 45 | led.set_low(); | ||
| 46 | Timer::after(Duration::from_secs(1)).await; | ||
| 47 | cortex_m::peripheral::SCB::sys_reset(); | ||
| 48 | } | ||
diff --git a/examples/boot/stm32l1/src/bin/b.rs b/examples/boot/stm32l1/src/bin/b.rs new file mode 100644 index 000000000..ed774fd70 --- /dev/null +++ b/examples/boot/stm32l1/src/bin/b.rs | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use embassy::executor::Spawner; | ||
| 6 | use embassy::time::{Duration, Timer}; | ||
| 7 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 8 | use embassy_stm32::Peripherals; | ||
| 9 | use panic_reset as _; | ||
| 10 | |||
| 11 | #[cfg(feature = "defmt-rtt")] | ||
| 12 | use defmt_rtt::*; | ||
| 13 | |||
| 14 | #[embassy::main] | ||
| 15 | async fn main(_spawner: Spawner, p: Peripherals) { | ||
| 16 | let mut led = Output::new(p.PB6, Level::High, Speed::Low); | ||
| 17 | |||
| 18 | loop { | ||
| 19 | led.set_high(); | ||
| 20 | Timer::after(Duration::from_millis(500)).await; | ||
| 21 | |||
| 22 | led.set_low(); | ||
| 23 | Timer::after(Duration::from_millis(500)).await; | ||
| 24 | } | ||
| 25 | } | ||
diff --git a/examples/boot/stm32l4/.cargo/config.toml b/examples/boot/stm32l4/.cargo/config.toml new file mode 100644 index 000000000..7b6c4c0ac --- /dev/null +++ b/examples/boot/stm32l4/.cargo/config.toml | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] | ||
| 2 | # replace your chip as listed in `probe-run --list-chips` | ||
| 3 | runner = "probe-run --chip STM32L475VG" | ||
| 4 | |||
| 5 | [build] | ||
| 6 | target = "thumbv7em-none-eabihf" | ||
diff --git a/examples/boot/stm32l4/Cargo.toml b/examples/boot/stm32l4/Cargo.toml new file mode 100644 index 000000000..394f26a12 --- /dev/null +++ b/examples/boot/stm32l4/Cargo.toml | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | [package] | ||
| 2 | authors = ["Ulf Lilleengen <[email protected]>"] | ||
| 3 | edition = "2018" | ||
| 4 | name = "embassy-boot-stm32l4-examples" | ||
| 5 | version = "0.1.0" | ||
| 6 | |||
| 7 | [dependencies] | ||
| 8 | embassy = { version = "0.1.0", path = "../../../embassy", features = ["nightly"] } | ||
| 9 | embassy-stm32 = { version = "0.1.0", path = "../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32l475vg", "time-driver-any", "exti"] } | ||
| 10 | embassy-boot-stm32 = { version = "0.1.0", path = "../../../embassy-boot/stm32", features = ["flash-2k"] } | ||
| 11 | embassy-traits = { version = "0.1.0", path = "../../../embassy-traits" } | ||
| 12 | |||
| 13 | defmt = { version = "0.3", optional = true } | ||
| 14 | defmt-rtt = { version = "0.3", optional = true } | ||
| 15 | panic-reset = { version = "0.1.1" } | ||
| 16 | embedded-hal = { version = "0.2.6" } | ||
| 17 | |||
| 18 | cortex-m = "0.7.3" | ||
| 19 | cortex-m-rt = "0.7.0" | ||
| 20 | |||
| 21 | [features] | ||
| 22 | defmt = [ | ||
| 23 | "dep:defmt", | ||
| 24 | "embassy-stm32/defmt", | ||
| 25 | "embassy-boot-stm32/defmt", | ||
| 26 | ] | ||
diff --git a/examples/boot/stm32l4/README.md b/examples/boot/stm32l4/README.md new file mode 100644 index 000000000..09e09d6ef --- /dev/null +++ b/examples/boot/stm32l4/README.md | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | # Examples using bootloader | ||
| 2 | |||
| 3 | Example for STM32L4 demonstrating the bootloader. The example consists of application binaries, 'a' | ||
| 4 | which allows you to press a button to start the DFU process, and 'b' which is the updated | ||
| 5 | application. | ||
| 6 | |||
| 7 | |||
| 8 | ## Prerequisites | ||
| 9 | |||
| 10 | * `cargo-binutils` | ||
| 11 | * `cargo-flash` | ||
| 12 | * `embassy-boot-stm32` | ||
| 13 | |||
| 14 | ## Usage | ||
| 15 | |||
| 16 | ``` | ||
| 17 | # Flash bootloader | ||
| 18 | cargo flash --manifest-path ../../../embassy-boot/stm32/Cargo.toml --release --features embassy-stm32/stm32l475vg,flash-2k --chip STM32L475VG | ||
| 19 | # Build 'b' | ||
| 20 | cargo build --release --bin b | ||
| 21 | # Generate binary for 'b' | ||
| 22 | cargo objcopy --release --bin b -- -O binary b.bin | ||
| 23 | ``` | ||
| 24 | |||
| 25 | # Flash `a` (which includes b.bin) | ||
| 26 | |||
| 27 | ``` | ||
| 28 | cargo flash --release --bin a --chip STM32L475VG | ||
| 29 | ``` | ||
diff --git a/examples/boot/stm32l4/build.rs b/examples/boot/stm32l4/build.rs new file mode 100644 index 000000000..e1da69328 --- /dev/null +++ b/examples/boot/stm32l4/build.rs | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | //! This build script copies the `memory.x` file from the crate root into | ||
| 2 | //! a directory where the linker can always find it at build time. | ||
| 3 | //! For many projects this is optional, as the linker always searches the | ||
| 4 | //! project root directory -- wherever `Cargo.toml` is. However, if you | ||
| 5 | //! are using a workspace or have a more complicated build setup, this | ||
| 6 | //! build script becomes required. Additionally, by requesting that | ||
| 7 | //! Cargo re-run the build script whenever `memory.x` is changed, | ||
| 8 | //! updating `memory.x` ensures a rebuild of the application with the | ||
| 9 | //! new memory settings. | ||
| 10 | |||
| 11 | use std::env; | ||
| 12 | use std::fs::File; | ||
| 13 | use std::io::Write; | ||
| 14 | use std::path::PathBuf; | ||
| 15 | |||
| 16 | fn main() { | ||
| 17 | // Put `memory.x` in our output directory and ensure it's | ||
| 18 | // on the linker search path. | ||
| 19 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); | ||
| 20 | File::create(out.join("memory.x")) | ||
| 21 | .unwrap() | ||
| 22 | .write_all(include_bytes!("memory.x")) | ||
| 23 | .unwrap(); | ||
| 24 | println!("cargo:rustc-link-search={}", out.display()); | ||
| 25 | |||
| 26 | // By default, Cargo will re-run a build script whenever | ||
| 27 | // any file in the project changes. By specifying `memory.x` | ||
| 28 | // here, we ensure the build script is only re-run when | ||
| 29 | // `memory.x` is changed. | ||
| 30 | println!("cargo:rerun-if-changed=memory.x"); | ||
| 31 | |||
| 32 | println!("cargo:rustc-link-arg-bins=--nmagic"); | ||
| 33 | println!("cargo:rustc-link-arg-bins=-Tlink.x"); | ||
| 34 | if env::var("CARGO_FEATURE_DEFMT").is_ok() { | ||
| 35 | println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); | ||
| 36 | } | ||
| 37 | } | ||
diff --git a/examples/boot/stm32l4/memory.x b/examples/boot/stm32l4/memory.x new file mode 100644 index 000000000..fd5bf1a5d --- /dev/null +++ b/examples/boot/stm32l4/memory.x | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | MEMORY | ||
| 2 | { | ||
| 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ | ||
| 4 | BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K | ||
| 5 | BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K | ||
| 6 | FLASH : ORIGIN = 0x08008000, LENGTH = 32K | ||
| 7 | DFU : ORIGIN = 0x08010000, LENGTH = 36K | ||
| 8 | RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 16K | ||
| 9 | } | ||
| 10 | |||
| 11 | __bootloader_state_start = ORIGIN(BOOTLOADER_STATE); | ||
| 12 | __bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE); | ||
| 13 | |||
| 14 | __bootloader_dfu_start = ORIGIN(DFU); | ||
| 15 | __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 000000000..a5a9e2302 --- /dev/null +++ b/examples/boot/stm32l4/src/bin/a.rs | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use embassy_boot_stm32::FirmwareUpdater; | ||
| 6 | use embassy_stm32::exti::ExtiInput; | ||
| 7 | use embassy_stm32::flash::Flash; | ||
| 8 | use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; | ||
| 9 | use embassy_stm32::Peripherals; | ||
| 10 | use embassy_traits::adapter::BlockingAsync; | ||
| 11 | use panic_reset as _; | ||
| 12 | |||
| 13 | #[cfg(feature = "defmt-rtt")] | ||
| 14 | use defmt_rtt::*; | ||
| 15 | |||
| 16 | static APP_B: &[u8] = include_bytes!("../../b.bin"); | ||
| 17 | |||
| 18 | #[embassy::main] | ||
| 19 | async fn main(_s: embassy::executor::Spawner, p: Peripherals) { | ||
| 20 | let flash = Flash::unlock(p.FLASH); | ||
| 21 | let mut flash = BlockingAsync::new(flash); | ||
| 22 | |||
| 23 | let button = Input::new(p.PC13, Pull::Up); | ||
| 24 | let mut button = ExtiInput::new(button, p.EXTI13); | ||
| 25 | |||
| 26 | let mut led = Output::new(p.PB14, Level::Low, Speed::Low); | ||
| 27 | led.set_high(); | ||
| 28 | |||
| 29 | let mut updater = FirmwareUpdater::default(); | ||
| 30 | button.wait_for_falling_edge().await; | ||
| 31 | let mut offset = 0; | ||
| 32 | for chunk in APP_B.chunks(2048) { | ||
| 33 | let mut buf: [u8; 2048] = [0; 2048]; | ||
| 34 | buf[..chunk.len()].copy_from_slice(chunk); | ||
| 35 | updater | ||
| 36 | .write_firmware(offset, &buf, &mut flash, 2048) | ||
| 37 | .await | ||
| 38 | .unwrap(); | ||
| 39 | offset += chunk.len(); | ||
| 40 | } | ||
| 41 | updater.mark_update(&mut flash).await.unwrap(); | ||
| 42 | led.set_low(); | ||
| 43 | cortex_m::peripheral::SCB::sys_reset(); | ||
| 44 | } | ||
diff --git a/examples/boot/stm32l4/src/bin/b.rs b/examples/boot/stm32l4/src/bin/b.rs new file mode 100644 index 000000000..814275988 --- /dev/null +++ b/examples/boot/stm32l4/src/bin/b.rs | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use embassy::executor::Spawner; | ||
| 6 | use embassy::time::{Duration, Timer}; | ||
| 7 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 8 | use embassy_stm32::Peripherals; | ||
| 9 | use panic_reset as _; | ||
| 10 | |||
| 11 | #[cfg(feature = "defmt-rtt")] | ||
| 12 | use defmt_rtt::*; | ||
| 13 | |||
| 14 | #[embassy::main] | ||
| 15 | async fn main(_spawner: Spawner, p: Peripherals) { | ||
| 16 | let mut led = Output::new(p.PA5, Level::High, Speed::Low); | ||
| 17 | |||
| 18 | loop { | ||
| 19 | led.set_high(); | ||
| 20 | Timer::after(Duration::from_millis(500)).await; | ||
| 21 | |||
| 22 | led.set_low(); | ||
| 23 | Timer::after(Duration::from_millis(500)).await; | ||
| 24 | } | ||
| 25 | } | ||
diff --git a/examples/boot/stm32wl/.cargo/config.toml b/examples/boot/stm32wl/.cargo/config.toml new file mode 100644 index 000000000..60076e06b --- /dev/null +++ b/examples/boot/stm32wl/.cargo/config.toml | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] | ||
| 2 | # replace your chip as listed in `probe-run --list-chips` | ||
| 3 | runner = "probe-run --chip STM32WLE5JCIx" | ||
| 4 | |||
| 5 | [build] | ||
| 6 | target = "thumbv7em-none-eabihf" | ||
diff --git a/examples/boot/stm32wl/Cargo.toml b/examples/boot/stm32wl/Cargo.toml new file mode 100644 index 000000000..9c69f4a65 --- /dev/null +++ b/examples/boot/stm32wl/Cargo.toml | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | [package] | ||
| 2 | authors = ["Ulf Lilleengen <[email protected]>"] | ||
| 3 | edition = "2018" | ||
| 4 | name = "embassy-boot-stm32wl-examples" | ||
| 5 | version = "0.1.0" | ||
| 6 | |||
| 7 | [dependencies] | ||
| 8 | embassy = { version = "0.1.0", path = "../../../embassy", features = ["nightly"] } | ||
| 9 | embassy-stm32 = { version = "0.1.0", path = "../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32wl55jc-cm4", "time-driver-any", "exti"] } | ||
| 10 | embassy-boot-stm32 = { version = "0.1.0", path = "../../../embassy-boot/stm32", features = ["flash-2k"] } | ||
| 11 | embassy-traits = { version = "0.1.0", path = "../../../embassy-traits" } | ||
| 12 | |||
| 13 | defmt = { version = "0.3", optional = true } | ||
| 14 | defmt-rtt = { version = "0.3", optional = true } | ||
| 15 | panic-reset = { version = "0.1.1" } | ||
| 16 | embedded-hal = { version = "0.2.6" } | ||
| 17 | |||
| 18 | cortex-m = "0.7.3" | ||
| 19 | cortex-m-rt = "0.7.0" | ||
| 20 | |||
| 21 | [features] | ||
| 22 | defmt = [ | ||
| 23 | "dep:defmt", | ||
| 24 | "embassy-stm32/defmt", | ||
| 25 | "embassy-boot-stm32/defmt", | ||
| 26 | ] | ||
diff --git a/examples/boot/stm32wl/README.md b/examples/boot/stm32wl/README.md new file mode 100644 index 000000000..a26a23852 --- /dev/null +++ b/examples/boot/stm32wl/README.md | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | # Examples using bootloader | ||
| 2 | |||
| 3 | Example for STM32WL demonstrating the bootloader. The example consists of application binaries, 'a' | ||
| 4 | which allows you to press a button to start the DFU process, and 'b' which is the updated | ||
| 5 | application. | ||
| 6 | |||
| 7 | |||
| 8 | ## Prerequisites | ||
| 9 | |||
| 10 | * `cargo-binutils` | ||
| 11 | * `cargo-flash` | ||
| 12 | * `embassy-boot-stm32` | ||
| 13 | |||
| 14 | ## Usage | ||
| 15 | |||
| 16 | ``` | ||
| 17 | # Flash bootloader | ||
| 18 | cargo flash --manifest-path ../../../embassy-boot/stm32/Cargo.toml --release --features embassy-stm32/stm32wl55jc-cm4,flash-2k --chip STM32WLE5JCIx | ||
| 19 | # Build 'b' | ||
| 20 | cargo build --release --bin b | ||
| 21 | # Generate binary for 'b' | ||
| 22 | cargo objcopy --release --bin b -- -O binary b.bin | ||
| 23 | ``` | ||
| 24 | |||
| 25 | # Flash `a` (which includes b.bin) | ||
| 26 | |||
| 27 | ``` | ||
| 28 | cargo flash --release --bin a --chip STM32WLE5JCIx | ||
| 29 | ``` | ||
diff --git a/examples/boot/stm32wl/build.rs b/examples/boot/stm32wl/build.rs new file mode 100644 index 000000000..e1da69328 --- /dev/null +++ b/examples/boot/stm32wl/build.rs | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | //! This build script copies the `memory.x` file from the crate root into | ||
| 2 | //! a directory where the linker can always find it at build time. | ||
| 3 | //! For many projects this is optional, as the linker always searches the | ||
| 4 | //! project root directory -- wherever `Cargo.toml` is. However, if you | ||
| 5 | //! are using a workspace or have a more complicated build setup, this | ||
| 6 | //! build script becomes required. Additionally, by requesting that | ||
| 7 | //! Cargo re-run the build script whenever `memory.x` is changed, | ||
| 8 | //! updating `memory.x` ensures a rebuild of the application with the | ||
| 9 | //! new memory settings. | ||
| 10 | |||
| 11 | use std::env; | ||
| 12 | use std::fs::File; | ||
| 13 | use std::io::Write; | ||
| 14 | use std::path::PathBuf; | ||
| 15 | |||
| 16 | fn main() { | ||
| 17 | // Put `memory.x` in our output directory and ensure it's | ||
| 18 | // on the linker search path. | ||
| 19 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); | ||
| 20 | File::create(out.join("memory.x")) | ||
| 21 | .unwrap() | ||
| 22 | .write_all(include_bytes!("memory.x")) | ||
| 23 | .unwrap(); | ||
| 24 | println!("cargo:rustc-link-search={}", out.display()); | ||
| 25 | |||
| 26 | // By default, Cargo will re-run a build script whenever | ||
| 27 | // any file in the project changes. By specifying `memory.x` | ||
| 28 | // here, we ensure the build script is only re-run when | ||
| 29 | // `memory.x` is changed. | ||
| 30 | println!("cargo:rerun-if-changed=memory.x"); | ||
| 31 | |||
| 32 | println!("cargo:rustc-link-arg-bins=--nmagic"); | ||
| 33 | println!("cargo:rustc-link-arg-bins=-Tlink.x"); | ||
| 34 | if env::var("CARGO_FEATURE_DEFMT").is_ok() { | ||
| 35 | println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); | ||
| 36 | } | ||
| 37 | } | ||
diff --git a/examples/boot/stm32wl/memory.x b/examples/boot/stm32wl/memory.x new file mode 100644 index 000000000..78dd69c35 --- /dev/null +++ b/examples/boot/stm32wl/memory.x | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | MEMORY | ||
| 2 | { | ||
| 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ | ||
| 4 | BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K | ||
| 5 | BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K | ||
| 6 | FLASH : ORIGIN = 0x08008000, LENGTH = 32K | ||
| 7 | DFU : ORIGIN = 0x08010000, LENGTH = 36K | ||
| 8 | RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 32K | ||
| 9 | } | ||
| 10 | |||
| 11 | __bootloader_state_start = ORIGIN(BOOTLOADER_STATE); | ||
| 12 | __bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE); | ||
| 13 | |||
| 14 | __bootloader_dfu_start = ORIGIN(DFU); | ||
| 15 | __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 000000000..b1f4a4a03 --- /dev/null +++ b/examples/boot/stm32wl/src/bin/a.rs | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use embassy_boot_stm32::FirmwareUpdater; | ||
| 6 | use embassy_stm32::exti::ExtiInput; | ||
| 7 | use embassy_stm32::flash::Flash; | ||
| 8 | use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; | ||
| 9 | use embassy_stm32::Peripherals; | ||
| 10 | use embassy_traits::adapter::BlockingAsync; | ||
| 11 | use panic_reset as _; | ||
| 12 | |||
| 13 | #[cfg(feature = "defmt-rtt")] | ||
| 14 | use defmt_rtt::*; | ||
| 15 | |||
| 16 | static APP_B: &[u8] = include_bytes!("../../b.bin"); | ||
| 17 | |||
| 18 | #[embassy::main] | ||
| 19 | async fn main(_s: embassy::executor::Spawner, p: Peripherals) { | ||
| 20 | let flash = Flash::new(p.FLASH); | ||
| 21 | let mut flash = BlockingAsync::new(flash); | ||
| 22 | |||
| 23 | let button = Input::new(p.PA0, Pull::Up); | ||
| 24 | let mut button = ExtiInput::new(button, p.EXTI0); | ||
| 25 | |||
| 26 | let mut led = Output::new(p.PB9, Level::Low, Speed::Low); | ||
| 27 | |||
| 28 | let mut updater = FirmwareUpdater::default(); | ||
| 29 | button.wait_for_falling_edge().await; | ||
| 30 | let mut offset = 0; | ||
| 31 | for chunk in APP_B.chunks(2048) { | ||
| 32 | let mut buf: [u8; 2048] = [0; 2048]; | ||
| 33 | buf[..chunk.len()].copy_from_slice(chunk); | ||
| 34 | // defmt::info!("Writing chunk at 0x{:x}", offset); | ||
| 35 | updater | ||
| 36 | .write_firmware(offset, &buf, &mut flash, 2048) | ||
| 37 | .await | ||
| 38 | .unwrap(); | ||
| 39 | offset += chunk.len(); | ||
| 40 | } | ||
| 41 | updater.mark_update(&mut flash).await.unwrap(); | ||
| 42 | // defmt::info!("Marked as updated"); | ||
| 43 | led.set_high(); | ||
| 44 | cortex_m::peripheral::SCB::sys_reset(); | ||
| 45 | } | ||
diff --git a/examples/boot/stm32wl/src/bin/b.rs b/examples/boot/stm32wl/src/bin/b.rs new file mode 100644 index 000000000..ffe15b661 --- /dev/null +++ b/examples/boot/stm32wl/src/bin/b.rs | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use embassy::executor::Spawner; | ||
| 6 | use embassy::time::{Duration, Timer}; | ||
| 7 | use embassy_stm32::gpio::{Level, Output, Speed}; | ||
| 8 | use embassy_stm32::Peripherals; | ||
| 9 | use panic_reset as _; | ||
| 10 | |||
| 11 | #[cfg(feature = "defmt-rtt")] | ||
| 12 | use defmt_rtt::*; | ||
| 13 | |||
| 14 | #[embassy::main] | ||
| 15 | async fn main(_spawner: Spawner, p: Peripherals) { | ||
| 16 | let mut led = Output::new(p.PB15, Level::High, Speed::Low); | ||
| 17 | |||
| 18 | loop { | ||
| 19 | led.set_high(); | ||
| 20 | Timer::after(Duration::from_millis(500)).await; | ||
| 21 | |||
| 22 | led.set_low(); | ||
| 23 | Timer::after(Duration::from_millis(500)).await; | ||
| 24 | } | ||
| 25 | } | ||
diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml index 0b21f6742..1cdb2f374 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 | |||
| 21 | defmt = "0.3" | 21 | defmt = "0.3" |
| 22 | defmt-rtt = "0.3" | 22 | defmt-rtt = "0.3" |
| 23 | 23 | ||
| 24 | embedded-storage = "0.3.0" | ||
| 25 | |||
| 24 | cortex-m = "0.7.3" | 26 | cortex-m = "0.7.3" |
| 25 | cortex-m-rt = "0.7.0" | 27 | cortex-m-rt = "0.7.0" |
| 26 | panic-probe = { version = "0.3", features = ["print-defmt"] } | 28 | 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 000000000..0ab7b133b --- /dev/null +++ b/examples/stm32l0/src/bin/flash.rs | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use defmt::{info, unwrap}; | ||
| 6 | use embassy::executor::Spawner; | ||
| 7 | use embassy_stm32::flash::Flash; | ||
| 8 | use embassy_stm32::Peripherals; | ||
| 9 | use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; | ||
| 10 | |||
| 11 | use defmt_rtt as _; // global logger | ||
| 12 | use panic_probe as _; | ||
| 13 | |||
| 14 | #[embassy::main] | ||
| 15 | async fn main(_spawner: Spawner, p: Peripherals) { | ||
| 16 | info!("Hello Flash!"); | ||
| 17 | |||
| 18 | const ADDR: u32 = 0x8026000; | ||
| 19 | |||
| 20 | let mut f = Flash::unlock(p.FLASH); | ||
| 21 | |||
| 22 | info!("Reading..."); | ||
| 23 | let mut buf = [0u8; 8]; | ||
| 24 | unwrap!(f.read(ADDR, &mut buf)); | ||
| 25 | info!("Read: {=[u8]:x}", buf); | ||
| 26 | |||
| 27 | info!("Erasing..."); | ||
| 28 | unwrap!(f.erase(ADDR, ADDR + 128)); | ||
| 29 | |||
| 30 | info!("Reading..."); | ||
| 31 | let mut buf = [0u8; 8]; | ||
| 32 | unwrap!(f.read(ADDR, &mut buf)); | ||
| 33 | info!("Read after erase: {=[u8]:x}", buf); | ||
| 34 | |||
| 35 | info!("Writing..."); | ||
| 36 | unwrap!(f.write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); | ||
| 37 | |||
| 38 | info!("Reading..."); | ||
| 39 | let mut buf = [0u8; 8]; | ||
| 40 | unwrap!(f.read(ADDR, &mut buf)); | ||
| 41 | info!("Read: {=[u8]:x}", buf); | ||
| 42 | assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]); | ||
| 43 | } | ||
diff --git a/examples/stm32l1/Cargo.toml b/examples/stm32l1/Cargo.toml index a7a6c228e..ce6b07729 100644 --- a/examples/stm32l1/Cargo.toml +++ b/examples/stm32l1/Cargo.toml | |||
| @@ -18,3 +18,4 @@ embedded-hal = "0.2.6" | |||
| 18 | panic-probe = { version = "0.3", features = ["print-defmt"] } | 18 | panic-probe = { version = "0.3", features = ["print-defmt"] } |
| 19 | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } | 19 | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } |
| 20 | heapless = { version = "0.7.5", default-features = false } | 20 | heapless = { version = "0.7.5", default-features = false } |
| 21 | 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 000000000..b234289af --- /dev/null +++ b/examples/stm32l1/src/bin/flash.rs | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use defmt::{info, unwrap}; | ||
| 6 | use embassy::executor::Spawner; | ||
| 7 | use embassy_stm32::flash::Flash; | ||
| 8 | use embassy_stm32::Peripherals; | ||
| 9 | use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; | ||
| 10 | |||
| 11 | use defmt_rtt as _; // global logger | ||
| 12 | use panic_probe as _; | ||
| 13 | |||
| 14 | #[embassy::main] | ||
| 15 | async fn main(_spawner: Spawner, p: Peripherals) { | ||
| 16 | info!("Hello Flash!"); | ||
| 17 | |||
| 18 | const ADDR: u32 = 0x8026000; | ||
| 19 | |||
| 20 | let mut f = Flash::unlock(p.FLASH); | ||
| 21 | |||
| 22 | info!("Reading..."); | ||
| 23 | let mut buf = [0u8; 8]; | ||
| 24 | unwrap!(f.read(ADDR, &mut buf)); | ||
| 25 | info!("Read: {=[u8]:x}", buf); | ||
| 26 | |||
| 27 | info!("Erasing..."); | ||
| 28 | unwrap!(f.erase(ADDR, ADDR + 256)); | ||
| 29 | |||
| 30 | info!("Reading..."); | ||
| 31 | let mut buf = [0u8; 8]; | ||
| 32 | unwrap!(f.read(ADDR, &mut buf)); | ||
| 33 | info!("Read after erase: {=[u8]:x}", buf); | ||
| 34 | |||
| 35 | info!("Writing..."); | ||
| 36 | unwrap!(f.write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); | ||
| 37 | |||
| 38 | info!("Reading..."); | ||
| 39 | let mut buf = [0u8; 8]; | ||
| 40 | unwrap!(f.read(ADDR, &mut buf)); | ||
| 41 | info!("Read: {=[u8]:x}", buf); | ||
| 42 | assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]); | ||
| 43 | } | ||
diff --git a/examples/stm32wl/Cargo.toml b/examples/stm32wl/Cargo.toml index 919104b01..01f317039 100644 --- a/examples/stm32wl/Cargo.toml +++ b/examples/stm32wl/Cargo.toml | |||
| @@ -8,7 +8,7 @@ resolver = "2" | |||
| 8 | [dependencies] | 8 | [dependencies] |
| 9 | embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] } | 9 | embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] } |
| 10 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "subghz", "unstable-pac", "exti"] } | 10 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "subghz", "unstable-pac", "exti"] } |
| 11 | embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time"] } | 11 | embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time", "defmt"] } |
| 12 | 12 | ||
| 13 | lorawan-device = { version = "0.7.1", default-features = false, features = ["async"] } | 13 | lorawan-device = { version = "0.7.1", default-features = false, features = ["async"] } |
| 14 | lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"] } | 14 | lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"] } |
| @@ -19,6 +19,7 @@ defmt-rtt = "0.3" | |||
| 19 | cortex-m = "0.7.3" | 19 | cortex-m = "0.7.3" |
| 20 | cortex-m-rt = "0.7.0" | 20 | cortex-m-rt = "0.7.0" |
| 21 | embedded-hal = "0.2.6" | 21 | embedded-hal = "0.2.6" |
| 22 | embedded-storage = "0.3.0" | ||
| 22 | panic-probe = { version = "0.3", features = ["print-defmt"] } | 23 | panic-probe = { version = "0.3", features = ["print-defmt"] } |
| 23 | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } | 24 | futures = { version = "0.3.17", default-features = false, features = ["async-await"] } |
| 24 | heapless = { version = "0.7.5", default-features = false } | 25 | 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 000000000..9e13c702a --- /dev/null +++ b/examples/stm32wl/src/bin/flash.rs | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use defmt::{info, unwrap}; | ||
| 6 | use embassy::executor::Spawner; | ||
| 7 | use embassy_stm32::flash::Flash; | ||
| 8 | use embassy_stm32::Peripherals; | ||
| 9 | use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; | ||
| 10 | |||
| 11 | use defmt_rtt as _; // global logger | ||
| 12 | use panic_probe as _; | ||
| 13 | |||
| 14 | #[embassy::main] | ||
| 15 | async fn main(_spawner: Spawner, p: Peripherals) { | ||
| 16 | info!("Hello Flash!"); | ||
| 17 | |||
| 18 | const ADDR: u32 = 0x8036000; | ||
| 19 | |||
| 20 | let mut f = Flash::unlock(p.FLASH); | ||
| 21 | |||
| 22 | info!("Reading..."); | ||
| 23 | let mut buf = [0u8; 8]; | ||
| 24 | unwrap!(f.read(ADDR, &mut buf)); | ||
| 25 | info!("Read: {=[u8]:x}", buf); | ||
| 26 | |||
| 27 | info!("Erasing..."); | ||
| 28 | unwrap!(f.erase(ADDR, ADDR + 2048)); | ||
| 29 | |||
| 30 | info!("Reading..."); | ||
| 31 | let mut buf = [0u8; 8]; | ||
| 32 | unwrap!(f.read(ADDR, &mut buf)); | ||
| 33 | info!("Read: {=[u8]:x}", buf); | ||
| 34 | |||
| 35 | info!("Writing..."); | ||
| 36 | unwrap!(f.write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); | ||
| 37 | |||
| 38 | info!("Reading..."); | ||
| 39 | let mut buf = [0u8; 8]; | ||
| 40 | unwrap!(f.read(ADDR, &mut buf)); | ||
| 41 | info!("Read: {=[u8]:x}", buf); | ||
| 42 | assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]); | ||
| 43 | } | ||
diff --git a/stm32-data b/stm32-data | |||
| Subproject 472ee98e8fdb11312392e47b16568c9d02fe654 | Subproject 419701c835dd0da3c37d8de02c95115f500dfa6 | ||
