The MCP23017 is one of the simplest ways to add a large number of digital inputs and outputs to an ESP32 without consuming sixteen more microcontroller pins. One MCP23017 provides 16 GPIOs over the existing I²C bus, and ESPHome can expose those expander pins almost like ordinary ESP32 GPIOs.
That makes it particularly useful for Home Assistant projects with many push buttons, reed switches, relays, status LEDs, alarm contacts or other slow digital signals. The important word is slow: the MCP23017 is an I²C GPIO expander, not a replacement for native ESP32 pins when you need PWM, microsecond timing, high-speed pulse counting or waveform generation.
This guide shows how to wire an MCP23017 safely to a 3.3 V ESP32, configure it in current ESPHome, use inputs with internal pull-ups, control outputs, use multiple expanders, and decide when the interrupt feature is worth connecting.
If your problem is several I²C sensors that all have the same fixed address, an I/O expander is not the solution. Use an I²C multiplexer instead; see our TCA9548A I²C Multiplexer with ESP32 guide.
What the MCP23017 Actually Does
The MCP23017 is a 16-bit general-purpose I/O expander from Microchip. Internally, its GPIOs are arranged as two eight-bit ports called GPIOA and GPIOB. Communication with the ESP32 happens over I²C, so only SDA and SCL are needed regardless of whether you use one expander pin or all sixteen.
Microchip specifies an operating supply range of 1.8 V to 5.5 V, three hardware address inputs, interrupt outputs and up to eight devices on one I²C bus. The device supports 100 kHz, 400 kHz and higher-speed I²C operation, although there is rarely a reason to push a Home Assistant GPIO expander to its maximum bus rate. For an ESP32 project, powering the expander at 3.3 V is usually the cleanest option because the I²C bus and interrupt signals then remain in the ESP32’s logic-voltage domain.
| Feature | MCP23017 |
|---|---|
| Digital GPIO | 16 |
| Interface | I²C |
| Default address | 0x20 |
| Address range | 0x20 to 0x27 |
| Supply voltage | 1.8 to 5.5 V |
| Ports | GPIOA and GPIOB, 8 pins each |
| Internal input pull-ups | Yes |
| Interrupt outputs | INTA and INTB |
| PWM / analogue output | No |
| ESPHome support | Native MCP23017 component |
Microchip’s current product page lists the MCP23017 as an in-production part and specifies 25 mA sink/source capability per I/O. Do not interpret that headline figure as permission to load sixteen outputs at 25 mA simultaneously. Per-pin limits, port/package current limits, voltage drop and thermal limits all matter. For practical ESPHome builds, use the expander as logic I/O and add a transistor, MOSFET or driver IC for substantial loads.
MCP23017 Pin Naming in ESPHome
The datasheet names the pins A0 to A7 and B0 to B7. ESPHome maps those sixteen pins to one continuous numbering range from 0 to 15.
| MCP23017 pin | ESPHome number | MCP23017 pin | ESPHome number |
|---|---|---|---|
| A0 | 0 | B0 | 8 |
| A1 | 1 | B1 | 9 |
| A2 | 2 | B2 | 10 |
| A3 | 3 | B3 | 11 |
| A4 | 4 | B4 | 12 |
| A5 | 5 | B5 | 13 |
| A6 | 6 | B6 | 14 |
| A7 | 7 | B7 | 15 |
This mapping is worth keeping next to your wiring diagram. If a button is physically connected to GPB3, for example, the ESPHome pin number is 11, not 3.
Wiring MCP23017 to an ESP32
For a classic ESP32 DevKit, GPIO21 and GPIO22 are convenient I²C examples. Other valid I²C-capable pins can be used if your board or project already assigns these pins elsewhere.
| MCP23017 | ESP32 DevKit | Purpose |
|---|---|---|
| VDD | 3.3 V | Power |
| VSS / GND | GND | Common ground |
| SDA | GPIO21 | I²C data |
| SCL | GPIO22 | I²C clock |
| A0 | GND | Address bit 0 |
| A1 | GND | Address bit 1 |
| A2 | GND | Address bit 2 |
| RESET | 3.3 V / pull-up | Keep device out of reset |
With A0, A1 and A2 low, the address is 0x20. Many breakout boards already include appropriate address pull-downs or solder jumpers, and many also pull RESET high. A bare MCP23017 does not magically know what you intended: do not leave address pins or RESET floating. Check the schematic of the exact board you bought.
I²C needs pull-up resistors on SDA and SCL. Breakout boards often include them, and another device already on the same bus may also include pull-ups. Several pull-ups in parallel reduce the effective resistance, so adding a new 4.7 kΩ pair every time you add a module is not automatically better.
Why 3.3 V Power Is the Easy Choice
The MCP23017 can operate from 5 V, but an ESP32 is a 3.3 V device and its GPIOs are not general-purpose 5 V tolerant inputs. If a 5 V-powered expander or breakout pulls SDA, SCL or an interrupt line to 5 V, that is unsafe for direct ESP32 connection.
Powering the MCP23017 from 3.3 V avoids this entire problem for ordinary projects. If you have a reason to operate the expander at 5 V, design the bus and interrupt level shifting deliberately rather than assuming a breakout board makes every signal safe.
I²C Addresses: 0x20 to 0x27
Three hardware address pins give eight possible MCP23017 addresses. This means eight chips can share the same SDA/SCL pair without an I²C multiplexer, providing as many as 128 digital I/O lines.
| A2 | A1 | A0 | Address |
|---|---|---|---|
| 0 | 0 | 0 | 0x20 |
| 0 | 0 | 1 | 0x21 |
| 0 | 1 | 0 | 0x22 |
| 0 | 1 | 1 | 0x23 |
| 1 | 0 | 0 | 0x24 |
| 1 | 0 | 1 | 0x25 |
| 1 | 1 | 0 | 0x26 |
| 1 | 1 | 1 | 0x27 |
On a breakout, ‘high’ normally means tying or soldering the corresponding address jumper to the board’s VDD rail. The exact jumper layout varies. Always confirm the detected address with an I²C scan instead of trusting a product listing.
Minimal ESPHome Configuration
The following configuration establishes Wi-Fi, Home Assistant native API, OTA updates and the I²C bus, then creates one MCP23017 hub at address 0x20.
esphome:
name: esp32-mcp23017
friendly_name: ESP32 MCP23017
esp32:
board: esp32dev
logger:
api:
ota:
- platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
i2c:
sda: GPIO21
scl: GPIO22
scan: true
frequency: 400kHz
mcp23017:
- id: mcp_hub
address: 0x20
Leave scan: true enabled while bringing the hardware up. If the device is powered and wired correctly, the boot log should report an I²C device at 0x20, or at whichever address you selected with A0–A2.
Use an MCP23017 Pin as a Digital Input
A common application is expanding buttons, door contacts and reed switches. The MCP23017 provides weak internal pull-ups, so a simple dry contact can connect the input to ground when active.
binary_sensor:
- platform: gpio
name: "Front Door Contact"
pin:
mcp23xxx: mcp_hub
number: 8
mode:
input: true
pullup: true
inverted: true
filters:
- delayed_on: 30ms
- delayed_off: 30ms
Here, number 8 is GPB0. The pull-up keeps the input high while the contact is open. Closing the contact pulls the pin to ground, so inverted: true makes the Home Assistant entity read ON when the contact closes.
The short delayed filters provide debounce and noise rejection. Mechanical push buttons and reed contacts can change state several times over a few milliseconds. Filtering prevents those transitions appearing as multiple events.
Long Wires Need More Than YAML
Internal pull-ups are convenient inside an enclosure, but they are weak. Long cables routed through a building are antennas for electrical noise and can pick up transients from relay coils, lighting circuits and motors.
For remote contacts, consider a stronger external pull-up, suitable filtering, surge/ESD protection and a wiring method designed for the installation environment. ESPHome filtering can suppress bounce; it cannot protect a silicon input from an excessive voltage transient.
Use an MCP23017 Pin as an Output
ESPHome can expose an expander output as a normal GPIO switch.
switch:
- platform: gpio
name: "MCP Output A0"
id: output_a0
pin:
mcp23xxx: mcp_hub
number: 0
mode:
output: true
inverted: false
restore_mode: ALWAYS_OFF
Number 0 is GPA0. This is appropriate for driving a small logic input, an LED through a correctly sized resistor, or the input of a transistor/driver stage.
Do not connect a bare relay coil, solenoid, motor or other inductive load directly to an MCP23017 GPIO. Use a transistor or MOSFET with appropriate flyback protection, or a relay module whose input is electrically compatible with 3.3 V logic. Our ESP32 Smart Relay for Home Assistant guide covers the load-side considerations in more detail.
The Boot-State Trap
After reset, MCP23017 GPIOs start as inputs. ESPHome later configures a pin as an output and applies the requested state. That means restore_mode: ALWAYS_OFF does not guarantee a dangerous external circuit can never twitch during power-up.
If an output must remain positively OFF before firmware is running, design that state in hardware with an appropriate pull-up or pull-down on the transistor or driver input. Safety should not depend on the I²C bus being initialized successfully.
Buttons and Relays Together: Complete Example
The following example uses four MCP23017 inputs for buttons or contacts and four outputs for low-voltage driver inputs. The outputs are shown as active high; change inverted only if your actual driver module requires active-low logic.
mcp23017:
- id: mcp_hub
address: 0x20
binary_sensor:
- platform: gpio
name: "Input 1"
pin:
mcp23xxx: mcp_hub
number: 8
mode:
input: true
pullup: true
inverted: true
filters:
- delayed_on_off: 30ms
- platform: gpio
name: "Input 2"
pin:
mcp23xxx: mcp_hub
number: 9
mode:
input: true
pullup: true
inverted: true
filters:
- delayed_on_off: 30ms
- platform: gpio
name: "Input 3"
pin:
mcp23xxx: mcp_hub
number: 10
mode:
input: true
pullup: true
inverted: true
- platform: gpio
name: "Input 4"
pin:
mcp23xxx: mcp_hub
number: 11
mode:
input: true
pullup: true
inverted: true
switch:
- platform: gpio
name: "Output 1"
pin:
mcp23xxx: mcp_hub
number: 0
mode:
output: true
restore_mode: ALWAYS_OFF
- platform: gpio
name: "Output 2"
pin:
mcp23xxx: mcp_hub
number: 1
mode:
output: true
restore_mode: ALWAYS_OFF
- platform: gpio
name: "Output 3"
pin:
mcp23xxx: mcp_hub
number: 2
mode:
output: true
restore_mode: ALWAYS_OFF
- platform: gpio
name: "Output 4"
pin:
mcp23xxx: mcp_hub
number: 3
mode:
output: true
restore_mode: ALWAYS_OFF
Because ESPHome treats these as normal GPIO-backed entities, Home Assistant receives the binary sensors and switches through the native API in the same way it would receive entities implemented with ESP32 pins.
Using the MCP23017 Interrupt Output
For many Home Assistant projects you can stop here. Polling sixteen slow buttons over I²C does not normally create a meaningful load. However, current ESPHome also supports the MCP23017 interrupt output. When configured, ESPHome can avoid repeatedly reading the expander and instead respond when an input changes.
ESPHome 2026.9 documents an interrupt_pin option for the MCP23017 and allows individual expander pins to select CHANGE, RISING or FALLING interrupt behaviour. The interrupt line must connect to a real ESP32 GPIO, not to another expander pin.
mcp23017:
- id: mcp_hub
address: 0x20
interrupt_pin: GPIO27
open_drain_interrupt: true
binary_sensor:
- platform: gpio
name: "Fast Door Contact"
pin:
mcp23xxx: mcp_hub
number: 15
mode:
input: true
pullup: true
inverted: true
interrupt: CHANGE
With open_drain_interrupt: true, the interrupt line needs a pull-up to 3.3 V. A physical resistor is the conservative choice. The MCP23017 provides INTA and INTB outputs associated with its two ports, so wire the interrupt output appropriate to the inputs you are using and follow the current ESPHome documentation if you need a more complex mirrored interrupt arrangement.
An interrupt does not make the MCP23017 equivalent to a native ESP32 GPIO for precision timing. ESPHome still has to communicate over I²C to determine the state. Use native GPIO, a hardware pulse counter or a dedicated peripheral for events where microsecond-level timing matters.
Using Two or More MCP23017 Expanders
Adding a second expander only requires a different hardware address and a second ESPHome hub definition.
mcp23017:
- id: io_bank_1
address: 0x20
- id: io_bank_2
address: 0x21
binary_sensor:
- platform: gpio
name: "Bank 1 Input"
pin:
mcp23xxx: io_bank_1
number: 8
mode:
input: true
pullup: true
inverted: true
- platform: gpio
name: "Bank 2 Input"
pin:
mcp23xxx: io_bank_2
number: 8
mode:
input: true
pullup: true
inverted: true
Set A0 high on the second chip to obtain 0x21. With all three address pins available, addresses 0x20 through 0x27 can coexist on the same bus.
As the bus grows, pay attention to wiring length, total bus capacitance and the effective pull-up resistance. Eight addressable chips do not automatically mean eight physically distant boards should be connected with long untwisted wires around a building. I²C was designed as a board-level bus, not as a general-purpose long-distance fieldbus.
MCP23017 vs PCF8574 vs PCA9685 vs TCA9548A
These inexpensive I²C modules are often grouped together in online listings, but they solve different problems.
| Device | Main job | When to use it |
|---|---|---|
| MCP23017 | 16 digital GPIO | Buttons, contacts, LEDs, driver inputs |
| PCF8574 | 8 simple I/O lines | Very simple low-cost expansion |
| PCA9685 | 16-channel PWM driver | Servos and dimmable LED/PWM outputs |
| TCA9548A | 8-way I²C multiplexer | Several sensors with the same I²C address |
The MCP23017 is usually the more capable choice when you want ordinary digital GPIO because it provides explicit direction control, internal pull-ups and interrupt support. The PCA9685 is a much better choice if what you actually need is hardware PWM. The TCA9548A does not add GPIO at all; it creates separate I²C branches.
What You Should Not Use MCP23017 Pins For
- NeoPixels or other tightly timed addressable LEDs. The data waveform requires timing the I²C expander cannot provide.
- High-frequency pulse counting. Use an ESP32 hardware counter-capable input or a dedicated counter.
- Fast rotary encoders. Slow manual controls may work, but native GPIO is more robust for rapid edges.
- Servo PWM. Use an ESP32 PWM channel or a PCA9685.
- Analogue measurements. MCP23017 GPIOs are digital only; use an ADC such as ADS1115 for analogue signals.
- Driving power loads directly. Use proper transistor, MOSFET, relay-driver or load-driver circuitry.
The expander is strongest where Home Assistant is strongest: relatively slow real-world states such as ‘door open’, ‘button pressed’, ‘relay requested’ and ‘alarm contact active’.
Troubleshooting: MCP23017 Does Not Appear in the I²C Scan
If ESPHome does not report the expected address, debug the electrical layer before changing entity YAML.
- Confirm VDD is actually present at the MCP23017 and that grounds are common.
- Check SDA and SCL have not been swapped.
- Verify A0, A1 and A2 and calculate the address they create.
- Check RESET is high; a low RESET pin holds the device inactive.
- Measure the idle SDA and SCL voltage. On a 3.3 V ESP32 bus it should not be pulled to 5 V.
- Check whether the breakout already has pull-ups before adding more.
- Reduce the I²C frequency to 100 kHz while troubleshooting long or messy wiring.
- Disconnect other I²C devices temporarily to rule out an address conflict or a device holding the bus low.
A useful conservative test configuration is:
i2c:
sda: GPIO21
scl: GPIO22
scan: true
frequency: 100kHz
Once the device appears reliably at the expected address, add the MCP23017 component and then add individual inputs and outputs.
Troubleshooting: Input Is Always ON or Always OFF
Start with the electrical state. With an internal pull-up enabled and a switch wired to ground, an open switch should measure near VDD and a closed switch should measure near 0 V.
If the voltage is correct but Home Assistant shows the opposite meaning, change inverted: true or false. Inversion is a logical interpretation; it does not fix a missing ground, an incorrect pin number or a floating contact.
Also check the A/B numbering conversion. GPB0 is ESPHome number 8, GPB7 is 15. Accidentally configuring pin 0 when the wire is on B0 is one of the easiest mistakes to make.
Troubleshooting: Outputs Flicker or Relays Trigger at Boot
Separate firmware behaviour from circuit behaviour. During reset the MCP23017 pins default to inputs, so an external relay driver input may float before ESPHome configures the pin. Add a hardware bias resistor so the driver has a defined safe state whenever the expander is unpowered, in reset or still initializing.
If relay switching causes the ESP32 or MCP23017 to reset, investigate power integrity and electrical noise rather than adding delays to ESPHome. Relay coils need flyback protection unless a module already includes it, and high-current load paths should not share poor breadboard wiring with logic power.
Troubleshooting: I²C Works Until Several Boards Are Connected
Every breakout adds wiring capacitance and often another pair of pull-up resistors. Too much capacitance makes edges slow; too many parallel pull-ups make the bus load unnecessarily heavy. Long cables also increase susceptibility to interference.
Try 100 kHz, shorten wiring, inspect the effective pull-up network and power the modules properly. If your GPIO devices are metres away from the ESP32, consider moving an ESP32 node closer to them or using a communication method intended for longer distances rather than stretching I²C indefinitely.
MCP23017 with Home Assistant
There is no special MCP23017 integration to install in Home Assistant. ESPHome owns the hardware interface and exposes the resulting switches and binary sensors through its native API.
That architecture is useful because local ESPHome logic can continue to work even if Home Assistant is temporarily unavailable. For example, a physical wall button connected to the MCP23017 can directly toggle an output on the same ESP32.
binary_sensor:
- platform: gpio
name: "Local Button"
pin:
mcp23xxx: mcp_hub
number: 8
mode:
input: true
pullup: true
inverted: true
filters:
- delayed_on: 30ms
on_press:
- switch.toggle: local_output
switch:
- platform: gpio
id: local_output
name: "Local Output"
pin:
mcp23xxx: mcp_hub
number: 0
mode:
output: true
restore_mode: ALWAYS_OFF
The action is executed by the ESP32 itself. Home Assistant still sees the state and can automate it, but an internet connection and a running Home Assistant server are not required for the local button-to-output action.
Practical Design Recommendations
- Run the MCP23017 at 3.3 V with an ESP32 unless you have a specific reason to use another voltage.
- Use 0x20 for the first chip and change A0–A2 only when adding more expanders.
- Keep an I²C scan enabled during initial commissioning.
- Use internal pull-ups for short local buttons and contacts; use stronger hardware conditioning where wiring is long or noisy.
- Use native ESP32 GPIO for timing-critical signals.
- Use driver circuitry for relays and other loads rather than treating the expander as a power output.
- Design important outputs to a safe hardware state during reset.
- Start at 100 kHz if the bus is unreliable, then increase the speed only if there is a reason.
- Use the interrupt feature when bus traffic or input response justifies the extra wire; polling is perfectly adequate for many home-automation inputs.
Final Thoughts
The MCP23017 is a very good match for ESPHome because it expands exactly the type of I/O that many Home Assistant projects run out of first: ordinary buttons, switches, contact sensors and control outputs. One inexpensive chip turns a two-wire I²C bus into sixteen additional digital lines, and up to eight addressed devices can coexist on the same bus.
The best results come from treating it as a logic expander rather than as sixteen ‘free ESP32 pins’. Its outputs have electrical limits, I²C adds latency, the power-up state is different from a configured output, and long building wiring needs real electrical design. Within those limits, the MCP23017 is an excellent way to build larger control panels and sensor interfaces without moving to a much larger microcontroller.
For current ESPHome syntax and interrupt options, see the ESPHome MCP230xx documentation. Hardware specifications are available from the Microchip MCP23017 product page.