Module kernel::services::i2c

source ·
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 a STOP 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 repeated START condition. Therefore, whether a bus operation should end with a STOP or with a START depends on whether the user intends to perform additional operations with that device as part of the same transaction. The Transaction interface in this module allows the user to indicate whether a read or write operation should end the bus transaction. The embedded_hal_async::i2c::I2c trait also has an I2c::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§

Structs§

Enums§

  • I²C bus address, in either 7-bit or 10-bit address format.
  • ErrorKind 🔒