Skip to content

Commit

Permalink
Embassy enable thread and interrupt by default, enable embassy when b…
Browse files Browse the repository at this point in the history
…uilding docs (#1485)

* Remove interrupt and thread executor embassy features

* Reserve sw interrupt 3 (4) instead of 0 for multicore systems with the embassy feature enabled

* Remove uneeded #[feature()] from examples

* Fix HIL tests

* Add thread mode context id and fix up examples

* improve embassy module docs

* changelog

* fixup hil tests

* Fixup usb examples
  • Loading branch information
MabezDev authored May 2, 2024
1 parent edd0371 commit f32565b
Show file tree
Hide file tree
Showing 31 changed files with 146 additions and 204 deletions.
4 changes: 4 additions & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- i2c: i2c1_handler used I2C0 register block by mistake (#1487)
- Removed ESP32 specific code for resolutions > 16 bit in ledc embedded_hal::pwm max_duty_cycle function. (#1441)
- Fixed division by zero in ledc embedded_hal::pwm set_duty_cycle function and converted to set_duty_hw instead of set_duty to eliminate loss of granularity. (#1441)
- Embassy examples now build on stable (#1485)

### Changed

Expand All @@ -32,6 +33,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Make software interrupts shareable (#1500)
- The `SystemParts` struct has been renamed to `SystemControl`, and now has a constructor which takes the `SYSTEM` peripheral (#1495)
- Timer abstraction: refactor `systimer` and `timer` modules into a common `timer` module (#1527)
- Removed the `embassy-executor-thread` and `embassy-executor-interrupt` features, they are now enabled by default when `embassy` is enabled. (#1485)
- Software interrupt 3 is now used instead of software interrupt 0 on the thread aware executor on multicore systems (#1485)
- Timer abstraction: refactor `systimer` and `timer` modules into a common `timer` module (#1527)

### Removed

Expand Down
8 changes: 3 additions & 5 deletions esp-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,7 @@ ufmt = ["dep:ufmt-write"]

#! ### Embassy Feature Flags
## Enable support for `embassy`, a modern asynchronous embedded framework.
embassy = ["embassy-time-driver", "procmacros/embassy"]
## Use the interrupt-mode embassy executor.
embassy-executor-interrupt = ["embassy", "embassy-executor"]
## Use the thread-mode embassy executor.
embassy-executor-thread = ["embassy", "embassy-executor"]
embassy = ["embassy-time-driver", "procmacros/embassy", "embassy-executor"]
## Uses hardware timers as alarms for the executors. Using this feature
## limits the number of executors to the number of hardware alarms provided
## by the time driver.
Expand Down Expand Up @@ -217,6 +213,8 @@ ci = [
"embedded-hal-02",
"ufmt",
"async",
"embassy",
"embassy-time-timg0",
]

[lints.clippy]
Expand Down
43 changes: 14 additions & 29 deletions esp-hal/src/embassy/executor/mod.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,26 @@
#[cfg(feature = "embassy-executor-thread")]
pub mod thread;
mod interrupt;
mod thread;

#[cfg(feature = "embassy-executor-thread")]
pub use thread::*;

#[cfg(feature = "embassy-executor-interrupt")]
pub mod interrupt;

#[cfg(feature = "embassy-executor-interrupt")]
pub use interrupt::*;
pub use thread::*;

#[cfg(any(
feature = "embassy-executor-thread",
feature = "embassy-executor-interrupt",
))]
#[export_name = "__pender"]
fn __pender(context: *mut ()) {
#[cfg(feature = "embassy-executor-interrupt")]
use crate::system::SoftwareInterrupt;

let context = (context as usize).to_le_bytes();

cfg_if::cfg_if! {
if #[cfg(feature = "embassy-executor-interrupt")] {
match context[0] {
#[cfg(feature = "embassy-executor-thread")]
0 => thread::pend_thread_mode(context[1] as usize),

#[cfg(not(feature = "embassy-executor-thread"))]
0 => unsafe { SoftwareInterrupt::<0>::steal().raise() },
1 => unsafe { SoftwareInterrupt::<1>::steal().raise() },
2 => unsafe { SoftwareInterrupt::<2>::steal().raise() },
3 => unsafe { SoftwareInterrupt::<3>::steal().raise() },
_ => {}
}
} else {
pend_thread_mode(context[1] as usize);
match context[0] {
// For interrupt executors, the context value is the
// software interrupt number
0 => unsafe { SoftwareInterrupt::<0>::steal().raise() },
1 => unsafe { SoftwareInterrupt::<1>::steal().raise() },
2 => unsafe { SoftwareInterrupt::<2>::steal().raise() },
3 => unsafe { SoftwareInterrupt::<3>::steal().raise() },
other => {
assert_eq!(other, THREAD_MODE_CONTEXT);
// THREAD_MODE_CONTEXT id is reserved for thread mode executors
thread::pend_thread_mode(context[1] as usize)
}
}
}
44 changes: 32 additions & 12 deletions esp-hal/src/embassy/executor/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use crate::get_core;
#[cfg(multi_core)]
use crate::peripherals::SYSTEM;

pub(crate) const THREAD_MODE_CONTEXT: u8 = 16;

/// global atomic used to keep track of whether there is work to do since sev()
/// is not available on either Xtensa or RISC-V
#[cfg(not(multi_core))]
Expand All @@ -19,15 +21,15 @@ static SIGNAL_WORK_THREAD_MODE: [AtomicBool; 2] = [AtomicBool::new(false), Atomi

#[cfg(multi_core)]
#[handler]
fn software0_interrupt() {
fn software3_interrupt() {
// This interrupt is fired when the thread-mode executor's core needs to be
// woken. It doesn't matter which core handles this interrupt first, the
// point is just to wake up the core that is currently executing
// `waiti`.
let system = unsafe { &*SYSTEM::PTR };
system
.cpu_intr_from_cpu_0()
.write(|w| w.cpu_intr_from_cpu_0().bit(false));
.cpu_intr_from_cpu_3()
.write(|w| w.cpu_intr_from_cpu_3().bit(false));
}

pub(crate) fn pend_thread_mode(core: usize) {
Expand All @@ -44,31 +46,49 @@ pub(crate) fn pend_thread_mode(core: usize) {

let system = unsafe { &*SYSTEM::PTR };
system
.cpu_intr_from_cpu_0()
.write(|w| w.cpu_intr_from_cpu_0().bit(true));
.cpu_intr_from_cpu_3()
.write(|w| w.cpu_intr_from_cpu_3().bit(true));
}
}

/// Multi-core Xtensa Executor
/// A thread aware Executor
#[cfg_attr(
multi_core,
doc = r#"
This executor is capable of waking an
executor running on another core if work
needs to be completed there for a task to
progress on this core.
"#
)]
pub struct Executor {
inner: raw::Executor,
not_send: PhantomData<*mut ()>,
}

impl Executor {
/// Create a new Executor.
///
/// On multi_core systems this will use software-interrupt 0 which isn't
/// available for anything else.
#[cfg_attr(
multi_core,
doc = r#"
This will use software-interrupt 3 which isn't
available for anything else to wake the other core(s).
"#
)]
pub fn new() -> Self {
#[cfg(multi_core)]
unsafe {
crate::system::SoftwareInterrupt::<0>::steal()
.set_interrupt_handler(software0_interrupt)
crate::system::SoftwareInterrupt::<3>::steal()
.set_interrupt_handler(software3_interrupt)
}

Self {
inner: raw::Executor::new(usize::from_le_bytes([0, get_core() as u8, 0, 0]) as *mut ()),
inner: raw::Executor::new(usize::from_le_bytes([
THREAD_MODE_CONTEXT,
get_core() as u8,
0,
0,
]) as *mut ()),
not_send: PhantomData,
}
}
Expand Down
96 changes: 21 additions & 75 deletions esp-hal/src/embassy/mod.rs
Original file line number Diff line number Diff line change
@@ -1,81 +1,27 @@
//! # Embassy driver
//! # Embassy
//!
//! ## Overview
//! The `embassy` driver for ESP chips is an essential part of the Embassy
//! embedded async/await runtime and is used by applications to perform
//! time-based operations and schedule asynchronous tasks. It provides a
//! high-level API for handling timers and alarms, abstracting the underlying
//! hardware details, and allowing users to focus on application logic rather
//! than low-level timer management.
//! The [embassy](https://github.com/embassy-rs/embassy) project is a toolkit to leverage async Rust
//! in embedded applications. This module adds the required
//! support to use embassy on Espressif chips.
//!
//! Here are important details about the module:
//! * `time_driver` module (`time_driver_systimer` or `time_driver_timg`,
//! depends on enabled feature)
//! - This module contains the implementations of the timer drivers for
//! different ESP chips.<br> It includes the `EmbassyTimer` struct, which
//! is responsible for handling alarms and timer events.
//! - `EmbassyTimer` struct represents timer driver for ESP chips. It
//! contains `alarms` - an array of `AlarmState` structs, which describe
//! the state of alarms associated with the timer driver.
//! * `AlarmState` struct
//! - This struct represents the state of an alarm. It contains information
//! about the alarm's timestamp, a callback function to be executed when
//! the alarm triggers, and a context pointer for passing user-defined
//! data to the callback.
//! * `executor` module
//! - This module contains the implementations of a multi-core safe
//! thread-mode and an interrupt-mode executor for Xtensa-based ESP chips.
//! ## Initialization
//!
//! ## Example
//! The following example demonstrates how to use the `embassy` driver to
//! schedule asynchronous tasks.<br> In this example, we use the `embassy`
//! driver to wait for a GPIO 9 pin state to change.
//! Embassy **must** be initialized by calling [`init`], before beginning any
//! async operations.
//!
//! ```no_run
//! #[cfg(feature = "embassy-time-systick")]
//! embassy::init(
//! &clocks,
//! esp_hal::systimer::SystemTimer::new(peripherals.SYSTIMER),
//! );
//! [`init`] installs a [global time driver](https://github.com/embassy-rs/embassy/tree/main/embassy-time#global-time-driver)
//! allowing users to use [embassy-time](https://docs.rs/embassy-time/latest/embassy_time/) APIs in any async context
//! within their application. A time driver must be chosen by enabling the
//! correct feature on esp-hal, see the crate level documentation for more
//! details.
//!
//! #[cfg(feature = "embassy-time-timg0")]
//! embassy::init(&clocks, timer_group0.timer0);
//! ## Executors
//!
//! let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
//! // GPIO 9 as input
//! let input = io.pins.gpio9.into_pull_down_input();
//!
//! // Async requires the GPIO interrupt to wake futures
//! esp_hal::interrupt::enable(
//! esp_hal::peripherals::Interrupt::GPIO,
//! esp_hal::interrupt::Priority::Priority1,
//! )
//! .unwrap();
//!
//! let executor = make_static!(Executor::new());
//! executor.run(|spawner| {
//! spawner.spawn(ping(input)).ok();
//! });
//! ```
//!
//! Where `ping` defined as:
//! ```no_run
//! async fn ping(mut pin: Gpio9<Input<PullDown>>) {
//! loop {
//! esp_println::println!("Waiting...");
//! pin.wait_for_rising_edge().await.unwrap();
//! esp_println::println!("Ping!");
//! Timer::after(Duration::from_millis(100)).await;
//! }
//! }
//! ```
//! For more embassy-related examples check out the [examples repo](https://github.com/esp-rs/esp-hal/tree/main/esp32-hal/examples)
//! for a corresponding board.
//! We offer two executor types, a thread mode [`Executor`](executor::Executor)
//! and [`InterruptExecutor`](executor::InterruptExecutor).
//! An [`InterruptExecutor`](executor::InterruptExecutor) can be used to achieve
//! preemptive multitasking in async applications, which is usually something reserved for more traditional RTOS systems, read more about it in [the embassy documentation](https://embassy.dev/book/dev/runtime.html).
#[cfg(any(
feature = "embassy-executor-interrupt",
feature = "embassy-executor-thread"
))]
pub mod executor;

use core::cell::Cell;
Expand All @@ -102,12 +48,12 @@ use time_driver::EmbassyTimer;

use crate::clock::Clocks;

/// Initialise embassy
pub fn init(clocks: &Clocks, td: time_driver::TimerType) {
EmbassyTimer::init(clocks, td)
/// Initialize embassy
pub fn init(clocks: &Clocks, time_driver: time_driver::TimerType) {
EmbassyTimer::init(clocks, time_driver)
}

pub struct AlarmState {
pub(crate) struct AlarmState {
pub callback: Cell<Option<(fn(*mut ()), *mut ())>>,
pub allocated: Cell<bool>,
}
Expand Down
29 changes: 16 additions & 13 deletions esp-hal/src/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,30 +229,33 @@ impl<const NUM: u8> crate::peripheral::Peripheral for SoftwareInterrupt<NUM> {
impl<const NUM: u8> crate::private::Sealed for SoftwareInterrupt<NUM> {}

/// This gives access to the available software interrupts.
///
/// Please note: Software interrupt 0 is not available when using the
/// `embassy-executor-thread` feature
#[cfg_attr(
multi_core,
doc = r#"
Please note: Software interrupt 3 is reserved
for inter-processor communication when the `embassy`
feature is enabled."#
)]
#[non_exhaustive]
pub struct SoftwareInterruptControl {
#[cfg(not(all(feature = "embassy-executor-thread", multi_core)))]
pub software_interrupt0: SoftwareInterrupt<0>,
pub software_interrupt1: SoftwareInterrupt<1>,
pub software_interrupt2: SoftwareInterrupt<2>,
#[cfg(not(all(feature = "embassy", multi_core)))]
pub software_interrupt3: SoftwareInterrupt<3>,
}

impl SoftwareInterruptControl {
fn new() -> Self {
// the thread-executor uses SW-INT0 when used on a multi-core system
// we cannot easily require `software_interrupt0` there since it's created
// before `main` via proc-macro

SoftwareInterruptControl {
#[cfg(not(all(feature = "embassy-executor-thread", multi_core)))]
software_interrupt0: SoftwareInterrupt,
software_interrupt1: SoftwareInterrupt,
software_interrupt2: SoftwareInterrupt,
software_interrupt3: SoftwareInterrupt,
software_interrupt0: SoftwareInterrupt {},
software_interrupt1: SoftwareInterrupt {},
software_interrupt2: SoftwareInterrupt {},
// the thread-executor uses SW-INT3 when used on a multi-core system
// we cannot easily require `software_interrupt3` there since it's created
// before `main` via proc-macro so we cfg it away from users
#[cfg(not(all(feature = "embassy", multi_core)))]
software_interrupt3: SoftwareInterrupt {},
}
}
}
Expand Down
3 changes: 0 additions & 3 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,6 @@ embedded-hal = ["esp-hal/embedded-hal"]

embassy = ["esp-hal/embassy"]

embassy-executor-thread = ["esp-hal/embassy-executor-thread"]
embassy-executor-interrupt = ["esp-hal/embassy-executor-interrupt"]

embassy-time-systick-16mhz = ["esp-hal/embassy-time-systick-16mhz"]
embassy-time-timg0 = ["esp-hal/embassy-time-timg0"]
embassy-generic-timers = ["embassy-time/generic-queue-8"]
Expand Down
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ To demonstrated, in `src/bin/embassy_hello_world.rs` you will see the following:

```rust
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: embassy embassy-generic-timers embassy-time-timg0 embassy-executor-thread
//% FEATURES: embassy embassy-generic-timers embassy-time-timg0
```

Another thing to be aware of is the GPIO pins being used. We have tried to use pins available the DevKit-C boards from Espressif, however this is being done on a best-effort basis.
Expand Down
3 changes: 1 addition & 2 deletions examples/src/bin/embassy_hello_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
//! concurrently.
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: embassy embassy-time-timg0 embassy-executor-thread embassy-generic-timers
//% FEATURES: embassy embassy-time-timg0 embassy-generic-timers

#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]

use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
Expand Down
3 changes: 1 addition & 2 deletions examples/src/bin/embassy_hello_world_systimer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
//! It's not supported on ESP32, on ESP32-S2 the frequency of the systimer is different (so it's left out here)
//% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2 esp32s3
//% FEATURES: embassy embassy-time-systick-16mhz embassy-executor-thread embassy-generic-timers
//% FEATURES: embassy embassy-time-systick-16mhz embassy-generic-timers

#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]

use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
Expand Down
Loading

0 comments on commit f32565b

Please sign in to comment.