From f3d71cf0f9d45526bf43436ff1d1983417f2eead Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 8 Nov 2021 01:40:01 +0100 Subject: [PATCH] Timer and Clock modules added - Clock module to set and retrieve system clock which can have varying frequencies. Also allows enabling peripheral clocks - Prelude updated - Common time types added, based on stm32f1xx HAL implementation - Basic timer implementation added --- Cargo.toml | 2 + src/clock.rs | 48 +++++++++++++ src/lib.rs | 4 ++ src/prelude.rs | 7 ++ src/time.rs | 156 +++++++++++++++++++++++++++++++++++++++++ src/timer.rs | 183 +++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 400 insertions(+) create mode 100644 src/clock.rs create mode 100644 src/time.rs create mode 100644 src/timer.rs diff --git a/Cargo.toml b/Cargo.toml index a1dee8b..43e16e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,8 @@ cortex-m = "0.7" cortex-m-rt = "0.7" nb = "1" embedded-hal = { features = ["unproven"], version = "0.2.6" } +void = { version = "1.0", default-features = false } +once_cell = { version = "1.8.0", default-features = false } [dependencies.va108xx] version = "0.1" diff --git a/src/clock.rs b/src/clock.rs new file mode 100644 index 0000000..d5a21dc --- /dev/null +++ b/src/clock.rs @@ -0,0 +1,48 @@ +use crate::time::Hertz; +use cortex_m::interrupt::{self, Mutex}; +use once_cell::unsync::OnceCell; +use va108xx::SYSCONFIG; + +static SYS_CLOCK: Mutex> = Mutex::new(OnceCell::new()); + +pub enum PeripheralClocks { + PortA = 0, + PortB = 1, + Spi0 = 4, + Spi1 = 5, + Spi2 = 6, + UArt0 = 8, + Uart1 = 9, + I2c0 = 16, + I2c1 = 17, + Irqsel = 21, + Ioconfig = 22, + Utility = 23, + Gpio = 24, +} + +/// The Vorago in powered by an external clock which might have different frequencies. +/// The clock can be set here so it can be used by other software components as well. +/// The clock can be set exactly once +pub fn set_sys_clock(freq: Hertz) { + interrupt::free(|cs| { + SYS_CLOCK.borrow(cs).set(freq).ok(); + }) +} + +/// Returns the configured system clock +pub fn get_sys_clock() -> Option { + interrupt::free(|cs| SYS_CLOCK.borrow(cs).get().copied()) +} + +pub fn enable_peripheral_clock(syscfg: &mut SYSCONFIG, clock: PeripheralClocks) { + syscfg + .peripheral_clk_enable + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << clock as u8)) }); +} + +pub fn disable_peripheral_clock(syscfg: &mut SYSCONFIG, clock: PeripheralClocks) { + syscfg + .peripheral_clk_enable + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << clock as u8)) }); +} diff --git a/src/lib.rs b/src/lib.rs index ccc7032..5c4fb8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,10 @@ pub use va108xx; +pub mod clock; pub mod gpio; pub mod prelude; +pub mod time; +pub mod timer; + pub use va108xx as pac; diff --git a/src/prelude.rs b/src/prelude.rs index bbdbc4f..ed67fbe 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,3 +2,10 @@ pub use embedded_hal::prelude::*; pub use crate::gpio::GpioExt as _va108xx_hal_gpio_GpioExt; + +pub use embedded_hal::digital::v2::InputPin as _embedded_hal_gpio_InputPin; +pub use embedded_hal::digital::v2::OutputPin as _embedded_hal_gpio_OutputPin; +pub use embedded_hal::digital::v2::StatefulOutputPin as _embedded_hal_gpio_StatefulOutputPin; +pub use embedded_hal::digital::v2::ToggleableOutputPin as _embedded_hal_gpio_ToggleableOutputPin; + +pub use crate::time::U32Ext as _va108xx_hal_time_U32Ext; diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..6b0200e --- /dev/null +++ b/src/time.rs @@ -0,0 +1,156 @@ +//! Time units +//! +//! See [`Hertz`], [`KiloHertz`] and [`MegaHertz`] for creating increasingly higher frequencies. +//! +//! The [`U32Ext`] trait adds various methods like `.hz()`, `.mhz()`, etc to the `u32` primitive type, +//! allowing it to be converted into frequencies. + +/// Bits per second +#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] +pub struct Bps(pub u32); + +/// Hertz +/// +/// Create a frequency specified in [Hertz](https://en.wikipedia.org/wiki/Hertz). +/// +/// See also [`KiloHertz`] and [`MegaHertz`] for semantically correct ways of creating higher +/// frequencies. +/// +/// # Examples +/// +/// ## Create an 60 Hz frequency +/// +/// ```rust +/// use stm32f1xx_hal::time::Hertz; +/// +/// let freq = 60.hz(); +/// ``` +#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] +pub struct Hertz(pub u32); + +/// Kilohertz +/// +/// Create a frequency specified in kilohertz. +/// +/// See also [`Hertz`] and [`MegaHertz`] for semantically correct ways of creating lower or higher +/// frequencies. +/// +/// # Examples +/// +/// ## Create a 100 Khz frequency +/// +/// This example creates a 100 KHz frequency. This could be used to set an I2C data rate or PWM +/// frequency, etc. +/// +/// ```rust +/// use stm32f1xx_hal::time::Hertz; +/// +/// let freq = 100.khz(); +/// ``` +#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] +pub struct KiloHertz(pub u32); + +/// Megahertz +/// +/// Create a frequency specified in megahertz. +/// +/// See also [`Hertz`] and [`KiloHertz`] for semantically correct ways of creating lower +/// frequencies. +/// +/// # Examples +/// +/// ## Create a an 8 MHz frequency +/// +/// This example creates an 8 MHz frequency that could be used to configure an SPI peripheral, etc. +/// +/// ```rust +/// use stm32f1xx_hal::time::Hertz; +/// +/// let freq = 8.mhz(); +/// ``` +#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] +pub struct MegaHertz(pub u32); + +/// Time unit +#[derive(PartialEq, PartialOrd, Clone, Copy)] +pub struct MilliSeconds(pub u32); + +#[derive(PartialEq, PartialOrd, Clone, Copy)] +pub struct MicroSeconds(pub u32); + +/// Extension trait that adds convenience methods to the `u32` type +pub trait U32Ext { + /// Wrap in `Bps` + fn bps(self) -> Bps; + + /// Wrap in `Hertz` + fn hz(self) -> Hertz; + + /// Wrap in `KiloHertz` + fn khz(self) -> KiloHertz; + + /// Wrap in `MegaHertz` + fn mhz(self) -> MegaHertz; + + /// Wrap in `MilliSeconds` + fn ms(self) -> MilliSeconds; + + /// Wrap in `MicroSeconds` + fn us(self) -> MicroSeconds; +} + +impl U32Ext for u32 { + fn bps(self) -> Bps { + Bps(self) + } + + fn hz(self) -> Hertz { + Hertz(self) + } + + fn khz(self) -> KiloHertz { + KiloHertz(self) + } + + fn mhz(self) -> MegaHertz { + MegaHertz(self) + } + + fn ms(self) -> MilliSeconds { + MilliSeconds(self) + } + + fn us(self) -> MicroSeconds { + MicroSeconds(self) + } +} + +impl From for Hertz { + fn from(val: KiloHertz) -> Self { + Self(val.0 * 1_000) + } +} + +impl From for Hertz { + fn from(val: MegaHertz) -> Self { + Self(val.0 * 1_000_000) + } +} + +impl From for KiloHertz { + fn from(val: MegaHertz) -> Self { + Self(val.0 * 1_000) + } +} + +impl From for Hertz { + fn from(val: MilliSeconds) -> Self { + Self(1_000 / val.0) + } +} + +impl From for Hertz { + fn from(val: MicroSeconds) -> Self { + Self(1_000_000 / val.0) + } +} diff --git a/src/timer.rs b/src/timer.rs new file mode 100644 index 0000000..c34e3ee --- /dev/null +++ b/src/timer.rs @@ -0,0 +1,183 @@ +use crate::{ + clock::{enable_peripheral_clock, PeripheralClocks}, + time::Hertz, +}; +use embedded_hal::timer::{Cancel, CountDown, Periodic}; +use va108xx::{Interrupt, IRQSEL, SYSCONFIG}; +use void::Void; + +const IRQ_DST_NONE: u32 = 0xffffffff; + +/// Hardware timers +pub struct CountDownTimer { + tim: TIM, + sys_clk: Hertz, + last_cnt: u32, +} + +/// Interrupt events +pub enum Event { + /// Timer timed out / count down ended + TimeOut, +} + +pub enum TimerErrors { + Canceled, +} + +fn enable_tim_clk(syscfg: &mut SYSCONFIG, idx: u8) { + syscfg + .tim_clk_enable + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << idx)) }); +} + +macro_rules! timers { + ($($TIM:ident: ($tim:ident, $i:expr),)+) => { + $( + use crate::pac::$TIM; + + impl CountDownTimer<$TIM> { + // XXX(why not name this `new`?) bummer: constructors need to have different names + // even if the `$TIM` are non overlapping (compare to the `free` function below + // which just works) + /// Configures a TIM peripheral as a periodic count down timer + pub fn $tim( + syscfg: &mut SYSCONFIG, sys_clk: Hertz, tim: $TIM + ) -> Self { + enable_tim_clk(syscfg, $i); + tim.ctrl.modify(|_, w| w.enable().set_bit()); + CountDownTimer { + tim, + sys_clk, + last_cnt: 0, + } + } + + /// Listen for events. This also actives the IRQ in the IRQSEL register + /// for the provided interrupt. It also actives the peripheral clock for + /// IRQSEL + pub fn listen( + &mut self, + event: Event, + syscfg: &mut SYSCONFIG, + irqsel: &mut IRQSEL, + interrupt: Interrupt, + ) { + match event { + Event::TimeOut => { + 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()); + } + } + } + + pub fn unlisten( + &mut self, event: Event, syscfg: &mut SYSCONFIG, irqsel: &mut IRQSEL + ) { + match event { + Event::TimeOut => { + 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()); + } + } + } + + pub fn release(self, syscfg: &mut SYSCONFIG) -> $TIM { + self.tim.ctrl.write(|w| w.enable().clear_bit()); + syscfg + .tim_clk_enable + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << $i)) }); + self.tim + } + + pub fn auto_disable(self, enable: bool) -> Self { + if enable { + self.tim.ctrl.modify(|_, w| w.auto_disable().set_bit()); + } else { + self.tim.ctrl.modify(|_, w| w.auto_disable().clear_bit()); + } + self + } + + pub fn auto_deactivate(self, enable: bool) -> Self { + if enable { + self.tim.ctrl.modify(|_, w| w.auto_deactivate().set_bit()); + } else { + self.tim.ctrl.modify(|_, w| w.auto_deactivate().clear_bit()); + } + self + } + } + + /// CountDown implementation for TIMx + impl CountDown for CountDownTimer<$TIM> { + type Time = Hertz; + + fn start(&mut self, timeout: T) + where + T: Into, + { + self.last_cnt = self.sys_clk.0 / timeout.into().0 - 1; + unsafe { + self.tim.rst_value.write(|w| w.bits(self.last_cnt)); + self.tim.cnt_value.write(|w| w.bits(self.last_cnt)); + } + } + + /// Return `Ok` if the timer has wrapped + /// Automatically clears the flag and restarts the time + 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; + Ok(()) + } else { + Err(nb::Error::WouldBlock) + } + } + } + + impl Periodic for CountDownTimer<$TIM> {} + + impl Cancel for CountDownTimer<$TIM> { + type Error = TimerErrors; + fn cancel(&mut self) -> Result<(), Self::Error> { + if !self.tim.ctrl.read().enable().bit_is_set() { + return Err(TimerErrors::Canceled); + } + self.tim.ctrl.write(|w| w.enable().clear_bit()); + Ok(()) + } + } + )+ + } +} + +timers! { + TIM0: (tim0, 0), + TIM1: (tim1, 1), + TIM2: (tim2, 2), + TIM3: (tim3, 3), + TIM4: (tim4, 4), + TIM5: (tim5, 5), + TIM6: (tim6, 6), + TIM7: (tim7, 7), + TIM8: (tim8, 8), + TIM9: (tim9, 9), + TIM10: (tim10, 10), + TIM11: (tim11, 11), + TIM12: (tim12, 12), + TIM13: (tim13, 13), + TIM14: (tim14, 14), + TIM15: (tim15, 15), + TIM16: (tim16, 16), + TIM17: (tim17, 17), + TIM18: (tim18, 18), + TIM19: (tim19, 19), + TIM20: (tim20, 20), + TIM21: (tim21, 21), + TIM22: (tim22, 22), + TIM23: (tim23, 23), +}