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