diff --git a/esp-alloc/src/lib.rs b/esp-alloc/src/lib.rs index e61879017..5a1883a63 100644 --- a/esp-alloc/src/lib.rs +++ b/esp-alloc/src/lib.rs @@ -442,7 +442,7 @@ impl EspHeapInner { used } - /// Return usage stats for the [Heap]. + /// Return usage stats for the [EspHeap]. /// /// Note: /// [HeapStats] directly implements [Display], so this function can be @@ -603,7 +603,7 @@ impl EspHeap { self.inner.with(|heap| heap.used()) } - /// Return usage stats for the [Heap]. + /// Return usage stats for the [EspHeap]. /// /// Note: /// [HeapStats] directly implements [Display], so this function can be diff --git a/esp-hal-procmacros/src/lib.rs b/esp-hal-procmacros/src/lib.rs index f8ba9744f..148ce2803 100644 --- a/esp-hal-procmacros/src/lib.rs +++ b/esp-hal-procmacros/src/lib.rs @@ -22,8 +22,9 @@ //! sections, such as SRAM or RTC RAM (slow or fast) with different initialization options. See //! its documentation for details. //! -//! - [`embassy::main`](macro@embassy_main) - Creates a new `executor` instance and declares an -//! application entry point spawning the corresponding function body as an async task. +//! - [`esp_rtos::main`](macro@rtos_main) - Creates a new instance of `esp_rtos::embassy::Executor` +//! and declares an application entry point spawning the corresponding function body as an async +//! task. //! //! ## Examples //! diff --git a/esp-hal/MIGRATING-1.0.0-rc.0.md b/esp-hal/MIGRATING-1.0.0-rc.0.md index 3a73f85f6..d713a7869 100644 --- a/esp-hal/MIGRATING-1.0.0-rc.0.md +++ b/esp-hal/MIGRATING-1.0.0-rc.0.md @@ -79,7 +79,9 @@ esp_hal::interrupt::bind_interrupt( ); ``` -## RMT PulseCode changes +## RMT changes + +### RMT PulseCode changes `PulseCode` used to be an extension trait implemented on `u32`. It is now a newtype struct, wrapping `u32`. @@ -113,7 +115,7 @@ let _ = tx_channel.transmit(&tx_data).wait().unwrap(); let _ = rx_channel.transmit(&mut rx_data).wait().unwrap(); ``` -## RMT Channel Changes +### RMT Channel Changes `rmt::Channel` used to have a `Raw: RawChannelAccess` generic parameter, which could be either `ConstChannelAccess` or `DynChannelAccess`. @@ -149,7 +151,7 @@ API as well. +let rx_transaction: RxTransaction<'_, PulseCode> = rx.transmit(&data); ``` -## RMT method changes +### RMT method changes The `rmt::Channel::transmit_continuously` and `rmt::Channel::transmit_continuously_with_loopcount` methods have been merged: @@ -268,9 +270,9 @@ Imports will need to be updated accordingly. Additionally, enum variant naming violations have been resolved, so the `RtcFastClock` and `RtcSlowClock` prefixes will need to be removed from any variants from these enums. -## Direct vectoring changes +## RISC-V interrupt direct vectoring changes -`enable_direct` now requires user to pass handler function to it. +`enable_direct` now requires user to pass handler function to it. ```diff interrupt::enable_direct( @@ -280,5 +282,86 @@ interrupt::enable_direct( + interrupt_handler, ) .unwrap(); +``` -``` \ No newline at end of file +## Async/embassy changes + +> This section affects `esp-hal-embassy` users. Ariel-OS users are not affected by these changes. + +The `esp-hal-embassy` has been discontinued. Embassy is continued to be supported as part of `esp-rtos`. + +### Configuration + +`esp-hal-embassy` configuration options have not been ported to `esp-rtos`. `esp-rtos` by default works with a single integrated timer queue. +To keep using generic timer queues, use the configuration options provided by `embassy-time`. Multiple timer queues (i.e. the previous `multiple-integrated` option) are not supported. + +The `low-power-wait` configuration can be substituted with a custom idle hook. You can specify an idle hook by calling `esp_rtos::start_with_idle_hook`, with a function that just `loop`s. + +### Setup + +The previous `esp_hal_embassy::main` macro has been replaced by `esp_rtos::main`. The `esp_hal_embassy::init` function has been replaced by `esp_rtos::start`, with a different signature; this function should be used for `esp_radio` as well. + +`esp_rtos::start` has a different signature for the different CPU architectures. The function takes a single timer instead of a variable number of timers. + +On Xtensa devices (ESP32/S2/S3): + +```diff +-#[esp_hal_embassy::main] ++#[esp_rtos::main] + async fn main(spawner: Spawner) { + // ... timer setup not shown here. +- esp_hal_embassy::init([timer0, timer1]); ++ esp_rtos::start(timer0); + // ... + } +``` + +On RISC-V devices (ESP32-C2/C3/C6/H2) you'll need to also pass `SoftwareInterrupt<0>` to `esp_rtos::start`: + +```diff ++use esp_hal::interrupt::software::SoftwareInterruptControl; + +-#[esp_hal_embassy::main] ++#[esp_rtos::main] + async fn main(spawner: Spawner) { + // ... timer setup not shown here. +- esp_hal_embassy::init([timer0, timer1]); + ++ let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); ++ esp_rtos::start(timer0, software_interrupt.software_interrupt0); + // ... + } +``` + +### Multi-core support + +`esp_rtos::embassy::Executor` expects to be run in an `esp_rtos` thread. This means it cannot be started on the second core, unless the second core is managed by `esp_rtos`: + +```rust +use esp_hal::system::Stack; +use esp_rtos::embassy::Executor; +use static_cell::StaticCell; + +static APP_CORE_STACK: StaticCell> = StaticCell::new(); +let app_core_stack = APP_CORE_STACK.init(Stack::new()); + +// AFTER esp_rtos::start + +esp_rtos::start_second_core( + peripherals.CPU_CTRL, + sw_int.software_interrupt0, + sw_int.software_interrupt1, + app_core_stack, + move || { + static EXECUTOR: StaticCell = StaticCell::new(); + let executor = EXECUTOR.init(Executor::new()); + executor.run(|spawner| { + // Spawn tasks from here. + }); + }, +); +``` + +### Interrupt executor changes + +Interrupt executors are provided as `esp_rtos::embassy::InterruptExecutor` with no additional changes. diff --git a/esp-rtos/Cargo.toml b/esp-rtos/Cargo.toml index 1fcf09c3e..470102793 100644 --- a/esp-rtos/Cargo.toml +++ b/esp-rtos/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/esp-rs/esp-hal" license = "MIT OR Apache-2.0" [package.metadata.espressif] -doc-config = { features = ["esp-hal/unstable"] } +doc-config = { features = ["esp-hal/unstable", "embassy", "esp-radio"] } check-configs = [ { features = ["esp-hal/unstable"] }, { features = ["esp-hal/unstable", "esp-alloc"] }, @@ -25,7 +25,7 @@ clippy-configs = [ [package.metadata.docs.rs] default-target = "riscv32imac-unknown-none-elf" -features = ["esp32c6"] +features = ["esp32c6", "embassy", "esp-radio"] [lib] bench = false diff --git a/esp-rtos/esp_config.yml b/esp-rtos/esp_config.yml index 526e7852d..638419b6a 100644 --- a/esp-rtos/esp_config.yml +++ b/esp-rtos/esp_config.yml @@ -8,9 +8,3 @@ options: constraints: - type: validator: positive_integer - - - name: stack-guard-value - description: The stack overflow guard value. This must be the same value as esp_hal's stack guard value. - default: - - value: 3740121773 - display_hint: Hex diff --git a/esp-rtos/src/embassy/mod.rs b/esp-rtos/src/embassy/mod.rs index de0191a21..4459e0b64 100644 --- a/esp-rtos/src/embassy/mod.rs +++ b/esp-rtos/src/embassy/mod.rs @@ -1,3 +1,5 @@ +//! OS-aware embassy executors. + use core::{cell::UnsafeCell, mem::MaybeUninit, sync::atomic::Ordering}; use embassy_executor::{SendSpawner, Spawner, raw}; @@ -34,6 +36,9 @@ pub trait Callbacks { fn on_idle(&mut self); } +/// Thread-mode executor. +/// +/// This executor runs in an OS thread. pub struct Executor { /// Signals that work is available. semaphore: Semaphore, @@ -144,6 +149,9 @@ impl Default for 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. +/// +/// Interrupt executors have potentially lower latency than thread-mode executors, but only a +/// limited number can be created. pub struct InterruptExecutor { executor: UnsafeCell>, interrupt: SoftwareInterrupt<'static, SWI>, @@ -207,13 +215,11 @@ impl InterruptExecutor { /// 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 + /// [`SendSpawner`] is returned instead of a [`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) + /// To obtain a [`Spawner`] for this executor, use [`Spawner::for_current_executor`] /// from a task running in it. pub fn start(&'static mut self, priority: Priority) -> SendSpawner { unsafe { diff --git a/esp-rtos/src/lib.rs b/esp-rtos/src/lib.rs index 91af2d9cc..d0ec7b66f 100644 --- a/esp-rtos/src/lib.rs +++ b/esp-rtos/src/lib.rs @@ -1,6 +1,13 @@ -//! This crate allows using esp-radio on top of esp-hal, without any other OS. +#![cfg_attr( + all(docsrs, not(not_really_docsrs)), + doc = "

You might want to browse the esp-hal documentation on the esp-rs website instead.

The documentation here on docs.rs is built for a single chip only (ESP32-C6, in particular), while on the esp-rs website you can select your exact chip from the list of supported devices. Available peripherals and their APIs change depending on the chip.

\n\n
\n\n" +)] +//! This crate provides RTOS functionality for `esp-radio`, and provides executors to enable +//! running `async` code. //! -//! This crate requires an esp-hal timer to operate. +//! ## Setup +//! +//! This crate requires an esp-hal timer to operate, and needs to be started like so: //! //! ```rust, no_run //! use esp_hal::timer::timg::TimerGroup; @@ -51,10 +58,17 @@ esp_rtos::start_second_core( //! // let esp_radio_controller = esp_radio::init().unwrap(); //! # } //! ``` +//! +//! To write `async` code, enable the `embassy` feature, and mark the main function with `#[esp_rtos::main]`. +//! Note that, to create async tasks, you will need the `task` macro from the `embassy-executor` crate. Do +//! NOT enable any of the `arch-*` features on `embassy-executor`. +//! //! ## Feature Flags #![doc = document_features::document_features!()] #![no_std] #![cfg_attr(xtensa, feature(asm_experimental_arch))] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![deny(missing_docs)] #[cfg(feature = "alloc")] extern crate alloc; @@ -72,6 +86,7 @@ mod timer; mod wait_queue; #[cfg(feature = "embassy")] +#[cfg_attr(docsrs, doc(cfg(feature = "embassy")))] pub mod embassy; use core::mem::MaybeUninit; @@ -91,6 +106,7 @@ use esp_hal::{ system::{CpuControl, Stack}, time::{Duration, Instant}, }; +#[cfg_attr(docsrs, doc(cfg(feature = "embassy")))] pub use macros::rtos_main as main; pub(crate) use scheduler::SCHEDULER; pub use task::CurrentThreadHandle; diff --git a/esp-rtos/src/semaphore.rs b/esp-rtos/src/semaphore.rs index b2fc30b7f..973ec13f3 100644 --- a/esp-rtos/src/semaphore.rs +++ b/esp-rtos/src/semaphore.rs @@ -1,3 +1,7 @@ +//! Semaphores and mutexes. +//! +//! This module provides the [`Semaphore`] type, which implements counting semaphores and mutexes. + use esp_hal::{ system::Cpu, time::{Duration, Instant}, @@ -131,11 +135,13 @@ impl SemaphoreInner { } } +/// Semaphore and mutex primitives. pub struct Semaphore { inner: NonReentrantMutex, } impl Semaphore { + /// Create a new counting semaphore. pub const fn new_counting(initial: u32, max: u32) -> Self { Semaphore { inner: NonReentrantMutex::new(SemaphoreInner::Counting { @@ -146,6 +152,9 @@ impl Semaphore { } } + /// Create a new mutex. + /// + /// If `recursive` is true, the mutex can be locked multiple times by the same task. pub const fn new_mutex(recursive: bool) -> Self { Semaphore { inner: NonReentrantMutex::new(SemaphoreInner::Mutex { @@ -158,10 +167,19 @@ impl Semaphore { } } + /// Try to take the semaphore. + /// + /// This is a non-blocking operation. pub fn try_take(&self) -> bool { self.inner.with(|sem| sem.try_take()) } + /// Take the semaphore. + /// + /// This is a blocking operation. + /// + /// If the semaphore is already taken, the task will be blocked until the semaphore is released. + /// Recursive mutexes can be locked multiple times by the mutex owner task. pub fn take(&self, timeout_us: Option) -> bool { let deadline = timeout_us.map(|us| Instant::now() + Duration::from_micros(us as u64)); loop { @@ -196,10 +214,12 @@ impl Semaphore { } } + /// Return the current count of the semaphore. pub fn current_count(&self) -> u32 { self.inner.with(|sem| sem.current_count()) } + /// Unlock the semaphore. pub fn give(&self) -> bool { self.inner.with(|sem| { if sem.try_give() {