diff --git a/.cargo/def-config.toml b/.cargo/def-config.toml index 22e52d8..6568a3f 100644 --- a/.cargo/def-config.toml +++ b/.cargo/def-config.toml @@ -1,13 +1,11 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = "gdb-multiarch -q -x jlink/jlink.gdb" +# runner = "gdb-multiarch -q -x jlink/jlink.gdb" # runner = "arm-none-eabi-gdb -q -x jlink/jlink-reva.gdb" # runner = "gdb-multiarch -q -x jlink/jlink-reva.gdb" # Probe-rs is currently problematic, possibly because of the # ROM protection? -# runner = "probe-rs run --chip-description-path ./scripts/VA416xx_Series.yaml" -# runner = ["probe-rs", "run", "--chip", "$CHIP", "--log-format", "{L} {s}"] - +runner = "probe-rs run --chip VA416xx_RAM --protocol swd" rustflags = [ "-C", diff --git a/Cargo.toml b/Cargo.toml index 286778a..645a69b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "va416xx", "va416xx-hal", + "va416xx-embassy", "vorago-peb1", "bootloader", "flashloader", diff --git a/README.md b/README.md index a3db5da..aad2a6e 100644 --- a/README.md +++ b/README.md @@ -119,3 +119,9 @@ configuration variables in your `settings.json`: The provided VS Code configurations also provide an integrated RTT logger, which you can access via the terminal at `RTT Ch:0 console`. In order for the RTT block address detection to work properly, `objdump-multiarch` and `nm-multiarch` need to be installed. + +### Using the RTT Viewer + +The RTT viewer can be used to display log messages received from the target. The base address +for the RTT block placement is 0x1fff8000. It is recommended to use a search range of 0x1000 around +that base address when using the RTT viewer. diff --git a/examples/README.md b/examples/README.md index 99fcfb1..83a27ad 100644 --- a/examples/README.md +++ b/examples/README.md @@ -20,6 +20,14 @@ cargo run --bin rtic-example ## Embassy example +Blinky with time driver IRQs in library + ```rs cargo run --bin embassy-example ``` + +Blinky with custom time driver IRQs + +```rs +cargo run --bin embassy-example --no-default-features --features custom-irqs +``` diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml index 9239590..a384ee2 100644 --- a/examples/embassy/Cargo.toml +++ b/examples/embassy/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +cfg-if = "1" cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" embedded-hal = "1" @@ -13,9 +14,9 @@ rtt-target = { version = "0.5" } panic-rtt-target = { version = "0.1" } critical-section = "1" -embassy-sync = { version = "0.6.0" } -embassy-time = { version = "0.3.2" } -embassy-time-driver = { version = "0.1" } +embassy-sync = { version = "0.6" } +embassy-time = { version = "0.4" } +va416xx-embassy = { path = "../../va416xx-embassy", default-features = false } [dependencies.ringbuf] version = "0.4" @@ -27,12 +28,11 @@ default-features = false features = ["critical-section"] [dependencies.embassy-executor] -version = "0.6.0" +version = "0.7" features = [ "arch-cortex-m", "executor-thread", "executor-interrupt", - "integrated-timers", ] [dependencies.va416xx-hal] @@ -40,6 +40,7 @@ path = "../../va416xx-hal" features = ["va41630"] [features] -default = ["ticks-hz-1_000"] +default = ["ticks-hz-1_000", "va416xx-embassy/irq-tim14-tim15"] +custom-irqs = [] 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/bin/uart-echo-with-irq.rs b/examples/embassy/src/bin/uart-echo-with-irq.rs index e4e97ee..66d1623 100644 --- a/examples/embassy/src/bin/uart-echo-with-irq.rs +++ b/examples/embassy/src/bin/uart-echo-with-irq.rs @@ -64,7 +64,7 @@ async fn main(spawner: Spawner) { .unwrap(); // Safety: Only called once here. unsafe { - embassy_example::init( + va416xx_embassy::init( &mut dp.sysconfig, &dp.irq_router, dp.tim15, diff --git a/examples/embassy/src/lib.rs b/examples/embassy/src/lib.rs index d00ea29..a0e30ef 100644 --- a/examples/embassy/src/lib.rs +++ b/examples/embassy/src/lib.rs @@ -1,6 +1,2 @@ #![no_std] -pub mod time_driver; - pub const EXTCLK_FREQ: u32 = 40_000_000; - -pub use time_driver::init; diff --git a/examples/embassy/src/main.rs b/examples/embassy/src/main.rs index d8345a2..6455684 100644 --- a/examples/embassy/src/main.rs +++ b/examples/embassy/src/main.rs @@ -8,6 +8,13 @@ use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; use va416xx_hal::{gpio::PinsG, pac, prelude::*, time::Hertz}; +cfg_if::cfg_if! { + if #[cfg(feature = "custom-irqs")] { + use va416xx_hal::pac::interrupt; + va416xx_embassy::embassy_time_driver_irqs!(timekeeper_irq = TIM12, alarm_irq = TIM11); + } +} + // main is itself an async function. #[embassy_executor::main] async fn main(_spawner: Spawner) { @@ -26,14 +33,26 @@ async fn main(_spawner: Spawner) { .unwrap(); // Safety: Only called once here. unsafe { - embassy_example::init( - &mut dp.sysconfig, - &dp.irq_router, - dp.tim15, - dp.tim14, - &clocks, - ) - }; + cfg_if::cfg_if! { + if #[cfg(not(feature = "custom-irqs"))] { + va416xx_embassy::init( + &mut dp.sysconfig, + &dp.irq_router, + dp.tim15, + dp.tim14, + &clocks + ); + } else { + va416xx_embassy::init( + &mut dp.sysconfig, + &dp.irq_router, + dp.tim12, + dp.tim11, + &clocks + ); + } + } + } 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)); diff --git a/examples/embassy/src/time_driver.rs b/examples/embassy/src/time_driver.rs deleted file mode 100644 index f4d94e4..0000000 --- a/examples/embassy/src/time_driver.rs +++ /dev/null @@ -1,323 +0,0 @@ -//! 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}, - timer::{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) -} diff --git a/jlink/jlink.gdb b/jlink/jlink.gdb index 2eac6a0..b7220cf 100644 --- a/jlink/jlink.gdb +++ b/jlink/jlink.gdb @@ -1,6 +1,6 @@ target remote localhost:2331 -monitor halt +monitor reset # *try* to stop at the user entry point (it might be gone due to inlining) break main diff --git a/va416xx-embassy/CHANGELOG.md b/va416xx-embassy/CHANGELOG.md new file mode 100644 index 0000000..87c4b55 --- /dev/null +++ b/va416xx-embassy/CHANGELOG.md @@ -0,0 +1,13 @@ +Change Log +======= + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [unreleased] + +## [v0.1.0] 2025-02-13 + +Initial release diff --git a/va416xx-embassy/Cargo.toml b/va416xx-embassy/Cargo.toml new file mode 100644 index 0000000..2453bcf --- /dev/null +++ b/va416xx-embassy/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "va416xx-embassy" +version = "0.1.0" +edition = "2021" +authors = ["Robin Mueller "] +description = "Embassy-rs support for the Vorago VA416xx family of microcontrollers" +homepage = "https://egit.irs.uni-stuttgart.de/rust/va416xx-rs" +repository = "https://egit.irs.uni-stuttgart.de/rust/va416xx-rs" +license = "Apache-2.0" +keywords = ["no-std", "hal", "cortex-m", "vorago", "va416xx"] +categories = ["aerospace", "embedded", "no-std", "hardware-support"] + +[dependencies] +critical-section = "1" + +embassy-sync = "0.6" +embassy-executor = "0.7" +embassy-time-driver = "0.2" +embassy-time-queue-utils = "0.1" +portable-atomic = "1" + +once_cell = { version = "1", default-features = false, features = ["critical-section"] } + +va416xx-hal = { version = "0.3", path = "../va416xx-hal" } + +[features] +default = ["irq-tim14-tim15"] +irqs-in-lib = [] +# This determines the reserved interrupt functions for the embassy time drivers. Only one +# is allowed to be selected! +irq-tim14-tim15 = ["irqs-in-lib"] +irq-tim13-tim14 = ["irqs-in-lib"] +# These TIMs are clocked slower! +irq-tim22-tim23 = ["irqs-in-lib"] + +[package.metadata.docs.rs] +rustdoc-args = ["--generate-link-to-definition"] diff --git a/va416xx-embassy/src/lib.rs b/va416xx-embassy/src/lib.rs new file mode 100644 index 0000000..eade0d8 --- /dev/null +++ b/va416xx-embassy/src/lib.rs @@ -0,0 +1,398 @@ +//! # Embassy-rs support for the Vorago VA416xx MCU family +//! +//! This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for the +//! VA416xx family. Currently, it contains the time driver to allow using embassy-rs. It uses the TIM +//! peripherals provided by the VA416xx family for this purpose. +//! +//! ## Usage +//! +//! This library only exposes the [embassy::init] method which sets up the time driver. This +//! function must be called once at the start of the application. +//! +//! This implementation requires two TIM peripherals provided by the VA108xx device. +//! The user can freely specify the two used TIM peripheral by passing the concrete TIM instances +//! into the [init] method. If the interrupt handlers are provided by the library, the ID of the +//! used TIM peripherals has to match the ID of the passed timer peripherals. Currently, this +//! can only be checked at run-time, and a run-time assertion will panic on the embassy +//! initialization in case of a missmatch. +//! +//! The application also requires two interrupt handlers to handle the timekeeper and alarm +//! interrupts. By default, this library will define the interrupt handler inside the library +//! itself by using the `irq-tim14-tim15` feature flag. This library exposes three combinations: +//! +//! - `irq-tim14-tim15`: Uses [pac::Interrupt::TIM14] for alarm and [pac::Interrupt::TIM15] +//! for timekeeper +//! - `irq-tim13-tim14`: Uses [pac::Interrupt::TIM13] for alarm and [pac::Interrupt::TIM14] +//! for timekeeper +//! - `irq-tim22-tim23`: Uses [pac::Interrupt::TIM22] for alarm and [pac::Interrupt::TIM23] +//! for timekeeper +//! +//! You can disable the default features and then specify one of the features above to use the +//! documented combination of IRQs. It is also possible to specify custom IRQs by importing and +//! using the [embassy_time_driver_irqs] macro to declare the IRQ handlers in the +//! application code. If this is done, [embassy::init_with_custom_irqs] must be used +//! method to pass the IRQ numbers to the library. +//! +//! ## Examples +//! +//! [embassy example projects](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy) +#![no_std] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +use core::{ + cell::{Cell, RefCell}, + sync::atomic::{AtomicU32, Ordering}, +}; + +use critical_section::{CriticalSection, Mutex}; + +use embassy_time_driver::{time_driver_impl, Driver, TICK_HZ}; +use embassy_time_queue_utils::Queue; +use once_cell::sync::OnceCell; +use va416xx_hal::{ + clock::Clocks, + enable_interrupt, + irq_router::enable_and_init_irq_router, + pac::{self, interrupt}, + pwm::ValidTim, + timer::{ + assert_tim_reset_for_two_cycles, enable_tim_clk, get_tim_raw, TimRegInterface, + TIM_IRQ_OFFSET, + }, +}; + +time_driver_impl!( + static TIME_DRIVER: TimerDriver = TimerDriver { + periods: AtomicU32::new(0), + alarms: Mutex::new(AlarmState::new()), + queue: Mutex::new(RefCell::new(Queue::new())), +}); + +/// Macro to define the IRQ handlers for the time driver. +/// +/// By default, the code generated by this macro will be defined inside the library depending on +/// the feature flags specified. However, the macro is exported to allow users to specify the +/// interrupt handlers themselves. +/// +/// Please note that you have to explicitely import the [macro@va108xx_hal::pac::interrupt] +/// macro in the application code in case this macro is used there. +#[macro_export] +macro_rules! embassy_time_driver_irqs { + ( + timekeeper_irq = $timekeeper_irq:ident, + alarm_irq = $alarm_irq:ident + ) => { + const TIMEKEEPER_IRQ: pac::Interrupt = pac::Interrupt::$timekeeper_irq; + + #[interrupt] + #[allow(non_snake_case)] + fn $timekeeper_irq() { + // Safety: We call it once here. + unsafe { $crate::time_driver().on_interrupt_timekeeping() } + } + + const ALARM_IRQ: pac::Interrupt = pac::Interrupt::$alarm_irq; + + #[interrupt] + #[allow(non_snake_case)] + fn $alarm_irq() { + // Safety: We call it once here. + unsafe { $crate::time_driver().on_interrupt_alarm() } + } + }; +} + +// Provide three combinations of IRQs for the time driver by default. + +#[cfg(feature = "irq-tim14-tim15")] +embassy_time_driver_irqs!(timekeeper_irq = TIM15, alarm_irq = TIM14); +#[cfg(feature = "irq-tim13-tim14")] +embassy_time_driver_irqs!(timekeeper_irq = TIM14, alarm_irq = TIM13); +#[cfg(feature = "irq-tim22-tim23")] +embassy_time_driver_irqs!(timekeeper_irq = TIM23, alarm_irq = TIM22); + +/// Expose the time driver so the user can specify the IRQ handlers themselves. +pub fn time_driver() -> &'static TimerDriver { + &TIME_DRIVER +} + +/// Initialization method for embassy +/// +/// If the interrupt handlers are provided by the library, the ID of the +/// used TIM peripherals has to match the ID of the passed timer peripherals. Currently, this +/// can only be checked at run-time, and a run-time assertion will panic on the embassy +/// initialization in case of a missmatch. +/// +/// # Safety +/// +/// This has to be called once at initialization time to initiate the time driver for +/// embassy. +pub unsafe fn init< + TimekeeperTim: TimRegInterface + ValidTim, + AlarmTim: TimRegInterface + ValidTim, +>( + syscfg: &mut pac::Sysconfig, + irq_router: &pac::IrqRouter, + timekeeper: TimekeeperTim, + alarm: AlarmTim, + clocks: &Clocks, +) { + #[cfg(feature = "irqs-in-lib")] + assert_eq!( + TimekeeperTim::ID, + TIMEKEEPER_IRQ as u8 - TIM_IRQ_OFFSET as u8, + "Timekeeper TIM and IRQ missmatch" + ); + #[cfg(feature = "irqs-in-lib")] + assert_eq!( + AlarmTim::ID, + ALARM_IRQ as u8 - TIM_IRQ_OFFSET as u8, + "Alarm TIM and IRQ missmatch" + ); + enable_and_init_irq_router(syscfg, irq_router); + TIME_DRIVER.init(syscfg, timekeeper, alarm, clocks) +} + +struct AlarmState { + timestamp: Cell, +} + +impl AlarmState { + const fn new() -> Self { + Self { + timestamp: Cell::new(u64::MAX), + } + } +} + +unsafe impl Send for AlarmState {} + +static SCALE: OnceCell = OnceCell::new(); +static TIMEKEEPER_TIM: OnceCell = OnceCell::new(); +static ALARM_TIM: OnceCell = OnceCell::new(); + +pub struct TimerDriver { + periods: AtomicU32, + /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. + alarms: Mutex, + queue: Mutex>, +} + +impl TimerDriver { + fn init( + &self, + syscfg: &mut pac::Sysconfig, + timekeeper_tim: TimekeeperTim, + alarm_tim: AlarmTim, + clocks: &Clocks, + ) { + if ALARM_TIM.get().is_some() || TIMEKEEPER_TIM.get().is_some() { + return; + } + ALARM_TIM.set(alarm_tim.tim_id()).ok(); + TIMEKEEPER_TIM.set(timekeeper_tim.tim_id()).ok(); + enable_tim_clk(syscfg, timekeeper_tim.tim_id()); + assert_tim_reset_for_two_cycles(syscfg, alarm_tim.tim_id()); + + // Initiate scale value here. This is required to convert timer ticks back to a timestamp. + SCALE + .set((TimekeeperTim::clock(clocks).raw() / TICK_HZ as u32) as u64) + .unwrap(); + let timekeeper_tim_regs = timekeeper_tim.reg_block(); + timekeeper_tim_regs + .rst_value() + .write(|w| unsafe { w.bits(u32::MAX) }); + // Decrementing counter. + timekeeper_tim_regs + .cnt_value() + .write(|w| unsafe { w.bits(u32::MAX) }); + // Switch on. Timekeeping should always be done. + unsafe { + enable_interrupt(TimekeeperTim::IRQ); + } + timekeeper_tim_regs + .ctrl() + .modify(|_, w| w.irq_enb().set_bit()); + timekeeper_tim_regs.enable().write(|w| unsafe { w.bits(1) }); + + enable_tim_clk(syscfg, AlarmTim::ID); + assert_tim_reset_for_two_cycles(syscfg, AlarmTim::ID); + let alarm_tim_regs = alarm_tim.reg_block(); + // Explicitely disable alarm timer until needed. + alarm_tim_regs.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(AlarmTim::IRQ); + } + } + + fn timekeeper_tim() -> &'static pac::tim0::RegisterBlock { + TIMEKEEPER_TIM + .get() + .map(|idx| unsafe { get_tim_raw(*idx as usize) }) + .unwrap() + } + fn alarm_tim() -> &'static pac::tim0::RegisterBlock { + ALARM_TIM + .get() + .map(|idx| unsafe { get_tim_raw(*idx as usize) }) + .unwrap() + } + + /// Should be called inside the IRQ of the timekeeper timer. + /// + /// # Safety + /// + /// This function has to be called once by the TIM IRQ used for the timekeeping. + pub unsafe fn on_interrupt_timekeeping(&self) { + self.next_period(); + } + + /// Should be called inside the IRQ of the alarm timer. + /// + /// # Safety + /// + ///This function has to be called once by the TIM IRQ used for the timekeeping. + pub unsafe fn on_interrupt_alarm(&self) { + critical_section::with(|cs| { + if self.alarms.borrow(cs).timestamp.get() <= self.now() { + self.trigger_alarm(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| { + let alarm = &self.alarms.borrow(cs); + let at = alarm.timestamp.get(); + if at < t { + self.trigger_alarm(cs); + } else { + let alarm_tim = Self::alarm_tim(); + + 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 trigger_alarm(&self, cs: CriticalSection) { + Self::alarm_tim().ctrl().modify(|_, w| { + w.irq_enb().clear_bit(); + w.enable().clear_bit() + }); + + let alarm = &self.alarms.borrow(cs); + // Setting the maximum value disables the alarm. + alarm.timestamp.set(u64::MAX); + + // Call after clearing alarm, so the callback can set another alarm. + let mut next = self + .queue + .borrow(cs) + .borrow_mut() + .next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = self + .queue + .borrow(cs) + .borrow_mut() + .next_expiration(self.now()); + } + } + + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + if SCALE.get().is_none() { + return false; + } + let alarm_tim = Self::alarm_tim(); + alarm_tim.ctrl().modify(|_, w| { + w.irq_enb().clear_bit(); + w.enable().clear_bit() + }); + + let alarm = self.alarms.borrow(cs); + 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).checked_mul(*SCALE.get().unwrap()); + alarm_tim.rst_value().write(|w| unsafe { w.bits(u32::MAX) }); + if timer_ticks.is_some_and(|v| v <= u32::MAX as u64) { + alarm_tim + .cnt_value() + .write(|w| unsafe { w.bits(timer_ticks.unwrap() 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 + } +} + +impl Driver for TimerDriver { + 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 - Self::timekeeper_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; + } + } + } + + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + critical_section::with(|cs| { + let mut queue = self.queue.borrow(cs).borrow_mut(); + + if queue.schedule_wake(at, waker) { + let mut next = queue.next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = queue.next_expiration(self.now()); + } + } + }) + } +} diff --git a/va416xx-hal/src/pwm.rs b/va416xx-hal/src/pwm.rs index 3716dfa..1d472e4 100644 --- a/va416xx-hal/src/pwm.rs +++ b/va416xx-hal/src/pwm.rs @@ -43,7 +43,7 @@ macro_rules! pwm_common_func { #[inline] fn enable_pwm_a(&mut self) { self.reg - .reg() + .reg_block() .ctrl() .modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmA as u8) }); } @@ -51,7 +51,7 @@ macro_rules! pwm_common_func { #[inline] fn enable_pwm_b(&mut self) { self.reg - .reg() + .reg_block() .ctrl() .modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmB as u8) }); } @@ -71,19 +71,25 @@ macro_rules! pwm_common_func { self.pwm_base.current_rst_val = self.pwm_base.clock.raw() / self.pwm_base.current_period.raw(); self.reg - .reg() + .reg_block() .rst_value() .write(|w| unsafe { w.bits(self.pwm_base.current_rst_val) }); } #[inline] pub fn disable(&mut self) { - self.reg.reg().ctrl().modify(|_, w| w.enable().clear_bit()); + self.reg + .reg_block() + .ctrl() + .modify(|_, w| w.enable().clear_bit()); } #[inline] pub fn enable(&mut self) { - self.reg.reg().ctrl().modify(|_, w| w.enable().set_bit()); + self.reg + .reg_block() + .ctrl() + .modify(|_, w| w.enable().set_bit()); } #[inline] @@ -120,7 +126,7 @@ macro_rules! pwmb_func { * self.pwm_base.current_lower_limit as u64) / DUTY_MAX as u64; self.reg - .reg() + .reg_block() .pwmb_value() .write(|w| unsafe { w.bits(pwmb_val as u32) }); } @@ -137,7 +143,7 @@ macro_rules! pwmb_func { * self.pwm_base.current_duty as u64) / DUTY_MAX as u64; self.reg - .reg() + .reg_block() .pwma_value() .write(|w| unsafe { w.bits(pwma_val as u32) }); } @@ -348,7 +354,7 @@ impl embedded_hal::pwm::SetDutyCycle for ReducedPwmPin { * (DUTY_MAX as u64 - self.pwm_base.current_duty as u64)) / DUTY_MAX as u64; self.reg - .reg() + .reg_block() .pwma_value() .write(|w| unsafe { w.bits(pwma_val as u32) }); Ok(()) @@ -368,7 +374,7 @@ impl embedded_hal::pwm::SetDutyCycle for PwmPin> = Mutex::new(Cell::new(0)); +pub const TIM_IRQ_OFFSET: usize = 48; + +/// Get the peripheral block of a TIM peripheral given the index. +/// +/// This function panics if the given index is greater than 23. +/// +/// # Safety +/// +/// This returns a direct handle to the peripheral block, which allows to circumvent ownership +/// rules for the peripheral block. You have to ensure that the retrieved peripheral block is not +/// used by any other software component. +#[inline(always)] +pub const unsafe fn get_tim_raw(tim_idx: usize) -> &'static pac::tim0::RegisterBlock { + match tim_idx { + 0 => unsafe { &*pac::Tim0::ptr() }, + 1 => unsafe { &*pac::Tim1::ptr() }, + 2 => unsafe { &*pac::Tim2::ptr() }, + 3 => unsafe { &*pac::Tim3::ptr() }, + 4 => unsafe { &*pac::Tim4::ptr() }, + 5 => unsafe { &*pac::Tim5::ptr() }, + 6 => unsafe { &*pac::Tim6::ptr() }, + 7 => unsafe { &*pac::Tim7::ptr() }, + 8 => unsafe { &*pac::Tim8::ptr() }, + 9 => unsafe { &*pac::Tim9::ptr() }, + 10 => unsafe { &*pac::Tim10::ptr() }, + 11 => unsafe { &*pac::Tim11::ptr() }, + 12 => unsafe { &*pac::Tim12::ptr() }, + 13 => unsafe { &*pac::Tim13::ptr() }, + 14 => unsafe { &*pac::Tim14::ptr() }, + 15 => unsafe { &*pac::Tim15::ptr() }, + 16 => unsafe { &*pac::Tim16::ptr() }, + 17 => unsafe { &*pac::Tim17::ptr() }, + 18 => unsafe { &*pac::Tim18::ptr() }, + 19 => unsafe { &*pac::Tim19::ptr() }, + 20 => unsafe { &*pac::Tim20::ptr() }, + 21 => unsafe { &*pac::Tim21::ptr() }, + 22 => unsafe { &*pac::Tim22::ptr() }, + 23 => unsafe { &*pac::Tim23::ptr() }, + _ => { + panic!("invalid alarm timer index") + } + } +} + //================================================================================================== // Defintions //================================================================================================== -/// Interrupt events -//pub enum Event { -/// Timer timed out / count down ended -//TimeOut, -//} - #[derive(Default, Debug, PartialEq, Eq, Copy, Clone)] pub struct CascadeCtrl { /// Enable Cascade 0 signal active as a requirement for counting @@ -143,11 +181,11 @@ pub trait TimPin { pub trait ValidTim { // TIM ID ranging from 0 to 23 for 24 TIM peripherals - const TIM_ID: u8; + const ID: u8; const IRQ: pac::Interrupt; fn clock(clocks: &Clocks) -> Hertz { - if Self::TIM_ID <= 15 { + if Self::ID <= 15 { clocks.apb1() } else { clocks.apb2() @@ -163,7 +201,7 @@ macro_rules! tim_markers { ) => { $( impl ValidTim for $TimX { - const TIM_ID: u8 = $id; + const ID: u8 = $id; const IRQ: pac::Interrupt = $Irq; } )+ @@ -171,7 +209,7 @@ macro_rules! tim_markers { } pub const fn const_clock(_: &Tim, clocks: &Clocks) -> Hertz { - if Tim::TIM_ID <= 15 { + if Tim::ID <= 15 { clocks.apb1() } else { clocks.apb2() @@ -371,7 +409,7 @@ pub type TimRegBlock = pac::tim0::RegisterBlock; /// implementations should be overridden. The implementing type must also have /// "control" over the corresponding pin ID, i.e. it must guarantee that a each /// pin ID is a singleton. -pub(super) unsafe trait TimRegInterface { +pub unsafe trait TimRegInterface { fn tim_id(&self) -> u8; const PORT_BASE: *const pac::tim0::RegisterBlock = pac::Tim0::ptr() as *const _; @@ -379,7 +417,7 @@ pub(super) unsafe trait TimRegInterface { /// All 24 TIM blocks are identical. This helper functions returns the correct /// memory mapped peripheral depending on the TIM ID. #[inline(always)] - fn reg(&self) -> &TimRegBlock { + fn reg_block(&self) -> &TimRegBlock { unsafe { &*Self::PORT_BASE.offset(self.tim_id() as isize) } } @@ -406,6 +444,12 @@ pub(super) unsafe trait TimRegInterface { } } +unsafe impl TimRegInterface for Tim { + fn tim_id(&self) -> u8 { + Tim::ID + } +} + /// Provide a safe register interface for [`ValidTimAndPin`]s /// /// This `struct` takes ownership of a [`ValidTimAndPin`] and provides an API to @@ -433,7 +477,7 @@ impl TimRegister { unsafe impl TimRegInterface for TimRegister { #[inline(always)] fn tim_id(&self) -> u8 { - Tim::TIM_ID + Tim::ID } } @@ -454,7 +498,7 @@ where unsafe impl TimRegInterface for TimAndPinRegister { #[inline(always)] fn tim_id(&self) -> u8 { - Tim::TIM_ID + Tim::ID } } @@ -467,7 +511,7 @@ pub(super) struct TimDynRegister { impl From> for TimDynRegister { fn from(_reg: TimAndPinRegister) -> Self { Self { - tim_id: Tim::TIM_ID, + tim_id: Tim::ID, pin_id: Pin::DYN, } } @@ -504,10 +548,10 @@ pub fn enable_tim_clk(syscfg: &mut pac::Sysconfig, idx: u8) { .modify(|r, w| unsafe { w.bits(r.bits() | (1 << idx)) }); } -unsafe impl TimRegInterface for CountdownTimer { +unsafe impl TimRegInterface for CountdownTimer { #[inline] fn tim_id(&self) -> u8 { - TIM::TIM_ID + Tim::ID } } @@ -517,11 +561,11 @@ impl CountdownTimer { /// You can use [Self::start] to start the countdown timer, and you may optionally call /// [Self::listen] to enable interrupts for the TIM peripheral as well. pub fn new(syscfg: &mut pac::Sysconfig, tim: Tim, clocks: &Clocks) -> Self { - enable_tim_clk(syscfg, Tim::TIM_ID); - assert_tim_reset(syscfg, Tim::TIM_ID); + enable_tim_clk(syscfg, Tim::ID); + assert_tim_reset(syscfg, Tim::ID); cortex_m::asm::nop(); cortex_m::asm::nop(); - deassert_tim_reset(syscfg, Tim::TIM_ID); + deassert_tim_reset(syscfg, Tim::ID); CountdownTimer { tim: unsafe { TimRegister::new(tim) }, @@ -551,7 +595,7 @@ impl CountdownTimer { /// Return `Ok` if the timer has wrapped. Peripheral will automatically clear the /// flag and restart the time if configured correctly pub fn wait(&mut self) -> nb::Result<(), void::Void> { - let cnt = self.tim.reg().cnt_value().read().bits(); + let cnt = self.tim.reg_block().cnt_value().read().bits(); if (cnt > self.last_cnt) || cnt == 0 { self.last_cnt = self.rst_val; Ok(()) @@ -563,7 +607,10 @@ impl CountdownTimer { #[inline] pub fn stop(&mut self) { - self.tim.reg().ctrl().write(|w| w.enable().clear_bit()); + self.tim + .reg_block() + .ctrl() + .write(|w| w.enable().clear_bit()); } #[inline] @@ -575,26 +622,38 @@ impl CountdownTimer { #[inline(always)] pub fn enable_interrupt(&mut self) { - self.tim.reg().ctrl().modify(|_, w| w.irq_enb().set_bit()); + self.tim + .reg_block() + .ctrl() + .modify(|_, w| w.irq_enb().set_bit()); } #[inline(always)] pub fn disable_interrupt(&mut self) { - self.tim.reg().ctrl().modify(|_, w| w.irq_enb().clear_bit()); + self.tim + .reg_block() + .ctrl() + .modify(|_, w| w.irq_enb().clear_bit()); } #[inline] pub fn release(self, syscfg: &mut pac::Sysconfig) -> Tim { - self.tim.reg().ctrl().write(|w| w.enable().clear_bit()); + self.tim + .reg_block() + .ctrl() + .write(|w| w.enable().clear_bit()); syscfg .tim_clk_enable() - .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << Tim::TIM_ID)) }); + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << Tim::ID)) }); self.tim.release() } /// Load the count down timer with a timeout but do not start it. pub fn load(&mut self, timeout: impl Into) { - self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit()); + self.tim + .reg_block() + .ctrl() + .modify(|_, w| w.enable().clear_bit()); self.curr_freq = timeout.into(); self.rst_val = (self.clock.raw() / self.curr_freq.raw()) - 1; self.set_reload(self.rst_val); @@ -604,27 +663,39 @@ impl CountdownTimer { #[inline(always)] pub fn set_reload(&mut self, val: u32) { - self.tim.reg().rst_value().write(|w| unsafe { w.bits(val) }); + self.tim + .reg_block() + .rst_value() + .write(|w| unsafe { w.bits(val) }); } #[inline(always)] pub fn set_count(&mut self, val: u32) { - self.tim.reg().cnt_value().write(|w| unsafe { w.bits(val) }); + self.tim + .reg_block() + .cnt_value() + .write(|w| unsafe { w.bits(val) }); } #[inline(always)] pub fn count(&self) -> u32 { - self.tim.reg().cnt_value().read().bits() + self.tim.reg_block().cnt_value().read().bits() } #[inline(always)] pub fn enable(&mut self) { - self.tim.reg().enable().write(|w| unsafe { w.bits(1) }); + self.tim + .reg_block() + .enable() + .write(|w| unsafe { w.bits(1) }); } #[inline(always)] pub fn disable(&mut self) { - self.tim.reg().ctrl().modify(|_, w| w.enable().clear_bit()); + self.tim + .reg_block() + .ctrl() + .modify(|_, w| w.enable().clear_bit()); } /// Disable the counter, setting both enable and active bit to 0 @@ -632,12 +703,12 @@ impl CountdownTimer { pub fn auto_disable(self, enable: bool) -> Self { if enable { self.tim - .reg() + .reg_block() .ctrl() .modify(|_, w| w.auto_disable().set_bit()); } else { self.tim - .reg() + .reg_block() .ctrl() .modify(|_, w| w.auto_disable().clear_bit()); } @@ -652,12 +723,12 @@ impl CountdownTimer { pub fn auto_deactivate(self, enable: bool) -> Self { if enable { self.tim - .reg() + .reg_block() .ctrl() .modify(|_, w| w.auto_deactivate().set_bit()); } else { self.tim - .reg() + .reg_block() .ctrl() .modify(|_, w| w.auto_deactivate().clear_bit()); } @@ -667,7 +738,7 @@ impl CountdownTimer { /// Configure the cascade parameters #[inline] pub fn cascade_control(&mut self, ctrl: CascadeCtrl) { - self.tim.reg().csd_ctrl().write(|w| { + self.tim.reg_block().csd_ctrl().write(|w| { w.csden0().bit(ctrl.enb_start_src_csd0); w.csdinv0().bit(ctrl.inv_csd0); w.csden1().bit(ctrl.enb_start_src_csd1); @@ -685,7 +756,7 @@ impl CountdownTimer { pub fn cascade_0_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> { let id = src.id()?; self.tim - .reg() + .reg_block() .cascade0() .write(|w| unsafe { w.cassel().bits(id) }); Ok(()) @@ -695,7 +766,7 @@ impl CountdownTimer { pub fn cascade_1_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> { let id = src.id()?; self.tim - .reg() + .reg_block() .cascade1() .write(|w| unsafe { w.cassel().bits(id) }); Ok(()) @@ -705,7 +776,7 @@ impl CountdownTimer { pub fn cascade_2_source(&mut self, src: CascadeSource) -> Result<(), InvalidCascadeSourceId> { let id = src.id()?; self.tim - .reg() + .reg_block() .cascade2() .write(|w| unsafe { w.cassel().bits(id) }); Ok(())