This tutorial explains how to connect and use the MCP23017 IC with Arduino development boards. The MCP23017 is a versatile 16-bit I/O expander that communicates with microcontrollers using the I²C protocol.
Why would we need an I²C port expander? Development boards, such as Arduino UNO or ESP32, come with a limited number of I/O pins. You can run out of pins if your project involves many sensors, buttons, or displays. Such boards support the I²C (or I2C) communication interface that uses two wires (SDA and SCL) to communicate with multiple devices. A port expander, such as the MCP23017, can provide you with extra digital I/O pins to interface with a microcontroller utilizing just two lines for the I2C bus.

I²C interface helps us to keep electronic designs modular and keep peripheral wiring clean and simple. Moreover, a port expander lets you scale up without redesigning your core microcontroller board.
For example, interfacing a matrix keypad using an I²C port expander like the MCP23017 can bring down the wire count from eight to four, two for SDA and SCL lines and two for power.
Overview of MCP23017
MCP23017 provides 16 bidirectional I/O pins, organized into two 8-bit ports, each of which can be configured individually as input or output. It supports I²C clock speeds up to 1.7 MHz in high-speed mode, allowing fast and efficient data transfer. Each pin can be equipped with internal pull-up resistors, which can be enabled through software. This makes it easier to interface with switches or open-drain devices.
Additionally, the MCP23017 includes configurable interrupt outputs that can be triggered by input changes on any pin. It supports a wide range of operating voltages, ranging from 1.8V to 5.5V.
Pinout

The I/O pins are subdivided into PORTA and PORTB, each consisting of 8 pins- GPA0 to GPA7 and GPB0 to GPB7. These I/O pins are bidirectional and also support interrupts. VDD connects to the positive of power supply, and VSS is the ground pin. SCK and SDA pins facilitate I2C communication. The RESET pin must be connected to VDD externally during operation. INTA and INTB are interrupt output pins for PORTA and PORTB respectively. The NC pins stand for “No Connection,” i.e., they are not used.
⚠ Caution: Do not use the pins GPA7 and GPB7 as inputs. It can lead to malfunctions such as SDA signal corruption, as stated by Microchip.
Address Pins
Pins A0, A1, and A2 are address pins of the MCP23017. They must be connected to either the ground or the positive of the power supply. They determine the address of the MCP23017 IC. The default address of MCP23017 is 0x20 when all three address pins are connected to ground.
A total of 8 MCP23017s can be connected to a single I2C bus with addresses ranging from 0x20 to 0x27. The table below shows how the address changes based on whether the address pins are connected to VDD (High) or VSS (Low).
| A0 | A1 | A2 | I2C Address(Hexadecimal) |
| 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 |
You can view the datasheet of the MCP23017 for more information.
Steps to Interface MCP23017 with Arduino
Here we shall learn a simple way to use the MCP23017 using an Arduino board, two pushbutton switches, and two LEDs. We shall read the status of the pushbuttons connected to MCP23017 via I2C interface and then command MCP23017 to switch LEDs based on the switch inputs.
Components required:
- Arduino UNO (or any other Arduino with 5V logic level)
- 2 LEDs
- 2 resistors (220 Ohm each)
- 2 pushbuttons
- A breadboard and connecting wires
For development boards that use 3.3 Volt logic, such as the ESP32, you will need a logic level converter that sits between the board and the MCP23017 and converts the voltage to suitable levels.
Step1: Wiring MCP23017 I2C Expander with Arduino
Connect the I2C pins in your microcontroller to the I2C pins of MCP23017. For Arduino UNO, connect the MCP23017, Arduino board, and other components as shown in the schematic below:

Connection details:
- Connect 5V pin of Arduino to VDD pin (pin 9) of MCP23017
- Connect GND pin of Arduino to VSS pin (pin 10) of MCP23017
- Connect A4 pin of Arduino to SDA pin (pin 13) of MCP23017
- Connect A5 pin of Arduino to SCK pin (pin 12) of MCP23017
- Connect a LED in series with a resistor between pin 1 of MCP23017 and the ground(negative rail)
- Connect another LED in series with a resistor between pin 2 of MCP23017 the ground
- Connect one pushbutton switch between GPA0 (pin 21) and ground, and another between GPA1 (pin 22) and ground.
Be mindful of the LEDs’ polarity while connecting.
📓Note: In a full-sized breadboard, you may need to use jumpers to bridge the connection between the power rails as they are discontinuous in the centre.
Step 2: Install MCP23017 Arduino Library
MPC23017 is a complex integrated IC that has multiple registers to configure each pin’s direction, pull-ups, interrupt behavior, and output state. Instead of writing custom code, we will use a well-tested library that will help interface with MCP23017 using I2C.
In Arduino IDE, go to Library Manager and search for “MCP23017”. Install the MCP23017 library by Bertrand Lemasle. This arduino-mcp23017 library is also available on GitHub.
Step 3: Upload Code to MCP23017 Using Arduino IDE
Connect your development board to your computer using a USB cable.
Then, open Arduino IDE and select the board you are using. For example, if you are using Arduino UNO, navigate Tools>Board>Arduino AVR Boards>Arduino UNO.
Also, ensure the correct port is selected. You can select the port from Tools>Port in Arduino IDE.
Next, upload the following code for testing MCP23017.
#include <Wire.h>
#include <MCP23017.h>
//I2C address of MCP23017, default is usually 0x20
#define MCP23017_ADDR 0x20
//Create MCP23017 object with the I2C address
MCP23017 mcp(MCP23017_ADDR);
//Variables to store previous button states
bool prevButton0 = HIGH;
bool prevButton1 = HIGH;
void setup() {
//Start I2C communication
Wire.begin();
//Initialize the MCP23017
mcp.init();
//Set pin A0 and A1 as input with internal pull-up resistors
mcp.pinMode(0, INPUT_PULLUP, false);
mcp.pinMode(1, INPUT_PULLUP, false);
//Set pin B0 and B1 as output
mcp.pinMode(8, OUTPUT);
mcp.pinMode(9, OUTPUT);
//Turn off LEDs initially
mcp.digitalWrite(8, LOW);
mcp.digitalWrite(9, LOW);
}
void loop() {
//Read current state of button A0
bool currentButton0 = mcp.digitalRead(0);
//If button is pressed and was not pressed before, toggle LED on B0
if (currentButton0 == LOW && prevButton0 == HIGH) {
bool ledState = mcp.digitalRead(8);
mcp.digitalWrite(8, !ledState);
}
//Update previous state of button A0
prevButton0 = currentButton0;
//Read current state of button A1
bool currentButton1 = mcp.digitalRead(1);
//If button is pressed and was not pressed before, toggle LED on B1
if (currentButton1 == LOW && prevButton1 == HIGH) {
bool ledState = mcp.digitalRead(9);
mcp.digitalWrite(9, !ledState);
}
//Update previous state of button A1
prevButton1 = currentButton1;
//Delay to debounce buttons
delay(100);
}
Code language: PHP (php)
Code Explanation
The first two lines of the code include the Wire and MCP23017 libraries. The Wire library enables I2C communication, while the MCP23017 library provides simple commands for controlling the expander pins. The I2C address of the chip is defined as 0x20. An MCP23017 object named “mcp” is created using this address so that all communication can be done through it.
Here is how the physical pins of MCP23017 are mapped in Arduino code:
| MCP23017 Pins | Port | Arduino Mapping |
|---|---|---|
| GPA0–GPA7 | A | 0-7 |
| GPB0–GPB7 | B | 8-15 |
Inside the setup function, the I2C connection is started using Wire.begin(), and the MCP23017 chip is initialized with mcp.init(). The code then sets pins A0 and A1 of the MCP23017 as input pins with internal pull-up resistors. These resistors keep the input HIGH when the button is not pressed and make it LOW when pressed. Pins B0 and B1 are set as output pins, which are connected to LEDs. Initially, both LEDs are turned off by writing a LOW signal to those pins.
In the loop section, the program continuously checks the state of each button. When button A0 is pressed, its state changes from HIGH to LOW. The code compares the current state with the previous state stored in a variable. If the button was not pressed before, the LED connected to B0 toggles its state i.e. it turns ON if it was OFF, and vice versa.
After that, the previous button state is updated so the LED does not keep changing while the button is held down. The same process is repeated for button A1 and LED B1. Finally, a short delay of 100 milliseconds is added to remove noise from the button signal, a common issue known as “bouncing.”
Testing
After the code is uploaded and the Arduino boots up, both LEDs connected to pins B0 and B1 of the MCP23017 must be initially off. When you press the button connected to pin A0, the LED connected to pin B0 should turn on. Pressing the same button again will toggle the LED off. Similarly, when you press the button connected to pin A1, the LED on pin B1 should light up.
Each subsequent press of those buttons will toggle the LEDs’ state, turning them on or off alternately.

Troubleshooting
If your project does not work after following all the steps, check all your connections
- Check your jumper wires for continuity using a multimeter.
- The polarity of both LEDs must be right for them to light up.
- Ensure the pins of the MCP23017 IC are properly set on the breadboard.
The I2C address is 0x20 when all address pins of MCP23017 are grounded, but it will be different otherwise. You can scan the I2C address by uploading the I2C scanner code available under File>Examples>Wire>i2c_scanner in Arduino IDE or by uploading the following code:
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(9600);
while (!Serial); // Leonardo: wait for Serial Monitor
Serial.println("\nI2C Scanner");
}
void loop() {
int nDevices = 0;
Serial.println("Scanning...");
for (byte address = 1; address < 127; ++address) {
// The i2c_scanner uses the return value of
// the Wire.endTransmission to see if
// a device did acknowledge to the address.
Wire.beginTransmission(address);
byte error = Wire.endTransmission();
if (error == 0) {
Serial.print("I2C device found at address 0x");
if (address < 16) {
Serial.print("0");
}
Serial.print(address, HEX);
Serial.println(" !");
++nDevices;
} else if (error == 4) {
Serial.print("Unknown error at address 0x");
if (address < 16) {
Serial.print("0");
}
Serial.println(address, HEX);
}
}
if (nDevices == 0) {
Serial.println("No I2C devices found\n");
} else {
Serial.println("done\n");
}
delay(5000); // Wait 5 seconds for next scan
}Code language: PHP (php)
Set the baud rate to 9600 in the Arduino IDE’s Serial Monitor to view the I2C address.
Final Thoughts
The GitHub library linked above also has examples for the interrupt functions in MCP23017 which you can try out.
Interfacing the MCP23017 with an Arduino is a practical way to expand available I/O pins. It is suitable for projects involving keypads, LEDs, sensors, or relay banks, where efficient pin management and scalability are important.
I hope you found this guide useful. Please leave your queries or feedback in the comments below.
Leave a Reply