Quickstart and guides#

pw_i2c: Cross-platform I2C API with interactive debugging

Quickstart#

Depend on the pw_i2c API and an appropriate implementation of the API like pw_i2c_linux. See Implementations for the list of existing implementations and to learn how to create your own.

cc_library(
  # ...
  deps = [
    # ...
    "@pigweed//pw_i2c:address",
    "@pigweed//pw_i2c:device",
    # ...
  ] + select({
    "@platforms//os:linux": [
      "@pigweed//pw_i2c_linux:initiator",
    ],
    "//conditions:default": [
      # Fake example of a custom implementation.
      "//lib/pw_i2c_my_device:initiator",
    ],
  }),
)

Note

This assumes that your Bazel WORKSPACE has a repository named @pigweed that points to the upstream Pigweed repository.

If creating your own implementation, depend on the virtual interface:

cc_library(
  name = "initiator",
  srcs = ["initiator.cc"],
  hdrs = ["initiator.h"],
  deps = ["@pigweed//pw_i2c:initiator"],
)

Write some C++ code to interact with an I2C device:

#include "pw_i2c_rp2040/initiator.h"

#include "hardware/i2c.h"

constexpr pw::i2c::Rp2040Initiator::Config ki2cConfig{
  .clock_frequency = 400'000,
  .sda_pin = 8,
  .scl_pin = 9,
};

 pw::i2c::Rp2040Initiator i2c_bus(ki2cConfig, i2c0);
 // Calls these Pico SDK functions:
 // * gpio_set_function(8, GPIO_FUNC_I2C)
 // * gpio_set_function(9, GPIO_FUNC_I2C)
 i2c_bus.Enable();

Guides#

Mock I2C transactions#

See the example in pw::i2c::MockInitiator.

Configure and read an I2C device’s registers#

#include <chrono>
#include <cstddef>
#include <cstdint>

#include "pw_bytes/bit.h"
#include "pw_i2c/address.h"
#include "pw_i2c/register_device.h"
#include "pw_log/log.h"
#include "pw_status/status.h"

using ::pw::Status;
using namespace std::chrono_literals;

// Search for `pi4ioe5v6416` in the Kudzu codebase to see real usage of
// pw::i2c::RegisterDevice
namespace pw::pi4ioe5v6416 {

namespace {

constexpr pw::i2c::Address kAddress = pw::i2c::Address::SevenBit<0x20>();
enum Register : uint32_t {
  InputPort0 = 0x0,
  ConfigPort0 = 0x6,
  PullUpDownEnablePort0 = 0x46,
  PullUpDownSelectionPort0 = 0x48,
};

}  // namespace

// This particular example instantiates `pw::i2c::RegisterDevice`
// as part of a higher-level general "device" interface.
// See //lib/pi4ioe5v6416/public/pi4ioe5v6416/device.h in Kudzu.
Device::Device(pw::i2c::Initiator& initiator)
    : initiator_(initiator),
      register_device_(initiator,
                       kAddress,
                       endian::little,
                       pw::i2c::RegisterAddressSize::k1Byte) {}

Status Device::Enable() {
  // Set port 0 as inputs for buttons (1=input)
  device_.WriteRegister8(Register::ConfigPort0,
                         0xff,
                         pw::chrono::SystemClock::for_at_least(10ms));
  // Select pullup resistors for button input (1=pullup)
  device_.WriteRegister8(Register::PullUpDownSelectionPort0,
                         0xff,
                         pw::chrono::SystemClock::for_at_least(10ms));
  // Enable pullup/down resistors for button input (1=enable)
  device_.WriteRegister8(Register::PullUpDownEnablePort0,
                         0xff,
                         pw::chrono::SystemClock::for_at_least(10ms));
  return OkStatus();
}

pw::Result<uint8_t> Device::ReadPort0() {
  return device_.ReadRegister8(Register::InputPort0,
                               pw::chrono::SystemClock::for_at_least(10ms));
}

}  // namespace pw::pi4ioe5v6416

The code example above was adapted from Kudzu. See the following files for real pw::i2c::RegisterDevice usage:

Access an I2C device’s registers over RPC#

pw::i2c::I2cService enables accessing an I2C device’s registers over RPC.

Using pw_console, invoke the service to perform an I2C read:

# Read register `0x0e` from the device at `0x22`.
device.rpcs.pw.i2c.I2c.I2cRead(
    bus_index=0,
    target_address=0x22,
    register_address=b'\x0e',
    read_size=1
)

For responders that support 4-byte register width, you can specify the register address like this:

device.rpcs.pw.i2c.I2c.I2cRead(
    bus_index=0,
    target_address=<address>,
    register_address=b'\x00\x00\x00\x00',
    read_size=4
)

To perform an I2C write:

device.rpcs.pw.i2c.I2c.I2cWrite(
    bus_index=0,
    target_address=0x22,
    register_address=b'\x0e',
    value=b'\xbc'
)

Multi-byte writes can also be specified with the bytes fields for register_address and value.

I2C responders that require multi-byte access may expect a specific endianness. The order of bytes specified in the bytes field will match the order of bytes sent or received on the bus. The maximum supported value for multi-byte access is 4 bytes.