diff --git a/CHANGELOG.md b/CHANGELOG.md index 28971d7..95916d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ 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] + +### Added + +- TIM Cascade example + +### Changed + +- `CountDownTimer` new function now expects an `impl Into` instead of `Hertz` + ## [0.2.3] ### Added diff --git a/Cargo.toml b/Cargo.toml index dda168f..a69002d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ once_cell = { version = "1.8.0", default-features = false } libm = "0.2.1" [dependencies.va108xx] -version = "0.2.3" +version = "0.2.4" [features] rt = ["va108xx/rt"] @@ -51,3 +51,7 @@ required-features = ["rt"] [[example]] name = "pwm" required-features = ["rt"] + +[[example]] +name = "cascade" +required-features = ["rt"] diff --git a/examples/cascade.rs b/examples/cascade.rs new file mode 100644 index 0000000..5c9ca21 --- /dev/null +++ b/examples/cascade.rs @@ -0,0 +1,143 @@ +//! Simple Cascade example +//! +//! A timer will be periodically started which starts another timer via the cascade feature. +//! This timer will then start another timer with the cascade feature as well. +#![no_main] +#![no_std] + +use core::cell::RefCell; +use cortex_m::interrupt::Mutex; +use cortex_m_rt::entry; +use panic_rtt_target as _; +use rtt_target::{rprintln, rtt_init_print}; +use va108xx_hal::{ + pac::{self, interrupt, TIM4, TIM5}, + prelude::*, + timer::{ + default_ms_irq_handler, set_up_ms_timer, CascadeCtrl, CascadeSource, CountDownTimer, Delay, + Event, + }, +}; + +static CSD_TGT_1: Mutex>>> = Mutex::new(RefCell::new(None)); +static CSD_TGT_2: Mutex>>> = Mutex::new(RefCell::new(None)); + +#[entry] +fn main() -> ! { + rtt_init_print!(); + rprintln!("-- VA108xx Cascade example application--"); + + let mut dp = pac::Peripherals::take().unwrap(); + let timer = set_up_ms_timer( + &mut dp.SYSCONFIG, + &mut dp.IRQSEL, + 50.mhz().into(), + dp.TIM0, + pac::Interrupt::OC0, + ); + let mut delay = Delay::new(timer); + + // Will be started periodically to trigger a cascade + let mut cascade_triggerer = + CountDownTimer::new(&mut dp.SYSCONFIG, 50.mhz(), dp.TIM3).auto_disable(true); + cascade_triggerer.listen( + Event::TimeOut, + &mut dp.SYSCONFIG, + &mut dp.IRQSEL, + va108xx::Interrupt::OC1, + ); + + // First target for cascade + let mut cascade_target_1 = + CountDownTimer::new(&mut dp.SYSCONFIG, 50.mhz(), dp.TIM4).auto_deactivate(true); + cascade_target_1 + .cascade_0_source(CascadeSource::TimBase, Some(3)) + .expect("Configuring cascade source for TIM4 failed"); + let mut csd_cfg = CascadeCtrl::default(); + csd_cfg.enb_start_src_csd0 = true; + // Use trigger mode here + csd_cfg.trg_csd0 = true; + cascade_target_1.cascade_control(csd_cfg); + // Normally it should already be sufficient to activate IRQ in the CTRL + // register but a full interrupt is use here to display print output when + // the timer expires + cascade_target_1.listen( + Event::TimeOut, + &mut dp.SYSCONFIG, + &mut dp.IRQSEL, + va108xx::Interrupt::OC2, + ); + // The counter will only activate when the cascade signal is coming in so + // it is okay to call start here to set the reset value + cascade_target_1.start(1.hz()); + + // Activated by first cascade target + let mut cascade_target_2 = + CountDownTimer::new(&mut dp.SYSCONFIG, 50.mhz(), dp.TIM5).auto_deactivate(true); + // Set TIM4 as cascade source + cascade_target_2 + .cascade_1_source(CascadeSource::TimBase, Some(4)) + .expect("Configuring cascade source for TIM5 failed"); + + csd_cfg = CascadeCtrl::default(); + csd_cfg.enb_start_src_csd1 = true; + // Use trigger mode here + csd_cfg.trg_csd1 = true; + cascade_target_2.cascade_control(csd_cfg); + // Normally it should already be sufficient to activate IRQ in the CTRL + // register but a full interrupt is use here to display print output when + // the timer expires + cascade_target_2.listen( + Event::TimeOut, + &mut dp.SYSCONFIG, + &mut dp.IRQSEL, + va108xx::Interrupt::OC3, + ); + // The counter will only activate when the cascade signal is coming in so + // it is okay to call start here to set the reset value + cascade_target_2.start(1.hz()); + + // Unpend all IRQs + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC0); + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC1); + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC2); + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC3); + } + // Make both cascade targets accessible from the IRQ handler with the Mutex dance + cortex_m::interrupt::free(|cs| { + CSD_TGT_1.borrow(cs).replace(Some(cascade_target_1)); + CSD_TGT_2.borrow(cs).replace(Some(cascade_target_2)); + }); + loop { + rprintln!("-- Triggering cascade in 0.5 seconds --"); + cascade_triggerer.start(2.hz()); + delay.delay_ms(5000); + } +} + +#[interrupt] +fn OC0() { + default_ms_irq_handler() +} + +#[interrupt] +fn OC1() { + static mut IDX: u32 = 0; + rprintln!("{}: Cascade triggered timed out", IDX); + *IDX += 1; +} + +#[interrupt] +fn OC2() { + static mut IDX: u32 = 0; + rprintln!("{}: First cascade target timed out", IDX); + *IDX += 1; +} + +#[interrupt] +fn OC3() { + static mut IDX: u32 = 0; + rprintln!("{}: Second cascade target timed out", IDX); + *IDX += 1; +} diff --git a/examples/spi.rs b/examples/spi.rs index 7d6d052..33daf31 100644 --- a/examples/spi.rs +++ b/examples/spi.rs @@ -139,7 +139,7 @@ fn main() -> ! { } // Application logic - let mut delay_tim = CountDownTimer::new(&mut dp.SYSCONFIG, 50.mhz().into(), dp.TIM1); + let mut delay_tim = CountDownTimer::new(&mut dp.SYSCONFIG, 50.mhz(), dp.TIM1); loop { match SPI_BUS_SEL { SpiBusSelect::SpiAPortA | SpiBusSelect::SpiAPortB => { diff --git a/src/timer.rs b/src/timer.rs index 3807807..346f62a 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -42,8 +42,61 @@ pub enum Event { TimeOut, } +#[derive(Default, Debug, PartialEq, Copy, Clone)] +pub struct CascadeCtrl { + /// Enable Cascade 0 signal active as a requirement for counting + pub enb_start_src_csd0: bool, + /// Invert Cascade 0, making it active low + pub inv_csd0: bool, + /// Enable Cascade 1 signal active as a requirement for counting + pub enb_start_src_csd1: bool, + /// Invert Cascade 1, making it active low + pub inv_csd1: bool, + /// Specify required operation if both Cascade 0 and Cascade 1 are active. + /// 0 is a logical AND of both cascade signals, 1 is a logical OR + pub dual_csd_op: bool, + /// Enable trigger mode for Cascade 0. In trigger mode, couting will start with the selected + /// cascade signal active, but once the counter is active, cascade control will be ignored + pub trg_csd0: bool, + /// Trigger mode, identical to [`trg_csd0`](CascadeCtrl) but for Cascade 1 + pub trg_csd1: bool, + /// Enable Cascade 2 signal active as a requirement to stop counting. This mode is similar + /// to the REQ_STOP control bit, but signalled by a Cascade source + pub enb_stop_src_csd2: bool, + /// Invert Cascade 2, making it active low + pub inv_csd2: bool, + /// The counter is automatically disabled if the corresponding Cascade 2 level-sensitive input + /// souce is active when the count reaches 0. If the counter is not 0, the cascade control is + /// ignored + pub trg_csd2: bool, +} + +#[derive(Debug, PartialEq)] +pub enum CascadeSel { + Csd0 = 0, + Csd1 = 1, + Csd2 = 2, +} + +/// The numbers are the base numbers for bundles like PORTA, PORTB or TIM +#[derive(Debug, PartialEq)] +pub enum CascadeSource { + PortABase = 0, + PortBBase = 32, + TimBase = 64, + RamSbe = 96, + RamMbe = 97, + RomSbe = 98, + RomMbe = 99, + Txev = 100, + ClockDividerBase = 120, +} + +#[derive(Debug, PartialEq)] pub enum TimerErrors { Canceled, + /// Invalid input for Cascade source + InvalidCsdSourceInput, } //================================================================================================== @@ -350,13 +403,84 @@ unsafe impl TimRegInterface for CountDownTimer { } } +macro_rules! csd_sel { + ($func_name:ident, $csd_reg:ident) => { + /// Configure the Cascade sources + pub fn $func_name( + &mut self, + src: CascadeSource, + id: Option, + ) -> Result<(), TimerErrors> { + let mut id_num = 0; + if let CascadeSource::PortABase + | CascadeSource::PortBBase + | CascadeSource::ClockDividerBase + | CascadeSource::TimBase = src + { + if id.is_none() { + return Err(TimerErrors::InvalidCsdSourceInput); + } + } + if id.is_some() { + id_num = id.unwrap(); + } + match src { + CascadeSource::PortABase => { + if id_num > 55 { + return Err(TimerErrors::InvalidCsdSourceInput); + } + self.tim.reg().$csd_reg.write(|w| unsafe { + w.cassel().bits(CascadeSource::PortABase as u8 + id_num) + }); + Ok(()) + } + CascadeSource::PortBBase => { + if id_num > 23 { + return Err(TimerErrors::InvalidCsdSourceInput); + } + self.tim.reg().$csd_reg.write(|w| unsafe { + w.cassel().bits(CascadeSource::PortBBase as u8 + id_num) + }); + Ok(()) + } + CascadeSource::TimBase => { + if id_num > 23 { + return Err(TimerErrors::InvalidCsdSourceInput); + } + self.tim.reg().$csd_reg.write(|w| unsafe { + w.cassel().bits(CascadeSource::TimBase as u8 + id_num) + }); + Ok(()) + } + CascadeSource::ClockDividerBase => { + if id_num > 7 { + return Err(TimerErrors::InvalidCsdSourceInput); + } + self.tim.reg().cascade0.write(|w| unsafe { + w.cassel() + .bits(CascadeSource::ClockDividerBase as u8 + id_num) + }); + Ok(()) + } + _ => { + self.tim + .reg() + .$csd_reg + .write(|w| unsafe { w.cassel().bits(src as u8) }); + Ok(()) + } + } + } + }; +} + impl CountDownTimer { /// Configures a TIM peripheral as a periodic count down timer - pub fn new(syscfg: &mut SYSCONFIG, sys_clk: Hertz, tim: TIM) -> Self { + pub fn new(syscfg: &mut SYSCONFIG, sys_clk: impl Into, tim: TIM) -> Self { enable_tim_clk(syscfg, TIM::TIM_ID); let cd_timer = CountDownTimer { tim: unsafe { TimRegister::new(tim) }, - sys_clk, + sys_clk: sys_clk.into(), rst_val: 0, curr_freq: 0.hz(), listening: false, @@ -380,7 +504,7 @@ impl CountDownTimer { Event::TimeOut => { enable_peripheral_clock(syscfg, PeripheralClocks::Irqsel); irqsel.tim[TIM::TIM_ID as usize].write(|w| unsafe { w.bits(interrupt as u32) }); - self.tim.reg().ctrl.modify(|_, w| w.irq_enb().set_bit()); + self.enable_interrupt(); self.listening = true; } } @@ -391,12 +515,22 @@ impl CountDownTimer { Event::TimeOut => { enable_peripheral_clock(syscfg, PeripheralClocks::Irqsel); irqsel.tim[TIM::TIM_ID as usize].write(|w| unsafe { w.bits(IRQ_DST_NONE) }); - self.tim.reg().ctrl.modify(|_, w| w.irq_enb().clear_bit()); + self.disable_interrupt(); self.listening = false; } } } + #[inline(always)] + pub fn enable_interrupt(&mut self) { + self.tim.reg().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()); + } + pub fn release(self, syscfg: &mut SYSCONFIG) -> TIM { self.tim.reg().ctrl.write(|w| w.enable().clear_bit()); syscfg @@ -405,6 +539,28 @@ impl CountDownTimer { 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.curr_freq = timeout.into(); + self.rst_val = self.sys_clk.0 / self.curr_freq.0; + unsafe { + self.tim.reg().rst_value.write(|w| w.bits(self.rst_val)); + self.tim.reg().cnt_value.write(|w| w.bits(self.rst_val)); + } + } + + #[inline(always)] + pub fn enable(&mut self) { + self.tim.reg().ctrl.modify(|_, w| w.enable().set_bit()); + } + + #[inline(always)] + pub fn disable(&mut self) { + self.tim.reg().ctrl.modify(|_, w| w.enable().clear_bit()); + } + + /// Disable the counter, setting both enable and active bit to 0 pub fn auto_disable(self, enable: bool) -> Self { if enable { self.tim @@ -420,6 +576,10 @@ impl CountDownTimer { self } + /// This option only applies when the Auto-Disable functionality is 0. + /// + /// The active bit is changed to 0 when count reaches 0, but the counter stays + /// enabled. When Auto-Disable is 1, Auto-Deactivate is implied pub fn auto_deactivate(self, enable: bool) -> Self { if enable { self.tim @@ -435,6 +595,26 @@ impl CountDownTimer { self } + /// Configure the cascade parameters + pub fn cascade_control(&mut self, ctrl: CascadeCtrl) { + self.tim.reg().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); + w.csdinv1().bit(ctrl.inv_csd1); + w.dcasop().bit(ctrl.dual_csd_op); + w.csdtrg0().bit(ctrl.trg_csd0); + w.csdtrg1().bit(ctrl.trg_csd1); + w.csden2().bit(ctrl.enb_stop_src_csd2); + w.csdinv2().bit(ctrl.inv_csd2); + w.csdtrg2().bit(ctrl.trg_csd2) + }); + } + + csd_sel!(cascade_0_source, cascade0); + csd_sel!(cascade_1_source, cascade1); + csd_sel!(cascade_2_source, cascade2); + pub fn curr_freq(&self) -> Hertz { self.curr_freq } @@ -448,18 +628,13 @@ impl CountDownTimer { impl CountDown for CountDownTimer { type Time = Hertz; + #[inline] fn start(&mut self, timeout: T) where T: Into, { - self.tim.reg().ctrl.modify(|_, w| w.enable().clear_bit()); - self.curr_freq = timeout.into(); - self.rst_val = self.sys_clk.0 / self.curr_freq.0; - unsafe { - self.tim.reg().rst_value.write(|w| w.bits(self.rst_val)); - self.tim.reg().cnt_value.write(|w| w.bits(self.rst_val)); - } - self.tim.reg().ctrl.modify(|_, w| w.enable().set_bit()); + self.load(timeout); + self.enable(); } /// Return `Ok` if the timer has wrapped. Peripheral will automatically clear the