Merge branch 'embassy-rs:main' into usb-dfu-erase-then-write

This commit is contained in:
Badr Bouslikhin 2024-02-11 19:35:42 +01:00 committed by GitHub
commit 2a09996a78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 958 additions and 361 deletions

View File

@ -2,7 +2,7 @@
Embassy is the next-generation framework for embedded applications. Write safe, correct and energy-efficient embedded code faster, using the Rust programming language, its async facilities, and the Embassy libraries.
## <a href="https://embassy.dev/dev/index.html">Documentation</a> - <a href="https://docs.embassy.dev/">API reference</a> - <a href="https://embassy.dev/">Website</a> - <a href="https://matrix.to/#/#embassy-rs:matrix.org">Chat</a>
## <a href="https://embassy.dev/book/dev/index.html">Documentation</a> - <a href="https://docs.embassy.dev/">API reference</a> - <a href="https://embassy.dev/">Website</a> - <a href="https://matrix.to/#/#embassy-rs:matrix.org">Chat</a>
## Rust + async ❤️ embedded
The Rust programming language is blazingly fast and memory-efficient, with no runtime, garbage collector or OS. It catches a wide variety of bugs at compile time, thanks to its full memory- and thread-safety, and expressive type system.

5
ci.sh
View File

@ -23,6 +23,8 @@ cargo batch \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,integrated-timers \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,rtos-trace \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,integrated-timers,rtos-trace \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread,integrated-timers \
--- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt \
@ -194,7 +196,8 @@ cargo batch \
--- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
--- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \
--- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
--- build --release --manifest-path examples/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabihf \
--- build --release --manifest-path examples/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabihf --features embassy-stm32/stm32wb55rg \
--- build --release --manifest-path examples/boot/bootloader/stm32-dual-bank/Cargo.toml --target thumbv7em-none-eabihf --features embassy-stm32/stm32h747xi-cm7 \
--- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \
--- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \

View File

@ -3,8 +3,10 @@
Over time, a couple of best practices have emerged. The following list should serve as a guideline for developers writing embedded software in _Rust_, especially in the context of the _Embassy_ framework.
== Passing Buffers by Reference
It may be tempting to pass arrays or wrappers, like link:https://docs.rs/heapless/latest/heapless/[`heapless::Vec`], to a function or return one just like you would with a `std::Vec`. However, in most embedded applications you don't want to spend ressources on an allocator and end up placing buffers on the stack.
This, however, can easily blow up your stack if you are not careful.
It may be tempting to pass arrays or wrappers, like link:https://docs.rs/heapless/latest/heapless/[`heapless::Vec`],
to a function or return one just like you would with a `std::Vec`. However, in most embedded applications you don't
want to spend resources on an allocator and end up placing buffers on the stack. This, however, can easily blow up
your stack if you are not careful.
Consider the following example:
[,rust]

View File

@ -29,11 +29,10 @@ If you see an error like this:
You are likely missing some features of the `embassy-executor` crate.
For Cortex-M targets, consider making sure that ALL of the following features are active in your `Cargo.toml` for the `embassy-executor` crate:
For Cortex-M targets, check whether ALL of the following features are enabled in your `Cargo.toml` for the `embassy-executor` crate:
* `arch-cortex-m`
* `executor-thread`
* `nightly`
For ESP32, consider using the executors and `#[main]` macro provided by your appropriate link:https://crates.io/crates/esp-hal-common[HAL crate].
@ -125,15 +124,18 @@ You have multiple versions of the same crate in your dependency tree. This means
embassy crates are coming from crates.io, and some from git, each of them pulling in a different set
of dependencies.
To resolve this issue, make sure to only use a single source for all your embassy crates! To do this,
you should patch your dependencies to use git sources using `[patch.crates.io]` and maybe `[patch.'https://github.com/embassy-rs/embassy.git']`.
To resolve this issue, make sure to only use a single source for all your embassy crates!
To do this, you should patch your dependencies to use git sources using `[patch.crates.io]`
and maybe `[patch.'https://github.com/embassy-rs/embassy.git']`.
Example:
[source,toml]
----
[patch.crates-io]
embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd35" }
embassy-time-queue-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd35" }
embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd35" }
# embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd35" }
----
Note that the git revision should match any other embassy patches or git dependencies that you are using!

View File

@ -1,6 +1,17 @@
= Starting a new Embassy project
Once youve successfully xref:getting_started.adoc[run some example projects], the next step is to make a standalone Embassy project. The easiest way to do this is to adapt an example for a similar chip to the one youre targeting.
Once youve successfully xref:getting_started.adoc[run some example projects], the next step is to make a standalone Embassy project.
There are some tools for generating Embassy projects: (WIP)
==== CLI
- link:https://github.com/adinack/cargo-embassy[cargo-embassy] (STM32 and NRF)
==== cargo-generate
- link:https://github.com/lulf/embassy-template[embassy-template] (STM32, NRF, and RP)
- link:https://github.com/bentwire/embassy-rp2040-template[embassy-rp2040-template] (RP)
But if you want to start from scratch:
As an example, lets create a new embassy project from scratch for a STM32G474. The same instructions are applicable for any supported chip with some minor changes.

View File

@ -38,13 +38,18 @@ DEFMT_LOG = "trace" # <- can change to info, warn, or error
== build.rs
This is the build script for your project. It links defmt (what is defmt?) and the `memory.x` file if needed. This file is pretty specific for each chipset, just copy and paste from the corresponding link:https://github.com/embassy-rs/embassy/tree/main/examples[example].
This is the build script for your project. It links defmt (what is link:https://defmt.ferrous-systems.com[defmt]?) and the `memory.x` file if needed. This file is pretty specific for each chipset, just copy and paste from the corresponding link:https://github.com/embassy-rs/embassy/tree/main/examples[example].
== Cargo.toml
This is your manifest file, where you can configure all of the embassy components to use the features you need.
TODO: someone should exhaustively describe every feature for every component!
==== Features
===== Time
- tick-hz-x: Configures the tick rate of `embassy-time`. Higher tick rate means higher precision, and higher CPU wakes.
- defmt-timestamp-uptime: defmt log entries will display the uptime in seconds.
...more to come
== memory.x

View File

@ -49,16 +49,51 @@ pub struct BootLoaderConfig<ACTIVE, DFU, STATE> {
pub state: STATE,
}
impl<'a, FLASH: NorFlash>
impl<'a, ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>
BootLoaderConfig<
BlockingPartition<'a, NoopRawMutex, FLASH>,
BlockingPartition<'a, NoopRawMutex, FLASH>,
BlockingPartition<'a, NoopRawMutex, FLASH>,
BlockingPartition<'a, NoopRawMutex, ACTIVE>,
BlockingPartition<'a, NoopRawMutex, DFU>,
BlockingPartition<'a, NoopRawMutex, STATE>,
>
{
/// Create a bootloader config from the flash and address symbols defined in the linkerfile
/// Constructs a `BootLoaderConfig` instance from flash memory and address symbols defined in the linker file.
///
/// This method initializes `BlockingPartition` instances for the active, DFU (Device Firmware Update),
/// and state partitions, leveraging start and end addresses specified by the linker. These partitions
/// are critical for managing firmware updates, application state, and boot operations within the bootloader.
///
/// # Parameters
/// - `active_flash`: A reference to a mutex-protected `RefCell` for the active partition's flash interface.
/// - `dfu_flash`: A reference to a mutex-protected `RefCell` for the DFU partition's flash interface.
/// - `state_flash`: A reference to a mutex-protected `RefCell` for the state partition's flash interface.
///
/// # Safety
/// The method contains `unsafe` blocks for dereferencing raw pointers that represent the start and end addresses
/// of the bootloader's partitions in flash memory. It is crucial that these addresses are accurately defined
/// in the memory.x file to prevent undefined behavior.
///
/// The caller must ensure that the memory regions defined by these symbols are valid and that the flash memory
/// interfaces provided are compatible with these regions.
///
/// # Returns
/// A `BootLoaderConfig` instance with `BlockingPartition` instances for the active, DFU, and state partitions.
///
/// # Example
/// ```ignore
/// // Assume `active_flash`, `dfu_flash`, and `state_flash` all share the same flash memory interface.
/// let layout = Flash::new_blocking(p.FLASH).into_blocking_regions();
/// let flash = Mutex::new(RefCell::new(layout.bank1_region));
///
/// let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash);
/// // `config` can now be used to create a `BootLoader` instance for managing boot operations.
/// ```
/// Working examples can be found in the bootloader examples folder.
// #[cfg(target_os = "none")]
pub fn from_linkerfile_blocking(flash: &'a Mutex<NoopRawMutex, RefCell<FLASH>>) -> Self {
pub fn from_linkerfile_blocking(
active_flash: &'a Mutex<NoopRawMutex, RefCell<ACTIVE>>,
dfu_flash: &'a Mutex<NoopRawMutex, RefCell<DFU>>,
state_flash: &'a Mutex<NoopRawMutex, RefCell<STATE>>,
) -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
@ -73,21 +108,21 @@ impl<'a, FLASH: NorFlash>
let end = &__bootloader_active_end as *const u32 as u32;
trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end);
BlockingPartition::new(flash, start, end - start)
BlockingPartition::new(active_flash, start, end - start)
};
let dfu = unsafe {
let start = &__bootloader_dfu_start as *const u32 as u32;
let end = &__bootloader_dfu_end as *const u32 as u32;
trace!("DFU: 0x{:x} - 0x{:x}", start, end);
BlockingPartition::new(flash, start, end - start)
BlockingPartition::new(dfu_flash, start, end - start)
};
let state = unsafe {
let start = &__bootloader_state_start as *const u32 as u32;
let end = &__bootloader_state_end as *const u32 as u32;
trace!("STATE: 0x{:x} - 0x{:x}", start, end);
BlockingPartition::new(flash, start, end - start)
BlockingPartition::new(state_flash, start, end - start)
};
Self { active, dfu, state }

View File

@ -16,11 +16,14 @@ pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
}
#[cfg(target_os = "none")]
impl<'a, FLASH: NorFlash>
FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, FLASH>, Partition<'a, NoopRawMutex, FLASH>>
impl<'a, DFU: NorFlash, STATE: NorFlash>
FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, DFU>, Partition<'a, NoopRawMutex, STATE>>
{
/// Create a firmware updater config from the flash and address symbols defined in the linkerfile
pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, FLASH>) -> Self {
pub fn from_linkerfile(
dfu_flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, DFU>,
state_flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, STATE>,
) -> Self {
extern "C" {
static __bootloader_state_start: u32;
static __bootloader_state_end: u32;
@ -33,14 +36,14 @@ impl<'a, FLASH: NorFlash>
let end = &__bootloader_dfu_end as *const u32 as u32;
trace!("DFU: 0x{:x} - 0x{:x}", start, end);
Partition::new(flash, start, end - start)
Partition::new(dfu_flash, start, end - start)
};
let state = unsafe {
let start = &__bootloader_state_start as *const u32 as u32;
let end = &__bootloader_state_end as *const u32 as u32;
trace!("STATE: 0x{:x} - 0x{:x}", start, end);
Partition::new(flash, start, end - start)
Partition::new(state_flash, start, end - start)
};
Self { dfu, state }

View File

@ -16,12 +16,43 @@ pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
}
#[cfg(target_os = "none")]
impl<'a, FLASH: NorFlash>
FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, FLASH>, BlockingPartition<'a, NoopRawMutex, FLASH>>
impl<'a, DFU: NorFlash, STATE: NorFlash>
FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, DFU>, BlockingPartition<'a, NoopRawMutex, STATE>>
{
/// Create a firmware updater config from the flash and address symbols defined in the linkerfile
/// Constructs a `FirmwareUpdaterConfig` instance from flash memory and address symbols defined in the linker file.
///
/// This method initializes `BlockingPartition` instances for the DFU (Device Firmware Update), and state
/// partitions, leveraging start and end addresses specified by the linker. These partitions are critical
/// for managing firmware updates, application state, and boot operations within the bootloader.
///
/// # Parameters
/// - `dfu_flash`: A reference to a mutex-protected `RefCell` for the DFU partition's flash interface.
/// - `state_flash`: A reference to a mutex-protected `RefCell` for the state partition's flash interface.
///
/// # Safety
/// The method contains `unsafe` blocks for dereferencing raw pointers that represent the start and end addresses
/// of the bootloader's partitions in flash memory. It is crucial that these addresses are accurately defined
/// in the memory.x file to prevent undefined behavior.
///
/// The caller must ensure that the memory regions defined by these symbols are valid and that the flash memory
/// interfaces provided are compatible with these regions.
///
/// # Returns
/// A `FirmwareUpdaterConfig` instance with `BlockingPartition` instances for the DFU, and state partitions.
///
/// # Example
/// ```ignore
/// // Assume `dfu_flash`, and `state_flash` share the same flash memory interface.
/// let layout = Flash::new_blocking(p.FLASH).into_blocking_regions();
/// let flash = Mutex::new(RefCell::new(layout.bank1_region));
///
/// let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash);
/// // `config` can now be used to create a `FirmwareUpdater` instance for managing boot operations.
/// ```
/// Working examples can be found in the bootloader examples folder.
pub fn from_linkerfile_blocking(
flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<FLASH>>,
dfu_flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<DFU>>,
state_flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<STATE>>,
) -> Self {
extern "C" {
static __bootloader_state_start: u32;
@ -35,14 +66,14 @@ impl<'a, FLASH: NorFlash>
let end = &__bootloader_dfu_end as *const u32 as u32;
trace!("DFU: 0x{:x} - 0x{:x}", start, end);
BlockingPartition::new(flash, start, end - start)
BlockingPartition::new(dfu_flash, start, end - start)
};
let state = unsafe {
let start = &__bootloader_state_start as *const u32 as u32;
let end = &__bootloader_state_end as *const u32 as u32;
trace!("STATE: 0x{:x} - 0x{:x}", start, end);
BlockingPartition::new(flash, start, end - start)
BlockingPartition::new(state_flash, start, end - start)
};
Self { dfu, state }

View File

@ -8,7 +8,7 @@ use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
/// Firmware updater flash configuration holding the two flashes used by the updater
///
/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use.
/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition
/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile_blocking`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition
/// the provided flash according to symbols defined in the linkerfile.
pub struct FirmwareUpdaterConfig<DFU, STATE> {
/// The dfu flash partition

View File

@ -581,6 +581,15 @@ impl embassy_time_queue_driver::TimerQueue for TimerQueue {
#[cfg(feature = "integrated-timers")]
embassy_time_queue_driver::timer_queue_impl!(static TIMER_QUEUE: TimerQueue = TimerQueue);
#[cfg(all(feature = "rtos-trace", feature = "integrated-timers"))]
const fn gcd(a: u64, b: u64) -> u64 {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
#[cfg(feature = "rtos-trace")]
impl rtos_trace::RtosTraceOSCallbacks for Executor {
fn task_list() {
@ -588,7 +597,8 @@ impl rtos_trace::RtosTraceOSCallbacks for Executor {
}
#[cfg(feature = "integrated-timers")]
fn time() -> u64 {
Instant::now().as_micros()
const GCD_1M: u64 = gcd(embassy_time_driver::TICK_HZ, 1_000_000);
embassy_time_driver::now() * (1_000_000 / GCD_1M) / (embassy_time_driver::TICK_HZ / GCD_1M)
}
#[cfg(not(feature = "integrated-timers"))]
fn time() -> u64 {

View File

@ -13,7 +13,7 @@ pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MO
pub use pac::spim0::config::ORDER_A as BitOrder;
pub use pac::spim0::frequency::FREQUENCY_A as Frequency;
use crate::chip::FORCE_COPY_BUFFER_SIZE;
use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE};
use crate::gpio::sealed::Pin as _;
use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits};
use crate::interrupt::typelevel::Interrupt;
@ -25,9 +25,9 @@ use crate::{interrupt, pac, Peripheral};
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
/// TX buffer was too long.
/// Supplied TX buffer overflows EasyDMA transmit buffer
TxBufferTooLong,
/// RX buffer was too long.
/// Supplied RX buffer overflows EasyDMA receive buffer
RxBufferTooLong,
/// EasyDMA can only read from data memory, read only buffers in flash will fail.
BufferNotInRAM,
@ -220,11 +220,19 @@ impl<'d, T: Instance> Spim<'d, T> {
// Set up the DMA write.
let (ptr, tx_len) = slice_ptr_parts(tx);
if tx_len > EASY_DMA_SIZE {
return Err(Error::TxBufferTooLong);
}
r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(tx_len as _) });
// Set up the DMA read.
let (ptr, rx_len) = slice_ptr_parts_mut(rx);
if rx_len > EASY_DMA_SIZE {
return Err(Error::RxBufferTooLong);
}
r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(rx_len as _) });

View File

@ -11,7 +11,7 @@ use embassy_hal_internal::{into_ref, PeripheralRef};
pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3};
pub use pac::spis0::config::ORDER_A as BitOrder;
use crate::chip::FORCE_COPY_BUFFER_SIZE;
use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE};
use crate::gpio::sealed::Pin as _;
use crate::gpio::{self, AnyPin, Pin as GpioPin};
use crate::interrupt::typelevel::Interrupt;
@ -227,11 +227,17 @@ impl<'d, T: Instance> Spis<'d, T> {
// Set up the DMA write.
let (ptr, len) = slice_ptr_parts(tx);
if len > EASY_DMA_SIZE {
return Err(Error::TxBufferTooLong);
}
r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) });
// Set up the DMA read.
let (ptr, len) = slice_ptr_parts_mut(rx);
if len > EASY_DMA_SIZE {
return Err(Error::RxBufferTooLong);
}
r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) });

View File

@ -68,7 +68,7 @@ rand_core = "0.6.3"
sdio-host = "0.5.0"
critical-section = "1.1"
#stm32-metapac = { version = "15" }
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-e702b4d564bc9e3c8a5c0141a11efdc5f7ee8f24" }
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-5bf4bec597bdf0d85402789b40c3a37b0f5a8e76" }
vcell = "0.1.3"
bxcan = "0.7.0"
nb = "1.0.0"
@ -89,7 +89,7 @@ critical-section = { version = "1.1", features = ["std"] }
proc-macro2 = "1.0.36"
quote = "1.0.15"
#stm32-metapac = { version = "15", default-features = false, features = ["metadata"]}
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-e702b4d564bc9e3c8a5c0141a11efdc5f7ee8f24", default-features = false, features = ["metadata"]}
stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-5bf4bec597bdf0d85402789b40c3a37b0f5a8e76", default-features = false, features = ["metadata"]}
[features]

View File

@ -8,6 +8,7 @@
#[cfg_attr(adc_f3, path = "f3.rs")]
#[cfg_attr(adc_f3_v1_1, path = "f3_v1_1.rs")]
#[cfg_attr(adc_v1, path = "v1.rs")]
#[cfg_attr(adc_l0, path = "v1.rs")]
#[cfg_attr(adc_v2, path = "v2.rs")]
#[cfg_attr(any(adc_v3, adc_g0), path = "v3.rs")]
#[cfg_attr(adc_v4, path = "v4.rs")]
@ -36,15 +37,15 @@ pub struct Adc<'d, T: Instance> {
}
pub(crate) mod sealed {
#[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))]
#[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))]
use embassy_sync::waitqueue::AtomicWaker;
#[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))]
#[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))]
pub struct State {
pub waker: AtomicWaker,
}
#[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))]
#[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))]
impl State {
pub const fn new() -> Self {
Self {
@ -59,14 +60,14 @@ pub(crate) mod sealed {
pub trait Instance: InterruptableInstance {
fn regs() -> crate::pac::adc::Adc;
#[cfg(not(any(adc_f1, adc_v1, adc_f3_v2, adc_f3_v1_1, adc_g0)))]
#[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0)))]
fn common_regs() -> crate::pac::adccommon::AdcCommon;
#[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))]
#[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))]
fn state() -> &'static State;
}
pub trait AdcPin<T: Instance> {
#[cfg(any(adc_v1, adc_v2))]
#[cfg(any(adc_v1, adc_l0, adc_v2))]
fn set_as_analog(&mut self) {}
fn channel(&self) -> u8;
@ -78,10 +79,10 @@ pub(crate) mod sealed {
}
/// ADC instance.
#[cfg(not(any(adc_f1, adc_v1, adc_v2, adc_v3, adc_v4, adc_f3, adc_f3_v1_1, adc_g0)))]
#[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_v2, adc_v3, adc_v4, adc_f3, adc_f3_v1_1, adc_g0)))]
pub trait Instance: sealed::Instance + crate::Peripheral<P = Self> {}
/// ADC instance.
#[cfg(any(adc_f1, adc_v1, adc_v2, adc_v3, adc_v4, adc_f3, adc_f3_v1_1, adc_g0))]
#[cfg(any(adc_f1, adc_v1, adc_l0, adc_v2, adc_v3, adc_v4, adc_f3, adc_f3_v1_1, adc_g0))]
pub trait Instance: sealed::Instance + crate::Peripheral<P = Self> + crate::rcc::RccPeripheral {}
/// ADC pin.
@ -96,12 +97,12 @@ foreach_adc!(
crate::pac::$inst
}
#[cfg(not(any(adc_f1, adc_v1, adc_f3_v2, adc_f3_v1_1, adc_g0)))]
#[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0)))]
fn common_regs() -> crate::pac::adccommon::AdcCommon {
return crate::pac::$common_inst
}
#[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))]
#[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))]
fn state() -> &'static sealed::State {
static STATE: sealed::State = sealed::State::new();
&STATE
@ -125,7 +126,7 @@ macro_rules! impl_adc_pin {
impl crate::adc::AdcPin<peripherals::$inst> for crate::peripherals::$pin {}
impl crate::adc::sealed::AdcPin<peripherals::$inst> for crate::peripherals::$pin {
#[cfg(any(adc_v1, adc_v2))]
#[cfg(any(adc_v1, adc_l0, adc_v2))]
fn set_as_analog(&mut self) {
<Self as crate::gpio::sealed::Pin>::set_as_analog(self);
}

View File

@ -1,6 +1,6 @@
/// ADC resolution
#[allow(missing_docs)]
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3, adc_f3_v1_1))]
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_l0, adc_g0, adc_f3, adc_f3_v1_1))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Resolution {
@ -25,7 +25,7 @@ pub enum Resolution {
impl Default for Resolution {
fn default() -> Self {
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3, adc_f3_v1_1))]
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_l0, adc_g0, adc_f3, adc_f3_v1_1))]
{
Self::TwelveBit
}
@ -46,7 +46,7 @@ impl From<Resolution> for crate::pac::adc::vals::Res {
Resolution::TwelveBit => crate::pac::adc::vals::Res::TWELVEBIT,
Resolution::TenBit => crate::pac::adc::vals::Res::TENBIT,
Resolution::EightBit => crate::pac::adc::vals::Res::EIGHTBIT,
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3, adc_f3_v1_1))]
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_l0, adc_g0, adc_f3, adc_f3_v1_1))]
Resolution::SixBit => crate::pac::adc::vals::Res::SIXBIT,
}
}
@ -65,7 +65,7 @@ impl Resolution {
Resolution::TwelveBit => (1 << 12) - 1,
Resolution::TenBit => (1 << 10) - 1,
Resolution::EightBit => (1 << 8) - 1,
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3, adc_f3_v1_1))]
#[cfg(any(adc_v1, adc_v2, adc_v3, adc_l0, adc_g0, adc_f3, adc_f3_v1_1))]
Resolution::SixBit => (1 << 6) - 1,
}
}

View File

@ -83,7 +83,7 @@ impl_sample_time!(
)
);
#[cfg(adc_g0)]
#[cfg(any(adc_l0, adc_g0))]
impl_sample_time!(
"1.5",
Cycles1_5,

View File

@ -4,6 +4,8 @@ use core::task::Poll;
use embassy_hal_internal::into_ref;
use embedded_hal_02::blocking::delay::DelayUs;
#[cfg(adc_l0)]
use stm32_metapac::adc::vals::Ckmode;
use crate::adc::{Adc, AdcPin, Instance, Resolution, SampleTime};
use crate::interrupt::typelevel::Interrupt;
@ -30,8 +32,13 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
}
}
#[cfg(not(adc_l0))]
pub struct Vbat;
#[cfg(not(adc_l0))]
impl AdcPin<ADC> for Vbat {}
#[cfg(not(adc_l0))]
impl super::sealed::AdcPin<ADC> for Vbat {
fn channel(&self) -> u8 {
18
@ -69,9 +76,18 @@ impl<'d, T: Instance> Adc<'d, T> {
// tstab = 14 * 1/fadc
delay.delay_us(1);
// set default PCKL/2 on L0s because HSI is disabled in the default clock config
#[cfg(adc_l0)]
T::regs().cfgr2().modify(|reg| reg.set_ckmode(Ckmode::PCLK_DIV2));
// A.7.1 ADC calibration code example
T::regs().cfgr1().modify(|reg| reg.set_dmaen(false));
T::regs().cr().modify(|reg| reg.set_adcal(true));
#[cfg(adc_l0)]
while !T::regs().isr().read().eocal() {}
#[cfg(not(adc_l0))]
while T::regs().cr().read().adcal() {}
// A.7.2 ADC enable sequence code example
@ -97,6 +113,7 @@ impl<'d, T: Instance> Adc<'d, T> {
}
}
#[cfg(not(adc_l0))]
pub fn enable_vbat(&self, _delay: &mut impl DelayUs<u32>) -> Vbat {
// SMP must be ≥ 56 ADC clock cycles when using HSI14.
//
@ -133,6 +150,12 @@ impl<'d, T: Instance> Adc<'d, T> {
T::regs().cfgr1().modify(|reg| reg.set_res(resolution.into()));
}
#[cfg(adc_l0)]
pub fn set_ckmode(&mut self, ckmode: Ckmode) {
// set ADC clock mode
T::regs().cfgr2().modify(|reg| reg.set_ckmode(ckmode));
}
pub async fn read(&mut self, pin: &mut impl AdcPin<T>) -> u16 {
let channel = pin.channel();
pin.set_as_analog();

View File

@ -664,6 +664,13 @@ impl<'a, C: Channel, W: Word> WritableRingBuffer<'a, C, W> {
self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow()));
}
/// Write elements directly to the raw buffer.
/// This can be used to fill the buffer before starting the DMA transfer.
#[allow(dead_code)]
pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> {
self.ringbuf.write_immediate(buf)
}
/// Write elements to the ring buffer
/// Return a tuple of the length written and the length remaining in the buffer
pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> {

View File

@ -934,6 +934,13 @@ impl<'a, C: Channel, W: Word> WritableRingBuffer<'a, C, W> {
self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow()));
}
/// Write elements directly to the raw buffer.
/// This can be used to fill the buffer before starting the DMA transfer.
#[allow(dead_code)]
pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> {
self.ringbuf.write_immediate(buf)
}
/// Write elements from the ring buffer
/// Return a tuple of the length written and the length remaining in the buffer
pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> {

View File

@ -37,6 +37,7 @@ pub struct ReadableDmaRingBuffer<'a, W: Word> {
}
#[derive(Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct OverrunError;
pub trait DmaCtrl {
@ -263,6 +264,17 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> {
self.cap() - dma.get_remaining_transfers()
}
/// Write elements directly to the buffer. This must be done before the DMA is started
/// or after the buffer has been cleared using `clear()`.
pub fn write_immediate(&mut self, buffer: &[W]) -> Result<(usize, usize), OverrunError> {
if self.end != 0 {
return Err(OverrunError);
}
let written = self.copy_from(buffer, 0..self.cap());
self.end = written % self.cap();
Ok((written, self.cap() - written))
}
/// Write an exact number of elements to the ringbuffer.
pub async fn write_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &[W]) -> Result<usize, OverrunError> {
let mut written_data = 0;

View File

@ -658,6 +658,7 @@ pub(crate) unsafe fn init(config: Config) {
#[cfg(stm32h5)]
audioclk: None,
per: None,
i2s_ckin: None,
);
}

View File

@ -477,6 +477,7 @@ pub(crate) unsafe fn init(config: Config) {
pll3_p: None,
pll3_q: None,
pll3_r: None,
iclk: None,
);
}

View File

@ -14,7 +14,7 @@ use crate::pac::timer::vals;
use crate::rcc::sealed::RccPeripheral;
#[cfg(feature = "low-power")]
use crate::rtc::Rtc;
use crate::timer::sealed::{Basic16bitInstance as BasicInstance, GeneralPurpose16bitInstance as Instance};
use crate::timer::sealed::{CoreInstance, GeneralPurpose16bitInstance as Instance};
use crate::{interrupt, peripherals};
// NOTE regarding ALARM_COUNT:
@ -234,8 +234,8 @@ impl RtcDriver {
w.set_ccie(0, true);
});
<T as BasicInstance>::Interrupt::unpend();
unsafe { <T as BasicInstance>::Interrupt::enable() };
<T as CoreInstance>::Interrupt::unpend();
unsafe { <T as CoreInstance>::Interrupt::enable() };
r.cr1().modify(|w| w.set_cen(true));
}
@ -251,7 +251,7 @@ impl RtcDriver {
// Clear all interrupt flags. Bits in SR are "write 0 to clear", so write the bitwise NOT.
// Other approaches such as writing all zeros, or RMWing won't work, they can
// miss interrupts.
r.sr().write_value(regs::SrGp(!sr.0));
r.sr().write_value(regs::SrGp16(!sr.0));
// Overflow
if sr.uif() {

View File

@ -23,7 +23,7 @@ pub struct ComplementaryPwmPin<'d, T, C> {
macro_rules! complementary_channel_impl {
($new_chx:ident, $channel:ident, $pin_trait:ident) => {
impl<'d, T: CaptureCompare16bitInstance> ComplementaryPwmPin<'d, T, $channel> {
impl<'d, T: ComplementaryCaptureCompare16bitInstance> ComplementaryPwmPin<'d, T, $channel> {
#[doc = concat!("Create a new ", stringify!($channel), " complementary PWM pin instance.")]
pub fn $new_chx(pin: impl Peripheral<P = impl $pin_trait<T>> + 'd, output_type: OutputType) -> Self {
into_ref!(pin);
@ -84,14 +84,13 @@ impl<'d, T: ComplementaryCaptureCompare16bitInstance> ComplementaryPwm<'d, T> {
this.inner.enable_outputs();
this.inner
.set_output_compare_mode(Channel::Ch1, OutputCompareMode::PwmMode1);
this.inner
.set_output_compare_mode(Channel::Ch2, OutputCompareMode::PwmMode1);
this.inner
.set_output_compare_mode(Channel::Ch3, OutputCompareMode::PwmMode1);
this.inner
.set_output_compare_mode(Channel::Ch4, OutputCompareMode::PwmMode1);
[Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4]
.iter()
.for_each(|&channel| {
this.inner.set_output_compare_mode(channel, OutputCompareMode::PwmMode1);
this.inner.set_output_compare_preload(channel, true);
});
this
}

View File

@ -1,5 +1,37 @@
//! Timers, PWM, quadrature decoder.
//!
//! Timer inheritance
//!
// sealed:
//
// Core -------------------------> 1CH -------------------------> 1CH_CMP
// | | ^ |
// +--> Basic_NoCr2 --> Basic +--> 2CH --> GP16 --> GP32 | +--> 2CH_CMP --> ADV
// | | | ^ | | ^ ^
// | | +------|--|--------------|-----------+ |
// | +--------------------+ +--------------|-----------|---------+
// | | | |
// | +--------------------------------------|-----------+
// +----------------------------------------------------+
//! ```text
//! BasicInstance --> CaptureCompare16bitInstance --+--> ComplementaryCaptureCompare16bitInstance
//! |
//! +--> CaptureCompare32bitInstance
//! ```
//!
//! Mapping:
//!
//! | trait | timer |
//! | :----------------------------------------: | ------------------------------------------------------------------------------------------------- |
//! | [BasicInstance] | Basic Timer |
//! | [CaptureCompare16bitInstance] | 1-channel Timer, 2-channel Timer, General Purpose 16-bit Timer |
//! | [CaptureCompare32bitInstance] | General Purpose 32-bit Timer |
//! | [ComplementaryCaptureCompare16bitInstance] | 1-channel with one complentary Timer, 2-channel with one complentary Timer, Advance Control Timer |
#[cfg(not(stm32l0))]
pub mod complementary_pwm;
pub mod qei;
pub mod simple_pwm;
@ -19,32 +51,32 @@ pub mod low_level {
pub(crate) mod sealed {
use super::*;
/// Basic 16-bit timer instance.
pub trait Basic16bitInstance: RccPeripheral {
/// Virtual Core 16-bit timer instance.
pub trait CoreInstance: RccPeripheral {
/// Interrupt for this timer.
type Interrupt: interrupt::typelevel::Interrupt;
/// Get access to the basic 16bit timer registers.
/// Get access to the virutal core 16bit timer registers.
///
/// Note: This works even if the timer is more capable, because registers
/// for the less capable timers are a subset. This allows writing a driver
/// for a given set of capabilities, and having it transparently work with
/// more capable timers.
fn regs() -> crate::pac::timer::TimBasic;
fn regs_core() -> crate::pac::timer::TimCore;
/// Start the timer.
fn start(&mut self) {
Self::regs().cr1().modify(|r| r.set_cen(true));
Self::regs_core().cr1().modify(|r| r.set_cen(true));
}
/// Stop the timer.
fn stop(&mut self) {
Self::regs().cr1().modify(|r| r.set_cen(false));
Self::regs_core().cr1().modify(|r| r.set_cen(false));
}
/// Reset the counter value to 0
fn reset(&mut self) {
Self::regs().cnt().write(|r| r.set_cnt(0));
Self::regs_core().cnt().write(|r| r.set_cnt(0));
}
/// Set the frequency of how many times per second the timer counts up to the max value or down to 0.
@ -64,7 +96,7 @@ pub(crate) mod sealed {
// the timer counts `0..=arr`, we want it to count `0..divide_by`
let arr = unwrap!(u16::try_from(divide_by - 1));
let regs = Self::regs();
let regs = Self::regs_core();
regs.psc().write(|r| r.set_psc(psc));
regs.arr().write(|r| r.set_arr(arr));
@ -77,7 +109,7 @@ pub(crate) mod sealed {
///
/// Returns whether the update interrupt flag was set.
fn clear_update_interrupt(&mut self) -> bool {
let regs = Self::regs();
let regs = Self::regs_core();
let sr = regs.sr().read();
if sr.uif() {
regs.sr().modify(|r| {
@ -91,29 +123,19 @@ pub(crate) mod sealed {
/// Enable/disable the update interrupt.
fn enable_update_interrupt(&mut self, enable: bool) {
Self::regs().dier().modify(|r| r.set_uie(enable));
}
/// Enable/disable the update dma.
fn enable_update_dma(&mut self, enable: bool) {
Self::regs().dier().modify(|r| r.set_ude(enable));
}
/// Get the update dma enable/disable state.
fn get_update_dma_state(&self) -> bool {
Self::regs().dier().read().ude()
Self::regs_core().dier().modify(|r| r.set_uie(enable));
}
/// Enable/disable autoreload preload.
fn set_autoreload_preload(&mut self, enable: bool) {
Self::regs().cr1().modify(|r| r.set_arpe(enable));
Self::regs_core().cr1().modify(|r| r.set_arpe(enable));
}
/// Get the timer frequency.
fn get_frequency(&self) -> Hertz {
let timer_f = Self::frequency();
let regs = Self::regs();
let regs = Self::regs_core();
let arr = regs.arr().read().arr();
let psc = regs.psc().read().psc();
@ -121,8 +143,72 @@ pub(crate) mod sealed {
}
}
/// Virtual Basic without CR2 16-bit timer instance.
pub trait BasicNoCr2Instance: CoreInstance {
/// Get access to the Baisc 16bit timer registers.
///
/// Note: This works even if the timer is more capable, because registers
/// for the less capable timers are a subset. This allows writing a driver
/// for a given set of capabilities, and having it transparently work with
/// more capable timers.
fn regs_basic_no_cr2() -> crate::pac::timer::TimBasicNoCr2;
/// Enable/disable the update dma.
fn enable_update_dma(&mut self, enable: bool) {
Self::regs_basic_no_cr2().dier().modify(|r| r.set_ude(enable));
}
/// Get the update dma enable/disable state.
fn get_update_dma_state(&self) -> bool {
Self::regs_basic_no_cr2().dier().read().ude()
}
}
/// Basic 16-bit timer instance.
pub trait BasicInstance: BasicNoCr2Instance {
/// Get access to the Baisc 16bit timer registers.
///
/// Note: This works even if the timer is more capable, because registers
/// for the less capable timers are a subset. This allows writing a driver
/// for a given set of capabilities, and having it transparently work with
/// more capable timers.
fn regs_basic() -> crate::pac::timer::TimBasic;
}
/// Gneral-purpose 1 channel 16-bit timer instance.
pub trait GeneralPurpose1ChannelInstance: CoreInstance {
/// Get access to the general purpose 1 channel 16bit timer registers.
///
/// Note: This works even if the timer is more capable, because registers
/// for the less capable timers are a subset. This allows writing a driver
/// for a given set of capabilities, and having it transparently work with
/// more capable timers.
fn regs_1ch() -> crate::pac::timer::Tim1ch;
/// Set clock divider.
fn set_clock_division(&mut self, ckd: vals::Ckd) {
Self::regs_1ch().cr1().modify(|r| r.set_ckd(ckd));
}
/// Get max compare value. This depends on the timer frequency and the clock frequency from RCC.
fn get_max_compare_value(&self) -> u16 {
Self::regs_1ch().arr().read().arr()
}
}
/// Gneral-purpose 1 channel 16-bit timer instance.
pub trait GeneralPurpose2ChannelInstance: GeneralPurpose1ChannelInstance {
/// Get access to the general purpose 2 channel 16bit timer registers.
///
/// Note: This works even if the timer is more capable, because registers
/// for the less capable timers are a subset. This allows writing a driver
/// for a given set of capabilities, and having it transparently work with
/// more capable timers.
fn regs_2ch() -> crate::pac::timer::Tim2ch;
}
/// Gneral-purpose 16-bit timer instance.
pub trait GeneralPurpose16bitInstance: Basic16bitInstance {
pub trait GeneralPurpose16bitInstance: BasicInstance + GeneralPurpose2ChannelInstance {
/// Get access to the general purpose 16bit timer registers.
///
/// Note: This works even if the timer is more capable, because registers
@ -135,7 +221,7 @@ pub(crate) mod sealed {
fn set_counting_mode(&mut self, mode: CountingMode) {
let (cms, dir) = mode.into();
let timer_enabled = Self::regs().cr1().read().cen();
let timer_enabled = Self::regs_core().cr1().read().cen();
// Changing from edge aligned to center aligned (and vice versa) is not allowed while the timer is running.
// Changing direction is discouraged while the timer is running.
assert!(!timer_enabled);
@ -150,62 +236,8 @@ pub(crate) mod sealed {
(cr1.cms(), cr1.dir()).into()
}
/// Set clock divider.
fn set_clock_division(&mut self, ckd: vals::Ckd) {
Self::regs_gp16().cr1().modify(|r| r.set_ckd(ckd));
}
}
/// Gneral-purpose 32-bit timer instance.
pub trait GeneralPurpose32bitInstance: GeneralPurpose16bitInstance {
/// Get access to the general purpose 32bit timer registers.
///
/// Note: This works even if the timer is more capable, because registers
/// for the less capable timers are a subset. This allows writing a driver
/// for a given set of capabilities, and having it transparently work with
/// more capable timers.
fn regs_gp32() -> crate::pac::timer::TimGp32;
/// Set timer frequency.
fn set_frequency(&mut self, frequency: Hertz) {
let f = frequency.0;
assert!(f > 0);
let timer_f = Self::frequency().0;
let pclk_ticks_per_timer_period = (timer_f / f) as u64;
let psc: u16 = unwrap!(((pclk_ticks_per_timer_period - 1) / (1 << 32)).try_into());
let arr: u32 = unwrap!((pclk_ticks_per_timer_period / (psc as u64 + 1)).try_into());
let regs = Self::regs_gp32();
regs.psc().write(|r| r.set_psc(psc));
regs.arr().write(|r| r.set_arr(arr));
regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTERONLY));
regs.egr().write(|r| r.set_ug(true));
regs.cr1().modify(|r| r.set_urs(vals::Urs::ANYEVENT));
}
/// Get timer frequency.
fn get_frequency(&self) -> Hertz {
let timer_f = Self::frequency();
let regs = Self::regs_gp32();
let arr = regs.arr().read().arr();
let psc = regs.psc().read().psc();
timer_f / arr / (psc + 1)
}
}
/// Advanced control timer instance.
pub trait AdvancedControlInstance: GeneralPurpose16bitInstance {
/// Get access to the advanced timer registers.
fn regs_advanced() -> crate::pac::timer::TimAdv;
}
/// Capture/Compare 16-bit timer instance.
pub trait CaptureCompare16bitInstance: GeneralPurpose16bitInstance {
/// Set input capture filter.
fn set_input_capture_filter(&mut self, channel: Channel, icf: vals::Icf) {
fn set_input_capture_filter(&mut self, channel: Channel, icf: vals::FilterValue) {
let raw_channel = channel.index();
Self::regs_gp16()
.ccmr_input(raw_channel / 2)
@ -256,14 +288,11 @@ pub(crate) mod sealed {
});
}
/// Enable timer outputs.
fn enable_outputs(&mut self);
/// Set output compare mode.
fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode) {
let r = Self::regs_gp16();
let raw_channel: usize = channel.index();
r.ccmr_output(raw_channel / 2)
Self::regs_gp16()
.ccmr_output(raw_channel / 2)
.modify(|w| w.set_ocm(raw_channel % 2, mode.into()));
}
@ -294,11 +323,6 @@ pub(crate) mod sealed {
Self::regs_gp16().ccr(channel.index()).read().ccr()
}
/// Get max compare value. This depends on the timer frequency and the clock frequency from RCC.
fn get_max_compare_value(&self) -> u16 {
Self::regs_gp16().arr().read().arr()
}
/// Get compare value for a channel.
fn get_compare_value(&self, channel: Channel) -> u16 {
Self::regs_gp16().ccr(channel.index()).read().ccr()
@ -333,35 +357,46 @@ pub(crate) mod sealed {
}
}
/// Capture/Compare 16-bit timer instance with complementary pin support.
pub trait ComplementaryCaptureCompare16bitInstance: CaptureCompare16bitInstance + AdvancedControlInstance {
/// Set complementary output polarity.
fn set_complementary_output_polarity(&mut self, channel: Channel, polarity: OutputPolarity) {
Self::regs_advanced()
.ccer()
.modify(|w| w.set_ccnp(channel.index(), polarity.into()));
#[cfg(not(stm32l0))]
/// Gneral-purpose 32-bit timer instance.
pub trait GeneralPurpose32bitInstance: GeneralPurpose16bitInstance {
/// Get access to the general purpose 32bit timer registers.
///
/// Note: This works even if the timer is more capable, because registers
/// for the less capable timers are a subset. This allows writing a driver
/// for a given set of capabilities, and having it transparently work with
/// more capable timers.
fn regs_gp32() -> crate::pac::timer::TimGp32;
/// Set timer frequency.
fn set_frequency(&mut self, frequency: Hertz) {
let f = frequency.0;
assert!(f > 0);
let timer_f = Self::frequency().0;
let pclk_ticks_per_timer_period = (timer_f / f) as u64;
let psc: u16 = unwrap!(((pclk_ticks_per_timer_period - 1) / (1 << 32)).try_into());
let arr: u32 = unwrap!((pclk_ticks_per_timer_period / (psc as u64 + 1)).try_into());
let regs = Self::regs_gp32();
regs.psc().write(|r| r.set_psc(psc));
regs.arr().write(|r| r.set_arr(arr));
regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTERONLY));
regs.egr().write(|r| r.set_ug(true));
regs.cr1().modify(|r| r.set_urs(vals::Urs::ANYEVENT));
}
/// Set clock divider for the dead time.
fn set_dead_time_clock_division(&mut self, value: vals::Ckd) {
Self::regs_advanced().cr1().modify(|w| w.set_ckd(value));
/// Get timer frequency.
fn get_frequency(&self) -> Hertz {
let timer_f = Self::frequency();
let regs = Self::regs_gp32();
let arr = regs.arr().read().arr();
let psc = regs.psc().read().psc();
timer_f / arr / (psc + 1)
}
/// Set dead time, as a fraction of the max duty value.
fn set_dead_time_value(&mut self, value: u8) {
Self::regs_advanced().bdtr().modify(|w| w.set_dtg(value));
}
/// Enable/disable a complementary channel.
fn enable_complementary_channel(&mut self, channel: Channel, enable: bool) {
Self::regs_advanced()
.ccer()
.modify(|w| w.set_ccne(channel.index(), enable));
}
}
/// Capture/Compare 32-bit timer instance.
pub trait CaptureCompare32bitInstance: GeneralPurpose32bitInstance + CaptureCompare16bitInstance {
/// Set comapre value for a channel.
fn set_compare_value(&mut self, channel: Channel, value: u32) {
Self::regs_gp32().ccr(channel.index()).modify(|w| w.set_ccr(value));
@ -382,6 +417,70 @@ pub(crate) mod sealed {
Self::regs_gp32().ccr(channel.index()).read().ccr()
}
}
#[cfg(not(stm32l0))]
/// Gneral-purpose 1 channel with one complementary 16-bit timer instance.
pub trait GeneralPurpose1ChannelComplementaryInstance: BasicNoCr2Instance + GeneralPurpose1ChannelInstance {
/// Get access to the general purpose 1 channel with one complementary 16bit timer registers.
///
/// Note: This works even if the timer is more capable, because registers
/// for the less capable timers are a subset. This allows writing a driver
/// for a given set of capabilities, and having it transparently work with
/// more capable timers.
fn regs_1ch_cmp() -> crate::pac::timer::Tim1chCmp;
/// Set clock divider for the dead time.
fn set_dead_time_clock_division(&mut self, value: vals::Ckd) {
Self::regs_1ch_cmp().cr1().modify(|w| w.set_ckd(value));
}
/// Set dead time, as a fraction of the max duty value.
fn set_dead_time_value(&mut self, value: u8) {
Self::regs_1ch_cmp().bdtr().modify(|w| w.set_dtg(value));
}
/// Enable timer outputs.
fn enable_outputs(&mut self) {
Self::regs_1ch_cmp().bdtr().modify(|w| w.set_moe(true));
}
}
#[cfg(not(stm32l0))]
/// Gneral-purpose 2 channel with one complementary 16-bit timer instance.
pub trait GeneralPurpose2ChannelComplementaryInstance:
BasicInstance + GeneralPurpose2ChannelInstance + GeneralPurpose1ChannelComplementaryInstance
{
/// Get access to the general purpose 2 channel with one complementary 16bit timer registers.
///
/// Note: This works even if the timer is more capable, because registers
/// for the less capable timers are a subset. This allows writing a driver
/// for a given set of capabilities, and having it transparently work with
/// more capable timers.
fn regs_2ch_cmp() -> crate::pac::timer::Tim2chCmp;
}
#[cfg(not(stm32l0))]
/// Advanced control timer instance.
pub trait AdvancedControlInstance:
GeneralPurpose2ChannelComplementaryInstance + GeneralPurpose16bitInstance
{
/// Get access to the advanced timer registers.
fn regs_advanced() -> crate::pac::timer::TimAdv;
/// Set complementary output polarity.
fn set_complementary_output_polarity(&mut self, channel: Channel, polarity: OutputPolarity) {
Self::regs_advanced()
.ccer()
.modify(|w| w.set_ccnp(channel.index(), polarity.into()));
}
/// Enable/disable a complementary channel.
fn enable_complementary_channel(&mut self, channel: Channel, enable: bool) {
Self::regs_advanced()
.ccer()
.modify(|w| w.set_ccne(channel.index(), enable));
}
}
}
/// Timer channel.
@ -572,61 +671,92 @@ impl From<OutputPolarity> for bool {
}
/// Basic 16-bit timer instance.
pub trait Basic16bitInstance: sealed::Basic16bitInstance + 'static {}
pub trait BasicInstance: sealed::BasicInstance + sealed::BasicNoCr2Instance + sealed::CoreInstance + 'static {}
/// Gneral-purpose 16-bit timer instance.
pub trait GeneralPurpose16bitInstance: sealed::GeneralPurpose16bitInstance + Basic16bitInstance + 'static {}
/// Gneral-purpose 32-bit timer instance.
pub trait GeneralPurpose32bitInstance:
sealed::GeneralPurpose32bitInstance + GeneralPurpose16bitInstance + 'static
{
}
/// Advanced control timer instance.
pub trait AdvancedControlInstance: sealed::AdvancedControlInstance + GeneralPurpose16bitInstance + 'static {}
/// Capture/Compare 16-bit timer instance.
// It's just a General-purpose 16-bit timer instance.
/// Capture Compare timer instance.
pub trait CaptureCompare16bitInstance:
sealed::CaptureCompare16bitInstance + GeneralPurpose16bitInstance + 'static
BasicInstance
+ sealed::GeneralPurpose2ChannelInstance
+ sealed::GeneralPurpose1ChannelInstance
+ sealed::GeneralPurpose16bitInstance
+ 'static
{
}
/// Capture/Compare 16-bit timer instance with complementary pin support.
pub trait ComplementaryCaptureCompare16bitInstance:
sealed::ComplementaryCaptureCompare16bitInstance + CaptureCompare16bitInstance + AdvancedControlInstance + 'static
{
}
/// Capture/Compare 32-bit timer instance.
#[cfg(not(stm32l0))]
// It's just a General-purpose 32-bit timer instance.
/// Capture Compare 32-bit timer instance.
pub trait CaptureCompare32bitInstance:
sealed::CaptureCompare32bitInstance + CaptureCompare16bitInstance + GeneralPurpose32bitInstance + 'static
CaptureCompare16bitInstance + sealed::GeneralPurpose32bitInstance + 'static
{
}
#[cfg(not(stm32l0))]
// It's just a Advanced Control timer instance.
/// Complementary Capture Compare 32-bit timer instance.
pub trait ComplementaryCaptureCompare16bitInstance:
CaptureCompare16bitInstance
+ sealed::GeneralPurpose1ChannelComplementaryInstance
+ sealed::GeneralPurpose2ChannelComplementaryInstance
+ sealed::AdvancedControlInstance
+ 'static
{
}
pin_trait!(Channel1Pin, CaptureCompare16bitInstance);
pin_trait!(Channel1ComplementaryPin, CaptureCompare16bitInstance);
pin_trait!(Channel2Pin, CaptureCompare16bitInstance);
pin_trait!(Channel2ComplementaryPin, CaptureCompare16bitInstance);
pin_trait!(Channel3Pin, CaptureCompare16bitInstance);
pin_trait!(Channel3ComplementaryPin, CaptureCompare16bitInstance);
pin_trait!(Channel4Pin, CaptureCompare16bitInstance);
pin_trait!(Channel4ComplementaryPin, CaptureCompare16bitInstance);
pin_trait!(ExternalTriggerPin, CaptureCompare16bitInstance);
pin_trait!(BreakInputPin, CaptureCompare16bitInstance);
pin_trait!(BreakInputComparator1Pin, CaptureCompare16bitInstance);
pin_trait!(BreakInputComparator2Pin, CaptureCompare16bitInstance);
pin_trait!(BreakInput2Pin, CaptureCompare16bitInstance);
pin_trait!(BreakInput2Comparator1Pin, CaptureCompare16bitInstance);
pin_trait!(BreakInput2Comparator2Pin, CaptureCompare16bitInstance);
cfg_if::cfg_if! {
if #[cfg(not(stm32l0))] {
pin_trait!(Channel1ComplementaryPin, ComplementaryCaptureCompare16bitInstance);
pin_trait!(Channel2ComplementaryPin, ComplementaryCaptureCompare16bitInstance);
pin_trait!(Channel3ComplementaryPin, ComplementaryCaptureCompare16bitInstance);
pin_trait!(Channel4ComplementaryPin, ComplementaryCaptureCompare16bitInstance);
pin_trait!(BreakInputPin, ComplementaryCaptureCompare16bitInstance);
pin_trait!(BreakInput2Pin, ComplementaryCaptureCompare16bitInstance);
pin_trait!(BreakInputComparator1Pin, ComplementaryCaptureCompare16bitInstance);
pin_trait!(BreakInputComparator2Pin, ComplementaryCaptureCompare16bitInstance);
pin_trait!(BreakInput2Comparator1Pin, ComplementaryCaptureCompare16bitInstance);
pin_trait!(BreakInput2Comparator2Pin, ComplementaryCaptureCompare16bitInstance);
}
}
#[allow(unused)]
macro_rules! impl_basic_16bit_timer {
macro_rules! impl_core_timer {
($inst:ident, $irq:ident) => {
impl sealed::Basic16bitInstance for crate::peripherals::$inst {
impl sealed::CoreInstance for crate::peripherals::$inst {
type Interrupt = crate::interrupt::typelevel::$irq;
fn regs() -> crate::pac::timer::TimBasic {
fn regs_core() -> crate::pac::timer::TimCore {
unsafe { crate::pac::timer::TimCore::from_ptr(crate::pac::$inst.as_ptr()) }
}
}
};
}
#[allow(unused)]
macro_rules! impl_basic_no_cr2_timer {
($inst:ident) => {
impl sealed::BasicNoCr2Instance for crate::peripherals::$inst {
fn regs_basic_no_cr2() -> crate::pac::timer::TimBasicNoCr2 {
unsafe { crate::pac::timer::TimBasicNoCr2::from_ptr(crate::pac::$inst.as_ptr()) }
}
}
};
}
#[allow(unused)]
macro_rules! impl_basic_timer {
($inst:ident) => {
impl sealed::BasicInstance for crate::peripherals::$inst {
fn regs_basic() -> crate::pac::timer::TimBasic {
unsafe { crate::pac::timer::TimBasic::from_ptr(crate::pac::$inst.as_ptr()) }
}
}
@ -634,7 +764,40 @@ macro_rules! impl_basic_16bit_timer {
}
#[allow(unused)]
macro_rules! impl_32bit_timer {
macro_rules! impl_1ch_timer {
($inst:ident) => {
impl sealed::GeneralPurpose1ChannelInstance for crate::peripherals::$inst {
fn regs_1ch() -> crate::pac::timer::Tim1ch {
unsafe { crate::pac::timer::Tim1ch::from_ptr(crate::pac::$inst.as_ptr()) }
}
}
};
}
#[allow(unused)]
macro_rules! impl_2ch_timer {
($inst:ident) => {
impl sealed::GeneralPurpose2ChannelInstance for crate::peripherals::$inst {
fn regs_2ch() -> crate::pac::timer::Tim2ch {
unsafe { crate::pac::timer::Tim2ch::from_ptr(crate::pac::$inst.as_ptr()) }
}
}
};
}
#[allow(unused)]
macro_rules! impl_gp16_timer {
($inst:ident) => {
impl sealed::GeneralPurpose16bitInstance for crate::peripherals::$inst {
fn regs_gp16() -> crate::pac::timer::TimGp16 {
unsafe { crate::pac::timer::TimGp16::from_ptr(crate::pac::$inst.as_ptr()) }
}
}
};
}
#[allow(unused)]
macro_rules! impl_gp32_timer {
($inst:ident) => {
impl sealed::GeneralPurpose32bitInstance for crate::peripherals::$inst {
fn regs_gp32() -> crate::pac::timer::TimGp32 {
@ -645,83 +808,144 @@ macro_rules! impl_32bit_timer {
}
#[allow(unused)]
macro_rules! impl_compare_capable_16bit {
macro_rules! impl_1ch_cmp_timer {
($inst:ident) => {
impl sealed::CaptureCompare16bitInstance for crate::peripherals::$inst {
fn enable_outputs(&mut self) {}
impl sealed::GeneralPurpose1ChannelComplementaryInstance for crate::peripherals::$inst {
fn regs_1ch_cmp() -> crate::pac::timer::Tim1chCmp {
unsafe { crate::pac::timer::Tim1chCmp::from_ptr(crate::pac::$inst.as_ptr()) }
}
}
};
}
#[allow(unused)]
macro_rules! impl_2ch_cmp_timer {
($inst:ident) => {
impl sealed::GeneralPurpose2ChannelComplementaryInstance for crate::peripherals::$inst {
fn regs_2ch_cmp() -> crate::pac::timer::Tim2chCmp {
unsafe { crate::pac::timer::Tim2chCmp::from_ptr(crate::pac::$inst.as_ptr()) }
}
}
};
}
#[allow(unused)]
macro_rules! impl_adv_timer {
($inst:ident) => {
impl sealed::AdvancedControlInstance for crate::peripherals::$inst {
fn regs_advanced() -> crate::pac::timer::TimAdv {
unsafe { crate::pac::timer::TimAdv::from_ptr(crate::pac::$inst.as_ptr()) }
}
}
};
}
foreach_interrupt! {
($inst:ident, timer, TIM_BASIC, UP, $irq:ident) => {
impl_basic_16bit_timer!($inst, $irq);
impl Basic16bitInstance for crate::peripherals::$inst {}
};
($inst:ident, timer, TIM_GP16, UP, $irq:ident) => {
impl_basic_16bit_timer!($inst, $irq);
impl_compare_capable_16bit!($inst);
impl Basic16bitInstance for crate::peripherals::$inst {}
impl GeneralPurpose16bitInstance for crate::peripherals::$inst {}
impl CaptureCompare16bitInstance for crate::peripherals::$inst {}
impl sealed::GeneralPurpose16bitInstance for crate::peripherals::$inst {
fn regs_gp16() -> crate::pac::timer::TimGp16 {
crate::pac::$inst
}
}
($inst:ident, timer, TIM_BASIC, UP, $irq:ident) => {
impl_core_timer!($inst, $irq);
impl_basic_no_cr2_timer!($inst);
impl_basic_timer!($inst);
impl BasicInstance for crate::peripherals::$inst {}
};
($inst:ident, timer, TIM_1CH, UP, $irq:ident) => {
impl_core_timer!($inst, $irq);
impl_basic_no_cr2_timer!($inst);
impl_basic_timer!($inst);
impl_1ch_timer!($inst);
impl_2ch_timer!($inst);
impl_gp16_timer!($inst);
impl BasicInstance for crate::peripherals::$inst {}
impl CaptureCompare16bitInstance for crate::peripherals::$inst {}
};
($inst:ident, timer, TIM_2CH, UP, $irq:ident) => {
impl_core_timer!($inst, $irq);
impl_basic_no_cr2_timer!($inst);
impl_basic_timer!($inst);
impl_1ch_timer!($inst);
impl_2ch_timer!($inst);
impl_gp16_timer!($inst);
impl BasicInstance for crate::peripherals::$inst {}
impl CaptureCompare16bitInstance for crate::peripherals::$inst {}
};
($inst:ident, timer, TIM_GP16, UP, $irq:ident) => {
impl_core_timer!($inst, $irq);
impl_basic_no_cr2_timer!($inst);
impl_basic_timer!($inst);
impl_1ch_timer!($inst);
impl_2ch_timer!($inst);
impl_gp16_timer!($inst);
impl BasicInstance for crate::peripherals::$inst {}
impl CaptureCompare16bitInstance for crate::peripherals::$inst {}
};
($inst:ident, timer, TIM_GP32, UP, $irq:ident) => {
impl_basic_16bit_timer!($inst, $irq);
impl_32bit_timer!($inst);
impl_compare_capable_16bit!($inst);
impl Basic16bitInstance for crate::peripherals::$inst {}
impl_core_timer!($inst, $irq);
impl_basic_no_cr2_timer!($inst);
impl_basic_timer!($inst);
impl_1ch_timer!($inst);
impl_2ch_timer!($inst);
impl_gp16_timer!($inst);
impl_gp32_timer!($inst);
impl BasicInstance for crate::peripherals::$inst {}
impl CaptureCompare16bitInstance for crate::peripherals::$inst {}
impl CaptureCompare32bitInstance for crate::peripherals::$inst {}
impl GeneralPurpose16bitInstance for crate::peripherals::$inst {}
impl GeneralPurpose32bitInstance for crate::peripherals::$inst {}
impl sealed::CaptureCompare32bitInstance for crate::peripherals::$inst {}
impl sealed::GeneralPurpose16bitInstance for crate::peripherals::$inst {
fn regs_gp16() -> crate::pac::timer::TimGp16 {
unsafe { crate::pac::timer::TimGp16::from_ptr(crate::pac::$inst.as_ptr()) }
}
}
};
($inst:ident, timer, TIM_ADV, UP, $irq:ident) => {
impl_basic_16bit_timer!($inst, $irq);
impl Basic16bitInstance for crate::peripherals::$inst {}
impl GeneralPurpose16bitInstance for crate::peripherals::$inst {}
($inst:ident, timer, TIM_1CH_CMP, UP, $irq:ident) => {
impl_core_timer!($inst, $irq);
impl_basic_no_cr2_timer!($inst);
impl_basic_timer!($inst);
impl_1ch_timer!($inst);
impl_2ch_timer!($inst);
impl_gp16_timer!($inst);
impl_1ch_cmp_timer!($inst);
impl_2ch_cmp_timer!($inst);
impl_adv_timer!($inst);
impl BasicInstance for crate::peripherals::$inst {}
impl CaptureCompare16bitInstance for crate::peripherals::$inst {}
impl ComplementaryCaptureCompare16bitInstance for crate::peripherals::$inst {}
impl AdvancedControlInstance for crate::peripherals::$inst {}
impl sealed::CaptureCompare16bitInstance for crate::peripherals::$inst {
fn enable_outputs(&mut self) {
use crate::timer::sealed::AdvancedControlInstance;
let r = Self::regs_advanced();
r.bdtr().modify(|w| w.set_moe(true));
}
}
impl sealed::ComplementaryCaptureCompare16bitInstance for crate::peripherals::$inst {}
impl sealed::GeneralPurpose16bitInstance for crate::peripherals::$inst {
fn regs_gp16() -> crate::pac::timer::TimGp16 {
unsafe { crate::pac::timer::TimGp16::from_ptr(crate::pac::$inst.as_ptr()) }
}
}
};
impl sealed::AdvancedControlInstance for crate::peripherals::$inst {
fn regs_advanced() -> crate::pac::timer::TimAdv {
crate::pac::$inst
}
}
($inst:ident, timer, TIM_2CH_CMP, UP, $irq:ident) => {
impl_core_timer!($inst, $irq);
impl_basic_no_cr2_timer!($inst);
impl_basic_timer!($inst);
impl_1ch_timer!($inst);
impl_2ch_timer!($inst);
impl_gp16_timer!($inst);
impl_1ch_cmp_timer!($inst);
impl_2ch_cmp_timer!($inst);
impl_adv_timer!($inst);
impl BasicInstance for crate::peripherals::$inst {}
impl CaptureCompare16bitInstance for crate::peripherals::$inst {}
impl ComplementaryCaptureCompare16bitInstance for crate::peripherals::$inst {}
};
($inst:ident, timer, TIM_ADV, UP, $irq:ident) => {
impl_core_timer!($inst, $irq);
impl_basic_no_cr2_timer!($inst);
impl_basic_timer!($inst);
impl_1ch_timer!($inst);
impl_2ch_timer!($inst);
impl_gp16_timer!($inst);
impl_1ch_cmp_timer!($inst);
impl_2ch_cmp_timer!($inst);
impl_adv_timer!($inst);
impl BasicInstance for crate::peripherals::$inst {}
impl CaptureCompare16bitInstance for crate::peripherals::$inst {}
impl ComplementaryCaptureCompare16bitInstance for crate::peripherals::$inst {}
};
}
// Update Event trigger DMA for every timer
dma_trait!(UpDma, Basic16bitInstance);
dma_trait!(UpDma, BasicInstance);
dma_trait!(Ch1Dma, CaptureCompare16bitInstance);
dma_trait!(Ch2Dma, CaptureCompare16bitInstance);

View File

@ -84,13 +84,12 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
this.set_frequency(freq);
this.inner.start();
this.inner.enable_outputs();
[Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4]
.iter()
.for_each(|&channel| {
this.inner.set_output_compare_mode(channel, OutputCompareMode::PwmMode1);
this.inner.set_output_compare_preload(channel, true)
this.inner.set_output_compare_preload(channel, true);
});
this
@ -202,7 +201,7 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
&mut dma,
req,
duty,
T::regs_gp16().ccr(channel.index()).as_ptr() as *mut _,
T::regs_1ch().ccr(channel.index()).as_ptr() as *mut _,
dma_transfer_option,
)
.await

View File

@ -50,7 +50,7 @@ async fn main(_spawner: Spawner) {
let nvmc = Nvmc::new(p.NVMC);
let nvmc = Mutex::new(BlockingAsync::new(nvmc));
let config = FirmwareUpdaterConfig::from_linkerfile(&nvmc);
let config = FirmwareUpdaterConfig::from_linkerfile(&nvmc, &nvmc);
let mut magic = [0; 4];
let mut updater = FirmwareUpdater::new(config, &mut magic);
loop {

View File

@ -36,7 +36,7 @@ async fn main(_s: Spawner) {
let flash = Flash::<_, _, FLASH_SIZE>::new_blocking(p.FLASH);
let flash = Mutex::new(RefCell::new(flash));
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash);
let mut aligned = AlignedBuffer([0; 1]);
let mut updater = BlockingFirmwareUpdater::new(config, &mut aligned.0);

View File

@ -3,8 +3,8 @@ 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
FLASH : ORIGIN = 0x08008000, LENGTH = 64K
DFU : ORIGIN = 0x08018000, LENGTH = 66K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K
}

View File

@ -28,7 +28,7 @@ async fn main(_spawner: Spawner) {
let mut led = Output::new(p.PA5, Level::Low, Speed::Low);
led.set_high();
let config = FirmwareUpdaterConfig::from_linkerfile(&flash);
let config = FirmwareUpdaterConfig::from_linkerfile(&flash, &flash);
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
let mut updater = FirmwareUpdater::new(config, &mut magic.0);
button.wait_for_falling_edge().await;

View File

@ -30,7 +30,7 @@ async fn main(_spawner: Spawner) {
let mut led = Output::new(p.PB7, Level::Low, Speed::Low);
led.set_high();
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash);
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
let mut updater = BlockingFirmwareUpdater::new(config, &mut magic.0);
let writer = updater.prepare_update().unwrap();

View File

@ -30,7 +30,7 @@ async fn main(_spawner: Spawner) {
let mut led = Output::new(p.PB14, Level::Low, Speed::Low);
led.set_high();
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash);
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
let mut updater = BlockingFirmwareUpdater::new(config, &mut magic.0);
let writer = updater.prepare_update().unwrap();

View File

@ -3,8 +3,8 @@ 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
FLASH : ORIGIN = 0x08008000, LENGTH = 64K
DFU : ORIGIN = 0x08018000, LENGTH = 66K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K
}

View File

@ -30,7 +30,7 @@ async fn main(_spawner: Spawner) {
led.set_high();
let config = FirmwareUpdaterConfig::from_linkerfile(&flash);
let config = FirmwareUpdaterConfig::from_linkerfile(&flash, &flash);
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
let mut updater = FirmwareUpdater::new(config, &mut magic.0);
button.wait_for_falling_edge().await;

View File

@ -3,8 +3,8 @@ 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
FLASH : ORIGIN = 0x08008000, LENGTH = 46K
DFU : ORIGIN = 0x08013800, LENGTH = 54K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K
}

View File

@ -30,7 +30,7 @@ async fn main(_spawner: Spawner) {
led.set_high();
let config = FirmwareUpdaterConfig::from_linkerfile(&flash);
let config = FirmwareUpdaterConfig::from_linkerfile(&flash, &flash);
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
let mut updater = FirmwareUpdater::new(config, &mut magic.0);
button.wait_for_falling_edge().await;

View File

@ -3,8 +3,8 @@ 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
FLASH : ORIGIN = 0x08008000, LENGTH = 64K
DFU : ORIGIN = 0x08018000, LENGTH = 68K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K
}

View File

@ -28,7 +28,7 @@ async fn main(_spawner: Spawner) {
let mut led = Output::new(p.PB14, Level::Low, Speed::Low);
led.set_high();
let config = FirmwareUpdaterConfig::from_linkerfile(&flash);
let config = FirmwareUpdaterConfig::from_linkerfile(&flash, &flash);
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
let mut updater = FirmwareUpdater::new(config, &mut magic.0);
button.wait_for_falling_edge().await;

View File

@ -1,29 +1,9 @@
# 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`
Example for STM32WB demonstrating the USB DFU application.
## Usage
```
# Flash bootloader
cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32wl55jc-cm4 --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
cargo flash --release --chip STM32WB55RGVx
```

View File

@ -30,7 +30,7 @@ async fn main(_spawner: Spawner) {
let flash = Flash::new_blocking(p.FLASH);
let flash = Mutex::new(RefCell::new(flash));
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash);
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
let mut firmware_state = BlockingFirmwareState::from_config(config, &mut magic.0);
firmware_state.mark_booted().expect("Failed to mark booted");

View File

@ -3,8 +3,8 @@ 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
FLASH : ORIGIN = 0x08008000, LENGTH = 64K
DFU : ORIGIN = 0x08018000, LENGTH = 68K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K
}

View File

@ -28,7 +28,7 @@ async fn main(_spawner: Spawner) {
let mut led = Output::new(p.PB9, Level::Low, Speed::Low);
led.set_high();
let config = FirmwareUpdaterConfig::from_linkerfile(&flash);
let config = FirmwareUpdaterConfig::from_linkerfile(&flash, &flash);
let mut magic = AlignedBuffer([0; WRITE_SIZE]);
let mut updater = FirmwareUpdater::new(config, &mut magic.0);
button.wait_for_falling_edge().await;

View File

@ -31,7 +31,7 @@ fn main() -> ! {
let flash = WatchdogFlash::start(Nvmc::new(p.NVMC), p.WDT, wdt_config);
let flash = Mutex::new(RefCell::new(flash));
let config = BootLoaderConfig::from_linkerfile_blocking(&flash);
let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash);
let active_offset = config.active.offset();
let bl: BootLoader = BootLoader::prepare(config);

View File

@ -27,7 +27,7 @@ fn main() -> ! {
let flash = WatchdogFlash::<FLASH_SIZE>::start(p.FLASH, p.WATCHDOG, Duration::from_secs(8));
let flash = Mutex::new(RefCell::new(flash));
let config = BootLoaderConfig::from_linkerfile_blocking(&flash);
let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash);
let active_offset = config.active.offset();
let bl: BootLoader = BootLoader::prepare(config);

View File

@ -0,0 +1,57 @@
[package]
edition = "2021"
name = "stm32-bootloader-dual-bank-flash-example"
version = "0.1.0"
description = "Example bootloader for dual-bank flash STM32 chips"
license = "MIT OR Apache-2.0"
[dependencies]
defmt = { version = "0.3", optional = true }
defmt-rtt = { version = "0.4", optional = true }
embassy-stm32 = { path = "../../../../embassy-stm32", features = [] }
embassy-boot-stm32 = { path = "../../../../embassy-boot-stm32" }
cortex-m = { version = "0.7.6", features = [
"inline-asm",
"critical-section-single-core",
] }
embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" }
cortex-m-rt = { version = "0.7" }
embedded-storage = "0.3.1"
embedded-storage-async = "0.4.0"
cfg-if = "1.0.0"
[features]
defmt = ["dep:defmt", "embassy-boot-stm32/defmt", "embassy-stm32/defmt"]
debug = ["defmt-rtt", "defmt"]
[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,44 @@
# STM32 dual-bank flash Bootloader
## Overview
This bootloader leverages `embassy-boot` to interact with the flash.
This example targets STM32 devices with dual-bank flash memory, with a primary focus on the STM32H747XI series.
Users must modify the `memory.x` configuration file to match with the memory layout of their specific STM32 device.
Additionally, this example can be extended to utilize external flash memory, such as QSPI, for storing partitions.
## Memory Configuration
In this example's `memory.x` file, various symbols are defined to assist in effective memory management within the bootloader environment.
For dual-bank STM32 devices, it's crucial to assign these symbols correctly to their respective memory banks.
### Symbol Definitions
The bootloader's state and active symbols are anchored to the flash origin of **bank 1**:
- `__bootloader_state_start` and `__bootloader_state_end`
- `__bootloader_active_start` and `__bootloader_active_end`
In contrast, the Device Firmware Upgrade (DFU) symbols are aligned with the DFU flash origin in **bank 2**:
- `__bootloader_dfu_start` and `__bootloader_dfu_end`
```rust
__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(**FLASH**);
__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(**FLASH**);
__bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(**FLASH**);
__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(**FLASH**);
__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(**DFU**);
__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(**DFU**);
```
## Flashing the Bootloader
To flash the bootloader onto your STM32H747XI device, use the following command:
```bash
cargo flash --features embassy-stm32/stm32h747xi-cm7 --release --chip STM32H747XIHx
```

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 = 128K
BOOTLOADER_STATE : ORIGIN = 0x08020000, LENGTH = 128K
ACTIVE : ORIGIN = 0x08040000, LENGTH = 512K
DFU : ORIGIN = 0x08100000, LENGTH = 640K
RAM (rwx) : ORIGIN = 0x24000000, LENGTH = 512K
}
__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(FLASH);
__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(FLASH);
__bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(FLASH);
__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(FLASH);
__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(DFU);
__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(DFU);

View File

@ -0,0 +1,53 @@
#![no_std]
#![no_main]
use core::cell::RefCell;
use cortex_m_rt::{entry, exception};
#[cfg(feature = "defmt")]
use defmt_rtt as _;
use embassy_boot_stm32::*;
use embassy_stm32::flash::{Flash, BANK1_REGION};
use embassy_sync::blocking_mutex::Mutex;
#[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();
}
*/
let layout = Flash::new_blocking(p.FLASH).into_blocking_regions();
let flash_bank1 = Mutex::new(RefCell::new(layout.bank1_region));
let flash_bank2 = Mutex::new(RefCell::new(layout.bank2_region));
let config = BootLoaderConfig::from_linkerfile_blocking(&flash_bank1, &flash_bank2, &flash_bank1);
let active_offset = config.active.offset();
let bl = BootLoader::prepare::<_, _, _, 2048>(config);
unsafe { bl.load(BANK1_REGION.base + active_offset) }
}
#[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) -> ! {
cortex_m::asm::udf();
}

View File

@ -25,7 +25,7 @@ fn main() -> ! {
let layout = Flash::new_blocking(p.FLASH).into_blocking_regions();
let flash = Mutex::new(RefCell::new(layout.bank1_region));
let config = BootLoaderConfig::from_linkerfile_blocking(&flash);
let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash);
let active_offset = config.active.offset();
let bl = BootLoader::prepare::<_, _, _, 2048>(config);

View File

@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
defmt = { version = "0.3", optional = true }
defmt-rtt = { version = "0.4", optional = true }
embassy-stm32 = { path = "../../../../embassy-stm32", features = ["stm32wb55rg"] }
embassy-stm32 = { path = "../../../../embassy-stm32", features = [] }
embassy-boot-stm32 = { path = "../../../../embassy-boot-stm32" }
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" }

View File

@ -7,5 +7,5 @@ The bootloader uses `embassy-boot` to interact with the flash.
Flash the bootloader
```
cargo flash --features embassy-stm32/stm32wl55jc-cm4 --release --chip STM32WLE5JCIx
cargo flash --features embassy-stm32/stm32wb55rg --release --chip STM32WB55RGVx
```

View File

@ -35,7 +35,7 @@ fn main() -> ! {
let layout = Flash::new_blocking(p.FLASH).into_blocking_regions();
let flash = Mutex::new(RefCell::new(layout.bank1_region));
let config = BootLoaderConfig::from_linkerfile_blocking(&flash);
let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash);
let active_offset = config.active.offset();
let bl = BootLoader::prepare::<_, _, _, 2048>(config);
if bl.state == State::DfuDetach {
@ -45,7 +45,7 @@ fn main() -> ! {
config.product = Some("USB-DFU Bootloader example");
config.serial_number = Some("1235678");
let fw_config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
let fw_config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash);
let mut buffer = AlignedBuffer([0; WRITE_SIZE]);
let updater = BlockingFirmwareUpdater::new(fw_config, &mut buffer.0[..]);

View File

@ -8,7 +8,7 @@ use embassy_stm32::pac::timer::vals::Mms;
use embassy_stm32::peripherals::{DAC1, DMA1_CH3, DMA1_CH4, TIM6, TIM7};
use embassy_stm32::rcc::low_level::RccPeripheral;
use embassy_stm32::time::Hertz;
use embassy_stm32::timer::low_level::Basic16bitInstance;
use embassy_stm32::timer::low_level::BasicInstance;
use micromath::F32Ext;
use {defmt_rtt as _, panic_probe as _};
@ -75,9 +75,9 @@ async fn dac_task1(mut dac: DacCh1<'static, DAC1, DMA1_CH3>) {
dac.enable();
TIM6::enable_and_reset();
TIM6::regs().arr().modify(|w| w.set_arr(reload as u16 - 1));
TIM6::regs().cr2().modify(|w| w.set_mms(Mms::UPDATE));
TIM6::regs().cr1().modify(|w| {
TIM6::regs_basic().arr().modify(|w| w.set_arr(reload as u16 - 1));
TIM6::regs_basic().cr2().modify(|w| w.set_mms(Mms::UPDATE));
TIM6::regs_basic().cr1().modify(|w| {
w.set_opm(false);
w.set_cen(true);
});
@ -112,9 +112,9 @@ async fn dac_task2(mut dac: DacCh2<'static, DAC1, DMA1_CH4>) {
}
TIM7::enable_and_reset();
TIM7::regs().arr().modify(|w| w.set_arr(reload as u16 - 1));
TIM7::regs().cr2().modify(|w| w.set_mms(Mms::UPDATE));
TIM7::regs().cr1().modify(|w| {
TIM7::regs_basic().arr().modify(|w| w.set_arr(reload as u16 - 1));
TIM7::regs_basic().cr2().modify(|w| w.set_mms(Mms::UPDATE));
TIM7::regs_basic().cr1().modify(|w| {
w.set_opm(false);
w.set_cen(true);
});

View File

@ -0,0 +1,40 @@
#![no_std]
#![no_main]
use defmt::*;
use embassy_executor::Spawner;
use embassy_stm32::adc::{Adc, SampleTime};
use embassy_stm32::peripherals::ADC;
use embassy_stm32::{adc, bind_interrupts};
use embassy_time::{Delay, Timer};
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
ADC1_COMP => adc::InterruptHandler<ADC>;
});
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_stm32::init(Default::default());
info!("Hello World!");
let mut adc = Adc::new(p.ADC, Irqs, &mut Delay);
adc.set_sample_time(SampleTime::Cycles79_5);
let mut pin = p.PA1;
let mut vrefint = adc.enable_vref(&mut Delay);
let vrefint_sample = adc.read(&mut vrefint).await;
let convert_to_millivolts = |sample| {
// From https://www.st.com/resource/en/datasheet/stm32l051c6.pdf
// 6.3.3 Embedded internal reference voltage
const VREFINT_MV: u32 = 1224; // mV
(u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16
};
loop {
let v = adc.read(&mut pin).await;
info!("--> {} - {} mV", v, convert_to_millivolts(v));
Timer::after_millis(100).await;
}
}

View File

@ -8,7 +8,7 @@ use embassy_stm32::pac::timer::vals::Mms;
use embassy_stm32::peripherals::{DAC1, DMA1_CH3, DMA1_CH4, TIM6, TIM7};
use embassy_stm32::rcc::low_level::RccPeripheral;
use embassy_stm32::time::Hertz;
use embassy_stm32::timer::low_level::Basic16bitInstance;
use embassy_stm32::timer::low_level::BasicInstance;
use micromath::F32Ext;
use {defmt_rtt as _, panic_probe as _};
@ -46,9 +46,9 @@ async fn dac_task1(mut dac: DacCh1<'static, DAC1, DMA1_CH3>) {
dac.enable();
TIM6::enable_and_reset();
TIM6::regs().arr().modify(|w| w.set_arr(reload as u16 - 1));
TIM6::regs().cr2().modify(|w| w.set_mms(Mms::UPDATE));
TIM6::regs().cr1().modify(|w| {
TIM6::regs_basic().arr().modify(|w| w.set_arr(reload as u16 - 1));
TIM6::regs_basic().cr2().modify(|w| w.set_mms(Mms::UPDATE));
TIM6::regs_basic().cr1().modify(|w| {
w.set_opm(false);
w.set_cen(true);
});
@ -83,9 +83,9 @@ async fn dac_task2(mut dac: DacCh2<'static, DAC1, DMA1_CH4>) {
}
TIM7::enable_and_reset();
TIM7::regs().arr().modify(|w| w.set_arr(reload as u16 - 1));
TIM7::regs().cr2().modify(|w| w.set_mms(Mms::UPDATE));
TIM7::regs().cr1().modify(|w| {
TIM7::regs_basic().arr().modify(|w| w.set_arr(reload as u16 - 1));
TIM7::regs_basic().cr2().modify(|w| w.set_mms(Mms::UPDATE));
TIM7::regs_basic().cr1().modify(|w| {
w.set_opm(false);
w.set_cen(true);
});

View File

@ -1,12 +0,0 @@
echo Running target=$1 elf=$2
STATUSCODE=$(
curl \
-sS \
--output /dev/stderr \
--write-out "%{http_code}" \
-H "Authorization: Bearer $TELEPROBE_TOKEN" \
https://teleprobe.embassy.dev/targets/$1/run --data-binary @$2
)
echo
echo HTTP Status code: $STATUSCODE
test "$STATUSCODE" -eq 200