Skip to content
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

Backport non-breaking changes from v1.0 to v0.2.x #301

Merged
merged 4 commits into from Aug 1, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
206 changes: 187 additions & 19 deletions src/blocking/i2c.rs
@@ -1,12 +1,122 @@
//! Blocking I2C API
//!
//! Slave addresses used by this API are 7-bit I2C addresses ranging from 0 to 127.
//! This API supports 7-bit and 10-bit addresses. Traits feature an `AddressMode`
//! marker type parameter. Two implementation of the `AddressMode` exist:
//! `SevenBitAddress` and `TenBitAddress`.
//!
//! Operations on 10-bit slave addresses are not supported by the API yet (but applications might
//! be able to emulate some operations).
//! Through this marker types it is possible to implement each address mode for
//! the traits independently in `embedded-hal` implementations and device drivers
//! can depend only on the mode that they support.
//!
//! Additionally, the I2C 10-bit address mode has been developed to be fully
//! backwards compatible with the 7-bit address mode. This allows for a
//! software-emulated 10-bit addressing implementation if the address mode
//! is not supported by the hardware.
//!
//! Since 7-bit addressing is the mode of the majority of I2C devices,
//! `SevenBitAddress` has been set as default mode and thus can be omitted if desired.
//!
//! ## Examples
//!
//! ### `embedded-hal` implementation for an MCU
//! Here is an example of an embedded-hal implementation of the `Write` trait
//! for both modes:
//! ```
//! # use embedded_hal::blocking::i2c::{SevenBitAddress, TenBitAddress, Write};
//! /// I2C0 hardware peripheral which supports both 7-bit and 10-bit addressing.
//! pub struct I2c0;
//!
//! impl Write<SevenBitAddress> for I2c0
//! {
//! # type Error = ();
//! #
//! fn write(&mut self, addr: u8, output: &[u8]) -> Result<(), Self::Error> {
//! // ...
//! # Ok(())
//! }
//! }
//!
//! impl Write<TenBitAddress> for I2c0
//! {
//! # type Error = ();
//! #
//! fn write(&mut self, addr: u16, output: &[u8]) -> Result<(), Self::Error> {
//! // ...
//! # Ok(())
//! }
//! }
//! ```
//!
//! ### Device driver compatible only with 7-bit addresses
//!
//! For demonstration purposes the address mode parameter has been omitted in this example.
//!
//! ```
//! # use embedded_hal::blocking::i2c::WriteRead;
//! const ADDR: u8 = 0x15;
//! # const TEMP_REGISTER: u8 = 0x1;
//! pub struct TemperatureSensorDriver<I2C> {
//! i2c: I2C,
//! }
//!
//! impl<I2C, E> TemperatureSensorDriver<I2C>
//! where
//! I2C: WriteRead<Error = E>,
//! {
//! pub fn read_temperature(&mut self) -> Result<u8, E> {
//! let mut temp = [0];
//! self.i2c
//! .write_read(ADDR, &[TEMP_REGISTER], &mut temp)
//! .and(Ok(temp[0]))
//! }
//! }
//! ```
//!
//! ### Device driver compatible only with 10-bit addresses
//!
//! ```
//! # use embedded_hal::blocking::i2c::{TenBitAddress, WriteRead};
//! const ADDR: u16 = 0x158;
//! # const TEMP_REGISTER: u8 = 0x1;
//! pub struct TemperatureSensorDriver<I2C> {
//! i2c: I2C,
//! }
//!
//! impl<I2C, E> TemperatureSensorDriver<I2C>
//! where
//! I2C: WriteRead<TenBitAddress, Error = E>,
//! {
//! pub fn read_temperature(&mut self) -> Result<u8, E> {
//! let mut temp = [0];
//! self.i2c
//! .write_read(ADDR, &[TEMP_REGISTER], &mut temp)
//! .and(Ok(temp[0]))
//! }
//! }
//! ```

use crate::private;

impl private::Sealed for SevenBitAddress {}
impl private::Sealed for TenBitAddress {}

/// Address mode (7-bit / 10-bit)
///
/// Note: This trait is sealed and should not be implemented outside of this crate.
pub trait AddressMode: private::Sealed {}

/// 7-bit address mode type
pub type SevenBitAddress = u8;

/// 10-bit address mode type
pub type TenBitAddress = u16;

impl AddressMode for SevenBitAddress {}

impl AddressMode for TenBitAddress {}

/// Blocking read
pub trait Read {
pub trait Read<A: AddressMode = SevenBitAddress> {
/// Error type
type Error;

Expand All @@ -28,15 +138,15 @@ pub trait Read {
/// - `MAK` = master acknowledge
/// - `NMAK` = master no acknowledge
/// - `SP` = stop condition
fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error>;
fn read(&mut self, address: A, buffer: &mut [u8]) -> Result<(), Self::Error>;
}

/// Blocking write
pub trait Write {
pub trait Write<A: AddressMode = SevenBitAddress> {
/// Error type
type Error;

/// Sends bytes to slave with address `addr`
/// Writes bytes to slave with address `address`
///
/// # I2C Events (contract)
///
Expand All @@ -52,31 +162,30 @@ pub trait Write {
/// - `SAK` = slave acknowledge
/// - `Bi` = ith byte of data
/// - `SP` = stop condition
fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error>;
fn write(&mut self, address: A, bytes: &[u8]) -> Result<(), Self::Error>;
}

/// Blocking write (iterator version)
#[cfg(feature = "unproven")]
pub trait WriteIter {
pub trait WriteIter<A: AddressMode = SevenBitAddress> {
/// Error type
type Error;

/// Sends bytes to slave with address `addr`
/// Writes bytes to slave with address `address`
///
/// # I2C Events (contract)
///
/// Same as `Write`
fn write<B>(&mut self, addr: u8, bytes: B) -> Result<(), Self::Error>
fn write<B>(&mut self, address: A, bytes: B) -> Result<(), Self::Error>
where
B: IntoIterator<Item = u8>;
}

/// Blocking write + read
pub trait WriteRead {
pub trait WriteRead<A: AddressMode = SevenBitAddress> {
/// Error type
type Error;

/// Sends bytes to slave with address `addr` and then reads enough bytes to fill `buffer` *in a
/// Writes bytes to slave with address `address` and then reads enough bytes to fill `buffer` *in a
/// single transaction*
///
/// # I2C Events (contract)
Expand All @@ -100,30 +209,89 @@ pub trait WriteRead {
/// - `SP` = stop condition
fn write_read(
&mut self,
address: u8,
address: A,
bytes: &[u8],
buffer: &mut [u8],
) -> Result<(), Self::Error>;
}

/// Blocking write (iterator version) + read
#[cfg(feature = "unproven")]
pub trait WriteIterRead {
pub trait WriteIterRead<A: AddressMode = SevenBitAddress> {
/// Error type
type Error;

/// Sends bytes to slave with address `addr` and then reads enough bytes to fill `buffer` *in a
/// Writes bytes to slave with address `address` and then reads enough bytes to fill `buffer` *in a
/// single transaction*
///
/// # I2C Events (contract)
///
/// Same as the `WriteRead` trait
fn write_iter_read<B>(
&mut self,
address: u8,
address: A,
bytes: B,
buffer: &mut [u8],
) -> Result<(), Self::Error>
where
B: IntoIterator<Item = u8>;
}

/// Transactional I2C operation.
///
/// Several operations can be combined as part of a transaction.
#[derive(Debug, PartialEq)]
pub enum Operation<'a> {
/// Read data into the provided buffer
Read(&'a mut [u8]),
/// Write data from the provided buffer
Write(&'a [u8]),
}

/// Transactional I2C interface.
///
/// This allows combining operations within an I2C transaction.
pub trait Transactional<A: AddressMode = SevenBitAddress> {
/// Error type
type Error;

/// Execute the provided operations on the I2C bus.
///
/// Transaction contract:
/// - Before executing the first operation an ST is sent automatically. This is followed by SAD+R/W as appropriate.
/// - Data from adjacent operations of the same type are sent after each other without an SP or SR.
/// - Between adjacent operations of a different type an SR and SAD+R/W is sent.
/// - After executing the last operation an SP is sent automatically.
/// - If the last operation is a `Read` the master does not send an acknowledge for the last byte.
///
/// - `ST` = start condition
/// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing
/// - `SR` = repeated start condition
/// - `SP` = stop condition
fn exec<'a>(&mut self, address: A, operations: &mut [Operation<'a>])
-> Result<(), Self::Error>;
}

/// Transactional I2C interface (iterator version).
///
/// This allows combining operation within an I2C transaction.
pub trait TransactionalIter<A: AddressMode = SevenBitAddress> {
/// Error type
type Error;

/// Execute the provided operations on the I2C bus (iterator version).
///
/// Transaction contract:
/// - Before executing the first operation an ST is sent automatically. This is followed by SAD+R/W as appropriate.
/// - Data from adjacent operations of the same type are sent after each other without an SP or SR.
/// - Between adjacent operations of a different type an SR and SAD+R/W is sent.
/// - After executing the last operation an SP is sent automatically.
/// - If the last operation is a `Read` the master does not send an acknowledge for the last byte.
///
/// - `ST` = start condition
/// - `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing
/// - `SR` = repeated start condition
/// - `SP` = stop condition
fn exec_iter<'a, O>(&mut self, address: A, operations: O) -> Result<(), Self::Error>
where
O: IntoIterator<Item = Operation<'a>>;
}
21 changes: 21 additions & 0 deletions src/blocking/spi.rs
Expand Up @@ -104,3 +104,24 @@ pub mod write_iter {
}
}
}

/// Operation for transactional SPI trait
///
/// This allows composition of SPI operations into a single bus transaction
#[derive(Debug, PartialEq)]
pub enum Operation<'a, W: 'static> {
/// Write data from the provided buffer, discarding read data
Write(&'a [W]),
/// Write data out while reading data into the provided buffer
Transfer(&'a mut [W]),
}

/// Transactional trait allows multiple actions to be executed
/// as part of a single SPI transaction
pub trait Transactional<W: 'static> {
/// Associated error type
type Error;

/// Execute the provided transactions
fn exec<'a>(&mut self, operations: &mut [Operation<'a, W>]) -> Result<(), Self::Error>;
}
78 changes: 78 additions & 0 deletions src/digital/v2.rs
Expand Up @@ -2,6 +2,46 @@
//!
//! Version 2 / fallible traits. Infallible implementations should set Error to `!`.

use core::{convert::From, ops::Not};

/// Digital output pin state
///
/// Conversion from `bool` and logical negation are also implemented
/// for this type.
/// ```rust
/// # use embedded_hal::digital::v2::PinState;
/// let state = PinState::from(false);
/// assert_eq!(state, PinState::Low);
/// assert_eq!(!state, PinState::High);
/// ```
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum PinState {
/// Low pin state
Low,
/// High pin state
High,
}

impl From<bool> for PinState {
fn from(value: bool) -> Self {
match value {
false => PinState::Low,
true => PinState::High,
}
}
}

impl Not for PinState {
type Output = PinState;

fn not(self) -> Self::Output {
match self {
PinState::High => PinState::Low,
PinState::Low => PinState::High,
}
}
}

/// Single digital push-pull output pin
pub trait OutputPin {
/// Error type
Expand All @@ -18,6 +58,17 @@ pub trait OutputPin {
/// *NOTE* the actual electrical state of the pin may not actually be high, e.g. due to external
/// electrical sources
fn set_high(&mut self) -> Result<(), Self::Error>;

/// Drives the pin high or low depending on the provided value
///
/// *NOTE* the actual electrical state of the pin may not actually be high or low, e.g. due to external
/// electrical sources
fn set_state(&mut self, state: PinState) -> Result<(), Self::Error> {
match state {
PinState::Low => self.set_low(),
PinState::High => self.set_high(),
}
}
}

/// Push-pull output pin that can read its output state
Expand Down Expand Up @@ -136,3 +187,30 @@ pub trait InputPin {
/// Is the input pin low?
fn is_low(&self) -> Result<bool, Self::Error>;
}

/// Single pin that can switch from input to output mode, and vice-versa.
///
/// Example use (assumes the `Error` type is the same for the `IoPin`,
/// `InputPin`, and `OutputPin`):
///
/// *This trait is available if embedded-hal is built with the `"unproven"` feature.*
#[cfg(feature = "unproven")]
pub trait IoPin<TInput, TOutput>
where
TInput: InputPin + IoPin<TInput, TOutput>,
TOutput: OutputPin + IoPin<TInput, TOutput>,
{
/// Error type.
type Error;

/// Tries to convert this pin to input mode.
///
/// If the pin is already in input mode, this method should succeed.
fn into_input_pin(self) -> Result<TInput, Self::Error>;

/// Tries to convert this pin to output mode with the given initial state.
///
/// If the pin is already in the requested state, this method should
/// succeed.
fn into_output_pin(self, state: PinState) -> Result<TOutput, Self::Error>;
}