Expand description
Blocking I2C API.
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
.
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.
§Bus sharing
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.
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
).
This API supports bus sharing natively. Types implementing I2c
are allowed
to represent either exclusive or shared access to an I2C bus. HALs typically
provide exclusive access implementations. Drivers shouldn’t care which
kind they receive, they just do transactions on it and let the
underlying implementation share or not.
The embedded-hal-bus
crate provides several
implementations for sharing I2C buses. You can use them to take an exclusive instance
you’ve received from a HAL and “split” it into multiple shared ones, to instantiate
several drivers on the same bus.
§Flushing
Implementations must flush the transfer, ensuring the bus has returned to an idle state before returning. No pipelining is allowed. Users must be able to shut down the I2C peripheral immediately after a transfer returns, without any risk of e.g. cutting short a stop condition.
(Implementations must wait until the last ACK bit to report it as an error anyway. Therefore pipelining would only yield very small time savings, not worth the complexity)
§For driver authors
Drivers can select the adequate address length with I2c<SevenBitAddress>
or I2c<TenBitAddress>
depending
on the target device. If it can use either, the driver can
be generic over the address kind as well, though this is rare.
Drivers should take the I2c
instance as an argument to new()
, and store it in their
struct. They should not take &mut I2c
, the trait has a blanket impl for all &mut T
so taking just I2c
ensures the user can still pass a &mut
, but is not forced to.
Drivers should not try to enable bus sharing by taking &mut I2c
at every method.
This is much less ergonomic than owning the I2c
, which still allows the user to pass an
implementation that does sharing behind the scenes
(from embedded-hal-bus
, or others).
§Device driver compatible only with 7-bit addresses
For demonstration purposes the address mode parameter has been omitted in this example.
use embedded_hal::i2c::{I2c, Error};
const ADDR: u8 = 0x15;
pub struct TemperatureSensorDriver<I2C> {
i2c: I2C,
}
impl<I2C: I2c> TemperatureSensorDriver<I2C> {
pub fn new(i2c: I2C) -> Self {
Self { i2c }
}
pub fn read_temperature(&mut self) -> Result<u8, I2C::Error> {
let mut temp = [0];
self.i2c.write_read(ADDR, &[TEMP_REGISTER], &mut temp)?;
Ok(temp[0])
}
}
§Device driver compatible only with 10-bit addresses
use embedded_hal::i2c::{Error, TenBitAddress, I2c};
const ADDR: u16 = 0x158;
pub struct TemperatureSensorDriver<I2C> {
i2c: I2C,
}
impl<I2C: I2c<TenBitAddress>> TemperatureSensorDriver<I2C> {
pub fn new(i2c: I2C) -> Self {
Self { i2c }
}
pub fn read_temperature(&mut self) -> Result<u8, I2C::Error> {
let mut temp = [0];
self.i2c.write_read(ADDR, &[TEMP_REGISTER], &mut temp)?;
Ok(temp[0])
}
}
§For HAL authors
HALs should not include bus sharing mechanisms. They should expose a single type representing
exclusive ownership over the bus, and let the user use embedded-hal-bus
if they want to share it. (One exception is if the underlying platform already
supports sharing, such as Linux or some RTOSs.)
Here is an example of an embedded-hal implementation of the I2C
trait
for both addressing modes. All trait methods have have default implementations in terms of transaction
.
As such, that is the only method that requires implementation in the HAL.
use embedded_hal::i2c::{self, SevenBitAddress, TenBitAddress, I2c, Operation};
/// I2C0 hardware peripheral which supports both 7-bit and 10-bit addressing.
pub struct I2c0;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Error {
// ...
}
impl i2c::Error for Error {
fn kind(&self) -> i2c::ErrorKind {
match *self {
// ...
}
}
}
impl i2c::ErrorType for I2c0 {
type Error = Error;
}
impl I2c<SevenBitAddress> for I2c0 {
fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> {
// ...
}
}
impl I2c<TenBitAddress> for I2c0 {
fn transaction(&mut self, address: u16, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> {
// ...
}
}
Enums§
- I2C error kind.
- I2C no acknowledge error source.
- I2C operation.
Traits§
- Address mode (7-bit / 10-bit).
- I2C error.
- I2C error type trait.
- Blocking I2C.
Type Aliases§
- 7-bit address mode type.
- 10-bit address mode type.