esp_hal_embassy/
lib.rs

1//! Embassy support for [esp-hal].
2//!
3//! [Embassy] is a modern asynchronous framework intended for use with embedded
4//! systems. This package provides support for building applications using
5//! Embassy with [esp-hal].
6//!
7//! Note that this crate currently requires you to enable the `unstable` feature
8//! on `esp-hal`.
9//!
10//! [esp-hal]: https://github.com/esp-rs/esp-hal
11//! [embassy]: https://github.com/embassy-rs/embassy
12//!
13//! ## Executors
14//!
15//! Two types of executors are provided:
16//!
17//! - [Executor]: A thread-mode executor
18//! - [InterruptExecutor]: An interrupt-mode executor
19//!
20//! [InterruptExecutor] can be used to achieve preemptive multitasking in
21//! asynchronous applications, which is typically something reserved for more
22//! traditional RTOS. More information can be found in the [Embassy
23//! documentation].
24//!
25//! [embassy documentation]: https://embassy.dev/book/
26//!
27//! ## Initialization
28//!
29//! Embassy **must** be initialized by calling the [init] function. This
30//! initialization must be performed *prior* to spawning any tasks.
31//!
32//! Initialization requires a number of timers to be passed in. The number of
33//! timers required depends on the timer queue flavour used, as well as the
34//! number of executors started. If you use the `multiple-integrated` timer
35//! queue flavour, then you need to pass as many timers as you start executors.
36//! In other cases, you can pass a single timer.
37//!
38//! ## Configuration
39//!
40//! You can configure the behaviour of the embassy runtime by using the
41//! following environment variables:
42#![doc = ""]
43#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_hal_embassy_config_table.md"))]
44#![doc = ""]
45//! ## Feature Flags
46#![doc = document_features::document_features!()]
47#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
48#![deny(missing_docs, rust_2018_idioms, rustdoc::all)]
49#![cfg_attr(xtensa, feature(asm_experimental_arch))]
50#![no_std]
51
52// MUST be the first module
53mod fmt;
54
55use esp_hal::timer::{AnyTimer, timg::Timer as TimgTimer};
56pub use macros::embassy_main as main;
57
58#[cfg(feature = "executors")]
59pub use self::executor::{Callbacks, Executor, InterruptExecutor};
60use self::time_driver::{EmbassyTimer, Timer};
61
62#[cfg(feature = "executors")]
63pub(crate) mod executor;
64mod time_driver;
65mod timer_queue;
66
67macro_rules! mk_static {
68    ($t:ty,$val:expr) => {{
69        static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
70        #[deny(unused_attributes)]
71        let x = STATIC_CELL.uninit().write(($val));
72        x
73    }};
74}
75
76mod private {
77    pub trait Sealed {}
78
79    #[derive(Clone, Copy)]
80    pub struct Internal;
81}
82
83/// A timer or collection on timers that can be passed to [`init`].
84pub trait TimeBase: private::Sealed {
85    #[doc(hidden)]
86    fn timers(self, _: private::Internal) -> &'static mut [Timer];
87}
88
89macro_rules! impl_timebase {
90    ($timebase:path) => {
91        impl private::Sealed for $timebase {}
92
93        impl TimeBase for $timebase {
94            fn timers(self, _: private::Internal) -> &'static mut [Timer] {
95                mk_static!([Timer; 1], [Timer::new(self)])
96            }
97        }
98    };
99}
100
101macro_rules! impl_timebase_array {
102    ($timebase:path, $n:literal) => {
103        impl private::Sealed for [$timebase; $n] {}
104
105        impl TimeBase for [$timebase; $n] {
106            fn timers(self, _: private::Internal) -> &'static mut [Timer] {
107                mk_static!([Timer; $n], self.map(|t| Timer::new(t)))
108            }
109        }
110    };
111}
112
113macro_rules! impl_array {
114    ($n:literal) => {
115        impl private::Sealed for [Timer; $n] {}
116
117        impl TimeBase for [Timer; $n] {
118            fn timers(self, _: private::Internal) -> &'static mut [Timer] {
119                mk_static!([Timer; $n], self)
120            }
121        }
122
123        impl_timebase_array!(AnyTimer<'static>, $n);
124        impl_timebase_array!(TimgTimer<'static>, $n);
125        #[cfg(systimer)]
126        impl_timebase_array!(esp_hal::timer::systimer::Alarm<'static>, $n);
127    };
128}
129
130impl private::Sealed for Timer {}
131impl TimeBase for Timer {
132    fn timers(self, _: private::Internal) -> &'static mut [Timer] {
133        mk_static!([Timer; 1], [self])
134    }
135}
136
137impl_timebase!(AnyTimer<'static>);
138impl_timebase!(TimgTimer<'static>);
139#[cfg(systimer)]
140impl_timebase!(esp_hal::timer::systimer::Alarm<'static>);
141
142impl private::Sealed for &'static mut [Timer] {}
143impl TimeBase for &'static mut [Timer] {
144    fn timers(self, _: private::Internal) -> &'static mut [Timer] {
145        self
146    }
147}
148
149impl<const N: usize> private::Sealed for &'static mut [Timer; N] {}
150impl<const N: usize> TimeBase for &'static mut [Timer; N] {
151    fn timers(self, _: private::Internal) -> &'static mut [Timer] {
152        self.as_mut()
153    }
154}
155
156impl_array!(1);
157impl_array!(2);
158impl_array!(3);
159impl_array!(4);
160
161/// Initialize embassy.
162///
163/// Call this as soon as possible, before the first timer-related operation.
164///
165/// The time driver can be one of a number of different options:
166///
167/// - A single object of, or a 1-4 element array of the following:
168///   - `esp_hal::timer::timg::Timer`
169#[cfg_attr(systimer, doc = "  - `esp_hal::timer::systimer::Alarm`")]
170///   - `esp_hal::timer::AnyTimer`
171///   - `esp_hal::timer::OneShotTimer`
172/// - A mutable static slice of `OneShotTimer` instances
173/// - A mutable static array of `OneShotTimer` instances
174///
175/// Note that if you use the `multiple-integrated` timer-queue flavour, then
176/// you need to pass as many timers as you start executors. In other cases,
177/// you can pass a single timer.
178///
179/// # Examples
180///
181/// ```rust, no_run
182#[doc = esp_hal::before_snippet!()]
183/// use esp_hal::timer::timg::TimerGroup;
184///
185/// let timg0 = TimerGroup::new(peripherals.TIMG0);
186/// esp_hal_embassy::init(timg0.timer0);
187///
188/// // ... now you can spawn embassy tasks or use `Timer::after` etc.
189/// # }
190/// ```
191pub fn init(time_driver: impl TimeBase) {
192    #[cfg(all(feature = "executors", multi_core, low_power_wait))]
193    unsafe {
194        use esp_hal::interrupt::software::SoftwareInterrupt;
195
196        #[esp_hal::ram]
197        extern "C" fn software3_interrupt() {
198            // This interrupt is fired when the thread-mode executor's core needs to be
199            // woken. It doesn't matter which core handles this interrupt first, the
200            // point is just to wake up the core that is currently executing
201            // `waiti`.
202            unsafe { SoftwareInterrupt::<3>::steal().reset() };
203        }
204
205        esp_hal::interrupt::bind_interrupt(
206            esp_hal::peripherals::Interrupt::FROM_CPU_INTR3,
207            software3_interrupt,
208        );
209    }
210
211    EmbassyTimer::init(time_driver.timers(private::Internal))
212}