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:
Ulf Lilleengen 2022-04-20 13:49:59 +02:00 committed by Ulf Lilleengen
parent 9c283cd445
commit 484e0acc63
59 changed files with 2115 additions and 137 deletions

View file

@ -3,6 +3,7 @@
* xref:hal.adoc[Hardware Abstraction Layer]
** xref:nrf.adoc[nRF]
** xref:stm32.adoc[STM32]
* xref:bootloader.adoc[Bootloader]
* xref:getting_started.adoc[Getting started]
** xref:basic_application.adoc[Basic application]
** xref:layer_by_layer.adoc[Layer by Layer]

View 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.

View file

@ -21,3 +21,8 @@ log = "0.4"
env_logger = "0.9"
rand = "0.8"
futures = { version = "0.3", features = ["executor"] }
[features]
write-4 = []
write-8 = []
invert-erase = []

View file

@ -17,8 +17,23 @@ mod fmt;
use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
use embedded_storage_async::nor_flash::AsyncNorFlash;
pub const BOOT_MAGIC: u32 = 0xD00DF00D;
pub const SWAP_MAGIC: u32 = 0xF00FDAAD;
#[cfg(not(any(feature = "write-4", feature = "write-8",)))]
compile_error!("No write size/alignment specified. Must specify exactly one of the following features: write-4, write-8");
const BOOT_MAGIC: u8 = 0xD0;
const SWAP_MAGIC: u8 = 0xF0;
#[cfg(feature = "write-4")]
const WRITE_SIZE: usize = 4;
#[cfg(feature = "write-8")]
const WRITE_SIZE: usize = 8;
#[cfg(feature = "invert-erase")]
const ERASE_VALUE: u8 = 0x00;
#[cfg(not(feature = "invert-erase"))]
const ERASE_VALUE: u8 = 0xFF;
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -80,12 +95,12 @@ pub trait FlashProvider {
}
/// BootLoader works with any flash implementing embedded_storage and can also work with
/// different page sizes.
/// different page sizes and flash write sizes.
pub struct BootLoader<const PAGE_SIZE: usize> {
// Page with current state of bootloader. The state partition has the following format:
// | Range | Description |
// | 0 - 4 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
// | 4 - N | Progress index used while swapping or reverting |
// | Range | Description |
// | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
// | WRITE_SIZE - N | Progress index used while swapping or reverting |
state: Partition,
// Location of the partition which will be booted from
active: Partition,
@ -100,7 +115,7 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
// DFU partition must have an extra page
assert!(dfu.len() - active.len() >= PAGE_SIZE);
// Ensure we have enough progress pages to store copy progress
assert!(active.len() / PAGE_SIZE >= (state.len() - 4) / PAGE_SIZE);
assert!(active.len() / PAGE_SIZE >= (state.len() - WRITE_SIZE) / PAGE_SIZE);
Self { active, dfu, state }
}
@ -203,15 +218,18 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
if !self.is_swapped(p.state())? {
trace!("Swapping");
self.swap(p)?;
trace!("Swapping done");
} else {
trace!("Reverting");
self.revert(p)?;
// Overwrite magic and reset progress
let fstate = p.state().flash();
fstate.write(self.state.from as u32, &[0, 0, 0, 0])?;
let aligned = Aligned([!ERASE_VALUE; WRITE_SIZE]);
fstate.write(self.state.from as u32, &aligned.0)?;
fstate.erase(self.state.from as u32, self.state.to as u32)?;
fstate.write(self.state.from as u32, &BOOT_MAGIC.to_le_bytes())?;
let aligned = Aligned([BOOT_MAGIC; WRITE_SIZE]);
fstate.write(self.state.from as u32, &aligned.0)?;
}
}
_ => {}
@ -227,12 +245,15 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
}
fn current_progress<P: FlashConfig>(&mut self, p: &mut P) -> Result<usize, BootError> {
let max_index = ((self.state.len() - 4) / 4) - 1;
let max_index = ((self.state.len() - WRITE_SIZE) / WRITE_SIZE) - 1;
let flash = p.flash();
let mut aligned = Aligned([!ERASE_VALUE; WRITE_SIZE]);
for i in 0..max_index {
let mut buf: [u8; 4] = [0; 4];
flash.read((self.state.from + 4 + i * 4) as u32, &mut buf)?;
if buf == [0xFF, 0xFF, 0xFF, 0xFF] {
flash.read(
(self.state.from + WRITE_SIZE + i * WRITE_SIZE) as u32,
&mut aligned.0,
)?;
if aligned.0 == [ERASE_VALUE; WRITE_SIZE] {
return Ok(i);
}
}
@ -241,8 +262,9 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P) -> Result<(), BootError> {
let flash = p.flash();
let w = self.state.from + 4 + idx * 4;
flash.write(w as u32, &[0, 0, 0, 0])?;
let w = self.state.from + WRITE_SIZE + idx * WRITE_SIZE;
let aligned = Aligned([!ERASE_VALUE; WRITE_SIZE]);
flash.write(w as u32, &aligned.0)?;
Ok(())
}
@ -314,21 +336,24 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
fn swap<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError> {
let page_count = self.active.len() / PAGE_SIZE;
// trace!("Page count: {}", page_count);
trace!("Page count: {}", page_count);
for page in 0..page_count {
trace!("COPY PAGE {}", page);
// Copy active page to the 'next' DFU page.
let active_page = self.active_addr(page_count - 1 - page);
let dfu_page = self.dfu_addr(page_count - page);
info!("Copy active {} to dfu {}", active_page, dfu_page);
//trace!("Copy active {} to dfu {}", active_page, dfu_page);
self.copy_page_once_to_dfu(page * 2, active_page, dfu_page, p)?;
// Copy DFU page to the active page
let active_page = self.active_addr(page_count - 1 - page);
let dfu_page = self.dfu_addr(page_count - 1 - page);
info!("Copy dfy {} to active {}", dfu_page, active_page);
//trace!("Copy dfy {} to active {}", dfu_page, active_page);
self.copy_page_once_to_active(page * 2 + 1, dfu_page, active_page, p)?;
}
info!("DONE COPYING");
Ok(())
}
@ -350,13 +375,15 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
}
fn read_state<P: FlashConfig>(&mut self, p: &mut P) -> Result<State, BootError> {
let mut magic: [u8; 4] = [0; 4];
let mut magic: [u8; WRITE_SIZE] = [0; WRITE_SIZE];
let flash = p.flash();
flash.read(self.state.from as u32, &mut magic)?;
match u32::from_le_bytes(magic) {
SWAP_MAGIC => Ok(State::Swap),
_ => Ok(State::Boot),
info!("Read magic: {:x}", magic);
if magic == [SWAP_MAGIC; WRITE_SIZE] {
Ok(State::Swap)
} else {
Ok(State::Boot)
}
}
}
@ -424,6 +451,39 @@ pub struct FirmwareUpdater {
dfu: Partition,
}
#[cfg(feature = "write-4")]
#[repr(align(4))]
pub struct Aligned([u8; 4]);
#[cfg(feature = "write-8")]
#[repr(align(8))]
pub struct Aligned([u8; 8]);
impl Default for FirmwareUpdater {
fn default() -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as usize,
&__bootloader_dfu_end as *const u32 as usize,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as usize,
&__bootloader_state_end as *const u32 as usize,
)
};
FirmwareUpdater::new(dfu, state)
}
}
impl FirmwareUpdater {
pub const fn new(dfu: Partition, state: Partition) -> Self {
Self { dfu, state }
@ -435,53 +495,45 @@ impl FirmwareUpdater {
}
/// Instruct bootloader that DFU should commence at next boot.
/// Must be provided with an aligned buffer to use for reading and writing magic;
pub async fn mark_update<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> {
#[repr(align(4))]
struct Aligned([u8; 4]);
let mut magic = Aligned([0; 4]);
flash.read(self.state.from as u32, &mut magic.0).await?;
let magic = u32::from_le_bytes(magic.0);
if magic != SWAP_MAGIC {
flash
.write(self.state.from as u32, &Aligned([0; 4]).0)
.await?;
flash
.erase(self.state.from as u32, self.state.to as u32)
.await?;
trace!(
"Setting swap magic at {} to 0x{:x}, LE: 0x{:x}",
self.state.from,
&SWAP_MAGIC,
&SWAP_MAGIC.to_le_bytes()
);
flash
.write(self.state.from as u32, &SWAP_MAGIC.to_le_bytes())
.await?;
}
Ok(())
let mut aligned = Aligned([0; WRITE_SIZE]);
self.set_magic(&mut aligned.0, SWAP_MAGIC, flash).await
}
/// Mark firmware boot successfully
pub async fn mark_booted<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> {
#[repr(align(4))]
struct Aligned([u8; 4]);
let mut aligned = Aligned([0; WRITE_SIZE]);
self.set_magic(&mut aligned.0, BOOT_MAGIC, flash).await
}
let mut magic = Aligned([0; 4]);
flash.read(self.state.from as u32, &mut magic.0).await?;
let magic = u32::from_le_bytes(magic.0);
async fn set_magic<F: AsyncNorFlash>(
&mut self,
aligned: &mut [u8],
magic: u8,
flash: &mut F,
) -> Result<(), F::Error> {
flash.read(self.state.from as u32, aligned).await?;
if magic != BOOT_MAGIC {
flash
.write(self.state.from as u32, &Aligned([0; 4]).0)
.await?;
let mut is_set = true;
for b in 0..aligned.len() {
if aligned[b] != magic {
is_set = false;
}
}
if !is_set {
for i in 0..aligned.len() {
aligned[i] = 0;
}
flash.write(self.state.from as u32, aligned).await?;
flash
.erase(self.state.from as u32, self.state.to as u32)
.await?;
flash
.write(self.state.from as u32, &BOOT_MAGIC.to_le_bytes())
.await?;
for i in 0..aligned.len() {
aligned[i] = magic;
}
flash.write(self.state.from as u32, aligned).await?;
}
Ok(())
}
@ -545,6 +597,7 @@ mod tests {
use super::*;
use core::convert::Infallible;
use core::future::Future;
use embedded_storage::nor_flash::ErrorType;
use embedded_storage_async::nor_flash::AsyncReadNorFlash;
use futures::executor::block_on;
@ -552,9 +605,11 @@ mod tests {
const ACTIVE: Partition = Partition::new(4096, 61440);
const DFU: Partition = Partition::new(61440, 122880);
/*
#[test]
fn test_bad_magic() {
let mut flash = MemFlash([0xff; 131072]);
let mut flash = SingleFlashProvider::new(&mut flash);
let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE);
@ -563,11 +618,13 @@ mod tests {
Err(BootError::BadMagic)
);
}
*/
#[test]
fn test_boot_state() {
let mut flash = MemFlash([0xff; 131072]);
flash.0[0..4].copy_from_slice(&BOOT_MAGIC.to_le_bytes());
flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]);
let mut flash = SingleFlashProvider::new(&mut flash);
let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE);
@ -588,19 +645,19 @@ mod tests {
let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE);
let mut updater = FirmwareUpdater::new(DFU, STATE);
for i in (DFU.from..DFU.to).step_by(4) {
let base = i - DFU.from;
let data: [u8; 4] = [
update[base],
update[base + 1],
update[base + 2],
update[base + 3],
];
block_on(updater.write_firmware(i - DFU.from, &data, &mut flash)).unwrap();
let mut offset = 0;
for chunk in update.chunks(4096) {
block_on(updater.write_firmware(offset, &chunk, &mut flash, 4096)).unwrap();
offset += chunk.len();
}
block_on(updater.mark_update(&mut flash)).unwrap();
assert_eq!(State::Swap, bootloader.prepare_boot(&mut flash).unwrap());
assert_eq!(
State::Swap,
bootloader
.prepare_boot(&mut SingleFlashProvider::new(&mut flash))
.unwrap()
);
for i in ACTIVE.from..ACTIVE.to {
assert_eq!(flash.0[i], update[i - ACTIVE.from], "Index {}", i);
@ -612,7 +669,12 @@ mod tests {
}
// Running again should cause a revert
assert_eq!(State::Swap, bootloader.prepare_boot(&mut flash).unwrap());
assert_eq!(
State::Swap,
bootloader
.prepare_boot(&mut SingleFlashProvider::new(&mut flash))
.unwrap()
);
for i in ACTIVE.from..ACTIVE.to {
assert_eq!(flash.0[i], original[i - ACTIVE.from], "Index {}", i);
@ -625,7 +687,12 @@ mod tests {
// Mark as booted
block_on(updater.mark_booted(&mut flash)).unwrap();
assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash).unwrap());
assert_eq!(
State::Boot,
bootloader
.prepare_boot(&mut SingleFlashProvider::new(&mut flash))
.unwrap()
);
}
struct MemFlash([u8; 131072]);
@ -656,9 +723,12 @@ mod tests {
}
}
impl ErrorType for MemFlash {
type Error = Infallible;
}
impl ReadNorFlash for MemFlash {
const READ_SIZE: usize = 4;
type Error = Infallible;
fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> {
let len = buf.len();
@ -673,10 +743,9 @@ mod tests {
impl AsyncReadNorFlash for MemFlash {
const READ_SIZE: usize = 4;
type Error = Infallible;
type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a;
fn read<'a>(&'a mut self, offset: usize, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
fn read<'a>(&'a mut self, offset: u32, buf: &'a mut [u8]) -> Self::ReadFuture<'a> {
async move {
let len = buf.len();
buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]);

View file

@ -13,7 +13,7 @@ defmt-rtt = { version = "0.3", optional = true }
embassy = { path = "../../embassy", default-features = false }
embassy-nrf = { path = "../../embassy-nrf", default-features = false, features = ["nightly"] }
embassy-boot = { path = "../boot", default-features = false }
embassy-boot = { path = "../boot", default-features = false, features = ["write-4"] }
cortex-m = { version = "0.7" }
cortex-m-rt = { version = "0.7" }
embedded-storage = "0.3.0"

View file

@ -4,9 +4,7 @@
mod fmt;
pub use embassy_boot::{
FirmwareUpdater, FlashProvider, Partition, SingleFlashProvider, State, BOOT_MAGIC,
};
pub use embassy_boot::{FirmwareUpdater, FlashProvider, Partition, SingleFlashProvider};
use embassy_nrf::{
nvmc::{Nvmc, PAGE_SIZE},
peripherals::WDT,
@ -184,29 +182,3 @@ impl<'d> ReadNorFlash for WatchdogFlash<'d> {
self.flash.capacity()
}
}
pub mod updater {
use super::*;
pub fn new() -> embassy_boot::FirmwareUpdater {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
static __bootloader_dfu_start: u32;
static __bootloader_dfu_end: u32;
}
let dfu = unsafe {
Partition::new(
&__bootloader_dfu_start as *const u32 as usize,
&__bootloader_dfu_end as *const u32 as usize,
)
};
let state = unsafe {
Partition::new(
&__bootloader_state_start as *const u32 as usize,
&__bootloader_state_end as *const u32 as usize,
)
};
embassy_boot::FirmwareUpdater::new(dfu, state)
}
}

View 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

View 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
```

View 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");
}
}

View 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);

View 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
}
}

View 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)
}
}

View 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();
}
}

View file

@ -42,6 +42,9 @@ embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["un
embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8", optional = true}
embedded-hal-async = { version = "0.1.0-alpha.0", optional = true}
embedded-storage = "0.3.0"
embedded-storage-async = { version = "0.3.0", optional = true }
defmt = { version = "0.3", optional = true }
log = { version = "0.4.14", optional = true }
cortex-m-rt = ">=0.6.15,<0.8"
@ -87,7 +90,7 @@ time-driver-tim12 = ["_time-driver"]
time-driver-tim15 = ["_time-driver"]
# Enable nightly-only features
nightly = ["embassy/nightly", "embedded-hal-1", "embedded-hal-async"]
nightly = ["embassy/nightly", "embedded-hal-1", "embedded-hal-async", "embedded-storage-async"]
# Reexport stm32-metapac at `embassy_stm32::pac`.
# This is unstable because semver-minor (non-breaking) releases of embassy-stm32 may major-bump (breaking) the stm32-metapac version.

View 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
}
}
}
}
*/

View file

@ -50,6 +50,8 @@ pub mod i2c;
#[cfg(crc)]
pub mod crc;
#[cfg(any(flash_l0, flash_l1, flash_wl, flash_wb, flash_l4))]
pub mod flash;
pub mod pwm;
#[cfg(rng)]
pub mod rng;

View file

@ -1,4 +1,4 @@
use crate::pac::RCC;
use crate::pac::{FLASH, RCC};
use crate::rcc::{set_freqs, Clocks};
use crate::time::U32Ext;
@ -15,10 +15,101 @@ pub const HSE32_FREQ: u32 = 32_000_000;
/// System clock mux source
#[derive(Clone, Copy)]
pub enum ClockSrc {
MSI(MSIRange),
HSE32,
HSI16,
}
#[derive(Clone, Copy, PartialOrd, PartialEq)]
pub enum MSIRange {
/// Around 100 kHz
Range0,
/// Around 200 kHz
Range1,
/// Around 400 kHz
Range2,
/// Around 800 kHz
Range3,
/// Around 1 MHz
Range4,
/// Around 2 MHz
Range5,
/// Around 4 MHz (reset value)
Range6,
/// Around 8 MHz
Range7,
/// Around 16 MHz
Range8,
/// Around 24 MHz
Range9,
/// Around 32 MHz
Range10,
/// Around 48 MHz
Range11,
}
impl MSIRange {
fn freq(&self) -> u32 {
match self {
MSIRange::Range0 => 100_000,
MSIRange::Range1 => 200_000,
MSIRange::Range2 => 400_000,
MSIRange::Range3 => 800_000,
MSIRange::Range4 => 1_000_000,
MSIRange::Range5 => 2_000_000,
MSIRange::Range6 => 4_000_000,
MSIRange::Range7 => 8_000_000,
MSIRange::Range8 => 16_000_000,
MSIRange::Range9 => 24_000_000,
MSIRange::Range10 => 32_000_000,
MSIRange::Range11 => 48_000_000,
}
}
fn vos(&self) -> VoltageScale {
if self > &MSIRange::Range8 {
VoltageScale::Range1
} else {
VoltageScale::Range2
}
}
}
impl Default for MSIRange {
fn default() -> MSIRange {
MSIRange::Range6
}
}
impl Into<u8> for MSIRange {
fn into(self) -> u8 {
match self {
MSIRange::Range0 => 0b0000,
MSIRange::Range1 => 0b0001,
MSIRange::Range2 => 0b0010,
MSIRange::Range3 => 0b0011,
MSIRange::Range4 => 0b0100,
MSIRange::Range5 => 0b0101,
MSIRange::Range6 => 0b0110,
MSIRange::Range7 => 0b0111,
MSIRange::Range8 => 0b1000,
MSIRange::Range9 => 0b1001,
MSIRange::Range10 => 0b1010,
MSIRange::Range11 => 0b1011,
}
}
}
/// Voltage Scale
///
/// Represents the voltage range feeding the CPU core. The maximum core
/// clock frequency depends on this value.
#[derive(Copy, Clone, PartialEq)]
pub enum VoltageScale {
Range1,
Range2,
}
/// AHB prescaler
#[derive(Clone, Copy, PartialEq)]
pub enum AHBPrescaler {
@ -85,6 +176,7 @@ impl Into<u8> for AHBPrescaler {
pub struct Config {
pub mux: ClockSrc,
pub ahb_pre: AHBPrescaler,
pub shd_ahb_pre: AHBPrescaler,
pub apb1_pre: APBPrescaler,
pub apb2_pre: APBPrescaler,
pub enable_lsi: bool,
@ -94,8 +186,9 @@ impl Default for Config {
#[inline]
fn default() -> Config {
Config {
mux: ClockSrc::HSI16,
mux: ClockSrc::MSI(MSIRange::default()),
ahb_pre: AHBPrescaler::NotDivided,
shd_ahb_pre: AHBPrescaler::NotDivided,
apb1_pre: APBPrescaler::NotDivided,
apb2_pre: APBPrescaler::NotDivided,
enable_lsi: false,
@ -104,13 +197,13 @@ impl Default for Config {
}
pub(crate) unsafe fn init(config: Config) {
let (sys_clk, sw) = match config.mux {
let (sys_clk, sw, vos) = match config.mux {
ClockSrc::HSI16 => {
// Enable HSI16
RCC.cr().write(|w| w.set_hsion(true));
while !RCC.cr().read().hsirdy() {}
(HSI_FREQ, 0x01)
(HSI_FREQ, 0x01, VoltageScale::Range2)
}
ClockSrc::HSE32 => {
// Enable HSE32
@ -120,7 +213,17 @@ pub(crate) unsafe fn init(config: Config) {
});
while !RCC.cr().read().hserdy() {}
(HSE32_FREQ, 0x02)
(HSE32_FREQ, 0x02, VoltageScale::Range1)
}
ClockSrc::MSI(range) => {
RCC.cr().write(|w| {
w.set_msirange(range.into());
w.set_msion(true);
});
while !RCC.cr().read().msirdy() {}
(range.freq(), 0x00, range.vos())
}
};
@ -135,6 +238,14 @@ pub(crate) unsafe fn init(config: Config) {
w.set_ppre2(config.apb2_pre.into());
});
RCC.extcfgr().modify(|w| {
if config.shd_ahb_pre == AHBPrescaler::NotDivided {
w.set_shdhpre(0);
} else {
w.set_shdhpre(config.shd_ahb_pre.into());
}
});
let ahb_freq: u32 = match config.ahb_pre {
AHBPrescaler::NotDivided => sys_clk,
pre => {
@ -144,6 +255,15 @@ pub(crate) unsafe fn init(config: Config) {
}
};
let shd_ahb_freq: u32 = match config.shd_ahb_pre {
AHBPrescaler::NotDivided => sys_clk,
pre => {
let pre: u8 = pre.into();
let pre = 1 << (pre as u32 - 7);
sys_clk / pre
}
};
let (apb1_freq, apb1_tim_freq) = match config.apb1_pre {
APBPrescaler::NotDivided => (ahb_freq, ahb_freq),
pre => {
@ -164,8 +284,7 @@ pub(crate) unsafe fn init(config: Config) {
}
};
// TODO: completely untested
let apb3_freq = ahb_freq;
let apb3_freq = shd_ahb_freq;
if config.enable_lsi {
let csr = RCC.csr().read();
@ -175,11 +294,32 @@ pub(crate) unsafe fn init(config: Config) {
}
}
// Adjust flash latency
let flash_clk_src_freq: u32 = shd_ahb_freq;
let ws = match vos {
VoltageScale::Range1 => match flash_clk_src_freq {
0..=18_000_000 => 0b000,
18_000_001..=36_000_000 => 0b001,
_ => 0b010,
},
VoltageScale::Range2 => match flash_clk_src_freq {
0..=6_000_000 => 0b000,
6_000_001..=12_000_000 => 0b001,
_ => 0b010,
},
};
FLASH.acr().modify(|w| {
w.set_latency(ws);
});
while FLASH.acr().read().latency() != ws {}
set_freqs(Clocks {
sys: sys_clk.hz(),
ahb1: ahb_freq.hz(),
ahb2: ahb_freq.hz(),
ahb3: ahb_freq.hz(),
ahb3: shd_ahb_freq.hz(),
apb1: apb1_freq.hz(),
apb2: apb2_freq.hz(),
apb3: apb3_freq.hz(),

View file

@ -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"

View 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"

View file

@ -4,7 +4,7 @@
#![feature(generic_associated_types)]
#![feature(type_alias_impl_trait)]
use embassy_boot_nrf::updater;
use embassy_boot_nrf::FirmwareUpdater;
use embassy_nrf::{
gpio::{Input, Pull},
gpio::{Level, Output, OutputDrive},
@ -26,10 +26,10 @@ async fn main(_s: embassy::executor::Spawner, p: Peripherals) {
let nvmc = Nvmc::new(p.NVMC);
let mut nvmc = BlockingAsync::new(nvmc);
let mut updater = FirmwareUpdater::default();
loop {
button.wait_for_any_edge().await;
if button.is_low() {
let mut updater = updater::new();
let mut offset = 0;
for chunk in APP_B.chunks(4096) {
let mut buf: [u8; 4096] = [0; 4096];

View 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"

View 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",
]

View 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
```

View 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");
}
}

View 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);

View 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();
}

View 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;
}
}

View 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"

View 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",
]

View 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
```

View 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");
}
}

View 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);

View 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();
}

View 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;
}
}

View 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"

View 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",
]

View 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
```

View 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");
}
}

View 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);

View 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();
}

View 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;
}
}

View 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"

View 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",
]

View 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
```

View 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");
}
}

View 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);

View 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();
}

View 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;
}
}

View file

@ -21,6 +21,8 @@ lorawan = { version = "0.7.1", default-features = false, features = ["default-cr
defmt = "0.3"
defmt-rtt = "0.3"
embedded-storage = "0.3.0"
cortex-m = "0.7.3"
cortex-m-rt = "0.7.0"
panic-probe = { version = "0.3", features = ["print-defmt"] }

View 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]);
}

View file

@ -18,3 +18,4 @@ embedded-hal = "0.2.6"
panic-probe = { version = "0.3", features = ["print-defmt"] }
futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
heapless = { version = "0.7.5", default-features = false }
embedded-storage = "0.3.0"

View 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]);
}

View file

@ -8,7 +8,7 @@ resolver = "2"
[dependencies]
embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-timestamp-uptime"] }
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "subghz", "unstable-pac", "exti"] }
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time"] }
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time", "defmt"] }
lorawan-device = { version = "0.7.1", default-features = false, features = ["async"] }
lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"] }
@ -19,6 +19,7 @@ defmt-rtt = "0.3"
cortex-m = "0.7.3"
cortex-m-rt = "0.7.0"
embedded-hal = "0.2.6"
embedded-storage = "0.3.0"
panic-probe = { version = "0.3", features = ["print-defmt"] }
futures = { version = "0.3.17", default-features = false, features = ["async-await"] }
heapless = { version = "0.7.5", default-features = false }

View 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