MCP23x17 I/O Expander

The MCP23x17 I/O expander component allows the user to configure inputs, outputs, interrupts, etc. via a serial interface such as SPI or I2C.

API Reference

Header File

Classes

class Mcp23x17 : public espp::BasePeripheral<>

Class for communicating with and controlling a MCP23X17 (23017, 23S17) GPIO expander including interrupt configuration.

MCP23x17 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,
    });
    // now make the mcp23x17 which handles GPIO
    espp::Mcp23x17 mcp23x17(
        {.port_0_direction_mask = (1 << 0), // input on A0
         .port_0_interrupt_mask = (1 << 0), // interrupt on A0
         .port_1_direction_mask = (1 << 7), // input on B7
         .port_1_interrupt_mask = (1 << 7), // interrupt on B7
         .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2,
                            std::placeholders::_3),
         .read_register =
             std::bind(&espp::I2c::read_at_register, &i2c, std::placeholders::_1,
                       std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
         .log_level = espp::Logger::Verbosity::WARN});
    // set pull up on the input pins
    std::error_code ec;
    mcp23x17.set_pull_up(espp::Mcp23x17::Port::PORT0, (1 << 0), ec);
    if (ec) {
      fmt::print("set_pull_up failed: {}\n", ec.message());
    }
    mcp23x17.set_pull_up(espp::Mcp23x17::Port::PORT1, (1 << 7), ec);
    if (ec) {
      fmt::print("set_pull_up failed: {}\n", ec.message());
    }
    // and finally, make the task to periodically poll the mcp23x17 and print
    // the state. NOTE: the Mcp23x17 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 = [&quit_test, &mcp23x17](std::mutex &m, std::condition_variable &cv) {
      static auto start = std::chrono::high_resolution_clock::now();
      auto now = std::chrono::high_resolution_clock::now();
      auto seconds = std::chrono::duration<float>(now - start).count();
      std::error_code ec;
      auto a_pins = mcp23x17.get_pins(espp::Mcp23x17::Port::PORT0, ec);
      if (ec) {
        fmt::print("get_pins failed: {}\n", ec.message());
        return false;
      }
      auto b_pins = mcp23x17.get_pins(espp::Mcp23x17::Port::PORT1, ec);
      if (ec) {
        fmt::print("get_pins failed: {}\n", ec.message());
        return false;
      }
      bool on = !(a_pins & (1 << 0));
      if (on) {
        mcp23x17.set_pins(espp::Mcp23x17::Port::PORT1, (1 << 3), ec);
      } else {
        mcp23x17.set_pins(espp::Mcp23x17::Port::PORT1, 0x00, ec);
      }
      if (ec) {
        fmt::print("set_pins failed: {}\n", ec.message());
        return false;
      }
      fmt::print("{:.3f}, {:#x}, {:#x}\n", seconds, a_pins, b_pins);
      quit_test = !(b_pins & (1 << 7));
      // NOTE: sleeping in this way allows the sleep to exit early when the
      // task is being stopped / destroyed
      {
        std::unique_lock<std::mutex> lk(m);
        cv.wait_for(lk, 50ms);
      }
      // don't want to stop the task
      return false;
    };
    auto task = espp::Task({.callback = task_fn,
                            .task_config =
                                {
                                    .name = "Mcp23x17 Task",
                                    .stack_size_bytes = 5 * 1024,
                                },
                            .log_level = espp::Logger::Verbosity::WARN});
    fmt::print("%time(s), port_a pins, port_b pins\n");
    task.start();

Public Types

enum class Port

The two GPIO ports the MCP23x17 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 Mcp23x17(const Config &config)

Construct the Mcp23x17 and configure it.

Parameters

configConfig structure for configuring the MCP23X17

inline void initialize(std::error_code &ec)

Initialize the device.

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 A and Port B.

Parameters

ec – Error code to set if an error occurs.

Returns

The pin values as a 16 bit mask (PA_0 lsb, PB_7 msb).

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_interrupt_capture(Port port, std::error_code &ec)

Get the pin state at the time of interrupt for the provided port.

Parameters
  • port – The port for which to get the pin states at time of capture.

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

Returns

An 8 bit mask of pin values for the provided port.

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

Configure the provided pins to interrupt on change.

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

  • mask – The pin mask to configure for interrupt (1=interrupt).

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

inline void set_interrupt_on_value(Port port, uint8_t pin_mask, uint8_t val_mask, std::error_code &ec)

Configure the provided pins to interrupt on value.

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

  • pin_mask – The pin mask to configure for interrupt (1=interrupt).

  • val_mask – The value mask for the pin level to trigger interrupt.

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

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

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

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

  • mask – The mask indicating direction (1 = input)

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

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

Set the input polarity 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 pin value.

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

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

Set the internal pull up (100 Kohm) for the port’s pins.

Parameters
  • port – The port associated with the provided pull up mask.

  • mask – Mask indicating which pins should be pulled up (1).

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

inline void set_interrupt_mirror(bool mirror, std::error_code &ec)

Configure the interrupt mirroring for the MCP23x17.

Parameters
  • mirror – True if the interrupt pins should be internally connected, false otherwise.

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

inline void set_interrupt_polarity(bool active_high, std::error_code &ec)

Set the polarity of the interrupt pins.

Parameters
  • active_high – True if it should be active-high, false for active-low (default).

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

inline bool probe (std::error_code &ec) requires(UseAddress)

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) requires(UseAddress)

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) requires(UseAddress)

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 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 = 0b0100000

Lower 3 bits are A2, A2, A0 pins on the chip.

struct Config

Configuration information for the Mcp23x17.

Public Members

uint8_t device_address = DEFAULT_ADDRESS

I2C address of this device.

uint8_t port_0_direction_mask = 0x00

Direction mask (1 = input) for port 0 / A.

uint8_t port_0_interrupt_mask = 0x00

Interrupt mask (1 = interrupt) for port 0 / A.

uint8_t port_1_direction_mask = 0x00

Direction mask (1 = input) for port 1 / B.

uint8_t port_1_interrupt_mask = 0x00

Interrupt mask (1 = interrupt) for port 1 / B.

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.