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() {