Contact Sales & After-Sales Service

Contact & Quotation

  • Inquire: Call 0086-755-23203480, or reach out via the form below/your sales contact to discuss our design, manufacturing, and assembly capabilities.
  • Quote: Email your PCB files to Sales@pcbsync.com (Preferred for large files) or submit online. We will contact you promptly. Please ensure your email is correct.
Drag & Drop Files, Choose Files to Upload You can upload up to 3 files.

Notes:
For PCB fabrication, we require PCB design file in Gerber RS-274X format (most preferred), *.PCB/DDB (Protel, inform your program version) format or *.BRD (Eagle) format. For PCB assembly, we require PCB design file in above mentioned format, drilling file and BOM. Click to download BOM template To avoid file missing, please include all files into one folder and compress it into .zip or .rar format.

MCP23017 Arduino: I2C GPIO Expander Tutorial

After designing control panels requiring dozens of buttons, LEDs, and switches, I’ve learned that running out of GPIO pins on <a href=”https://pcbsync.com/arduino/”>Arduino</a> happens faster than most makers expect. The Arduino Uno provides only 20 digital I/O pins—after allocating pins for serial communication, I2C, SPI, and basic project requirements, you’re left with precious few for sensors, displays, and controls. The MCP23017 Arduino combination solves this limitation elegantly, adding 16 additional GPIO pins while consuming only two I2C pins for communication. Even better, the clever I2C addressing scheme enables connecting up to eight MCP23017 chips on a single bus, potentially expanding to 128 GPIO pins total.

The maker community often tries complex workarounds when facing GPIO shortages—multiplexing LEDs, using shift registers, or upgrading to expensive Mega boards. These approaches add complexity and cost while the MCP23017 Arduino integration provides straightforward expansion at minimal expense. Understanding proper implementation transforms GPIO limitations from project roadblocks into non-issues. Whether building LED matrix displays, keypad matrices, relay control panels, or sensor networks, mastering MCP23017 usage enables professional-grade projects without GPIO constraints.

From understanding I2C addressing and pin configuration to implementing interrupt-driven input monitoring, from controlling LED arrays to reading button matrices, this comprehensive tutorial covers everything needed to leverage the MCP23017’s capabilities effectively in real-world Arduino projects.

Understanding the MCP23017 GPIO Expander

Before wiring circuits and writing code, understanding the MCP23017’s capabilities and limitations ensures choosing it for appropriate applications.

MCP23017 Key Specifications

SpecificationValuePractical Impact
GPIO Pins16 (two 8-bit ports)Double Uno’s digital pins
InterfaceI2C (2-wire)Uses only SCL/SDA
Operating Voltage1.8V to 5.5VWorks with 3.3V and 5V systems
Output Current25mA per pinDrives LEDs directly
Input Pull-upsConfigurable 100kΩSimplifies button interfacing
Clock SpeedUp to 1.7MHz (I2C)Fast response time
I2C Addresses8 configurableChain up to 8 modules
Package OptionsDIP-28, SOIC-28, SSOP-28DIP for breadboarding

Port Structure: The MCP23017 organizes pins into two 8-bit ports labeled PORTA (GPA0-GPA7) and PORTB (GPB0-GPB7). Each port can be configured independently, enabling flexible applications like using PORTA for outputs (LEDs) while PORTB handles inputs (buttons).

Current Capabilities: Each pin sources or sinks 25mA maximum, sufficient for standard LEDs with current-limiting resistors. The entire chip has a combined maximum current rating of 125mA, preventing powering 16 LEDs at 25mA simultaneously—design accordingly.

When to Use MCP23017 vs Alternatives

Choose MCP23017 When:

  • Need 8-16 additional GPIO pins
  • Project already uses I2C (doesn’t consume additional pins)
  • Require bidirectional pins (input and output capability)
  • Want built-in pull-up resistors for buttons
  • Need interrupt capability for efficient input monitoring

Consider Alternatives When:

  • Require only outputs: 74HC595 shift registers cost less and chain easily
  • Need 32+ pins: PCF8575 provides 16 pins, MCP23S17 (SPI version) offers higher speed
  • Require analog inputs: CD4051 analog multiplexer or additional ADCs
  • Speed critical: Direct Arduino pins always faster than I2C communication

MCP23017 Pinout and I2C Addressing

Understanding physical connections and addressing prevents common wiring mistakes and enables multi-chip configurations.

MCP23017 Pin Configuration

The MCP23017 comes in a 28-pin DIP package (breadboard-friendly) with logical pin grouping:

Pin Number(s)Pin NameFunction
21-28GPA0-GPA7Port A GPIO pins
1-8GPB0-GPB7Port B GPIO pins
9VDDPower supply (1.8-5.5V)
10VSSGround
12SCLI2C Clock
13SDAI2C Data
18RESETActive-low reset (connect to VDD via 10kΩ)
15-17A0, A1, A2Address selection pins
19, 20INTA, INTBInterrupt outputs (optional)
11NCNot connected

Power Connections: VDD (pin 9) connects to 5V or 3.3V depending on your Arduino logic level. VSS (pin 10) connects to ground. Add 0.1µF ceramic capacitor between VDD and VSS close to chip for decoupling.

Reset Pin: Pin 18 must be HIGH for normal operation. Connect directly to VDD or through 10kΩ pull-up resistor. Pulling LOW resets the chip to default state.

I2C Address Configuration

The MCP23017’s base I2C address is 0x20 (binary: 0100 A2 A1 A0). The three address pins (A0, A1, A2) modify the final three bits:

A2A1A0I2C AddressHex Value
000320x20
001330x21
010340x22
011350x23
100360x24
101370x25
110380x26
111390x27

Address Selection: Connect address pins to GND for logic LOW or VDD for logic HIGH. The default configuration (all three grounded) results in address 0x20—the most common setup for single-chip applications.

Multiple MCP23017 Setup: When using multiple chips, assign unique addresses to each. Connect the second chip’s A0 to VDD (address 0x21), third chip’s A1 to VDD (address 0x22), etc. This enables controlling 128 total GPIO pins (8 chips × 16 pins) on one I2C bus.

Wiring MCP23017 to Arduino

Proper physical connections ensure reliable I2C communication and correct GPIO operation.

Basic Single MCP23017 Connection

Arduino Uno to MCP23017 Wiring:

Arduino 5V → MCP23017 Pin 9 (VDD)

Arduino GND → MCP23017 Pin 10 (VSS)

Arduino A4 (SDA) → MCP23017 Pin 13 (SDA)

Arduino A5 (SCL) → MCP23017 Pin 12 (SCL)

MCP23017 Pin 18 (RESET) → MCP23017 Pin 9 (VDD) via 10kΩ resistor

MCP23017 Pins 15,16,17 (A0,A1,A2) → GND (address 0x20)

I2C Pull-up Resistors: The I2C bus requires pull-up resistors (typically 4.7kΩ or 10kΩ) on both SDA and SCL lines. Many Arduino boards include these pull-ups internally, but adding external 4.7kΩ resistors from SDA/SCL to 5V improves reliability, especially with long wires or multiple I2C devices.

Decoupling Capacitor: Place 0.1µF ceramic capacitor between VDD (pin 9) and VSS (pin 10) as close to the chip as possible. This filters power supply noise preventing erratic behavior.

Connecting Outputs (LEDs)

LED Connection to MCP23017 Pin:

MCP23017 GPIO pin → 220Ω resistor → LED anode (+)

LED cathode (-) → GND

The 220Ω resistor limits current to safe levels (~20mA with 5V supply). Calculate appropriate resistor value: R = (VDD – LED_forward_voltage) / desired_current

Multiple LED Connections: Connect LEDs to as many GPIO pins as needed, remembering the combined current limitation (125mA total across all pins). Eight LEDs at 15mA each consume 120mA—approaching the limit.

Connecting Inputs (Buttons)

Button with Internal Pull-up:

Button terminal 1 → MCP23017 GPIO pin

Button terminal 2 → GND

Enable internal pull-up in software

The MCP23017’s configurable 100kΩ internal pull-ups eliminate external resistor requirements. When button presses, pin reads LOW; when released, internal pull-up holds pin HIGH.

External Pull-up (if preferred):

10kΩ resistor → 5V to MCP23017 GPIO pin

Button between GPIO pin and GND

External pull-ups provide stronger signal but require additional components. Use when signal integrity concerns exist or faster response needed.

Installing and Using MCP23017 Library

Software integration requires proper library installation and understanding basic functions.

Adafruit MCP23X17 Library Installation

Library Manager Method (recommended):

  1. Open Arduino IDE → Sketch → Include Library → Manage Libraries
  2. Search “Adafruit MCP23017”
  3. Install “Adafruit MCP23X17” library
  4. Library manager installs dependencies automatically

Manual Installation:

  1. Download from GitHub: https://github.com/adafruit/Adafruit-MCP23017-Arduino-Library
  2. Extract ZIP to Arduino/libraries folder
  3. Restart Arduino IDE

Basic LED Blink Example

#include <Adafruit_MCP23X17.h>

Adafruit_MCP23X17 mcp;

void setup() {

  Serial.begin(9600);

  // Initialize MCP23017 at default address 0x20

  if (!mcp.begin_I2C()) {

    Serial.println(“MCP23017 not found!”);

    while(1);

  }

  // Configure pin 0 as output

  mcp.pinMode(0, OUTPUT);

  Serial.println(“MCP23017 initialized”);

}

void loop() {

  mcp.digitalWrite(0, HIGH);  // LED on

  delay(500);

  mcp.digitalWrite(0, LOW);   // LED off

  delay(500);

}

This basic example demonstrates core functions: initialization, pin configuration, and digital output control. The syntax mirrors standard Arduino functions making transition seamless.

Reading Button Input

#include <Adafruit_MCP23X17.h>

Adafruit_MCP23X17 mcp;

void setup() {

  Serial.begin(9600);

  if (!mcp.begin_I2C()) {

    Serial.println(“Error initializing MCP23017!”);

    while(1);

  }

  // Configure pin 8 as input with pull-up

  mcp.pinMode(8, INPUT_PULLUP);

  Serial.println(“Ready”);

}

void loop() {

  // Read button state (LOW when pressed)

  if (mcp.digitalRead(8) == LOW) {

    Serial.println(“Button pressed!”);

    delay(200);  // Simple debounce

  }

}

The INPUT_PULLUP mode enables MCP23017’s internal 100kΩ pull-up resistor, simplifying button interfacing without external components.

Advanced MCP23017 Features and Applications

Beyond basic I/O, the MCP23017 offers features enabling sophisticated applications.

Controlling Multiple Devices (LED Array)

#include <Adafruit_MCP23X17.h>

Adafruit_MCP23X17 mcp;

void setup() {

  Serial.begin(9600);

  mcp.begin_I2C();

  // Configure all PORTA pins (0-7) as outputs

  for(int pin = 0; pin < 8; pin++) {

    mcp.pinMode(pin, OUTPUT);

  }

}

void loop() {

  // Running light effect

  for(int pin = 0; pin < 8; pin++) {

    mcp.digitalWrite(pin, HIGH);

    delay(100);

    mcp.digitalWrite(pin, LOW);

  }

}

This example demonstrates sequential LED control creating a “running light” effect across eight LEDs connected to PORTA.

Port-Wide Operations

Writing to pins individually works but inefficient when controlling multiple pins simultaneously. The MCP23017 supports port-wide operations:

void setup() {

  mcp.begin_I2C();

  // Configure entire PORTA as outputs using register write

  mcp.writeGPIOAB(0x00FF);  // PORTA outputs (0x00), PORTB outputs (0xFF)

}

void loop() {

  // Turn on all PORTA LEDs simultaneously

  mcp.writeGPIOA(0xFF);

  delay(500);

  // Turn off all PORTA LEDs

  mcp.writeGPIOA(0x00);

  delay(500);

}

Port-wide operations execute single I2C transaction controlling 8 pins versus eight separate transactions—significantly faster for synchronized outputs.

Using Multiple MCP23017 Chips

#include <Adafruit_MCP23X17.h>

Adafruit_MCP23X17 mcp1;  // Address 0x20

Adafruit_MCP23X17 mcp2;  // Address 0x21

void setup() {

  Serial.begin(9600);

  // Initialize first chip at default address

  if (!mcp1.begin_I2C(0x20)) {

    Serial.println(“MCP1 initialization failed!”);

  }

  // Initialize second chip at address 0x21

  if (!mcp2.begin_I2C(0x21)) {

    Serial.println(“MCP2 initialization failed!”);

  }

  // Configure pins on both chips

  mcp1.pinMode(0, OUTPUT);

  mcp2.pinMode(0, OUTPUT);

}

void loop() {

  // Control LEDs on both chips

  mcp1.digitalWrite(0, HIGH);

  mcp2.digitalWrite(0, LOW);

  delay(500);

  mcp1.digitalWrite(0, LOW);

  mcp2.digitalWrite(0, HIGH);

  delay(500);

}

Each MCP23017 object manages one chip at its unique I2C address, enabling independent control of 32 total GPIO pins (16 per chip).

Interrupt-Driven Input Monitoring

The MCP23017 includes interrupt capability notifying Arduino when input pins change state—far more efficient than constantly polling inputs:

#include <Adafruit_MCP23X17.h>

Adafruit_MCP23X17 mcp;

const int INTERRUPT_PIN = 2;  // Arduino interrupt pin

volatile bool interruptFlag = false;

void handleInterrupt() {

  interruptFlag = true;

}

void setup() {

  Serial.begin(9600);

  mcp.begin_I2C();

  // Configure pin 0 as input with pull-up

  mcp.pinMode(0, INPUT_PULLUP);

  // Enable interrupt on change for pin 0

  mcp.setupInterrupts(true, false, LOW);

  mcp.setupInterruptPin(0, CHANGE);

  // Attach Arduino interrupt handler

  pinMode(INTERRUPT_PIN, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), handleInterrupt, FALLING);

}

void loop() {

  if (interruptFlag) {

    interruptFlag = false;

    // Read which pin caused interrupt

    uint16_t val = mcp.getCapturedInterrupt();

    Serial.print(“Interrupt on pin: “);

    Serial.println(val, BIN);

    // Clear interrupt

    mcp.clearInterrupts();

  }

}

Interrupt mode dramatically reduces I2C traffic by eliminating constant polling. The MCP23017 only interrupts Arduino when input state changes.

Practical Application: 4×4 Keypad Matrix

Matrix keypads efficiently read multiple buttons using fewer pins through row-column scanning:

#include <Adafruit_MCP23X17.h>

Adafruit_MCP23X17 mcp;

// Keypad configuration

const int ROWS = 4;

const int COLS = 4;

// Map MCP23017 pins

int rowPins[ROWS] = {0, 1, 2, 3};     // PORTA

int colPins[COLS] = {8, 9, 10, 11};   // PORTB

char keys[ROWS][COLS] = {

  {‘1’, ‘2’, ‘3’, ‘A’},

  {‘4’, ‘5’, ‘6’, ‘B’},

  {‘7’, ‘8’, ‘9’, ‘C’},

  {‘*’, ‘0’, ‘#’, ‘D’}

};

void setup() {

  Serial.begin(9600);

  mcp.begin_I2C();

  // Configure rows as outputs, columns as inputs with pull-ups

  for(int i = 0; i < ROWS; i++) {

    mcp.pinMode(rowPins[i], OUTPUT);

    mcp.digitalWrite(rowPins[i], HIGH);

  }

  for(int i = 0; i < COLS; i++) {

    mcp.pinMode(colPins[i], INPUT_PULLUP);

  }

}

char getKey() {

  for(int row = 0; row < ROWS; row++) {

    // Set current row LOW

    mcp.digitalWrite(rowPins[row], LOW);

    // Check each column

    for(int col = 0; col < COLS; col++) {

      if(mcp.digitalRead(colPins[col]) == LOW) {

        // Key pressed, return it

        char key = keys[row][col];

        // Wait for release

        while(mcp.digitalRead(colPins[col]) == LOW);

        // Restore row HIGH

        mcp.digitalWrite(rowPins[row], HIGH);

        return key;

      }

    }

    // Restore row HIGH

    mcp.digitalWrite(rowPins[row], HIGH);

  }

  return 0;  // No key pressed

}

void loop() {

  char key = getKey();

  if(key) {

    Serial.print(“Key pressed: “);

    Serial.println(key);

  }

  delay(100);

}

This keypad reader scans 16 buttons using only 8 GPIO pins through matrix scanning—demonstrating GPIO expander efficiency for input-heavy applications.

Useful Resources for MCP23017 Development

Official Documentation

MCP23017 Datasheet: https://ww1.microchip.com/downloads/en/devicedoc/20001952c.pdf
Complete technical reference from Microchip including register descriptions and electrical specifications.

Adafruit MCP23017 Guide: https://learn.adafruit.com/adafruit-mcp23017-i2c-gpio-expander
Comprehensive tutorial with wiring diagrams, library usage, and example projects.

Adafruit MCP23X17 Library: https://github.com/adafruit/Adafruit-MCP23017-Arduino-Library
Official Arduino library source code with examples and documentation.

Hardware Suppliers

Adafruit MCP23017 Breakout: https://www.adafruit.com/product/732
Quality breakout board with headers and STEMMA QT connectors.

SparkFun Qwiic GPIO: https://www.sparkfun.com/products/17047
Alternative breakout with Qwiic connectors for solderless connections.

Alternative Libraries and Tools

MCP23017 Arduino Library by Bertrand Lemasle: https://github.com/blemasle/arduino-mcp23017
Alternative library with different feature set and API.

I2C Scanner Sketch: https://playground.arduino.cc/Main/I2cScanner/
Useful tool for detecting I2C device addresses on your bus.

Frequently Asked Questions

Q: Why doesn’t my MCP23017 respond when I try to communicate?

A: Check these common issues: (1) Verify I2C wiring—SDA to A4, SCL to A5 on Arduino Uno (different pins on Mega: SDA pin 20, SCL pin 21), (2) Confirm address pins (A0, A1, A2) match your code—all grounded = 0x20, (3) Check RESET pin—must be HIGH (connected to VDD), (4) Measure voltage at VDD pin—should match Arduino supply (5V or 3.3V), (5) Run I2C scanner sketch to detect device presence and confirm address. If scanner doesn’t find device, hardware connection problem exists. Add or strengthen I2C pull-up resistors (4.7kΩ) if wires exceed 6 inches.

Q: Can I mix 3.3V and 5V devices on the same I2C bus?

A: Yes, with precautions. The MCP23017 operates from 1.8V to 5.5V, so power it from whichever voltage your system uses. For I2C communication, the bus voltage determines logic levels. If mixing 3.3V and 5V devices, power MCP23017 from 3.3V and ensure I2C pull-ups connect to 3.3V (not 5V). Most 5V Arduino tolerates 3.3V I2C levels adequately. For guaranteed compatibility, use level shifters between 3.3V and 5V I2C segments. GPIO pins should only connect to circuits matching MCP23017’s supply voltage.

Q: How fast can I read/write MCP23017 GPIO pins?

A: Speed depends on I2C clock frequency (default 100kHz). Each I2C transaction (reading or writing single pin) requires ~0.5-1ms including communication overhead. Reading 16 pins individually takes ~8-16ms versus ~1ms reading entire ports simultaneously via readGPIOAB(). For high-speed applications (>1kHz sampling), direct Arduino pins outperform I2C expanders. MCP23017 excels for moderate-speed applications (buttons, LEDs, relays) where wiring simplicity matters more than maximum speed. The MCP23017’s 1.7MHz I2C capability enables faster operation if your Arduino supports it, but improvement remains modest compared to direct GPIO.

Q: How many LEDs can I control from one MCP23017?

A: Theoretically 16 (one per GPIO pin), but practical limit is ~5-6 at full brightness. Each pin sources/sinks 25mA maximum, but the entire chip’s combined current must not exceed 125mA. Six LEDs at 20mA each = 120mA, approaching the limit. For controlling more LEDs, reduce brightness (higher current-limiting resistors) or use external drivers (transistors, MOSFET arrays, LED driver chips). For matrix displays, use LED driver chips (MAX7219, TLC5940) designed for that purpose. The MCP23017 works well for indicator LEDs and control panel status lights but isn’t optimized for high-density LED arrays.

Q: Do I need pull-up resistors on I2C lines if Arduino has internal ones?

A: Depends on bus conditions. Arduino’s internal pull-ups (~50kΩ) work adequately for short connections (under 6 inches) with 1-2 devices. For better reliability, especially with long wires, multiple devices, or high-speed communication, add external 4.7kΩ resistors from SDA and SCL to VDD. External pull-ups provide stronger signal rise times improving noise immunity. If experiencing intermittent communication errors, glitches, or unreliable detection, external pull-ups often solve these issues. In professional designs, always include external pull-ups rather than relying on internal ones.

Conclusion: Unlimited GPIO Within Reach

Mastering MCP23017 Arduino integration removes GPIO limitations that otherwise constrain project complexity. The ability to add 16 bidirectional pins while consuming only two I2C connections transforms what’s possible with standard Arduino boards. Whether building control panels with dozens of buttons and indicators, interfacing relay boards for home automation, reading keypad matrices, or controlling LED displays, the MCP23017 provides professional-grade GPIO expansion at minimal cost and complexity.

From my perspective designing commercial control systems, GPIO expanders aren’t luxury add-ons—they’re fundamental building blocks enabling practical product design without upgrading to expensive microcontrollers. The MCP23017’s mature ecosystem, extensive library support, and straightforward I2C interface make it accessible to beginners while providing features professionals depend upon for reliable operation.

Whether expanding a single Arduino Uno to handle 32 GPIO points or building systems with 128 I/O using eight daisy-chained MCP23017 chips, understanding proper implementation enables projects limited only by imagination rather than GPIO availability. The difference between abandoning ambitious projects due to pin shortages and confidently building full-featured systems comes down to mastering GPIO expansion techniques like the MCP23017 offers.

Leave a Reply

Your email address will not be published. Required fields are marked *

Contact Sales & After-Sales Service

Contact & Quotation

  • Inquire: Call 0086-755-23203480, or reach out via the form below/your sales contact to discuss our design, manufacturing, and assembly capabilities.

  • Quote: Email your PCB files to Sales@pcbsync.com (Preferred for large files) or submit online. We will contact you promptly. Please ensure your email is correct.

Drag & Drop Files, Choose Files to Upload You can upload up to 3 files.

Notes:
For PCB fabrication, we require PCB design file in Gerber RS-274X format (most preferred), *.PCB/DDB (Protel, inform your program version) format or *.BRD (Eagle) format. For PCB assembly, we require PCB design file in above mentioned format, drilling file and BOM. Click to download BOM template To avoid file missing, please include all files into one folder and compress it into .zip or .rar format.