Expand description
I²C Driver Service
This module contains a service definition for drivers for the I²C bus.
§About I²C
I²C, according to the RP2040 datasheet, is “an ubiquitous serial bus first described in the Dead Sea Scrolls, and later used by Philips Semiconductor”. It’s a two-wire, multi-drop bus, allowing multiple devices to be connected to a single clock and data line.
Unlike SPI, this is a “real protocol”, and not just a sort of shared hallucination about the meanings of certain wires. That means it has rules. Some of these rules are relevant to users of this module. In particular:
- I²C has a first-class notion of controller and target devices. The bus has a single controller (formerly referred to as the “master”), which initiates all bus operations. All other devices are targets (formerly, insensitively referred to as “slaves”), which may only respond to operations that target their address. The interfaces in this module assume that the MnemOS kernel is running on the device acting as the bus controller.
- In order to communicate with a target device, the controller must first
send a
START
condition on the bus. When the controller has finished communicating with that device, it will send aSTOP
condition. If the controller completes a read or write operation and wishes to perform additional read or write operations with the same device, it may instead send a repeatedSTART
condition. Therefore, whether a bus operation should end with aSTOP
or with aSTART
depends on whether the user intends to perform additional operations with that device as part of the same transaction. TheTransaction
interface in this module allows the user to indicate whether a read or write operation should end the bus transaction. Theembedded_hal_async::i2c::I2c
trait also has anI2c::transaction
method, which may be used to perform multiple read and write operations within the same transaction.
§Usage
Users of the I²C bus will primarily interact with this module
using the I2cClient
type, which implements a client for the
I2cService
service. This client type can be used to perform read and
write operations on the I²C bus. A new client can be acquired
using I2cClient::from_registry
.
Once an I2cClient
has been obtained, it can be used to perform
I²C operations. Two interfaces are available: an
implementation of the embedded_hal_async::i2c::I2c
trait,
and a lower-level interface using the I2cClient::start_transaction
method. In general, the embedded_hal_async::i2c::I2c
trait is the
recommended interface.
The lower-level interface allows reusing the same heap-allocated buffer for
multiple I²C bus transactions. It also provides the ability to
interleave other code between the write and read operations of an
I²C transaction without sending a STOP condition. If either of
these are necessary, the Transaction
interface may be preferred over the
embedded_hal_async
interface.
§On Buffer Reuse
Because of mnemOS’ message-passing design, the I2cService
operates
with owned buffers, rather than borrowed buffers, so a FixedVec
<u8>
is
used as the buffer type for both read and write operations. This means
that we must allocate when performing I²C operations. To
reduce the amount of allocation necessary, all Transaction
methods
return the buffer that was passed in, allowing the buffer to be reused
for multiple operations.
To facilitate this, the Transaction::read
method also takes a
len
parameter indicating the actual number of bytes to read into
the buffer, rather than always filling the entire buffer with
bytes. This way, we can size the buffer to the largest buffer
required for a sequence of operations, but perform smaller reads and
writes using the same FixedVec
<u8>
, avoiding reallocations.
The implementation of embedded_hal_async::i2c::I2c::transaction
will allocate a single buffer large enough for the largest operation
in the transaction, and reuse that buffer for every operation within
the transaction.
Note that the Transaction::write
method does not need to take a len
parameter, and will always write all bytes currently in the buffer. The
len
parameter is only needed for Transaction::read
, because reads are
limited by the buffer’s total capacity, rather than the current length of
the initialized portion.
Modules§
- Messages used to communicate with an
I2cService
implementation.
Structs§
- A client for the
I2cService
. - Errors returned by the
I2cService
- Service definition for I²C bus drivers.
- A transaction on the I²C bus.
Enums§
- I²C bus address, in either 7-bit or 10-bit address format.