Electronics Design AU
Components

MCP23017 vs PCF8574: Which I2C GPIO Expander Should You Use?

Last updated 10 July 2026 · 7 min read

Direct Answer

The MCP23017 (16-bit, Microchip) and PCF8574 (8-bit, NXP/TI) are the two most commonly specified I2C GPIO expander ICs, and they differ in ways that matter for real designs, not just pin count. The MCP23017 provides true push-pull GPIO with individually configurable direction, polarity, and internal pull-ups per pin, plus a full interrupt-on-change subsystem (per-pin interrupt enable, compare-to-default or compare-to-previous modes, and a hardware INT output) — making it the better choice whenever any pin needs to drive an output confidently or trigger the host on a change without polling. The PCF8574 uses NXP's older quasi-bidirectional I/O structure (a weak internal pull-up and a strong pull-down only, no true push-pull output), has no interrupt masking or configuration (only a single shared active-low interrupt output that fires on any pin change with no way to identify which pin without a full port read), but is simpler, cheaper, and adequate for straightforward input-scanning or open-drain-style output applications like driving an LCD backpack. Both are available with different fixed address-pin combinations, but the practical addressable range differs: the MCP23017 supports up to 8 devices on one bus (3 address pins), the PCF8574 also up to 8 (3 address pins) with the PCF8574A variant providing a second block of 8 at a different base address, allowing 16 total on one bus if both variants are mixed.

Detailed Explanation

Running out of GPIO pins on a microcontroller is a common constraint, especially on smaller packages or when a design later grows to need more inputs or outputs than were originally planned for. I2C GPIO expanders solve this by adding pins over the same two-wire I2C bus already used for other peripherals, at the cost of a small amount of I2C traffic and (depending on the part) some limitations compared to a microcontroller's native GPIO. The MCP23017 and PCF8574 are the two parts most commonly reached for, and the choice between them is a real design decision, not an arbitrary preference — they differ in output drive capability, interrupt handling, and configurability in ways that determine whether a given application will actually work correctly.

Output Drive: Push-Pull vs Quasi-Bidirectional

The MCP23017 provides genuine push-pull GPIO on every pin, configurable per-pin as input or output via the IODIR register — functionally similar to a microcontroller's own GPIO pins, including a driven-high output state capable of sourcing real current (up to the part's per-pin and total package current limits).

The PCF8574 uses NXP's older quasi-bidirectional I/O architecture: writing a pin high enables only a weak internal current source (not a true driven-high output), while writing a pin low enables a strong pull-down capable of sinking meaningful current. This asymmetry means the PCF8574 behaves somewhat like an open-drain output with an always-on weak pull-up — adequate for reading switch inputs (with the weak pull-up providing the idle-high state) and for active-low output applications, but not a substitute for a true push-pull output when a pin genuinely needs to source current to drive something.

Interrupt Capability

Both parts can generate a hardware interrupt on a GPIO state change, but the depth of that capability differs substantially:

  • PCF8574: A single shared, unmasked interrupt output. Any pin changing state asserts the interrupt; there is no register indicating which pin caused it. Firmware must read the full port and diff it against the last known state to determine what changed — workable for simple applications, but with an inherent race condition if multiple pins change in quick succession.
  • MCP23017: A configurable interrupt-on-change subsystem. GPINTEN enables interrupt-on-change per individual pin; DEFVAL/INTCON select whether the comparison is against a fixed default value or the previous pin state; INTCAP captures the exact GPIO state at the moment the interrupt fired (avoiding the PCF8574's race condition); and INTF identifies which specific pin triggered the interrupt. The MCP23017 also supports mirroring both 8-bit ports' interrupts onto a single physical INT pin, or keeping them separate.

See the FAQ above for the practical consequence of this difference in a keypad or limit-switch application.

Pin Count and Addressing

The MCP23017 provides 16 GPIO pins per device, organised as two 8-bit ports (GPIOA and GPIOB), each with its own register set. The PCF8574 provides 8 pins per device. Both parts set their I2C address using three hardware address pins (A0–A2), allowing up to 8 devices of the same part on one bus — for the PCF8574, the PCF8574A variant uses a different fixed base address, so mixing standard PCF8574 and PCF8574A devices on the same bus can extend this to 16 total 8-bit expanders (128 pins) if that many are genuinely needed, though at that scale a design should also consider whether an I2C bus multiplexer or a different expansion approach might be more maintainable.

SPI Variants

Both families have SPI-interface siblings for applications where I2C's shared-bus addressing limit or speed ceiling is a constraint: the MCP23S17 is the SPI equivalent of the MCP23017 (same register map, different physical interface), and there is no direct PCF8574 SPI equivalent from the same vendor family — a design needing SPI-interface port expansion with PCF8574-like simplicity typically looks at a different part family rather than an SPI variant of the PCF8574 specifically.

Practical Examples

A keypad and status-LED product uses an MCP23017 to read an 4×4 matrix keypad (8 pins) and drive 6 status LEDs (6 pins) from the remaining GPIOB pins. The design relies on the MCP23017's per-pin interrupt-on-change and INTCAP register to wake the host MCU only when a key is actually pressed, rather than polling the keypad continuously — a pattern the PCF8574's shared, unmasked interrupt output could support only with more firmware complexity to disambiguate which key changed.

A cost-sensitive sensor node uses a PCF8574 purely to read 8 digital limit-switch inputs, wired so each switch pulls its pin low when triggered (taking advantage of the PCF8574's weak internal pull-up providing the idle-high state with no external pull-up resistors needed). The application reads the full port on a periodic poll rather than relying on the interrupt output, sidestepping the "which pin changed" ambiguity entirely and taking advantage of the PCF8574's lower unit cost for a design that doesn't need push-pull outputs or per-pin interrupt configuration.

Design Considerations

  • Choose the MCP23017 whenever any expanded pin needs to drive an output with real source current, or when per-pin interrupt disambiguation matters — a keypad, a bank of independently-monitored inputs, or any output that isn't simply an active-low LED driver.
  • The PCF8574 is a reasonable, lower-cost choice for straightforward input scanning or active-low output applications where its quasi-bidirectional limitations and shared interrupt line are acceptable trade-offs, such as an LCD backpack or a bank of switch inputs polled periodically.
  • I2C bus pull-ups are still required regardless of which expander is used — see the FAQ above; don't confuse the expander's own GPIO-side pull-up behaviour with the separate SDA/SCL bus pull-up requirement.
  • Plan address-pin strapping early if multiple expanders of the same part will share a bus — running out of the 8 available addresses on a single part family is a common late-stage surprise; the MCP23017's 16 pins per device often means fewer total ICs are needed than the equivalent PCF8574-based design.
  • Zeus Design's electronics engineering team selects and integrates I2C peripheral ICs, including GPIO expansion, for embedded products.

Common Mistakes

  • Expecting the PCF8574 to source real current from a "high" output, then finding an LED or other load doesn't light or drive properly — see the FAQ above for the active-low wiring pattern that works around the quasi-bidirectional structure's weak pull-up.
  • Assuming the PCF8574 can identify which pin caused an interrupt without reading the full port and diffing against the previous state in firmware — there is no per-pin interrupt source register on this part, unlike the MCP23017.
  • Forgetting I2C bus pull-up resistors because the expander's own GPIO pins have pull-up behaviour, and assuming that satisfies the bus's own SDA/SCL pull-up requirement — these are unrelated (see the FAQ above).
  • Not checking total sink/source current limits when using many pins simultaneously to drive LEDs or other loads directly — both parts have per-pin and total-package current limits well below what a microcontroller's own GPIO can sometimes provide, and exceeding them (even below the absolute maximum per pin) can affect logic-level accuracy on the remaining pins.
  • Choosing based on pin count alone without considering the output-drive and interrupt differences — a design that needs push-pull outputs or precise interrupt handling will hit real functional limitations with the PCF8574 regardless of how many spare pins it has.

Frequently Asked Questions

Can I use the PCF8574 to drive an LED or a high-current load directly?
Not reliably as a sourcing output. The PCF8574's quasi-bidirectional structure only provides a weak internal pull-up (typically on the order of 100 µA) when a pin is written high — nowhere near enough current to drive an LED at a useful brightness directly. It can sink current reasonably well when driven low (this is the 'strong pull-down' half of quasi-bidirectional operation), so the common working pattern is active-low: wire the LED (with a series resistor) between the supply rail and the PCF8574 pin, and drive the pin low to turn the LED on. The MCP23017, with true push-pull GPIO, can both source and sink meaningful current directly and doesn't need this workaround, though neither part is intended to drive a load beyond a few tens of milliams per pin — a transistor or driver IC is still required for anything higher current.
How do I identify which pin caused an interrupt on a PCF8574 vs an MCP23017?
This is one of the starkest practical differences between the two parts. The PCF8574 has one shared, unmasked interrupt output that asserts whenever any input pin changes state — the only way to determine which pin changed is to read the full 8-bit port immediately after the interrupt and compare it against the previously known state in firmware, which also has a race condition if multiple pins change close together. The MCP23017 has dedicated INTCAP and GPINTEN registers: INTCAP captures the exact pin state at the moment of the interrupt (avoiding the race condition), GPINTEN lets you enable interrupt-on-change per individual pin, and the interrupt source can be read directly rather than inferred by comparison. For any design where knowing exactly which input changed matters — a keypad, a bank of limit switches — the MCP23017's interrupt subsystem is a substantial practical advantage, not just a spec-sheet feature.
Do I need external pull-up resistors on the I2C bus for these expanders, separate from any GPIO pull-ups?
Yes — this is a common point of confusion because both parts also have their own GPIO-side pull-up behaviour (the MCP23017's configurable internal GPIO pull-ups, the PCF8574's weak quasi-bidirectional pull-ups), but that is entirely separate from the I2C bus itself. SDA and SCL still need their own pull-up resistors (commonly 2.2–10 kΩ, sized per the bus speed and total bus capacitance) exactly as with any other I2C device — see what is I2C? for the general pull-up sizing guidance. Confusing the GPIO-pin pull-up configuration registers with the I2C bus pull-up requirement is an easy mistake when first bringing up either part.

References

Related Questions

Related Forum Discussions