Skip to content

Commit

Permalink
Add AdcChannel trait.
Browse files Browse the repository at this point in the history
  • Loading branch information
reitermarkus committed Jan 17, 2024
1 parent b28c64e commit 4302795
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 0 deletions.
1 change: 1 addition & 0 deletions embedded-hal-async/Cargo.toml
Expand Up @@ -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"] }
184 changes: 184 additions & 0 deletions 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<u32, Self::Error> {
/// 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<T> {
/// ready_pin: T,
/// };
///
/// impl<T> MyWaitingAdc<T> {
/// pub fn data(&mut self) -> u32 {
/// 42
/// }
/// }
///
/// impl<T> adc::ErrorType for MyWaitingAdc<T> {
/// type Error = adc::ErrorKind;
/// }
///
/// impl<T: Wait> AdcChannel for MyWaitingAdc<T> {
/// async fn read(&mut self) -> Result<u32, Self::Error> {
/// 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<u32, Self::Error>;
}

impl<T> AdcChannel for &mut T
where
T: AdcChannel + ?Sized,
{
#[inline]
async fn read(&mut self) -> Result<u32, Self::Error> {
(*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<u32, Self::Error> {
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);
}
}
1 change: 1 addition & 0 deletions embedded-hal-async/src/lib.rs
Expand Up @@ -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;
Expand Down
109 changes: 109 additions & 0 deletions embedded-hal/src/adc.rs
@@ -0,0 +1,109 @@
//! Blocking analog-digital conversion traits.

use core::fmt::Debug;

/// 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<u32, Self::Error> {
/// 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<u32, Self::Error>;
}

impl<T> AdcChannel for &mut T
where
T: AdcChannel + ?Sized,
{
#[inline]
fn read(&mut self) -> Result<u32, Self::Error> {
(*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<T: ErrorType + ?Sized> ErrorType for &mut T {
type Error = T::Error;
}
1 change: 1 addition & 0 deletions embedded-hal/src/lib.rs
Expand Up @@ -2,6 +2,7 @@
#![warn(missing_docs)]
#![no_std]

pub mod adc;
pub mod delay;
pub mod digital;
pub mod i2c;
Expand Down

0 comments on commit 4302795

Please sign in to comment.