Skip to content

Proposal: add async digital traits #649

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 135 additions & 1 deletion embedded-hal-async/src/digital.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
//! Asynchronous digital I/O.
//!
//! The [`OutputPin`], [`StatefulOutputPin`] and [`InputPin`] traits are `async` variants
//! of the [blocking traits](embedded_hal::digital). These traits are useful for when
//! digital I/O may block execution, such as access through an I/O expander or over some
//! other transport.
//!
//! The [`Wait`] trait allows asynchronously waiting for a change in pin level.
//!
//! # Example
//!
//! ```rust
Expand All @@ -15,7 +22,134 @@
//! .expect("failed to await input pin")
//! }
//! ```
pub use embedded_hal::digital::{Error, ErrorKind, ErrorType};
//!
//! # For HAL authors
//!
//! If the digital I/O is implemented using memory mapped I/O and acts immediately, then the async traits
//! (except for [`Wait`]) can be implemented by calling the blocking traits and wrapping the result in
//! [`Poll::Ready`](core::task::Poll::Ready).
pub use embedded_hal::digital::{Error, ErrorKind, ErrorType, PinState};

/// Asynchronous single digital push-pull output pin.
pub trait OutputPin: ErrorType {
/// Drives the pin low.
///
/// This returns [`Ready`](core::task::Poll::Ready) when the pin has been driven low.
///
/// *NOTE* the actual electrical state of the pin may not actually be low, e.g. due to external
/// electrical sources.
async fn set_low(&mut self) -> Result<(), Self::Error>;

/// Drives the pin high.
///
/// This returns [`Ready`](core::task::Poll::Ready) when the pin has been driven high.
///
/// *NOTE* the actual electrical state of the pin may not actually be high, e.g. due to external
/// electrical sources.
async fn set_high(&mut self) -> Result<(), Self::Error>;

/// Drives the pin high or low depending on the provided value.
///
/// This returns [`Ready`](core::task::Poll::Ready) when the pin has been driven to the provided state.
///
/// *NOTE* the actual electrical state of the pin may not actually be high or low, e.g. due to external
/// electrical sources.
#[inline]
async fn set_state(&mut self, state: PinState) -> Result<(), Self::Error> {
match state {
PinState::Low => self.set_low().await,
PinState::High => self.set_high().await,
}
}
}

impl<T: OutputPin + ?Sized> OutputPin for &mut T {
#[inline]
async fn set_low(&mut self) -> Result<(), Self::Error> {
T::set_low(self).await
}

#[inline]
async fn set_high(&mut self) -> Result<(), Self::Error> {
T::set_high(self).await
}

#[inline]
async fn set_state(&mut self, state: PinState) -> Result<(), Self::Error> {
T::set_state(self, state).await
}
}

/// Asynchronous push-pull output pin that can read its output state.
pub trait StatefulOutputPin: OutputPin {
/// Is the pin in drive high mode?
///
/// This returns [`Ready`](core::task::Poll::Ready) when the pin's drive mode been read.
///
/// *NOTE* this does *not* read the electrical state of the pin.
async fn is_set_high(&mut self) -> Result<bool, Self::Error>;

/// Is the pin in drive low mode?
///
/// This returns [`Ready`](core::task::Poll::Ready) when the pin's drive mode been read.
///
/// *NOTE* this does *not* read the electrical state of the pin.
async fn is_set_low(&mut self) -> Result<bool, Self::Error>;

/// Toggle pin output.
///
/// This returns [`Ready`](core::task::Poll::Ready) when the pin has been toggled.
async fn toggle(&mut self) -> Result<(), Self::Error> {
let was_low: bool = self.is_set_low().await?;
self.set_state(PinState::from(was_low)).await
}
}

impl<T: StatefulOutputPin + ?Sized> StatefulOutputPin for &mut T {
#[inline]
async fn is_set_high(&mut self) -> Result<bool, Self::Error> {
T::is_set_high(self).await
}

#[inline]
async fn is_set_low(&mut self) -> Result<bool, Self::Error> {
T::is_set_low(self).await
}

#[inline]
async fn toggle(&mut self) -> Result<(), Self::Error> {
T::toggle(self).await
}
}

/// Asynchronous single digital input pin.
pub trait InputPin: ErrorType {
/// Is the input pin high?
///
/// This returns [`Ready`](core::task::Poll::Ready) when the pin's electrical state has been read.
///
/// *NOTE* the input state of the pin may have changed before the future is polled.
async fn is_high(&mut self) -> Result<bool, Self::Error>;

/// Is the input pin low?
///
/// This returns [`Ready`](core::task::Poll::Ready) when the pin's electrical state has been read.
///
/// *NOTE* the input state of the pin may have changed before the future is polled.
async fn is_low(&mut self) -> Result<bool, Self::Error>;
}

impl<T: InputPin + ?Sized> InputPin for &mut T {
#[inline]
async fn is_high(&mut self) -> Result<bool, Self::Error> {
T::is_high(self).await
}

#[inline]
async fn is_low(&mut self) -> Result<bool, Self::Error> {
T::is_low(self).await
}
}

/// Asynchronously wait for GPIO pin state.
pub trait Wait: ErrorType {
Expand Down
2 changes: 1 addition & 1 deletion embedded-hal/src/digital.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Digital I/O.
//! Blocking Digital I/O.

use core::ops::Not;

Expand Down