ESP32 GPIO Expanders: MCP23017 vs PCF8574 vs PCF8575

Compare MCP23017, PCF8574 and PCF8575 GPIO expanders for ESP32 and ESPHome: pin count, I2C speed, interrupts, pull-ups, relay and button use, wiring and example YAML.

If an ESP32 has run out of GPIOs, an I²C GPIO expander is often cleaner than moving to a larger board. The three parts you are most likely to encounter are the MCP23017, PCF8574 and PCF8575. All can add digital inputs and outputs using only SDA and SCL, but they are not interchangeable in capability.

The short version is simple: MCP23017 is the best general-purpose choice when you want predictable GPIO behaviour, configurable pull-ups and proper interrupt control. PCF8574 is excellent when you only need eight cheap, simple I/O lines. PCF8575 extends the same PCF approach to 16 pins and is often the cheapest way to build a large button, LED or relay-control panel.

The key difference is architectural. MCP23017 behaves much more like ordinary microcontroller GPIO, with direction registers, output registers, pull-ups and configurable interrupt logic. PCF8574 and PCF8575 use quasi-bidirectional pins: there is no normal input/output direction register, and a pin is effectively used as an input by writing it HIGH and then reading it.

MCP23017 vs PCF8574 vs PCF8575 at a Glance

FeatureMCP23017PCF8574PCF8575
GPIO count16816
InterfaceI²CI²C / SMBusI²C / SMBus
Maximum documented I²C speed1.7 MHz100 kHz400 kHz
Hardware address pins333
Devices per address familyUp to 8Up to 8Up to 8
GPIO direction registerYesNo – quasi-bidirectionalNo – quasi-bidirectional
Internal pull-up controlYesNo conventional pull-up registerNo conventional pull-up register
Interrupt output2 configurable outputs1 open-drain INT1 open-drain INT
Per-pin interrupt configurationYesNo – port-change styleNo – port-change style
External reset pinYesNo dedicated resetNo dedicated reset
ESPHome supportNativeNativeNative through PCF8574 component
Best fitMixed inputs/outputs, buttons, control panelsSimple 8-pin expansionLow-cost 16-pin expansion

Do not choose only by pin count. PCF8575 and MCP23017 both provide 16 lines, but the MCP gives much more control over how each line behaves.

How I²C GPIO Expansion Works

An I²C expander sits on the same two-wire bus as sensors, displays and other peripherals:

ESP32 3.3V
   │
   ├── SDA ─────────────┬── MCP23017 / PCF857x SDA
   │                    └── other I²C devices
   │
   ├── SCL ─────────────┬── MCP23017 / PCF857x SCL
   │                    └── other I²C devices
   │
   └── GND ───────────────── common ground

The expander receives commands over I²C and changes its remote GPIOs. Reading a button means the ESP32 sends an I²C transaction and receives the expander’s port state. Driving an output means transmitting the new output state over the bus.

This makes expanders excellent for slow digital I/O such as switches, contact sensors, relay-module inputs, indicator LEDs and keypads. They are not replacements for native ESP32 pins when you need microsecond timing, high-frequency PWM, UART, I²S, WS2812 timing or similar specialised functions.

MCP23017: the Most Flexible Option

The MCP23017 provides two eight-bit GPIO banks, normally referred to as GPA0–GPA7 and GPB0–GPB7. Each pin can be individually configured as input or output. Inputs can use the chip’s internal pull-ups, and the device has a significantly more capable interrupt system than the PCF parts.

Microchip specifies I²C operation at 100 kHz, 400 kHz and up to 1.7 MHz. That does not mean your entire ESP32 bus should automatically run at 1.7 MHz: every other device on the same bus must also support the selected speed.

The MCP23017 also has polarity inversion, output-latch and interrupt-capture registers. For a large control panel, these details are useful because software can distinguish what changed instead of repeatedly treating the expander as one simple 16-bit port.

MCP23017 interrupt advantages

There are two interrupt outputs, INTA and INTB. They can operate independently or be combined, and individual GPIOs can be configured to interrupt on a change or when their state differs from a programmed default. An interrupt-capture register preserves the port state associated with the interrupt.

This is a major advantage for button panels or many contact sensors. The ESP32 can sleep or perform other work and react only when the MCP23017 reports a real input event.

PCF8574: Simple and Very Common

The PCF8574 adds eight I/O lines and is found on countless low-cost breakout boards, including many I²C backpacks for HD44780 character LCDs. It uses a much simpler internal architecture than the MCP23017.

Its eight pins are quasi-bidirectional. There is no separate direction register. Writing a zero actively drives a pin LOW. Writing a one releases it into its HIGH/input state using the device’s internal current-source behaviour. You then read the port to determine whether an external circuit is pulling that line LOW.

That approach is elegant for switches and simple outputs, but it can surprise users expecting ordinary push-pull GPIO behaviour. A PCF8574 pin driven HIGH is not equivalent to a normal ESP32 push-pull HIGH output.

TI specifies the PCF8574 for up to 100 kHz I²C. That is adequate for buttons, relays and character displays, but it is noticeably slower than the MCP23017 and PCF8575 if you need to update many lines frequently.

PCF8575: 16 Pins with the Same PCF Behaviour

The PCF8575 is essentially the larger option in this comparison: 16 quasi-bidirectional lines arranged as two eight-bit ports. It retains the simple PCF programming model but supports a faster 400 kHz I²C bus.

For a project that needs 16 low-speed digital lines and does not require MCP23017-style pull-up registers or sophisticated interrupt configuration, the PCF8575 can be excellent value. Typical examples include large relay boards, status indicators and panels of dry-contact inputs.

In ESPHome, PCF8575 is configured through the pcf8574 component with pcf8575: true. ESPHome numbers its pins from 0 to 15, even though the datasheet labels the two groups differently.

GPIO Direction: the Biggest Practical Difference

With MCP23017, software explicitly configures a pin as input or output. This matches the programming model developers already know from ESP32:

Pin 0 → INPUT_PULLUP
Pin 1 → INPUT
Pin 8 → OUTPUT
Pin 9 → OUTPUT

With PCF8574/PCF8575, the concept is different. A bit written LOW is an active LOW output. A bit written HIGH is released and can be read as an input. Libraries and ESPHome hide much of this behaviour, but it remains important electrically.

This is why MCP23017 is easier when the same PCB mixes several input and output types or when you want predictable pull-up behaviour. PCF parts are ideal when the application is simple enough that their quasi-bidirectional model is an advantage rather than a constraint.

Internal Pull-Ups: MCP23017 Wins

MCP23017 has configurable internal pull-ups on its GPIO inputs. In ESPHome you can therefore define an input with a pull-up much like a native ESP32 pin.

PCF8574 and PCF8575 do not have an equivalent programmable pull-up register. Their HIGH/input state includes the quasi-bidirectional current-source behaviour, but this should not be confused with a conventional selectable pull-up. For long button wires, noisy environments or defined logic thresholds, an external resistor is often the better design.

A typical dry-contact input is easy to wire: pull the input up to 3.3 V and let the switch connect it to ground. This also keeps the logic entirely within the ESP32’s 3.3 V domain.

Interrupts: All Three Can Avoid Constant Polling

All three devices have interrupt capability, but the sophistication differs.

FeatureMCP23017PCF8574 / PCF8575
Interrupt outputsINTA + INTBOne INT
Output typeConfigurable; open-drain availableOpen-drain
Per-pin interrupt enableYesSimple port change behaviour
Compare to stored/default stateYesNo equivalent register system
Captured state registerYesNo equivalent capture register
ESPHome interrupt-driven supportYesYes

Current ESPHome can use an interrupt_pin for both families. When configured, ESPHome reads the expander when its INT line changes instead of continuously polling it. The INT connection must go to a real ESP32 GPIO, not to another expander pin.

Because PCF8574/PCF8575 INT is active-low and open-drain, it requires a pull-up. MCP23017 can also be configured for open-drain interrupts, which is particularly useful if the expander is powered above 3.3 V while the ESP32 interrupt pin remains in the 3.3 V domain.

I²C Addresses and Using Multiple Expanders

Each part has three hardware address pins, normally labelled A0, A1 and A2. That gives eight address combinations.

DeviceCommon address rangeNotes
MCP230170x20–0x27A0–A2 select address
PCF85740x20–0x27A0–A2 select address
PCF8574A0x38–0x3FDifferent base range from PCF8574
PCF85750x20–0x27A0–A2 select address

Notice the collision: MCP23017, PCF8574 and PCF8575 can all occupy the 0x20–0x27 range. If you mix them on one bus, choose address-pin combinations carefully. Two I²C devices cannot share the same address unless additional bus switching or multiplexing is used.

PCF8574A is useful when you need additional PCF8574 devices because its address family is different. Always run an I²C scan on the finished hardware rather than relying only on breakout-board labels.

3.3 V Wiring with ESP32

For a straightforward ESP32 design, power the expander from 3.3 V and keep SDA/SCL pull-ups at 3.3 V. All three families support operation in that range.

Breakout modules sometimes include their own I²C pull-up resistors. If you connect several modules, those pull-ups end up in parallel. Too many parallel pull-ups can make the effective resistance unnecessarily low, so inspect the modules instead of automatically adding another pair.

If an expander is powered from 5 V, do not automatically pull SDA, SCL or an interrupt line up to 5 V and connect it directly to the ESP32. Keep the ESP32 side within its 3.3 V logic domain and use a proper level-shifting arrangement where required.

Can These Expanders Drive Relays?

They can control relay modules or driver transistors, but do not connect a bare relay coil directly to an expander GPIO. A relay coil is an inductive load and normally requires a transistor or MOSFET driver plus flyback protection.

PCF8574 and PCF8575 are often used with active-low relay boards because pulling an output LOW fits their electrical behaviour well. However, check the relay module’s input current and voltage requirements. A board designed for 5 V logic is not automatically a clean match for a 3.3 V expander.

MCP23017 gives more conventional output behaviour and is the easier choice when you are designing a custom relay interface. For mains switching, the expander is only the logic side; isolation, relay/contact ratings, creepage and the enclosure still determine whether the finished product is safe.

For the complete output side, see our ESP32 smart relay with ESPHome guide.

Which Expander Is Best for Buttons and Contact Sensors?

For a few simple buttons, any of the three works well. For a large input panel, MCP23017 is normally the most comfortable because it provides pull-ups and richer interrupt handling.

Input projectBest choiceWhy
4–8 simple buttonsPCF8574Cheap and sufficient
8–16 dry contactsMCP23017 or PCF8575Both provide enough pins
Long wired contact panelMCP23017Clear input configuration and pull-up control
Interrupt-heavy button panelMCP23017Per-pin interrupt configuration and capture
Lowest-cost 16-input panelPCF8575Simple 16-bit device

For door and window contacts, debounce in software and use sensible external wiring. Our ESP32 reed-switch Home Assistant guide covers the sensor-side logic and filtering.

Which Expander Is Best for a Keypad?

A matrix keypad repeatedly changes some lines between driven outputs and sensed inputs. MCP23017 is therefore the cleaner choice because direction and output state are explicitly controlled. Microchip even publishes application guidance for matrix keypads using its GPIO expanders.

PCF8574 can be used for keypads and is found in many hobby designs, but its quasi-bidirectional behaviour needs to be understood. If you are writing your own scanning code, do not assume each PCF pin behaves exactly like a native MCU GPIO.

ESPHome Example: MCP23017

Current ESPHome has native MCP23017 support. This example adds one relay-module output and one button input with an internal pull-up:

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: true

mcp23017:
  - id: mcp_hub
    address: 0x20
    interrupt_pin: GPIO27
    open_drain_interrupt: true

switch:
  - platform: gpio
    name: "Expander Relay"
    pin:
      mcp23xxx: mcp_hub
      number: 8
      mode:
        output: true
      inverted: false

binary_sensor:
  - platform: gpio
    name: "Expander Button"
    pin:
      mcp23xxx: mcp_hub
      number: 0
      mode:
        input: true
        pullup: true
      interrupt: FALLING
      inverted: true
    filters:
      - delayed_on: 20ms
      - delayed_off: 20ms

ESPHome numbers MCP23017 GPA0–GPA7 as 0–7 and GPB0–GPB7 as 8–15. The interrupt pin shown above must be wired from the MCP23017’s interrupt output to ESP32 GPIO27.

ESPHome Example: PCF8574

For an eight-pin PCF8574:

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: true

pcf8574:
  - id: pcf8_hub
    address: 0x20
    pcf8575: false
    interrupt_pin: GPIO27

switch:
  - platform: gpio
    name: "PCF8574 Output 0"
    pin:
      pcf8574: pcf8_hub
      number: 0
      mode:
        output: true
      inverted: false

binary_sensor:
  - platform: gpio
    name: "PCF8574 Input 1"
    pin:
      pcf8574: pcf8_hub
      number: 1
      mode:
        input: true
      inverted: true
    filters:
      - delayed_on: 20ms
      - delayed_off: 20ms

For a physical button, use a defined external bias suitable for your wiring. The PCF input model is different from MCP23017, so do not add an ESPHome pullup: true and expect a separate internal pull-up register to appear.

ESPHome Example: PCF8575

PCF8575 uses the same ESPHome component. Set pcf8575: true and use pins 0–15:

pcf8574:
  - id: pcf16_hub
    address: 0x21
    pcf8575: true
    interrupt_pin: GPIO27

switch:
  - platform: gpio
    name: "PCF8575 Output 12"
    pin:
      pcf8574: pcf16_hub
      number: 12
      mode:
        output: true
      inverted: false

The pcf8574: key is not a mistake: ESPHome’s PCF8574 component handles both the eight-bit and 16-bit devices.

Arduino Example: MCP23017

With the Adafruit MCP23X17 library, MCP23017 programming closely resembles normal Arduino GPIO:

#include <Wire.h>
#include <Adafruit_MCP23X17.h>

Adafruit_MCP23X17 mcp;

void setup() {
  Serial.begin(115200);
  Wire.begin();

  if (!mcp.begin_I2C(0x20)) {
    Serial.println("MCP23017 not found");
    while (true) delay(100);
  }

  mcp.pinMode(0, INPUT_PULLUP);
  mcp.pinMode(8, OUTPUT);
  mcp.digitalWrite(8, LOW);
}

void loop() {
  bool pressed = !mcp.digitalRead(0);
  mcp.digitalWrite(8, pressed ? HIGH : LOW);
  delay(20);
}

This example deliberately uses a low update rate. I²C GPIO should be treated as remote I/O, not as a substitute for timing-critical native GPIO.

Arduino Example: PCF8574 without a Library

The PCF8574 protocol is simple enough to demonstrate with Wire. Writing 0xFF leaves all eight lines in their HIGH/input state; reading returns the current port value:

#include <Wire.h>

constexpr uint8_t PCF_ADDR = 0x20;

void setup() {
  Serial.begin(115200);
  Wire.begin();

  // Release all pins HIGH so they can be read as inputs.
  Wire.beginTransmission(PCF_ADDR);
  Wire.write(0xFF);
  Wire.endTransmission();
}

void loop() {
  Wire.requestFrom(PCF_ADDR, (uint8_t) 1);

  if (Wire.available()) {
    uint8_t state = Wire.read();
    Serial.printf("Port = 0x%02X\n", state);
  }

  delay(250);
}

If you later drive some bits LOW, maintain a software copy of the output byte so you do not accidentally change unrelated pins when writing a new port value.

Performance and Bus Traffic

For human-speed controls, all three devices are fast enough. The differences matter when software repeatedly updates or scans many I/O lines.

  • PCF8574: limited to 100 kHz, so use it for simple low-rate I/O.
  • PCF8575: 400 kHz makes the 16-bit device more comfortable for larger panels.
  • MCP23017: supports faster I²C and richer register access, but the practical bus speed is still limited by the slowest device on that I²C segment.

Using the interrupt pin is often more important than increasing bus speed. A 100 kHz expander that generates an interrupt only when a button changes can create less traffic than a faster expander polled hundreds of times per second.

What Not to Put on an I²C GPIO Expander

Use native ESP32 GPIO for functions that depend on tight timing or dedicated peripherals. Avoid putting these on MCP23017/PCF857x unless the specific software component explicitly supports it:

  • WS2812 / NeoPixel data.
  • High-frequency PWM.
  • UART RX/TX.
  • I²S signals.
  • OneWire buses such as DS18B20.
  • Fast pulse counting.
  • Stepper-motor step pulses.

ESPHome makes the same distinction: expander pins can emulate GPIO for many components, but components that depend on more complicated communication or timing do not automatically work through an expander.

Common Problems

SymptomFirst checks
I²C scan finds no expanderCheck 3.3 V power, common ground, SDA/SCL pins, pull-ups and address straps.
Wrong device responds at 0x20Check for address collision; MCP23017 and PCF857x share common address ranges.
PCF input always reads HIGHConfirm the pin was released HIGH before reading and that the external switch actually pulls it LOW.
MCP button floatsEnable its pull-up or add an external bias resistor.
INT never changesCheck open-drain pull-up, interrupt configuration and wiring to a real ESP32 GPIO.
Relay works invertedMany relay modules are active LOW; configure inverted: true if appropriate.
Random I²C errors after adding boardsInspect total pull-up resistance, wiring length, bus speed and supply noise.
Fast pulse input misses eventsMove the signal to a native ESP32 GPIO or dedicated counter peripheral.

Which GPIO Expander Should You Buy?

Buy MCP23017 when the expander is an important part of the design rather than just a cheap way to gain pins. It is the best option for mixed input/output panels, many switches, interrupt-driven inputs and projects where predictable GPIO configuration matters.

Buy PCF8574 when you need only eight slow lines, especially for a few buttons, simple outputs or an LCD backpack. It is cheap, widely available and easy to understand once you remember the quasi-bidirectional behaviour.

Buy PCF8575 when you need 16 low-speed lines at minimal cost and the simpler PCF architecture is acceptable. It is particularly attractive for large relay/input boards where each line changes relatively slowly.

If the price difference is small, MCP23017 is the safest default. PCF8574 and PCF8575 remain excellent parts when their simpler electrical model matches the application.

Related ESP32 Guides

Datasheets & External Resources

Specifications and ESPHome behaviour were checked against the current manufacturer and ESPHome documentation.

Share your love