diff --git a/Cargo.toml b/Cargo.toml index 46cd1d76718..dd85124e873 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,8 +22,10 @@ alloc = [] nightly = ["embedded-hal-async", "embedded-io-async"] esp-idf-sys = ["dep:esp-idf-sys", "atomic-waker"] riscv-ulp-hal = [] -wake-from-isr = [] # Only enable if you plan to use the `edge-executor` crate -embassy-sync = [] # Only for backwards compatibility +wake-from-isr = [] # Only enable if you plan to use the `edge-executor` crate +embassy-sync = [] # Only for backwards compatibility +gp_timer_api = [] # ESP-IDF 5 generap purpose timer api + # Propagated esp-idf-sys features native = ["esp-idf-sys/native"] @@ -37,12 +39,16 @@ libstart = ["esp-idf-sys/libstart"] nb = "1.0.0" embedded-can = "0.4.1" embedded-hal = "=1.0.0-rc.1" -embedded-hal-0-2 = { package = "embedded-hal", version = "0.2.7", features = ["unproven"] } +embedded-hal-0-2 = { package = "embedded-hal", version = "0.2.7", features = [ + "unproven", +] } embedded-hal-nb = "=1.0.0-rc.1" embedded-hal-async = { version = "=1.0.0-rc.1", optional = true } embedded-io = "0.6" embedded-io-async = { version = "0.6", optional = true } -esp-idf-sys = { version = "0.33.5", optional = true, default-features = false, features = ["native"] } +esp-idf-sys = { version = "0.33.5", optional = true, default-features = false, features = [ + "native", +] } critical-section = { version = "1.1.1", optional = true } heapless = "0.7" num_enum = { version = "0.7", default-features = false } @@ -51,6 +57,9 @@ log = { version = "0.4", default-features = false } atomic-waker = { version = "1.1.1", optional = true, default-features = false } embassy-sync = { version = "0.3" } +[patch.crates-io] +esp-idf-sys = { git = "https://github.com/Vollbrecht/esp-idf-sys", branch = "gp-timer-api"} + [build-dependencies] embuild = "0.31.3" diff --git a/src/peripherals.rs b/src/peripherals.rs index 7b026588a20..f4df9274528 100644 --- a/src/peripherals.rs +++ b/src/peripherals.rs @@ -37,6 +37,7 @@ use crate::spi; ))] use crate::task::watchdog; #[cfg(not(feature = "riscv-ulp-hal"))] +#[cfg(not(feature = "gp_timer_api"))] use crate::timer; #[cfg(not(feature = "riscv-ulp-hal"))] use crate::uart; @@ -124,24 +125,28 @@ pub struct Peripherals { not(feature = "riscv-ulp-hal"), not(feature = "embassy-time-isr-queue-timer00") ))] + #[cfg(not(feature = "gp_timer_api"))] pub timer00: timer::TIMER00, #[cfg(all( any(esp32, esp32s2, esp32s3), not(feature = "riscv-ulp-hal"), not(feature = "embassy-time-isr-queue-timer01") ))] + #[cfg(not(feature = "gp_timer_api"))] pub timer01: timer::TIMER01, #[cfg(all( not(esp32c2), not(feature = "riscv-ulp-hal"), not(feature = "embassy-time-isr-queue-timer10") ))] + #[cfg(not(feature = "gp_timer_api"))] pub timer10: timer::TIMER10, #[cfg(all( any(esp32, esp32s2, esp32s3), not(feature = "riscv-ulp-hal"), not(feature = "embassy-time-isr-queue-timer11") ))] + #[cfg(not(feature = "gp_timer_api"))] pub timer11: timer::TIMER11, #[cfg(all( not(feature = "riscv-ulp-hal"), @@ -278,24 +283,28 @@ impl Peripherals { not(feature = "riscv-ulp-hal"), not(feature = "embassy-time-isr-queue-timer00") ))] + #[cfg(not(feature = "gp_timer_api"))] timer00: timer::TIMER00::new(), #[cfg(all( any(esp32, esp32s2, esp32s3), not(feature = "riscv-ulp-hal"), not(feature = "embassy-time-isr-queue-timer01") ))] + #[cfg(not(feature = "gp_timer_api"))] timer01: timer::TIMER01::new(), #[cfg(all( not(esp32c2), not(feature = "riscv-ulp-hal"), not(feature = "embassy-time-isr-queue-timer10") ))] + #[cfg(not(feature = "gp_timer_api"))] timer10: timer::TIMER10::new(), #[cfg(all( any(esp32, esp32s2, esp32s3), not(feature = "riscv-ulp-hal"), not(feature = "embassy-time-isr-queue-timer11") ))] + #[cfg(not(feature = "gp_timer_api"))] timer11: timer::TIMER11::new(), #[cfg(all( not(feature = "riscv-ulp-hal"), diff --git a/src/timer.rs b/src/timer.rs index e79eba2a2ba..0a41c7933f0 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -1,7 +1,9 @@ +#[cfg(not(feature = "gp_timer_api"))] use core::marker::PhantomData; use esp_idf_sys::*; +#[cfg(not(feature = "gp_timer_api"))] use crate::peripheral::Peripheral; #[cfg(feature = "alloc")] @@ -66,11 +68,12 @@ pub mod config { } } +#[cfg(not(feature = "gp_timer_api"))] pub trait Timer: Send { fn group() -> timer_group_t; fn index() -> timer_idx_t; } - +#[cfg(not(feature = "gp_timer_api"))] pub struct TimerDriver<'d> { timer: u8, divider: u32, @@ -78,6 +81,7 @@ pub struct TimerDriver<'d> { _p: PhantomData<&'d mut ()>, } +#[cfg(not(feature = "gp_timer_api"))] impl<'d> TimerDriver<'d> { pub fn new( _timer: impl Peripheral

+ 'd, @@ -345,6 +349,7 @@ impl<'d> TimerDriver<'d> { } } +#[cfg(not(feature = "gp_timer_api"))] impl<'d> Drop for TimerDriver<'d> { fn drop(&mut self) { self.disable_interrupt().unwrap(); @@ -360,8 +365,10 @@ impl<'d> Drop for TimerDriver<'d> { } } +#[cfg(not(feature = "gp_timer_api"))] unsafe impl<'d> Send for TimerDriver<'d> {} +#[cfg(not(feature = "gp_timer_api"))] #[cfg(feature = "nightly")] impl<'d> embedded_hal_async::delay::DelayUs for TimerDriver<'d> { async fn delay_us(&mut self, us: u32) { @@ -377,6 +384,7 @@ impl<'d> embedded_hal_async::delay::DelayUs for TimerDriver<'d> { } } +#[cfg(not(feature = "gp_timer_api"))] macro_rules! impl_timer { ($timer:ident: $group:expr, $index:expr) => { crate::impl_peripheral!($timer); @@ -397,6 +405,7 @@ macro_rules! impl_timer { #[allow(clippy::type_complexity)] #[cfg(not(any(esp32, esp32s2, esp32s3)))] +#[cfg(not(feature = "gp_timer_api"))] #[cfg(feature = "alloc")] static mut ISR_HANDLERS: [Option>; 2] = [None, None]; @@ -421,10 +430,414 @@ pub(crate) static PIN_NOTIF: [crate::interrupt::asynch::HalIsrNotification; 4] = crate::interrupt::asynch::HalIsrNotification::new(), ]; +#[cfg(not(feature = "gp_timer_api"))] impl_timer!(TIMER00: timer_group_t_TIMER_GROUP_0, timer_idx_t_TIMER_0); #[cfg(any(esp32, esp32s2, esp32s3))] +#[cfg(not(feature = "gp_timer_api"))] impl_timer!(TIMER01: timer_group_t_TIMER_GROUP_0, timer_idx_t_TIMER_1); #[cfg(not(esp32c2))] +#[cfg(not(feature = "gp_timer_api"))] impl_timer!(TIMER10: timer_group_t_TIMER_GROUP_1, timer_idx_t_TIMER_0); #[cfg(any(esp32, esp32s2, esp32s3))] +#[cfg(not(feature = "gp_timer_api"))] impl_timer!(TIMER11: timer_group_t_TIMER_GROUP_1, timer_idx_t_TIMER_1); + +// implements a wrapper for the general purpose timer api used in esp-idf 5 +#[cfg(not(esp_idf_version_major = "4"))] +#[cfg(feature = "gp_timer_api")] +pub mod gp_timer { + + use super::*; + + pub enum Direction { + Up, + Down, + } + + pub struct Config { + pub xtal: bool, // GPTimer clock source + pub direction: Direction, // Count direction + pub resolution: u32, // Counter resolution (working frequency) in Hz, hence, the step size of each count tick equals to (1 / resolution_hz) seconds + } + + impl Config { + pub fn new() -> Self { + Default::default() + } + + #[must_use] + pub fn xtal(mut self, xtal: bool) -> Self { + self.xtal = xtal; + self + } + + #[must_use] + pub fn direction(mut self, direction: Direction) -> Self { + self.direction = direction; + self + } + + #[must_use] + pub fn resolution(mut self, resolution: u32) -> Self { + self.resolution = resolution; + self + } + } + + impl Default for Config { + fn default() -> Self { + Self { + xtal: false, + direction: Direction::Up, + resolution: 1_000_000, + } + } + } + pub trait Timer { + fn get_handle(&self) -> gptimer_handle_t; + } + + // This functions are allowed to run within ISR context + pub trait ISRMethods: Timer { + // This function will transit the timer state from “enable” to “run”. + fn start(&self) -> Result<(), EspError> { + esp!(unsafe { gptimer_start(self.get_handle()) })?; + + Ok(()) + } + + // This function will transit the timer state from “run” to “enable”. + fn stop(&self) -> Result<(), EspError> { + esp!(unsafe { gptimer_stop(self.get_handle()) })?; + + Ok(()) + } + + // When updating the raw count of an active timer, the timer will immediately start counting from the new value. + fn set_counter(&mut self, value: u64) -> Result<(), EspError> { + esp!(unsafe { gptimer_set_raw_count(self.get_handle(), value) })?; + + Ok(()) + } + + // This function will trigger a software capture event and then return the captured count value. + fn counter(&self) -> Result { + let mut value = 0_u64; + esp!(unsafe { gptimer_get_raw_count(self.get_handle(), &mut value) })?; + + Ok(value) + } + + // Set alarm event actions for GPTimer. The alarm event will be triggered when the counter value reaches the alarm value. + // Additionally setting a counter reload value with auto reload enabled will cause the counter to reset to the reload value when the alarm event is triggered. + fn set_alarm_action(&mut self, alarm_config: AlarmConfig) -> Result<(), EspError> { + let alarm_config: gptimer_alarm_config_t = alarm_config.into(); + esp!(unsafe { gptimer_set_alarm_action(self.get_handle(), &alarm_config) })?; + + Ok(()) + } + } + + pub trait NonISRSaveMethods: Timer { + // This function will transit the timer state from “init” to “enable”. + // or from “enable” to “init” if enabel=false. + fn enable(&self, enable: bool) -> Result<(), EspError> { + if enable { + esp!(unsafe { gptimer_enable(self.get_handle()) })?; + } else { + esp!(unsafe { gptimer_disable(self.get_handle()) })?; + } + + Ok(()) + } + + // A timer must be in the “init” state before it can be deleted. + fn delete(&mut self) -> Result<(), EspError> { + esp!(unsafe { gptimer_del_timer(self.get_handle()) })?; + + Ok(()) + } + + async fn delay(&mut self, _counter: u64) -> Result<(), EspError> { + self.enable(false)?; + //self.enable_alarm(false)?; + //self.set_counter(0)?; + //self.set_alarm(counter)?; + + self.reset_wait(); + + //self.enable_interrupt()?; + //self.enable_alarm(true)?; + self.enable(true)?; + + self.wait().await + } + + fn reset_wait(&mut self) { + let notif = &PIN_NOTIF[self.get_index().unwrap() as usize]; + notif.reset(); + } + + async fn wait(&mut self) -> Result<(), EspError> { + let notif = &PIN_NOTIF[self.get_index().unwrap() as usize]; + + notif.wait().await; + + Ok(()) + } + + fn get_index(&self) -> Option; + } + + pub struct TimerDriverInISR(gptimer_handle_t); + + impl ISRMethods for TimerDriverInISR {} + impl TimerDriverInISR { + pub fn from_raw_handle(handle: gptimer_handle_t) -> Self { + Self(handle) + } + } + impl Timer for TimerDriverInISR { + fn get_handle(&self) -> gptimer_handle_t { + self.0 + } + } + + pub struct TimerDriver { + handle: gptimer_handle_t, + isr_handle_idx: Option, + } + impl Timer for TimerDriver { + fn get_handle(&self) -> gptimer_handle_t { + self.handle + } + } + impl ISRMethods for TimerDriver {} + impl NonISRSaveMethods for TimerDriver { + fn get_index(&self) -> Option { + self.isr_handle_idx + } + } + + impl TimerDriver { + pub fn new(config: &Config) -> Result { + let mut gptimer_ptr: *mut gptimer_t = core::ptr::null_mut(); + let ptr: *mut *mut gptimer_t = &mut gptimer_ptr; + + let conf = gptimer_config_t { + clk_src: if config.xtal { + soc_periph_gptimer_clk_src_t_GPTIMER_CLK_SRC_XTAL + } else { + soc_periph_gptimer_clk_src_t_GPTIMER_CLK_SRC_APB + }, + direction: match config.direction { + Direction::Up => gptimer_count_direction_t_GPTIMER_COUNT_UP, + Direction::Down => gptimer_count_direction_t_GPTIMER_COUNT_DOWN, + }, + resolution_hz: config.resolution, + // Flags in this context currently allow for setting the interrupt to be shared + //flags: timer_bindgen, + ..Default::default() + }; + + esp!(unsafe { gptimer_new_timer(&conf, ptr.cast()) })?; + + Ok(Self { + handle: if !ptr.is_null() { + unsafe { *ptr } + } else { + panic!("gptimer gave us a null pointer") + }, + isr_handle_idx: None, + }) + } + + pub unsafe fn subscribe<'d>( + &mut self, + callback: impl FnMut(&TimerDriverInISR, &AlarmEvent) + Send + 'd, + ) -> Result<(), EspError> { + // indexing for ISR_HANDLERS and PIN_NOTIF static arrays + let guard = GP_ISR_HANDLER_GUARD.enter(); + + let empty_index = + Self::find_empty_index().unwrap_or_else(|| panic!("no empty index found")); + self.isr_handle_idx = Some(empty_index); + + let isr_handle: Box = + Box::new(callback); + GP_ISR_HANDLERS[empty_index as usize] = + Some(unsafe { core::mem::transmute(isr_handle) }); + + drop(guard); + + let ptr_index = empty_index as *mut core::ffi::c_void; + + let event_callback = gptimer_event_callbacks_t { + on_alarm: Some(Self::callback), + }; + esp!(unsafe { + gptimer_register_event_callbacks(self.handle, &event_callback, ptr_index) + })?; + + Ok(()) + } + + #[cfg(feature = "alloc")] + pub fn unsubscribe(&mut self) -> Result<(), EspError> { + unsafe { + GP_ISR_HANDLERS[self.get_index().unwrap() as usize] = None; + } + + Ok(()) + } + + unsafe extern "C" fn callback( + handle: *mut gptimer_t, + event: *const gptimer_alarm_event_data_t, + user_data: *mut core::ffi::c_void, + ) -> bool { + use core::num::NonZeroU32; + + let driver = TimerDriverInISR::from_raw_handle(handle); + let data: &AlarmEvent = unsafe { &*event }.into(); + + let index = user_data as usize; + + crate::interrupt::with_isr_yield_signal(move || { + #[cfg(feature = "alloc")] + { + if let Some(handler) = GP_ISR_HANDLERS[index].as_mut() { + handler(&driver, data); + } + } + PIN_NOTIF[index].notify(NonZeroU32::new(1).unwrap()); + }) + } + + // find the first empty index in GP_ISR_HANDLER + // Safety: not thread save standalone !! + // To make it thread save this function should only be called in an GP_ISR_HANDLER_GUARD.enter() block + // and the guard should live till the the returnd index slot in the GP_ISR_HANDLER is filled + fn find_empty_index() -> Option { + for (i, handler) in unsafe { GP_ISR_HANDLERS.iter().enumerate() } { + if handler.is_none() { + return Some(i as u8); + } + } + None + } + } + + impl Drop for TimerDriver { + fn drop(&mut self) { + // This function will transit the timer state from “enable” to “init”. + // This function will disable the interrupt service if it’s installed. + // This function will release the PM lock if it’s acquired in the gptimer_enable. + esp!(unsafe { gptimer_disable(self.handle) }).unwrap(); + + #[cfg(feature = "alloc")] + unsafe { + if let Some(index) = self.isr_handle_idx { + GP_ISR_HANDLERS[index as usize] = None; + PIN_NOTIF[index as usize].reset(); + } + } + + // Delete the GPTimer handle. + // A timer must be in the “init” state before it can be deleted. + esp!(unsafe { gptimer_del_timer(self.handle) }).unwrap(); + } + } + + unsafe impl Send for TimerDriver {} + + pub struct AlarmConfig { + pub alarm_count: u64, + pub reload_count: u64, + pub auto_reload: bool, + } + + impl AlarmConfig { + pub fn new() -> Self { + Default::default() + } + + #[must_use] + pub fn alarm_count(mut self, alarm_count: u64) -> Self { + self.alarm_count = alarm_count; + self + } + + #[must_use] + pub fn reload_count(mut self, reload_count: u64) -> Self { + self.reload_count = reload_count; + self + } + + #[must_use] + pub fn auto_reload(mut self, auto_reload: bool) -> Self { + self.auto_reload = auto_reload; + self + } + } + + // impl default for Alarm Config + impl Default for AlarmConfig { + fn default() -> Self { + Self { + alarm_count: 1000, + reload_count: 0, + auto_reload: false, + } + } + } + + impl From for gptimer_alarm_config_t { + fn from(item: AlarmConfig) -> Self { + let mut flag = gptimer_alarm_config_t__bindgen_ty_1::default(); + Self { + alarm_count: item.alarm_count, + reload_count: item.reload_count, + flags: if item.auto_reload { + flag.set_auto_reload_on_alarm(1); + flag + } else { + flag.set_auto_reload_on_alarm(0); + flag + }, + } + } + } + + // maybe just reexport as new named type? + + // Rust - C conversions + #[repr(C)] + pub struct AlarmEvent { + pub count_value: u64, + pub alarm_value: u64, + } + + impl From<&gptimer_alarm_event_data_t> for &AlarmEvent { + fn from(item: &gptimer_alarm_event_data_t) -> Self { + unsafe { &*(item as *const gptimer_alarm_event_data_t as *const AlarmEvent) } + } + } + + #[cfg(feature = "alloc")] + static GP_ISR_HANDLER_GUARD: crate::task::CriticalSection = crate::task::CriticalSection::new(); + + #[allow(clippy::type_complexity)] + #[cfg(not(any(esp32, esp32s2, esp32s3)))] + #[cfg(feature = "alloc")] + static mut GP_ISR_HANDLERS: [Option< + Box, + >; 2] = [None, None]; + + #[allow(clippy::type_complexity)] + #[cfg(any(esp32, esp32s2, esp32s3))] + #[cfg(feature = "alloc")] + static mut GP_ISR_HANDLERS: [Option< + Box, + >; 4] = [None, None, None, None]; +}