diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml index 2cc60e9..49eafe5 100644 --- a/examples/embassy/Cargo.toml +++ b/examples/embassy/Cargo.toml @@ -13,7 +13,7 @@ panic-rtt-target = { version = "0.1" } critical-section = "1" embassy-sync = { version = "0.6.0" } -embassy-time = { version = "0.3.2", features = ["tick-hz-1_000"] } +embassy-time = { version = "0.3.2" } embassy-time-driver = { version = "0.1" } [dependencies.once_cell] @@ -33,3 +33,8 @@ features = [ [dependencies.va416xx-hal] path = "../../va416xx-hal" features = ["va41630"] + +[features] +default = ["ticks-hz-1_000"] +ticks-hz-1_000 = ["embassy-time/tick-hz-1_000"] +ticks-hz-32_768 = ["embassy-time/tick-hz-32_768"] diff --git a/examples/embassy/src/lib.rs b/examples/embassy/src/lib.rs index 8b3c317..b934a40 100644 --- a/examples/embassy/src/lib.rs +++ b/examples/embassy/src/lib.rs @@ -1,309 +1,4 @@ #![no_std] -use core::{ - cell::Cell, - mem, ptr, - sync::atomic::{AtomicU32, AtomicU8, Ordering}, -}; -use critical_section::CriticalSection; -use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_sync::blocking_mutex::Mutex; +pub mod time_driver; -use embassy_time_driver::{time_driver_impl, AlarmHandle, Driver, TICK_HZ}; -use rtt_target::rprintln; -use va416xx_hal::{ - clock::Clocks, - enable_interrupt, - irq_router::enable_and_init_irq_router, - pac::{self, interrupt}, - pwm::{assert_tim_reset_for_two_cycles, enable_tim_clk, ValidTim}, -}; - -pub type TimekeeperClk = pac::Tim15; -pub type AlarmClk0 = pac::Tim14; -pub type AlarmClk1 = pac::Tim13; -pub type AlarmClk2 = pac::Tim12; - -// Uses integer division to get a margin of 75 % of the base value added on the ticks -const fn three_quarters_of_period(period: u64) -> u64 { - (period * 3) / 4 -} - -/// Initialization method for embassy -/// -/// # Safety -/// This has to be called once at initialization time to initiate the time driver for -/// embassy. -pub unsafe fn init( - syscfg: &mut pac::Sysconfig, - irq_router: &pac::IrqRouter, - timekeeper: TimekeeperClk, - alarm: AlarmClk0, - clocks: &Clocks, -) { - enable_and_init_irq_router(syscfg, irq_router); - DRIVER.init(syscfg, timekeeper, alarm, clocks) -} - -const fn alarm_tim(idx: usize) -> &'static pac::tim0::RegisterBlock { - // Safety: This is a static memory-mapped peripheral. - match idx { - 0 => unsafe { &*AlarmClk0::ptr() }, - 1 => unsafe { &*AlarmClk1::ptr() }, - 2 => unsafe { &*AlarmClk2::ptr() }, - _ => { - panic!("invalid alarm timer index") - } - } -} - -const fn timekeeping_tim() -> &'static pac::tim0::RegisterBlock { - // Safety: This is a memory-mapped peripheral. - unsafe { &*TimekeeperClk::ptr() } -} - -struct AlarmState { - timestamp: Cell, - - // This is really a Option<(fn(*mut ()), *mut ())> - // but fn pointers aren't allowed in const yet - callback: Cell<*const ()>, - ctx: Cell<*mut ()>, -} - -impl AlarmState { - const fn new() -> Self { - Self { - timestamp: Cell::new(u64::MAX), - callback: Cell::new(ptr::null()), - ctx: Cell::new(ptr::null_mut()), - } - } -} - -unsafe impl Send for AlarmState {} - -const ALARM_COUNT: usize = 1; - -pub struct TimerDriverEmbassy { - periods: AtomicU32, - alarm_count: AtomicU8, - /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. - alarms: Mutex, -} - -impl TimerDriverEmbassy { - fn init( - &self, - syscfg: &mut pac::Sysconfig, - timekeeper: TimekeeperClk, - alarm_tim: AlarmClk0, - clocks: &Clocks, - ) { - enable_tim_clk(syscfg, TimekeeperClk::TIM_ID); - assert_tim_reset_for_two_cycles(syscfg, TimekeeperClk::TIM_ID); - - let rst_val = (TimekeeperClk::clock(clocks).raw() / TICK_HZ as u32) - 1; - timekeeper.rst_value().write(|w| unsafe { w.bits(rst_val) }); - // Decrementing counter. - timekeeper.cnt_value().write(|w| unsafe { w.bits(rst_val) }); - // Switch on. Timekeeping should always be done. - unsafe { - enable_interrupt(TimekeeperClk::IRQ); - } - timekeeper.ctrl().modify(|_, w| w.irq_enb().set_bit()); - timekeeper.enable().write(|w| unsafe { w.bits(1) }); - - enable_tim_clk(syscfg, AlarmClk0::TIM_ID); - assert_tim_reset_for_two_cycles(syscfg, AlarmClk0::TIM_ID); - - // Explicitely disable alarm timer until needed. - alarm_tim.ctrl().modify(|_, w| { - w.irq_enb().clear_bit(); - w.enable().clear_bit() - }); - // Enable general interrupts. The IRQ enable of the peripheral remains cleared. - unsafe { - enable_interrupt(AlarmClk0::IRQ); - } - } - - // Should be called inside the IRQ of the timekeeper timer. - fn on_interrupt_timekeeping(&self) { - self.next_period(); - } - - // Should be called inside the IRQ of the alarm timer. - fn on_interrupt_alarm(&self, idx: usize) { - critical_section::with(|cs| { - if self.alarms.borrow(cs)[idx].timestamp.get() <= self.now() { - self.trigger_alarm(idx, cs) - } - }) - } - - fn next_period(&self) { - let period = self.periods.fetch_add(1, Ordering::AcqRel) + 1; - let rst_val = timekeeping_tim().rst_value().read().bits() as u64; - let t = period as u64 * rst_val; - critical_section::with(|cs| { - for i in 0..ALARM_COUNT { - let alarm = &self.alarms.borrow(cs)[i]; - let at = alarm.timestamp.get(); - let alarm_tim = alarm_tim(0); - if at < t { - self.trigger_alarm(i, cs); - } else if at - t <= u32::MAX as u64 { - alarm_tim.enable().write(|w| unsafe { w.bits(0) }); - alarm_tim - .cnt_value() - .write(|w| unsafe { w.bits((at - t) as u32) }); - alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit()); - alarm_tim.enable().write(|w| unsafe { w.bits(1) }) - } - } - }) - } - - fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState { - // safety: we're allowed to assume the AlarmState is created by us, and - // we never create one that's out of bounds. - unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) } - } - - fn trigger_alarm(&self, n: usize, cs: CriticalSection) { - alarm_tim(n).ctrl().modify(|_, w| { - w.irq_enb().clear_bit(); - w.enable().clear_bit() - }); - - let alarm = &self.alarms.borrow(cs)[n]; - // Setting the maximum value disables the alarm. - alarm.timestamp.set(u64::MAX); - - // Call after clearing alarm, so the callback can set another alarm. - - // safety: - // - we can ignore the possiblity 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 - let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) }; - f(alarm.ctx.get()); - } -} - -impl Driver for TimerDriverEmbassy { - fn now(&self) -> u64 { - let mut period1: u32; - let mut period2: u32; - let mut counter_val: u32; - - let rst_val = timekeeping_tim().rst_value().read().bits(); - loop { - // Acquire ensures that we get the latest value of `periods` and - // no instructions can be reordered before the load. - period1 = self.periods.load(Ordering::Acquire); - - counter_val = rst_val - timekeeping_tim().cnt_value().read().bits(); - - // Double read to protect against race conditions when the counter is overflowing. - period2 = self.periods.load(Ordering::Relaxed); - if period1 == period2 { - return (period1 as u64 * rst_val as u64) + counter_val as u64; - } - } - } - - unsafe fn allocate_alarm(&self) -> Option { - let id = self - .alarm_count - .fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { - if x < ALARM_COUNT as u8 { - Some(x + 1) - } else { - None - } - }); - - match id { - Ok(id) => Some(AlarmHandle::new(id)), - Err(_) => None, - } - } - - fn set_alarm_callback( - &self, - alarm: embassy_time_driver::AlarmHandle, - callback: fn(*mut ()), - ctx: *mut (), - ) { - critical_section::with(|cs| { - let alarm = self.get_alarm(cs, alarm); - - alarm.callback.set(callback as *const ()); - alarm.ctx.set(ctx); - }) - } - - fn set_alarm(&self, alarm: embassy_time_driver::AlarmHandle, timestamp: u64) -> bool { - critical_section::with(|cs| { - let n = alarm.id(); - let alarm_tim = alarm_tim(n.into()); - alarm_tim.ctrl().modify(|_, w| { - w.irq_enb().clear_bit(); - w.enable().clear_bit() - }); - - let alarm = self.get_alarm(cs, alarm); - alarm.timestamp.set(timestamp); - - let t = self.now(); - if timestamp <= t { - alarm.timestamp.set(u64::MAX); - return false; - } - - // If it hasn't triggered yet, setup the relevant reset value, regardless of whether - // the interrupts are enabled or not. When they are enabled at a later point, the - // right value is already set. - - // If the timestamp is in the next few ticks, add a bit of buffer to be sure the alarm - // is not missed. - // - // This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed - // by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time, - // and we don't do that here. - let safe_timestamp = timestamp.max(t + 3); - let diff = safe_timestamp - t; - alarm_tim.rst_value().write(|w| unsafe { w.bits(u32::MAX) }); - if diff <= u32::MAX as u64 { - alarm_tim - .cnt_value() - .write(|w| unsafe { w.bits(diff as u32) }); - alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit()); - alarm_tim.enable().write(|w| unsafe { w.bits(1) }); - } - // If it's too far in the future, don't enable timer yet. - // It will be enabled later by `next_period`. - - true - }) - } -} - -time_driver_impl!( - static DRIVER: TimerDriverEmbassy = TimerDriverEmbassy { - periods: AtomicU32::new(0), - alarm_count: AtomicU8::new(0), - alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [AlarmState::new(); ALARM_COUNT]) -}); - -#[interrupt] -#[allow(non_snake_case)] -fn TIM15() { - DRIVER.on_interrupt_timekeeping() -} - -#[interrupt] -#[allow(non_snake_case)] -fn TIM14() { - DRIVER.on_interrupt_alarm(0) -} +pub use time_driver::init; diff --git a/examples/embassy/src/main.rs b/examples/embassy/src/main.rs index 2680937..6f75702 100644 --- a/examples/embassy/src/main.rs +++ b/examples/embassy/src/main.rs @@ -1,7 +1,7 @@ #![no_std] #![no_main] use embassy_executor::Spawner; -use embassy_time::Timer; +use embassy_time::{Duration, Instant, Ticker}; use embedded_hal::digital::StatefulOutputPin; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; @@ -37,8 +37,10 @@ async fn main(_spawner: Spawner) { }; let portg = PinsG::new(&mut dp.sysconfig, dp.portg); let mut led = portg.pg5.into_readable_push_pull_output(); + let mut ticker = Ticker::every(Duration::from_secs(1)); loop { - Timer::after_secs(1).await; + ticker.next().await; + rprintln!("Current time: {}", Instant::now().as_secs()); led.toggle().ok(); } } diff --git a/examples/embassy/src/time_driver.rs b/examples/embassy/src/time_driver.rs new file mode 100644 index 0000000..bf88171 --- /dev/null +++ b/examples/embassy/src/time_driver.rs @@ -0,0 +1,323 @@ +//! This is a sample time driver implementation for the VA416xx family of devices, supporting +//! one alarm and requiring/reserving 2 TIM peripherals. You could adapt this implementation to +//! support more alarms. +use core::{ + cell::Cell, + mem, ptr, + sync::atomic::{AtomicU32, AtomicU8, Ordering}, +}; +use critical_section::CriticalSection; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex; + +use embassy_time_driver::{time_driver_impl, AlarmHandle, Driver, TICK_HZ}; +use once_cell::sync::OnceCell; +use va416xx_hal::{ + clock::Clocks, + enable_interrupt, + irq_router::enable_and_init_irq_router, + pac::{self, interrupt}, + pwm::{assert_tim_reset_for_two_cycles, enable_tim_clk, ValidTim}, +}; + +pub type TimekeeperClk = pac::Tim15; +pub type AlarmClk0 = pac::Tim14; +pub type AlarmClk1 = pac::Tim13; +pub type AlarmClk2 = pac::Tim12; + +/// Initialization method for embassy +/// +/// # Safety +/// This has to be called once at initialization time to initiate the time driver for +/// embassy. +pub unsafe fn init( + syscfg: &mut pac::Sysconfig, + irq_router: &pac::IrqRouter, + timekeeper: TimekeeperClk, + alarm: AlarmClk0, + clocks: &Clocks, +) { + enable_and_init_irq_router(syscfg, irq_router); + DRIVER.init(syscfg, timekeeper, alarm, clocks) +} + +const fn alarm_tim(idx: usize) -> &'static pac::tim0::RegisterBlock { + // Safety: This is a static memory-mapped peripheral. + match idx { + 0 => unsafe { &*AlarmClk0::ptr() }, + 1 => unsafe { &*AlarmClk1::ptr() }, + 2 => unsafe { &*AlarmClk2::ptr() }, + _ => { + panic!("invalid alarm timer index") + } + } +} + +const fn timekeeping_tim() -> &'static pac::tim0::RegisterBlock { + // Safety: This is a memory-mapped peripheral. + unsafe { &*TimekeeperClk::ptr() } +} + +struct AlarmState { + timestamp: Cell, + + // This is really a Option<(fn(*mut ()), *mut ())> + // but fn pointers aren't allowed in const yet + callback: Cell<*const ()>, + ctx: Cell<*mut ()>, +} + +impl AlarmState { + const fn new() -> Self { + Self { + timestamp: Cell::new(u64::MAX), + callback: Cell::new(ptr::null()), + ctx: Cell::new(ptr::null_mut()), + } + } +} + +unsafe impl Send for AlarmState {} + +const ALARM_COUNT: usize = 1; + +static SCALE: OnceCell = OnceCell::new(); + +pub struct TimerDriverEmbassy { + periods: AtomicU32, + alarm_count: AtomicU8, + /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. + alarms: Mutex, +} + +impl TimerDriverEmbassy { + fn init( + &self, + syscfg: &mut pac::Sysconfig, + timekeeper: TimekeeperClk, + alarm_tim: AlarmClk0, + clocks: &Clocks, + ) { + enable_tim_clk(syscfg, TimekeeperClk::TIM_ID); + assert_tim_reset_for_two_cycles(syscfg, TimekeeperClk::TIM_ID); + + // Initiate scale value here. This is required to convert timer ticks back to a timestamp. + SCALE + .set((TimekeeperClk::clock(clocks).raw() / TICK_HZ as u32) as u64) + .unwrap(); + timekeeper + .rst_value() + .write(|w| unsafe { w.bits(u32::MAX) }); + // Decrementing counter. + timekeeper + .cnt_value() + .write(|w| unsafe { w.bits(u32::MAX) }); + // Switch on. Timekeeping should always be done. + unsafe { + enable_interrupt(TimekeeperClk::IRQ); + } + timekeeper.ctrl().modify(|_, w| w.irq_enb().set_bit()); + timekeeper.enable().write(|w| unsafe { w.bits(1) }); + + enable_tim_clk(syscfg, AlarmClk0::TIM_ID); + assert_tim_reset_for_two_cycles(syscfg, AlarmClk0::TIM_ID); + + // Explicitely disable alarm timer until needed. + alarm_tim.ctrl().modify(|_, w| { + w.irq_enb().clear_bit(); + w.enable().clear_bit() + }); + // Enable general interrupts. The IRQ enable of the peripheral remains cleared. + unsafe { + enable_interrupt(AlarmClk0::IRQ); + } + } + + // Should be called inside the IRQ of the timekeeper timer. + fn on_interrupt_timekeeping(&self) { + self.next_period(); + } + + // Should be called inside the IRQ of the alarm timer. + fn on_interrupt_alarm(&self, idx: usize) { + critical_section::with(|cs| { + if self.alarms.borrow(cs)[idx].timestamp.get() <= self.now() { + self.trigger_alarm(idx, cs) + } + }) + } + + fn next_period(&self) { + let period = self.periods.fetch_add(1, Ordering::AcqRel) + 1; + let t = (period as u64) << 32; + critical_section::with(|cs| { + for i in 0..ALARM_COUNT { + let alarm = &self.alarms.borrow(cs)[i]; + let at = alarm.timestamp.get(); + let alarm_tim = alarm_tim(0); + if at < t { + self.trigger_alarm(i, cs); + } else { + let remaining_ticks = (at - t) * *SCALE.get().unwrap(); + if remaining_ticks <= u32::MAX as u64 { + alarm_tim.enable().write(|w| unsafe { w.bits(0) }); + alarm_tim + .cnt_value() + .write(|w| unsafe { w.bits(remaining_ticks as u32) }); + alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit()); + alarm_tim.enable().write(|w| unsafe { w.bits(1) }) + } + } + } + }) + } + + fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState { + // safety: we're allowed to assume the AlarmState is created by us, and + // we never create one that's out of bounds. + unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) } + } + + fn trigger_alarm(&self, n: usize, cs: CriticalSection) { + alarm_tim(n).ctrl().modify(|_, w| { + w.irq_enb().clear_bit(); + w.enable().clear_bit() + }); + + let alarm = &self.alarms.borrow(cs)[n]; + // Setting the maximum value disables the alarm. + alarm.timestamp.set(u64::MAX); + + // Call after clearing alarm, so the callback can set another alarm. + + // safety: + // - we can ignore the possiblity 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 + let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) }; + f(alarm.ctx.get()); + } +} + +impl Driver for TimerDriverEmbassy { + fn now(&self) -> u64 { + if SCALE.get().is_none() { + return 0; + } + let mut period1: u32; + let mut period2: u32; + let mut counter_val: u32; + + loop { + // Acquire ensures that we get the latest value of `periods` and + // no instructions can be reordered before the load. + period1 = self.periods.load(Ordering::Acquire); + + counter_val = u32::MAX - timekeeping_tim().cnt_value().read().bits(); + + // Double read to protect against race conditions when the counter is overflowing. + period2 = self.periods.load(Ordering::Relaxed); + if period1 == period2 { + let now = (((period1 as u64) << 32) | counter_val as u64) / *SCALE.get().unwrap(); + return now; + } + } + } + + unsafe fn allocate_alarm(&self) -> Option { + let id = self + .alarm_count + .fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { + if x < ALARM_COUNT as u8 { + Some(x + 1) + } else { + None + } + }); + + match id { + Ok(id) => Some(AlarmHandle::new(id)), + Err(_) => None, + } + } + + fn set_alarm_callback( + &self, + alarm: embassy_time_driver::AlarmHandle, + callback: fn(*mut ()), + ctx: *mut (), + ) { + critical_section::with(|cs| { + let alarm = self.get_alarm(cs, alarm); + + alarm.callback.set(callback as *const ()); + alarm.ctx.set(ctx); + }) + } + + fn set_alarm(&self, alarm: embassy_time_driver::AlarmHandle, timestamp: u64) -> bool { + if SCALE.get().is_none() { + return false; + } + critical_section::with(|cs| { + let n = alarm.id(); + let alarm_tim = alarm_tim(n.into()); + alarm_tim.ctrl().modify(|_, w| { + w.irq_enb().clear_bit(); + w.enable().clear_bit() + }); + + let alarm = self.get_alarm(cs, alarm); + alarm.timestamp.set(timestamp); + + let t = self.now(); + if timestamp <= t { + alarm.timestamp.set(u64::MAX); + return false; + } + + // If it hasn't triggered yet, setup the relevant reset value, regardless of whether + // the interrupts are enabled or not. When they are enabled at a later point, the + // right value is already set. + + // If the timestamp is in the next few ticks, add a bit of buffer to be sure the alarm + // is not missed. + // + // This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed + // by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time, + // and we don't do that here. + let safe_timestamp = timestamp.max(t + 3); + let timer_ticks = (safe_timestamp - t) * *SCALE.get().unwrap(); + alarm_tim.rst_value().write(|w| unsafe { w.bits(u32::MAX) }); + if timer_ticks <= u32::MAX as u64 { + alarm_tim + .cnt_value() + .write(|w| unsafe { w.bits(timer_ticks as u32) }); + alarm_tim.ctrl().modify(|_, w| w.irq_enb().set_bit()); + alarm_tim.enable().write(|w| unsafe { w.bits(1) }); + } + // If it's too far in the future, don't enable timer yet. + // It will be enabled later by `next_period`. + + true + }) + } +} + +time_driver_impl!( + static DRIVER: TimerDriverEmbassy = TimerDriverEmbassy { + periods: AtomicU32::new(0), + alarm_count: AtomicU8::new(0), + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [AlarmState::new(); ALARM_COUNT]) +}); + +#[interrupt] +#[allow(non_snake_case)] +fn TIM15() { + DRIVER.on_interrupt_timekeeping() +} + +#[interrupt] +#[allow(non_snake_case)] +fn TIM14() { + DRIVER.on_interrupt_alarm(0) +}