diff --git a/CHANGELOG.md b/CHANGELOG.md index b66bd56..8ebf0c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +### Added + +- DelayUs and DelayMs trait implementations for timer + ## [0.2.1] ### Added diff --git a/Cargo.toml b/Cargo.toml index e1843e1..7410c27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,3 +43,7 @@ opt-level = "s" [[example]] name = "timer-ticks" required-features = ["rt"] + +[[example]] +name = "tests" +required-features = ["rt"] diff --git a/examples/tests.rs b/examples/tests.rs index 12172b9..9b3452c 100644 --- a/examples/tests.rs +++ b/examples/tests.rs @@ -9,7 +9,13 @@ use cortex_m_rt::entry; use embedded_hal::digital::v2::{InputPin, OutputPin, ToggleableOutputPin}; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; -use va108xx_hal::gpio::{PinState, PinsA, PinsB}; +use va108xx_hal::{ + gpio::{PinState, PinsA, PinsB}, + pac::{self, interrupt}, + prelude::*, + time::Hertz, + timer::{default_ms_irq_handler, set_up_ms_timer, CountDownTimer, Delay}, +}; #[allow(dead_code)] #[derive(Debug)] @@ -25,7 +31,8 @@ enum TestCase { // Tie PA0 to an oscilloscope and configure pulse detection Pulse, // Tie PA0, PA1 and PA3 to an oscilloscope - Delay, + DelayGpio, + DelayMs, } #[entry] @@ -33,10 +40,11 @@ fn main() -> ! { rtt_init_print!(); rprintln!("-- VA108xx Test Application --"); let mut dp = va108xx::Peripherals::take().unwrap(); + let cp = cortex_m::Peripherals::take().unwrap(); let pinsa = PinsA::new(&mut dp.SYSCONFIG, None, dp.PORTA); let pinsb = PinsB::new(&mut dp.SYSCONFIG, Some(dp.IOCONFIG), dp.PORTB); let mut led1 = pinsa.pa10.into_push_pull_output(); - let test_case = TestCase::Delay; + let test_case = TestCase::DelayMs; match test_case { TestCase::TestBasic @@ -125,7 +133,7 @@ fn main() -> ! { cortex_m::asm::delay(25_000_000); } } - TestCase::Delay => { + TestCase::DelayGpio => { let mut out_0 = pinsa.pa0.into_push_pull_output().delay(true, false); let mut out_1 = pinsa.pa1.into_push_pull_output().delay(false, true); let mut out_2 = pinsa.pa3.into_push_pull_output().delay(true, true); @@ -136,6 +144,47 @@ fn main() -> ! { cortex_m::asm::delay(25_000_000); } } + TestCase::DelayMs => { + let ms_timer = set_up_ms_timer( + &mut dp.SYSCONFIG, + &mut dp.IRQSEL, + 50.mhz().into(), + dp.TIM0, + pac::Interrupt::OC0, + ); + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::OC0); + } + let mut delay = Delay::new(ms_timer); + for _ in 0..5 { + led1.toggle().ok(); + delay.delay_ms(500); + led1.toggle().ok(); + delay.delay_ms(500); + } + + let mut delay_timer = CountDownTimer::tim1(&mut dp.SYSCONFIG, 50.mhz().into(), dp.TIM1); + let mut pa0 = pinsa.pa0.into_push_pull_output(); + for _ in 0..5 { + led1.toggle().ok(); + delay_timer.delay_ms(200_u32); + led1.toggle().ok(); + delay_timer.delay_ms(200_u32); + } + let ahb_freq: Hertz = 50.mhz().into(); + let mut syst_delay = cortex_m::delay::Delay::new(cp.SYST, ahb_freq.0); + // Test usecond delay using both TIM peripheral and SYST + loop { + pa0.toggle().ok(); + delay_timer.delay_us(50_u32); + pa0.toggle().ok(); + delay_timer.delay_us(50_u32); + pa0.toggle().ok(); + syst_delay.delay_us(50); + pa0.toggle().ok(); + syst_delay.delay_us(50); + } + } } rprintln!("Test success"); @@ -144,3 +193,8 @@ fn main() -> ! { cortex_m::asm::delay(25_000_000); } } + +#[interrupt] +fn OC0() { + default_ms_irq_handler() +} diff --git a/examples/timer-ticks.rs b/examples/timer-ticks.rs index cac31f5..9df51cf 100644 --- a/examples/timer-ticks.rs +++ b/examples/timer-ticks.rs @@ -12,7 +12,7 @@ use va108xx_hal::{ pac::{self, interrupt}, prelude::*, time::Hertz, - timer::{set_up_ms_timer, CountDownTimer, Event}, + timer::{default_ms_irq_handler, set_up_ms_timer, CountDownTimer, Event, MS_COUNTER}, }; #[allow(dead_code)] @@ -21,7 +21,6 @@ enum LibType { Hal, } -static MS_COUNTER: Mutex> = Mutex::new(Cell::new(0)); static SEC_COUNTER: Mutex> = Mutex::new(Cell::new(0)); #[entry] @@ -105,11 +104,7 @@ fn unmask_irqs() { #[interrupt] fn OC0() { - cortex_m::interrupt::free(|cs| { - let mut ms = MS_COUNTER.borrow(cs).get(); - ms += 1; - MS_COUNTER.borrow(cs).set(ms); - }); + default_ms_irq_handler() } #[interrupt] diff --git a/src/timer.rs b/src/timer.rs index 823e43b..f581064 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -10,17 +10,26 @@ use crate::{ time::Hertz, timer, }; -use embedded_hal::timer::{Cancel, CountDown, Periodic}; +use core::cell::Cell; +use cortex_m::interrupt::Mutex; +use embedded_hal::{ + blocking::delay, + timer::{Cancel, CountDown, Periodic}, +}; use va108xx::{Interrupt, IRQSEL, SYSCONFIG}; use void::Void; const IRQ_DST_NONE: u32 = 0xffffffff; +pub static MS_COUNTER: Mutex> = Mutex::new(Cell::new(0)); /// Hardware timers pub struct CountDownTimer { tim: TIM, + curr_freq: Hertz, sys_clk: Hertz, + rst_val: u32, last_cnt: u32, + listening: bool, } /// Interrupt events @@ -57,6 +66,9 @@ macro_rules! timers { CountDownTimer { tim, sys_clk, + rst_val: 0, + curr_freq: 0.hz(), + listening: false, last_cnt: 0, } } @@ -76,6 +88,7 @@ macro_rules! timers { enable_peripheral_clock(syscfg, PeripheralClocks::Irqsel); irqsel.tim[$i].write(|w| unsafe { w.bits(interrupt as u32) }); self.tim.ctrl.modify(|_, w| w.irq_enb().set_bit()); + self.listening = true; } } } @@ -88,6 +101,7 @@ macro_rules! timers { enable_peripheral_clock(syscfg, PeripheralClocks::Irqsel); irqsel.tim[$i].write(|w| unsafe { w.bits(IRQ_DST_NONE) }); self.tim.ctrl.modify(|_, w| w.irq_enb().clear_bit()); + self.listening = false; } } } @@ -117,6 +131,14 @@ macro_rules! timers { } self } + + pub fn curr_freq(&self) -> Hertz { + self.curr_freq + } + + pub fn listening(&self) -> bool { + self.listening + } } /// CountDown implementation for TIMx @@ -127,21 +149,28 @@ macro_rules! timers { where T: Into, { - self.last_cnt = self.sys_clk.0 / timeout.into().0 - 1; + self.tim.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.rst_value.write(|w| w.bits(self.last_cnt)); - self.tim.cnt_value.write(|w| w.bits(self.last_cnt)); + self.tim.rst_value.write(|w| w.bits(self.rst_val)); + self.tim.cnt_value.write(|w| w.bits(self.rst_val)); } + self.tim.ctrl.modify(|_, w| w.enable().set_bit()); } - /// Return `Ok` if the timer has wrapped - /// Automatically clears the flag and restarts the time + /// Return `Ok` if the timer has wrapped. Peripheral will automatically clear the + /// flag and restart the time if configured correctly fn wait(&mut self) -> nb::Result<(), Void> { let cnt = self.tim.cnt_value.read().bits(); - if cnt == 0 || cnt < self.last_cnt { - self.last_cnt = cnt; + if cnt > self.last_cnt { + self.last_cnt = self.rst_val; + Ok(()) + } else if cnt == 0 { + self.last_cnt = self.rst_val; Ok(()) } else { + self.last_cnt = cnt; Err(nb::Error::WouldBlock) } } @@ -159,6 +188,60 @@ macro_rules! timers { Ok(()) } } + + /// Delay for microseconds. + /// + /// For delays less than 100 us, an assembly delay will be used. + /// For larger delays, the timer peripheral will be used. + /// Please note that the delay using the peripheral might not + /// work properly in debug mode. + impl delay::DelayUs for CountDownTimer<$TIM> { + fn delay_us(&mut self, us: u32) { + if(us < 100) { + cortex_m::asm::delay(us * (self.sys_clk.0 / 2_000_000)); + } else { + // Configuring the peripheral for higher frequencies is unstable + self.start(1000.khz()); + // The subtracted value is an empirical value measures by using tests with + // an oscilloscope. + for _ in 0..us - 7 { + nb::block!(self.wait()).unwrap(); + } + } + } + } + /// Forwards call to u32 variant of delay + impl delay::DelayUs for CountDownTimer<$TIM> { + fn delay_us(&mut self, us: u16) { + self.delay_us(u32::from(us)); + } + } + /// Forwards call to u32 variant of delay + impl delay::DelayUs for CountDownTimer<$TIM> { + fn delay_us(&mut self, us: u8) { + self.delay_us(u32::from(us)); + } + } + + impl delay::DelayMs for CountDownTimer<$TIM> { + fn delay_ms(&mut self, ms: u32) { + self.start(1000.hz()); + for _ in 0..ms { + nb::block!(self.wait()).unwrap(); + } + } + } + impl delay::DelayMs for CountDownTimer<$TIM> { + fn delay_ms(&mut self, ms: u16) { + self.delay_ms(u32::from(ms)); + } + } + impl embedded_hal::blocking::delay::DelayMs for CountDownTimer<$TIM> { + fn delay_ms(&mut self, ms: u8) { + self.delay_ms(u32::from(ms)); + } + } + )+ } } @@ -171,10 +254,26 @@ pub fn set_up_ms_timer( sys_clk: Hertz, tim0: TIM0, irq: pac::Interrupt, -) { +) -> CountDownTimer { let mut ms_timer = CountDownTimer::tim0(syscfg, sys_clk, tim0); ms_timer.listen(timer::Event::TimeOut, syscfg, irqsel, irq); ms_timer.start(1000.hz()); + ms_timer +} + +/// This function can be called in a specified interrupt handler to increment +/// the MS counter +pub fn default_ms_irq_handler() { + cortex_m::interrupt::free(|cs| { + let mut ms = MS_COUNTER.borrow(cs).get(); + ms += 1; + MS_COUNTER.borrow(cs).set(ms); + }); +} + +/// Get the current MS tick count +pub fn get_ms_ticks() -> u32 { + cortex_m::interrupt::free(|cs| MS_COUNTER.borrow(cs).get()) } timers! { @@ -203,3 +302,30 @@ timers! { TIM22: (tim22, 22), TIM23: (tim23, 23), } + +//================================================================================================== +// Delay implementations +//================================================================================================== + +pub struct Delay { + cd_tim: CountDownTimer, +} + +impl Delay { + pub fn new(tim0: CountDownTimer) -> Self { + Delay { cd_tim: tim0 } + } +} + +/// This assumes that the user has already set up a MS tick timer in TIM0 as a system tick +impl embedded_hal::blocking::delay::DelayMs for Delay { + fn delay_ms(&mut self, ms: u32) { + if self.cd_tim.curr_freq() != 1000.hz() || !self.cd_tim.listening() { + return; + } + let start_time = get_ms_ticks(); + while get_ms_ticks() - start_time < ms { + cortex_m::asm::nop(); + } + } +}