diff --git a/embedded-hal-async/Cargo.toml b/embedded-hal-async/Cargo.toml index 4d267933..93e322c4 100644 --- a/embedded-hal-async/Cargo.toml +++ b/embedded-hal-async/Cargo.toml @@ -20,3 +20,4 @@ defmt-03 = ["dep:defmt-03", "embedded-hal/defmt-03"] [dependencies] embedded-hal = { version = "1.0.0", path = "../embedded-hal" } defmt-03 = { package = "defmt", version = "0.3", optional = true } +tokio = { version = "1", features = ["rt", "macros"] } diff --git a/embedded-hal-async/src/adc.rs b/embedded-hal-async/src/adc.rs new file mode 100644 index 00000000..81165923 --- /dev/null +++ b/embedded-hal-async/src/adc.rs @@ -0,0 +1,184 @@ +//! Asynchronous analog-digital conversion traits. + +pub use embedded_hal::adc::{Error, ErrorKind, ErrorType}; + +/// Read data from an ADC. +/// +/// # Examples +/// +/// In the first naive example, [`read`](crate::adc::AdcChannel::read) is implemented +/// using a spin loop and only returns once data is ready. +/// +/// ``` +/// # use embedded_hal_async::adc::{AdcChannel, ErrorKind, ErrorType, Error}; +/// # +/// struct MySpinningAdc; +/// +/// impl MySpinningAdc { +/// pub fn is_ready(&mut self) -> bool { +/// // Just pretend this returns `false` the first few times. +/// true +/// } +/// +/// pub fn data(&mut self) -> u32 { +/// 42 +/// } +/// } +/// +/// impl ErrorType for MySpinningAdc { +/// type Error = ErrorKind; +/// } +/// +/// impl AdcChannel for MySpinningAdc { +/// async fn read(&mut self) -> Result { +/// while !self.is_ready() { +/// core::hint::spin_loop(); +/// } +/// +/// Ok(self.data()) +/// } +/// } +/// ``` +/// +/// The second example assumes an ADC that supports a “ready pin” which implements [`Wait`](crate::digital::Wait). +/// When the “ready pin” goes high, data is ready. +/// +/// ``` +/// # use embedded_hal_async::{adc::{self, ErrorKind, ErrorType, Error, AdcChannel}, digital::{self, Wait, Error as _, ErrorType as _}}; +/// # +/// struct MyWaitingAdc { +/// ready_pin: T, +/// }; +/// +/// impl MyWaitingAdc { +/// pub fn data(&mut self) -> u32 { +/// 42 +/// } +/// } +/// +/// impl adc::ErrorType for MyWaitingAdc { +/// type Error = adc::ErrorKind; +/// } +/// +/// impl AdcChannel for MyWaitingAdc { +/// async fn read(&mut self) -> Result { +/// match self.ready_pin.wait_for_high().await { +/// Ok(()) => (), +/// Err(err) => return Err(match err.kind() { +/// digital::ErrorKind::Other => adc::ErrorKind::Other, +/// _ => adc::ErrorKind::Other, +/// }) +/// } +/// +/// Ok(self.data()) +/// } +/// } +/// ``` +pub trait AdcChannel: ErrorType { + /// Reads data from the ADC. + /// + /// # Note for Implementers + /// + /// This should wait until data is ready and then read it. + /// If the ADC's precision is less than 32 bits, the value must be scaled accordingly. + async fn read(&mut self) -> Result; +} + +impl AdcChannel for &mut T +where + T: AdcChannel + ?Sized, +{ + #[inline] + async fn read(&mut self) -> Result { + (*self).read().await + } +} + +#[cfg(test)] +mod test { + use super::*; + + /// Scale an integer containing `bits` bits to 32 bits. + fn scale_bits(raw_data: u32, bits: u32) -> u32 { + let mut scaled_data: u32 = 0; + + let mut remaining_bits = u32::BITS; + while remaining_bits > 0 { + let shl = bits.min(remaining_bits); + scaled_data = (scaled_data.wrapping_shl(shl)) | (raw_data.wrapping_shr(bits - shl)); + remaining_bits -= shl; + } + + scaled_data + } + + #[test] + fn scale_bits_i8_to_i32() { + let raw_data = u32::from(i8::MIN as u8); + let scaled_data = scale_bits(raw_data, 8); + assert!(i32::MIN <= (scaled_data as i32) && (scaled_data as i32) <= (i32::MIN + 1 << 8)); + } + + macro_rules! impl_adc { + ($Adc:ident, $bits:literal, $uint:ty) => { + struct $Adc($uint); + + impl $Adc { + const MAX: $uint = !(<$uint>::MAX.wrapping_shl($bits - 1).wrapping_shl(1)); + + pub fn data(&mut self) -> $uint { + self.0 + } + } + + impl ErrorType for $Adc { + type Error = core::convert::Infallible; + } + + impl AdcChannel for $Adc { + async fn read(&mut self) -> Result { + Ok(scale_bits(u32::from(self.data()), $bits)) + } + } + }; + } + + macro_rules! test_adc { + ($Adc:ident, $bits:literal, $uint:ty) => {{ + impl_adc!($Adc, $bits, $uint); + + // 0 should always be scaled to 0. + let mut adc_0 = $Adc(0); + assert_eq!(adc_0.read().await, Ok(0)); + + // `$Adc::MAX` should always be scaled to `u32::MAX`. + let mut adc_max = $Adc($Adc::MAX); + assert_eq!(adc_max.read().await, Ok(u32::MAX)); + }}; + } + + #[tokio::test] + async fn test_8_bit() { + test_adc!(Adc8, 8, u8); + } + + #[tokio::test] + async fn test_12_bit() { + test_adc!(Adc12, 12, u16); + } + + #[tokio::test] + async fn test_16_bit() { + test_adc!(Adc16, 16, u16); + } + + #[tokio::test] + async fn test_24_bit() { + test_adc!(Adc24, 24, u32); + } + + #[tokio::test] + async fn test_32_bit() { + test_adc!(Adc32, 32, u32); + } +} diff --git a/embedded-hal-async/src/lib.rs b/embedded-hal-async/src/lib.rs index 44901dec..b6d504d7 100644 --- a/embedded-hal-async/src/lib.rs +++ b/embedded-hal-async/src/lib.rs @@ -9,6 +9,7 @@ #![cfg_attr(nightly, feature(async_fn_in_trait, impl_trait_projections))] #![allow(async_fn_in_trait)] +pub mod adc; pub mod delay; pub mod digital; pub mod i2c; diff --git a/embedded-hal/src/adc.rs b/embedded-hal/src/adc.rs new file mode 100644 index 00000000..c2885716 --- /dev/null +++ b/embedded-hal/src/adc.rs @@ -0,0 +1,112 @@ +//! Blocking analog-digital conversion traits. + +use core::fmt::Debug; + +#[cfg(feature = "defmt-03")] +use crate::defmt; + +/// Read data from an ADC. +/// +/// # Examples +/// +/// In the first naive example, [`read`](crate::adc::AdcChannel::read) is implemented +/// using a spin loop and only returns once data is ready. +/// +/// ``` +/// use embedded_hal::adc::{AdcChannel, ErrorKind, ErrorType, Error}; +/// +/// struct MySpinningAdc; +/// +/// impl MySpinningAdc { +/// pub fn is_ready(&mut self) -> bool { +/// // Just pretend this returns `false` the first few times. +/// true +/// } +/// +/// pub fn data(&mut self) -> u32 { +/// 42 +/// } +/// } +/// +/// impl ErrorType for MySpinningAdc { +/// type Error = ErrorKind; +/// } +/// +/// impl AdcChannel for MySpinningAdc { +/// fn read(&mut self) -> Result { +/// while !self.is_ready() { +/// core::hint::spin_loop(); +/// } +/// +/// Ok(self.data()) +/// } +/// } +/// ``` +pub trait AdcChannel: ErrorType { + /// Reads data from the ADC. + /// + /// # Note for Implementers + /// + /// This should wait until data is ready and then read it. + /// If the ADC's precision is less than 32 bits, the value must be scaled accordingly. + fn read(&mut self) -> Result; +} + +impl AdcChannel for &mut T +where + T: AdcChannel + ?Sized, +{ + #[inline] + fn read(&mut self) -> Result { + (*self).read() + } +} + +/// ADC error. +pub trait Error: Debug { + /// Convert error to a generic ADC error kind. + /// + /// By using this method, ADC errors freely defined by HAL implementations + /// can be converted to a set of generic ADC errors upon which generic + /// code can act. + fn kind(&self) -> ErrorKind; +} + +impl Error for core::convert::Infallible { + #[inline] + fn kind(&self) -> ErrorKind { + match *self {} + } +} + +/// ADC error kind. +/// +/// This represents a common set of ADC operation errors. HAL implementations are +/// free to define more specific or additional error types. However, by providing +/// a mapping to these common ADC errors, generic code can still react to them. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] +#[non_exhaustive] +pub enum ErrorKind { + /// A different error occurred. The original error may contain more information. + Other, +} + +impl Error for ErrorKind { + #[inline] + fn kind(&self) -> ErrorKind { + *self + } +} + +/// ADC error type trait. +/// +/// This just defines the error type, to be used by the other ADC traits. +pub trait ErrorType { + /// Error type. + type Error: Error; +} + +impl ErrorType for &mut T { + type Error = T::Error; +} diff --git a/embedded-hal/src/lib.rs b/embedded-hal/src/lib.rs index f5eb76c3..4a65fad4 100644 --- a/embedded-hal/src/lib.rs +++ b/embedded-hal/src/lib.rs @@ -2,6 +2,7 @@ #![warn(missing_docs)] #![no_std] +pub mod adc; pub mod delay; pub mod digital; pub mod i2c;