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 @@ + + + +
SDA
SDA
SCL
SCL
MCU
MCU
SDA
SDA
SCL
SCL
I2C DEVICE 1
I2C DEVICE 1
SDA
SDA
SCL
SCL
I2C DEVICE 2
I2C DEVICE 2
Text is not SVG - cannot display
\ 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 {} -}