added blocking delay functions

- DelayUs and DelayMs trait implementations for CountDown timer
  peripherals
- Bugfix for wait function
This commit is contained in:
Robin Müller 2021-11-17 10:34:55 +01:00
parent 030b555a7f
commit 2a9225fda5
5 changed files with 203 additions and 20 deletions

View File

@ -8,6 +8,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [unreleased] ## [unreleased]
### Added
- DelayUs and DelayMs trait implementations for timer
## [0.2.1] ## [0.2.1]
### Added ### Added

View File

@ -43,3 +43,7 @@ opt-level = "s"
[[example]] [[example]]
name = "timer-ticks" name = "timer-ticks"
required-features = ["rt"] required-features = ["rt"]
[[example]]
name = "tests"
required-features = ["rt"]

View File

@ -9,7 +9,13 @@ use cortex_m_rt::entry;
use embedded_hal::digital::v2::{InputPin, OutputPin, ToggleableOutputPin}; use embedded_hal::digital::v2::{InputPin, OutputPin, ToggleableOutputPin};
use panic_rtt_target as _; use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print}; 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)] #[allow(dead_code)]
#[derive(Debug)] #[derive(Debug)]
@ -25,7 +31,8 @@ enum TestCase {
// Tie PA0 to an oscilloscope and configure pulse detection // Tie PA0 to an oscilloscope and configure pulse detection
Pulse, Pulse,
// Tie PA0, PA1 and PA3 to an oscilloscope // Tie PA0, PA1 and PA3 to an oscilloscope
Delay, DelayGpio,
DelayMs,
} }
#[entry] #[entry]
@ -33,10 +40,11 @@ fn main() -> ! {
rtt_init_print!(); rtt_init_print!();
rprintln!("-- VA108xx Test Application --"); rprintln!("-- VA108xx Test Application --");
let mut dp = va108xx::Peripherals::take().unwrap(); 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 pinsa = PinsA::new(&mut dp.SYSCONFIG, None, dp.PORTA);
let pinsb = PinsB::new(&mut dp.SYSCONFIG, Some(dp.IOCONFIG), dp.PORTB); let pinsb = PinsB::new(&mut dp.SYSCONFIG, Some(dp.IOCONFIG), dp.PORTB);
let mut led1 = pinsa.pa10.into_push_pull_output(); let mut led1 = pinsa.pa10.into_push_pull_output();
let test_case = TestCase::Delay; let test_case = TestCase::DelayMs;
match test_case { match test_case {
TestCase::TestBasic TestCase::TestBasic
@ -125,7 +133,7 @@ fn main() -> ! {
cortex_m::asm::delay(25_000_000); 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_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_1 = pinsa.pa1.into_push_pull_output().delay(false, true);
let mut out_2 = pinsa.pa3.into_push_pull_output().delay(true, 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); 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"); rprintln!("Test success");
@ -144,3 +193,8 @@ fn main() -> ! {
cortex_m::asm::delay(25_000_000); cortex_m::asm::delay(25_000_000);
} }
} }
#[interrupt]
fn OC0() {
default_ms_irq_handler()
}

View File

@ -12,7 +12,7 @@ use va108xx_hal::{
pac::{self, interrupt}, pac::{self, interrupt},
prelude::*, prelude::*,
time::Hertz, 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)] #[allow(dead_code)]
@ -21,7 +21,6 @@ enum LibType {
Hal, Hal,
} }
static MS_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
static SEC_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0)); static SEC_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
#[entry] #[entry]
@ -105,11 +104,7 @@ fn unmask_irqs() {
#[interrupt] #[interrupt]
fn OC0() { fn OC0() {
cortex_m::interrupt::free(|cs| { default_ms_irq_handler()
let mut ms = MS_COUNTER.borrow(cs).get();
ms += 1;
MS_COUNTER.borrow(cs).set(ms);
});
} }
#[interrupt] #[interrupt]

View File

@ -10,17 +10,26 @@ use crate::{
time::Hertz, time::Hertz,
timer, 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 va108xx::{Interrupt, IRQSEL, SYSCONFIG};
use void::Void; use void::Void;
const IRQ_DST_NONE: u32 = 0xffffffff; const IRQ_DST_NONE: u32 = 0xffffffff;
pub static MS_COUNTER: Mutex<Cell<u32>> = Mutex::new(Cell::new(0));
/// Hardware timers /// Hardware timers
pub struct CountDownTimer<TIM> { pub struct CountDownTimer<TIM> {
tim: TIM, tim: TIM,
curr_freq: Hertz,
sys_clk: Hertz, sys_clk: Hertz,
rst_val: u32,
last_cnt: u32, last_cnt: u32,
listening: bool,
} }
/// Interrupt events /// Interrupt events
@ -57,6 +66,9 @@ macro_rules! timers {
CountDownTimer { CountDownTimer {
tim, tim,
sys_clk, sys_clk,
rst_val: 0,
curr_freq: 0.hz(),
listening: false,
last_cnt: 0, last_cnt: 0,
} }
} }
@ -76,6 +88,7 @@ macro_rules! timers {
enable_peripheral_clock(syscfg, PeripheralClocks::Irqsel); enable_peripheral_clock(syscfg, PeripheralClocks::Irqsel);
irqsel.tim[$i].write(|w| unsafe { w.bits(interrupt as u32) }); irqsel.tim[$i].write(|w| unsafe { w.bits(interrupt as u32) });
self.tim.ctrl.modify(|_, w| w.irq_enb().set_bit()); 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); enable_peripheral_clock(syscfg, PeripheralClocks::Irqsel);
irqsel.tim[$i].write(|w| unsafe { w.bits(IRQ_DST_NONE) }); irqsel.tim[$i].write(|w| unsafe { w.bits(IRQ_DST_NONE) });
self.tim.ctrl.modify(|_, w| w.irq_enb().clear_bit()); self.tim.ctrl.modify(|_, w| w.irq_enb().clear_bit());
self.listening = false;
} }
} }
} }
@ -117,6 +131,14 @@ macro_rules! timers {
} }
self self
} }
pub fn curr_freq(&self) -> Hertz {
self.curr_freq
}
pub fn listening(&self) -> bool {
self.listening
}
} }
/// CountDown implementation for TIMx /// CountDown implementation for TIMx
@ -127,21 +149,28 @@ macro_rules! timers {
where where
T: Into<Hertz>, T: Into<Hertz>,
{ {
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 { unsafe {
self.tim.rst_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.last_cnt)); 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 /// Return `Ok` if the timer has wrapped. Peripheral will automatically clear the
/// Automatically clears the flag and restarts the time /// flag and restart the time if configured correctly
fn wait(&mut self) -> nb::Result<(), Void> { fn wait(&mut self) -> nb::Result<(), Void> {
let cnt = self.tim.cnt_value.read().bits(); let cnt = self.tim.cnt_value.read().bits();
if cnt == 0 || cnt < self.last_cnt { if cnt > self.last_cnt {
self.last_cnt = cnt; self.last_cnt = self.rst_val;
Ok(())
} else if cnt == 0 {
self.last_cnt = self.rst_val;
Ok(()) Ok(())
} else { } else {
self.last_cnt = cnt;
Err(nb::Error::WouldBlock) Err(nb::Error::WouldBlock)
} }
} }
@ -159,6 +188,60 @@ macro_rules! timers {
Ok(()) 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<u32> 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<u16> 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<u8> for CountDownTimer<$TIM> {
fn delay_us(&mut self, us: u8) {
self.delay_us(u32::from(us));
}
}
impl delay::DelayMs<u32> 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<u16> for CountDownTimer<$TIM> {
fn delay_ms(&mut self, ms: u16) {
self.delay_ms(u32::from(ms));
}
}
impl embedded_hal::blocking::delay::DelayMs<u8> 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, sys_clk: Hertz,
tim0: TIM0, tim0: TIM0,
irq: pac::Interrupt, irq: pac::Interrupt,
) { ) -> CountDownTimer<TIM0> {
let mut ms_timer = CountDownTimer::tim0(syscfg, sys_clk, tim0); let mut ms_timer = CountDownTimer::tim0(syscfg, sys_clk, tim0);
ms_timer.listen(timer::Event::TimeOut, syscfg, irqsel, irq); ms_timer.listen(timer::Event::TimeOut, syscfg, irqsel, irq);
ms_timer.start(1000.hz()); 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! { timers! {
@ -203,3 +302,30 @@ timers! {
TIM22: (tim22, 22), TIM22: (tim22, 22),
TIM23: (tim23, 23), TIM23: (tim23, 23),
} }
//==================================================================================================
// Delay implementations
//==================================================================================================
pub struct Delay {
cd_tim: CountDownTimer<TIM0>,
}
impl Delay {
pub fn new(tim0: CountDownTimer<TIM0>) -> 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<u32> 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();
}
}
}