mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-28 12:50:53 +00:00
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:
parent
5e05402e98
commit
571760884b
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
///
|
||||
|
@ -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
|
||||
}
|
||||
|
201
esp-hal-embassy/src/timer_queue.rs
Normal file
201
esp-hal-embassy/src/timer_queue.rs
Normal 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") },
|
||||
>;
|
||||
}
|
@ -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)
|
||||
|
@ -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 }
|
||||
|
@ -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 }
|
||||
|
@ -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"}
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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,
|
||||
|
@ -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]
|
||||
|
@ -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)]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
}
|
@ -18,6 +18,7 @@ rustflags = [
|
||||
|
||||
[env]
|
||||
DEFMT_LOG = "info"
|
||||
ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE="multiple-integrated"
|
||||
|
||||
[unstable]
|
||||
build-std = ["core"]
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! lcd_cam i8080 tests
|
||||
|
||||
//% CHIPS: esp32s3
|
||||
//% FEATURES: unstable generic-queue
|
||||
//% FEATURES: unstable
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! PARL_IO TX async test
|
||||
|
||||
//% CHIPS: esp32c6 esp32h2
|
||||
//% FEATURES: unstable generic-queue
|
||||
//% FEATURES: unstable
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
@ -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
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -28,6 +28,7 @@ rustflags = [
|
||||
|
||||
[env]
|
||||
ESP_LOG = "info"
|
||||
ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE="multiple-integrated"
|
||||
|
||||
[unstable]
|
||||
build-std = ["alloc", "core"]
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
|
||||
|
@ -11,7 +11,6 @@
|
||||
//! - SCL => GPIO5
|
||||
|
||||
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
|
||||
//% FEATURES: embassy-generic-timers
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
@ -11,7 +11,6 @@
|
||||
//! pins.
|
||||
|
||||
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
|
||||
//% FEATURES: embassy-generic-timers
|
||||
//% TAG: bmp180
|
||||
|
||||
#![no_std]
|
||||
|
@ -12,7 +12,6 @@
|
||||
//! - DIN => GPIO5
|
||||
|
||||
//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
|
||||
//% FEATURES: embassy-generic-timers
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
@ -26,7 +26,6 @@
|
||||
//! | XSMT | +3V3 |
|
||||
|
||||
//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
|
||||
//% FEATURES: embassy-generic-timers
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
)?;
|
||||
|
Loading…
x
Reference in New Issue
Block a user