diff --git a/CHANGELOG.md b/CHANGELOG.md index fc5f967..b66bd56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [unreleased] +## [0.2.1] + +### Added + +- Adds the IRQ interface to configure interrupts on output and input pins +- Utility function to set up millisecond timer with `TIM0` +- Function to set clock divisor registers in `clock` module + ### Changed - Minor optimizations and tweaks for GPIO module +- Moved the `FilterClkSel` struct to the `clock` module, re-exporting in `gpio` +- Clearing output state at initialization of Output pins ## [0.2.0] diff --git a/Cargo.toml b/Cargo.toml index 941e255..e1843e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "va108xx-hal" -version = "0.2.0" +version = "0.2.1" authors = ["Robin Mueller "] edition = "2021" description = "HAL for the Vorago VA108xx family of microcontrollers" diff --git a/examples/timer-ticks.rs b/examples/timer-ticks.rs index 5e20f0b..cac31f5 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::{CountDownTimer, Event}, + timer::{set_up_ms_timer, CountDownTimer, Event}, }; #[allow(dead_code)] @@ -65,23 +65,21 @@ fn main() -> ! { } } LibType::Hal => { - let mut ms_timer = - CountDownTimer::tim0(&mut dp.SYSCONFIG, get_sys_clock().unwrap(), dp.TIM0); - let mut second_timer = - CountDownTimer::tim1(&mut dp.SYSCONFIG, get_sys_clock().unwrap(), dp.TIM1); - ms_timer.listen( - Event::TimeOut, + set_up_ms_timer( &mut dp.SYSCONFIG, &mut dp.IRQSEL, + 50.mhz().into(), + dp.TIM0, interrupt::OC0, ); + let mut second_timer = + CountDownTimer::tim1(&mut dp.SYSCONFIG, get_sys_clock().unwrap(), dp.TIM1); second_timer.listen( Event::TimeOut, &mut dp.SYSCONFIG, &mut dp.IRQSEL, interrupt::OC1, ); - ms_timer.start(1000.hz()); second_timer.start(1.hz()); unmask_irqs(); } diff --git a/src/clock.rs b/src/clock.rs index d0c9656..857ac72 100644 --- a/src/clock.rs +++ b/src/clock.rs @@ -25,6 +25,18 @@ pub enum PeripheralClocks { Gpio = 24, } +#[derive(Debug, PartialEq)] +pub enum FilterClkSel { + SysClk = 0, + Clk1 = 1, + Clk2 = 2, + Clk3 = 3, + Clk4 = 4, + Clk5 = 5, + Clk6 = 6, + Clk7 = 7, +} + /// 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 @@ -39,6 +51,19 @@ pub fn get_sys_clock() -> Option { interrupt::free(|cs| SYS_CLOCK.borrow(cs).get().copied()) } +pub fn set_clk_div_register(syscfg: &mut SYSCONFIG, clk_sel: FilterClkSel, div: u32) { + match clk_sel { + FilterClkSel::SysClk => (), + FilterClkSel::Clk1 => syscfg.ioconfig_clkdiv1.write(|w| unsafe { w.bits(div) }), + FilterClkSel::Clk2 => syscfg.ioconfig_clkdiv2.write(|w| unsafe { w.bits(div) }), + FilterClkSel::Clk3 => syscfg.ioconfig_clkdiv3.write(|w| unsafe { w.bits(div) }), + FilterClkSel::Clk4 => syscfg.ioconfig_clkdiv4.write(|w| unsafe { w.bits(div) }), + FilterClkSel::Clk5 => syscfg.ioconfig_clkdiv5.write(|w| unsafe { w.bits(div) }), + FilterClkSel::Clk6 => syscfg.ioconfig_clkdiv6.write(|w| unsafe { w.bits(div) }), + FilterClkSel::Clk7 => syscfg.ioconfig_clkdiv7.write(|w| unsafe { w.bits(div) }), + } +} + pub fn enable_peripheral_clock(syscfg: &mut SYSCONFIG, clock: PeripheralClocks) { syscfg .peripheral_clk_enable diff --git a/src/gpio/dynpins.rs b/src/gpio/dynpins.rs index cd5229b..8a1dd9f 100644 --- a/src/gpio/dynpins.rs +++ b/src/gpio/dynpins.rs @@ -57,9 +57,17 @@ //! operation, the trait functions will return //! [`InvalidPinType`](Error::InvalidPinType). -use super::pins::{FilterClkSel, FilterType, Pin, PinError, PinId, PinMode, PinState}; +use super::pins::{ + common_reg_if_functions, FilterType, InterruptEdge, InterruptLevel, Pin, PinError, PinId, + PinMode, PinState, +}; use super::reg::RegisterInterface; +use crate::{ + clock::FilterClkSel, + pac::{self, IRQSEL, SYSCONFIG}, +}; use embedded_hal::digital::v2::{InputPin, OutputPin, ToggleableOutputPin}; +use paste::paste; //================================================================================================== // DynPinMode configurations @@ -293,42 +301,7 @@ impl DynPin { self.into_mode(DYN_RD_OPEN_DRAIN_OUTPUT); } - #[inline] - pub fn datamask(&self) -> bool { - self.regs.datamask() - } - - #[inline] - pub fn clear_datamask(self) -> Self { - self.regs.clear_datamask(); - self - } - - #[inline] - pub fn set_datamask(self) -> Self { - self.regs.set_datamask(); - self - } - - #[inline] - pub fn is_high_masked(&self) -> Result { - self.regs.read_pin_masked() - } - - #[inline] - pub fn is_low_masked(&self) -> Result { - self.regs.read_pin_masked().map(|v| !v) - } - - #[inline] - pub fn set_high_masked(&mut self) -> Result<(), PinError> { - self.regs.write_pin_masked(true) - } - - #[inline] - pub fn set_low_masked(&mut self) -> Result<(), PinError> { - self.regs.write_pin_masked(false) - } + common_reg_if_functions!(); /// See p.53 of the programmers guide for more information. /// Possible delays in clock cycles: @@ -371,6 +344,40 @@ impl DynPin { } } + pub fn interrupt_edge( + mut self, + edge_type: InterruptEdge, + syscfg: Option<&mut SYSCONFIG>, + irqsel: &mut IRQSEL, + interrupt: pac::Interrupt, + ) -> Result { + match self.mode { + DynPinMode::Input(_) | DynPinMode::Output(_) => { + self._irq_enb(syscfg, irqsel, interrupt); + self.regs.interrupt_edge(edge_type); + Ok(self) + } + _ => Err(PinError::InvalidPinType), + } + } + + pub fn interrupt_level( + mut self, + level_type: InterruptLevel, + syscfg: Option<&mut SYSCONFIG>, + irqsel: &mut IRQSEL, + interrupt: crate::pac::Interrupt, + ) -> Result { + match self.mode { + DynPinMode::Input(_) | DynPinMode::Output(_) => { + self._irq_enb(syscfg, irqsel, interrupt); + self.regs.interrupt_level(level_type); + Ok(self) + } + _ => Err(PinError::InvalidPinType), + } + } + #[inline] fn _read(&self) -> Result { match self.mode { diff --git a/src/gpio/pins.rs b/src/gpio/pins.rs index 6656087..79d4a30 100644 --- a/src/gpio/pins.rs +++ b/src/gpio/pins.rs @@ -89,9 +89,11 @@ use super::dynpins::{DynAlternate, DynGroup, DynInput, DynOutput, DynPinId, DynPinMode}; use super::reg::RegisterInterface; -use crate::pac::{IOCONFIG, PORTA, PORTB, SYSCONFIG}; -use crate::typelevel::Is; -use crate::Sealed; +use crate::{ + pac::{self, IOCONFIG, IRQSEL, PORTA, PORTB, SYSCONFIG}, + typelevel::Is, + Sealed, +}; use core::convert::Infallible; use core::marker::PhantomData; use embedded_hal::digital::v2::{InputPin, OutputPin, ToggleableOutputPin}; @@ -101,6 +103,19 @@ use paste::paste; // Errors and Definitions //================================================================================================== +#[derive(Debug, PartialEq)] +pub enum InterruptEdge { + HighToLow, + LowToHigh, + BothEdges, +} + +#[derive(Debug, PartialEq)] +pub enum InterruptLevel { + Low = 0, + High = 1, +} + #[derive(Debug, PartialEq)] pub enum PinState { Low = 0, @@ -164,6 +179,7 @@ pub struct Input { impl Sealed for Input {} +#[derive(Debug, PartialEq)] pub enum FilterType { SystemClock = 0, DirectInputWithSynchronization = 1, @@ -173,16 +189,7 @@ pub enum FilterType { FilterFourClockCycles = 5, } -pub enum FilterClkSel { - SysClk = 0, - Clk1 = 1, - Clk2 = 2, - Clk3 = 3, - Clk4 = 4, - Clk5 = 5, - Clk6 = 6, - Clk7 = 7, -} +pub use crate::clock::FilterClkSel; //================================================================================================== // Output configuration @@ -347,6 +354,77 @@ impl AnyPin for Pin { type Mode = M; } +macro_rules! common_reg_if_functions { + () => { + paste!( + #[inline] + pub fn datamask(&self) -> bool { + self.regs.datamask() + } + + #[inline] + pub fn clear_datamask(self) -> Self { + self.regs.clear_datamask(); + self + } + + #[inline] + pub fn set_datamask(self) -> Self { + self.regs.set_datamask(); + self + } + + #[inline] + pub fn is_high_masked(&self) -> Result { + self.regs.read_pin_masked() + } + + #[inline] + pub fn is_low_masked(&self) -> Result { + self.regs.read_pin_masked().map(|v| !v) + } + + #[inline] + pub fn set_high_masked(&mut self) -> Result<(), PinError> { + self.regs.write_pin_masked(true) + } + + #[inline] + pub fn set_low_masked(&mut self) -> Result<(), PinError> { + self.regs.write_pin_masked(false) + } + + fn _irq_enb( + &mut self, + syscfg: Option<&mut va108xx::SYSCONFIG>, + irqsel: &mut va108xx::IRQSEL, + interrupt: va108xx::Interrupt, + ) { + if syscfg.is_some() { + crate::clock::enable_peripheral_clock( + syscfg.unwrap(), + crate::clock::PeripheralClocks::Irqsel, + ); + } + self.regs.enable_irq(); + match self.regs.id().group { + // Set the correct interrupt number in the IRQSEL register + DynGroup::A => { + irqsel.porta[self.regs.id().num as usize] + .write(|w| unsafe { w.bits(interrupt as u32) }); + } + DynGroup::B => { + irqsel.portb[self.regs.id().num as usize] + .write(|w| unsafe { w.bits(interrupt as u32) }); + } + } + } + ); + }; +} + +pub(crate) use common_reg_if_functions; + impl Pin { /// Create a new [`Pin`] /// @@ -429,42 +507,7 @@ impl Pin { self.into_mode() } - #[inline] - pub fn datamask(&self) -> bool { - self.regs.datamask() - } - - #[inline] - pub fn clear_datamask(self) -> Self { - self.regs.clear_datamask(); - self - } - - #[inline] - pub fn set_datamask(self) -> Self { - self.regs.set_datamask(); - self - } - - #[inline] - pub fn is_high_masked(&self) -> Result { - self.regs.read_pin_masked() - } - - #[inline] - pub fn is_low_masked(&self) -> Result { - self.regs.read_pin_masked().map(|v| !v) - } - - #[inline] - pub fn set_high_masked(&mut self) -> Result<(), PinError> { - self.regs.write_pin_masked(true) - } - - #[inline] - pub fn set_low_masked(&mut self) -> Result<(), PinError> { - self.regs.write_pin_masked(false) - } + common_reg_if_functions!(); #[inline] pub(crate) fn _set_high(&mut self) { @@ -526,6 +569,32 @@ impl AsMut for Pin { // Additional functionality //================================================================================================== +impl Pin> { + pub fn interrupt_edge( + mut self, + edge_type: InterruptEdge, + syscfg: Option<&mut SYSCONFIG>, + irqsel: &mut IRQSEL, + interrupt: pac::Interrupt, + ) -> Self { + self._irq_enb(syscfg, irqsel, interrupt); + self.regs.interrupt_edge(edge_type); + self + } + + pub fn interrupt_level( + mut self, + level_type: InterruptLevel, + syscfg: Option<&mut SYSCONFIG>, + irqsel: &mut IRQSEL, + interrupt: pac::Interrupt, + ) -> Self { + self._irq_enb(syscfg, irqsel, interrupt); + self.regs.interrupt_level(level_type); + self + } +} + impl Pin> { /// See p.53 of the programmers guide for more information. /// Possible delays in clock cycles: @@ -545,6 +614,30 @@ impl Pin> { self.regs.pulse_mode(enable, default_state); self } + + pub fn interrupt_edge( + mut self, + edge_type: InterruptEdge, + syscfg: Option<&mut SYSCONFIG>, + irqsel: &mut IRQSEL, + interrupt: pac::Interrupt, + ) -> Self { + self._irq_enb(syscfg, irqsel, interrupt); + self.regs.interrupt_edge(edge_type); + self + } + + pub fn interrupt_level( + mut self, + level_type: InterruptLevel, + syscfg: Option<&mut SYSCONFIG>, + irqsel: &mut IRQSEL, + interrupt: pac::Interrupt, + ) -> Self { + self._irq_enb(syscfg, irqsel, interrupt); + self.regs.interrupt_level(level_type); + self + } } impl Pin> { @@ -687,7 +780,7 @@ macro_rules! pins { )+ } - impl $PinsName{ + impl $PinsName { /// Create a new struct containing all the Pins. Passing the IOCONFIG peripheral /// is optional because it might be required to create pin definitions for both /// ports. diff --git a/src/gpio/reg.rs b/src/gpio/reg.rs index 72e2e76..9f3792e 100644 --- a/src/gpio/reg.rs +++ b/src/gpio/reg.rs @@ -1,5 +1,6 @@ use super::dynpins::{self, DynGroup, DynPinId, DynPinMode}; -use super::pins::{FilterClkSel, FilterType, PinError, PinState}; +use super::pins::{FilterType, InterruptEdge, InterruptLevel, PinError, PinState}; +use crate::clock::FilterClkSel; use va108xx::{ioconfig, porta, IOCONFIG, PORTA, PORTB}; /// Type definition to avoid confusion: These register blocks are identical @@ -31,16 +32,13 @@ impl From for ModeFields { use dynpins::DynInput::*; fields.dir = false; match config { - Floating => { - fields.pull_en = false; - } + Floating => (), PullUp => { fields.pull_en = true; fields.pull_dir = true; } PullDown => { fields.pull_en = true; - fields.pull_dir = false; } } } @@ -48,9 +46,7 @@ impl From for ModeFields { use dynpins::DynOutput::*; fields.dir = true; match config { - PushPull => { - fields.opendrn = false; - } + PushPull => (), OpenDrain => { fields.opendrn = true; } @@ -60,7 +56,6 @@ impl From for ModeFields { } ReadablePushPull => { fields.enb_input = true; - fields.opendrn = false; } } } @@ -154,6 +149,8 @@ pub(super) unsafe trait RegisterInterface { unsafe { if dir { portreg.dir().modify(|r, w| w.bits(r.bits() | mask)); + // Clear output + portreg.clrout().write(|w| w.bits(mask)); } else { portreg.dir().modify(|r, w| w.bits(r.bits() & !mask)); } @@ -200,6 +197,20 @@ pub(super) unsafe trait RegisterInterface { } } + #[inline] + fn enable_irq(&self) { + self.port_reg() + .irq_enb + .modify(|r, w| unsafe { w.bits(r.bits() | self.mask_32()) }); + } + + #[inline] + fn disable_irq(&self) { + self.port_reg() + .irq_enb + .modify(|r, w| unsafe { w.bits(r.bits() & !self.mask_32()) }); + } + #[inline] fn get_perid(&self) -> u32 { let portreg = self.port_reg(); @@ -273,6 +284,53 @@ pub(super) unsafe trait RegisterInterface { unsafe { self.port_reg().togout().write(|w| w.bits(self.mask_32())) }; } + /// Only useful for interrupt pins. Configure whether to use edges or level as interrupt soure + /// When using edge mode, it is possible to generate interrupts on both edges as well + #[inline] + fn interrupt_edge(&mut self, edge_type: InterruptEdge) { + unsafe { + self.port_reg() + .irq_sen + .modify(|r, w| w.bits(r.bits() & !self.mask_32())); + match edge_type { + InterruptEdge::HighToLow => { + self.port_reg() + .irq_evt + .modify(|r, w| w.bits(r.bits() & !self.mask_32())); + } + InterruptEdge::LowToHigh => { + self.port_reg() + .irq_evt + .modify(|r, w| w.bits(r.bits() | self.mask_32())); + } + InterruptEdge::BothEdges => { + self.port_reg() + .irq_edge + .modify(|r, w| w.bits(r.bits() | self.mask_32())); + } + } + } + } + + /// Configure which edge or level type triggers an interrupt + #[inline] + fn interrupt_level(&mut self, level: InterruptLevel) { + unsafe { + self.port_reg() + .irq_sen + .modify(|r, w| w.bits(r.bits() | self.mask_32())); + if level == InterruptLevel::Low { + self.port_reg() + .irq_evt + .modify(|r, w| w.bits(r.bits() & !self.mask_32())); + } else { + self.port_reg() + .irq_evt + .modify(|r, w| w.bits(r.bits() | self.mask_32())); + } + } + } + /// Only useful for input pins #[inline] fn filter_type(&self, filter: FilterType, clksel: FilterClkSel) { diff --git a/src/timer.rs b/src/timer.rs index 7b15c02..823e43b 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -5,7 +5,10 @@ //! - [MS and second tick implementation](https://github.com/robamu-org/va108xx-hal-rs/blob/main/examples/timer-ticks.rs) use crate::{ clock::{enable_peripheral_clock, PeripheralClocks}, + pac, + prelude::*, time::Hertz, + timer, }; use embedded_hal::timer::{Cancel, CountDown, Periodic}; use va108xx::{Interrupt, IRQSEL, SYSCONFIG}; @@ -160,6 +163,20 @@ macro_rules! timers { } } +// Set up a millisecond timer on TIM0. Please note that you still need to unmask the related IRQ +// and provide an IRQ handler yourself +pub fn set_up_ms_timer( + syscfg: &mut pac::SYSCONFIG, + irqsel: &mut pac::IRQSEL, + sys_clk: Hertz, + tim0: TIM0, + irq: pac::Interrupt, +) { + let mut ms_timer = CountDownTimer::tim0(syscfg, sys_clk, tim0); + ms_timer.listen(timer::Event::TimeOut, syscfg, irqsel, irq); + ms_timer.start(1000.hz()); +} + timers! { TIM0: (tim0, 0), TIM1: (tim1, 1), diff --git a/src/uart.rs b/src/uart.rs index 60355f5..0fb06c9 100644 --- a/src/uart.rs +++ b/src/uart.rs @@ -1,6 +1,7 @@ //! # API for the UART peripheral //! //! ## Examples +//! //! - [UART example](https://github.com/robamu-org/va108xx-hal-rs/blob/main/examples/uart.rs) use core::{convert::Infallible, ptr}; use core::{marker::PhantomData, ops::Deref};