The MCP23017 is an I/O expander chip. It has 16 GPIO pins which you can control using an I2C interface using two pins from a Raspberry Pi, plus a power source and sink (which can also come from the Pi). It's not quite as simple as directly controlling the Pi's GPIO pins, but it's not complicated, either.
You need to install
i2c-tools, which is probably in your distribution's package manager. You also need a kernel with I2C support; you might need to
modprobe i2c-dev. It would presumably be possible to do without either of these things, and bitbang the I2C protocol over GPIO, but I don't understand the protocol well enough to try.
On pin numbering: if you like, you can refer to the datasheet for the MCP23017. There's a small dot in one corner of the chip, with a semi-circular cut-out at that end. The pin nearest the dot is pin 1, with pins 2, 3, ..., 14 along that long side of the chip. On the other side, pins 15 through 28 go in the other direction, so that pin 15 is opposite pin 14 and pin 28 is opposite pin 1.
On the Pi, we'll be using pins 3 (SDA) and 5 (SCL) to talk to the MCP, and pins 1 (3v3) and 6 (ground) as power source and sink.
We'll start by connecting the chip. Connect pins 9 and 18 to 3v3, and pin 10 to ground. For now, connect pins 15 through 17 to ground as well. (They configure the I2C address of the chip, which I'll talk about later.) Now to be able to talk to the chip, connect pin 12 to the Pi's pin 5 (SCL) and pin 13 to the Pi's pin 3 (SDA). Here's my first ever attempt at a circuit diagram showing it, although I haven't included the Pi:
At this point, run
sudo i2cdetect -y 0 and you should get the following output:
0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --
This says there's an I2C device with address
0x20 that you can talk to. (From now on I won't bother telling you to use
-y says "don't ask me for confirmation", and
0 is the I2C bus to use. Bus 1 also exists, but it won't detect anything. I'm not sure what it corresponds to.)
(Update: in the comments, Tiersten informs me that bus 1 is on the camera CSI connector, which is the thing in between the ethernet and HDMI ports.)
To actually talk to it, use the programs
i2cdump for reading, and
i2cset for writing. The chip has 21 registers at 22 addresses (one register has two addresses), and reading and writing these allows you to read and write to the 16 GPIO pins. (The register addresses have nothing to do with the device address.)
Let's start by activating an LED on pin 1. The 16 GPIO pins are divided into banks 'A' and 'B' of eight pins each, and somewhat counterintuitively, pin 1 is
GPB0 (pin 0 in bank B). We need to set it up for output; the register we need is
IODIRB at address
0x01, which controls the direction of each pin in bank B. (They can all be set individually, but they're all done from the same register.)
To get the current value of this register, run
i2cget -y 0 0x20 0x01. Here
0 is the bus again,
0x20 is the device address and
0x01 is the register address. It will probably print
0xff. This should be read as eight bits rather than as a single byte; the least significant bit corresponds to
GPB0, and so on. A
1 bit indicates that pin is configured for input, and a
0 indicates output, so we need to turn off bit 1. Run
i2cset -y 0 0x20 0x01 0xfe, which means "assign value
0xfe to register
0x01 of device
0x20 on bus
0". Of course, you could use
0x00 or something else instead of
0xfe, as long as the final bit is
Now to turn it on. (Connect the LED first, if you haven't already. Its
+ terminal connects to pin 1 on the MCP, its
- terminal connects to ground via a suitable resistor.) The register we use for this is
GPIOB at address
0x13. Write a 1 to bit 1 of this register and the LED should turn on:
i2cset -y 0 0x20 0x13 0x01. Write a 0 again to turn it off:
i2cset -y 0 0x20 0x13 0x00.
Now we'll read the state of a button, which we'll put on pin 28. This is
GPA7, i.e. pin 7 on bank A. So connect a button with one terminal connected to pin 28 and the other connected to ground.
The direction register for bank A is
IODIRA at address
IODIRB, it's probably already set up as
0xff, but if it doesn't already have the most significant bit set (which is true iff its value is less than
0x80), you'll need to write to it.
Now you can read the value of the button as the most significant bit from
0x12. Except that there's no power being supplied to that pin or to the button, so you'll just read
You can fix this by putting a pull-up resistor into your circuit; a 10 kΩ resistor between 3v3 and pin 28 will do the trick. But the MCP has pull-up resistors built-in, they just aren't enabled by default. To enable it for
GPA7, we use register
GPPUA at address
0x0c, and turn on the MSB:
i2cset -y 0 0x20 0x0c 0x80. (Only two of the Pi's GPIO pins have pull-up resistors, so even if you don't need the extra GPIOs, the MCP might make your circuit simpler.)
(Update: it appears that in fact all of the Pi's GPIO pins have pull-up resistors, and most have pull-down resistors as well. The
gpio program from WiringPi can enable and disable these.)
i2cget -y 0 0x20 0x12 should return
0x00 if the putton is pressed, and
0x80 if the button is released. I found that for a short time after releasing the button, I would read
0xc0, indicating that
GPA6 was also returning a 1 bit. I assume this is just due to electrical interference or something; pin 27 isn't connected to either power or ground, so its value is unreliable. (When I enabled its pull-up resistor, or connected it to ground, it read the expected value every time.)
You can read the value of every register using
i2cdump -y 0 0x20. This actually returns 256 registers; I don't know what happens if you try to write to a register that doesn't exist, but I wouldn't be surprised if it's possible to destroy the chip like that, so I'm not going to try.
You can set the address of the device to any value from
0x28 by connecting pins 15, 16 and 17 to a combination of power and ground. These pins are called A0, A1 and A2 respectively; the device address is
a is 1 if
A0 is connected to power and 0 if it's connected to ground;
c are the same for
A2. If you don't connect them, I think their values depend on things like electrical interference and can't be relied upon.
The device address seems to be important if you have multiple I2C devices on one bus. I assume the protocol here is to connect SCL and SDA to both devices in parallel, and use the device address to only talk to one of them. But until I get a second I2C device, I can't test that.
If you haven't looked at the datasheet yet, the 16 GPIO pins are pins 1 through 8 (
GPB7) and pins 21 through 28 (
Most of the registers have an A version and a B version; the address of the A version has its least significant bit 0, and the address of the B version has its LSB 1. So
IODIRB are at
GPPUB are at
0x0d; etc. The exception is the register
IOCON, which can be considered shared between the two pin banks; it has addresses
The registers that I understand are (register address given for bank A):
0x00): set pins in the given bank to be either input or output pins.
0x02): invert polarity of input pins. In the example above,
i2cset -y 0 0x20 0x02 0x80would cause reads of
1when the button is pressed and
0when it's not. Has no effect when reading output pins.
0x0c): enable or disable pull-up resistors.
0x12): read and write the values of pins. If not all pins in a bank have the same direction, you'll sometimes be reading an output pin or writing an input pin. If you read an output pin, you'll be told its current value. If you write an input pin, you'll set the value it takes when it next becomes an output pin.
0x14): for an output pin, reading and writing this will have the same effect as
GPIOx. For an input pin, its value is the value that the pin will take if it becomes set to output. Writing to
GPIOxactually modifies this register. ("OLAT" means "output latch".)
The other registers are
0x10). Mostly they seem related to interrupt pins 19 (
INTB) and 20 (
INTA), but I haven't looked closely into that.
IOCON does expose one interesting feature: its most significant bit is called
BANK. If you set
BANK to 1 (i.e.
i2cset -y 0 0x20 0x0a 0x80) it gives almost every register a different address. (Only
IODIRA stays the same.) The new address is the old one, but with the last five bits rotated one to the right; so a register's bank is now given by its
0x10 bit instead of its
0x01 bit. I'm not sure why this is considered useful; maybe for compatibility with other chips? I don't think you can always tell just by looking which mode the chip is in, because
IOCON also moves; but I doubt that's a significant problem in the real world.