diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7511f86..a3d02ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,8 +10,8 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: targets: "thumbv6m-none-eabi" - - run: cargo check --target thumbv6m-none-eabi --release - - run: cargo check --target thumbv6m-none-eabi --examples --release + - run: cargo check --target thumbv6m-none-eabi + - run: cargo check --target thumbv6m-none-eabi --examples test: name: Run Tests diff --git a/Cargo.toml b/Cargo.toml index 15a0ab4..618e8a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ [workspace] resolver = "2" members = [ - "vorago-reb1", - "va108xx", - "va108xx-hal", + "vorago-reb1", + "va108xx", + "va108xx-hal", + "va108xx-embassy", "examples/simple", "examples/rtic", "examples/embassy", @@ -22,7 +23,7 @@ codegen-units = 1 debug = 2 debug-assertions = true # <- incremental = false -# 1 instead of 0, the flashloader is too larger otherwise.. +# 1 instead of 0, the flashloader is too larger otherwise.. # opt-level = 1 # <- overflow-checks = true # <- diff --git a/board-tests/Cargo.toml b/board-tests/Cargo.toml index 3e5946b..6413c13 100644 --- a/board-tests/Cargo.toml +++ b/board-tests/Cargo.toml @@ -6,9 +6,9 @@ edition = "2021" [dependencies] cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" -panic-halt = "0.2" -rtt-target = "0.5" -panic-rtt-target = "0.1.3" +panic-halt = "1" +rtt-target = "0.6" +panic-rtt-target = "0.2" embedded-hal = "1" embedded-hal-nb = "1" embedded-io = "0.6" diff --git a/bootloader/Cargo.toml b/bootloader/Cargo.toml index 37a4e84..b03716f 100644 --- a/bootloader/Cargo.toml +++ b/bootloader/Cargo.toml @@ -7,9 +7,9 @@ edition = "2021" cortex-m = "0.7" cortex-m-rt = "0.7" embedded-hal = "1" -panic-rtt-target = { version = "0.1.3" } -panic-halt = { version = "0.2" } -rtt-target = { version = "0.5" } +panic-rtt-target = "0.2" +panic-halt = "1" +rtt-target = "0.6" crc = "3" num_enum = { version = "0.7", default-features = false } static_assertions = "1" diff --git a/examples/README.md b/examples/README.md index 93d1893..9df12f4 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 632fd6b..fdde545 100644 --- a/examples/embassy/Cargo.toml +++ b/examples/embassy/Cargo.toml @@ -4,37 +4,29 @@ 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" -rtt-target = { version = "0.5" } -panic-rtt-target = { version = "0.1" } +rtt-target = "0.6" +panic-rtt-target = "0.2" critical-section = "1" portable-atomic = { version = "1", features = ["unsafe-assume-single-core"]} -embassy-sync = { version = "0.6.0" } -embassy-time = { version = "0.3.2" } -embassy-time-driver = { version = "0.1" } +embassy-sync = "0.6" +embassy-time = "0.4" +embassy-executor = { version = "0.7", features = [ + "arch-cortex-m", + "executor-thread", + "executor-interrupt" +]} -[dependencies.once_cell] -version = "1" -default-features = false -features = ["critical-section"] - -[dependencies.embassy-executor] -version = "0.6.0" -features = [ - "arch-cortex-m", - "executor-thread", - "executor-interrupt", - "integrated-timers", -] - -[dependencies.va108xx-hal] -path = "../../va108xx-hal" +va108xx-hal = { path = "../../va108xx-hal" } +va108xx-embassy = { path = "../../va108xx-embassy", default-features = false } [features] -default = ["ticks-hz-1_000"] +default = ["ticks-hz-1_000", "va108xx-embassy/irq-oc30-oc31"] +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/lib.rs b/examples/embassy/src/lib.rs deleted file mode 100644 index b934a40..0000000 --- a/examples/embassy/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -#![no_std] -pub mod time_driver; - -pub use time_driver::init; diff --git a/examples/embassy/src/main.rs b/examples/embassy/src/main.rs index 4dae3fa..d8c9354 100644 --- a/examples/embassy/src/main.rs +++ b/examples/embassy/src/main.rs @@ -5,6 +5,16 @@ use embassy_time::{Duration, Instant, Ticker}; use embedded_hal::digital::StatefulOutputPin; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; +use va108xx_embassy::embassy; + +cfg_if::cfg_if! { + if #[cfg(feature = "custom-irqs")] { + use va108xx_embassy::embassy_time_driver_irqs; + use va108xx_hal::pac::interrupt; + embassy_time_driver_irqs!(timekeeper_irq = OC23, alarm_irq = OC24); + } +} + use va108xx_hal::{gpio::PinsA, pac, prelude::*}; const SYSCLK_FREQ: Hertz = Hertz::from_raw(50_000_000); @@ -19,14 +29,28 @@ async fn main(_spawner: Spawner) { // Safety: Only called once here. unsafe { - embassy_example::init( - &mut dp.sysconfig, - &dp.irqsel, - SYSCLK_FREQ, - dp.tim23, - dp.tim22, - ) - }; + cfg_if::cfg_if! { + if #[cfg(not(feature = "custom-irqs"))] { + embassy::init( + &mut dp.sysconfig, + &dp.irqsel, + SYSCLK_FREQ, + dp.tim23, + dp.tim22, + ); + } else { + embassy::init_with_custom_irqs( + &mut dp.sysconfig, + &dp.irqsel, + SYSCLK_FREQ, + dp.tim23, + dp.tim22, + pac::Interrupt::OC23, + pac::Interrupt::OC24, + ); + } + } + } let porta = PinsA::new(&mut dp.sysconfig, Some(dp.ioconfig), dp.porta); let mut led0 = porta.pa10.into_readable_push_pull_output(); diff --git a/examples/embassy/src/time_driver.rs b/examples/embassy/src/time_driver.rs deleted file mode 100644 index 45a4e5b..0000000 --- a/examples/embassy/src/time_driver.rs +++ /dev/null @@ -1,333 +0,0 @@ -//! This is a sample time driver implementation for the VA108xx family of devices, supporting -//! one alarm and requiring/reserving 2 TIM peripherals. You could adapt this implementation to -//! support more alarms. -//! -//! This driver implementation reserves interrupts OC31 and OC30 for the timekeeping. -use core::{cell::Cell, mem, ptr}; -use critical_section::CriticalSection; -use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_sync::blocking_mutex::Mutex; -use portable_atomic::{AtomicU32, AtomicU8, Ordering}; - -use embassy_time_driver::{time_driver_impl, AlarmHandle, Driver, TICK_HZ}; -use once_cell::sync::OnceCell; -use va108xx_hal::{ - clock::enable_peripheral_clock, - enable_interrupt, - pac::{self, interrupt}, - prelude::*, - timer::{enable_tim_clk, ValidTim}, - PeripheralSelect, -}; - -pub type TimekeeperClk = pac::Tim23; -pub type AlarmClk0 = pac::Tim22; -pub type AlarmClk1 = pac::Tim21; -pub type AlarmClk2 = pac::Tim20; - -const TIMEKEEPER_IRQ: pac::Interrupt = pac::Interrupt::OC31; -const ALARM_IRQ: pac::Interrupt = pac::Interrupt::OC30; - -/// 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, - irqsel: &pac::Irqsel, - sysclk: impl Into, - timekeeper: TimekeeperClk, - alarm_tim: AlarmClk0, -) { - DRIVER.init(syscfg, irqsel, sysclk, timekeeper, alarm_tim) -} - -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]) -}); - -/// Timekeeper interrupt. -#[interrupt] -#[allow(non_snake_case)] -fn OC31() { - DRIVER.on_interrupt_timekeeping() -} - -/// Alarm timer interrupt. -#[interrupt] -#[allow(non_snake_case)] -fn OC30() { - DRIVER.on_interrupt_alarm(0) -} - -#[inline(always)] -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") - } - } -} - -#[inline(always)] -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, - irqsel: &pac::Irqsel, - sysclk: impl Into, - timekeeper: TimekeeperClk, - alarm_tim: AlarmClk0, - ) { - enable_peripheral_clock(syscfg, PeripheralSelect::Irqsel); - enable_tim_clk(syscfg, TimekeeperClk::TIM_ID); - let sysclk = sysclk.into(); - // Initiate scale value here. This is required to convert timer ticks back to a timestamp. - SCALE.set((sysclk.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. - irqsel - .tim0(TimekeeperClk::TIM_ID as usize) - .write(|w| unsafe { w.bits(TIMEKEEPER_IRQ as u32) }); - unsafe { - enable_interrupt(TIMEKEEPER_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); - - // 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(ALARM_IRQ); - } - irqsel - .tim0(AlarmClk0::TIM_ID as usize) - .write(|w| unsafe { w.bits(ALARM_IRQ as u32) }); - } - - // 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 - }) - } -} diff --git a/examples/rtic/Cargo.toml b/examples/rtic/Cargo.toml index 31425db..8df5541 100644 --- a/examples/rtic/Cargo.toml +++ b/examples/rtic/Cargo.toml @@ -8,37 +8,19 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" embedded-hal = "1" embedded-io = "0.6" -rtt-target = { version = "0.5" } -panic-rtt-target = { version = "0.1" } +rtt-target = "0.6" +panic-rtt-target = "0.2" # Even though we do not use this directly, we need to activate this feature explicitely # so that RTIC compiles because thumv6 does not have CAS operations natively. portable-atomic = { version = "1", features = ["unsafe-assume-single-core"]} -[dependencies.rtic] -version = "2" -features = ["thumbv6-backend"] +rtic = { version = "2", features = ["thumbv6-backend"] } +rtic-monotonics = { version = "2", features = ["cortex-m-systick"] } +rtic-sync = { version = "1.3", features = ["defmt-03"] } -[dependencies.rtic-monotonics] -version = "2" -features = ["cortex-m-systick"] +once_cell = {version = "1", default-features = false, features = ["critical-section"]} +ringbuf = { version = "0.4.7", default-features = false, features = ["portable-atomic"] } -[dependencies.rtic-sync] -version = "1.3" -features = ["defmt-03"] - -[dependencies.once_cell] -version = "1" -default-features = false -features = ["critical-section"] - -[dependencies.ringbuf] -version = "0.4.7" -default-features = false -features = ["portable-atomic"] - -[dependencies.va108xx-hal] -version = "0.8" - -[dependencies.vorago-reb1] -path = "../../vorago-reb1" +va108xx-hal = "0.8" +vorago-reb1 = { path = "../../vorago-reb1" } diff --git a/examples/simple/Cargo.toml b/examples/simple/Cargo.toml index a9ab5c5..7f6969b 100644 --- a/examples/simple/Cargo.toml +++ b/examples/simple/Cargo.toml @@ -6,10 +6,10 @@ edition = "2021" [dependencies] cortex-m = {version = "0.7", features = ["critical-section-single-core"]} cortex-m-rt = "0.7" -panic-halt = "0.2" -panic-rtt-target = "0.1" +panic-halt = "1" +panic-rtt-target = "0.2" critical-section = "1" -rtt-target = "0.5" +rtt-target = "0.6" embedded-hal = "1" embedded-hal-nb = "1" embedded-io = "0.6" diff --git a/flashloader/Cargo.toml b/flashloader/Cargo.toml index 65e701a..439fe5c 100644 --- a/flashloader/Cargo.toml +++ b/flashloader/Cargo.toml @@ -9,55 +9,24 @@ cortex-m-rt = "0.7" embedded-hal = "1" embedded-hal-nb = "1" embedded-io = "0.6" -panic-rtt-target = { version = "0.1.3" } -rtt-target = { version = "0.5" } +panic-rtt-target = "0.2" +rtt-target = "0.6" num_enum = { version = "0.7", default-features = false } log = "0.4" crc = "3" - -[dependencies.satrs] -version = "0.2" -default-features = false - -[dependencies.rtt-log] -version = "0.4" - -[dependencies.ringbuf] -version = "0.4.7" -default-features = false -features = ["portable-atomic"] - -[dependencies.once_cell] -version = "1" -default-features = false -features = ["critical-section"] - -[dependencies.spacepackets] -version = "0.11" -default-features = false - -[dependencies.cobs] -git = "https://github.com/robamu/cobs.rs.git" -branch = "all_features" -default-features = false - +cobs = { version = "0.3", default-features = false } +satrs = { version = "0.2", default-features = false } +rtt-log = "0.5" +ringbuf = { version = "0.4.7", default-features = false, features = ["portable-atomic"] } +once_cell = { version = "1", default-features = false, features = ["critical-section"] } +spacepackets = { version = "0.11", default-features = false } # Even though we do not use this directly, we need to activate this feature explicitely # so that RTIC compiles because thumv6 does not have CAS operations natively. -[dependencies.portable-atomic] -version = "1" -features = ["unsafe-assume-single-core"] +portable-atomic = {version = "1", features = ["unsafe-assume-single-core"]} -[dependencies.rtic] -version = "2" -features = ["thumbv6-backend"] - -[dependencies.rtic-monotonics] -version = "2" -features = ["cortex-m-systick"] - -[dependencies.rtic-sync] -version = "1" -features = ["defmt-03"] +rtic = { version = "2", features = ["thumbv6-backend"] } +rtic-monotonics = { version = "2", features = ["cortex-m-systick"] } +rtic-sync = {version = "1", features = ["defmt-03"]} [dependencies.va108xx-hal] path = "../va108xx-hal" diff --git a/va108xx-embassy/Cargo.toml b/va108xx-embassy/Cargo.toml new file mode 100644 index 0000000..68a7dcb --- /dev/null +++ b/va108xx-embassy/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "va108xx-embassy" +version = "0.1.0" +edition = "2021" + +[dependencies] +critical-section = "1" +portable-atomic = { version = "1", features = ["unsafe-assume-single-core"]} + +embassy-sync = "0.6" +embassy-executor = "0.7" +embassy-time-driver = "0.2" +embassy-time-queue-utils = "0.1" + +once_cell = { version = "1", default-features = false, features = ["critical-section"] } + +[dependencies.va108xx-hal] +path = "../va108xx-hal" + +[features] +default = ["irq-oc30-oc31"] +irqs-in-lib = [] +# This determines the reserved interrupt functions for the embassy time drivers. Only one +# is allowed to be selected! +irq-oc28-oc29 = ["irqs-in-lib"] +irq-oc29-oc30 = ["irqs-in-lib"] +irq-oc30-oc31 = ["irqs-in-lib"] diff --git a/va108xx-embassy/README.md b/va108xx-embassy/README.md new file mode 100644 index 0000000..62a00e8 --- /dev/null +++ b/va108xx-embassy/README.md @@ -0,0 +1,10 @@ +[![Crates.io](https://img.shields.io/crates/v/va108xx-embassy)](https://crates.io/crates/va108xx-embassy) +[![docs.rs](https://img.shields.io/docsrs/va108xx-embassy)](https://docs.rs/va108xx-embassy) + +# Embassy-rs support for the Vorago VA108xx MCU family + +This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for the +VA108xx family. Currently, it contains the time driver to allow using embassy-rs. It uses the TIM +peripherals provided by the VA108xx family for this purpose. + +The documentation contains more information on how to use this crate. diff --git a/va108xx-embassy/src/lib.rs b/va108xx-embassy/src/lib.rs new file mode 100644 index 0000000..171fc49 --- /dev/null +++ b/va108xx-embassy/src/lib.rs @@ -0,0 +1,416 @@ +//! # Embassy-rs support for the Vorago VA108xx MCU family +//! +//! This repository contains the [embassy-rs](https://github.com/embassy-rs/embassy) support for the +//! VA108xx family. Currently, it contains the time driver to allow using embassy-rs. It uses the TIM +//! peripherals provided by the VA108xx 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 [embassy::init_with_custom_irqs] and [embassy::init] method. +//! +//! 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-oc30-oc31` feature flag. This library exposes three combinations: +//! +//! - `irq-oc30-oc31`: Uses [pac::Interrupt::OC30] and [pac::Interrupt::OC31] +//! - `irq-oc29-oc30`: Uses [pac::Interrupt::OC29] and [pac::Interrupt::OC30] +//! - `irq-oc28-oc29`: Uses [pac::Interrupt::OC28] and [pac::Interrupt::OC20] +//! +//! 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::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 project](https://egit.irs.uni-stuttgart.de/rust/va108xx-rs/src/branch/main/examples/embassy) +#![no_std] +use core::cell::{Cell, RefCell}; +use critical_section::CriticalSection; +use embassy_sync::blocking_mutex::CriticalSectionMutex as Mutex; +use portable_atomic::{AtomicU32, Ordering}; + +use embassy_time_driver::{time_driver_impl, Driver, TICK_HZ}; +use embassy_time_queue_utils::Queue; +use once_cell::sync::OnceCell; +#[cfg(feature = "irqs-in-lib")] +use va108xx_hal::pac::interrupt; +use va108xx_hal::{ + clock::enable_peripheral_clock, + enable_interrupt, pac, + prelude::*, + timer::{enable_tim_clk, get_tim_raw, TimRegInterface}, + PeripheralSelect, +}; + +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 [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::embassy::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::embassy::time_driver().on_interrupt_alarm() } + } + }; +} + +// Provide three combinations of IRQs for the time driver by default. + +#[cfg(feature = "irq-oc30-oc31")] +embassy_time_driver_irqs!(timekeeper_irq = OC31, alarm_irq = OC30); +#[cfg(feature = "irq-oc29-oc30")] +embassy_time_driver_irqs!(timekeeper_irq = OC30, alarm_irq = OC29); +#[cfg(feature = "irq-oc28-oc29")] +embassy_time_driver_irqs!(timekeeper_irq = OC29, alarm_irq = OC28); + +pub mod embassy { + use super::*; + use va108xx_hal::{pac, timer::TimRegInterface}; + + /// 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 + /// + /// # Safety + /// + /// This has to be called once at initialization time to initiate the time driver for + /// embassy. + #[cfg(feature = "irqs-in-lib")] + pub unsafe fn init( + syscfg: &mut pac::Sysconfig, + irqsel: &pac::Irqsel, + sysclk: impl Into, + timekeeper_tim: impl TimRegInterface, + alarm_tim: impl TimRegInterface, + ) { + TIME_DRIVER.init( + syscfg, + irqsel, + sysclk, + timekeeper_tim, + alarm_tim, + TIMEKEEPER_IRQ, + ALARM_IRQ, + ) + } + + /// 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_with_custom_irqs( + syscfg: &mut pac::Sysconfig, + irqsel: &pac::Irqsel, + sysclk: impl Into, + timekeeper_tim: impl TimRegInterface, + alarm_tim: impl TimRegInterface, + timekeeper_irq: pac::Interrupt, + alarm_irq: pac::Interrupt, + ) { + TIME_DRIVER.init( + syscfg, + irqsel, + sysclk, + timekeeper_tim, + alarm_tim, + timekeeper_irq, + alarm_irq, + ) + } +} + +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 { + #[allow(clippy::too_many_arguments)] + fn init( + &self, + syscfg: &mut pac::Sysconfig, + irqsel: &pac::Irqsel, + sysclk: impl Into, + timekeeper_tim: impl TimRegInterface, + alarm_tim: impl TimRegInterface, + timekeeper_irq: pac::Interrupt, + alarm_irq: pac::Interrupt, + ) { + if ALARM_TIM.get().is_some() { + return; + } + ALARM_TIM.set(alarm_tim.tim_id()).ok(); + TIMEKEEPER_TIM.set(timekeeper_tim.tim_id()).ok(); + enable_peripheral_clock(syscfg, PeripheralSelect::Irqsel); + enable_tim_clk(syscfg, timekeeper_tim.tim_id()); + let timekeeper_reg_block = timekeeper_tim.reg_block(); + let alarm_tim_reg_block = alarm_tim.reg_block(); + let sysclk = sysclk.into(); + // Initiate scale value here. This is required to convert timer ticks back to a timestamp. + SCALE.set((sysclk.raw() / TICK_HZ as u32) as u64).unwrap(); + timekeeper_reg_block + .rst_value() + .write(|w| unsafe { w.bits(u32::MAX) }); + // Decrementing counter. + timekeeper_reg_block + .cnt_value() + .write(|w| unsafe { w.bits(u32::MAX) }); + // Switch on. Timekeeping should always be done. + irqsel + .tim0(timekeeper_tim.tim_id() as usize) + .write(|w| unsafe { w.bits(timekeeper_irq as u32) }); + unsafe { + enable_interrupt(timekeeper_irq); + } + timekeeper_reg_block + .ctrl() + .modify(|_, w| w.irq_enb().set_bit()); + timekeeper_reg_block + .enable() + .write(|w| unsafe { w.bits(1) }); + + enable_tim_clk(syscfg, alarm_tim.tim_id()); + + // Explicitely disable alarm timer until needed. + alarm_tim_reg_block.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(alarm_irq); + } + irqsel + .tim0(alarm_tim.tim_id() as usize) + .write(|w| unsafe { w.bits(alarm_irq as u32) }); + } + + /// 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 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() + } + + 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).checked_mul(*SCALE.get().unwrap()); + if remaining_ticks.is_some_and(|v| v <= u32::MAX as u64) { + alarm_tim.enable().write(|w| unsafe { w.bits(0) }); + alarm_tim + .cnt_value() + .write(|w| unsafe { w.bits(remaining_ticks.unwrap() 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/va108xx-hal/CHANGELOG.md b/va108xx-hal/CHANGELOG.md index dd25ba2..92038de 100644 --- a/va108xx-hal/CHANGELOG.md +++ b/va108xx-hal/CHANGELOG.md @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). methods which mutable modify the pin instead of consuming and returning it. - Add `downgrade` method for `Pin` and `upgrade` method for `DynPin` as explicit conversion methods. +- Add new `get_tim_raw` unsafe method to retrieve TIM peripheral blocks. +- Simplified PWM module implementation. ## [v0.8.0] 2024-09-30 diff --git a/va108xx-hal/Cargo.toml b/va108xx-hal/Cargo.toml index 71f7af0..3ad52c9 100644 --- a/va108xx-hal/Cargo.toml +++ b/va108xx-hal/Cargo.toml @@ -15,32 +15,18 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"]} cortex-m-rt = "0.7" nb = "1" paste = "1" +embedded-hal = "1" embedded-hal-nb = "1" embedded-io = "0.6" fugit = "0.3" typenum = "1" critical-section = "1" -delegate = "0.12" +delegate = ">=0.12, <=0.13" +void = { version = "1", default-features = false } +once_cell = {version = "1", default-features = false } +va108xx = { version = "0.3", default-features = false, features = ["critical-section"]} -[dependencies.va108xx] -version = "0.3" -default-features = false -features = ["critical-section"] - -[dependencies.embedded-hal] -version = "1" - -[dependencies.void] -version = "1" -default-features = false - -[dependencies.once_cell] -version = "1.14" -default-features = false - -[dependencies.defmt] -version = "0.3" -optional = true +defmt = { version = "0.3", optional = true } [features] default = ["rt"] diff --git a/va108xx-hal/src/pwm.rs b/va108xx-hal/src/pwm.rs index ec1ce1b..2c69165 100644 --- a/va108xx-hal/src/pwm.rs +++ b/va108xx-hal/src/pwm.rs @@ -10,14 +10,12 @@ use core::marker::PhantomData; use crate::pac; use crate::time::Hertz; -use crate::timer::{ - TimAndPinRegister, TimDynRegister, TimPin, TimRegInterface, ValidTim, ValidTimAndPin, -}; +use crate::timer::{TimDynRegister, TimPin, TimRegInterface, ValidTim, ValidTimAndPin}; use crate::{clock::enable_peripheral_clock, gpio::DynPinId}; const DUTY_MAX: u16 = u16::MAX; -pub struct PwmBase { +pub struct PwmCommon { sys_clk: Hertz, /// For PWMB, this is the upper limit current_duty: u16, @@ -35,123 +33,13 @@ enum StatusSelPwm { pub struct PwmA {} pub struct PwmB {} -//================================================================================================== -// Common -//================================================================================================== - -macro_rules! pwm_common_func { - () => { - #[inline] - fn enable_pwm_a(&mut self) { - self.reg - .reg() - .ctrl() - .modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmA as u8) }); - } - - #[inline] - fn enable_pwm_b(&mut self) { - self.reg - .reg() - .ctrl() - .modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmB as u8) }); - } - - #[inline] - pub fn get_period(&self) -> Hertz { - self.pwm_base.current_period - } - - #[inline] - pub fn set_period(&mut self, period: impl Into) { - self.pwm_base.current_period = period.into(); - // Avoid division by 0 - if self.pwm_base.current_period.raw() == 0 { - return; - } - self.pwm_base.current_rst_val = - self.pwm_base.sys_clk.raw() / self.pwm_base.current_period.raw(); - self.reg - .reg() - .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()); - } - - #[inline] - pub fn enable(&mut self) { - self.reg.reg().ctrl().modify(|_, w| w.enable().set_bit()); - } - - #[inline] - pub fn period(&self) -> Hertz { - self.pwm_base.current_period - } - - #[inline(always)] - pub fn duty(&self) -> u16 { - self.pwm_base.current_duty - } - }; -} - -macro_rules! pwmb_func { - () => { - pub fn pwmb_lower_limit(&self) -> u16 { - self.pwm_base.current_lower_limit - } - - pub fn pwmb_upper_limit(&self) -> u16 { - self.pwm_base.current_duty - } - - /// Set the lower limit for PWMB - /// - /// The PWM signal will be 1 as long as the current RST counter is larger than - /// the lower limit. For example, with a lower limit of 0.5 and and an upper limit - /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high - /// state - pub fn set_pwmb_lower_limit(&mut self, duty: u16) { - self.pwm_base.current_lower_limit = duty; - let pwmb_val: u64 = (self.pwm_base.current_rst_val as u64 - * self.pwm_base.current_lower_limit as u64) - / DUTY_MAX as u64; - self.reg - .reg() - .pwmb_value() - .write(|w| unsafe { w.bits(pwmb_val as u32) }); - } - - /// Set the higher limit for PWMB - /// - /// The PWM signal will be 1 as long as the current RST counter is smaller than - /// the higher limit. For example, with a lower limit of 0.5 and and an upper limit - /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high - /// state - pub fn set_pwmb_upper_limit(&mut self, duty: u16) { - self.pwm_base.current_duty = duty; - let pwma_val: u64 = (self.pwm_base.current_rst_val as u64 - * self.pwm_base.current_duty as u64) - / DUTY_MAX as u64; - self.reg - .reg() - .pwma_value() - .write(|w| unsafe { w.bits(pwma_val as u32) }); - } - }; -} - //================================================================================================== // Strongly typed PWM pin //================================================================================================== pub struct PwmPin { - reg: TimAndPinRegister, - pwm_base: PwmBase, + pin_and_tim: (Pin, Tim), + inner: ReducedPwmPin, mode: PhantomData, } @@ -163,34 +51,82 @@ where pub fn new( sys_cfg: &mut pac::Sysconfig, sys_clk: impl Into + Copy, - tim_and_pin: (Pin, Tim), + pin_and_tim: (Pin, Tim), initial_period: impl Into + Copy, ) -> Self { let mut pin = PwmPin { - pwm_base: PwmBase { - current_duty: 0, - current_lower_limit: 0, - current_period: initial_period.into(), - current_rst_val: 0, - sys_clk: sys_clk.into(), - }, - reg: unsafe { TimAndPinRegister::new(tim_and_pin.0, tim_and_pin.1) }, + pin_and_tim, + inner: ReducedPwmPin::::new( + Tim::TIM_ID, + Pin::DYN, + PwmCommon { + current_duty: 0, + current_lower_limit: 0, + current_period: initial_period.into(), + current_rst_val: 0, + sys_clk: sys_clk.into(), + }, + ), + //unsafe { TimAndPin::new(tim_and_pin.0, tim_and_pin.1) }, mode: PhantomData, }; enable_peripheral_clock(sys_cfg, crate::clock::PeripheralClocks::Gpio); enable_peripheral_clock(sys_cfg, crate::clock::PeripheralClocks::Ioconfig); sys_cfg .tim_clk_enable() - .modify(|r, w| unsafe { w.bits(r.bits() | pin.reg.mask_32()) }); + .modify(|r, w| unsafe { w.bits(r.bits() | pin.pin_and_tim.1.mask_32()) }); pin.enable_pwm_a(); pin.set_period(initial_period); pin } - pub fn release(self) -> (Pin, Tim) { - self.reg.release() + + pub fn reduce(self) -> ReducedPwmPin { + self.inner } - pwm_common_func!(); + pub fn release(self) -> (Pin, Tim) { + self.pin_and_tim + } + + #[inline] + fn enable_pwm_a(&mut self) { + self.inner.enable_pwm_a(); + } + + #[inline] + fn enable_pwm_b(&mut self) { + self.inner.enable_pwm_b(); + } + + #[inline] + pub fn get_period(&self) -> Hertz { + self.inner.get_period() + } + + #[inline] + pub fn set_period(&mut self, period: impl Into) { + self.inner.set_period(period); + } + + #[inline] + pub fn disable(&mut self) { + self.inner.disable(); + } + + #[inline] + pub fn enable(&mut self) { + self.inner.enable(); + } + + #[inline] + pub fn period(&self) -> Hertz { + self.inner.period() + } + + #[inline(always)] + pub fn duty(&self) -> u16 { + self.inner.duty() + } } impl From> for PwmPin @@ -199,9 +135,9 @@ where { fn from(other: PwmPin) -> Self { let mut pwmb = Self { - reg: other.reg, - pwm_base: other.pwm_base, mode: PhantomData, + pin_and_tim: other.pin_and_tim, + inner: other.inner.into(), }; pwmb.enable_pwm_b(); pwmb @@ -213,13 +149,13 @@ where (PIN, TIM): ValidTimAndPin, { fn from(other: PwmPin) -> Self { - let mut pwmb = Self { - reg: other.reg, - pwm_base: other.pwm_base, + let mut pwma = Self { mode: PhantomData, + pin_and_tim: other.pin_and_tim, + inner: other.inner.into(), }; - pwmb.enable_pwm_a(); - pwmb + pwma.enable_pwm_a(); + pwma } } @@ -263,33 +199,105 @@ where /// Reduced version where type information is deleted pub struct ReducedPwmPin { - reg: TimDynRegister, - pwm_base: PwmBase, - pin_id: DynPinId, + dyn_reg: TimDynRegister, + common: PwmCommon, mode: PhantomData, } -impl From> for ReducedPwmPin { - fn from(pwm_pin: PwmPin) -> Self { - ReducedPwmPin { - reg: TimDynRegister::from(pwm_pin.reg), - pwm_base: pwm_pin.pwm_base, - pin_id: PIN::DYN, +impl ReducedPwmPin { + pub(crate) fn new(tim_id: u8, pin_id: DynPinId, common: PwmCommon) -> Self { + Self { + dyn_reg: TimDynRegister { tim_id, pin_id }, + common, mode: PhantomData, } } } +/* +impl From> for ReducedPwmPin { + fn from(pwm_pin: PwmPin) -> Self { + ReducedPwmPin { + dyn_reg: TimDynRegister { -impl ReducedPwmPin { - pwm_common_func!(); + + } + // ::from(pwm_pin.reg), + common: pwm_pin.pwm_base, + pin_id: Pin::DYN, + mode: PhantomData, + } + } +} +*/ + +impl ReducedPwmPin { + #[inline] + fn enable_pwm_a(&mut self) { + self.dyn_reg + .reg_block() + .ctrl() + .modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmA as u8) }); + } + + #[inline] + fn enable_pwm_b(&mut self) { + self.dyn_reg + .reg_block() + .ctrl() + .modify(|_, w| unsafe { w.status_sel().bits(StatusSelPwm::PwmB as u8) }); + } + + #[inline] + pub fn get_period(&self) -> Hertz { + self.common.current_period + } + + #[inline] + pub fn set_period(&mut self, period: impl Into) { + self.common.current_period = period.into(); + // Avoid division by 0 + if self.common.current_period.raw() == 0 { + return; + } + self.common.current_rst_val = self.common.sys_clk.raw() / self.common.current_period.raw(); + self.dyn_reg + .reg_block() + .rst_value() + .write(|w| unsafe { w.bits(self.common.current_rst_val) }); + } + + #[inline] + pub fn disable(&mut self) { + self.dyn_reg + .reg_block() + .ctrl() + .modify(|_, w| w.enable().clear_bit()); + } + + #[inline] + pub fn enable(&mut self) { + self.dyn_reg + .reg_block() + .ctrl() + .modify(|_, w| w.enable().set_bit()); + } + + #[inline] + pub fn period(&self) -> Hertz { + self.common.current_period + } + + #[inline(always)] + pub fn duty(&self) -> u16 { + self.common.current_duty + } } impl From> for ReducedPwmPin { fn from(other: ReducedPwmPin) -> Self { let mut pwmb = Self { - reg: other.reg, - pwm_base: other.pwm_base, - pin_id: other.pin_id, + dyn_reg: other.dyn_reg, + common: other.common, mode: PhantomData, }; pwmb.enable_pwm_b(); @@ -300,9 +308,8 @@ impl From> for ReducedPwmPin { impl From> for ReducedPwmPin { fn from(other: ReducedPwmPin) -> Self { let mut pwmb = Self { - reg: other.reg, - pwm_base: other.pwm_base, - pin_id: other.pin_id, + dyn_reg: other.dyn_reg, + common: other.common, mode: PhantomData, }; pwmb.enable_pwm_a(); @@ -314,15 +321,83 @@ impl From> for ReducedPwmPin { // PWMB implementations //================================================================================================== -impl PwmPin +impl PwmPin where - (PIN, TIM): ValidTimAndPin, + (Pin, Tim): ValidTimAndPin, { - pwmb_func!(); + pub fn pwmb_lower_limit(&self) -> u16 { + self.inner.pwmb_lower_limit() + } + + pub fn pwmb_upper_limit(&self) -> u16 { + self.inner.pwmb_upper_limit() + } + + /// Set the lower limit for PWMB + /// + /// The PWM signal will be 1 as long as the current RST counter is larger than + /// the lower limit. For example, with a lower limit of 0.5 and and an upper limit + /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high + /// state + pub fn set_pwmb_lower_limit(&mut self, duty: u16) { + self.inner.set_pwmb_lower_limit(duty); + } + + /// Set the higher limit for PWMB + /// + /// The PWM signal will be 1 as long as the current RST counter is smaller than + /// the higher limit. For example, with a lower limit of 0.5 and and an upper limit + /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high + /// state + pub fn set_pwmb_upper_limit(&mut self, duty: u16) { + self.inner.set_pwmb_upper_limit(duty); + } } impl ReducedPwmPin { - pwmb_func!(); + #[inline(always)] + pub fn pwmb_lower_limit(&self) -> u16 { + self.common.current_lower_limit + } + + #[inline(always)] + pub fn pwmb_upper_limit(&self) -> u16 { + self.common.current_duty + } + + /// Set the lower limit for PWMB + /// + /// The PWM signal will be 1 as long as the current RST counter is larger than + /// the lower limit. For example, with a lower limit of 0.5 and and an upper limit + /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high + /// state + #[inline(always)] + pub fn set_pwmb_lower_limit(&mut self, duty: u16) { + self.common.current_lower_limit = duty; + let pwmb_val: u64 = (self.common.current_rst_val as u64 + * self.common.current_lower_limit as u64) + / DUTY_MAX as u64; + self.dyn_reg + .reg_block() + .pwmb_value() + .write(|w| unsafe { w.bits(pwmb_val as u32) }); + } + + /// Set the higher limit for PWMB + /// + /// The PWM signal will be 1 as long as the current RST counter is smaller than + /// the higher limit. For example, with a lower limit of 0.5 and and an upper limit + /// of 0.7, Only a fixed period between 0.5 * period and 0.7 * period will be in a high + /// state + pub fn set_pwmb_upper_limit(&mut self, duty: u16) { + self.common.current_duty = duty; + let pwma_val: u64 = (self.common.current_rst_val as u64 * self.common.current_duty as u64) + / DUTY_MAX as u64; + self.dyn_reg + .reg_block() + .pwma_value() + .write(|w| unsafe { w.bits(pwma_val as u32) }); + } } //================================================================================================== @@ -345,12 +420,12 @@ impl embedded_hal::pwm::SetDutyCycle for ReducedPwmPin { #[inline] fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { - self.pwm_base.current_duty = duty; - let pwma_val: u64 = (self.pwm_base.current_rst_val as u64 - * (DUTY_MAX as u64 - self.pwm_base.current_duty as u64)) + self.common.current_duty = duty; + let pwma_val: u64 = (self.common.current_rst_val as u64 + * (DUTY_MAX as u64 - self.common.current_duty as u64)) / DUTY_MAX as u64; - self.reg - .reg() + self.dyn_reg + .reg_block() .pwma_value() .write(|w| unsafe { w.bits(pwma_val as u32) }); Ok(()) @@ -365,15 +440,7 @@ impl embedded_hal::pwm::SetDutyCycle for PwmPin Result<(), Self::Error> { - self.pwm_base.current_duty = duty; - let pwma_val: u64 = (self.pwm_base.current_rst_val as u64 - * (DUTY_MAX as u64 - self.pwm_base.current_duty as u64)) - / DUTY_MAX as u64; - self.reg - .reg() - .pwma_value() - .write(|w| unsafe { w.bits(pwma_val as u32) }); - Ok(()) + self.inner.set_duty_cycle(duty) } } diff --git a/va108xx-hal/src/timer.rs b/va108xx-hal/src/timer.rs index 79d4cbe..58e3fcf 100644 --- a/va108xx-hal/src/timer.rs +++ b/va108xx-hal/src/timer.rs @@ -26,6 +26,48 @@ use fugit::RateExtU32; const IRQ_DST_NONE: u32 = 0xffffffff; pub static MS_COUNTER: Mutex> = Mutex::new(Cell::new(0)); +/// 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 //================================================================================================== @@ -248,7 +290,7 @@ pub type TimRegBlock = 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 tim0::RegisterBlock = pac::Tim0::ptr() as *const _; @@ -256,7 +298,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) } } @@ -293,70 +335,16 @@ pub(super) unsafe trait TimRegInterface { } } -/// Provide a safe register interface for [`ValidTimAndPin`]s -/// -/// This `struct` takes ownership of a [`ValidTimAndPin`] and provides an API to -/// access the corresponding registers. -pub(super) struct TimAndPinRegister { - pin: Pin, - tim: Tim, -} - -pub(super) struct TimRegister { - tim: TIM, -} - -impl TimRegister { - #[inline] - pub(super) unsafe fn new(tim: TIM) -> Self { - TimRegister { tim } - } - - pub(super) fn release(self) -> TIM { - self.tim - } -} - -unsafe impl TimRegInterface for TimRegister { +unsafe impl TimRegInterface for Tim { fn tim_id(&self) -> u8 { - TIM::TIM_ID + Tim::TIM_ID } } -impl TimAndPinRegister -where - (PIN, TIM): ValidTimAndPin, -{ - #[inline] - pub(super) unsafe fn new(pin: PIN, tim: TIM) -> Self { - TimAndPinRegister { pin, tim } - } - - pub(super) fn release(self) -> (PIN, TIM) { - (self.pin, self.tim) - } -} - -unsafe impl TimRegInterface for TimAndPinRegister { - #[inline(always)] - fn tim_id(&self) -> u8 { - TIM::TIM_ID - } -} - -pub(super) struct TimDynRegister { - tim_id: u8, +pub(crate) struct TimDynRegister { + pub(crate) tim_id: u8, #[allow(dead_code)] - pin_id: DynPinId, -} - -impl From> for TimDynRegister { - fn from(_reg: TimAndPinRegister) -> Self { - Self { - tim_id: TIM::TIM_ID, - pin_id: PIN::DYN, - } - } + pub(crate) pin_id: DynPinId, } unsafe impl TimRegInterface for TimDynRegister { @@ -371,8 +359,8 @@ unsafe impl TimRegInterface for TimDynRegister { //================================================================================================== /// Hardware timers -pub struct CountdownTimer { - tim: TimRegister, +pub struct CountdownTimer { + tim: Tim, curr_freq: Hertz, irq_cfg: Option, sys_clk: Hertz, @@ -401,12 +389,12 @@ unsafe impl TimRegInterface for CountdownTimer { } } -impl CountdownTimer { +impl CountdownTimer { /// Configures a TIM peripheral as a periodic count down timer - pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into, tim: TIM) -> Self { - enable_tim_clk(syscfg, TIM::TIM_ID); + pub fn new(syscfg: &mut pac::Sysconfig, sys_clk: impl Into, tim: Tim) -> Self { + enable_tim_clk(syscfg, Tim::TIM_ID); let cd_timer = CountdownTimer { - tim: unsafe { TimRegister::new(tim) }, + tim, sys_clk: sys_clk.into(), irq_cfg: None, rst_val: 0, @@ -416,7 +404,7 @@ impl CountdownTimer { }; cd_timer .tim - .reg() + .reg_block() .ctrl() .modify(|_, w| w.enable().set_bit()); cd_timer @@ -441,7 +429,7 @@ impl CountdownTimer { } if let Some(irq_sel) = irq_sel { irq_sel - .tim0(TIM::TIM_ID as usize) + .tim0(Tim::TIM_ID as usize) .write(|w| unsafe { w.bits(irq_cfg.irq as u32) }); } } @@ -460,7 +448,7 @@ impl CountdownTimer { Event::TimeOut => { enable_peripheral_clock(syscfg, PeripheralClocks::Irqsel); irqsel - .tim0(TIM::TIM_ID as usize) + .tim0(Tim::TIM_ID as usize) .write(|w| unsafe { w.bits(IRQ_DST_NONE) }); self.disable_interrupt(); self.listening = false; @@ -470,25 +458,37 @@ 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()); } - pub fn release(self, syscfg: &mut pac::Sysconfig) -> TIM { - self.tim.reg().ctrl().write(|w| w.enable().clear_bit()); + pub fn release(self, syscfg: &mut pac::Sysconfig) -> Tim { + 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)) }); - self.tim.release() + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << Tim::TIM_ID)) }); + self.tim } /// 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.sys_clk.raw() / self.curr_freq.raw(); self.set_reload(self.rst_val); @@ -497,17 +497,23 @@ 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)] @@ -518,24 +524,30 @@ impl CountdownTimer { unsafe { enable_interrupt(irq_cfg.irq) }; } } - 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().enable().write(|w| unsafe { w.bits(0) }); + self.tim + .reg_block() + .enable() + .write(|w| unsafe { w.bits(0) }); } /// Disable the counter, setting both enable and active bit to 0 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()); } @@ -549,12 +561,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()); } @@ -563,7 +575,7 @@ impl CountdownTimer { /// Configure the cascade parameters 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); @@ -580,7 +592,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(()) @@ -589,7 +601,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(()) @@ -598,7 +610,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(()) @@ -627,7 +639,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(()) @@ -639,10 +651,13 @@ impl CountdownTimer { /// Returns [false] if the timer was not active, and true otherwise. pub fn cancel(&mut self) -> bool { - if !self.tim.reg().ctrl().read().enable().bit_is_set() { + if !self.tim.reg_block().ctrl().read().enable().bit_is_set() { return false; } - self.tim.reg().ctrl().write(|w| w.enable().clear_bit()); + self.tim + .reg_block() + .ctrl() + .write(|w| w.enable().clear_bit()); true } } diff --git a/va108xx/docs.sh b/va108xx/docs.sh new file mode 100755 index 0000000..37563d2 --- /dev/null +++ b/va108xx/docs.sh @@ -0,0 +1,3 @@ +#!/bin/sh +export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options" +cargo +nightly doc --all-features --open diff --git a/vorago-reb1/Cargo.toml b/vorago-reb1/Cargo.toml index 5dfbf5c..31ee76e 100644 --- a/vorago-reb1/Cargo.toml +++ b/vorago-reb1/Cargo.toml @@ -15,10 +15,8 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" embedded-hal = "1" nb = "1" -bitfield = "0.17" - -[dependencies.max116xx-10bit] -version = "0.3" +bitfield = ">=0.17, <=0.18" +max116xx-10bit = "0.3" [dependencies.va108xx-hal] version = ">=0.8, <0.9" @@ -28,11 +26,11 @@ features = ["rt"] rt = ["va108xx-hal/rt"] [dev-dependencies] -panic-halt = "0.2" +panic-halt = "1" nb = "1" -rtt-target = "0.5" -panic-rtt-target = "0.1" -embedded-hal-bus = "0.2" +rtt-target = "0.6" +panic-rtt-target = "0.2" +embedded-hal-bus = "0.3" dummy-pin = "1" [package.metadata.docs.rs]