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.
35// TODO restore this:
36// If you use the `multiple-integrated` timer
37// queue flavour, then you need to pass as many timers as you start executors.
38// In other cases, you can pass a single timer.
39//! ## Configuration
40//!
41//! You can configure the behaviour of the embassy runtime by using the
42//! following environment variables:
43#![doc = ""]
44#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_hal_embassy_config_table.md"))]
45#![doc = ""]
46//! ## Feature Flags
47#![doc = document_features::document_features!()]
48#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
49#![deny(missing_docs, rust_2018_idioms, rustdoc::all)]
50#![cfg_attr(xtensa, feature(asm_experimental_arch))]
51#![no_std]
52
53// MUST be the first module
54mod fmt;
55
56#[cfg(not(feature = "esp32"))]
57use esp_hal::timer::systimer::Alarm;
58use esp_hal::timer::{timg::Timer as TimgTimer, AnyTimer};
59pub use macros::embassy_main as main;
60
61#[cfg(feature = "executors")]
62pub use self::executor::{Executor, InterruptExecutor};
63use self::time_driver::{EmbassyTimer, Timer};
64
65#[cfg(feature = "executors")]
66pub(crate) mod executor;
67mod time_driver;
68mod timer_queue;
69
70macro_rules! mk_static {
71 ($t:ty,$val:expr) => {{
72 static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
73 #[deny(unused_attributes)]
74 let x = STATIC_CELL.uninit().write(($val));
75 x
76 }};
77}
78
79/// A trait to allow better UX for initializing the timers.
80///
81/// This trait is meant to be used only for the `init` function.
82/// Calling `timers()` multiple times may panic.
83pub trait TimerCollection {
84 /// Returns the timers as a slice.
85 fn timers(self) -> &'static mut [Timer];
86}
87
88/// Helper trait to reduce boilerplate.
89///
90/// We can't blanket-implement for `Into<AnyTimer>` because of possible
91/// conflicting implementations.
92trait IntoAnyTimer: Into<AnyTimer> {}
93
94impl IntoAnyTimer for AnyTimer {}
95
96impl IntoAnyTimer for TimgTimer where Self: Into<AnyTimer> {}
97
98#[cfg(not(feature = "esp32"))]
99impl IntoAnyTimer for Alarm where Self: Into<AnyTimer> {}
100
101impl<T> TimerCollection for T
102where
103 T: IntoAnyTimer,
104{
105 fn timers(self) -> &'static mut [Timer] {
106 Timer::new(self.into()).timers()
107 }
108}
109
110impl TimerCollection for Timer {
111 fn timers(self) -> &'static mut [Timer] {
112 let timers = mk_static!([Timer; 1], [self]);
113 timers.timers()
114 }
115}
116
117impl TimerCollection for &'static mut [Timer] {
118 fn timers(self) -> &'static mut [Timer] {
119 self
120 }
121}
122
123impl<const N: usize> TimerCollection for &'static mut [Timer; N] {
124 fn timers(self) -> &'static mut [Timer] {
125 self.as_mut()
126 }
127}
128
129macro_rules! impl_array {
130 ($n:literal) => {
131 impl<T> TimerCollection for [T; $n]
132 where
133 T: IntoAnyTimer,
134 {
135 fn timers(self) -> &'static mut [Timer] {
136 mk_static!([Timer; $n], self.map(|t| Timer::new(t.into())))
137 }
138 }
139 };
140}
141
142impl_array!(2);
143impl_array!(3);
144impl_array!(4);
145
146/// Initialize embassy.
147///
148/// Call this as soon as possible, before the first timer-related operation.
149///
150/// The time driver can be one of a number of different options:
151///
152/// - A timg `Timer` instance
153/// - A systimer `Alarm` instance
154/// - An `AnyTimer` instance
155/// - A `OneShotTimer` instance
156/// - A mutable static slice of `OneShotTimer` instances
157/// - A mutable static array of `OneShotTimer` instances
158/// - A 2, 3, 4 element array of `AnyTimer` instances
159// TODO: restore this:
160// /// Note that if you use the `multiple-integrated` timer-queue flavour, then
161// /// you need to pass as many timers as you start executors. In other cases,
162// /// you can pass a single timer.
163/// # Examples
164///
165/// ```rust, no_run
166#[doc = esp_hal::before_snippet!()]
167/// use esp_hal::timer::timg::TimerGroup;
168///
169/// let timg0 = TimerGroup::new(peripherals.TIMG0);
170/// esp_hal_embassy::init(timg0.timer0);
171///
172/// // ... now you can spawn embassy tasks or use `Timer::after` etc.
173/// # }
174/// ```
175pub fn init(time_driver: impl TimerCollection) {
176 #[cfg(all(feature = "executors", multi_core, low_power_wait))]
177 unsafe {
178 use esp_hal::interrupt::software::SoftwareInterrupt;
179
180 #[esp_hal::ram]
181 extern "C" fn software3_interrupt() {
182 // This interrupt is fired when the thread-mode executor's core needs to be
183 // woken. It doesn't matter which core handles this interrupt first, the
184 // point is just to wake up the core that is currently executing
185 // `waiti`.
186 unsafe { SoftwareInterrupt::<3>::steal().reset() };
187 }
188
189 esp_hal::interrupt::bind_interrupt(
190 esp_hal::peripherals::Interrupt::FROM_CPU_INTR3,
191 software3_interrupt,
192 );
193 }
194
195 EmbassyTimer::init(time_driver.timers())
196}