feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
+1
@@ -0,0 +1 @@
|
||||
{"files":{".cargo_vcs_info.json":"8a83ee6cf028c8b0a5814315fee580b03b458a23dc2d39e1a21975852cf2fc99","CHANGELOG.md":"4cac2db23fb802bf2d7e5e66dc1f108a706e26b79d280f612507790e03ab4acb","Cargo.toml":"45950e27807af03f857860894f8b6f9fdc51897479b7f32ccca8a48b521cd531","Cargo.toml.orig":"2358f9028fff44f0971a6409bd5da348e22f4a1e617db996d9d0e8de957b7a83","MIGRATING-0.3.md":"3967d441bad1c90a791eea83a6dcb56d352a12b3fc7b3225df9313dcb393cd2d","MIGRATING-0.4.md":"b2feba195d8279e9a0090821cb9028e703c4fcc2b3038cafdd78c556c82b03fa","README.md":"6944768d98fe8278f0226a5a1aa97f8eafc27470339b5f03cfbb8431f0b84801","build.rs":"b1448257101c4a799f8b574673caebfe8e6e4474c273a47f20d26798eb27eb3e","src/executor/interrupt.rs":"cc7d2e4e10f9b5b269a1214eec2c3b3b922053e2a011fe1813c5617be3c1c9ba","src/executor/mod.rs":"e2ac0459621fafde02d269f6d75dc62e4405c434cf4f5d7a4366c002db67bde8","src/executor/thread.rs":"f59c75d9d64b14d6f3c90e635e5e2733f3fb920a27e499ec481e0764008e8303","src/fmt.rs":"ecadf3ab68ae254b4a766108a63c8b55c7ef4108ee5ab069250daacf44314c78","src/lib.rs":"8c2bcaa88066d1991ddb889bb2107c12cc4996f2a8d6d03dd1d3ce9d97cff760","src/time_driver.rs":"e9e946d749fa69efb2609f67dd10fca1ecdc4256c3f4e9fe9a1d0f4407a1eeb6"},"package":"c7d0f2537ea2ff9bea26a1c8bfe43ad580d4c89febf27189653a9cf95f1f7961"}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"git": {
|
||||
"sha1": "bdfec3781fd59c4d51173706e3cbee3760bbf8b6"
|
||||
},
|
||||
"path_in_vcs": "esp-hal-embassy"
|
||||
}
|
||||
Vendored
+68
@@ -0,0 +1,68 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
### Fixed
|
||||
|
||||
### Removed
|
||||
|
||||
## 0.5.0 - 2024-11-20
|
||||
|
||||
### Added
|
||||
|
||||
- `ESP_HAL_EMBASSY_LOW_POWER_WAIT` configuration option. (#2329)
|
||||
|
||||
### Changed
|
||||
|
||||
- Reduce memory footprint by 4 bytes on multi-core MCUs.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Alarm interrupts are now handled on the core that allocated them. (For executors created on the second core after calling `esp_hal_embassy::init`) (#2451)
|
||||
|
||||
### Removed
|
||||
|
||||
## 0.4.0 - 2024-10-10
|
||||
|
||||
### Changed
|
||||
|
||||
- MSRV bump to 1.79 (#2156)
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed the `clocks` parameter from `esp_hal_embassy::init`. (#1999)
|
||||
|
||||
## 0.3.0 - 2024-08-29
|
||||
|
||||
### Added
|
||||
|
||||
- This package now re-exports the `esp_hal_procmacros::main` macro (#1828)
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated to latest release (`0.6.0`) for `embassy-executor` (#1942)
|
||||
- Changed `init` to accept timers of multiple types (#1957, #2012)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed a bug where the timeout was huge whenever the timestamp at the time of scheduling was already in the past (#1875)
|
||||
- Fixed interrupt executors looping endlessly when `integrated-timers` is used. (#1936)
|
||||
|
||||
## 0.2.0 - 2024-07-15
|
||||
|
||||
### Changed
|
||||
|
||||
- Removed the TIMG and SYSTIMER time drivers, replaced by a generic time driver taking `OneShotTimer<ErasedTimer>` (#1753)
|
||||
|
||||
## 0.1.0 - 2024-06-04
|
||||
|
||||
[Unreleased]: https://github.com/esp-rs/esp-hal/commits/main/esp-hal-embassy?since=2024-11-20
|
||||
Vendored
+104
@@ -0,0 +1,104 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
rust-version = "1.79.0"
|
||||
name = "esp-hal-embassy"
|
||||
version = "0.5.0"
|
||||
build = "build.rs"
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Embassy support for esp-hal"
|
||||
readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/esp-rs/esp-hal"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
default-target = "riscv32imac-unknown-none-elf"
|
||||
features = ["esp32c6"]
|
||||
|
||||
[lib]
|
||||
name = "esp_hal_embassy"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies.critical-section]
|
||||
version = "1.2.0"
|
||||
|
||||
[dependencies.defmt]
|
||||
version = "0.3.8"
|
||||
optional = true
|
||||
|
||||
[dependencies.document-features]
|
||||
version = "0.2.10"
|
||||
|
||||
[dependencies.embassy-executor]
|
||||
version = "0.6.3"
|
||||
optional = true
|
||||
|
||||
[dependencies.embassy-time-driver]
|
||||
version = "0.1.0"
|
||||
features = ["tick-hz-1_000_000"]
|
||||
|
||||
[dependencies.esp-hal]
|
||||
version = "0.22.0"
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4.22"
|
||||
optional = true
|
||||
|
||||
[dependencies.macros]
|
||||
version = "0.15.0"
|
||||
features = ["embassy"]
|
||||
package = "esp-hal-procmacros"
|
||||
|
||||
[dependencies.portable-atomic]
|
||||
version = "1.9.0"
|
||||
|
||||
[dependencies.static_cell]
|
||||
version = "2.1.0"
|
||||
|
||||
[build-dependencies.esp-build]
|
||||
version = "0.1.0"
|
||||
|
||||
[build-dependencies.esp-config]
|
||||
version = "0.2.0"
|
||||
features = ["build"]
|
||||
|
||||
[build-dependencies.esp-metadata]
|
||||
version = "0.4.0"
|
||||
|
||||
[features]
|
||||
default = ["executors"]
|
||||
defmt = [
|
||||
"dep:defmt",
|
||||
"embassy-executor?/defmt",
|
||||
"esp-hal/defmt",
|
||||
]
|
||||
esp32 = ["esp-hal/esp32"]
|
||||
esp32c2 = ["esp-hal/esp32c2"]
|
||||
esp32c3 = ["esp-hal/esp32c3"]
|
||||
esp32c6 = ["esp-hal/esp32c6"]
|
||||
esp32h2 = ["esp-hal/esp32h2"]
|
||||
esp32s2 = ["esp-hal/esp32s2"]
|
||||
esp32s3 = ["esp-hal/esp32s3"]
|
||||
executors = [
|
||||
"dep:embassy-executor",
|
||||
"esp-hal/__esp_hal_embassy",
|
||||
]
|
||||
integrated-timers = ["embassy-executor?/integrated-timers"]
|
||||
log = ["dep:log"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = "allow"
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
# Migration Guide from 0.3.x to v0.4.x
|
||||
|
||||
## Initialization
|
||||
|
||||
You no longer have to set up clocks and pass them to `esp_hal_embassy::init`.
|
||||
|
||||
```diff
|
||||
use esp_hal::{
|
||||
- clock::ClockControl,
|
||||
- peripherals::Peripherals,
|
||||
prelude::*,
|
||||
- system::SystemControl,
|
||||
};
|
||||
|
||||
#[esp_hal_embassy::main]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
- let peripherals = Peripherals::take();
|
||||
- let system = SystemControl::new(peripherals.SYSTEM);
|
||||
- let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
|
||||
+ let peripherals = esp_hal::init(esp_hal::Config::default());
|
||||
|
||||
let timg0 = TimerGroup::new(peripherals.TIMG0);
|
||||
- esp_hal_embassy::init(&clocks, timg0);
|
||||
+ esp_hal_embassy::init(timg0.timer0);
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
You have to specify a timer instance (that may be a `TimerGroup` timer unit
|
||||
or a `SystemTimer` alarm) or an array of `AnyTimer`s when calling `init`.
|
||||
An example of how you can set multiple timers (for example when using
|
||||
multiple executors):
|
||||
|
||||
```rust
|
||||
use esp_hal::{
|
||||
prelude::*,
|
||||
timer::{
|
||||
AnyTimer,
|
||||
systimer::SystemTimer
|
||||
}
|
||||
};
|
||||
|
||||
#[esp_hal_embassy::main]
|
||||
async fn main(_spawner: Spawner) -> ! {
|
||||
let peripherals = esp_hal::init(esp_hal::Config::default());
|
||||
|
||||
let timg0 = TimerGroup::new(peripherals.TIMG0);
|
||||
let timer0: AnyTimer = timg0.timer0.into();
|
||||
let timer1: AnyTimer = timg0.timer1.into();
|
||||
|
||||
// You can use either a TimerGroup timer, a SystemTimer alarm,
|
||||
// or you can mix and match them as well.
|
||||
let systimer = SystemTimer::new(peripherals.SYSTIMER).split::<Target>();
|
||||
let timer2: AnyTimer = systimer.alarm0;
|
||||
|
||||
esp_hal_embassy::init([timer0, timer1, timer2]);
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Note that you only have to convert into `AnyTimer` if you want to use multiple timers.
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# Migration Guide from 0.4.x to v0.5.x
|
||||
|
||||
You don't have to make changes to your code to update esp-hal-embassy.
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
# esp-hal-embassy
|
||||
|
||||
[](https://crates.io/crates/esp-hal-embassy)
|
||||
[](https://docs.rs/esp-hal-embassy)
|
||||

|
||||

|
||||
[](https://matrix.to/#/#esp-rs:matrix.org)
|
||||
|
||||
[Embassy] support for `esp-hal`.
|
||||
|
||||
[embassy]: https://github.com/embassy-rs/embassy
|
||||
|
||||
## [Documentation]
|
||||
|
||||
[documentation]: https://docs.rs/esp-hal-embassy/
|
||||
|
||||
## Minimum Supported Rust Version (MSRV)
|
||||
|
||||
This crate is guaranteed to compile on stable Rust 1.76 and up. It _might_
|
||||
compile with older versions but that may change in any new patch release.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of:
|
||||
|
||||
- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
### Contribution
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in
|
||||
the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without
|
||||
any additional terms or conditions.
|
||||
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
use std::{error::Error, str::FromStr};
|
||||
|
||||
use esp_build::assert_unique_used_features;
|
||||
use esp_config::{generate_config, Value};
|
||||
use esp_metadata::{Chip, Config};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// NOTE: update when adding new device support!
|
||||
// Ensure that exactly one chip has been specified:
|
||||
assert_unique_used_features!(
|
||||
"esp32", "esp32c2", "esp32c3", "esp32c6", "esp32h2", "esp32s2", "esp32s3"
|
||||
);
|
||||
|
||||
// NOTE: update when adding new device support!
|
||||
// Determine the name of the configured device:
|
||||
let device_name = if cfg!(feature = "esp32") {
|
||||
"esp32"
|
||||
} else if cfg!(feature = "esp32c2") {
|
||||
"esp32c2"
|
||||
} else if cfg!(feature = "esp32c3") {
|
||||
"esp32c3"
|
||||
} else if cfg!(feature = "esp32c6") {
|
||||
"esp32c6"
|
||||
} else if cfg!(feature = "esp32h2") {
|
||||
"esp32h2"
|
||||
} else if cfg!(feature = "esp32s2") {
|
||||
"esp32s2"
|
||||
} else if cfg!(feature = "esp32s3") {
|
||||
"esp32s3"
|
||||
} else {
|
||||
unreachable!() // We've confirmed exactly one known device was selected
|
||||
};
|
||||
|
||||
// Load the configuration file for the configured device:
|
||||
let chip = Chip::from_str(device_name)?;
|
||||
let config = Config::for_chip(&chip);
|
||||
|
||||
// Define all necessary configuration symbols for the configured device:
|
||||
config.define_symbols();
|
||||
|
||||
// emit config
|
||||
generate_config(
|
||||
"esp_hal_embassy",
|
||||
&[(
|
||||
"low-power-wait",
|
||||
"Enables the lower-power wait if no tasks are ready to run on the thread-mode executor. This allows the MCU to use less power if the workload allows. Recommended for battery-powered systems. May impact analog performance.",
|
||||
Value::Bool(true),
|
||||
None
|
||||
)],
|
||||
true,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
//! Interrupt-mode executor.
|
||||
|
||||
use core::{cell::UnsafeCell, mem::MaybeUninit};
|
||||
|
||||
use embassy_executor::{raw, SendSpawner};
|
||||
use esp_hal::{
|
||||
interrupt::{self, software::SoftwareInterrupt, InterruptHandler},
|
||||
Cpu,
|
||||
};
|
||||
use portable_atomic::{AtomicUsize, Ordering};
|
||||
|
||||
const COUNT: usize = 3 + cfg!(not(multi_core)) as usize;
|
||||
static mut EXECUTORS: [CallbackContext; COUNT] = [const { CallbackContext::new() }; COUNT];
|
||||
|
||||
/// Interrupt mode 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.
|
||||
pub struct InterruptExecutor<const SWI: u8> {
|
||||
core: AtomicUsize,
|
||||
executor: UnsafeCell<MaybeUninit<raw::Executor>>,
|
||||
interrupt: SoftwareInterrupt<SWI>,
|
||||
}
|
||||
|
||||
unsafe impl<const SWI: u8> Send for InterruptExecutor<SWI> {}
|
||||
unsafe impl<const SWI: u8> Sync for InterruptExecutor<SWI> {}
|
||||
|
||||
struct CallbackContext {
|
||||
raw_executor: UnsafeCell<*mut raw::Executor>,
|
||||
}
|
||||
|
||||
impl CallbackContext {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
raw_executor: UnsafeCell::new(core::ptr::null_mut()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self) -> *mut raw::Executor {
|
||||
unsafe { *self.raw_executor.get() }
|
||||
}
|
||||
|
||||
fn set(&self, executor: *mut raw::Executor) {
|
||||
unsafe { self.raw_executor.get().write(executor) };
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn handle_interrupt<const NUM: u8>() {
|
||||
let swi = unsafe { SoftwareInterrupt::<NUM>::steal() };
|
||||
swi.reset();
|
||||
|
||||
unsafe {
|
||||
let executor = unwrap!(EXECUTORS[NUM as usize].get().as_mut());
|
||||
executor.poll();
|
||||
}
|
||||
}
|
||||
|
||||
impl<const SWI: u8> InterruptExecutor<SWI> {
|
||||
/// Create a new `InterruptExecutor`.
|
||||
/// This takes the software interrupt to be used internally.
|
||||
#[inline]
|
||||
pub const fn new(interrupt: SoftwareInterrupt<SWI>) -> Self {
|
||||
Self {
|
||||
core: AtomicUsize::new(usize::MAX),
|
||||
executor: UnsafeCell::new(MaybeUninit::uninit()),
|
||||
interrupt,
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the executor at the given priority level.
|
||||
///
|
||||
/// This initializes the executor, enables the interrupt, and returns.
|
||||
/// 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
|
||||
/// 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)
|
||||
/// from a task running in it.
|
||||
pub fn start(&'static mut self, priority: interrupt::Priority) -> SendSpawner {
|
||||
if self
|
||||
.core
|
||||
.compare_exchange(
|
||||
usize::MAX,
|
||||
Cpu::current() as usize,
|
||||
Ordering::Acquire,
|
||||
Ordering::Relaxed,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
panic!("InterruptExecutor::start() called multiple times on the same executor.");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
(*self.executor.get())
|
||||
.as_mut_ptr()
|
||||
.write(raw::Executor::new((SWI as usize) as *mut ()));
|
||||
|
||||
EXECUTORS[SWI as usize].set((*self.executor.get()).as_mut_ptr());
|
||||
}
|
||||
|
||||
let swi_handler = match SWI {
|
||||
0 => handle_interrupt::<0>,
|
||||
1 => handle_interrupt::<1>,
|
||||
2 => handle_interrupt::<2>,
|
||||
#[cfg(not(multi_core))]
|
||||
3 => handle_interrupt::<3>,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
self.interrupt
|
||||
.set_interrupt_handler(InterruptHandler::new(swi_handler, priority));
|
||||
|
||||
let executor = unsafe { (*self.executor.get()).assume_init_ref() };
|
||||
executor.spawner().make_send()
|
||||
}
|
||||
|
||||
/// Get a SendSpawner for this executor
|
||||
///
|
||||
/// This returns a [`SendSpawner`] you can use to spawn tasks on this
|
||||
/// executor.
|
||||
///
|
||||
/// This MUST only be called on an executor that has already been started.
|
||||
/// The function will panic otherwise.
|
||||
pub fn spawner(&'static self) -> SendSpawner {
|
||||
if self.core.load(Ordering::Acquire) == usize::MAX {
|
||||
panic!("InterruptExecutor::spawner() called on uninitialized executor.");
|
||||
}
|
||||
let executor = unsafe { (*self.executor.get()).assume_init_ref() };
|
||||
executor.spawner().make_send()
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
pub use self::{interrupt::*, thread::*};
|
||||
|
||||
mod interrupt;
|
||||
mod thread;
|
||||
|
||||
#[export_name = "__pender"]
|
||||
fn __pender(context: *mut ()) {
|
||||
use esp_hal::interrupt::software::SoftwareInterrupt;
|
||||
|
||||
match context as usize {
|
||||
// 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() },
|
||||
#[cfg(not(multi_core))]
|
||||
3 => unsafe { SoftwareInterrupt::<3>::steal().raise() },
|
||||
// THREAD_MODE_CONTEXT + core ID
|
||||
16 => thread::pend_thread_mode(0),
|
||||
#[cfg(multi_core)]
|
||||
17 => thread::pend_thread_mode(1),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
//! Multicore-aware thread-mode embassy executor.
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use embassy_executor::{raw, Spawner};
|
||||
use esp_hal::Cpu;
|
||||
#[cfg(multi_core)]
|
||||
use esp_hal::{interrupt::software::SoftwareInterrupt, macros::handler};
|
||||
#[cfg(low_power_wait)]
|
||||
use portable_atomic::{AtomicBool, Ordering};
|
||||
|
||||
pub(crate) const THREAD_MODE_CONTEXT: usize = 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
|
||||
static SIGNAL_WORK_THREAD_MODE: [AtomicBool; Cpu::COUNT] =
|
||||
[const { AtomicBool::new(false) }; Cpu::COUNT];
|
||||
|
||||
#[cfg(all(multi_core, low_power_wait))]
|
||||
#[handler]
|
||||
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`.
|
||||
unsafe { SoftwareInterrupt::<3>::steal().reset() };
|
||||
}
|
||||
|
||||
pub(crate) fn pend_thread_mode(_core: usize) {
|
||||
#[cfg(low_power_wait)]
|
||||
{
|
||||
// Signal that there is work to be done.
|
||||
SIGNAL_WORK_THREAD_MODE[_core].store(true, Ordering::SeqCst);
|
||||
|
||||
// If we are pending a task on the current core, we're done. Otherwise, we
|
||||
// need to make sure the other core wakes up.
|
||||
#[cfg(multi_core)]
|
||||
if _core != Cpu::current() as usize {
|
||||
// We need to clear the interrupt from software. We don't actually
|
||||
// need it to trigger and run the interrupt handler, we just need to
|
||||
// kick waiti to return.
|
||||
unsafe { SoftwareInterrupt::<3>::steal().raise() };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Thread mode executor.
|
||||
///
|
||||
/// This is the simplest and most common kind of executor. It runs on thread
|
||||
/// mode (at the lowest priority level).
|
||||
#[cfg_attr(multi_core, doc = "")]
|
||||
#[cfg_attr(
|
||||
multi_core,
|
||||
doc = "This executor is safe to use on multiple cores. You need to
|
||||
create one instance per core. The executors don't steal tasks from each other."
|
||||
)]
|
||||
pub struct Executor {
|
||||
inner: raw::Executor,
|
||||
not_send: PhantomData<*mut ()>,
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
/// Create a new Executor.
|
||||
#[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(all(multi_core, low_power_wait))]
|
||||
unsafe {
|
||||
SoftwareInterrupt::<3>::steal().set_interrupt_handler(software3_interrupt);
|
||||
}
|
||||
|
||||
Self {
|
||||
inner: raw::Executor::new((THREAD_MODE_CONTEXT + Cpu::current() as usize) as *mut ()),
|
||||
not_send: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the executor.
|
||||
///
|
||||
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
|
||||
/// this executor. Use it to spawn the initial task(s). After `init`
|
||||
/// returns, the executor starts running the tasks.
|
||||
///
|
||||
/// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is
|
||||
/// `Copy`), for example by passing it as an argument to the initial
|
||||
/// tasks.
|
||||
///
|
||||
/// This function requires `&'static mut self`. This means you have to store
|
||||
/// the Executor instance in a place where it'll live forever and grants
|
||||
/// you mutable access. There's a few ways to do this:
|
||||
///
|
||||
/// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
|
||||
/// - a `static mut` (unsafe, not recommended)
|
||||
/// - a local variable in a function you know never returns (like `fn main()
|
||||
/// -> !`), upgrading its lifetime with `transmute`. (unsafe)
|
||||
///
|
||||
/// This function never returns.
|
||||
pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
|
||||
init(self.inner.spawner());
|
||||
|
||||
#[cfg(low_power_wait)]
|
||||
let cpu = Cpu::current() as usize;
|
||||
|
||||
loop {
|
||||
unsafe { self.inner.poll() };
|
||||
|
||||
#[cfg(low_power_wait)]
|
||||
Self::wait_impl(cpu);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(xtensa, low_power_wait))]
|
||||
fn wait_impl(cpu: usize) {
|
||||
// Manual critical section implementation that only masks interrupts handlers.
|
||||
// We must not acquire the cross-core on dual-core systems because that would
|
||||
// prevent the other core from doing useful work while this core is sleeping.
|
||||
let token: critical_section::RawRestoreState;
|
||||
unsafe { core::arch::asm!("rsil {0}, 5", out(reg) token) };
|
||||
|
||||
// we do not care about race conditions between the load and store operations,
|
||||
// interrupts will only set this value to true.
|
||||
if SIGNAL_WORK_THREAD_MODE[cpu].load(Ordering::SeqCst) {
|
||||
// if there is work to do, exit critical section and loop back to polling
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"wsr.ps {0}",
|
||||
"rsync",
|
||||
in(reg) token
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// `waiti` sets the `PS.INTLEVEL` when slipping into sleep because critical
|
||||
// sections in Xtensa are implemented via increasing `PS.INTLEVEL`.
|
||||
// The critical section ends here. Take care not add code after
|
||||
// `waiti` if it needs to be inside the CS.
|
||||
unsafe { core::arch::asm!("waiti 0") };
|
||||
}
|
||||
// If this races and some waker sets the signal, we'll reset it, but still poll.
|
||||
SIGNAL_WORK_THREAD_MODE[cpu].store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[cfg(all(riscv, low_power_wait))]
|
||||
fn wait_impl(cpu: usize) {
|
||||
// we do not care about race conditions between the load and store operations,
|
||||
// interrupts will only set this value to true.
|
||||
critical_section::with(|_| {
|
||||
// if there is work to do, loop back to polling
|
||||
if !SIGNAL_WORK_THREAD_MODE[cpu].load(Ordering::SeqCst) {
|
||||
// if not, wait for interrupt
|
||||
unsafe { core::arch::asm!("wfi") };
|
||||
}
|
||||
});
|
||||
// if an interrupt occurred while waiting, it will be serviced here
|
||||
// If this races and some waker sets the signal, we'll reset it, but still poll.
|
||||
SIGNAL_WORK_THREAD_MODE[cpu].store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Executor {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
Vendored
+226
@@ -0,0 +1,226 @@
|
||||
#![macro_use]
|
||||
#![allow(unused_macros)]
|
||||
|
||||
#[cfg(all(feature = "defmt", feature = "log"))]
|
||||
compile_error!("You may not enable both `defmt` and `log` features.");
|
||||
|
||||
macro_rules! assert {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assert_eq {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert_eq!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert_eq!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assert_ne {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::assert_ne!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::assert_ne!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! debug_assert {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! debug_assert_eq {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert_eq!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert_eq!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! debug_assert_ne {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::debug_assert_ne!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug_assert_ne!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! todo {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::todo!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::todo!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! unreachable {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::unreachable!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::unreachable!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! panic {
|
||||
($($x:tt)*) => {
|
||||
{
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
::core::panic!($($x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::panic!($($x)*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! trace {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::trace!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::trace!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! debug {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::debug!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::debug!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! info {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::info!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::info!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! warn {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::warn!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::warn!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! error {
|
||||
($s:literal $(, $x:expr)* $(,)?) => {
|
||||
{
|
||||
#[cfg(feature = "log")]
|
||||
::log::error!($s $(, $x)*);
|
||||
#[cfg(feature = "defmt")]
|
||||
::defmt::error!($s $(, $x)*);
|
||||
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||
let _ = ($( & $x ),*);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
macro_rules! unwrap {
|
||||
($($x:tt)*) => {
|
||||
::defmt::unwrap!($($x)*)
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
macro_rules! unwrap {
|
||||
($arg:expr) => {
|
||||
match $crate::fmt::Try::into_result($arg) {
|
||||
::core::result::Result::Ok(t) => t,
|
||||
::core::result::Result::Err(e) => {
|
||||
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
|
||||
}
|
||||
}
|
||||
};
|
||||
($arg:expr, $($msg:expr),+ $(,)? ) => {
|
||||
match $crate::fmt::Try::into_result($arg) {
|
||||
::core::result::Result::Ok(t) => t,
|
||||
::core::result::Result::Err(e) => {
|
||||
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub struct NoneError;
|
||||
|
||||
pub trait Try {
|
||||
type Ok;
|
||||
type Error;
|
||||
#[allow(unused)]
|
||||
fn into_result(self) -> Result<Self::Ok, Self::Error>;
|
||||
}
|
||||
|
||||
impl<T> Try for Option<T> {
|
||||
type Ok = T;
|
||||
type Error = NoneError;
|
||||
|
||||
#[inline]
|
||||
fn into_result(self) -> Result<T, NoneError> {
|
||||
self.ok_or(NoneError)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Try for Result<T, E> {
|
||||
type Ok = T;
|
||||
type Error = E;
|
||||
|
||||
#[inline]
|
||||
fn into_result(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
Vendored
+169
@@ -0,0 +1,169 @@
|
||||
//! Embassy support for [esp-hal].
|
||||
//!
|
||||
//! [Embassy] is a modern asynchronous framework intended for use with embedded
|
||||
//! systems. This package provides support for building applications using
|
||||
//! Embassy with [esp-hal].
|
||||
//!
|
||||
//! [esp-hal]: https://github.com/esp-rs/esp-hal
|
||||
//! [embassy]: https://github.com/embassy-rs/embassy
|
||||
//!
|
||||
//! ## Executors
|
||||
//!
|
||||
//! Two types of executors are provided:
|
||||
//!
|
||||
//! - [Executor]: A thread-mode executor
|
||||
//! - [InterruptExecutor]: An interrupt-mode executor
|
||||
//!
|
||||
//! [InterruptExecutor] can be used to achieve preemptive multitasking in
|
||||
//! asynchronous applications, which is typically something reserved for more
|
||||
//! traditional RTOS. More information can be found in the [Embassy
|
||||
//! documentation].
|
||||
//!
|
||||
//! [embassy documentation]: https://embassy.dev/book/
|
||||
//!
|
||||
//! ## Initialization
|
||||
//!
|
||||
//! Embassy **must** be initialized by calling the [init] function. This
|
||||
//! initialization must be performed *prior* to spawning any tasks.
|
||||
//!
|
||||
//! ## Feature Flags
|
||||
#![doc = document_features::document_features!()]
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(xtensa, feature(asm_experimental_arch))]
|
||||
#![no_std]
|
||||
|
||||
// MUST be the first module
|
||||
mod fmt;
|
||||
|
||||
#[cfg(not(feature = "esp32"))]
|
||||
use esp_hal::timer::systimer::Alarm;
|
||||
use esp_hal::timer::{timg::Timer as TimgTimer, AnyTimer};
|
||||
pub use macros::main;
|
||||
|
||||
#[cfg(feature = "executors")]
|
||||
pub use self::executor::{Executor, InterruptExecutor};
|
||||
use self::time_driver::{EmbassyTimer, Timer};
|
||||
|
||||
#[cfg(feature = "executors")]
|
||||
mod executor;
|
||||
mod time_driver;
|
||||
|
||||
macro_rules! mk_static {
|
||||
($t:ty,$val:expr) => {{
|
||||
static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new();
|
||||
#[deny(unused_attributes)]
|
||||
let x = STATIC_CELL.uninit().write(($val));
|
||||
x
|
||||
}};
|
||||
}
|
||||
|
||||
/// A trait to allow better UX for initializing the timers.
|
||||
///
|
||||
/// This trait is meant to be used only for the `init` function.
|
||||
/// Calling `timers()` multiple times may panic.
|
||||
pub trait TimerCollection {
|
||||
/// Returns the timers as a slice.
|
||||
fn timers(self) -> &'static mut [Timer];
|
||||
}
|
||||
|
||||
/// Helper trait to reduce boilerplate.
|
||||
///
|
||||
/// We can't blanket-implement for `Into<AnyTimer>` because of possible
|
||||
/// conflicting implementations.
|
||||
trait IntoAnyTimer: Into<AnyTimer> {}
|
||||
|
||||
impl IntoAnyTimer for AnyTimer {}
|
||||
|
||||
impl<T, DM> IntoAnyTimer for TimgTimer<T, DM>
|
||||
where
|
||||
DM: esp_hal::Mode,
|
||||
Self: Into<AnyTimer>,
|
||||
{
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "esp32"))]
|
||||
impl<T, DM, COMP, UNIT> IntoAnyTimer for Alarm<'_, T, DM, COMP, UNIT>
|
||||
where
|
||||
DM: esp_hal::Mode,
|
||||
Self: Into<AnyTimer>,
|
||||
{
|
||||
}
|
||||
|
||||
impl<T> TimerCollection for T
|
||||
where
|
||||
T: IntoAnyTimer,
|
||||
{
|
||||
fn timers(self) -> &'static mut [Timer] {
|
||||
Timer::new(self.into()).timers()
|
||||
}
|
||||
}
|
||||
|
||||
impl TimerCollection for Timer {
|
||||
fn timers(self) -> &'static mut [Timer] {
|
||||
let timers = mk_static!([Timer; 1], [self]);
|
||||
timers.timers()
|
||||
}
|
||||
}
|
||||
|
||||
impl TimerCollection for &'static mut [Timer] {
|
||||
fn timers(self) -> &'static mut [Timer] {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> TimerCollection for &'static mut [Timer; N] {
|
||||
fn timers(self) -> &'static mut [Timer] {
|
||||
self.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_array {
|
||||
($n:literal) => {
|
||||
impl<T> TimerCollection for [T; $n]
|
||||
where
|
||||
T: IntoAnyTimer,
|
||||
{
|
||||
fn timers(self) -> &'static mut [Timer] {
|
||||
mk_static!([Timer; $n], self.map(|t| Timer::new(t.into())))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_array!(2);
|
||||
impl_array!(3);
|
||||
impl_array!(4);
|
||||
|
||||
/// Initialize embassy.
|
||||
///
|
||||
/// Call this as soon as possible, before the first timer-related operation.
|
||||
///
|
||||
/// The time driver can be one of a number of different options:
|
||||
///
|
||||
/// - A timg `Timer` instance
|
||||
/// - A systimer `Alarm` instance
|
||||
/// - An `AnyTimer` instance
|
||||
/// - A `OneShotTimer` instance
|
||||
/// - A mutable static slice of `OneShotTimer` instances
|
||||
/// - A mutable static array of `OneShotTimer` instances
|
||||
/// - A 2, 3, 4 element array of `AnyTimer` instances
|
||||
///
|
||||
/// Note that if you use the `integrated-timers` feature,
|
||||
/// you need to pass as many timers as you start executors.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust, no_run
|
||||
#[doc = esp_hal::before_snippet!()]
|
||||
/// use esp_hal::timg::TimerGroup;
|
||||
///
|
||||
/// let timg0 = TimerGroup::new(peripherals.TIMG0);
|
||||
/// esp_hal_embassy::init(timg0.timer0);
|
||||
///
|
||||
/// // ... now you can spawn embassy tasks or use `Timer::after` etc.
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn init(time_driver: impl TimerCollection) {
|
||||
EmbassyTimer::init(time_driver.timers())
|
||||
}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
use core::cell::{Cell, RefCell};
|
||||
|
||||
use critical_section::Mutex;
|
||||
use embassy_time_driver::{AlarmHandle, Driver};
|
||||
use esp_hal::{
|
||||
interrupt::{InterruptHandler, Priority},
|
||||
prelude::*,
|
||||
time::now,
|
||||
timer::{AnyTimer, OneShotTimer},
|
||||
};
|
||||
|
||||
pub const MAX_SUPPORTED_ALARM_COUNT: usize = 7;
|
||||
|
||||
pub type Timer = OneShotTimer<'static, AnyTimer>;
|
||||
|
||||
static TIMERS: Mutex<RefCell<Option<&'static mut [Timer]>>> = Mutex::new(RefCell::new(None));
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
struct AlarmState {
|
||||
pub callback: Cell<Option<(fn(*mut ()), *mut ())>>,
|
||||
pub allocated: Cell<bool>,
|
||||
}
|
||||
|
||||
unsafe impl Send for AlarmState {}
|
||||
|
||||
impl AlarmState {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
callback: Cell::new(None),
|
||||
allocated: Cell::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct EmbassyTimer {
|
||||
alarms: Mutex<[AlarmState; MAX_SUPPORTED_ALARM_COUNT]>,
|
||||
}
|
||||
|
||||
embassy_time_driver::time_driver_impl!(static DRIVER: EmbassyTimer = EmbassyTimer {
|
||||
alarms: Mutex::new([const { AlarmState::new() }; MAX_SUPPORTED_ALARM_COUNT]),
|
||||
});
|
||||
|
||||
impl EmbassyTimer {
|
||||
pub(super) fn init(timers: &'static mut [Timer]) {
|
||||
if timers.len() > MAX_SUPPORTED_ALARM_COUNT {
|
||||
panic!(
|
||||
"Maximum of {} timers can be used.",
|
||||
MAX_SUPPORTED_ALARM_COUNT
|
||||
);
|
||||
}
|
||||
|
||||
critical_section::with(|cs| {
|
||||
timers.iter_mut().enumerate().for_each(|(n, timer)| {
|
||||
timer.enable_interrupt(false);
|
||||
timer.stop();
|
||||
|
||||
if DRIVER.alarms.borrow(cs)[n].allocated.get() {
|
||||
// FIXME: we should track which core allocated an alarm and bind the interrupt
|
||||
// to that core.
|
||||
timer.set_interrupt_handler(HANDLERS[n]);
|
||||
}
|
||||
});
|
||||
|
||||
TIMERS.replace(cs, Some(timers));
|
||||
});
|
||||
}
|
||||
|
||||
fn on_interrupt(&self, id: usize) {
|
||||
let cb = critical_section::with(|cs| {
|
||||
let mut timers = TIMERS.borrow_ref_mut(cs);
|
||||
let timers = unwrap!(timers.as_mut(), "Time driver not initialized");
|
||||
let timer = &mut timers[id];
|
||||
|
||||
timer.clear_interrupt();
|
||||
|
||||
let alarm = &self.alarms.borrow(cs)[id];
|
||||
alarm.callback.get()
|
||||
});
|
||||
|
||||
if let Some((f, ctx)) = cb {
|
||||
f(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn arm(timer: &mut Timer, timestamp: u64) {
|
||||
let now = now().duration_since_epoch();
|
||||
let ts = timestamp.micros();
|
||||
// if the TS is already in the past make the timer fire immediately
|
||||
let timeout = if ts > now { ts - now } else { 0.micros() };
|
||||
unwrap!(timer.schedule(timeout));
|
||||
timer.enable_interrupt(true);
|
||||
}
|
||||
}
|
||||
|
||||
impl Driver for EmbassyTimer {
|
||||
fn now(&self) -> u64 {
|
||||
now().ticks()
|
||||
}
|
||||
|
||||
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
|
||||
critical_section::with(|cs| {
|
||||
for (i, alarm) in self.alarms.borrow(cs).iter().enumerate() {
|
||||
if alarm.allocated.get() {
|
||||
continue;
|
||||
}
|
||||
let mut timer = TIMERS.borrow_ref_mut(cs);
|
||||
// `allocate_alarm` may be called before `esp_hal_embassy::init()`, so
|
||||
// we need to check if we have timers.
|
||||
if let Some(timer) = &mut *timer {
|
||||
// If we do, bind the interrupt handler to the timer.
|
||||
// This ensures that alarms allocated after init are correctly bound to the
|
||||
// core that created the executor.
|
||||
let timer = unwrap!(
|
||||
timer.get_mut(i),
|
||||
"There are not enough timers to allocate a new alarm. Call `esp_hal_embassy::init()` with the correct number of timers."
|
||||
);
|
||||
timer.set_interrupt_handler(HANDLERS[i]);
|
||||
}
|
||||
|
||||
// set alarm so it is not overwritten
|
||||
alarm.allocated.set(true);
|
||||
return Some(AlarmHandle::new(i as u8));
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
|
||||
let n = alarm.id() as usize;
|
||||
critical_section::with(|cs| {
|
||||
let alarm = &self.alarms.borrow(cs)[n];
|
||||
alarm.callback.set(Some((callback, ctx)));
|
||||
})
|
||||
}
|
||||
|
||||
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
|
||||
// If `embassy-executor/integrated-timers` is enabled and there are no pending
|
||||
// timers, embassy still calls `set_alarm` with `u64::MAX`. By returning
|
||||
// `true` we signal that no re-polling is necessary.
|
||||
if timestamp == u64::MAX {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The hardware fires the alarm even if timestamp is lower than the current
|
||||
// time. In this case the interrupt handler will pend a wake-up when we exit the
|
||||
// critical section.
|
||||
//
|
||||
// This is correct behavior. See https://docs.rs/embassy-time-driver/0.1.0/embassy_time_driver/trait.Driver.html#tymethod.set_alarm
|
||||
// (... the driver should return true and arrange to call the alarm callback as
|
||||
// soon as possible, but not synchronously.)
|
||||
critical_section::with(|cs| {
|
||||
let mut timers = TIMERS.borrow_ref_mut(cs);
|
||||
let timers = unwrap!(timers.as_mut(), "Time driver not initialized");
|
||||
let timer = &mut timers[alarm.id() as usize];
|
||||
|
||||
Self::arm(timer, timestamp);
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
static HANDLERS: [InterruptHandler; MAX_SUPPORTED_ALARM_COUNT] = [
|
||||
handler0, handler1, handler2, handler3, handler4, handler5, handler6,
|
||||
];
|
||||
|
||||
#[handler(priority = Priority::max())]
|
||||
fn handler0() {
|
||||
DRIVER.on_interrupt(0);
|
||||
}
|
||||
#[handler(priority = Priority::max())]
|
||||
fn handler1() {
|
||||
DRIVER.on_interrupt(1);
|
||||
}
|
||||
#[handler(priority = Priority::max())]
|
||||
fn handler2() {
|
||||
DRIVER.on_interrupt(2);
|
||||
}
|
||||
#[handler(priority = Priority::max())]
|
||||
fn handler3() {
|
||||
DRIVER.on_interrupt(3);
|
||||
}
|
||||
#[handler(priority = Priority::max())]
|
||||
fn handler4() {
|
||||
DRIVER.on_interrupt(4);
|
||||
}
|
||||
#[handler(priority = Priority::max())]
|
||||
fn handler5() {
|
||||
DRIVER.on_interrupt(5);
|
||||
}
|
||||
#[handler(priority = Priority::max())]
|
||||
fn handler6() {
|
||||
DRIVER.on_interrupt(6);
|
||||
}
|
||||
Reference in new issue
Block a user