Skip to content

Commit

Permalink
Merge #401
Browse files Browse the repository at this point in the history
401: Add OpenDrainIO pin state (with InputPin capability) r=jonas-schievink a=Finomnis

As already reported in #339, there is currently no way to create an I/O pin in nrf-hal.

There are several reasons why having this would be useful. My personal usecase is the DHT22/AM2302 sensor. Controlling it requires a single-wire pulled-up open drain IO.

Sadly, this means that all libraries that can interface with it [require `InputPin + OutputPin`](https://docs.rs/dht-sensor/0.2.1/dht_sensor/trait.InputOutputPin.html).

# Solution
There is no inherent reason why nrf chips shouldn't be able to implement this. The input buffer is always available and allows reading back the pin values during every pin configuration. Although for energy saving reasons, the input buffer is currently disabled during the `OpenDrain` state.

My proposal is to add an `OpenDrainIO` state that does not disable the input buffer and implements `InputPin`.

Co-authored-by: Finomnis <[email protected]>
  • Loading branch information
bors[bot] and Finomnis authored Sep 27, 2022
2 parents 7b8387d + 2520dd4 commit 1f8fccf
Showing 1 changed file with 100 additions and 0 deletions.
100 changes: 100 additions & 0 deletions nrf-hal-common/src/gpio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ pub struct PushPull;
/// Open drain output (type state).
pub struct OpenDrain;

/// Open drain input/output (type state).
pub struct OpenDrainIO;

/// Represents a digital input or output level.
#[derive(Debug, Eq, PartialEq)]
pub enum Level {
Expand Down Expand Up @@ -284,6 +287,40 @@ impl<MODE> Pin<MODE> {
pin
}

/// Convert the pin to be an open-drain input/output.
///
/// Similar to [`into_open_drain_output`](Self::into_open_drain_output), but can also be read from.
///
/// This method currently does not support configuring an internal pull-up or pull-down
/// resistor.
pub fn into_open_drain_input_output(
self,
config: OpenDrainConfig,
initial_output: Level,
) -> Pin<Output<OpenDrainIO>> {
let mut pin = Pin {
_mode: PhantomData,
pin_port: self.pin_port,
};

match initial_output {
Level::Low => pin.set_low().unwrap(),
Level::High => pin.set_high().unwrap(),
}

// This is safe, as we restrict our access to the dedicated register for this pin.
self.conf().write(|w| {
w.dir().output();
w.input().connect();
w.pull().disabled();
w.drive().variant(config.variant());
w.sense().disabled();
w
});

pin
}

/// Disconnects the pin.
///
/// In disconnected mode the pin cannot be used as input or output.
Expand Down Expand Up @@ -311,6 +348,18 @@ impl<MODE> InputPin for Pin<Input<MODE>> {
}
}

impl InputPin for Pin<Output<OpenDrainIO>> {
type Error = Void;

fn is_high(&self) -> Result<bool, Self::Error> {
self.is_low().map(|v| !v)
}

fn is_low(&self) -> Result<bool, Self::Error> {
Ok(self.block().in_.read().bits() & (1 << self.pin()) == 0)
}
}

impl<MODE> OutputPin for Pin<Output<MODE>> {
type Error = Void;

Expand Down Expand Up @@ -406,6 +455,7 @@ macro_rules! gpio {
PullDown,
PullUp,
PushPull,
OpenDrainIO,

PhantomData,
$PX
Expand Down Expand Up @@ -555,6 +605,44 @@ macro_rules! gpio {
pin
}

/// Convert the pin to be an open-drain input/output
///
/// Similar to [`into_open_drain_output`](Self::into_open_drain_output), but can also be read from.
///
/// This method currently does not support configuring an
/// internal pull-up or pull-down resistor.
pub fn into_open_drain_input_output(self,
config: OpenDrainConfig,
initial_output: Level,
)
-> $PXi<Output<OpenDrainIO>>
{
let mut pin = $PXi {
_mode: PhantomData,
};

match initial_output {
Level::Low => pin.set_low().unwrap(),
Level::High => pin.set_high().unwrap(),
}

// This is safe, as we restrict our access to the
// dedicated register for this pin.
let pin_cnf = unsafe {
&(*$PX::ptr()).pin_cnf[$i]
};
pin_cnf.write(|w| {
w.dir().output();
w.input().connect();
w.pull().disabled();
w.drive().variant(config.variant());
w.sense().disabled();
w
});

pin
}

/// Disconnects the pin.
///
/// In disconnected mode the pin cannot be used as input or output.
Expand Down Expand Up @@ -586,6 +674,18 @@ macro_rules! gpio {
}
}

impl InputPin for $PXi<Output<OpenDrainIO>> {
type Error = Void;

fn is_high(&self) -> Result<bool, Self::Error> {
self.is_low().map(|v| !v)
}

fn is_low(&self) -> Result<bool, Self::Error> {
Ok(unsafe { ((*$PX::ptr()).in_.read().bits() & (1 << $i)) == 0 })
}
}

impl<MODE> From<$PXi<MODE>> for Pin<MODE> {
fn from(value: $PXi<MODE>) -> Self {
value.degrade()
Expand Down

0 comments on commit 1f8fccf

Please sign in to comment.