MCP23017 I/O expander
Sixteen more pins over I2C, in a 28-pin DIP. Aliases: 16-bit expander, port expander, MCP23S17.
Where the PCF8574 is a latch with a weak pull-up, this is a proper pair of ports: direction, pull-ups, polarity, push-pull outputs and interrupts, each set by a register.
Pins
| Pin | What it does |
|---|---|
GPA0 to GPA7, GPB0 to GPB7 |
The two ports. |
VDD, VSS |
Supply, 1.8 to 5.5 V, and ground. |
SCL, SDA |
I2C. |
A0, A1, A2 |
The low three bits of the address, 0x20 to 0x27. |
RESET |
Active low. |
INTA, INTB |
Interrupt outputs for port A and port B. |
NC1, NC2 |
Not connected. |
Three things must be wired or the chip is not there. RESET has no pull-up and
must be tied to VDD. A0, A1 and A2 have no pull-downs and must each go to
VDD or VSS. And the bus needs 4.7 kΩ pull-ups on SDA and SCL, because this is
a bare chip. Left floating, the part does what a real one does on a bad day: nothing
at all.
The library
Adafruit_MCP23X17.h works as its tutorials use it; pins 0 to 7 are GPA0 to
GPA7 and 8 to 15 are GPB0 to GPB7:
#include <Adafruit_MCP23X17.h>
Adafruit_MCP23X17 mcp;
void setup() {
if (!mcp.begin_I2C()) { while (1); } // 0x20
mcp.pinMode(0, OUTPUT); // GPA0
mcp.pinMode(8, INPUT_PULLUP); // GPB0
}
void loop() {
mcp.digitalWrite(0, !mcp.digitalRead(8));
}
The registers
At power-up IOCON.BANK is 0 and the A and B registers alternate: IODIRA at 0x00,
IODIRB at 0x01, then IPOL, GPINTEN, DEFVAL, INTCON, IOCON, GPPU,
INTF, INTCAP, GPIO and OLAT, a pair each, to 0x15. Every pin powers up an
input. Setting BANK moves port B's registers to 0x10 to 0x1A at once, which is
how a sketch that sets it ends up writing to the wrong register with its next byte.
After each byte the register pointer steps on, so a sketch writes IODIRA and
IODIRB in one transfer. With SEQOP set it toggles between the A and B register of
a pair instead, or with BANK also set stays where it is.
What the model gets right
Both register maps and the pointer's three behaviors. Outputs from OLAT, 100 kΩ
pull-ups from GPPU, and IPOL inverting what GPIO reads. Interrupt-on-change:
compare against the last value (INTCON 0) or against DEFVAL (INTCON 1), INTF
naming the pin, INTCAP catching the port at that moment, and a read of INTCAP or
GPIO clearing it. With DEFVAL, a pin that is still different fires again at once.
INTA and INTB are push-pull active low at power-up; INTPOL, ODR and MIRROR
change that as the datasheet says.
What it does not model
SPI, which is the MCP23S17. Output current limits. The slew-rate control. Microchip's
current datasheet says GPA7 and GPB7 should be outputs only, because a change on
either during a read can corrupt SDA on real silicon; here they work as inputs like
the rest, so a circuit that relies on that works in Mokxi and may not on the bench.
Common mistakes
Leaving RESET or an address pin floating. Forgetting the bus pull-ups. Using the
old Adafruit library's begin(0), which took 0 to 7; the new one takes the address,
begin_I2C(0x20).