pw_i2c_linux#

Linux userspace implementation for pw_i2c

Stable C++17

pw_i2c_linux implements the pw_i2c interface using the Linux userspace i2c-dev driver. Transfers are executed using blocking ioctl calls. Write+read transactions are implemented atomically using a single system call, and a retry mechanism is used to support bus arbitration between multiple controllers.

API reference#

class LinuxInitiator : public pw::i2c::Initiator#

Initiator interface implementation using the Linux userspace i2c-dev driver.

Takes exclusive control of an I2C bus device (ex. “/dev/i2c-0”). The user is responsible to open the device node prior to creating the initiator. The file descriptor is closed when the initiator object is destroyed.

The bus device must support the full I2C functionality. Users of the class are encouraged to use the OpenI2cBus helper to ensure the bus is valid.

Access to the bus is guarded by an internal mutex, so this initiator can be safely used from multiple threads.

Public Functions

LinuxInitiator(int fd)#

Construct an instantiator using an open file descriptor. The file descriptor is closed during destruction.

Parameters:

fd[in] Valid file descriptor for an I2C device node.

Public Static Functions

static Result<int> OpenI2cBus(const char *bus_path)#

Open an I2C bus and validate that full I2C functionality is supported.

Parameters:

bus_path[in] Path to the I2C bus device node.

Return values:
  • OK – The device node was opened successfully.

  • InvalidArgument – Failed to open the device node or to validate I2C functionality.

Returns:

The open file descriptor on success.

Examples#

A simple example illustrating the usage:

#include "pw_i2c/address.h"
#include "pw_i2c/device.h"
#include "pw_i2c_linux/initiator.h"
#include "pw_log/log.h"
#include "pw_result/result.h"

constexpr auto kBusPath = "/dev/i2c-0";
constexpr auto kAddress = pw::i2c::Address::SevenBit<0x42>();

pw::Result<int> result = pw::i2c::LinuxInitiator::OpenI2cBus(kBusPath);
if (!result.ok()) {
  PW_LOG_ERROR("Failed to open I2C bus [%s]", kBusPath);
  return result.status();
}
pw::i2c::LinuxInitiator initiator(*result);
pw::i2c::Device device(initiator, address);
// Use device to talk to address.

In real-world use cases, you may want to create an initiator singleton. This can be done by initializing a function-local static variable with a lambda:

#include <functional>

#include "pw_i2c/address.h"
#include "pw_i2c/device.h"
#include "pw_i2c/initiator.h"
#include "pw_i2c_linux/initiator.h"
#include "pw_log/log.h"
#include "pw_result/result.h"
#include "pw_status/status.h"

// Open the I2C bus and return an initiator singleton.
pw::i2c::Initiator* GetInitiator() {
  static constexpr auto kBusPath = "/dev/i2c-0";
  static auto* initiator = std::invoke([]() -> pw::i2c::Initiator* {
    pw::Result<int> result = pw::i2c::LinuxInitiator::OpenI2cBus(kBusPath);
    if (!result.ok()) {
      PW_LOG_ERROR("Failed to open I2C bus [%s]", kBusPath);
      return nullptr;
    }
    return new pw::i2c::Initiator(*result);
  });
  return initiator;
}

// Use the initiator from anywhere.
constexpr auto kAddress = pw::i2c::Address::SevenBit<0x42>();
auto* initiator = GetInitiator();
if (initiator == nullptr) {
  PW_LOG_ERROR("I2C initiator unavailable");
  return pw::Status::Internal();
}
pw::i2c::Device device(*initiator, address);
// Use device to talk to address.

Caveats#

Only 7-bit addresses are supported right now, but it should be possible to add support for 10-bit addresses with minimal changes - as long as the Linux driver supports 10-bit addresses.