Skip to content

MCP23017 gpio i2c extension

Connect to RPi

Pinout

alt text

alt text

Wiring

MCP23017 RPi note
VCC 1 3.3v
GND 6
SDA 3
SCL 5
RESET 1 3.3v

i2c

install
sudo apt install i2c-tools

detect

1
2
3
4
5
6
7
8
9
sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 

Demo

Connect LED to port PA0

1
2
3
4
5
6
# set all portA pin as output
sudo i2cset -y 1 0x20 0x00 0x00 

# set gister 0x0c
sudo i2cset -y 1 0x20 0x14 0x01
sudo i2cset -y 1 0x20 0x14 0x00

Registers

The MCP23017 supports two register addressing modes:

1
2
Bank 0 (Sequential Addressing - Default Mode)
Bank 1 (Split Addressing Mode)

alt text

demo

The demo above use register

  • register 0x00(IODIRA) as set portA all pin to output
  • register 0x14(OLATA) set as 0x00/0x01 to set port PA0 low/high

Datasheet

3.5.1 I/O DIRECTION REGISTER

Controls the direction of the data I/O. When a bit is set, the corresponding pin becomes an input. When a bit is clear, the corresponding pin becomes an output.

alt text

3.5.11 OUTPUT LATCH REGISTER (OLAT)

The OLAT register provides access to the output latches. A read from this register results in a read of the OLAT and not the port itself. A write to this register modifies the output latches that modifies the pins configured as outputs.

alt text

register address

The data sheet use bank1 address mode The address is 0x14 for OLATA register in bank0 mode


Permission

Check file owner and add you'r user to file group

sudo usermod -aG i2c $USER

CPP Demo

Toggle PA0 on/off

#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <chrono>
#include <thread>
#include <cstdint>  // For fixed-width integers

int main() {
    int file;
    const char *i2cBus = "/dev/i2c-1"; // I2C bus
    const int deviceAddress = 0x20;    // MCP23017 I2C address

    // Open the I2C bus
    file = open(i2cBus, O_RDWR);
    if (file < 0) {
        std::cerr << "Failed to open I2C bus\n";
        return -1;
    }

    // Connect to the MCP23017 device
    if (ioctl(file, I2C_SLAVE, deviceAddress) < 0) {
        std::cerr << "Failed to connect to I2C device\n";
        close(file);
        return -1;
    }

    // Set IODIRA register (0x00) to 0x00 (all output)
    uint8_t config[2] = {0x00, 0x00};
    if (write(file, config, 2) != 2) {
        std::cerr << "Failed to write to I2C device\n";
    }

    // Toggle PA0 port using GPIOA (0x14)
    for (int i=0; i<10; i++)
    {
        uint8_t state = i%2;
        uint8_t output[2] = {0x14, state};
        if (write(file, output, 2) != 2) {
            std::cerr << "Failed to write GPIOA\n";
        }
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    // // Read GPIOA (0x12)
    // uint8_t reg = 0x12;
    // if (write(file, &reg, 1) != 1) {
    //     std::cerr << "Failed to set register address\n";
    // }

    // uint8_t data;
    // if (read(file, &data, 1) != 1) {
    //     std::cerr << "Failed to read from I2C device\n";
    // } else {
    //     std::cout << "GPIOA Data: 0x" << std::hex << (int)data << std::dec << std::endl;
    // }

    close(file);
    return 0;
}

Python Demo

pip3 install smbus2

Toggle PA0 on/off

from smbus2 import SMBus
import time

I2C_BUS = 1         # Raspberry Pi uses I2C bus 1
I2C_ADDR = 0x20     # MCP23017 I2C address

# MCP23017 Register Addresses
IODIRA = 0x00       # I/O direction register for GPIOA
GPIOA = 0x12        # Register to read/write GPIOA
OLATA = 0x14        # Output latch register for GPIOA

with SMBus(I2C_BUS) as bus:
    # Set all GPIOA pins as output (0x00 means all pins are outputs)
    bus.write_byte_data(I2C_ADDR, IODIRA, 0x00)

    while True:
        # Turn PA0 ON (set bit 0 to 1)
        bus.write_byte_data(I2C_ADDR, OLATA, 0x01)
        print("PA0 ON")
        time.sleep(1)

        # Turn PA0 OFF (set bit 0 to 0)
        bus.write_byte_data(I2C_ADDR, OLATA, 0x00)
        print("PA0 OFF")
        time.sleep(1)

Reference