Reference#
pw_i2c: Cross-platform I2C API with interactive debugging
Overview#
pw::i2c::Address
is a helper class for representing I2C addresses.pw::i2c::Initiator
is the common, base driver interface for communicating with I2C devices.pw::i2c::Device
is a helper class that takes a reference to anpw::i2c::Initiator
instance and provides easier access to a single I2C device.pw::i2c::RegisterDevice
extendspw::i2c::Device
for easier access to a single I2C device’s registers.pw::i2c::I2cService
is a service for performing I2C transactions over RPC.pw::i2c::MockInitiator
is a generic mocked backend forpw::i2c::Initiator
. It usespw::i2c::Transaction
to represent expected I2C transactions.
pw::i2c::Address
#
-
class Address#
A helper class that represents I2C addresses.
#include "pw_i2c/address.h" constexpr pw::i2c::Address kAddress1 = pw::i2c::Address::SevenBit<0x42>(); uint8_t raw_address_1 = kAddress1.GetSevenBit(); const pw::i2c::Address kAddress2(0x200); // 10-bit uint16_t raw_address_2 = kAddress2.GetTenBit(); // Note: kAddress2.GetSevenBit() would fail an assertion here.
Public Functions
-
explicit Address(uint16_t address)#
Creates a
pw::i2c::Address
instance.constexpr pw::i2c::Address kAddress(0x200);
- Parameters:
address – [in] A 10-bit address as an unsigned integer. This method does a runtime assertion to ensure that
address
is 10 bits or less.- Returns:
A
pw::i2c::Address
instance.
-
uint8_t GetSevenBit() const#
Gets the 7-bit address that was provided when this instance was created.
This method does a runtime assertion to ensure that the address is 7 bits or less.
- Returns:
A 7-bit address as an unsigned integer.
-
inline uint16_t GetTenBit() const#
Gets the 10-bit address that was provided when this instance was created.
- Returns:
A 10-bit address as an unsigned integer.
Public Static Functions
-
template<uint16_t kAddress>
static inline constexpr Address TenBit()# Creates a
pw::i2c::Address
instance for an address that’s 10 bits or less.This constant expression does a compile-time assertion to ensure that the provided address is 10 bits or less.
constexpr pw::i2c::Address kAddress = pw::i2c::Address::TenBit(0x200);
- Returns:
A
pw::i2c::Address
instance.
-
template<uint8_t kAddress>
static inline constexpr Address SevenBit()# Creates a
pw::i2c::Address
instance for an address that’s 7 bits or less.This constant expression does a compile-time assertion to ensure that the provided address is 7 bits or less.
constexpr pw::i2c::Address kAddress = pw::i2c::Address::SevenBit(0x42);
- Returns:
A
pw::i2c::Address
instance.
-
explicit Address(uint16_t address)#
pw::i2c::Initiator
#
-
class Initiator#
The common, base driver interface for initiating thread-safe transactions with devices on an I2C bus. Other documentation may call this style of interface an I2C “master”, “central”, or “controller”.
pw::i2c::Initiator
isn’t required to support 10-bit addressing. If only 7-bit addressing is supported,pw::i2c::Initiator
fails a runtime assertion when given an address that is out of 7-bit address range.The implementer of this pure virtual interface is responsible for ensuring thread safety and enabling functionality such as initialization, configuration, enabling and disabling, unsticking SDA, and detecting device address registration collisions.
Note
pw::i2c::Initiator
uses internal synchronization, so it’s safe to initiate transactions from multiple threads. However, a combined write and read transaction may not be atomic when there are multiple initiators on the bus. Furthermore, devices may require specific sequences of transactions, and application logic must provide the synchronization to execute these sequences correctly.Subclassed by pw::i2c::GmockInitiator, pw::i2c::LinuxInitiator, pw::i2c::MockInitiator
Public Functions
- inline Status WriteReadFor(
- Address device_address,
- ConstByteSpan tx_buffer,
- ByteSpan rx_buffer,
- chrono::SystemClock::duration timeout,
Writes bytes to an I2C device and then reads bytes from that same device as either one atomic I2C transaction or two independent I2C transactions.
If the I2C bus is a multi-initiator then the implementer of this class must ensure it’s a single-atomic transaction.
The signal on the bus for the atomic transaction should look like this:
START + I2C_ADDRESS + WRITE(0) + TX_BUFFER_BYTES + START + I2C_ADDRESS + READ(1) + RX_BUFFER_BYTES + STOP
The signal on the bus for the two independent transactions should look like this:
START + I2C_ADDRESS + WRITE(0) + TX_BUFFER_BYTES + STOP START + I2C_ADDRESS + READ(1) + RX_BUFFER_BYTES + STOP
- Parameters:
device_address – [in] The address of the I2C device.
tx_buffer – [in] The transmit buffer.
rx_buffer – [out] The receive buffer.
timeout – [in] The maximum duration to block waiting for both exclusive bus access and the completion of the I2C transaction or transactions.
- Pre:
The provided address must be supported by the initiator. I.e. don’t use a 10-bit address if the initiator only supports 7-bit addresses. This method fails a runtime assertion if this precondition isn’t met.
- Returns:
Code
Description
The transaction or transactions succeeded.
The device address provided is bigger than 10 bits.
Was unable to acquire exclusive initiator access and complete the I2C transaction in time.
A NACK condition occurred, meaning the addressed device didn’t respond or was unable to process the request.
The interface isn’t initialized or enabled.
- inline Status WriteReadFor(
- Address device_address,
- const void *tx_buffer,
- size_t tx_size_bytes,
- void *rx_buffer,
- size_t rx_size_bytes,
- chrono::SystemClock::duration timeout,
A variation of
pw::i2c::Initiator::WriteReadFor
that accepts explicit sizes for the amount of bytes to write to the device and read from the device.
-
inline Status WriteFor(Address device_address, ConstByteSpan tx_buffer, chrono::SystemClock::duration timeout)#
Write bytes to the I2C device.
The signal on the bus should look like this:
START + I2C_ADDRESS + WRITE(0) + TX_BUFFER_BYTES + STOP
- Parameters:
device_address – [in] The address of the I2C device.
tx_buffer – [in] The transmit buffer.
timeout – [in] The maximum duration to block waiting for both exclusive bus access and the completion of the I2C transaction.
- Pre:
The provided address must be supported by the initiator. I.e. don’t use a 10-bit address if the initiator only supports 7-bit addresses. This method fails a runtime assertion if this precondition isn’t met.
- Returns:
Code
Description
The transaction succeeded.
The device address provided is bigger than 10 bits.
Was unable to acquire exclusive initiator access and complete the I2C transaction in time.
A NACK condition occurred, meaning the addressed device didn’t respond or was unable to process the request.
The interface isn’t initialized or enabled.
- inline Status WriteFor(
- Address device_address,
- const void *tx_buffer,
- size_t tx_size_bytes,
- chrono::SystemClock::duration timeout,
A variation of
pw::i2c::Initiator::WriteFor
that accepts an explicit size for the amount of bytes to write to the device.
-
inline Status ReadFor(Address device_address, ByteSpan rx_buffer, chrono::SystemClock::duration timeout)#
Reads bytes from an I2C device.
The signal on the bus should look like this:
START + I2C_ADDRESS + READ(1) + RX_BUFFER_BYTES + STOP
- Parameters:
device_address – [in] The address of the I2C device.
rx_buffer – [out] The receive buffer.
timeout – [in] The maximum duration to block waiting for both exclusive bus access and the completion of the I2C transaction.
- Pre:
The provided address must be supported by the initiator. I.e. don’t use a 10-bit address if the initiator only supports 7-bit addresses. This method fails a runtime assertion if this precondition isn’t met.
- Returns:
Code
Description
The transaction succeeded.
The device address provided is bigger than 10 bits.
Was unable to acquire exclusive initiator access and complete the I2C transaction in time.
A NACK condition occurred, meaning the addressed device didn’t respond or was unable to process the request.
The interface isn’t initialized or enabled.
-
inline Status ReadFor(Address device_address, void *rx_buffer, size_t rx_size_bytes, chrono::SystemClock::duration timeout)#
A variation of
pw::i2c::Initiator::ReadFor
that accepts an explicit size for the amount of bytes to read from the device.
-
inline Status ProbeDeviceFor(Address device_address, chrono::SystemClock::duration timeout)#
Probes the device for an I2C ACK after only writing the address. This is done by attempting to read a single byte from the specified device.
Warning
This method is not compatible with all devices. For example, some I2C devices require a device_address in W mode before they can ack the device_address in R mode. In this case, use WriteReadFor to read a register with known value.
- Parameters:
device_address – [in] The address of the I2C device.
timeout – [in] The maximum duration to block waiting for both exclusive bus access and the completion of the I2C transaction.
- Pre:
The provided address must be supported by the initiator. I.e. don’t use a 10-bit address if the initiator only supports 7-bit addresses. This method fails a runtime assertion if this precondition isn’t met.
- Returns:
Code
Description
The transaction succeeded.
The device address provided is bigger than 10 bits.
Was unable to acquire exclusive initiator access and complete the I2C transaction in time.
A NACK condition occurred, meaning the addressed device didn’t respond or was unable to process the request.
The interface isn’t initialized or enabled.
pw::i2c::Device
#
-
class Device#
The common interface for generic I2C devices. Reads and writes arbitrary chunks of data over an I2C bus to an I2C device. This class contains
pw::i2c::Address
and wraps thepw::i2c::Initiator
API. Only works with devices that have a single device address.pw::i2c::Device
is intended to represent ownership of a specific responder. Individual transactions are atomic but there’s no synchronization for sequences of transactions. Therefore, shared access should be faciliated with higher-level application abstractions. To help enforce this,pw::i2c::Device
instances are only movable and not copyable.Subclassed by pw::i2c::RegisterDevice
Public Functions
-
inline constexpr Device(Initiator &initiator, Address device_address)#
Creates a
pw::i2c::Device
instance.The address for the I2C device is set in this constructor and can’t be modified later.
- Parameters:
initiator – [in] A reference to a
pw::i2c::Initiator
instance.device_address – [in] The address of the I2C device.
- Returns:
A
pw::i2c::Device
instance.
-
inline Status WriteReadFor(ConstByteSpan tx_buffer, ByteSpan rx_buffer, chrono::SystemClock::duration timeout)#
- inline Status WriteReadFor(
- const void *tx_buffer,
- size_t tx_size_bytes,
- void *rx_buffer,
- size_t rx_size_bytes,
- chrono::SystemClock::duration timeout,
Wraps the variation of
pw::i2c::Initiator::WriteReadFor
that accepts explicit sizes for the amount of bytes to write to the device and read from the device.
-
inline Status WriteFor(ConstByteSpan tx_buffer, chrono::SystemClock::duration timeout)#
Wraps
pw::i2c::Initiator::WriteFor
.
-
inline Status WriteFor(const void *tx_buffer, size_t tx_size_bytes, chrono::SystemClock::duration timeout)#
Wraps the variation of
pw::i2c::Initiator::WriteFor
that accepts an explicit size for the amount of bytes to write to the device.
-
inline Status ReadFor(ByteSpan rx_buffer, chrono::SystemClock::duration timeout)#
Wraps
pw::i2c::Initiator::ReadFor
.
-
inline Status ReadFor(void *rx_buffer, size_t rx_size_bytes, chrono::SystemClock::duration timeout)#
Wraps the variation of
pw::i2c::Initiator::ReadFor
that accepts an explicit size for the amount of bytes to read from the device.
-
inline Status ProbeFor(chrono::SystemClock::duration timeout)#
-
inline constexpr Device(Initiator &initiator, Address device_address)#
pw::i2c::RegisterDevice
#
See Configure and read an I2C device’s registers for example usage of
pw::i2c::RegisterDevice
.
-
class RegisterDevice : public pw::i2c::Device#
The common interface for I2C register devices. Contains methods to help read and write the device’s registers.
Warning
This interface assumes that you know how to consult your device’s datasheet to determine correct address sizes, data sizes, endianness, etc.
Public Functions
- inline constexpr RegisterDevice(
- Initiator &initiator,
- Address address,
- endian register_address_order,
- endian data_order,
- RegisterAddressSize register_address_size,
This constructor specifies the endianness of the register address and data separately. If your register address and data have the same endianness and you’d like to specify them both with a single argument, see the other
pw::i2c::RegisterDevice
constructor.- Parameters:
initiator – [in] A
pw::i2c::Initiator
instance for the bus that the device is on.address – [in] The address of the I2C device.
register_address_order – [in] The endianness of the register address.
data_order – [in] The endianness of the data.
register_address_size – [in] The size of the register address.
-
inline constexpr RegisterDevice(Initiator &initiator, Address address, endian order, RegisterAddressSize register_address_size)#
This constructor specifies the endianness of the register address and data with a single argument. If your register address and data have different endianness, use the other
pw::i2c::RegisterDevice
constructor.- Parameters:
initiator – [in] A
pw::i2c::Initiator
instance for the bus that the device is on.address – [in] The address of the I2C device.
order – [in] The endianness of both the register address and register data.
register_address_size – [in] The size of the register address.
- inline Status WriteRegisters(
- uint32_t register_address,
- ConstByteSpan register_data,
- ByteSpan buffer,
- chrono::SystemClock::duration timeout,
Writes data to multiple contiguous registers starting at a specific register. This method is byte-addressable.
register_address
andregister_data
use the endianness that was provided when thispw::i2c::RegisterDevice
instance was constructed.- Parameters:
register_address – [in] The register address to begin writing at.
register_data – [in] The data to write. Endianness is taken into account if the data is 2 or 4 bytes.
buffer – [in] A buffer for constructing the write data. The size of this buffer must be at least as large as the size of
register_address
plus the size ofregister_data
.timeout – [in] The maximum duration to block waiting for both exclusive bus access and the completion of the I2C transaction.
- Pre:
This method assumes that you’ve verified that your device supports bulk writes and that
register_data
is a correct size for your device.- Returns:
Code
Description
The bulk write was successful.
Unable to acquire exclusive bus access and complete the transaction in time.
The interface is not initialized or enabled.
An issue occurred while building
register_data
.register_address
is larger than the 10-bit address space.The size of
buffer
is less than the size ofregister_address
plus the size ofregister_data
.The device took too long to respond to the NACK.
- inline Status WriteRegisters8(
- uint32_t register_address,
- span<const uint8_t> register_data,
- ByteSpan buffer,
- chrono::SystemClock::duration timeout,
Variant of
pw::i2c::RegisterDevice::WriteRegisters()
that requiresregister_data
to be exactly 8 bits.
- inline Status WriteRegisters16(
- uint32_t register_address,
- span<const uint16_t> register_data,
- ByteSpan buffer,
- chrono::SystemClock::duration timeout,
Variant of
pw::i2c::RegisterDevice::WriteRegisters()
that requiresregister_data
to be exactly 16 bits.
- inline Status WriteRegisters32(
- uint32_t register_address,
- span<const uint32_t> register_data,
- ByteSpan buffer,
- chrono::SystemClock::duration timeout,
Variant of
pw::i2c::RegisterDevice::WriteRegisters()
that requiresregister_data
to be exactly 32 bits.
-
Status ReadRegisters(uint32_t register_address, ByteSpan return_data, chrono::SystemClock::duration timeout)#
Reads data from multiple contiguous registers starting from a specific offset or register. This method is byte-addressable.
register_address
andreturn_data
use the endianness that was provided when thispw::i2c::RegisterDevice
instance was constructed.- Parameters:
register_address – [in] The register address to begin reading at.
return_data – [out] The area to read the data into. The amount of data that will be read is equal to the size of this span. Endianness is taken into account if this span is 2 or 4 bytes.
timeout – [in] The maximum duration to block waiting for both exclusive bus access and the completion of the I2C transaction.
- Pre:
This method assumes that you’ve verified that your device supports bulk reads and that
return_data
is a correct size for your device.- Returns:
Code
Description
The bulk read was successful.
Unable to acquire exclusive bus access and complete the transaction in time.
The interface is not initialized or enabled.
An issue occurred while building
return_data
.register_address
is larger than the 10-bit address space.The device took too long to respond to the NACK.
-
inline Status ReadRegisters8(uint32_t register_address, span<uint8_t> return_data, chrono::SystemClock::duration timeout)#
Variant of
pw::i2c::RegisterDevice::ReadRegisters()
that requiresreturn_data
to be exactly 8 bits.
-
inline Status ReadRegisters16(uint32_t register_address, span<uint16_t> return_data, chrono::SystemClock::duration timeout)#
Variant of
pw::i2c::RegisterDevice::ReadRegisters()
that requiresreturn_data
to be exactly 16 bits.
-
inline Status ReadRegisters32(uint32_t register_address, span<uint32_t> return_data, chrono::SystemClock::duration timeout)#
Variant of
pw::i2c::RegisterDevice::ReadRegisters()
that requiresreturn_data
to be exactly 32 bits.
-
inline Status WriteRegister(uint32_t register_address, std::byte register_data, chrono::SystemClock::duration timeout)#
Sends a register address to write to and then writes to that address.
register_address
andregister_data
use the endianness that was provided when thispw::i2c::RegisterDevice
instance was constructed.- Parameters:
register_address – [in] The register address to write to.
register_data – [in] The data that should be written at the address. The maximum allowed size is 4 bytes.
timeout – [in] The maximum duration to block waiting for both exclusive bus access and the completion of the I2C transaction.
- Pre:
This method assumes that you’ve verified that
register_data
is a correct size for your device.- Returns:
Code
Description
The write was successful.
Unable to acquire exclusive bus access and complete the transaction in time.
The interface is not initialized or enabled.
An issue occurred while writing the data.
register_address
is larger than the 10-bit address space.The device took too long to respond to the NACK.
-
inline Status WriteRegister8(uint32_t register_address, uint8_t register_data, chrono::SystemClock::duration timeout)#
Variant of
pw::i2c::RegisterDevice::WriteRegister()
that writes exactly 8 bits.
-
inline Status WriteRegister16(uint32_t register_address, uint16_t register_data, chrono::SystemClock::duration timeout)#
Variant of
pw::i2c::RegisterDevice::WriteRegister()
that writes exactly 16 bits.
-
inline Status WriteRegister32(uint32_t register_address, uint32_t register_data, chrono::SystemClock::duration timeout)#
Variant of
pw::i2c::RegisterDevice::WriteRegister()
that writes exactly 32 bits.
-
inline Result<std::byte> ReadRegister(uint32_t register_address, chrono::SystemClock::duration timeout)#
Sends a register address to read from and then reads from that address.
register_address
and the return data use the endianness that was provided when thispw::i2c::RegisterDevice
instance was constructed.- Parameters:
register_address – [in] The register address to read.
timeout – [in] The maximum duration to block waiting for both exclusive bus access and the completion of the I2C transaction.
- Pre:
This method assumes that you’ve verified that the return data size is a correct size for your device.
- Returns:
Code
Description
Returns the register data.
Unable to acquire exclusive bus access and complete the transaction in time.
The interface is not initialized or enabled.
An issue occurred while building the return data.
register_address
is larger than the 10-bit address space.The device took too long to respond to the NACK.
-
inline Result<uint8_t> ReadRegister8(uint32_t register_address, chrono::SystemClock::duration timeout)#
Variant of
pw::i2c::RegisterDevice::ReadRegister()
that returns exactly 8 bits.
-
inline Result<uint16_t> ReadRegister16(uint32_t register_address, chrono::SystemClock::duration timeout)#
Variant of
pw::i2c::RegisterDevice::ReadRegister()
that returns exactly 16 bits.
-
inline Result<uint32_t> ReadRegister32(uint32_t register_address, chrono::SystemClock::duration timeout)#
Variant of
pw::i2c::RegisterDevice::ReadRegister()
that returns exactly 32 bits.
pw::i2c::I2cService
#
-
class I2cService : public pw_rpc::pwpb::I2c::Service<I2cService>#
RPC service for performing I2C transactions.
Public Types
-
using InitiatorSelector = pw::Function<Initiator*(size_t pos)>#
A callback that returns a
pw::i2c::Initiator
instance for the given bus index position ornullptr
if the position is not valid for this I2C device.
Public Functions
-
inline explicit I2cService(InitiatorSelector &&initiator_selector)#
Creates an I2cService instance.
-
using InitiatorSelector = pw::Function<Initiator*(size_t pos)>#
pw::i2c::MockInitiator
#
-
class MockInitiator : public pw::i2c::Initiator#
A generic mocked backend for
pw::i2c::Initiator
that’s specifically designed to make it easier to develop I2C device drivers.pw::i2c::MockInitiator
compares actual I2C transactions against expected transactions. The expected transactions are represented as a list ofpw::i2c::Transaction
instances that are passed as arguments in thepw::i2c::MockInitiator
constructor. Each consecutive call topw::i2c::MockInitiator
iterates to the next expected transaction.pw::i2c::MockInitiator::Finalize()
indicates whether the actual transactions matched the expected transactions.pw::i2c::MockInitiator
is intended to be used within GoogleTest tests. See pw_unit_test .#include <chrono> #include "pw_bytes/array.h" #include "pw_i2c/address.h" #include "pw_i2c/initiator_mock.h" #include "pw_result/result.h" #include "pw_unit_test/framework.h" using namespace std::chrono_literals; namespace { TEST(I2CTestSuite, I2CWriteTestCase) { constexpr pw::i2c::Address kAddress = pw::i2c::Address::SevenBit<0x01>(); constexpr auto kExpectedWrite = pw::bytes::Array<1, 2, 3>(); auto expected_transactions = pw::i2c::MakeExpectedTransactionArray( {pw::i2c::WriteTransaction(pw::OkStatus(), kAddress, kExpectedWrite, 1ms)} ); pw::i2c::MockInitiator initiator(expected_transactions); pw::ConstByteSpan kActualWrite = pw::bytes::Array<1, 2, 3>(); pw::Status status = initiator.WriteFor(kAddress, kActualWrite, 1ms); EXPECT_EQ(initiator.Finalize(), pw::OkStatus()); } }
Public Functions
-
inline Status Finalize() const#
Indicates whether the actual I2C transactions matched the expected transactions. Should be called at the end of the test.
- Returns:
Code
Description
The actual transactions matched the expected transactions.
The mocked set of transactions hasn’t been exhausted.
-
~MockInitiator() override#
Runs
pw::i2c::MockInitiator::Finalize()
regardless of whether it was already optionally finalized.
-
inline Status Finalize() const#
pw::i2c::Transaction
#
-
class Transaction#
Base class for creating transaction instances. For read-only, write-only, or probe transactions, improve code readability by using one of the following helpers instead:
pw::i2c::ReadTransaction
pw::i2c::WriteTransaction
pw::i2c::ProbeTransaction
If you need to create a write-then-read transaction, you can use this class.
Public Functions
- inline constexpr Transaction(
- Status expected_return_value,
- Address device_address,
- ConstByteSpan write_buffer,
- ConstByteSpan read_buffer,
- std::optional<chrono::SystemClock::duration> timeout = std::nullopt,
Constructor for creating write-only, read-only, or write-then-read transactions.
- inline constexpr Transaction(
- Status expected_return_value,
- Address device_address,
- std::optional<chrono::SystemClock::duration> timeout = std::nullopt,
Alternative constructor for creating probe transactions.
-
inline ConstByteSpan read_buffer() const#
Gets the buffer that is virtually read.
-
inline ConstByteSpan write_buffer() const#
Gets the buffer that the I2C device should write to.
-
inline std::optional<chrono::SystemClock::duration> timeout() const#
Gets the minimum duration to wait for a blocking I2C transaction.
pw::i2c::ReadTransaction
#
- constexpr Transaction pw::i2c::ReadTransaction(
- Status expected_return_value,
- Address device_address,
- ConstByteSpan read_buffer,
- std::optional<chrono::SystemClock::duration> timeout = std::nullopt,
A helper that constructs a read-only I2C transaction. Used for testing read transactions with
pw::i2c::MockInitiator
.
pw::i2c::WriteTransaction
#
- constexpr Transaction pw::i2c::WriteTransaction(
- Status expected_return_value,
- Address device_address,
- ConstByteSpan write_buffer,
- std::optional<chrono::SystemClock::duration> timeout = std::nullopt,
A helper that constructs a write-only I2C transaction. Used for testing write transactions with
pw::i2c::MockInitiator
.
pw::i2c::ProbeTransaction
#
- constexpr Transaction pw::i2c::ProbeTransaction(
- Status expected_return_value,
- Address device_address,
- std::optional<chrono::SystemClock::duration> timeout = std::nullopt,
A helper that constructs a one-byte read I2C transaction. Used for testing probe transactions with
pw::i2c::MockInitiator
.