Document esp-rtos just a bit (#4208)

* Slight MG touchup

* Document esp-rtos in migration guide

* Document things

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Improve esp-rtos docs

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Dániel Buga 2025-09-30 13:50:26 +02:00 committed by GitHub
parent 0e7c5f91e6
commit d2d9151949
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 144 additions and 24 deletions

View File

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

View File

@ -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
//!

View File

@ -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<Dir, const CHANNEL: u8>` or `DynChannelAccess<Dir>`.
@ -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();
```
```
## 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<Stack<8192>> = 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<Executor> = 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.

View File

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

View File

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

View File

@ -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<const SWI: u8> {
executor: UnsafeCell<MaybeUninit<raw::Executor>>,
interrupt: SoftwareInterrupt<'static, SWI>,
@ -207,13 +215,11 @@ impl<const SWI: u8> InterruptExecutor<SWI> {
/// 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 {

View File

@ -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 = "<div style='padding:30px;background:#810;color:#fff;text-align:center;'><p>You might want to <a href='https://docs.espressif.com/projects/rust/'>browse the <code>esp-hal</code> documentation on the esp-rs website</a> instead.</p><p>The documentation here on <a href='https://docs.rs'>docs.rs</a> 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.</p></div>\n\n<br/>\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;

View File

@ -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<SemaphoreInner>,
}
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<u32>) -> 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() {