Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions embassy-mspm0/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ build = [
{target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1345dgs28", "time-driver-any"]},
{target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1106dgs28", "time-driver-any"]},
{target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1228pt", "time-driver-any"]},
{target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1306rhb", "time-driver-any", "executor-thread"]},
{target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1306rhb", "time-driver-any", "executor-interrupt"]},
{target = "thumbv6m-none-eabi", features = ["defmt", "mspm0l1306rhb", "time-driver-any", "executor-thread", "executor-interrupt"]},
]

[package.metadata.embassy_docs]
Expand All @@ -47,6 +50,7 @@ rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
embassy-sync = { version = "0.8.0", path = "../embassy-sync" }
embassy-executor = { version = "0.10.0", path = "../embassy-executor", optional = true }
# TODO: Support other tick rates
embassy-time-driver = { version = "0.2.2", path = "../embassy-time-driver", optional = true, features = ["tick-hz-32_768"] }
embassy-time-queue-utils = { version = "0.3.2", path = "../embassy-time-queue-utils", optional = true }
Expand Down Expand Up @@ -116,6 +120,14 @@ nrst-pin-as-gpio = []
## Allow using the SWD pins as regular GPIO pins.
swd-pins-as-gpio = []

low-power = []

executor-thread = ["_executor"]

executor-interrupt = ["_executor"]

_executor = ["dep:embassy-executor", "low-power"]

#! ## Time

# Features starting with `_` are for internal use only. They're not intended
Expand Down
253 changes: 253 additions & 0 deletions embassy-mspm0/src/executor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
//! MSPM0-specific `embassy-executor` platform.
//!
//! This module provides an `embassy-executor` platform specific for MSPM0 chips that integrates [`low_power::sleep()`](crate::low_power::sleep) in the main loop.
//! Read the `embassy-executor` README for information about what "executor platforms" are and how they work.
//!
//! To use it:
//! - Enable the `executor-thread` and/or `executor-interrupt` feature on this crate.
//! - **Do not** enable features `platform-cortex-m`, `executor-thread` or `executor-interrupt` in the `embassy-executor` crate.
//! - Tell the `main` macro to use this executor like this:
//!
//! ```rust,no_run
//! #[embassy_executor::main(executor = "embassy_mspm0::executor::Executor", entry = "cortex_m_rt::entry")]
//! async fn main(spawner: Spawner) {
//! let p = embassy_mspm0::init(Config::default());
//! // ...
//! }
//! ```

#[unsafe(export_name = "__pender")]
#[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))]
fn __pender(context: *mut ()) {
// `context` is either `THREAD_PENDER`, or an interrupt number passed to `InterruptExecutor::start`.
let context = context as usize;

#[cfg(feature = "executor-thread")]
// Try to optimize away the branch when only thread mode is enabled.
if !cfg!(feature = "executor-interrupt") || context == thread::THREAD_PENDER {
thread::SIGNAL_WORK_THREAD_MODE.store(true, core::sync::atomic::Ordering::SeqCst);
return;
}

#[cfg(feature = "executor-interrupt")]
{
use cortex_m::interrupt::InterruptNumber;
use cortex_m::peripheral::NVIC;

#[derive(Clone, Copy)]
struct Irq(u16);

// SAFETY: `context` was an `InterruptNumber` when passed to `InterruptExecutor::start`.
unsafe impl InterruptNumber for Irq {
fn number(self) -> u16 {
self.0
}
}

// MSPM0 is Cortex-M0+, which has no STIR.
NVIC::pend(Irq(context as u16));
}
}

#[cfg(feature = "executor-thread")]
pub use thread::*;
#[cfg(feature = "executor-thread")]
mod thread {
use core::marker::PhantomData;
use core::sync::atomic::{AtomicBool, Ordering};

use embassy_executor::{Spawner, raw};

pub(super) const THREAD_PENDER: usize = usize::MAX;

/// Set by the pender to signal pending work; checked before sleeping since `WFI` ignores `SEV`.
pub(crate) static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false);

/// Thread-mode executor that deep-sleeps on idle via [`low_power::sleep`](crate::low_power::sleep).
///
/// This is the simplest and most common kind of executor. It runs on
/// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction
/// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction
/// is executed, to make the `WFE` exit from sleep and poll the task.
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
}

impl Executor {
/// Create a new Executor.
pub fn new() -> Self {
Self {
inner: raw::Executor::new(THREAD_PENDER as *mut ()),
not_send: PhantomData,
}
}

/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it to spawn the initial task(s). After `init` returns,
/// the executor starts running the tasks.
///
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
/// for example by passing it as an argument to the initial tasks.
///
/// This function requires `&'static mut self`. This means you have to store the
/// Executor instance in a place where it'll live forever and grants you mutable
/// access. There's a few ways to do this:
///
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
/// - a `static mut` (unsafe)
/// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
///
/// This function never returns.
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
init(self.inner.spawner());

loop {
unsafe {
self.inner.poll();

critical_section::with(|cs| {
if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) {
SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst);
} else {
crate::low_power::sleep(cs);
}
});
}
}
}
}

impl Default for Executor {
fn default() -> Self {
Self::new()
}
}
}

#[cfg(feature = "executor-interrupt")]
pub use interrupt::*;
#[cfg(feature = "executor-interrupt")]
mod interrupt {
use core::cell::{Cell, UnsafeCell};
use core::mem::MaybeUninit;

use cortex_m::interrupt::InterruptNumber;
use cortex_m::peripheral::NVIC;
use critical_section::Mutex;
use embassy_executor::raw;

/// Interrupt-mode executor.
///
/// This executor runs tasks in interrupt mode. The interrupt handler is set up
/// to poll tasks, and when a task is woken the interrupt is pended from software.
///
/// This allows running async tasks at a priority higher than thread mode. One
/// use case is to leave thread mode free for non-async tasks. Another use case is
/// to run multiple executors: one in thread mode for low priority tasks and another in
/// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower
/// priority ones.
///
/// It is even possible to run multiple interrupt mode executors at different priorities,
/// by assigning different priorities to the interrupts.
///
/// To use it, you have to pick an interrupt that won't be used by the hardware.
/// MSPM0 has no dedicated software interrupt, so use the interrupt of a peripheral the
/// application leaves unused.
///
/// It is somewhat more complex to use, it's recommended to use the thread-mode
/// `Executor` instead, if it works for your use case.
pub struct InterruptExecutor {
started: Mutex<Cell<bool>>,
executor: UnsafeCell<MaybeUninit<raw::Executor>>,
}

unsafe impl Send for InterruptExecutor {}
unsafe impl Sync for InterruptExecutor {}

impl InterruptExecutor {
/// Create a new, not started `InterruptExecutor`.
#[inline]
pub const fn new() -> Self {
Self {
started: Mutex::new(Cell::new(false)),
executor: UnsafeCell::new(MaybeUninit::uninit()),
}
}

/// Executor interrupt callback.
///
/// # Safety
///
/// - You MUST call this from the interrupt handler, and from nowhere else.
/// - You must not call this before calling `start()`.
pub unsafe fn on_interrupt(&'static self) {
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
executor.poll();
}

/// Start the executor.
///
/// This initializes the executor, enables the interrupt, and returns.
/// The executor keeps running in the background through the interrupt.
///
/// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`]
/// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a
/// different "thread" (the interrupt), so spawning tasks on it is effectively
/// sending them.
///
/// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from
/// a task running in it.
///
/// # Interrupt requirements
///
/// You must write the interrupt handler yourself, and make it call [`on_interrupt()`](Self::on_interrupt).
///
/// This method already enables (unmasks) the interrupt, you must NOT do it yourself.
///
/// You must set the interrupt priority before calling this method. You MUST NOT
/// do it after.
///
/// [`SendSpawner`]: embassy_executor::SendSpawner
pub fn start(&'static self, irq: impl InterruptNumber) -> embassy_executor::SendSpawner {
if critical_section::with(|cs| self.started.borrow(cs).replace(true)) {
panic!("InterruptExecutor::start() called multiple times on the same executor.");
}

unsafe {
(&mut *self.executor.get())
.as_mut_ptr()
.write(raw::Executor::new(irq.number() as *mut ()))
}

let executor = unsafe { (&*self.executor.get()).assume_init_ref() };

unsafe { NVIC::unmask(irq) }

executor.spawner().make_send()
}

/// Get a SendSpawner for this executor
///
/// This returns a [`SendSpawner`](embassy_executor::SendSpawner) you can use to spawn tasks on this
/// executor.
///
/// This MUST only be called on an executor that has already been started.
/// The function will panic otherwise.
pub fn spawner(&'static self) -> embassy_executor::SendSpawner {
if !critical_section::with(|cs| self.started.borrow(cs).get()) {
panic!("InterruptExecutor::spawner() called on uninitialized executor.");
}
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
executor.spawner().make_send()
}
}

impl Default for InterruptExecutor {
fn default() -> Self {
Self::new()
}
}
}
5 changes: 5 additions & 0 deletions embassy-mspm0/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ mod macros;

pub mod adc;
pub mod dma;
#[cfg(feature = "_executor")]
pub mod executor;
pub mod gpio;
// TODO: I2C unicomm
#[cfg(not(unicomm))]
pub mod i2c;
#[cfg(not(unicomm))]
pub mod i2c_target;
#[cfg(feature = "low-power")]
pub mod low_power;
#[cfg(any(mspm0g150x, mspm0g151x, mspm0g350x, mspm0g351x))]
pub mod mathacl;
pub mod sysctl;
Expand Down Expand Up @@ -194,6 +198,7 @@ pub fn init(config: Config) -> Peripherals {
w.set_mfpclken(true);
});

// TODO: Errata PCMU_ERR_03 states that BOR thresholds other than 0 don't work in STANDBY.
pac::SYSCTL.borthreshold().modify(|w| {
w.set_level(0);
});
Expand Down
70 changes: 70 additions & 0 deletions embassy-mspm0/src/low_power/c110x.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//! C-series deep sleep: STOP0/2 + STANDBY0/1 (no STOP1).
//!
//! Covers mspm0c110x and mspm0c1105/c1106. These families lack the STOP1 (4 MHz SYSOSC) sub-mode,
//! and their STOP0 additionally clears `USELFCLK`.
//!
//! The entry sequence from the TRM is:
//! `PMODECFG.DSLEEP` selects STOP vs STANDBY,
//! `SYSOSCCFG.DISABLESTOP` selects STOP0 vs STOP2 (this family has no 4 MHz STOP1) with STOP0 also clearing `MCLKCFG.USELFCLK`,
//! `MCLKCFG.STOPCLKSTBY` selects STANDBY0 vs STANDBY1.

use critical_section::CriticalSection;
use pac::sysctl::vals::Dsleep;

use crate::pac;

/// Deep-sleep idle modes, ordered by increasing power saving.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SleepMode {
/// SYSOSC available. Fastest wake, highest STOP current.
Stop0,
/// SYSOSC disabled; ULPCLK runs from LFCLK. Lowest STOP current.
Stop2,
/// low-speed peripherals retained.
Standby0,
/// only TIMG0/TIMG1 remain clocked. Lowest wake-capable current.
Standby1,
}

/// Enter a deep-sleep `mode` and block until an interrupt wakes the core.
///
/// This runs with interrupts masked, but `WFI` still wakes on enabled interrupts with PRIMASK set.
/// They will run once the `CriticalSection` exits.
///
/// # Safety
/// The caller is responsible for ensuring deep sleep is safe right now: no transaction that must survive is in
/// flight (PD1 powers down and its peripherals lose state unless retained by the mode), and a wake source is armed.
pub unsafe fn enter_sleep(_cs: CriticalSection, mode: SleepMode) {
let sysctl = pac::SYSCTL;

let dsleep = match mode {
SleepMode::Stop0 | SleepMode::Stop2 => Dsleep::STOP,
SleepMode::Standby0 | SleepMode::Standby1 => Dsleep::STANDBY,
};
sysctl.pmodecfg().modify(|w| w.set_dsleep(dsleep));

match mode {
SleepMode::Stop0 => {
sysctl.sysosccfg().modify(|w| w.set_disablestop(false));
sysctl.mclkcfg().modify(|w| w.set_uselfclk(false));
}
SleepMode::Stop2 => sysctl.sysosccfg().modify(|w| w.set_disablestop(true)),
SleepMode::Standby0 => sysctl.mclkcfg().modify(|w| w.set_stopclkstby(false)),
SleepMode::Standby1 => sysctl.mclkcfg().modify(|w| w.set_stopclkstby(true)),
}

super::arm_and_wait();
}

/// Map the family-independent [`SleepLevel`](super::SleepLevel) to this family's [`SleepMode`].
pub(super) fn level_to_mode(level: super::SleepLevel) -> SleepMode {
use super::SleepLevel;

match level {
SleepLevel::Stop0 | SleepLevel::Stop1 => SleepMode::Stop0,
SleepLevel::Stop2 => SleepMode::Stop2,
SleepLevel::Standby0 => SleepMode::Standby0,
SleepLevel::Standby1 => SleepMode::Standby1,
}
}
Loading