From 77b262dfaf03742bf4edf69d536d1adb6d0a3be9 Mon Sep 17 00:00:00 2001 From: Andrey Zgarbul Date: Sat, 31 Jul 2021 08:07:24 +0300 Subject: [PATCH 1/4] backport i2c --- src/blocking/i2c.rs | 206 ++++++++++++++++++++++++++++++++++++++++---- src/lib.rs | 4 + 2 files changed, 191 insertions(+), 19 deletions(-) diff --git a/src/blocking/i2c.rs b/src/blocking/i2c.rs index 57192cac8..60bb1538d 100644 --- a/src/blocking/i2c.rs +++ b/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 for I2c0 +//! { +//! # type Error = (); +//! # +//! fn write(&mut self, addr: u8, output: &[u8]) -> Result<(), Self::Error> { +//! // ... +//! # Ok(()) +//! } +//! } +//! +//! impl Write 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, +//! } +//! +//! impl TemperatureSensorDriver +//! where +//! I2C: WriteRead, +//! { +//! pub fn read_temperature(&mut self) -> Result { +//! 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, +//! } +//! +//! impl TemperatureSensorDriver +//! where +//! I2C: WriteRead, +//! { +//! pub fn read_temperature(&mut self) -> Result { +//! 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 { /// Error type type Error; @@ -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 { /// Error type type Error; - /// Sends bytes to slave with address `addr` + /// Writes bytes to slave with address `address` /// /// # I2C Events (contract) /// @@ -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 { /// 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(&mut self, addr: u8, bytes: B) -> Result<(), Self::Error> + fn write(&mut self, address: A, bytes: B) -> Result<(), Self::Error> where B: IntoIterator; } /// Blocking write + read -pub trait WriteRead { +pub trait WriteRead { /// 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) @@ -100,19 +209,18 @@ 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 { /// 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) @@ -120,10 +228,70 @@ pub trait WriteIterRead { /// Same as the `WriteRead` trait fn write_iter_read( &mut self, - address: u8, + address: A, bytes: B, buffer: &mut [u8], ) -> Result<(), Self::Error> where B: IntoIterator; } + +/// 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 { + /// 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 { + /// 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>; +} diff --git a/src/lib.rs b/src/lib.rs index be6c954b0..e5db66af1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -992,3 +992,7 @@ pub enum Direction { /// 1, 2, 3 Upcounting, } + +mod private { + pub trait Sealed {} +} From 1ef9f26b3ea025c07418325d953c821ef1eaff54 Mon Sep 17 00:00:00 2001 From: Andrey Zgarbul Date: Sat, 31 Jul 2021 08:37:30 +0300 Subject: [PATCH 2/4] backport digital --- src/digital/v2.rs | 78 ++++++++++++++++++++++++++++++++++++++++ src/digital/v2_compat.rs | 6 ++++ 2 files changed, 84 insertions(+) diff --git a/src/digital/v2.rs b/src/digital/v2.rs index d539606fb..81fd67cbb 100644 --- a/src/digital/v2.rs +++ b/src/digital/v2.rs @@ -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 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 @@ -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 @@ -136,3 +187,30 @@ pub trait InputPin { /// Is the input pin low? fn is_low(&self) -> Result; } + +/// 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 +where + TInput: InputPin + IoPin, + TOutput: OutputPin + IoPin, +{ + /// 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; + + /// 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; +} diff --git a/src/digital/v2_compat.rs b/src/digital/v2_compat.rs index 9c87271c8..929f413dd 100644 --- a/src/digital/v2_compat.rs +++ b/src/digital/v2_compat.rs @@ -116,6 +116,7 @@ mod tests { } #[allow(deprecated)] + #[cfg(feature = "unproven")] impl v1::StatefulOutputPin for OldOutputPinImpl { fn is_set_low(&self) -> bool { self.state == false @@ -127,6 +128,7 @@ mod tests { } #[allow(deprecated)] + #[cfg(feature = "unproven")] impl v1::toggleable::Default for OldOutputPinImpl {} struct NewOutputPinConsumer { @@ -142,10 +144,12 @@ mod tests { } } + #[cfg(feature = "unproven")] struct NewToggleablePinConsumer { _pin: T, } + #[cfg(feature = "unproven")] impl NewToggleablePinConsumer where T: v2::ToggleableOutputPin, @@ -156,6 +160,7 @@ mod tests { } #[test] + #[cfg(feature = "unproven")] fn v2_v1_toggleable_implicit() { let i = OldOutputPinImpl { state: false }; let _c = NewToggleablePinConsumer::new(i); @@ -212,6 +217,7 @@ mod tests { #[cfg(feature = "unproven")] #[test] + #[cfg(feature = "unproven")] fn v2_v1_input_implicit() { let i = OldInputPinImpl { state: false }; let _c = NewInputPinConsumer::new(i); From d84543f13a327f8339e71ade34cca94930589d77 Mon Sep 17 00:00:00 2001 From: Andrey Zgarbul Date: Sat, 31 Jul 2021 10:32:12 +0300 Subject: [PATCH 3/4] backport spi::Transactional --- src/blocking/spi.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/blocking/spi.rs b/src/blocking/spi.rs index 84e48029f..76623e290 100644 --- a/src/blocking/spi.rs +++ b/src/blocking/spi.rs @@ -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 { + /// Associated error type + type Error; + + /// Execute the provided transactions + fn exec<'a>(&mut self, operations: &mut [Operation<'a, W>]) -> Result<(), Self::Error>; +} From 769b933390219de9329a680ab837ec03b6e91477 Mon Sep 17 00:00:00 2001 From: Andrey Zgarbul Date: Sun, 1 Aug 2021 11:14:36 +0300 Subject: [PATCH 4/4] update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21310a4c9..103f51115 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added +- `Transactional` SPI interface for executing groups of SPI transactions. +- `Transactional` I2C interface for executing groups of I2C transactions. +- 10-bit addressing mode for I2C traits. +- `set_state` method for `OutputPin` using an input `PinState` value. +- `IoPin` trait for pins that can change between being inputs or outputs + dynamically. ### Changed