PCA9535 / PCA9555 I/O Expander

The PCA9535 (and the register-identical PCA9555) is a 16-bit I2C GPIO expander with two 8-bit ports. Each pin can be configured as an input or an output, with optional input polarity inversion. The PCA9535 additionally provides an open-drain interrupt output.

The espp::Pca9535 component allows the user to read the input pins, drive the output pins, and configure per-port direction and polarity over I2C.

API Reference

Header File

Classes

class Pca9535 : public espp::BasePeripheral<>

Class for communicating with and controlling a PCA9535 / PCA9555 16-bit I2C GPIO expander. The PCA9535 and PCA9555 share an identical register map; the PCA9535 additionally provides an open-drain interrupt output, while the PCA9555 is otherwise identical. A single driver therefore covers both parts.

The device exposes two 8-bit ports (Port 0 and Port 1), each of which can be independently configured for input or output, and each of which supports input polarity inversion.

PCA9535 Example

    // make the I2C that we'll use to communicate
    espp::I2c i2c({
        .port = I2C_NUM_0,
        .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO,
        .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO,
    });
    std::error_code ec;
    auto pca9535_device =
        i2c.add_device<uint8_t>({.device_address = espp::Pca9535::DEFAULT_ADDRESS,
                                 .timeout_ms = static_cast<int>(i2c.config().timeout_ms),
                                 .scl_speed_hz = i2c.config().clk_speed,
                                 .log_level = espp::Logger::Verbosity::WARN},
                                ec);
    if (!pca9535_device) {
      fmt::print("PCA9535 I2C device initialization failed: {}\n", ec.message());
      return;
    }
    // now make the pca9535 which handles GPIO. Port 0 is all inputs, and Port 1
    // is all outputs.
    espp::Pca9535 pca9535({.port_0_direction_mask = 0xFF, // all inputs on port 0
                           .port_1_direction_mask = 0x00, // all outputs on port 1
                           .write = espp::make_i2c_addressed_write(pca9535_device),
                           .read_register = espp::make_i2c_addressed_read_register(pca9535_device),
                           .log_level = espp::Logger::Verbosity::WARN});
    // and finally, make the task to periodically poll the pca9535 and print the
    // state. NOTE: the Pca9535 does not internally manage its own state update,
    // so whatever rate we use here is the rate at which the state will update.
    auto task_fn = [&pca9535](std::mutex &m, std::condition_variable &cv) {
      static auto start = std::chrono::high_resolution_clock::now();
      static uint8_t output = 0;
      auto now = std::chrono::high_resolution_clock::now();
      auto seconds = std::chrono::duration<float>(now - start).count();
      std::error_code ec;
      // read the inputs on port 0
      auto p0_pins = pca9535.get_pins(espp::Pca9535::Port::PORT0, ec);
      if (ec) {
        fmt::print("get_pins failed: {}\n", ec.message());
      } else {
        // toggle the output on port 1
        output = ~output;
        pca9535.set_pins(espp::Pca9535::Port::PORT1, output, ec);
        if (ec) {
          fmt::print("set_pins failed: {}\n", ec.message());
        } else {
          fmt::print("{:.3f}, {:#x}, {:#x}\n", seconds, p0_pins, output);
        }
      }
      // Always sleep (even after an error) so a persistently-failing device does
      // not spin this task in a tight loop. Sleeping this way also lets the sleep
      // exit early when the task is being stopped / destroyed.
      {
        std::unique_lock<std::mutex> lk(m);
        cv.wait_for(lk, 500ms);
      }
      // don't want to stop the task
      return false;
    };
    auto task = espp::Task({.callback = task_fn,
                            .task_config =
                                {
                                    .name = "Pca9535 Task",
                                    .stack_size_bytes = 5 * 1024,
                                },
                            .log_level = espp::Logger::Verbosity::WARN});
    fmt::print("%time(s), port_0 pins, port_1 output\n");
    task.start();

Public Types

enum class Port

The two GPIO ports the PCA9535 / PCA9555 has.

Values:

enumerator PORT0

Port 0.

enumerator PORT1

Port 1.

typedef std::function<bool(uint8_t)> probe_fn

Function to probe the peripheral

Param address:

The address to probe

Return:

True if the peripheral is found at the given address

Public Functions

inline explicit Pca9535(const Config &config)

Construct the Pca9535 and configure it.

Parameters:

configConfig structure for configuring the PCA9535 / PCA9555

inline void initialize(std::error_code &ec)

Initialize the device.

Writes the direction (Config) registers and the polarity inversion registers for both ports from the configured masks.

Parameters:

ec – Error code to set if there is an error.

inline uint8_t get_pins(Port port, std::error_code &ec)

Read the pin values on the provided port.

Parameters:
  • port – The Port for which to read the pins.

  • ec – Error code to set if there is an error.

Returns:

The pin values as an 8 bit mask.

inline uint16_t get_pins(std::error_code &ec)

Read the pin values on both Port 0 and Port 1.

Parameters:

ec – Error code to set if an error occurs.

Returns:

The pin values as a 16 bit mask (Port 0 low byte, Port 1 high byte).

inline void set_pins(Port port, uint8_t output, std::error_code &ec)

Set the pin values on the provided port.

Parameters:
  • port – The Port for which to set the pin outputs.

  • output – The pin values as an 8 bit mask to set.

  • ec – Error code to set if there is an error.

inline uint8_t get_output(Port port, std::error_code &ec)

Read back the output register for the provided port.

Parameters:
  • port – The Port for which to read the output register.

  • ec – Error code to set if there is an error.

Returns:

The output register value as an 8 bit mask.

inline void set_direction(Port port, uint8_t mask, std::error_code &ec)

Set the i/o direction for the pins according to mask.

Note

For the PCA9535 / PCA9555 the direction (Config) register uses the convention 1 = input, 0 = output. This differs from some other expanders, so take care when porting masks between drivers.

Parameters:
  • port – The port associated with the provided pin mask.

  • mask – The mask indicating direction (1 = input, 0 = output).

  • ec – Error code to set if there is an error.

inline uint8_t get_direction(Port port, std::error_code &ec)

Read the direction (configuration) register for a port.

Parameters:
  • port – The Port whose direction register to read.

  • ec – The error code, set on failure.

Returns:

The direction mask (1 = input, 0 = output). Useful for a read-modify-write when only some pins should change direction.

inline void set_polarity_inversion(Port port, uint8_t mask, std::error_code &ec)

Set the input polarity inversion for the pins according to mask.

Parameters:
  • port – The port associated with the provided polarity mask.

  • mask – Polarity mask for the pins, 1 -> invert the input pin value.

  • ec – Error code to set if there is an error.

inline bool probe(std::error_code &ec) const

Probe the peripheral

Note

This function is thread safe

Note

If the probe function is not set, this function will return false and set the error code to operation_not_supported

Note

This function is only available if UseAddress is true

Parameters:

ec – The error code to set if there is an error

Returns:

True if the peripheral is found

inline void set_address(uint8_t address)

Set the address of the peripheral

Note

This function is thread safe

Note

This function is only available if UseAddress is true

Parameters:

address – The address of the peripheral

inline void set_probe(const probe_fn &probe)

Set the probe function

Note

This function is thread safe

Note

This should rarely be used, as the probe function is usually set in the constructor. If you need to change the probe function, consider using the set_config function instead.

Note

This function is only available if UseAddress is true

Parameters:

probe – The probe function

inline void set_write(const write_fn &write)

Set the write function

Note

This function is thread safe

Note

This should rarely be used, as the write function is usually set in the constructor. If you need to change the write function, consider using the set_config function instead.

Parameters:

write – The write function

inline void set_read(const read_fn &read)

Set the read function

Note

This function is thread safe

Note

This should rarely be used, as the read function is usually set in the constructor. If you need to change the read function, consider using the set_config function instead.

Parameters:

read – The read function

inline void set_read_register(const read_register_fn &read_register)

Set the read register function

Note

This function is thread safe

Note

This should rarely be used, as the read register function is usually set in the constructor. If you need to change the read register function, consider using the set_config function instead.

Parameters:

read_register – The read register function

inline void set_write_then_read(const write_then_read_fn &write_then_read)

Set the write then read function

Note

This function is thread safe

Note

This should rarely be used, as the write then read function is usually set in the constructor. If you need to change the write then

Parameters:

write_then_read – The write then read function

inline void set_separate_write_then_read_delay(const std::chrono::milliseconds &delay)

Set the delay between the write and read operations in write_then_read

Note

This function is thread safe

Note

This should rarely be used, as the delay is usually set in the constructor. If you need to change the delay, consider using the set_config function instead.

Note

This delay is only used if the write_then_read function is not set to a custom function and the write and read functions are separate functions.

Parameters:

delay – The delay between the write and read operations in write_then_read

inline void set_config(const Config &config)

Set the configuration for the peripheral

Note

This function is thread safe

Note

The configuration should normally be set in the constructor, but this function can be used to change the configuration after the peripheral has been created - for instance if the peripheral could be found on different communications buses.

Parameters:

config – The configuration for the peripheral

inline void set_config(Config &&config)

Set the configuration for the peripheral

Note

This function is thread safe

Note

The configuration should normally be set in the constructor, but this function can be used to change the configuration after the peripheral has been created - for instance if the peripheral could be found on different communications buses.

Parameters:

config – The configuration for the peripheral

inline const Config &config() const

Get the configuration for the peripheral

Returns:

The configuration for the peripheral

inline uint8_t address() const

Get the address of the peripheral

Returns:

The address of the peripheral

inline const std::string &get_name() const

Get the name of the component

Note

This is the tag of the logger

Returns:

A const reference to the name of the component

inline void set_log_tag(const std::string_view &tag)

Set the tag for the logger

Parameters:

tag – The tag to use for the logger

inline espp::Logger::Verbosity get_log_level() const

Get the log level for the logger

Returns:

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

Parameters:

level – The verbosity level to use for the logger

inline void set_log_verbosity(espp::Logger::Verbosity level)

Set the log verbosity for the logger

See also

set_log_level

Note

This is a convenience method that calls set_log_level

Parameters:

level – The verbosity level to use for the logger

inline espp::Logger::Verbosity get_log_verbosity() const

Get the log verbosity for the logger

See also

get_log_level

Note

This is a convenience method that calls get_log_level

Returns:

The verbosity level of the logger

inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)

Set the rate limit for the logger

Note

Only calls to the logger that have _rate_limit suffix will be rate limited

Parameters:

rate_limit – The rate limit to use for the logger

Public Static Attributes

static constexpr uint8_t DEFAULT_ADDRESS = 0x20

Default I2C address (A2..A0 = 000). Valid range is 0x20 - 0x27.

struct Config

Configuration information for the Pca9535.

Public Members

uint8_t device_address = DEFAULT_ADDRESS

I2C address of this device.

uint8_t port_0_direction_mask = 0xFF

Direction mask (1 = input, 0 = output) for port 0. Default all inputs.

uint8_t port_0_polarity_mask = 0x00

Polarity inversion mask (1 = inverted) for port 0 inputs.

uint8_t port_1_direction_mask = 0xFF

Direction mask (1 = input, 0 = output) for port 1. Default all inputs.

uint8_t port_1_polarity_mask = 0x00

Polarity inversion mask (1 = inverted) for port 1 inputs.

BasePeripheral::write_fn write

Function to write to the device.

BasePeripheral::read_register_fn read_register

Function to read bytes at a register address from the device.

bool auto_init = true

True if the device should be initialized on construction.

Logger::Verbosity log_level = {Logger::Verbosity::WARN}

Log verbosity for the component.