From 0b3ccca69b49d0a18675aca55ef41b5cba1d899b Mon Sep 17 00:00:00 2001 From: Richard Dodd Date: Wed, 5 May 2021 14:46:51 +0100 Subject: [PATCH] Add a pin variant that can change between disconnected/input/output. I'm in no way attached to the name and encourage better suggestions. --- embassy-nrf/src/gpio.rs | 194 +++++++++++++++++++++++++++++++--------- 1 file changed, 152 insertions(+), 42 deletions(-) diff --git a/embassy-nrf/src/gpio.rs b/embassy-nrf/src/gpio.rs index a06ee6053..4269cbe16 100644 --- a/embassy-nrf/src/gpio.rs +++ b/embassy-nrf/src/gpio.rs @@ -41,24 +41,7 @@ impl<'d, T: Pin> Input<'d, T> { pub fn new(pin: impl Unborrow + 'd, pull: Pull) -> Self { unborrow!(pin); - pin.conf().write(|w| { - w.dir().input(); - w.input().connect(); - match pull { - Pull::None => { - w.pull().disabled(); - } - Pull::Up => { - w.pull().pullup(); - } - Pull::Down => { - w.pull().pulldown(); - } - } - w.drive().s0s1(); - w.sense().disabled(); - w - }); + init_input(&pin, pull); Self { pin, @@ -93,6 +76,7 @@ pub enum Level { High, } +// These numbers match DRIVE_A exactly so hopefully the compiler will unify them. #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] @@ -129,30 +113,7 @@ impl<'d, T: Pin> Output<'d, T> { ) -> Self { unborrow!(pin); - match initial_output { - Level::High => pin.set_high(), - Level::Low => pin.set_low(), - } - - let drive = match drive { - OutputDrive::Standard => DRIVE_A::S0S1, - OutputDrive::HighDrive0Standard1 => DRIVE_A::H0S1, - OutputDrive::Standard0HighDrive1 => DRIVE_A::S0H1, - OutputDrive::HighDrive => DRIVE_A::H0H1, - OutputDrive::Disconnect0Standard1 => DRIVE_A::D0S1, - OutputDrive::Disconnect0HighDrive1 => DRIVE_A::D0H1, - OutputDrive::Standard0Disconnect1 => DRIVE_A::S0D1, - OutputDrive::HighDrive0Disconnect1 => DRIVE_A::H0D1, - }; - - pin.conf().write(|w| { - w.dir().output(); - w.input().disconnect(); - w.pull().disabled(); - w.drive().variant(drive); - w.sense().disabled(); - w - }); + init_output(&pin, initial_output, drive); Self { pin, @@ -205,6 +166,101 @@ impl<'d, T: Pin> StatefulOutputPin for Output<'d, T> { } } +/// GPIO flexible pin. +/// +/// This pin can either be a disconnected, input, or output pin. +pub struct FlexPin<'d, T: Pin> { + pub(crate) pin: T, + phantom: PhantomData<&'d mut T>, +} + +impl<'d, T: Pin> FlexPin<'d, T> { + /// Wrap the pin in a `FlexPin`. + /// + /// The pin remains disconnected. + pub fn new(pin: impl Unborrow + 'd) -> Self { + unborrow!(pin); + // Pin will be in disconnected state. + Self { + pin, + phantom: PhantomData, + } + } + + pub fn to_input(&self, pull: Pull) { + self.pin.conf().reset(); // TODO is this necessary? + init_input(&self.pin, pull); + } + + pub fn to_output(&self, initial_output: Level, drive: OutputDrive) { + self.pin.conf().reset(); // TODO is this necessary? + init_output(&self.pin, initial_output, drive); + } + + pub fn disconnect(&self) { + self.pin.conf().reset(); + } +} + +impl<'d, T: Pin> Drop for FlexPin<'d, T> { + fn drop(&mut self) { + self.pin.conf().reset(); + } +} + +/// Implement [`InputPin`] for [`FlexPin`]; +/// +/// If the pin is not in input mode the result is unspecified. +impl<'d, T: Pin> InputPin for FlexPin<'d, T> { + type Error = Infallible; + + fn is_high(&self) -> Result { + self.is_low().map(|v| !v) + } + + fn is_low(&self) -> Result { + Ok(self.pin.block().in_.read().bits() & (1 << self.pin.pin()) == 0) + } +} + +impl<'d, T: Pin> OutputPin for FlexPin<'d, T> { + type Error = Infallible; + + /// Set the output as high. + fn set_high(&mut self) -> Result<(), Self::Error> { + unsafe { + self.pin + .block() + .outset + .write(|w| w.bits(1u32 << self.pin.pin())); + } + Ok(()) + } + + /// Set the output as low. + fn set_low(&mut self) -> Result<(), Self::Error> { + unsafe { + self.pin + .block() + .outclr + .write(|w| w.bits(1u32 << self.pin.pin())); + } + Ok(()) + } +} + +impl<'d, T: Pin> StatefulOutputPin for FlexPin<'d, T> { + /// Is the output pin set as high? + fn is_set_high(&self) -> Result { + self.is_set_low().map(|v| !v) + } + + /// Is the output pin set as low? + fn is_set_low(&self) -> Result { + Ok(self.pin.block().out.read().bits() & (1 << self.pin.pin()) == 0) + } +} + pub(crate) mod sealed { use super::*; @@ -314,6 +370,60 @@ impl sealed::Pin for AnyPin { } } +// ===================== + +/// Set up a pin for input +#[inline] +fn init_input(pin: &T, pull: Pull) { + pin.conf().write(|w| { + w.dir().input(); + w.input().connect(); + match pull { + Pull::None => { + w.pull().disabled(); + } + Pull::Up => { + w.pull().pullup(); + } + Pull::Down => { + w.pull().pulldown(); + } + } + w.drive().s0s1(); + w.sense().disabled(); + w + }); +} + +/// Set up a pin for output +#[inline] +fn init_output(pin: &T, initial_output: Level, drive: OutputDrive) { + match initial_output { + Level::High => pin.set_high(), + Level::Low => pin.set_low(), + } + + let drive = match drive { + OutputDrive::Standard => DRIVE_A::S0S1, + OutputDrive::HighDrive0Standard1 => DRIVE_A::H0S1, + OutputDrive::Standard0HighDrive1 => DRIVE_A::S0H1, + OutputDrive::HighDrive => DRIVE_A::H0H1, + OutputDrive::Disconnect0Standard1 => DRIVE_A::D0S1, + OutputDrive::Disconnect0HighDrive1 => DRIVE_A::D0H1, + OutputDrive::Standard0Disconnect1 => DRIVE_A::S0D1, + OutputDrive::HighDrive0Disconnect1 => DRIVE_A::H0D1, + }; + + pin.conf().write(|w| { + w.dir().output(); + w.input().disconnect(); + w.pull().disabled(); + w.drive().variant(drive); + w.sense().disabled(); + w + }); +} + // ==================== pub trait OptionalPin: sealed::OptionalPin + Sized {