Update our time driver for upcoming embassy changes (#2701)

* Add a timer-driven task

* Spawn another timer

* Log

* foo

* Do not access current time on each schedule

* Update generic queue

* Minimize alarm priorities

* Point to github with patches

* Fix build without any queue impl selected

* Remove explicit generic-queue features

* Define cfgs, fix calling something uninitialized

* Clean up RefCell+generic queue

* Fix arg order

* Feature

* Fix single integrated-timer queue

* Fix next expiration when arming

* Add note

* Adjust impl to latest changes

* Local patch

* Refactor the refactor refactor

* Track the timer item's owner

* Clear owner on dequeue

* Clean up

* Point at the right branch

* Fix panic message

* Hide private function

* Remove integrated-timer references

* Point at upstream embassy

* Configure via esp-config

* Document, clean up, fix

* Hack

* Remove patches

* Update config separator, test the complex variant

* Undo esp-config hack

* Remove trouble example, update edge-net

* Update test deps

* Document

* Update bt-hci.

* Fix generic queue

* Fix panic message

* Fix UB

* Fix rebase

* Resolve UB

* Avoid mutable reference in interrupt executor

---------

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
This commit is contained in:
Dániel Buga 2025-01-14 17:17:58 +01:00 committed by GitHub
parent 5e05402e98
commit 571760884b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 664 additions and 352 deletions

View File

@ -9,8 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added `ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE` (#2701)
- Added `ESP_HAL_EMBASSY_CONFIG_GENERIC_QUEUE_SIZE` instead of using `embassy-time/generic-queue-*` (#2701)
### Changed
- Bump MSRV to 1.83 (#2615)
- Updated embassy-time to v0.4 (#2701)
- Config: Crate prefixes and configuration keys are now separated by `_CONFIG_` (#2848)
- Bump MSRV to 1.84 (#2951)
@ -20,6 +25,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed
- The `integrated-timers` option has been replaced by configuration options. (#2701)
## 0.5.0 - 2024-11-20
### Added

View File

@ -14,16 +14,20 @@ default-target = "riscv32imac-unknown-none-elf"
features = ["esp32c6"]
[dependencies]
critical-section = "1.2.0"
defmt = { version = "0.3.8", optional = true }
document-features = "0.2.10"
embassy-executor = { version = "0.6.3", optional = true }
embassy-time-driver = { version = "0.1.0", features = [ "tick-hz-1_000_000" ] }
esp-hal = { version = "0.22.0", path = "../esp-hal" }
log = { version = "0.4.22", optional = true }
macros = { version = "0.15.0", features = ["embassy"], package = "esp-hal-procmacros", path = "../esp-hal-procmacros" }
portable-atomic = "1.9.0"
static_cell = "2.1.0"
critical-section = "1.2.0"
defmt = { version = "0.3.8", optional = true }
document-features = "0.2.10"
embassy-executor = { version = "0.7.0", features = ["timer-item-payload-size-4"], optional = true }
embassy-sync = { version = "0.6.1" }
embassy-time = { version = "0.4.0" }
embassy-time-driver = { version = "0.2.0", features = [ "tick-hz-1_000_000" ] }
embassy-time-queue-utils = { version = "0.1.0", features = ["_generic-queue"] }
esp-config = { version = "0.2.0", path = "../esp-config" }
esp-hal = { version = "0.22.0", path = "../esp-hal" }
log = { version = "0.4.22", optional = true }
macros = { version = "0.15.0", features = ["embassy"], package = "esp-hal-procmacros", path = "../esp-hal-procmacros" }
portable-atomic = "1.9.0"
static_cell = "2.1.0"
[build-dependencies]
esp-build = { version = "0.1.0", path = "../esp-build" }
@ -47,8 +51,6 @@ defmt = ["dep:defmt", "embassy-executor?/defmt", "esp-hal/defmt"]
log = ["dep:log"]
## Provide `Executor` and `InterruptExecutor`
executors = ["dep:embassy-executor", "esp-hal/__esp_hal_embassy"]
## Use the executor-integrated `embassy-time` timer queue.
integrated-timers = ["embassy-executor?/integrated-timers"]
[lints.rust]
unexpected_cfgs = "allow"

View File

@ -13,3 +13,24 @@ configurations to match the new format.
-ESP_HAL_EMBASSY_LOW_POWER_WAIT="false"
+ESP_HAL_EMBASSY_CONFIG_LOW_POWER_WAIT="false"
```
### Removal of `integrated-timers`
The `integrated-timers` feature has been replaced by two new configuration options:
- `ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE`: selects a timer queue implementation, which allows
tuning based on a few requirements. Possible options:
- `generic`: a generic queue implementation is used. This option does not depend on
embassy-executor, uses a global critical section to access the timer queue, and its
capacity determines how many tasks can wait for timers at the same time. You need to pass only a
single timer to `esp_hal_embassy::init`.
- `single-integrated`: This option requires embassy-executor and the `executors` feature, uses a
global critical section to access the timer queue. You need to pass only a single timer to
`esp_hal_embassy::init`. This is slightly faster than `generic` and does not need a capacity
configuration. This is the default option when the `executors` feature is enabled.
- `multiple-integrated`: This option requires embassy-executor and the `executors` feature, uses
more granular locks to access the timer queue. You need to pass one timer for each embassy
executor to `esp_hal_embassy::init` and the option does not need a capacity configuration.
This is the most performant option.
- `ESP_HAL_EMBASSY_CONFIG_GENERIC_QUEUE_SIZE`: determines the capacity of the timer queue when using
the `generic` option. `esp_hal_embassy` ignores the `embassy-time/generic-queue-*` features.

View File

@ -1,10 +1,10 @@
use std::{error::Error, str::FromStr};
use std::{error::Error as StdError, str::FromStr};
use esp_build::assert_unique_used_features;
use esp_config::{generate_config, Value};
use esp_config::{generate_config, Error, Validator, Value};
use esp_metadata::{Chip, Config};
fn main() -> Result<(), Box<dyn Error>> {
fn main() -> Result<(), Box<dyn StdError>> {
// NOTE: update when adding new device support!
// Ensure that exactly one chip has been specified:
assert_unique_used_features!(
@ -39,16 +39,70 @@ fn main() -> Result<(), Box<dyn Error>> {
config.define_symbols();
// emit config
generate_config(
let crate_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
)],
),
(
"timer-queue",
"<p>The flavour of the timer queue provided by this crate. Accepts one of `single-integrated`, `multiple-integrated` or `generic`. Integrated queues require the `executors` feature to be enabled.</p><p>If you use embassy-executor, the `single-integrated` queue is recommended for ease of use, while the `multiple-integrated` queue is recommended for performance. The `multiple-integrated` option needs one timer per executor.</p><p>The `generic` queue allows using embassy-time without the embassy executors.</p>",
Value::String(if cfg!(feature = "executors") {
String::from("single-integrated")
} else {
String::from("generic")
}),
Some(Validator::Custom(Box::new(|value| {
let Value::String(string) = value else {
return Err(Error::Validation(String::from("Expected a string")));
};
if !cfg!(feature = "executors") {
if string.as_str() != "generic" {
return Err(Error::Validation(format!("Expected 'generic' because the `executors` feature is not enabled. Found {string}")));
}
return Ok(());
}
match string.as_str() {
"single-integrated" => Ok(()), // preferred for ease of use
"multiple-integrated" => Ok(()), // preferred for performance
"generic" => Ok(()), // allows using embassy-time without the embassy executors
_ => Err(Error::Validation(format!("Expected 'single-integrated', 'multiple-integrated' or 'generic', found {string}")))
}
})))
),
(
"generic-queue-size",
"The capacity of the queue when the `generic` timer queue flavour is selected.",
Value::Integer(64),
Some(Validator::PositiveInteger),
),
],
true,
);
println!("cargo:rustc-check-cfg=cfg(integrated_timers)");
println!("cargo:rustc-check-cfg=cfg(single_queue)");
println!("cargo:rustc-check-cfg=cfg(generic_timers)");
match &crate_config["ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE"] {
Value::String(s) if s.as_str() == "single-integrated" => {
println!("cargo:rustc-cfg=integrated_timers");
println!("cargo:rustc-cfg=single_queue");
}
Value::String(s) if s.as_str() == "multiple-integrated" => {
println!("cargo:rustc-cfg=integrated_timers");
}
Value::String(s) if s.as_str() == "generic" => {
println!("cargo:rustc-cfg=generic_timers");
println!("cargo:rustc-cfg=single_queue");
}
_ => unreachable!(),
}
Ok(())
}

View File

@ -2,13 +2,15 @@
use core::{cell::UnsafeCell, mem::MaybeUninit};
use embassy_executor::{raw, SendSpawner};
use embassy_executor::SendSpawner;
use esp_hal::{
interrupt::{self, software::SoftwareInterrupt, InterruptHandler},
Cpu,
};
use portable_atomic::{AtomicUsize, Ordering};
use super::InnerExecutor;
const COUNT: usize = 3 + cfg!(not(multi_core)) as usize;
static mut EXECUTORS: [CallbackContext; COUNT] = [const { CallbackContext::new() }; COUNT];
@ -19,7 +21,7 @@ static mut EXECUTORS: [CallbackContext; COUNT] = [const { CallbackContext::new()
/// software.
pub struct InterruptExecutor<const SWI: u8> {
core: AtomicUsize,
executor: UnsafeCell<MaybeUninit<raw::Executor>>,
executor: UnsafeCell<MaybeUninit<InnerExecutor>>,
interrupt: SoftwareInterrupt<SWI>,
}
@ -27,7 +29,7 @@ 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>,
raw_executor: UnsafeCell<*mut InnerExecutor>,
}
impl CallbackContext {
@ -37,11 +39,14 @@ impl CallbackContext {
}
}
fn get(&self) -> *mut raw::Executor {
unsafe { *self.raw_executor.get() }
/// # Safety:
///
/// The caller must ensure `set` has been called before.
unsafe fn get(&self) -> &InnerExecutor {
unsafe { &**self.raw_executor.get() }
}
fn set(&self, executor: *mut raw::Executor) {
fn set(&self, executor: *mut InnerExecutor) {
unsafe { self.raw_executor.get().write(executor) };
}
}
@ -51,8 +56,9 @@ extern "C" fn handle_interrupt<const NUM: u8>() {
swi.reset();
unsafe {
let executor = unwrap!(EXECUTORS[NUM as usize].get().as_mut());
executor.poll();
// SAFETY: The executor is always initialized before the interrupt is enabled.
let executor = EXECUTORS[NUM as usize].get();
executor.inner.poll();
}
}
@ -99,7 +105,7 @@ impl<const SWI: u8> InterruptExecutor<SWI> {
unsafe {
(*self.executor.get())
.as_mut_ptr()
.write(raw::Executor::new((SWI as usize) as *mut ()));
.write(InnerExecutor::new(priority, (SWI as usize) as *mut ()));
EXECUTORS[SWI as usize].set((*self.executor.get()).as_mut_ptr());
}
@ -117,7 +123,8 @@ impl<const SWI: u8> InterruptExecutor<SWI> {
.set_interrupt_handler(InterruptHandler::new(swi_handler, priority));
let executor = unsafe { (*self.executor.get()).assume_init_ref() };
executor.spawner().make_send()
executor.init();
executor.inner.spawner().make_send()
}
/// Get a SendSpawner for this executor
@ -132,6 +139,6 @@ impl<const SWI: u8> InterruptExecutor<SWI> {
panic!("InterruptExecutor::spawner() called on uninitialized executor.");
}
let executor = unsafe { (*self.executor.get()).assume_init_ref() };
executor.spawner().make_send()
executor.inner.spawner().make_send()
}
}

View File

@ -1,4 +1,9 @@
use embassy_executor::raw;
use esp_hal::interrupt::Priority;
pub use self::{interrupt::*, thread::*};
#[cfg(not(single_queue))]
use crate::timer_queue::TimerQueue;
mod interrupt;
mod thread;
@ -22,3 +27,35 @@ fn __pender(context: *mut ()) {
_ => unreachable!(),
}
}
#[repr(C)]
pub(crate) struct InnerExecutor {
inner: raw::Executor,
#[cfg(not(single_queue))]
pub(crate) timer_queue: TimerQueue,
}
impl InnerExecutor {
/// Create a new executor.
///
/// When the executor has work to do, it will call the pender function and
/// pass `context` to it.
///
/// See [`Executor`] docs for details on the pender.
pub(crate) fn new(_prio: Priority, context: *mut ()) -> Self {
Self {
inner: raw::Executor::new(context),
#[cfg(not(single_queue))]
timer_queue: TimerQueue::new(_prio),
}
}
pub(crate) fn init(&self) {
let inner_executor_ptr = self as *const _ as *mut InnerExecutor;
// Expose provenance so that casting back to InnerExecutor in the time driver is
// not UB.
_ = inner_executor_ptr.expose_provenance();
#[cfg(not(single_queue))]
self.timer_queue.set_context(inner_executor_ptr.cast());
}
}

View File

@ -2,13 +2,15 @@
use core::marker::PhantomData;
use embassy_executor::{raw, Spawner};
use embassy_executor::Spawner;
#[cfg(multi_core)]
use esp_hal::interrupt::software::SoftwareInterrupt;
use esp_hal::{interrupt::Priority, Cpu};
#[cfg(low_power_wait)]
use portable_atomic::{AtomicBool, Ordering};
use super::InnerExecutor;
pub(crate) const THREAD_MODE_CONTEXT: usize = 16;
/// global atomic used to keep track of whether there is work to do since sev()
@ -45,7 +47,7 @@ pub(crate) fn pend_thread_mode(_core: usize) {
create one instance per core. The executors don't steal tasks from each other."
)]
pub struct Executor {
inner: raw::Executor,
inner: InnerExecutor,
not_send: PhantomData<*mut ()>,
}
@ -59,7 +61,10 @@ This will use software-interrupt 3 which isn't available for anything else to wa
)]
pub fn new() -> Self {
Self {
inner: raw::Executor::new((THREAD_MODE_CONTEXT + Cpu::current() as usize) as *mut ()),
inner: InnerExecutor::new(
Priority::Priority1,
(THREAD_MODE_CONTEXT + Cpu::current() as usize) as *mut (),
),
not_send: PhantomData,
}
}
@ -90,13 +95,15 @@ This will use software-interrupt 3 which isn't available for anything else to wa
Priority::min(),
));
init(self.inner.spawner());
self.inner.init();
init(self.inner.inner.spawner());
#[cfg(low_power_wait)]
let cpu = Cpu::current() as usize;
loop {
unsafe { self.inner.poll() };
unsafe { self.inner.inner.poll() };
#[cfg(low_power_wait)]
Self::wait_impl(cpu);

View File

@ -29,6 +29,19 @@
//! Embassy **must** be initialized by calling the [init] function. This
//! initialization must be performed *prior* to spawning any tasks.
//!
//! Initialization requires a number of timers to be passed in. The number of
//! timers required depends on the timer queue flavour used, as well as the
//! number of executors started. If you use the `multiple-integrated` timer
//! queue flavour, then you need to pass as many timers as you start executors.
//! In other cases, you can pass a single timer.
//!
//! ## Configuration
//!
//! You can configure the behaviour of the embassy runtime by using the
//! following environment variables:
#![doc = ""]
#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_hal_embassy_config_table.md"))]
#![doc = ""]
//! ## Feature Flags
#![doc = document_features::document_features!()]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
@ -49,8 +62,9 @@ pub use self::executor::{Executor, InterruptExecutor};
use self::time_driver::{EmbassyTimer, Timer};
#[cfg(feature = "executors")]
mod executor;
pub(crate) mod executor;
mod time_driver;
mod timer_queue;
macro_rules! mk_static {
($t:ty,$val:expr) => {{
@ -142,8 +156,9 @@ impl_array!(4);
/// - 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.
/// Note that if you use the `multiple-integrated` timer-queue flavour, then
/// you need to pass as many timers as you start executors. In other cases,
/// you can pass a single timer.
///
/// # Examples
///

View File

@ -1,6 +1,12 @@
//! Embassy time driver implementation
//!
//! The time driver is responsible for keeping time, as well as to manage the
//! wait queue for embassy-time.
#[cfg(not(single_queue))]
use core::cell::Cell;
use embassy_time_driver::{AlarmHandle, Driver};
use embassy_time_driver::Driver;
use esp_hal::{
interrupt::{InterruptHandler, Priority},
sync::Locked,
@ -11,28 +17,60 @@ use esp_hal::{
pub type Timer = OneShotTimer<'static, Blocking>;
/// Alarm handle, assigned by the driver.
#[derive(Clone, Copy)]
pub(crate) struct AlarmHandle {
id: usize,
}
impl AlarmHandle {
/// Create an AlarmHandle
///
/// Safety: May only be called by the current global Driver impl.
/// The impl is allowed to rely on the fact that all `AlarmHandle` instances
/// are created by itself in unsafe code (e.g. indexing operations)
pub unsafe fn new(id: usize) -> Self {
Self { id }
}
pub fn update(&self, expiration: u64) -> bool {
if expiration == u64::MAX {
true
} else {
DRIVER.set_alarm(*self, expiration)
}
}
}
enum AlarmState {
Created(extern "C" fn()),
Allocated(extern "C" fn()),
Initialized(&'static mut Timer),
}
impl AlarmState {
fn initialize(timer: &'static mut Timer, interrupt_handler: extern "C" fn()) -> AlarmState {
fn initialize(timer: &'static mut Timer, interrupt_handler: InterruptHandler) -> AlarmState {
// If the driver is initialized, bind the interrupt handler to the
// timer. This ensures that alarms allocated after init are correctly
// bound to the core that created the executor.
timer.set_interrupt_handler(InterruptHandler::new(interrupt_handler, Priority::max()));
timer.set_interrupt_handler(interrupt_handler);
timer.enable_interrupt(true);
AlarmState::Initialized(timer)
}
}
struct AlarmInner {
pub callback: Cell<(*const (), *mut ())>,
/// If multiple queues are used, we store the appropriate timer queue here.
// FIXME: we currently store the executor, but we could probably avoid an addition by actually
// storing a reference to the timer queue.
#[cfg(not(single_queue))]
pub context: Cell<*const ()>,
pub state: AlarmState,
}
struct Alarm {
// FIXME: we should be able to use priority-limited locks here, but we can initialize alarms
// while running at an arbitrary priority level. We need to rework alarm allocation to only use
// a critical section to allocate an alarm, but not when using it.
pub inner: Locked<AlarmInner>,
}
@ -42,14 +80,37 @@ impl Alarm {
pub const fn new(handler: extern "C" fn()) -> Self {
Self {
inner: Locked::new(AlarmInner {
callback: Cell::new((core::ptr::null(), core::ptr::null_mut())),
#[cfg(not(single_queue))]
context: Cell::new(core::ptr::null_mut()),
state: AlarmState::Created(handler),
}),
}
}
}
/// embassy requires us to implement the [embassy_time_driver::Driver] trait,
/// which we do here. This trait needs us to be able to tell the current time,
/// as well as to schedule a wake-up at a certain time.
///
/// We are free to choose how we implement these features, and we provide three
/// options:
///
/// - If the `generic` feature is enabled, we implement a single timer queue,
/// using the implementation provided by embassy-time-queue-driver.
/// - If the `single-integrated` feature is enabled, we implement a single timer
/// queue, using our own integrated timer implementation. Our implementation
/// is a copy of the embassy integrated timer queue, with the addition of
/// clearing the "owner" information upon dequeueing.
/// - If the `multiple-integrated` feature is enabled, we provide a separate
/// timer queue for each executor. We store a separate timer queue for each
/// executor, and we use the scheduled task's owner to determine which queue
/// to use. This mode allows us to use less disruptive locks around the timer
/// queue, but requires more timers - one per timer queue.
pub(super) struct EmbassyTimer {
/// The timer queue, if we use a single one (single-integrated, or generic).
#[cfg(single_queue)]
pub(crate) inner: crate::timer_queue::TimerQueue,
alarms: [Alarm; MAX_SUPPORTED_ALARM_COUNT],
available_timers: Locked<Option<&'static mut [Timer]>>,
}
@ -70,14 +131,21 @@ macro_rules! alarms {
};
}
// TODO: we can reduce this to 1 for single_queue, but that would break current
// tests. Resolve when tests can use separate configuration sets, or update
// tests to always pass a single timer.
const MAX_SUPPORTED_ALARM_COUNT: usize = 7;
embassy_time_driver::time_driver_impl!(static DRIVER: EmbassyTimer = EmbassyTimer {
// Single queue, needs maximum priority.
#[cfg(single_queue)]
inner: crate::timer_queue::TimerQueue::new(Priority::max()),
alarms: alarms!(0, 1, 2, 3, 4, 5, 6),
available_timers: Locked::new(None),
});
impl EmbassyTimer {
pub(super) fn init(mut timers: &'static mut [Timer]) {
pub(super) fn init(timers: &'static mut [Timer]) {
assert!(
timers.len() <= MAX_SUPPORTED_ALARM_COUNT,
"Maximum {} timers can be used.",
@ -90,36 +158,27 @@ impl EmbassyTimer {
timer.stop();
});
// Initialize already allocated timers
for alarm in DRIVER.alarms.iter() {
timers = alarm.inner.with(move |alarm| {
if let AlarmState::Allocated(interrupt_handler) = alarm.state {
// Pluck off a timer
let Some((timer, remaining_timers)) = timers.split_first_mut() else {
not_enough_timers();
};
alarm.state = AlarmState::initialize(timer, interrupt_handler);
remaining_timers
} else {
timers
}
});
}
// Store the available timers
DRIVER
.available_timers
.with(|available_timers| *available_timers = Some(timers));
}
#[cfg(not(single_queue))]
pub(crate) fn set_callback_ctx(&self, alarm: AlarmHandle, ctx: *const ()) {
self.alarms[alarm.id].inner.with(|alarm| {
alarm.context.set(ctx.cast_mut());
})
}
fn on_interrupt(&self, id: usize) {
let (cb, ctx) = self.alarms[id].inner.with(|alarm| {
// On interrupt, we clear the alarm that was triggered...
#[cfg_attr(single_queue, allow(clippy::let_unit_value))]
let _ctx = self.alarms[id].inner.with(|alarm| {
if let AlarmState::Initialized(timer) = &mut alarm.state {
timer.clear_interrupt();
alarm.callback.get()
#[cfg(not(single_queue))]
alarm.context.get()
} else {
unsafe {
// SAFETY: `on_interrupt` is registered right when the alarm is initialized.
@ -128,15 +187,17 @@ impl EmbassyTimer {
}
});
let cb: fn(*mut ()) = unsafe {
// Safety:
// - we can ignore the possibility of `f` being unset (null) because of the
// safety contract of `allocate_alarm`.
// - other than that we only store valid function pointers into alarm.callback
core::mem::transmute(cb)
};
// ... and process the timer queue if we have one. For multiple queues, the
// timer queue is stored in the alarm's context.
#[cfg(all(integrated_timers, not(single_queue)))]
{
let executor = unsafe { &*_ctx.cast::<crate::executor::InnerExecutor>() };
executor.timer_queue.dispatch();
}
cb(ctx);
// If we have a single queue, it lives in this struct.
#[cfg(single_queue)]
self.inner.dispatch();
}
/// Returns `true` if the timer was armed, `false` if the timestamp is in
@ -156,14 +217,18 @@ impl EmbassyTimer {
false
}
}
}
impl Driver for EmbassyTimer {
fn now(&self) -> u64 {
now().ticks()
}
unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
/// Allocate an alarm, if possible.
///
/// Returns `None` if there are no available alarms.
///
/// When using multiple timer queues, the `priority` parameter indicates the
/// priority of the interrupt handler. It is 1 for thread-mode
/// executors, or equals to the priority of an interrupt executor.
///
/// When using a single timer queue, the `priority` parameter is always the
/// highest value possible.
pub(crate) unsafe fn allocate_alarm(&self, priority: Priority) -> Option<AlarmHandle> {
for (i, alarm) in self.alarms.iter().enumerate() {
let handle = alarm.inner.with(|alarm| {
let AlarmState::Created(interrupt_handler) = alarm.state else {
@ -171,9 +236,6 @@ impl Driver for EmbassyTimer {
};
let timer = self.available_timers.with(|available_timers| {
// `allocate_alarm` may be called before `esp_hal_embassy::init()`. If
// `timers` is `None`, we return `None` to signal that the alarm cannot be
// initialized yet. These alarms will be initialized when `init` is called.
if let Some(timers) = available_timers.take() {
// If the driver is initialized, we can allocate a timer.
// If this fails, we can't do anything about it.
@ -181,22 +243,18 @@ impl Driver for EmbassyTimer {
not_enough_timers();
};
*available_timers = Some(rest);
Some(timer)
timer
} else {
None
panic!("schedule_wake called before esp_hal_embassy::init()")
}
});
alarm.state = match timer {
Some(timer) => AlarmState::initialize(timer, interrupt_handler),
alarm.state = AlarmState::initialize(
timer,
InterruptHandler::new(interrupt_handler, priority),
);
None => {
// No timers are available yet, mark the alarm as allocated.
AlarmState::Allocated(interrupt_handler)
}
};
Some(AlarmHandle::new(i as u8))
Some(AlarmHandle::new(i))
});
if handle.is_some() {
@ -207,23 +265,11 @@ impl Driver for EmbassyTimer {
None
}
fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
let n = alarm.id() as usize;
self.alarms[n].inner.with(|alarm| {
alarm.callback.set((callback as *const (), ctx));
})
}
/// Set an alarm to fire at a certain timestamp.
///
/// Returns `false` if the timestamp is in the past.
fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {
let alarm = &self.alarms[alarm.id() as usize];
// 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;
}
let alarm = &self.alarms[alarm.id];
// 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
@ -237,17 +283,96 @@ impl Driver for EmbassyTimer {
if let AlarmState::Initialized(timer) = &mut alarm.state {
Self::arm(timer, timestamp)
} else {
panic!("set_alarm called before esp_hal_embassy::init()")
unsafe {
// SAFETY: We only create `AlarmHandle` instances after the alarm is
// initialized.
core::hint::unreachable_unchecked()
}
}
})
}
}
impl Driver for EmbassyTimer {
fn now(&self) -> u64 {
now().ticks()
}
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
#[cfg(not(single_queue))]
unsafe {
// If we have multiple queues, we have integrated timers and our own timer queue
// implementation.
use embassy_executor::raw::Executor as RawExecutor;
use portable_atomic::{AtomicPtr, Ordering};
let task = embassy_executor::raw::task_from_waker(waker);
// SAFETY: it is impossible to schedule a task that has not yet been spawned,
// so the executor is guaranteed to be set to a non-null value.
let mut executor = task.executor().unwrap_unchecked() as *const RawExecutor;
let owner = task
.timer_queue_item()
.payload
.as_ref::<AtomicPtr<RawExecutor>>();
// Try to take ownership over the timer item.
let owner = owner.compare_exchange(
core::ptr::null_mut(),
executor.cast_mut(),
Ordering::AcqRel,
Ordering::Acquire,
);
// We can't take ownership, but we may still be able to enqueue the task. Point
// at the current owner.
if let Err(owner) = owner {
executor = owner;
};
// It is possible that the task's owner changes in the mean time. It doesn't
// matter, at this point the only interesting question is: can we enqueue in the
// currently loaded owner's timer queue?
// Try to enqueue in the current owner's timer queue. This will fail if the
// owner has a lower priority ceiling than the current context.
// SAFETY: we've exposed provenance in `InnerExecutor::init`, which is called
// when the executor is started. Because the executor wasn't running
// before init, it is impossible to get a pointer here that has no
// provenance exposed.
// The cast is then safe, because the RawExecutor is the first field of the
// InnerExecutor, and repr(C) guarantees that the fields are laid out in the
// order they are defined, and the first field has 0 offset.
let executor_addr = executor as usize;
let executor = core::ptr::with_exposed_provenance_mut::<crate::executor::InnerExecutor>(
executor_addr,
);
(*executor).timer_queue.schedule_wake(at, waker);
}
#[cfg(single_queue)]
self.inner.schedule_wake(at, waker);
}
}
#[cold]
#[track_caller]
fn not_enough_timers() -> ! {
// This is wrapped in a separate function because rustfmt does not like
// extremely long strings. Also, if log is used, this avoids storing the string
// twice.
panic!("There are not enough timers to allocate a new alarm. Call esp_hal_embassy::init() with the correct number of timers, or consider using one of the embassy-timer/generic-queue-X features.");
panic!("There are not enough timers to allocate a new alarm. Call esp_hal_embassy::init() with the correct number of timers, or consider either using the `single-integrated` or the `generic` timer queue flavors.");
}
pub(crate) fn set_up_alarm(priority: Priority, _ctx: *mut ()) -> AlarmHandle {
let alarm = unsafe {
DRIVER
.allocate_alarm(priority)
.unwrap_or_else(|| not_enough_timers())
};
#[cfg(not(single_queue))]
DRIVER.set_callback_ctx(alarm, _ctx);
alarm
}

View File

@ -0,0 +1,201 @@
//! Timer waiter queue.
//!
//! This module implements the timer queue, which is managed by the time driver.
//! The timer queue contains wakers and their expiration times, and is used to
//! wake tasks at the correct time.
#[cfg(not(single_queue))]
use core::cell::Cell;
use core::cell::RefCell;
use embassy_sync::blocking_mutex::Mutex;
use esp_hal::{interrupt::Priority, sync::RawPriorityLimitedMutex};
use queue_impl::RawQueue;
use crate::time_driver::{set_up_alarm, AlarmHandle};
struct TimerQueueInner {
queue: RawQueue,
alarm: Option<AlarmHandle>,
}
pub(crate) struct TimerQueue {
inner: Mutex<RawPriorityLimitedMutex, RefCell<TimerQueueInner>>,
priority: Priority,
#[cfg(not(single_queue))]
context: Cell<*mut ()>,
}
unsafe impl Sync for TimerQueue {}
impl TimerQueue {
pub(crate) const fn new(prio: Priority) -> Self {
Self {
inner: Mutex::const_new(
RawPriorityLimitedMutex::new(prio),
RefCell::new(TimerQueueInner {
queue: RawQueue::new(),
alarm: None,
}),
),
priority: prio,
#[cfg(not(single_queue))]
context: Cell::new(core::ptr::null_mut()),
}
}
#[cfg(not(single_queue))]
pub(crate) fn set_context(&self, context: *mut ()) {
self.context.set(context);
}
#[cfg(not(single_queue))]
fn context(&self) -> *mut () {
self.context.get()
}
#[cfg(single_queue)]
fn context(&self) -> *mut () {
core::ptr::null_mut()
}
pub fn dispatch(&self) {
let now = esp_hal::time::now().ticks();
self.arm_alarm(now);
}
fn arm_alarm(&self, mut next_expiration: u64) {
loop {
let set = self.inner.lock(|inner| {
let mut q = inner.borrow_mut();
next_expiration = q.queue.next_expiration(next_expiration);
let alarm = q
.alarm
.get_or_insert_with(|| set_up_alarm(self.priority, self.context()));
alarm.update(next_expiration)
});
if set {
break;
}
}
}
pub fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
if self
.inner
.lock(|inner| inner.borrow_mut().queue.schedule_wake(at, waker))
{
self.dispatch();
}
}
}
#[cfg(integrated_timers)]
mod queue_impl {
use core::{cell::Cell, cmp::min, ptr, task::Waker};
use embassy_executor::raw::TaskRef;
use portable_atomic::{AtomicPtr, Ordering};
/// Copy of the embassy integrated timer queue, that clears the owner upon
/// dequeueing.
pub(super) struct RawQueue {
head: Cell<Option<TaskRef>>,
}
impl RawQueue {
/// Creates a new timer queue.
pub const fn new() -> Self {
Self {
head: Cell::new(None),
}
}
/// Schedules a task to run at a specific time.
///
/// If this function returns `true`, the called should find the next
/// expiration time and set a new alarm for that time.
pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool {
let task = embassy_executor::raw::task_from_waker(waker);
let item = task.timer_queue_item();
if item.next.get().is_none() {
// If not in the queue, add it and update.
let prev = self.head.replace(Some(task));
item.next.set(if prev.is_none() {
Some(unsafe { TaskRef::dangling() })
} else {
prev
});
item.expires_at.set(at);
true
} else if at <= item.expires_at.get() {
// If expiration is sooner than previously set, update.
item.expires_at.set(at);
true
} else {
// Task does not need to be updated.
false
}
}
/// Dequeues expired timers and returns the next alarm time.
///
/// The provided callback will be called for each expired task. Tasks
/// that never expire will be removed, but the callback will not
/// be called.
pub fn next_expiration(&mut self, now: u64) -> u64 {
let mut next_expiration = u64::MAX;
self.retain(|p| {
let item = p.timer_queue_item();
let expires = item.expires_at.get();
if expires <= now {
// Timer expired, process task.
embassy_executor::raw::wake_task(p);
false
} else {
// Timer didn't yet expire, or never expires.
next_expiration = min(next_expiration, expires);
expires != u64::MAX
}
});
next_expiration
}
fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) {
let mut prev = &self.head;
while let Some(p) = prev.get() {
if unsafe { p == TaskRef::dangling() } {
// prev was the last item, stop
break;
}
let item = p.timer_queue_item();
if f(p) {
// Skip to next
prev = &item.next;
} else {
// Remove it
prev.set(item.next.get());
// Clear owner
unsafe {
// SAFETY: our payload is an AtomicPtr.
item.payload
.as_ref::<AtomicPtr<()>>()
.store(ptr::null_mut(), Ordering::Relaxed);
}
item.next.set(None);
}
}
}
}
}
#[cfg(generic_timers)]
mod queue_impl {
pub(super) type RawQueue = embassy_time_queue_utils::queue_generic::ConstGenericQueue<
{ esp_config::esp_config_int!(usize, "ESP_HAL_EMBASSY_CONFIG_GENERIC_QUEUE_SIZE") },
>;
}

View File

@ -204,6 +204,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Removed `get_` prefixes from functions (#2528)
- The `Camera` and `I8080` drivers' constructors now only accepts blocking-mode DMA channels. (#2519)
- Many peripherals are now disabled by default and also get disabled when the driver is dropped (#2544)
- Updated embassy-time to v0.4 (#2701)
- Config: Crate prefixes and configuration keys are now separated by `_CONFIG_` (#2848)
- UART: `read_byte` and `write_byte` made private. (#2915)

View File

@ -27,7 +27,7 @@ defmt = { version = "0.3.8", optional = true }
delegate = "0.13.2"
digest = { version = "0.10.7", default-features = false, optional = true }
document-features = "0.2.10"
embassy-embedded-hal = { version = "0.2.0", optional = true }
embassy-embedded-hal = { version = "0.3.0", optional = true }
embassy-futures = "0.1.1"
embassy-sync = "0.6.1"
embassy-usb-driver = { version = "0.1.0", optional = true }

View File

@ -40,7 +40,7 @@ portable-atomic = { version = "1.9.0", default-features = false }
portable_atomic_enum = { version = "0.3.1", features = ["portable-atomic"] }
rand_core = "0.6.4"
bt-hci = { version = "0.1.1", optional = true }
bt-hci = { version = "0.2.0", optional = true }
esp-config = { version = "0.2.0", path = "../esp-config" }
xtensa-lx-rt = { version = "0.17.2", path = "../xtensa-lx-rt", optional = true }

View File

@ -9,14 +9,14 @@ publish = false
aligned = { version = "0.4.2", optional = true }
bleps = { git = "https://github.com/bjoernQ/bleps", package = "bleps", rev = "a5148d8ae679e021b78f53fd33afb8bb35d0b62e", features = [ "macros", "async"] }
blocking-network-stack = { git = "https://github.com/bjoernQ/blocking-network-stack.git", rev = "b3ecefc222d8806edd221f266999ca339c52d34e" }
bt-hci = "0.1.1"
bt-hci = "0.2.0"
cfg-if = "1.0.0"
critical-section = "1.1.3"
embassy-executor = { version = "0.6.0", features = ["task-arena-size-20480"] }
embassy-executor = { version = "0.7.0", features = ["task-arena-size-20480"] }
embassy-futures = "0.1.1"
embassy-net = { version = "0.5.0", features = [ "tcp", "udp", "dhcpv4", "medium-ethernet"] }
embassy-net = { version = "0.6.0", features = [ "tcp", "udp", "dhcpv4", "medium-ethernet"] }
embassy-sync = "0.6.0"
embassy-time = "0.3.2"
embassy-time = "0.4.0"
embassy-usb = { version = "0.2.0", default-features = false }
embedded-can = "0.4.1"
embedded-hal-async = "1.0.0"
@ -43,13 +43,12 @@ sha2 = { version = "0.10.8", default-features = false }
smoltcp = { version = "0.12.0", default-features = false, features = [ "medium-ethernet", "socket-raw"] }
embedded-time = "=0.12.1"
static_cell = { version = "2.1.0", features = ["nightly"] }
trouble-host = { git = "https://github.com/embassy-rs/trouble", package = "trouble-host", rev = "4f1114ce58e96fe54f5ed7e726f66e1ad8d9ce54", features = [ "log", "gatt" ] }
usb-device = "0.3.2"
usbd-serial = "0.2.2"
edge-dhcp = "0.3.0"
edge-raw = "0.3.0"
edge-nal = "0.3.0"
edge-nal-embassy = "0.3.0"
edge-dhcp = { version = "0.5.0", git = "https://github.com/bugadani/edge-net", rev = "b72678e15bb7c6e72809df235778bb7b48ac146d" }
edge-raw = { version = "0.5.0", git = "https://github.com/bugadani/edge-net", rev = "b72678e15bb7c6e72809df235778bb7b48ac146d" }
edge-nal = { version = "0.5.0", git = "https://github.com/bugadani/edge-net", rev = "b72678e15bb7c6e72809df235778bb7b48ac146d" }
edge-nal-embassy = { version = "0.5.0", git = "https://github.com/bugadani/edge-net", rev = "b72678e15bb7c6e72809df235778bb7b48ac146d" }
[features]
esp32 = ["esp-hal/esp32", "esp-backtrace/esp32", "esp-hal-embassy?/esp32", "esp-println/esp32", "esp-storage?/esp32", "esp-wifi?/esp32"]
@ -63,7 +62,6 @@ esp32s3 = ["esp-hal/esp32s3", "esp-backtrace/esp32s3", "esp-hal-embassy?/esp32s3
esp-wifi = ["dep:esp-wifi"]
embassy = ["dep:esp-hal-embassy"]
embassy-generic-timers = ["embassy-time/generic-queue-8"]
[profile.release]
codegen-units = 1
@ -73,8 +71,3 @@ incremental = false
opt-level = 3
lto = 'fat'
overflow-checks = false
[patch.crates-io]
# Patch until https://github.com/ivmarkov/edge-net/pull/50 is merged
edge-nal = { git = "https://github.com/ivmarkov/edge-net", rev = "5a85bcc8b8726e8b7044e9526f01cdba1fd684da"}
edge-nal-embassy = { git = "https://github.com/ivmarkov/edge-net", rev = "5a85bcc8b8726e8b7044e9526f01cdba1fd684da"}

View File

@ -4,7 +4,7 @@
//! concurrently.
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: embassy esp-hal-embassy/integrated-timers esp-hal/unstable
//% FEATURES: embassy esp-hal/unstable
#![no_std]
#![no_main]

View File

@ -7,7 +7,7 @@
//! - LED => GPIO0
//% CHIPS: esp32 esp32s3
//% FEATURES: embassy embassy-generic-timers esp-hal/unstable
//% FEATURES: embassy esp-hal/unstable
#![no_std]
#![no_main]

View File

@ -7,7 +7,7 @@
//! - LED => GPIO0
//% CHIPS: esp32 esp32s3
//% FEATURES: embassy embassy-generic-timers esp-hal/unstable
//% FEATURES: embassy esp-hal/unstable
#![no_std]
#![no_main]

View File

@ -15,7 +15,7 @@
// The interrupt-executor is created in `main` and is used to spawn `high_prio`.
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: embassy esp-hal-embassy/log esp-hal-embassy/integrated-timers esp-hal/unstable
//% FEATURES: embassy esp-hal-embassy/log esp-hal/unstable
#![no_std]
#![no_main]

View File

@ -4,7 +4,7 @@
//! - Connect GPIO4 and GPIO5
//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: embassy embassy-generic-timers esp-hal/unstable
//% FEATURES: embassy esp-hal/unstable
#![no_std]
#![no_main]

View File

@ -6,7 +6,7 @@
//! - generated pulses => GPIO4
//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: embassy embassy-generic-timers esp-hal/unstable
//% FEATURES: embassy esp-hal/unstable
#![no_std]
#![no_main]

View File

@ -4,7 +4,7 @@
//! writing to and reading from UART.
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: embassy embassy-generic-timers esp-hal/unstable
//% FEATURES: embassy esp-hal/unstable
#![no_std]
#![no_main]

View File

@ -13,7 +13,7 @@
//! CS => GPIO5
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: embassy embassy-generic-timers esp-hal/unstable
//% FEATURES: embassy esp-hal/unstable
#![no_std]
#![no_main]

View File

@ -7,7 +7,7 @@
//! - DM => GPIO19
//% CHIPS: esp32s2 esp32s3
//% FEATURES: embassy embassy-generic-timers esp-hal/unstable
//% FEATURES: embassy esp-hal/unstable
#![no_std]
#![no_main]

View File

@ -3,7 +3,7 @@
//! Most dev-kits use a USB-UART-bridge - in that case you won't see any output.
//% CHIPS: esp32c3 esp32c6 esp32h2 esp32s3
//% FEATURES: embassy embassy-generic-timers esp-hal/unstable
//% FEATURES: embassy esp-hal/unstable
#![no_std]
#![no_main]

View File

@ -9,7 +9,7 @@
//! Because of the huge task-arena size configured this won't work on ESP32-S2
//!
//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/wifi esp-wifi/utils esp-wifi/sniffer esp-hal/unstable
//% FEATURES: embassy esp-wifi esp-wifi/wifi esp-wifi/utils esp-wifi/sniffer esp-hal/unstable
//% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6
#![no_std]
@ -235,7 +235,7 @@ async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) {
loop {
_ = io::server::run(
&mut Server::<64>::new(ip),
&mut Server::<_, 64>::new_with_et(ip),
&ServerOptions::new(ip, Some(&mut gw_buf)),
&mut bound_socket,
&mut buf,

View File

@ -12,7 +12,7 @@
//! Because of the huge task-arena size configured this won't work on ESP32-S2
//!
//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/wifi esp-wifi/utils esp-hal/unstable
//% FEATURES: embassy esp-wifi esp-wifi/wifi esp-wifi/utils esp-hal/unstable
//% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6
#![no_std]

View File

@ -10,7 +10,7 @@
//! Because of the huge task-arena size configured this won't work on ESP32-S2 and ESP32-C2
//!
//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/wifi esp-wifi/utils esp-hal/unstable
//% FEATURES: embassy esp-wifi esp-wifi/wifi esp-wifi/utils esp-hal/unstable
//% CHIPS: esp32 esp32s2 esp32s3 esp32c3 esp32c6
#![allow(static_mut_refs)]

View File

@ -4,7 +4,7 @@
//! - offers one service with three characteristics (one is read/write, one is write only, one is read/write/notify)
//! - pressing the boot-button on a dev-board will send a notification if it is subscribed
//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/ble esp-hal/unstable
//% FEATURES: embassy esp-wifi esp-wifi/ble esp-hal/unstable
//% CHIPS: esp32 esp32s3 esp32c2 esp32c3 esp32c6 esp32h2
#![no_std]

View File

@ -7,7 +7,7 @@
//!
//! Because of the huge task-arena size configured this won't work on ESP32-S2
//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/wifi esp-wifi/utils esp-hal/unstable
//% FEATURES: embassy esp-wifi esp-wifi/wifi esp-wifi/utils esp-hal/unstable
//% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6
#![no_std]

View File

@ -4,7 +4,7 @@
//!
//! Because of the huge task-arena size configured this won't work on ESP32-S2
//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/wifi esp-wifi/utils esp-wifi/esp-now esp-hal/unstable
//% FEATURES: embassy esp-wifi esp-wifi/wifi esp-wifi/utils esp-wifi/esp-now esp-hal/unstable
//% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6
#![no_std]

View File

@ -4,7 +4,7 @@
//!
//! Because of the huge task-arena size configured this won't work on ESP32-S2
//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/wifi esp-wifi/utils esp-wifi/esp-now esp-hal/unstable
//% FEATURES: embassy esp-wifi esp-wifi/wifi esp-wifi/utils esp-wifi/esp-now esp-hal/unstable
//% CHIPS: esp32 esp32s2 esp32s3 esp32c2 esp32c3 esp32c6
#![no_std]

View File

@ -1,169 +0,0 @@
//! Embassy BLE Example using Trouble
//!
//! - starts Bluetooth advertising
//! - offers a GATT service providing a battery percentage reading
//! - automatically notifies subscribers every second
//!
//% FEATURES: embassy embassy-generic-timers esp-wifi esp-wifi/ble esp-hal/unstable
//% CHIPS: esp32 esp32s3 esp32c2 esp32c3 esp32c6 esp32h2
#![no_std]
#![no_main]
use bt_hci::controller::ExternalController;
use embassy_executor::Spawner;
use embassy_futures::join::join3;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_time::{Duration, Timer};
use esp_alloc as _;
use esp_backtrace as _;
use esp_hal::{clock::CpuClock, rng::Rng, timer::timg::TimerGroup};
use esp_wifi::{ble::controller::BleConnector, init, EspWifiController};
use log::*;
use static_cell::StaticCell;
use trouble_host::{
advertise::{AdStructure, Advertisement, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE},
attribute::{AttributeTable, CharacteristicProp, Service, Uuid},
Address,
BleHost,
BleHostResources,
PacketQos,
};
// When you are okay with using a nightly compiler it's better to use https://docs.rs/static_cell/2.1.0/static_cell/macro.make_static.html
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
}};
}
#[esp_hal_embassy::main]
async fn main(_s: Spawner) {
esp_println::logger::init_logger_from_env();
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
let peripherals = esp_hal::init(config);
esp_alloc::heap_allocator!(72 * 1024);
let timg0 = TimerGroup::new(peripherals.TIMG0);
let init = &*mk_static!(
EspWifiController<'static>,
init(
timg0.timer0,
Rng::new(peripherals.RNG),
peripherals.RADIO_CLK,
)
.unwrap()
);
#[cfg(feature = "esp32")]
{
let timg1 = TimerGroup::new(peripherals.TIMG1);
esp_hal_embassy::init(timg1.timer0);
}
#[cfg(not(feature = "esp32"))]
{
let systimer = esp_hal::timer::systimer::SystemTimer::new(peripherals.SYSTIMER);
esp_hal_embassy::init(systimer.alarm0);
}
let mut bluetooth = peripherals.BT;
let connector = BleConnector::new(&init, &mut bluetooth);
let controller: ExternalController<_, 20> = ExternalController::new(connector);
static HOST_RESOURCES: StaticCell<BleHostResources<8, 8, 256>> = StaticCell::new();
let host_resources = HOST_RESOURCES.init(BleHostResources::new(PacketQos::None));
let mut ble: BleHost<'_, _> = BleHost::new(controller, host_resources);
ble.set_random_address(Address::random([0xff, 0x9f, 0x1a, 0x05, 0xe4, 0xff]));
let mut table: AttributeTable<'_, NoopRawMutex, 10> = AttributeTable::new();
let id = b"Trouble ESP32";
let appearance = [0x80, 0x07];
let mut bat_level = [0; 1];
// Generic Access Service (mandatory)
let mut svc = table.add_service(Service::new(0x1800));
let _ = svc.add_characteristic_ro(0x2a00, id);
let _ = svc.add_characteristic_ro(0x2a01, &appearance[..]);
svc.build();
// Generic attribute service (mandatory)
table.add_service(Service::new(0x1801));
// Battery service
let bat_level_handle = table.add_service(Service::new(0x180f)).add_characteristic(
0x2a19,
&[CharacteristicProp::Read, CharacteristicProp::Notify],
&mut bat_level,
);
let mut adv_data = [0; 31];
AdStructure::encode_slice(
&[
AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
AdStructure::ServiceUuids16(&[Uuid::Uuid16([0x0f, 0x18])]),
AdStructure::CompleteLocalName(b"Trouble ESP32"),
],
&mut adv_data[..],
)
.unwrap();
let server = ble.gatt_server::<NoopRawMutex, 10, 256>(&table);
info!("Starting advertising and GATT service");
// Run all 3 tasks in a join. They can also be separate embassy tasks.
let _ = join3(
// Runs the BLE host task
ble.run(),
// Processing events from GATT server (if an attribute was written)
async {
loop {
match server.next().await {
Ok(_event) => {
info!("Gatt event!");
}
Err(e) => {
warn!("Error processing GATT events: {:?}", e);
}
}
}
},
// Advertise our presence to the world.
async {
loop {
let mut advertiser = ble
.advertise(
&Default::default(),
Advertisement::ConnectableScannableUndirected {
adv_data: &adv_data[..],
scan_data: &[],
},
)
.await
.unwrap();
let conn = advertiser.accept().await.unwrap();
// Keep connection alive and notify with value change
let mut tick: u8 = 0;
loop {
if !conn.is_connected() {
break;
}
Timer::after(Duration::from_secs(1)).await;
tick = tick.wrapping_add(1);
server
.notify(&ble, bat_level_handle, &conn, &[tick])
.await
.ok();
}
}
},
)
.await;
}

View File

@ -18,6 +18,7 @@ rustflags = [
[env]
DEFMT_LOG = "info"
ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE="multiple-integrated"
[unstable]
build-std = ["core"]

View File

@ -200,7 +200,7 @@ defmt-rtt = { version = "0.4.1", optional = true }
embassy-executor = "0.6.0"
embassy-futures = "0.1.1"
embassy-sync = "0.6.0"
embassy-time = "0.3.2"
embassy-time = "0.4.0"
embedded-hal = "1.0.0"
embedded-can = "0.4.1"
embedded-hal-async = "1.0.0"
@ -219,9 +219,9 @@ xtensa-lx-rt = { path = "../xtensa-lx-rt", optional = true }
crypto-bigint = { version = "0.5.5", default-features = false }
digest = { version = "0.10.7", default-features = false }
elliptic-curve = { version = "0.13.8", default-features = false, features = ["sec1"] }
embassy-executor = { version = "0.6.0", default-features = false }
embassy-executor = { version = "0.7.0", default-features = false }
# Add the `embedded-test/defmt` feature for more verbose testing
embedded-test = { version = "0.5.0", git = "https://github.com/probe-rs/embedded-test.git", rev = "7109473", default-features = false, features = ["embassy", "external-executor"] }
embedded-test = { version = "0.6.0", default-features = false, features = ["embassy", "external-executor"] }
fugit = "0.3.7"
hex-literal = "0.4.1"
nb = "1.1.0"
@ -290,14 +290,6 @@ esp32s3 = [
embassy = [
"dep:esp-hal-embassy",
]
generic-queue = [
"embassy",
"embassy-time/generic-queue-64"
]
integrated-timers = [
"embassy",
"esp-hal-embassy/integrated-timers",
]
octal-psram = ["esp-hal/octal-psram", "esp-alloc"]
# https://doc.rust-lang.org/cargo/reference/profiles.html#test

View File

@ -2,8 +2,7 @@
//! code.
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES(integrated): unstable embassy integrated-timers
//% FEATURES(generic): unstable embassy generic-queue
//% FEATURES: unstable embassy
#![no_std]
#![no_main]

View File

@ -1,8 +1,7 @@
//! Reproduction and regression test for a sneaky issue.
//% CHIPS: esp32 esp32s2 esp32s3 esp32c3 esp32c6 esp32h2
//% FEATURES(integrated): unstable embassy integrated-timers
//% FEATURES(generic): unstable embassy generic-queue
//% FEATURES: unstable embassy
#![no_std]
#![no_main]

View File

@ -1,8 +1,7 @@
//! Embassy timer and executor Test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES(integrated): unstable embassy integrated-timers
//% FEATURES(generic): unstable embassy generic-queue
//% FEATURES: unstable embassy
#![no_std]
#![no_main]

View File

@ -1,7 +1,7 @@
//! GPIO Test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable embassy generic-queue
//% FEATURES: unstable embassy
//% FEATURES(stable):
#![no_std]

View File

@ -6,7 +6,7 @@
//! async API works for user handlers automatically.
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable integrated-timers
//% FEATURES: unstable embassy
#![no_std]
#![no_main]

View File

@ -4,7 +4,7 @@
//! with loopback mode enabled).
//% CHIPS: esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable generic-queue
//% FEATURES: unstable
// FIXME: re-enable on ESP32 when it no longer fails spuriously
#![no_std]

View File

@ -1,7 +1,7 @@
//! lcd_cam i8080 tests
//% CHIPS: esp32s3
//% FEATURES: unstable generic-queue
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,7 +1,7 @@
//! PARL_IO TX async test
//% CHIPS: esp32c6 esp32h2
//% FEATURES: unstable generic-queue
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -1,7 +1,7 @@
//! SPI Full Duplex test suite.
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable generic-queue
//% FEATURES: unstable
// FIXME: add async test cases that don't rely on PCNT

View File

@ -1,7 +1,7 @@
//! UART Test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable embassy generic-queue
//% FEATURES: unstable embassy
#![no_std]
#![no_main]

View File

@ -1,7 +1,7 @@
//! UART TX/RX Async Test
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: unstable generic-queue
//% FEATURES: unstable
#![no_std]
#![no_main]

View File

@ -28,6 +28,7 @@ rustflags = [
[env]
ESP_LOG = "info"
ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE="multiple-integrated"
[unstable]
build-std = ["alloc", "core"]

View File

@ -7,8 +7,10 @@ publish = false
[dependencies]
cfg-if = "1.0.0"
embassy-executor = { version = "0.6.0", features = ["task-arena-size-12288"] }
embassy-time = "0.3.2"
embassy-executor = { version = "0.7.0", features = ["task-arena-size-12288"] }
embassy-time = "0.4.0"
embassy-futures = "0.1.1"
embassy-sync = "0.6.1"
embedded-graphics = "0.8.1"
embedded-hal-async = "1.0.0"
esp-alloc = { path = "../esp-alloc" }
@ -28,8 +30,6 @@ esp32h2 = ["esp-backtrace/esp32h2", "esp-hal/esp32h2", "esp-hal-embassy/esp32h2"
esp32s2 = ["esp-backtrace/esp32s2", "esp-hal/esp32s2", "esp-hal-embassy/esp32s2", "esp-println/esp32s2"]
esp32s3 = ["esp-backtrace/esp32s3", "esp-hal/esp32s3", "esp-hal-embassy/esp32s3", "esp-println/esp32s3"]
embassy-generic-timers = ["embassy-time/generic-queue-8"]
[profile.release]
debug = 2
debug-assertions = true

View File

@ -1,7 +1,6 @@
//! Embassy executor benchmark, used to try out optimization ideas.
//% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: esp-hal-embassy/integrated-timers
#![no_std]
#![no_main]
@ -23,9 +22,11 @@ use esp_hal::{
use esp_println::println;
static mut COUNTER: u32 = 0;
static mut T2_COUNTER: u32 = 0;
static mut T3_COUNTER: u32 = 0;
const CLOCK: CpuClock = CpuClock::max();
const TEST_MILLIS: u64 = 50;
const TEST_MILLIS: u64 = 500;
#[handler]
fn timer_handler() {
@ -33,6 +34,8 @@ fn timer_handler() {
let cpu_clock = CLOCK.hz() as u64;
let timer_ticks_per_second = SystemTimer::ticks_per_second();
let cpu_cycles_per_timer_ticks = cpu_clock / timer_ticks_per_second;
println!("task2 count={}", unsafe { T2_COUNTER });
println!("task3 count={}", unsafe { T3_COUNTER });
println!(
"Test OK, count={}, cycles={}/100",
c,
@ -54,15 +57,33 @@ impl Future for Task1 {
static TASK1: TaskStorage<Task1> = TaskStorage::new();
#[embassy_executor::task]
async fn task2() {
loop {
unsafe { T2_COUNTER += 1 };
embassy_time::Timer::after(embassy_time::Duration::from_millis(10)).await;
}
}
#[embassy_executor::task]
async fn task3() {
loop {
unsafe { T3_COUNTER += 1 };
embassy_time::Timer::after(embassy_time::Duration::from_millis(100)).await;
}
}
#[esp_hal_embassy::main]
async fn main(spawner: Spawner) {
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
let config = esp_hal::Config::default().with_cpu_clock(CLOCK);
let peripherals = esp_hal::init(config);
let systimer = SystemTimer::new(peripherals.SYSTIMER);
esp_hal_embassy::init(systimer.alarm0);
println!("Embassy initialized!");
spawner.spawn(TASK1.spawn(|| Task1 {})).unwrap();
spawner.spawn(task2()).unwrap();
spawner.spawn(task3()).unwrap();
println!("Starting test");

View File

@ -11,7 +11,6 @@
//! - SCL => GPIO5
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: embassy-generic-timers
#![no_std]
#![no_main]

View File

@ -11,7 +11,6 @@
//! pins.
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: embassy-generic-timers
//% TAG: bmp180
#![no_std]

View File

@ -12,7 +12,6 @@
//! - DIN => GPIO5
//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: embassy-generic-timers
#![no_std]
#![no_main]

View File

@ -26,7 +26,6 @@
//! | XSMT | +3V3 |
//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
//% FEATURES: embassy-generic-timers
#![no_std]
#![no_main]

View File

@ -210,6 +210,9 @@ fn apply_feature_rules(package: &Package, config: &Config) -> Vec<String> {
features.push("coex".to_owned());
}
}
Package::EspHalEmbassy => {
features.push("esp-hal/unstable".to_owned());
}
_ => {}
}
features

View File

@ -731,7 +731,7 @@ fn lint_packages(workspace: &Path, args: LintPackagesArgs) -> Result<()> {
&[
"-Zbuild-std=core",
&format!("--target={}", chip.target()),
&format!("--features={chip},executors,defmt,integrated-timers,esp-hal/unstable"),
&format!("--features={chip},executors,defmt,esp-hal/unstable"),
],
args.fix,
)?;