diff --git a/CHANGELOG.md b/CHANGELOG.md index dc598a8..a8b663d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [0.2.0] +### Changed + +- New GPIO implementation which uses type-level programming. Implementation heavily based on the + ATSAMD GPIO HAL: https://docs.rs/atsamd-hal/0.13.0/atsamd_hal/gpio/v2/index.html +- Changes to API, therefore minor version bump + ### Added - UART implementation diff --git a/Cargo.toml b/Cargo.toml index 2d1459a..941e255 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "va108xx-hal" -version = "0.1.0" +version = "0.2.0" authors = ["Robin Mueller "] edition = "2021" description = "HAL for the Vorago VA108xx family of microcontrollers" @@ -14,6 +14,7 @@ categories = ["embedded", "no-std", "hardware-support"] cortex-m = "0.7" cortex-m-rt = "0.7" nb = "1" +paste = "1.0" embedded-hal = { features = ["unproven"], version = "0.2.6" } void = { version = "1.0", default-features = false } once_cell = { version = "1.8.0", default-features = false } diff --git a/README.md b/README.md index 309aa75..263aee0 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ is contained within the embedded-hal = "0.2.6" [dependencies.va108xx-hal] - version = "0.1" + version = "0.2" features = ["rt"] ``` diff --git a/examples/blinky.rs b/examples/blinky.rs index 8eab007..fda0262 100644 --- a/examples/blinky.rs +++ b/examples/blinky.rs @@ -9,21 +9,15 @@ use cortex_m_rt::entry; use embedded_hal::digital::v2::ToggleableOutputPin; use panic_halt as _; -use va108xx_hal::{pac, prelude::*}; +use va108xx_hal::{gpio::PinsA, pac, prelude::*}; #[entry] fn main() -> ! { let mut dp = pac::Peripherals::take().unwrap(); - let porta = dp.PORTA.split(&mut dp.SYSCONFIG).unwrap(); - let mut led1 = porta - .pa10 - .into_push_pull_output(&mut dp.IOCONFIG, &mut dp.PORTA); - let mut led2 = porta - .pa7 - .into_push_pull_output(&mut dp.IOCONFIG, &mut dp.PORTA); - let mut led3 = porta - .pa6 - .into_push_pull_output(&mut dp.IOCONFIG, &mut dp.PORTA); + let porta = PinsA::new(&mut dp.SYSCONFIG, Some(dp.IOCONFIG), dp.PORTA); + let mut led1 = porta.pa10.into_push_pull_output(); + let mut led2 = porta.pa7.into_push_pull_output(); + let mut led3 = porta.pa6.into_push_pull_output(); for _ in 0..10 { led1.set_low().ok(); led2.set_low().ok(); diff --git a/examples/tests.rs b/examples/tests.rs index 6fca5a3..12172b9 100644 --- a/examples/tests.rs +++ b/examples/tests.rs @@ -6,11 +6,10 @@ #![no_std] use cortex_m_rt::entry; -use embedded_hal::digital::v2::{InputPin, OutputPin, StatefulOutputPin, ToggleableOutputPin}; +use embedded_hal::digital::v2::{InputPin, OutputPin, ToggleableOutputPin}; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; -use va108xx_hal::gpio::{porta, portb, PinState}; -use va108xx_hal::prelude::*; +use va108xx_hal::gpio::{PinState, PinsA, PinsB}; #[allow(dead_code)] #[derive(Debug)] @@ -20,6 +19,8 @@ enum TestCase { TestPullup, TestPulldown, TestMask, + // Tie PORTB[22] to PORTB[23] for this test + PortB, Perid, // Tie PA0 to an oscilloscope and configure pulse detection Pulse, @@ -32,11 +33,9 @@ fn main() -> ! { rtt_init_print!(); rprintln!("-- VA108xx Test Application --"); let mut dp = va108xx::Peripherals::take().unwrap(); - let porta = dp.PORTA.split(&mut dp.SYSCONFIG).unwrap(); - let _portb = dp.PORTB.split(&mut dp.SYSCONFIG).unwrap(); - let mut led1 = porta - .pa10 - .into_push_pull_output(&mut dp.IOCONFIG, &mut 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 mut led1 = pinsa.pa10.into_push_pull_output(); let test_case = TestCase::Delay; match test_case { @@ -56,82 +55,70 @@ fn main() -> ! { match test_case { TestCase::TestBasic => { // Tie PORTA[0] to PORTA[1] for these tests! - let mut out = porta - .pa0 - .into_push_pull_output(&mut dp.IOCONFIG, &mut dp.PORTA) - .enable_input(&mut dp.IOCONFIG, true); - let input = porta - .pa1 - .into_floating_input(&mut dp.IOCONFIG, &mut dp.PORTA); + let mut out = pinsa.pa0.into_readable_push_pull_output(); + let input = pinsa.pa1.into_floating_input(); out.set_high().unwrap(); - assert!(out.is_set_high().unwrap()); assert!(input.is_high().unwrap()); out.set_low().unwrap(); - assert!(out.is_set_low().unwrap()); assert!(input.is_low().unwrap()); } TestCase::TestPullup => { // Tie PORTA[0] to PORTA[1] for these tests! - let input = porta - .pa1 - .into_pull_up_input(&mut dp.IOCONFIG, &mut dp.PORTA); + let input = pinsa.pa1.into_pull_up_input(); assert!(input.is_high().unwrap()); - let mut out = porta - .pa0 - .into_push_pull_output(&mut dp.IOCONFIG, &mut dp.PORTA); + let mut out = pinsa.pa0.into_readable_push_pull_output(); out.set_low().unwrap(); assert!(input.is_low().unwrap()); out.set_high().unwrap(); assert!(input.is_high().unwrap()); - out.into_floating_input(&mut dp.IOCONFIG, &mut dp.PORTA); + out.into_floating_input(); assert!(input.is_high().unwrap()); } TestCase::TestPulldown => { // Tie PORTA[0] to PORTA[1] for these tests! - let input = porta - .pa1 - .into_pull_down_input(&mut dp.IOCONFIG, &mut dp.PORTA); + let input = pinsa.pa1.into_pull_down_input(); assert!(input.is_low().unwrap()); - let mut out = porta - .pa0 - .into_push_pull_output(&mut dp.IOCONFIG, &mut dp.PORTA); + let mut out = pinsa.pa0.into_push_pull_output(); out.set_low().unwrap(); assert!(input.is_low().unwrap()); out.set_high().unwrap(); assert!(input.is_high().unwrap()); - out.into_floating_input(&mut dp.IOCONFIG, &mut dp.PORTA); + out.into_floating_input(); assert!(input.is_low().unwrap()); } TestCase::TestMask => { // Tie PORTA[0] to PORTA[1] for these tests! - let input = porta - .pa1 - .into_pull_down_input(&mut dp.IOCONFIG, &mut dp.PORTA) - .clear_datamask(&mut dp.PORTA); - assert!(!input.datamask(&dp.PORTA)); - let out = porta - .pa0 - .into_push_pull_output(&mut dp.IOCONFIG, &mut dp.PORTA) - .clear_datamask(&mut dp.PORTA); - assert!(input.is_low_masked(&mut dp.PORTA).is_err()); - assert!(out.set_high_masked(&mut dp.PORTA).is_err()); + let input = pinsa.pa1.into_pull_down_input().clear_datamask(); + assert!(!input.datamask()); + let mut out = pinsa.pa0.into_push_pull_output().clear_datamask(); + assert!(input.is_low_masked().is_err()); + assert!(out.set_high_masked().is_err()); + } + TestCase::PortB => { + // Tie PORTB[22] to PORTB[23] for these tests! + let mut out = pinsb.pb22.into_readable_push_pull_output(); + let input = pinsb.pb23.into_floating_input(); + out.set_high().unwrap(); + assert!(input.is_high().unwrap()); + out.set_low().unwrap(); + assert!(input.is_low().unwrap()); } TestCase::Perid => { - assert_eq!(porta::get_perid(&dp.PORTA), 0x004007e1); - assert_eq!(portb::get_perid(&dp.PORTB), 0x004007e1); + assert_eq!(PinsA::get_perid(), 0x004007e1); + assert_eq!(PinsB::get_perid(), 0x004007e1); } TestCase::Pulse => { - let mut output_pulsed = porta + let mut output_pulsed = pinsa .pa0 - .into_push_pull_output(&mut dp.IOCONFIG, &mut dp.PORTA) - .pulse_mode(&mut dp.PORTA, true, PinState::Low); + .into_push_pull_output() + .pulse_mode(true, PinState::Low); rprintln!("Pulsing high 10 times.."); output_pulsed.set_low().unwrap(); for _ in 0..10 { output_pulsed.set_high().unwrap(); cortex_m::asm::delay(25_000_000); } - let mut output_pulsed = output_pulsed.pulse_mode(&mut dp.PORTA, true, PinState::High); + let mut output_pulsed = output_pulsed.pulse_mode(true, PinState::High); rprintln!("Pulsing low 10 times.."); for _ in 0..10 { output_pulsed.set_low().unwrap(); @@ -139,18 +126,9 @@ fn main() -> ! { } } TestCase::Delay => { - let mut out_0 = porta - .pa0 - .into_push_pull_output(&mut dp.IOCONFIG, &mut dp.PORTA) - .delay(&mut dp.PORTA, true, false); - let mut out_1 = porta - .pa1 - .into_push_pull_output(&mut dp.IOCONFIG, &mut dp.PORTA) - .delay(&mut dp.PORTA, false, true); - let mut out_2 = porta - .pa3 - .into_push_pull_output(&mut dp.IOCONFIG, &mut dp.PORTA) - .delay(&mut dp.PORTA, true, true); + 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); for _ in 0..20 { out_0.toggle().unwrap(); out_1.toggle().unwrap(); diff --git a/examples/uart.rs b/examples/uart.rs index 01a06d3..1aa72dd 100644 --- a/examples/uart.rs +++ b/examples/uart.rs @@ -7,18 +7,18 @@ use core::fmt::Write; use cortex_m_rt::entry; use panic_rtt_target as _; use rtt_target::{rprintln, rtt_init_print}; -use va108xx_hal::{pac, prelude::*, uart}; +use va108xx_hal::{gpio::PinsB, pac, prelude::*, uart}; #[entry] fn main() -> ! { rtt_init_print!(); - rprintln!("-- VA108xx UART test application--"); + rprintln!("-- VA108xx UART example application--"); let mut dp = pac::Peripherals::take().unwrap(); - let gpiob = dp.PORTB.split(&mut dp.SYSCONFIG).unwrap(); - let tx = gpiob.pb21.into_funsel_1(&mut dp.IOCONFIG); - let rx = gpiob.pb20.into_funsel_1(&mut dp.IOCONFIG); + let gpiob = PinsB::new(&mut dp.SYSCONFIG, Some(dp.IOCONFIG), dp.PORTB); + let tx = gpiob.pb21.into_funsel_1(); + let rx = gpiob.pb20.into_funsel_1(); let uartb = uart::Uart::uartb( dp.UARTB, diff --git a/src/gpio.rs b/src/gpio.rs deleted file mode 100644 index 701aeef..0000000 --- a/src/gpio.rs +++ /dev/null @@ -1,663 +0,0 @@ -//! API for the GPIO pins -//! -//! ## Examples -//! -//! - [Blinky example](https://github.com/robamu-org/va108xx-hal-rs/blob/main/examples/blinky.rs) -use crate::pac::SYSCONFIG; -use core::convert::Infallible; -use core::marker::PhantomData; -use cortex_m::singleton; -use embedded_hal::digital::v2::{InputPin, OutputPin, StatefulOutputPin, ToggleableOutputPin}; -use va108xx::IOCONFIG; - -/// Extension trait to split a GPIO peripheral in independent pins and registers -pub trait GpioExt { - /// The parts to split the GPIO into. - type Parts; - - /// Splits the GPIO block into independent pins and registers. - fn split(&mut self, syscfg: &mut SYSCONFIG) -> Option; -} - -#[derive(Debug, PartialEq)] -pub enum PinModeError { - InputDisabledForOutput, - IsMasked, -} - -#[derive(Debug, PartialEq)] -pub enum PinState { - Low = 0, - High = 1, -} - -enum PortId { - A, - B, -} - -trait GpioRegExt { - fn is_low(&self, pos: u8) -> bool; - fn is_set_low(&self, pos: usize, port_id: &PortId) -> Result; - fn input_enabled_for_output(&self, pos: usize, port_id: &PortId) -> bool; - fn set_high(&self, pos: u8); - fn set_low(&self, pos: u8); - fn toggle(&self, pos: u8); -} - -/// Input mode (type state) -pub struct Input { - _mode: PhantomData, -} - -/// Output mode (type state) -pub struct Output { - _mode: PhantomData, -} - -/// Floating input (type state) -pub struct Floating; -/// Pulled down input (type state) -pub struct PullDown; -/// Pulled up input (type state) -pub struct PullUp; -/// Open drain output (type state) -pub struct OpenDrain; -// Push-pull output (type state) -pub struct PushPull; - -pub struct OutputInverted; - -pub struct InputInverted; - -// FUNSEL0 is the regular GPIO port configuration -pub struct FUNSEL1; -pub struct FUNSEL2; -pub struct FUNSEL3; - -/// Function select (type state) -pub struct AltFunc { - _mode: PhantomData, -} - -pub enum FilterType { - SystemClock = 0, - DirectInputWithSynchronization = 1, - FilterOneClockCycle = 2, - FilterTwoClockCycles = 3, - FilterThreeClockCycles = 4, - FilterFourClockCycles = 5, -} - -pub enum FilterClkSel { - SysClk = 0, - Clk1 = 1, - Clk2 = 2, - Clk3 = 3, - Clk4 = 4, - Clk5 = 5, - Clk6 = 6, - Clk7 = 7, -} - -/// Fully erased pin -pub struct Pin { - i: u8, - port_id: PortId, - port: *const dyn GpioRegExt, - _mode: PhantomData, -} -// NOTE(unsafe) this only enables read access to the same pin from multiple threads -unsafe impl Send for Pin {} -impl StatefulOutputPin for Pin> { - #[inline(always)] - fn is_set_high(&self) -> Result { - self.is_set_low().map(|v| !v) - } - #[inline(always)] - fn is_set_low(&self) -> Result { - unsafe { (*self.port).is_set_low(self.i.into(), &self.port_id) } - } -} -impl OutputPin for Pin> { - type Error = PinModeError; - #[inline(always)] - fn set_high(&mut self) -> Result<(), Self::Error> { - unsafe { (*self.port).set_high(self.i) }; - Ok(()) - } - #[inline(always)] - fn set_low(&mut self) -> Result<(), Self::Error> { - unsafe { (*self.port).set_low(self.i) } - Ok(()) - } -} - -impl ToggleableOutputPin for Pin> { - type Error = Infallible; - #[inline(always)] - fn toggle(&mut self) -> Result<(), Self::Error> { - unsafe { (*self.port).toggle(self.i) } - Ok(()) - } -} - -impl InputPin for Pin> { - type Error = Infallible; - #[inline(always)] - fn is_high(&self) -> Result { - self.is_low().map(|v| !v) - } - #[inline(always)] - fn is_low(&self) -> Result { - Ok(unsafe { (*self.port).is_low(self.i) }) - } -} -impl InputPin for Pin> { - type Error = Infallible; - #[inline(always)] - fn is_high(&self) -> Result { - self.is_low().map(|v| !v) - } - #[inline(always)] - fn is_low(&self) -> Result { - Ok(unsafe { (*self.port).is_low(self.i) }) - } -} - -// This is only needs to be implemented for PORTA because PORTB is derived -// from PORTA -impl GpioRegExt for crate::pac::porta::RegisterBlock { - #[inline(always)] - fn is_low(&self, pos: u8) -> bool { - self.datainraw().read().bits() & (1 << pos) == 0 - } - #[inline(always)] - fn is_set_low(&self, pos: usize, port_id: &PortId) -> Result { - if self.input_enabled_for_output(pos, port_id) { - Ok(self.datainraw().read().bits() & (1 << pos) == 0) - } else { - Err(PinModeError::InputDisabledForOutput) - } - } - #[inline(always)] - fn set_high(&self, pos: u8) { - unsafe { self.setout().write(|w| w.bits(1 << pos)) } - } - #[inline(always)] - fn set_low(&self, pos: u8) { - unsafe { self.clrout().write(|w| w.bits(1 << pos)) } - } - #[inline(always)] - fn toggle(&self, pos: u8) { - unsafe { self.togout().write(|w| w.bits(1 << pos)) } - } - #[inline(always)] - fn input_enabled_for_output(&self, pos: usize, port_id: &PortId) -> bool { - unsafe { - let iocfg = &(*IOCONFIG::ptr()); - match port_id { - PortId::A => iocfg.porta[pos].read().iewo().bit_is_set(), - PortId::B => iocfg.portb[pos].read().iewo().bit_is_set(), - } - } - } -} - -macro_rules! gpio { - ($PORTX:ident, $portx:ident, $port_id:path, [ - $($PXi:ident: ($pxi:ident, $i:expr),)+ - ]) => { - pub mod $portx { - use core::marker::PhantomData; - use core::convert::Infallible; - use super::{ - FUNSEL1, FUNSEL2, FUNSEL3, Floating, AltFunc, GpioExt, Input, OpenDrain, - PullUp, Output, FilterType, FilterClkSel, Pin, GpioRegExt, PushPull, - PinModeError, PinState, PortId, singleton - }; - use crate::{pac::$PORTX, pac::SYSCONFIG, pac::IOCONFIG}; - use embedded_hal::digital::v2::{ - InputPin, OutputPin, StatefulOutputPin, ToggleableOutputPin - }; - pub struct Parts { - $( - pub $pxi: $PXi>, - )+ - } - - pub fn get_perid(port: &$PORTX) -> u32 { - port.perid.read().bits() - } - - impl GpioExt for $PORTX { - type Parts = Parts; - - /// This function splits the PORT into the individual pins - /// Should only be called once and returns None on subsequent calls - fn split(&mut self, syscfg: &mut SYSCONFIG) -> Option { - let _: &'static mut bool = singleton!(: bool = false)?; - syscfg.peripheral_clk_enable.modify(|_, w| { - w.$portx().set_bit(); - w.gpio().set_bit(); - w.ioconfig().set_bit(); - w - }); - Some(Parts { - $( - $pxi: $PXi { _mode : PhantomData }, - )+ - }) - } - } - - fn _set_alternate_mode(iocfg: &mut IOCONFIG, index: usize, mode: u8) { - iocfg.$portx[index].modify(|_, w| unsafe { - w.funsel().bits(mode) - }); - } - - $( - pub struct $PXi { - _mode: PhantomData, - } - - impl $PXi { - pub fn into_funsel_1(self, iocfg: &mut IOCONFIG) -> $PXi> { - _set_alternate_mode(iocfg, $i, 1); - $PXi { _mode: PhantomData } - } - pub fn into_funsel_2(self, iocfg: &mut IOCONFIG) -> $PXi> { - _set_alternate_mode(iocfg, $i, 2); - $PXi { _mode: PhantomData } - } - pub fn into_funsel_3(self, iocfg: &mut IOCONFIG) -> $PXi> { - _set_alternate_mode(iocfg, $i, 3); - $PXi { _mode: PhantomData } - } - - // Get DATAMASK bit for this particular pin - #[inline(always)] - pub fn datamask(&self, port: &$PORTX) -> bool { - (port.datamask().read().bits() >> $i) == 1 - } - /// Set DATAMASK bit for this particular pin. 1 is the default - /// state of the bit and allows access of the corresponding bit - #[inline(always)] - pub fn set_datamask(self, port: &mut $PORTX) -> Self { - unsafe { - port.datamask().modify(|r, w| w.bits(r.bits() | (1 << $i))) - } - self - } - - /// Clear DATAMASK bit for this particular pin. This prevents access - /// of the corresponding bit for output and input operations - #[inline(always)] - pub fn clear_datamask(self, port: &mut $PORTX) -> Self { - unsafe { - port.datamask().modify(|r, w| w.bits(r.bits() & !(1 << $i))) - } - self - } - - pub fn into_floating_input( - self, iocfg: &mut IOCONFIG, port: &mut $PORTX - ) -> $PXi> { - unsafe { - iocfg.$portx[$i].modify(|_, w| { - w.funsel().bits(0); - w.pen().clear_bit(); - w.opendrn().clear_bit() - }); - port.dir().modify(|r,w| w.bits(r.bits() & !(1 << $i))); - } - $PXi { _mode: PhantomData } - } - - pub fn into_pull_up_input(self, iocfg: &mut IOCONFIG, port: &mut $PORTX) -> $PXi> { - unsafe { - iocfg.$portx[$i].modify(|_, w| { - w.funsel().bits(0); - w.pen().set_bit(); - w.plevel().set_bit(); - w.opendrn().clear_bit() - }); - port.dir().modify(|r,w| w.bits(r.bits() & !(1 << $i))); - } - $PXi { _mode: PhantomData } - } - - pub fn into_pull_down_input( - self, iocfg: &mut IOCONFIG, port: &mut $PORTX - ) -> $PXi> { - unsafe { - iocfg.$portx[$i].modify(|_, w| { - w.funsel().bits(0); - w.pen().set_bit(); - w.plevel().clear_bit(); - w.opendrn().clear_bit() - }); - port.dir().modify(|r,w| w.bits(r.bits() & !(1 << $i))); - } - $PXi { _mode: PhantomData } - } - - pub fn into_open_drain_output( - self, iocfg: &mut IOCONFIG - ) -> $PXi> { - unsafe { - iocfg.$portx[$i].modify(|_, w| { - w.funsel().bits(0); - w.pen().clear_bit(); - w.opendrn().set_bit() - }); - let port_reg = &(*$PORTX::ptr()); - port_reg.dir().modify(|r,w| w.bits(r.bits() | (1 << $i))); - } - $PXi { _mode: PhantomData } - } - - pub fn into_push_pull_output( - self, iocfg: &mut IOCONFIG, port_reg: &mut $PORTX - ) -> $PXi> { - unsafe { - iocfg.$portx[$i].modify(|_, w| { - w.funsel().bits(0); - w.opendrn().clear_bit() - }); - port_reg.dir().modify(|r,w| w.bits(r.bits() | (1 << $i))); - port_reg.clrout().write(|w| w.bits(1 << $i)) - } - $PXi { _mode: PhantomData } - } - - pub fn filter_type( - self, iocfg: &mut IOCONFIG, filter: FilterType, clksel: FilterClkSel - ) -> Self { - unsafe { - iocfg.$portx[$i].modify(|_, w| { - w.flttype().bits(filter as u8); - w.fltclk().bits(clksel as u8) - }); - } - self - } - } - - impl $PXi> { - pub fn input_inversion(self, iocfg: &mut IOCONFIG, enable: bool) -> Self { - if enable { - iocfg.$portx[$i].modify(|_, w| w.invinp().set_bit()); - } else { - iocfg.$portx[$i].modify(|_, w| w.invinp().clear_bit()); - } - self - } - - /// This function takes account for the data mask register - #[inline(always)] - pub fn is_high_masked(&self, port: &$PORTX) -> Result { - self.is_low_masked(port).map(|v| !v) - } - - /// This function takes account for the data mask register - #[inline(always)] - pub fn is_low_masked(&self, port: &$PORTX) -> Result { - if ((port.datamask().read().bits() >> $i) & 1) == 0 { - Err(PinModeError::IsMasked) - } else { - Ok(port.datain().read().bits() & (1 << $i) == 0) - } - } - } - - impl $PXi> { - pub fn output_inversion(self, iocfg: &mut IOCONFIG, enable: bool) -> Self { - if enable { - iocfg.$portx[$i].modify(|_, w| w.invout().set_bit()); - } else { - iocfg.$portx[$i].modify(|_, w| w.invout().clear_bit()); - } - self - } - - /// Enable Input even when in output mode. In - /// this mode the input receiver is enabled even - /// if the direction is configured as an output. - /// This allows monitoring of output values - pub fn enable_input(self, iocfg: &mut IOCONFIG, enable: bool) -> Self { - if enable { - iocfg.$portx[$i].modify(|_, w| w.iewo().set_bit()); - } else { - iocfg.$portx[$i].modify(|_, w| w.iewo().clear_bit()); - } - self - } - - /// Enable Pull up/down even when output is active. The Default is to - /// disable pull up/down when output is actively driven. This bit enables the - /// pull up/down all the time. - /// - /// # Arguments - /// - /// `enable` - Enable the peripheral functionality - /// `enable_pullup` - Enable the pullup itself - pub fn enable_pull_up( - self, iocfg: &mut IOCONFIG, enable: bool, enable_pullup: bool - ) -> Self { - iocfg.$portx[$i].modify(|_, w| { - if enable { w.pwoa().set_bit(); } else { w.pwoa().clear_bit(); } - if enable_pullup { w.pen().set_bit(); } else { w.pen().clear_bit(); } - w.plevel().set_bit() - }); - self - } - /// Enable Pull up/down even when output is active. The Default is to disable - /// pull up/down when output is actively driven. This bit enables the - /// pull up/down all the time. - /// - /// # Arguments - /// - /// `enable` - Enable the peripheral functionality - /// `enable_pullup` - Enable the pulldown itself - pub fn enable_pull_down( - self, iocfg: &mut IOCONFIG, enable: bool, enable_pulldown: bool - ) -> Self { - iocfg.$portx[$i].modify(|_, w| { - if enable { w.pwoa().set_bit(); } else { w.pwoa().clear_bit(); } - if enable_pulldown { w.pen().set_bit(); } else { w.pen().clear_bit(); } - w.plevel().clear_bit() - }); - self - } - } - - impl $PXi> { - /// Erases the pin number from the type - /// - /// This is useful when you want to collect the pins into an array where you - /// need all the elements to have the same type - pub fn downgrade(self) -> Pin> { - Pin { - i: $i, - port_id: $port_id, - port: $PORTX::ptr() as *const dyn GpioRegExt, - _mode: self._mode, - } - } - - /// This function takes account for the data mask register - #[inline(always)] - pub fn set_low_masked(&self, port: &$PORTX) -> Result<(), PinModeError> { - if ((port.datamask().read().bits() >> $i) & 1) == 0 { - Err(PinModeError::IsMasked) - } else { - Ok(unsafe { (*$PORTX::ptr()).set_low($i) }) - } - } - /// This function takes account for the data mask register - #[inline(always)] - pub fn set_high_masked(&self, port: &$PORTX) -> Result<(), PinModeError> { - if ((port.datamask().read().bits() >> $i) & 1) == 0 { - Err(PinModeError::IsMasked) - } else { - Ok(unsafe { (*$PORTX::ptr()).set_high($i) }) - } - } - - pub fn pulse_mode(self, port: &mut $PORTX, enable: bool, default_state: PinState) -> Self { - unsafe { - if enable { - port.pulse().modify(|r, w| w.bits(r.bits() | (1 << $i))); - } else { - port.pulse().modify(|r, w| w.bits(r.bits() & !(1 << $i))); - } - if default_state == PinState::Low { - port.pulsebase().modify(|r,w| w.bits(r.bits() & !(1 << $i))); - } - else { - port.pulsebase().modify(|r,w| w.bits(r.bits() | (1 << $i))); - } - } - self - } - - pub fn delay(self, port: &mut $PORTX, delay_1: bool, delay_2: bool) -> Self { - unsafe { - if delay_1 { - port.delay1().modify(|r, w| w.bits(r.bits() | (1 << $i))); - } else { - port.delay1().modify(|r, w| w.bits(r.bits() & !(1 << $i))); - } - if delay_2 { - port.delay2().modify(|r, w| w.bits(r.bits() | (1 << $i))); - } else { - port.delay2().modify(|r, w| w.bits(r.bits() & !(1 << $i))); - } - } - self - } - } - - impl StatefulOutputPin for $PXi> { - #[inline(always)] - fn is_set_high(&self) -> Result { - self.is_set_low().map(|v| !v) - } - #[inline(always)] - fn is_set_low(&self) -> Result { - unsafe { - (*$PORTX::ptr()).is_set_low($i, &$port_id) - } - } - } - impl OutputPin for $PXi> { - type Error = PinModeError; - #[inline(always)] - fn set_high(&mut self) -> Result<(), Self::Error> { - Ok(unsafe { (*$PORTX::ptr()).set_high($i) }) - } - #[inline(always)] - fn set_low(&mut self) -> Result<(), Self::Error> { - Ok(unsafe { (*$PORTX::ptr()).set_low($i) }) - } - } - impl ToggleableOutputPin for $PXi> { - type Error = Infallible; - #[inline(always)] - fn toggle(&mut self) -> Result<(), Self::Error> { - Ok(unsafe { (*$PORTX::ptr()).toggle($i) }) - } - } - impl $PXi> { - /// Erases the pin number from the type - /// - /// This is useful when you want to collect the pins into an array where you - /// need all the elements to have the same type - pub fn downgrade(self) -> Pin> { - Pin { - i: $i, - port_id: $port_id, - port: $PORTX::ptr() as *const dyn GpioRegExt, - _mode: self._mode, - } - } - } - impl InputPin for $PXi> { - type Error = Infallible; - #[inline(always)] - fn is_high(&self) -> Result { - self.is_low().map(|v| !v) - } - #[inline(always)] - fn is_low(&self) -> Result { - Ok(unsafe { (*$PORTX::ptr()).is_low($i) }) - } - } - )+ - } - } -} - -gpio!(PORTA, porta, PortId::A, [ - PA0: (pa0, 0), - PA1: (pa1, 1), - PA2: (pa2, 2), - PA3: (pa3, 3), - PA4: (pa4, 4), - PA5: (pa5, 5), - PA6: (pa6, 6), - PA7: (pa7, 7), - PA8: (pa8, 8), - PA9: (pa9, 9), - PA10: (pa10, 10), - PA11: (pa11, 11), - PA12: (pa12, 12), - PA13: (pa13, 13), - PA14: (pa14, 14), - PA15: (pa15, 15), - PA16: (pa16, 16), - PA17: (pa17, 17), - PA18: (pa18, 18), - PA19: (pa19, 19), - PA20: (pa20, 20), - PA21: (pa21, 21), - PA22: (pa22, 22), - PA23: (pa23, 23), - PA24: (pa24, 24), - PA25: (pa25, 25), - PA26: (pa26, 26), - PA27: (pa27, 27), - PA28: (pa28, 28), - PA29: (pa29, 29), - PA30: (pa30, 30), - PA31: (pa31, 31), -]); - -gpio!(PORTB, portb, PortId::B, [ - PB0: (pb0, 0), - PB1: (pb1, 1), - PB2: (pb2, 2), - PB3: (pb3, 3), - PB4: (pb4, 4), - PB5: (pb5, 5), - PB6: (pb6, 6), - PB7: (pb7, 7), - PB8: (pb8, 8), - PB9: (pb9, 9), - PB10: (pb10, 10), - PB11: (pb11, 11), - PB12: (pb12, 12), - PB13: (pb13, 13), - PB14: (pb14, 14), - PB15: (pb15, 15), - PB16: (pb16, 16), - PB17: (pb17, 17), - PB18: (pb18, 18), - PB19: (pb19, 19), - PB20: (pb20, 20), - PB21: (pb21, 21), - PB22: (pb22, 22), - PB23: (pb23, 23), -]); diff --git a/src/gpio/dynpins.rs b/src/gpio/dynpins.rs new file mode 100644 index 0000000..cd5229b --- /dev/null +++ b/src/gpio/dynpins.rs @@ -0,0 +1,491 @@ +//! # Type-erased, value-level module for GPIO pins +//! +//! Although the type-level API is generally preferred, it is not suitable in +//! all cases. Because each pin is represented by a distinct type, it is not +//! possible to store multiple pins in a homogeneous data structure. The +//! value-level API solves this problem by erasing the type information and +//! tracking the pin at run-time. +//! +//! Value-level pins are represented by the [`DynPin`] type. [`DynPin`] has two +//! fields, `id` and `mode` with types [`DynPinId`] and [`DynPinMode`] +//! respectively. The implementation of these types closely mirrors the +//! type-level API. +//! +//! Instances of [`DynPin`] cannot be created directly. Rather, they must be +//! created from their type-level equivalents using [`From`]/[`Into`]. +//! +//! ``` +//! // Move a pin out of the Pins struct and convert to a DynPin +//! let pa0: DynPin = pins.pa0.into(); +//! ``` +//! +//! Conversions between pin modes use a value-level version of the type-level +//! API. +//! +//! ``` +//! // Use one of the literal function names +//! pa0.into_floating_input(); +//! // Use a method and a DynPinMode variant +//! pa0.into_mode(DYN_FLOATING_INPUT); +//! ``` +//! +//! Because the pin state cannot be tracked at compile-time, many [`DynPin`] +//! operations become fallible. Run-time checks are inserted to ensure that +//! users don't try to, for example, set the output level of an input pin. +//! +//! Users may try to convert value-level pins back to their type-level +//! equivalents. However, this option is fallible, because the compiler cannot +//! guarantee the pin has the correct ID or is in the correct mode at +//! compile-time. Use [`TryFrom`](core::convert::TryFrom)/ +//! [`TryInto`](core::convert::TryInto) for this conversion. +//! +//! ``` +//! // Convert to a `DynPin` +//! let pa0: DynPin = pins.pa0.into(); +//! // Change pin mode +//! pa0.into_floating_input(); +//! // Convert back to a `Pin` +//! let pa0: Pin = pa0.try_into().unwrap(); +//! ``` +//! +//! # Embedded HAL traits +//! +//! This module implements all of the embedded HAL GPIO traits for [`DynPin`]. +//! However, whereas the type-level API uses +//! `Error = core::convert::Infallible`, the value-level API can return a real +//! error. If the [`DynPin`] is not in the correct [`DynPinMode`] for the +//! operation, the trait functions will return +//! [`InvalidPinType`](Error::InvalidPinType). + +use super::pins::{FilterClkSel, FilterType, Pin, PinError, PinId, PinMode, PinState}; +use super::reg::RegisterInterface; +use embedded_hal::digital::v2::{InputPin, OutputPin, ToggleableOutputPin}; + +//================================================================================================== +// DynPinMode configurations +//================================================================================================== + +/// Value-level `enum` for disabled configurations +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum DynDisabled { + Floating, + PullDown, + PullUp, +} + +/// Value-level `enum` for input configurations +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum DynInput { + Floating, + PullDown, + PullUp, +} + +/// Value-level `enum` for output configurations +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum DynOutput { + PushPull, + OpenDrain, + ReadablePushPull, + ReadableOpenDrain, +} + +/// Value-level `enum` for alternate peripheral function configurations +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum DynAlternate { + Funsel1, + Funsel2, + Funsel3, +} + +//================================================================================================== +// DynPinMode +//================================================================================================== + +/// Value-level `enum` representing pin modes +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum DynPinMode { + Input(DynInput), + Output(DynOutput), + Alternate(DynAlternate), +} + +/// Value-level variant of [`DynPinMode`] for floating input mode +pub const DYN_FLOATING_INPUT: DynPinMode = DynPinMode::Input(DynInput::Floating); +/// Value-level variant of [`DynPinMode`] for pull-down input mode +pub const DYN_PULL_DOWN_INPUT: DynPinMode = DynPinMode::Input(DynInput::PullDown); +/// Value-level variant of [`DynPinMode`] for pull-up input mode +pub const DYN_PULL_UP_INPUT: DynPinMode = DynPinMode::Input(DynInput::PullUp); + +/// Value-level variant of [`DynPinMode`] for push-pull output mode +pub const DYN_PUSH_PULL_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::PushPull); +/// Value-level variant of [`DynPinMode`] for open-drain output mode +pub const DYN_OPEN_DRAIN_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::OpenDrain); +/// Value-level variant of [`DynPinMode`] for readable push-pull output mode +pub const DYN_RD_PUSH_PULL_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::ReadablePushPull); +/// Value-level variant of [`DynPinMode`] for readable opendrain output mode +pub const DYN_RD_OPEN_DRAIN_OUTPUT: DynPinMode = DynPinMode::Output(DynOutput::ReadableOpenDrain); + +/// Value-level variant of [`DynPinMode`] for function select 1 +pub const DYN_ALT_FUNC_1: DynPinMode = DynPinMode::Alternate(DynAlternate::Funsel1); +/// Value-level variant of [`DynPinMode`] for function select 2 +pub const DYN_ALT_FUNC_2: DynPinMode = DynPinMode::Alternate(DynAlternate::Funsel2); +/// Value-level variant of [`DynPinMode`] for function select 3 +pub const DYN_ALT_FUNC_3: DynPinMode = DynPinMode::Alternate(DynAlternate::Funsel3); + +//================================================================================================== +// DynGroup & DynPinId +//================================================================================================== + +/// Value-level `enum` for pin groups +#[derive(PartialEq, Clone, Copy)] +pub enum DynGroup { + A, + B, +} + +/// Value-level `struct` representing pin IDs +#[derive(PartialEq, Clone, Copy)] +pub struct DynPinId { + pub group: DynGroup, + pub num: u8, +} + +//================================================================================================== +// DynRegisters +//================================================================================================== + +/// Provide a safe register interface for [`DynPin`]s +/// +/// This `struct` takes ownership of a [`DynPinId`] and provides an API to +/// access the corresponding regsiters. +struct DynRegisters { + id: DynPinId, +} + +// [`DynRegisters`] takes ownership of the [`DynPinId`], and [`DynPin`] +// guarantees that each pin is a singleton, so this implementation is safe. +unsafe impl RegisterInterface for DynRegisters { + #[inline] + fn id(&self) -> DynPinId { + self.id + } +} + +impl DynRegisters { + /// Create a new instance of [`DynRegisters`] + /// + /// # Safety + /// + /// Users must never create two simultaneous instances of this `struct` with + /// the same [`DynPinId`] + #[inline] + unsafe fn new(id: DynPinId) -> Self { + DynRegisters { id } + } +} + +//================================================================================================== +// DynPin +//================================================================================================== + +/// A value-level pin, parameterized by [`DynPinId`] and [`DynPinMode`] +/// +/// This type acts as a type-erased version of [`Pin`]. Every pin is represented +/// by the same type, and pins are tracked and distinguished at run-time. +pub struct DynPin { + regs: DynRegisters, + mode: DynPinMode, +} + +impl DynPin { + /// Create a new [`DynPin`] + /// + /// # Safety + /// + /// Each [`DynPin`] must be a singleton. For a given [`DynPinId`], there + /// must be at most one corresponding [`DynPin`] in existence at any given + /// time. Violating this requirement is `unsafe`. + #[inline] + unsafe fn new(id: DynPinId, mode: DynPinMode) -> Self { + DynPin { + regs: DynRegisters::new(id), + mode, + } + } + + /// Return a copy of the pin ID + #[inline] + pub fn id(&self) -> DynPinId { + self.regs.id + } + + /// Return a copy of the pin mode + #[inline] + pub fn mode(&self) -> DynPinMode { + self.mode + } + + /// Convert the pin to the requested [`DynPinMode`] + #[inline] + pub fn into_mode(&mut self, mode: DynPinMode) { + // Only modify registers if we are actually changing pin mode + if mode != self.mode { + self.regs.change_mode(mode); + self.mode = mode; + } + } + + #[inline] + pub fn into_funsel_1(&mut self) { + self.into_mode(DYN_ALT_FUNC_1); + } + + #[inline] + pub fn into_funsel_2(&mut self) { + self.into_mode(DYN_ALT_FUNC_2); + } + + #[inline] + pub fn into_funsel_3(&mut self) { + self.into_mode(DYN_ALT_FUNC_3); + } + + /// Configure the pin to operate as a floating input + #[inline] + pub fn into_floating_input(&mut self) { + self.into_mode(DYN_FLOATING_INPUT); + } + + /// Configure the pin to operate as a pulled down input + #[inline] + pub fn into_pull_down_input(&mut self) { + self.into_mode(DYN_PULL_DOWN_INPUT); + } + + /// Configure the pin to operate as a pulled up input + #[inline] + pub fn into_pull_up_input(&mut self) { + self.into_mode(DYN_PULL_UP_INPUT); + } + + /// Configure the pin to operate as a push-pull output + #[inline] + pub fn into_push_pull_output(&mut self) { + self.into_mode(DYN_PUSH_PULL_OUTPUT); + } + + /// Configure the pin to operate as a push-pull output + #[inline] + pub fn into_open_drain_output(&mut self) { + self.into_mode(DYN_OPEN_DRAIN_OUTPUT); + } + + /// Configure the pin to operate as a push-pull output + #[inline] + pub fn into_readable_push_pull_output(&mut self) { + self.into_mode(DYN_RD_PUSH_PULL_OUTPUT); + } + + /// Configure the pin to operate as a push-pull output + #[inline] + pub fn into_readable_open_drain_output(&mut self) { + 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) + } + + /// See p.53 of the programmers guide for more information. + /// Possible delays in clock cycles: + /// - Delay 1: 1 + /// - Delay 2: 2 + /// - Delay 1 + Delay 2: 3 + #[inline] + pub fn delay(self, delay_1: bool, delay_2: bool) -> Result { + match self.mode { + DynPinMode::Output(_) => { + self.regs.delay(delay_1, delay_2); + Ok(self) + } + _ => Err(PinError::InvalidPinType), + } + } + + /// See p.52 of the programmers guide for more information. + /// When configured for pulse mode, a given pin will set the non-default state for exactly + /// one clock cycle before returning to the configured default state + pub fn pulse_mode(self, enable: bool, default_state: PinState) -> Result { + match self.mode { + DynPinMode::Output(_) => { + self.regs.pulse_mode(enable, default_state); + Ok(self) + } + _ => Err(PinError::InvalidPinType), + } + } + + /// See p.37 and p.38 of the programmers guide for more information. + #[inline] + pub fn filter_type(self, filter: FilterType, clksel: FilterClkSel) -> Result { + match self.mode { + DynPinMode::Input(_) => { + self.regs.filter_type(filter, clksel); + Ok(self) + } + _ => Err(PinError::InvalidPinType), + } + } + + #[inline] + fn _read(&self) -> Result { + match self.mode { + DynPinMode::Input(_) | DYN_RD_OPEN_DRAIN_OUTPUT | DYN_RD_PUSH_PULL_OUTPUT => { + Ok(self.regs.read_pin()) + } + _ => Err(PinError::InvalidPinType), + } + } + #[inline] + fn _write(&mut self, bit: bool) -> Result<(), PinError> { + match self.mode { + DynPinMode::Output(_) => { + self.regs.write_pin(bit); + Ok(()) + } + _ => Err(PinError::InvalidPinType), + } + } + #[inline] + fn _toggle(&mut self) -> Result<(), PinError> { + match self.mode { + DynPinMode::Output(_) => { + self.regs.toggle(); + Ok(()) + } + _ => Err(PinError::InvalidPinType), + } + } + + #[inline] + fn _is_low(&self) -> Result { + self._read().map(|v| !v) + } + #[inline] + fn _is_high(&self) -> Result { + self._read() + } + #[inline] + fn _set_low(&mut self) -> Result<(), PinError> { + self._write(false) + } + #[inline] + fn _set_high(&mut self) -> Result<(), PinError> { + self._write(true) + } +} + +//================================================================================================== +// Convert between Pin and DynPin +//================================================================================================== + +impl From> for DynPin { + /// Erase the type-level information in a [`Pin`] and return a value-level + /// [`DynPin`] + #[inline] + fn from(_pin: Pin) -> Self { + // The `Pin` is consumed, so it is safe to replace it with the + // corresponding `DynPin` + unsafe { DynPin::new(I::DYN, M::DYN) } + } +} + +impl TryFrom for Pin { + type Error = PinError; + + /// Try to recreate a type-level [`Pin`] from a value-level [`DynPin`] + /// + /// There is no way for the compiler to know if the conversion will be + /// successful at compile-time. We must verify the conversion at run-time + /// or refuse to perform it. + #[inline] + fn try_from(pin: DynPin) -> Result { + if pin.regs.id == I::DYN && pin.mode == M::DYN { + // The `DynPin` is consumed, so it is safe to replace it with the + // corresponding `Pin` + Ok(unsafe { Self::new() }) + } else { + Err(PinError::InvalidPinType) + } + } +} + +//================================================================================================== +// Embedded HAL traits +//================================================================================================== + +impl OutputPin for DynPin { + type Error = PinError; + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + self._set_high() + } + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + self._set_low() + } +} + +impl InputPin for DynPin { + type Error = PinError; + #[inline] + fn is_high(&self) -> Result { + self._is_high() + } + #[inline] + fn is_low(&self) -> Result { + self._is_low() + } +} + +impl ToggleableOutputPin for DynPin { + type Error = PinError; + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + self._toggle() + } +} diff --git a/src/gpio/mod.rs b/src/gpio/mod.rs new file mode 100644 index 0000000..69e49df --- /dev/null +++ b/src/gpio/mod.rs @@ -0,0 +1,30 @@ +//! # GPIO module +//! +//! The implementation of this GPIO module is heavily based on the +//! [ATSAMD HAL implementation](https://docs.rs/atsamd-hal/0.13.0/atsamd_hal/gpio/v2/index.html). +//! +//! This API provides two different submodules, [`pin`] and [`dynpin`], +//! representing two different ways to handle GPIO pins. The default, [`pin`], +//! is a type-level API that tracks the state of each pin at compile-time. The +//! alternative, [`dynpin`] is a type-erased, value-level API that tracks the +//! state of each pin at run-time. +//! +//! The type-level API is strongly preferred. By representing the state of each +//! pin within the type system, the compiler can detect logic errors at +//! compile-time. Furthermore, the type-level API has absolutely zero run-time +//! cost. +//! +//! If needed, [`dynpin`] can be used to erase the type-level differences +//! between pins. However, by doing so, pins must now be tracked at run-time, +//! and each pin has a non-zero memory footprint. +//! +//! ## Examples +//! +//! - [Blinky example](https://github.com/robamu-org/va108xx-hal-rs/blob/main/examples/blinky.rs) +pub mod dynpins; +pub use dynpins::*; + +pub mod pins; +pub use pins::*; + +mod reg; diff --git a/src/gpio/pins.rs b/src/gpio/pins.rs new file mode 100644 index 0000000..681c1cd --- /dev/null +++ b/src/gpio/pins.rs @@ -0,0 +1,821 @@ +//! # Type-level module for GPIO pins +//! +//! This module provides a type-level API for GPIO pins. It uses the type system +//! to track the state of pins at compile-time. Representing GPIO pins in this +//! manner incurs no run-time overhead. Each [`Pin`] struct is zero-sized, so +//! there is no data to copy around. Instead, real code is generated as a side +//! effect of type transformations, and the resulting assembly is nearly +//! identical to the equivalent, hand-written C. +//! +//! To track the state of pins at compile-time, this module uses traits to +//! represent [type classes] and types as instances of those type classes. For +//! example, the trait [`InputConfig`] acts as a [type-level enum] of the +//! available input configurations, and the types [`Floating`], [`PullDown`] and +//! [`PullUp`] are its type-level variants. +//! +//! Type-level [`Pin`]s are parameterized by two type-level enums, [`PinId`] and +//! [`PinMode`]. +//! +//! ``` +//! pub struct Pin +//! where +//! I: PinId, +//! M: PinMode, +//! { +//! // ... +//! } +//! ``` +//! +//! A `PinId` identifies a pin by it's group (A, B) and pin number. Each +//! `PinId` instance is named according to its datasheet identifier, e.g. +//! [`PA02`]. +//! +//! A `PinMode` represents the various pin modes. The available `PinMode` +//! variants are [`Input`], [`Output`] and [`Alternate`], each with its own corresponding +//! configurations. +//! +//! It is not possible for users to create new instances of a [`Pin`]. Singleton +//! instances of each pin are made available to users through the [`Pins`] +//! struct. +//! +//! To create the [`PinsA`] or [`PinsB`] struct, users must supply the PAC +//! [`PORTA`](crate::pac::PORTA) or [`PORTB`](crate::pac::PORTB)peripheral. +//! The struct takes ownership of the port and provides the corresponding pins. Each [`Pin`] +//! within the [`PinsA`] or [`PinsB`] struct can be moved out and used individually. +//! The pins structure can also consume the [`IOCONFIG`] structure optionally by +//! passing it as an option +//! +//! ``` +//! let mut peripherals = Peripherals::take().unwrap(); +//! let pins = Pins::new(&mut dp.SYSCONFIG, Some(dp.IOCONFIG), dp.PORTA); +//! ``` +//! +//! Pins can be converted between modes using several different methods. +//! +//! ``` +//! // Use one of the literal function names +//! let pa0 = pins.pa0.into_floating_input(); +//! // Use a generic method and one of the `PinMode` variant types +//! let pa0 = pins.pa0.into_mode::(); +//! // Specify the target type and use `From`/`Into` +//! let pa0: Pin = pins.pa0.into(); +//! ``` +//! +//! # Embedded HAL traits +//! +//! This module implements all of the embedded HAL GPIO traits for each [`Pin`] +//! in the corresponding [`PinMode`]s, namely: [`InputPin`], [`OutputPin`], +//! [`ToggleableOutputPin`]. +//! +//! For example, you can control the logic level of an `OutputPin` like so +//! +//! ``` +//! use atsamd_hal::pac::Peripherals; +//! use atsamd_hal::gpio::v2::Pins; +//! use embedded_hal::digital::v2::OutputPin; +//! +//! let mut peripherals = Peripherals::take().unwrap(); +//! let mut pins = Pins::new(&mut dp.SYSCONFIG, Some(dp.IOCONFIG), dp.PORTA); +//! pins.pa0.set_high(); +//! ``` +//! +//! # Type-level features +//! +//! This module also provides additional, type-level tools to work with GPIO +//! pins. +//! +//! The [`OptionalPinId`] and [`OptionalPin`] traits use the [`OptionalKind`] +//! pattern to act as type-level versions of [`Option`] for `PinId` and `Pin` +//! respectively. And the [`AnyPin`] trait defines an [`AnyKind`] type class +//! for all `Pin` types. +//! +//! [type classes]: crate::typelevel#type-classes +//! [type-level enum]: crate::typelevel#type-level-enum +//! [`OptionalKind`]: crate::typelevel#optionalkind-trait-pattern +//! [`AnyKind`]: crate::typelevel#anykind-trait-pattern + +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 core::convert::Infallible; +use core::marker::PhantomData; +use embedded_hal::digital::v2::{InputPin, OutputPin, ToggleableOutputPin}; +use paste::paste; + +//================================================================================================== +// Errors and Definitions +//================================================================================================== + +#[derive(Debug, PartialEq)] +pub enum PinState { + Low = 0, + High = 1, +} + +/// GPIO error type +/// +/// [`DynPin`]s are not tracked and verified at compile-time, so run-time +/// operations are fallible. This `enum` represents the corresponding errors. +#[derive(Debug, PartialEq)] +pub enum PinError { + /// The pin did not have the correct ID or mode for the requested operation + InvalidPinType, + InputDisabledForOutput, + IsMasked, +} + +//================================================================================================== +// Input configuration +//================================================================================================== + +/// Type-level enum for input configurations +/// +/// The valid options are [`Floating`], [`PullDown`] and [`PullUp`]. +pub trait InputConfig: Sealed { + /// Corresponding [`DynInput`](super::DynInput) + const DYN: DynInput; +} + +pub enum Floating {} +pub enum PullDown {} +pub enum PullUp {} + +impl InputConfig for Floating { + const DYN: DynInput = DynInput::Floating; +} +impl InputConfig for PullDown { + const DYN: DynInput = DynInput::PullDown; +} +impl InputConfig for PullUp { + const DYN: DynInput = DynInput::PullUp; +} + +impl Sealed for Floating {} +impl Sealed for PullDown {} +impl Sealed for PullUp {} + +/// Type-level variant of [`PinMode`] for floating input mode +pub type InputFloating = Input; +/// Type-level variant of [`PinMode`] for pull-down input mode +pub type InputPullDown = Input; +/// Type-level variant of [`PinMode`] for pull-up input mode +pub type InputPullUp = Input; + +/// Type-level variant of [`PinMode`] for input modes +/// +/// Type `C` is one of three input configurations: [`Floating`], [`PullDown`] or +/// [`PullUp`] +pub struct Input { + cfg: PhantomData, +} + +impl Sealed for Input {} + +pub enum FilterType { + SystemClock = 0, + DirectInputWithSynchronization = 1, + FilterOneClockCycle = 2, + FilterTwoClockCycles = 3, + FilterThreeClockCycles = 4, + FilterFourClockCycles = 5, +} + +pub enum FilterClkSel { + SysClk = 0, + Clk1 = 1, + Clk2 = 2, + Clk3 = 3, + Clk4 = 4, + Clk5 = 5, + Clk6 = 6, + Clk7 = 7, +} + +//================================================================================================== +// Output configuration +//================================================================================================== + +pub trait OutputConfig: Sealed { + const DYN: DynOutput; +} + +/// Type-level variant of [`OutputConfig`] for a push-pull configuration +pub enum PushPull {} +/// Type-level variant of [`OutputConfig`] for an open drain configuration +pub enum OpenDrain {} + +/// Type-level variant of [`OutputConfig`] for a readable push-pull configuration +pub enum ReadablePushPull {} +/// Type-level variant of [`OutputConfig`] for a readable open-drain configuration +pub enum ReadableOpenDrain {} + +impl Sealed for PushPull {} +impl Sealed for OpenDrain {} +impl Sealed for ReadableOpenDrain {} +impl Sealed for ReadablePushPull {} + +impl OutputConfig for PushPull { + const DYN: DynOutput = DynOutput::PushPull; +} +impl OutputConfig for OpenDrain { + const DYN: DynOutput = DynOutput::OpenDrain; +} +impl OutputConfig for ReadablePushPull { + const DYN: DynOutput = DynOutput::ReadablePushPull; +} +impl OutputConfig for ReadableOpenDrain { + const DYN: DynOutput = DynOutput::ReadableOpenDrain; +} + +/// Type-level variant of [`PinMode`] for output modes +/// +/// Type `C` is one of two output configurations: [`PushPull`] or [`Readable`] +pub struct Output { + cfg: PhantomData, +} + +impl Sealed for Output {} + +/// Type-level variant of [`PinMode`] for push-pull output mode +pub type PushPullOutput = Output; +/// Type-level variant of [`PinMode`] for open drain output mode +pub type OutputOpenDrain = Output; + +pub type OutputReadablePushPull = Output; +pub type OutputReadableOpenDrain = Output; + +//================================================================================================== +// Alternate configurations +//================================================================================================== + +/// Type-level enum for alternate peripheral function configurations +pub trait AlternateConfig: Sealed { + const DYN: DynAlternate; +} + +pub enum Funsel1 {} +pub enum Funsel2 {} +pub enum Funsel3 {} + +impl AlternateConfig for Funsel1 { + const DYN: DynAlternate = DynAlternate::Funsel1; +} +impl AlternateConfig for Funsel2 { + const DYN: DynAlternate = DynAlternate::Funsel2; +} +impl AlternateConfig for Funsel3 { + const DYN: DynAlternate = DynAlternate::Funsel3; +} + +impl Sealed for Funsel1 {} +impl Sealed for Funsel2 {} +impl Sealed for Funsel3 {} + +/// Type-level variant of [`PinMode`] for alternate peripheral functions +/// +/// Type `C` is an [`AlternateConfig`] +pub struct Alternate { + cfg: PhantomData, +} + +impl Sealed for Alternate {} + +pub type AltFunc1 = Alternate; +pub type AltFunc2 = Alternate; +pub type AltFunc3 = Alternate; + +/// Type alias for the [`PinMode`] at reset +pub type Reset = InputFloating; + +//================================================================================================== +// Pin modes +//================================================================================================== + +/// Type-level enum representing pin modes +/// +/// The valid options are [`Input`], [`Output`] and [`Alternate`]. +pub trait PinMode: Sealed { + /// Corresponding [`DynPinMode`](super::DynPinMode) + const DYN: DynPinMode; +} + +impl PinMode for Input { + const DYN: DynPinMode = DynPinMode::Input(C::DYN); +} +impl PinMode for Output { + const DYN: DynPinMode = DynPinMode::Output(C::DYN); +} +impl PinMode for Alternate { + const DYN: DynPinMode = DynPinMode::Alternate(C::DYN); +} + +//================================================================================================== +// Pin IDs +//================================================================================================== + +/// Type-level enum for pin IDs +pub trait PinId: Sealed { + /// Corresponding [`DynPinId`](super::DynPinId) + const DYN: DynPinId; +} + +macro_rules! pin_id { + ($Group:ident, $Id:ident, $NUM:literal) => { + // Need paste macro to use ident in doc attribute + paste! { + #[doc = "Pin ID representing pin " $Id] + pub enum $Id {} + impl Sealed for $Id {} + impl PinId for $Id { + const DYN: DynPinId = DynPinId { + group: DynGroup::$Group, + num: $NUM, + }; + } + } + }; +} + +//================================================================================================== +// Pin +//================================================================================================== + +/// A type-level GPIO pin, parameterized by [`PinId`] and [`PinMode`] types + +pub struct Pin { + pub(in crate::gpio) regs: Registers, + mode: PhantomData, +} + +impl Sealed for Pin {} + +impl AnyPin for Pin { + type Id = I; + type Mode = M; +} + +impl Pin { + /// Create a new [`Pin`] + /// + /// # Safety + /// + /// Each [`Pin`] must be a singleton. For a given [`PinId`], there must be + /// at most one corresponding [`Pin`] in existence at any given time. + /// Violating this requirement is `unsafe`. + #[inline] + pub(crate) unsafe fn new() -> Pin { + Pin { + regs: Registers::new(), + mode: PhantomData, + } + } + + /// Convert the pin to the requested [`PinMode`] + #[inline] + pub fn into_mode(mut self) -> Pin { + // Only modify registers if we are actually changing pin mode + // This check should compile away + if N::DYN != M::DYN { + self.regs.change_mode::(); + } + // Safe because we drop the existing Pin + unsafe { Pin::new() } + } + + /// Configure the pin for function select 1. See Programmer Guide p.40 for the function table + #[inline] + pub fn into_funsel_1(self) -> Pin { + self.into_mode() + } + + /// Configure the pin for function select 2. See Programmer Guide p.40 for the function table + #[inline] + pub fn into_funsel_2(self) -> Pin { + self.into_mode() + } + + /// Configure the pin for function select 3. See Programmer Guide p.40 for the function table + #[inline] + pub fn into_funsel_3(self) -> Pin { + self.into_mode() + } + + /// Configure the pin to operate as a floating input + #[inline] + pub fn into_floating_input(self) -> Pin { + self.into_mode() + } + + /// Configure the pin to operate as a pulled down input + #[inline] + pub fn into_pull_down_input(self) -> Pin { + self.into_mode() + } + + /// Configure the pin to operate as a pulled up input + #[inline] + pub fn into_pull_up_input(self) -> Pin { + self.into_mode() + } + + /// Configure the pin to operate as a push-pull output + #[inline] + pub fn into_push_pull_output(self) -> Pin { + self.into_mode() + } + + /// Configure the pin to operate as a readable push-pull output + #[inline] + pub fn into_readable_push_pull_output(self) -> Pin { + self.into_mode() + } + + /// Configure the pin to operate as a readable open-drain output + #[inline] + pub fn into_readable_open_drain_output(self) -> 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) + } + + #[inline] + pub(crate) fn _set_high(&mut self) { + self.regs.write_pin(true) + } + + #[inline] + pub(crate) fn _set_low(&mut self) { + self.regs.write_pin(false) + } + + #[inline] + pub(crate) fn _toggle(&mut self) { + self.regs.toggle(); + } + + #[inline] + pub(crate) fn _is_low(&self) -> bool { + !self.regs.read_pin() + } + + #[inline] + pub(crate) fn _is_high(&self) -> bool { + self.regs.read_pin() + } +} + +pub type SpecificPin

= Pin<

::Id,

::Mode>; + +//================================================================================================== +// AnyPin +//================================================================================================== + +/// Type class for [`Pin`] types +/// +/// This trait uses the [`AnyKind`] trait pattern to create a [type class] for +/// [`Pin`] types. See the `AnyKind` documentation for more details on the +/// pattern. +pub trait AnyPin: Is> { + type Id: PinId; + type Mode: PinMode; +} + +impl AsRef for Pin { + #[inline] + fn as_ref(&self) -> &Self { + self + } +} + +impl AsMut for Pin { + #[inline] + fn as_mut(&mut self) -> &mut Self { + self + } +} + +//================================================================================================== +// Additional functionality +//================================================================================================== + +impl Pin> { + /// See p.53 of the programmers guide for more information. + /// Possible delays in clock cycles: + /// - Delay 1: 1 + /// - Delay 2: 2 + /// - Delay 1 + Delay 2: 3 + #[inline] + pub fn delay(self, delay_1: bool, delay_2: bool) -> Self { + self.regs.delay(delay_1, delay_2); + self + } + + /// See p.52 of the programmers guide for more information. + /// When configured for pulse mode, a given pin will set the non-default state for exactly + /// one clock cycle before returning to the configured default state + pub fn pulse_mode(self, enable: bool, default_state: PinState) -> Self { + self.regs.pulse_mode(enable, default_state); + self + } +} + +impl Pin> { + /// See p.37 and p.38 of the programmers guide for more information. + #[inline] + pub fn filter_type(self, filter: FilterType, clksel: FilterClkSel) -> Self { + self.regs.filter_type(filter, clksel); + self + } +} + +//================================================================================================== +// Embedded HAL traits +//================================================================================================== + +impl OutputPin for Pin> { + type Error = Infallible; + + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + self._set_high(); + Ok(()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + self._set_low(); + Ok(()) + } +} + +impl ToggleableOutputPin for Pin> { + type Error = Infallible; + + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + self._toggle(); + Ok(()) + } +} + +impl InputPin for Pin> { + type Error = Infallible; + + #[inline] + fn is_high(&self) -> Result { + Ok(self._is_high()) + } + #[inline] + fn is_low(&self) -> Result { + Ok(self._is_low()) + } +} + +impl InputPin for Pin { + type Error = Infallible; + + #[inline] + fn is_high(&self) -> Result { + Ok(self._is_high()) + } + + #[inline] + fn is_low(&self) -> Result { + Ok(self._is_low()) + } +} + +impl InputPin for Pin { + type Error = Infallible; + + #[inline] + fn is_high(&self) -> Result { + Ok(self._is_high()) + } + + #[inline] + fn is_low(&self) -> Result { + Ok(self._is_low()) + } +} + +//================================================================================================== +// Registers +//================================================================================================== + +/// Provide a safe register interface for [`Pin`]s +/// +/// This `struct` takes ownership of a [`PinId`] and provides an API to +/// access the corresponding regsiters. +pub(in crate::gpio) struct Registers { + id: PhantomData, +} + +// [`Registers`] takes ownership of the [`PinId`], and [`Pin`] guarantees that +// each pin is a singleton, so this implementation is safe. +unsafe impl RegisterInterface for Registers { + #[inline] + fn id(&self) -> DynPinId { + I::DYN + } +} + +impl Registers { + /// Create a new instance of [`Registers`] + /// + /// # Safety + /// + /// Users must never create two simultaneous instances of this `struct` with + /// the same [`PinId`] + #[inline] + unsafe fn new() -> Self { + Registers { id: PhantomData } + } + + /// Provide a type-level equivalent for the + /// [`RegisterInterface::change_mode`] method. + #[inline] + pub(in crate::gpio) fn change_mode(&mut self) { + RegisterInterface::change_mode(self, M::DYN); + } +} + +//================================================================================================== +// Pin definitions +//================================================================================================== + +macro_rules! pins { + ( + $Port:ident, $PinsName:ident, $($Id:ident,)+, + ) => { + paste!( + /// Collection of all the individual [`Pin`]s for a given port (PORTA or PORTB) + pub struct $PinsName { + iocfg: Option, + port: $Port, + $( + #[doc = "Pin " $Id] + pub [<$Id:lower>]: Pin<$Id, Reset>, + )+ + } + + 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. + #[inline] + pub fn new( + syscfg: &mut SYSCONFIG, + iocfg: Option, + port: $Port + ) -> $PinsName { + syscfg.peripheral_clk_enable.modify(|_, w| { + w.[<$Port:lower>]().set_bit(); + w.gpio().set_bit(); + w.ioconfig().set_bit() + }); + $PinsName { + iocfg, + port, + // Safe because we only create one `Pin` per `PinId` + $( + [<$Id:lower>]: unsafe { Pin::new() }, + )+ + } + } + + /// Get the peripheral ID + /// Safety: Read-only register + pub fn get_perid() -> u32 { + let port = unsafe { &(*$Port::ptr()) }; + port.perid.read().bits() + } + + /// Consumes the Pins struct and returns the port definitions + pub fn release(self) -> (Option, $Port) { + (self.iocfg, self.port) + } + } + ); + } +} + +macro_rules! declare_pins { + ( + $Group:ident, $PinsName:ident, $Port:ident, [$(($Id:ident, $NUM:literal),)+] + ) => { + pins!($Port, $PinsName, $($Id,)+,); + $( + pin_id!($Group, $Id, $NUM); + )+ + } +} + +declare_pins!( + A, + PinsA, + PORTA, + [ + (PA0, 0), + (PA1, 1), + (PA2, 2), + (PA3, 3), + (PA4, 4), + (PA5, 5), + (PA6, 6), + (PA7, 7), + (PA8, 8), + (PA9, 9), + (PA10, 10), + (PA11, 11), + (PA12, 12), + (PA13, 13), + (PA14, 14), + (PA15, 15), + (PA16, 16), + (PA17, 17), + (PA18, 18), + (PA19, 19), + (PA20, 20), + (PA21, 21), + (PA22, 22), + (PA23, 23), + (PA24, 24), + (PA25, 25), + (PA26, 26), + (PA27, 27), + (PA28, 28), + (PA29, 29), + (PA30, 30), + (PA31, 31), + ] +); + +declare_pins!( + B, + PinsB, + PORTB, + [ + (PB0, 0), + (PB1, 1), + (PB2, 2), + (PB3, 3), + (PB4, 4), + (PB5, 5), + (PB6, 6), + (PB7, 7), + (PB8, 8), + (PB9, 9), + (PB10, 10), + (PB11, 11), + (PB12, 12), + (PB13, 13), + (PB14, 14), + (PB15, 15), + (PB16, 16), + (PB17, 17), + (PB18, 18), + (PB19, 19), + (PB20, 20), + (PB21, 21), + (PB22, 22), + (PB23, 23), + ] +); diff --git a/src/gpio/reg.rs b/src/gpio/reg.rs new file mode 100644 index 0000000..13f53b9 --- /dev/null +++ b/src/gpio/reg.rs @@ -0,0 +1,369 @@ +use super::dynpins::{self, DynGroup, DynPinId, DynPinMode}; +use super::pins::{FilterClkSel, FilterType, PinError, PinState}; +use va108xx::{ioconfig, porta, IOCONFIG, PORTA, PORTB}; + +/// Type definition to avoid confusion: These register blocks are identical +type PortRegisterBlock = porta::RegisterBlock; + +//================================================================================================== +// ModeFields +//================================================================================================== + +/// Collect all fields needed to set the [`PinMode`](super::PinMode) +#[derive(Default)] +struct ModeFields { + dir: bool, + opendrn: bool, + pull_en: bool, + /// true for pullup, false for pulldown + pull_dir: bool, + funsel: u8, + enb_input: bool, +} + +impl From for ModeFields { + #[inline] + fn from(mode: DynPinMode) -> Self { + let mut fields = Self::default(); + use DynPinMode::*; + match mode { + Input(config) => { + use dynpins::DynInput::*; + fields.dir = false; + match config { + Floating => { + fields.pull_en = false; + } + PullUp => { + fields.pull_en = true; + fields.pull_dir = true; + } + PullDown => { + fields.pull_en = true; + fields.pull_dir = false; + } + } + } + Output(config) => { + use dynpins::DynOutput::*; + fields.dir = true; + match config { + PushPull => { + fields.opendrn = false; + } + OpenDrain => { + fields.opendrn = true; + } + ReadableOpenDrain => { + fields.enb_input = true; + fields.opendrn = true; + } + ReadablePushPull => { + fields.enb_input = true; + fields.opendrn = false; + } + } + } + Alternate(config) => { + use dynpins::DynAlternate::*; + match config { + Funsel1 => { + fields.funsel = 1; + } + Funsel2 => { + fields.funsel = 2; + } + Funsel3 => { + fields.funsel = 3; + } + } + } + } + fields + } +} +//================================================================================================== +// Register Interface +//================================================================================================== + +pub type IocfgPort = ioconfig::PORTA; +#[repr(C)] +pub(super) struct IOCFG_PORT_GROUP { + port: [IocfgPort; 32], +} + +/// Provide a safe register interface for pin objects +/// +/// [`PORTA`] and [`PORTB`], like every PAC `struct`, is [`Send`] but not [`Sync`], because it +/// points to a `RegisterBlock` of `VolatileCell`s. Unfortunately, such an +/// interface is quite restrictive. Instead, it would be ideal if we could split +/// the [`PORT`] into independent pins that are both [`Send`] and [`Sync`]. +/// +/// [`PORT`] is a single, zero-sized marker `struct` that provides access to +/// every [`PORT`] register. Instead, we would like to create zero-sized marker +/// `struct`s for every pin, where each pin is only allowed to control its own +/// registers. Furthermore, each pin `struct` should be a singleton, so that +/// exclusive access to the `struct` also guarantees exclusive access to the +/// corresponding registers. Finally, the pin `struct`s should not have any +/// interior mutability. Together, these requirements would allow the pin +/// `struct`s to be both [`Send`] and [`Sync`]. +/// +/// This trait creates a safe API for accomplishing these goals. Implementers +/// supply a pin ID through the [`id`] function. The remaining functions provide +/// a safe API for accessing the registers associated with that pin ID. Any +/// modification of the registers requires `&mut self`, which destroys interior +/// mutability. +/// +/// # Safety +/// +/// Users should only implement the [`id`] function. No default function +/// implementations should be overridden. The implementing type must also have +/// "control" over the corresponding pin ID, i.e. it must guarantee that a each +/// pin ID is a singleton. +/// +/// [`id`]: Self::id +pub(super) unsafe trait RegisterInterface { + /// Provide a [`DynPinId`] identifying the set of registers controlled by + /// this type. + fn id(&self) -> DynPinId; + + const PORTA: *const PortRegisterBlock = PORTA::ptr(); + const PORTB: *const PortRegisterBlock = PORTB::ptr(); + const PORT_CFG: *const IOCFG_PORT_GROUP = IOCONFIG::ptr() as *const _; + + /// Change the pin mode + #[inline] + fn change_mode(&mut self, mode: DynPinMode) { + let ModeFields { + dir, + funsel, + opendrn, + pull_dir, + pull_en, + enb_input, + } = mode.into(); + let (portreg, iocfg) = (self.port_reg(), self.iocfg_port()); + iocfg.port[self.id().num as usize].write(|w| { + w.opendrn().bit(opendrn); + w.pen().bit(pull_en); + w.plevel().bit(pull_dir); + w.iewo().bit(enb_input); + unsafe { w.funsel().bits(funsel) } + }); + let mask = self.mask_32(); + unsafe { + if dir { + portreg.dir().modify(|r, w| w.bits(r.bits() | mask)); + } else { + portreg.dir().modify(|r, w| w.bits(r.bits() & !mask)); + } + } + } + + #[inline] + fn port_reg(&self) -> &PortRegisterBlock { + match self.id().group { + DynGroup::A => unsafe { &(*Self::PORTA) }, + DynGroup::B => unsafe { &(*Self::PORTB) }, + } + } + fn iocfg_port(&self) -> &IOCFG_PORT_GROUP { + match self.id().group { + DynGroup::A => unsafe { &*Self::PORT_CFG }, + DynGroup::B => unsafe { &*Self::PORT_CFG.add(1) }, + } + } + + #[inline] + fn mask_32(&self) -> u32 { + 1 << self.id().num + } + + #[inline] + fn enable_input_for_output(&self, enable: bool) { + let iocfg = self.iocfg_port(); + iocfg.port[self.id().num as usize].modify(|_, w| w.iewo().bit(enable)) + } + + /// Set the direction of a pin + #[inline] + fn set_dir(&mut self, bit: bool) { + let portreg = self.port_reg(); + let mask = self.mask_32(); + // Safety: Only the bit for this Pin ID is modified + unsafe { + if bit { + portreg.dir().modify(|r, w| w.bits(r.bits() | mask)); + } else { + portreg.dir().modify(|r, w| w.bits(r.bits() & !mask)); + } + } + } + + fn get_perid(&self) -> u32 { + let portreg = self.port_reg(); + portreg.perid.read().bits() + } + + /// Read the logic level of an output pin + fn read_pin(&self) -> bool { + let portreg = self.port_reg(); + ((portreg.datainraw().read().bits() >> self.id().num) & 0x01) == 1 + } + + // Get DATAMASK bit for this particular pin + #[inline(always)] + fn datamask(&self) -> bool { + let portreg = self.port_reg(); + (portreg.datamask().read().bits() >> self.id().num) == 1 + } + + /// Read a pin but use the masked version but check whether the datamask for the pin is + /// cleared as well + fn read_pin_masked(&self) -> Result { + if !self.datamask() { + Err(PinError::IsMasked) + } else { + let portreg = self.port_reg(); + Ok(((portreg.datain().read().bits() >> self.id().num) & 0x01) == 1) + } + } + + /// Write the logic level of an output pin + #[inline] + fn write_pin(&mut self, bit: bool) { + let portreg = self.port_reg(); + let mask = self.mask_32(); + // Safety: SETOUT is a "mask" register, and we only write the bit for + // this pin ID + unsafe { + if bit { + portreg.setout().write(|w| w.bits(mask)); + } else { + portreg.clrout().write(|w| w.bits(mask)); + } + } + } + + /// Write the logic level of an output pin but check whether the datamask for the pin is + /// cleared as well + #[inline] + fn write_pin_masked(&mut self, bit: bool) -> Result<(), PinError> { + if !self.datamask() { + Err(PinError::IsMasked) + } else { + let portreg = self.port_reg(); + let mask = self.mask_32(); + // Safety: SETOUT is a "mask" register, and we only write the bit for + // this pin ID + unsafe { + if bit { + portreg.setout().write(|w| w.bits(mask)); + } else { + portreg.clrout().write(|w| w.bits(mask)); + } + Ok(()) + } + } + } + + /// Toggle the logic level of an output pin + #[inline] + fn toggle(&mut self) { + let portreg = self.port_reg(); + let mask = self.mask_32(); + // Safety: TOGOUT is a "mask" register, and we only write the bit for + // this pin ID + unsafe { portreg.togout().write(|w| w.bits(mask)) }; + } + + /// Only useful for input pins + #[inline] + fn filter_type(&self, filter: FilterType, clksel: FilterClkSel) { + let iocfg = self.iocfg_port(); + iocfg.port[self.id().num as usize].modify(|_, w| { + // Safety: Only write to register for this Pin ID + unsafe { + w.flttype().bits(filter as u8); + w.fltclk().bits(clksel as u8) + } + }); + } + + /// Set DATAMASK bit for this particular pin. 1 is the default + /// state of the bit and allows access of the corresponding bit + #[inline(always)] + fn set_datamask(&self) { + let portreg = self.port_reg(); + unsafe { + portreg + .datamask() + .modify(|r, w| w.bits(r.bits() | self.mask_32())) + } + } + + /// Clear DATAMASK bit for this particular pin. This prevents access + /// of the corresponding bit for output and input operations + #[inline(always)] + fn clear_datamask(&self) { + let portreg = self.port_reg(); + unsafe { + portreg + .datamask() + .modify(|r, w| w.bits(r.bits() & !self.mask_32())) + } + } + + /// Only useful for output pins + /// See p.52 of the programmers guide for more information. + /// When configured for pulse mode, a given pin will set the non-default state for exactly + /// one clock cycle before returning to the configured default state + fn pulse_mode(&self, enable: bool, default_state: PinState) { + let portreg = self.port_reg(); + unsafe { + if enable { + portreg + .pulse() + .modify(|r, w| w.bits(r.bits() | self.mask_32())); + } else { + portreg + .pulse() + .modify(|r, w| w.bits(r.bits() & !self.mask_32())); + } + if default_state == PinState::Low { + portreg + .pulsebase() + .modify(|r, w| w.bits(r.bits() & !self.mask_32())); + } else { + portreg + .pulsebase() + .modify(|r, w| w.bits(r.bits() | self.mask_32())); + } + } + } + + /// Only useful for output pins + fn delay(&self, delay_1: bool, delay_2: bool) { + let portreg = self.port_reg(); + unsafe { + if delay_1 { + portreg + .delay1() + .modify(|r, w| w.bits(r.bits() | self.mask_32())); + } else { + portreg + .delay1() + .modify(|r, w| w.bits(r.bits() & !self.mask_32())); + } + if delay_2 { + portreg + .delay2() + .modify(|r, w| w.bits(r.bits() | self.mask_32())); + } else { + portreg + .delay2() + .modify(|r, w| w.bits(r.bits() & !self.mask_32())); + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 8c9e872..def728a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,10 +7,15 @@ pub mod gpio; pub mod prelude; pub mod time; pub mod timer; +pub mod typelevel; pub mod uart; pub use va108xx as pac; -mod sealed { +mod private { + /// Super trait used to mark traits with an exhaustive set of + /// implementations pub trait Sealed {} } + +pub(crate) use private::Sealed; diff --git a/src/prelude.rs b/src/prelude.rs index ed67fbe..ab136ca 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,8 +1,8 @@ //! Prelude pub use embedded_hal::prelude::*; -pub use crate::gpio::GpioExt as _va108xx_hal_gpio_GpioExt; - +// embedded-hal doesn’t yet have v2 in its prelude, so we need to +// export it ourselves 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; diff --git a/src/typelevel.rs b/src/typelevel.rs new file mode 100644 index 0000000..256a336 --- /dev/null +++ b/src/typelevel.rs @@ -0,0 +1,60 @@ +///! # Module support type-level programming +/// +/// This module is more or less taken from the +/// [ATSAMD HAL](https://docs.rs/atsamd-hal/0.13.0/atsamd_hal/typelevel/index.html). +/// You can find a thorough explanation of the patterns used there. +use crate::Sealed; + +/// Type-level version of the [None] variant +#[derive(Default)] +pub struct NoneT; +impl Sealed for NoneT {} + +/// Marker trait for type identity +/// +/// This trait is used as part of the [`AnyKind`] trait pattern. It represents +/// the concept of type identity, because all implementors have +/// `::Type == Self`. When used as a trait bound with a specific +/// type, it guarantees that the corresponding type parameter is exactly the +/// specific type. Stated differently, it guarantees that `T == Specific` in +/// the following example. +/// +/// ``` +/// where T: Is +/// ``` +/// +/// Moreover, the super traits guarantee that any instance of or reference to a +/// type `T` can be converted into the `Specific` type. +/// +/// ``` +/// fn example(mut any: T) +/// where +/// T: Is, +/// { +/// let specific_mut: &mut Specific = any.as_mut(); +/// let specific_ref: &Specific = any.as_ref(); +/// let specific: Specific = any.into(); +/// } +/// ``` +/// +/// [`AnyKind`]: #anykind-trait-pattern +pub trait Is +where + Self: Sealed, + Self: From>, + Self: Into>, + Self: AsRef>, + Self: AsMut>, +{ + type Type; +} + +/// Type alias for [`Is::Type`] +pub type IsType = ::Type; + +impl Is for T +where + T: Sealed + AsRef + AsMut, +{ + type Type = T; +} diff --git a/src/uart.rs b/src/uart.rs index 7ceef9a..499a189 100644 --- a/src/uart.rs +++ b/src/uart.rs @@ -6,9 +6,10 @@ use libm::floorf; use crate::clock::enable_peripheral_clock; use crate::{ clock, - gpio::porta::{PA16, PA17, PA18, PA19, PA2, PA26, PA27, PA3, PA30, PA31, PA8, PA9}, - gpio::portb::{PB18, PB19, PB20, PB21, PB22, PB23, PB6, PB7, PB8, PB9}, - gpio::{AltFunc, FUNSEL1, FUNSEL2, FUNSEL3}, + gpio::pins::{ + Alternate, Funsel1, Funsel2, Funsel3, Pin, PA16, PA17, PA18, PA19, PA2, PA26, PA27, PA3, + PA30, PA31, PA8, PA9, PB18, PB19, PB20, PB21, PB22, PB23, PB6, PB7, PB8, PB9, + }, pac::{uarta as uart_base, SYSCONFIG, UARTA, UARTB}, prelude::*, time::{Bps, Hertz}, @@ -18,20 +19,20 @@ use embedded_hal::{blocking, serial}; pub trait Pins {} -impl Pins for (PA9>, PA8>) {} -impl Pins for (PA17>, PA16>) {} -impl Pins for (PA31>, PA30>) {} +impl Pins for (Pin>, Pin>) {} +impl Pins for (Pin>, Pin>) {} +impl Pins for (Pin>, Pin>) {} -impl Pins for (PB9>, PB8>) {} -impl Pins for (PB23>, PB22>) {} +impl Pins for (Pin>, Pin>) {} +impl Pins for (Pin>, Pin>) {} -impl Pins for (PA3>, PA2>) {} -impl Pins for (PA19>, PA18>) {} -impl Pins for (PA27>, PA26>) {} +impl Pins for (Pin>, Pin>) {} +impl Pins for (Pin>, Pin>) {} +impl Pins for (Pin>, Pin>) {} -impl Pins for (PB7>, PB6>) {} -impl Pins for (PB19>, PB18>) {} -impl Pins for (PB21>, PB20>) {} +impl Pins for (Pin>, Pin>) {} +impl Pins for (Pin>, Pin>) {} +impl Pins for (Pin>, Pin>) {} #[derive(Debug)] pub enum Error {