diff --git a/src/i2c-shared-bus.svg b/src/i2c-shared-bus.svg
new file mode 100644
index 000000000..fad8cee6c
--- /dev/null
+++ b/src/i2c-shared-bus.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/i2c.rs b/src/i2c.rs
index 9b6298da4..4c4af4feb 100644
--- a/src/i2c.rs
+++ b/src/i2c.rs
@@ -1,5 +1,81 @@
//! Blocking I2C API
//!
+//! # Bus vs Device
+//!
+//! I2C allows sharing a single bus between many I2C devices. The SDA and SCL lines are
+//! wired in parallel to all devices. When starting a transfer an "address" is sent
+//! so that the addressed device can respond and all the others can ignore the transfer.
+//!
+#![doc= include_str!("i2c-shared-bus.svg")]
+//!
+//! This bus sharing is common when having multiple I2C devices in the same board, since it uses fewer MCU
+//! pins (`2` instead of `2*n`), and fewer MCU I2C peripherals (`1` instead of `n`).
+//!
+//! However, it poses a challenge when building portable drivers for I2C devices. The driver needs to
+//! be able to talk to its device on the bus, while not interfering with other drivers talking to other
+//! devices.
+//!
+//! To solve this, `embedded-hal` has two kinds of I2C traits: **I2C bus** and **I2C device**.
+//!
+//! ## Bus
+//!
+//! I2C bus traits represent **exclusive ownership** over the whole I2C bus. This is usually the entire
+//! I2C MCU peripheral, plus the SDA and SCL pins.
+//!
+//! Owning an instance of an I2C bus guarantees exclusive access, this is, we have the guarantee no other
+//! piece of code will try to use the bus while we own it.
+//!
+//! ## Device
+//!
+//! [`I2cDevice`] represents **ownership over a single I2C device** in a (possibly shared) bus. This consists of
+//! access to the **underlying I2C bus**. If shared, it'll be behind some kind of lock/mutex.
+//!
+//! An [`I2cDevice`] allows initiating [transactions](I2cDevice::transaction) against the target device on the bus. A transaction
+//! consists of several read or write transfers, possibly with multiple start, stop or repeated start conditions.
+//!
+//! For the entire duration of the transaction, the [`I2cDevice`] implementation will ensure no other transaction
+//! can be opened on the same bus. This is the key that allows correct sharing of the bus.
+//!
+//! # For driver authors
+//!
+//! When implementing a driver, use [`I2cDevice`], so that your driver cooperates nicely with other
+//! drivers for other devices in the same shared I2C bus.
+//!
+//! ```
+//! // TODO code snippet
+//! ```
+//!
+//! # For HAL authors
+//!
+//! HALs **must** implement [`I2cBusBase`], and [`I2cBus`] for the supported address modes.
+//!
+//! There is little reason for HALs to implement [`I2cDevice`], this task is better left to the HAL-independent implementations).
+//!
+//! HALs **must not** add infrastructure for sharing at the [`I2cBus`] level. User code owning a [`I2cBus`] must have the guarantee
+//! of exclusive access.
+//!
+//! # Flushing
+//!
+//! To improve performance, [`I2cBus`] implementations are allowed to return before the operation is finished, i.e. when the bus is still not
+//! idle.
+//!
+//! When using an [`I2cBus`], call [`flush`](I2cBus::flush) to wait for operations to actually finish. Examples of situations
+//! where this is needed are:
+//! - To synchronize I2C activity and GPIO activity during a transaction.
+//! - Before deinitializing the hardware I2C peripheral.
+//!
+//! When using an [`I2cDevice`], you can still call [`flush`](I2cBus::flush) on the bus within a transaction.
+//! It's very rarely needed, because [`transaction`](I2cDevice::transaction) already flushes the bus for you
+//! when finished.
+//!
+//! For example, for [`write`](I2cBusBase::write) operations, it is common for hardware I2C peripherals to have a small
+//! FIFO buffer, usually 1-4 bytes. Software writes data to the FIFO, and the peripheral sends it on SDA at its own pace,
+//! at the specified I2C frequency. It is allowed for an implementation of [`write`](I2cBusBase::write) to return as soon
+//! as all the data has been written to the FIFO, before it is actually sent. Calling [`flush`](I2cBus::flush) would
+//! wait until all the bits have actually been sent and the FIFO is empty.
+//!
+//! # Addresses
+//!
//! 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`.
@@ -140,7 +216,11 @@
//! }
//! ```
-use crate::private;
+mod private {
+ pub trait Sealed {}
+ impl Sealed for super::SevenBitAddress {}
+ impl Sealed for super::TenBitAddress {}
+}
/// I2C error
pub trait Error: core::fmt::Debug {
@@ -244,7 +324,7 @@ impl ErrorType for &mut T {
/// 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 + 'static {}
+pub trait AddressMode: Copy + private::Sealed + 'static {}
/// 7-bit address mode type
pub type SevenBitAddress = u8;
@@ -256,202 +336,175 @@ impl AddressMode for SevenBitAddress {}
impl AddressMode for TenBitAddress {}
+/// Direction of an I2C transfer.
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum Direction {
+ /// I2C read (RnW bit is 1)
+ Read,
+ /// I2C write (RnW bit is 0)
+ Write,
+}
+
/// Blocking I2C traits
pub mod blocking {
+ use super::*;
- use super::{AddressMode, ErrorType, SevenBitAddress};
-
- /// Transactional I2C operation.
+ /// I2C device trait
///
- /// 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]),
- }
+ /// `I2cDevice` represents ownership over a single I2C device on a (possibly shared) bus.
+ ///
+ /// See the [module-level documentation](self) for important usage information.
+ pub trait I2cDevice: ErrorType {
+ /// I2C Bus type for this device.
+ type Bus: I2cBusBase;
- /// Blocking I2C
- pub trait I2c: ErrorType {
- /// Reads enough bytes from slave with `address` to fill `buffer`
- ///
- /// # I2C Events (contract)
- ///
- /// ``` text
- /// Master: ST SAD+R MAK MAK ... NMAK SP
- /// Slave: SAK B0 B1 ... BN
- /// ```
- ///
- /// Where
- ///
- /// - `ST` = start condition
- /// - `SAD+R` = slave address followed by bit 1 to indicate reading
- /// - `SAK` = slave acknowledge
- /// - `Bi` = ith byte of data
- /// - `MAK` = master acknowledge
- /// - `NMAK` = master no acknowledge
- /// - `SP` = stop condition
- fn read(&mut self, address: A, buffer: &mut [u8]) -> Result<(), Self::Error>;
-
- /// Writes bytes to slave with address `address`
+ /// Perform a transaction against the device.
///
- /// # I2C Events (contract)
+ /// - Locks the bus
+ /// - Calls `f` with an exclusive reference to the bus, which can then be used to do transfers against the device.
+ /// - Does a [stop condition](I2cBus::stop) on the bus.
+ /// - [Flushes](I2cBus::flush) the bus.
+ /// - Unlocks the bus.
///
- /// ``` text
- /// Master: ST SAD+W B0 B1 ... BN SP
- /// Slave: SAK SAK SAK ... SAK
- /// ```
- ///
- /// Where
- ///
- /// - `ST` = start condition
- /// - `SAD+W` = slave address followed by bit 0 to indicate writing
- /// - `SAK` = slave acknowledge
- /// - `Bi` = ith byte of data
- /// - `SP` = stop condition
- fn write(&mut self, address: A, bytes: &[u8]) -> Result<(), Self::Error>;
-
- /// Writes bytes to slave with address `address`
+ /// The locking mechanism is implementation-defined. The only requirement is that it must prevent two
+ /// transactions from executing concurrently on the same bus. Examples of implementations are:
+ /// critical sections, blocking mutexes, returning an error or panicking if the bus is already busy.
+ fn transaction(
+ &mut self,
+ f: impl FnOnce(&mut Self::Bus) -> Result::Error>,
+ ) -> Result;
+
+ /// Do a write within a transaction.
///
- /// # I2C Events (contract)
+ /// This is a convenience method equivalent to `device.transaction(|bus| { bus.start(address, Write); bus.write(buf) })`.
///
- /// Same as the `write` method
- fn write_iter(&mut self, address: A, bytes: B) -> Result<(), Self::Error>
+ /// See also: [`I2cDevice::transaction`], [`I2cBusBase::write`]
+ fn write(&mut self, address: A, buf: &[u8]) -> Result<(), Self::Error>
where
- B: IntoIterator- ;
+ Self::Bus: I2cBus,
+ {
+ self.transaction(|bus| {
+ bus.start(address, Direction::Write)?;
+ bus.write(buf)
+ })
+ }
- /// Writes bytes to slave with address `address` and then reads enough bytes to fill `buffer` *in a
- /// single transaction*
- ///
- /// # I2C Events (contract)
- ///
- /// ``` text
- /// Master: ST SAD+W O0 O1 ... OM SR SAD+R MAK MAK ... NMAK SP
- /// Slave: SAK SAK SAK ... SAK SAK I0 I1 ... IN
- /// ```
+ /// Do a read within a transaction.
///
- /// Where
+ /// This is a convenience method equivalent to `device.transaction(|bus| bus.read(buf))`.
///
- /// - `ST` = start condition
- /// - `SAD+W` = slave address followed by bit 0 to indicate writing
- /// - `SAK` = slave acknowledge
- /// - `Oi` = ith outgoing byte of data
- /// - `SR` = repeated start condition
- /// - `SAD+R` = slave address followed by bit 1 to indicate reading
- /// - `Ii` = ith incoming byte of data
- /// - `MAK` = master acknowledge
- /// - `NMAK` = master no acknowledge
- /// - `SP` = stop condition
- fn write_read(
- &mut self,
- address: A,
- bytes: &[u8],
- buffer: &mut [u8],
- ) -> Result<(), Self::Error>;
+ /// See also: [`I2cDevice::transaction`], [`I2cBusBase::read`]
+ fn read(&mut self, address: A, buf: &mut [u8]) -> Result<(), Self::Error>
+ where
+ Self::Bus: I2cBus,
+ {
+ self.transaction(|bus| {
+ bus.start(address, Direction::Read)?;
+ bus.read(buf)
+ })
+ }
- /// Writes bytes to slave with address `address` and then reads enough bytes to fill `buffer` *in a
- /// single transaction*
+ /// Do a write, restart, read transaction.
///
- /// # I2C Events (contract)
+ /// This is a convenience method equivalent to `device.transaction(|bus| bus.transfer(read, write))`.
///
- /// Same as the `write_read` method
- fn write_iter_read(
+ /// See also: [`I2cDevice::transaction`], [`I2cBus::transfer`]
+ fn write_read(
&mut self,
address: A,
- bytes: B,
- buffer: &mut [u8],
+ write: &[u8],
+ read: &mut [u8],
) -> Result<(), Self::Error>
where
- B: IntoIterator
- ;
+ Self::Bus: I2cBus,
+ {
+ self.transaction(|bus| {
+ bus.start(address, Direction::Write)?;
+ bus.write(write)?;
+ bus.start(address, Direction::Read)?;
+ bus.read(read)
+ })
+ }
+ }
- /// Execute the provided operations on the I2C bus.
+ impl I2cDevice for &mut T {
+ type Bus = T::Bus;
+ fn transaction(
+ &mut self,
+ f: impl FnOnce(&mut Self::Bus) -> Result::Error>,
+ ) -> Result {
+ T::transaction(self, f)
+ }
+ }
+
+ /// I2C bus base trait.
+ ///
+ /// This trait contains the methods that don't depend on the address mode. You will
+ /// likely want to add bounds on [`I2cBus`] instead.
+ pub trait I2cBusBase: ErrorType {
+ /// Read data bytes from the SPI device.
///
- /// 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.
+ /// This returns an error if the bus is not in "started" state, or if it's started for the wrong
+ /// direction. You must have called [`start`](I2cBus::start)
+ /// before calling this method with the correct direction.
+ fn read(&mut self, bytes: &mut [u8]) -> Result<(), Self::Error>;
+
+ /// Write data bytes to the I2C device.
///
- /// - `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 transaction<'a>(
- &mut self,
- address: A,
- operations: &mut [Operation<'a>],
- ) -> Result<(), Self::Error>;
+ /// This is an error if the bus is not in "started" state, or if it's started for the wrong
+ /// direction. You must have called [`start`](I2cBus::start)
+ /// before calling this method with the correct direction.
+ fn write(&mut self, bytes: &[u8]) -> Result<(), Self::Error>;
- /// Execute the provided operations on the I2C bus (iterator version).
+ /// Do a stop condition.
///
- /// 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.
+ /// This is a no-op if the bus is already in "stopped" state.
+ fn stop(&mut self) -> Result<(), Self::Error>;
+
+ /// Wait until all operations have completed, and return all pending errors.
///
- /// - `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 transaction_iter<'a, O>(&mut self, address: A, operations: O) -> Result<(), Self::Error>
- where
- O: IntoIterator
- >;
+ /// See the [module-level documentation](self) for more details on flushing.
+ fn flush(&mut self) -> Result<(), Self::Error>;
}
- impl> I2c for &mut T {
- fn read(&mut self, address: A, buffer: &mut [u8]) -> Result<(), Self::Error> {
- T::read(self, address, buffer)
- }
+ /// I2C bus trait.
+ ///
+ /// This trait is generic on the address mode, and has [`I2cBusBase`] as a supertrait
+ /// for the methods that don't depend on the address mode.
+ pub trait I2cBus: I2cBusBase + ErrorType {
+ /// Do a start or repeated-start condition, and send the address byte(s).
+ ///
+ /// This does a start condition if the bus was in "stopped" state, and a repeated-start
+ /// condition if it was in the "started" state. The bus changes to the "started" state.
+ ///
+ /// Note that implementations are allowed to buffer operations and defer errors. This means
+ /// that a call to `start` returning without an error doesn't necessarily mean the addressed
+ /// device has ACKed the address byte. The NACK error can be reported in later calls instead.
+ /// For more details, see the [module-level documentation](self).
+ fn start(&mut self, address: A, direction: Direction) -> Result<(), Self::Error>;
+ }
- fn write(&mut self, address: A, bytes: &[u8]) -> Result<(), Self::Error> {
- T::write(self, address, bytes)
+ impl I2cBusBase for &mut T {
+ fn read(&mut self, bytes: &mut [u8]) -> Result<(), Self::Error> {
+ T::read(self, bytes)
}
- fn write_iter(&mut self, address: A, bytes: B) -> Result<(), Self::Error>
- where
- B: IntoIterator
- ,
- {
- T::write_iter(self, address, bytes)
+ fn write(&mut self, bytes: &[u8]) -> Result<(), Self::Error> {
+ T::write(self, bytes)
}
- fn write_read(
- &mut self,
- address: A,
- bytes: &[u8],
- buffer: &mut [u8],
- ) -> Result<(), Self::Error> {
- T::write_read(self, address, bytes, buffer)
+ fn stop(&mut self) -> Result<(), Self::Error> {
+ T::stop(self)
}
- fn write_iter_read(
- &mut self,
- address: A,
- bytes: B,
- buffer: &mut [u8],
- ) -> Result<(), Self::Error>
- where
- B: IntoIterator
- ,
- {
- T::write_iter_read(self, address, bytes, buffer)
- }
-
- fn transaction<'a>(
- &mut self,
- address: A,
- operations: &mut [Operation<'a>],
- ) -> Result<(), Self::Error> {
- T::transaction(self, address, operations)
+ fn flush(&mut self) -> Result<(), Self::Error> {
+ T::flush(self)
}
+ }
- fn transaction_iter<'a, O>(&mut self, address: A, operations: O) -> Result<(), Self::Error>
- where
- O: IntoIterator
- >,
- {
- T::transaction_iter(self, address, operations)
+ impl> I2cBus for &mut T {
+ fn start(&mut self, address: A, direction: Direction) -> Result<(), Self::Error> {
+ T::start(self, address, direction)
}
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 30099bf0b..9236dda40 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -361,11 +361,3 @@ pub mod digital;
pub mod i2c;
pub mod serial;
pub mod spi;
-
-mod private {
- use crate::i2c::{SevenBitAddress, TenBitAddress};
- pub trait Sealed {}
-
- impl Sealed for SevenBitAddress {}
- impl Sealed for TenBitAddress {}
-}