So on a project Ive been working on, I realized that I was connecting analog circuits and an LCD screen, buttons LED’s etc to an Arduino uno. What i quickly realized is that I was rapidly running out of IO pins.
After some searching I came across THIS beauty from microchip. The MCP23017, for about $1.50 or less you can get this chip which will add 2 banks of 8 IO pins!! HOLY SMOKES!! you say? well you would be right.
This IC is easy to implement.
for this circuit I wired in 4 buttons in bank A and 4 LEDs on bank B. the LEDs are outputs so when the MCP23017 turns them on you will get 5V at each LED, so drop them across a resistor to ground and bingo, for the buttons the opposite is true. attach the buttons to +5V and when they are pressed you will get a high reading on that pin.
the other wires are Vdd to +5V, Vss to ground, RESET to +5V, SCL and SDA to the corresponding arduino pins (A4 and A5) and thats it right? Not quite. A0, A1, and A2 are your address pins. Since this chip uses the I2C protocol, all other I2C devices will be on the same line, therefore the chip needs to be addressed appropriately. pins A0, A1, and A2 are your address pins.
don’t forget to use pullup resistors for the I2C lines since they are active low.
Addressing the MCP23017
so the address for the IC is based on a strange 7 bit register.
this means if you connect A2, A1, A0 to ground you get the slave address:
0100000 in the register which correlates to a hex value of 0x20. you can change these pins to whatever you like. for example if you wire A0 to +5v and leave the rest at ground you would have 0100001 and your address would be 0x21 and so on. use whatever combination works best.
Programming the IC
to use the I2C protocol within the arduino library you will need to use the wire() functions, so don’t forget to
in your sketch header. Then under void Setup() you have to set up each bank.
this is not nearly as complex as it looks. first you need to let the arduino know that you intend to use the wire connection so add “Wire.begin();” then to communicate with the device you have to run the beginTransmission function. so remember the address pins? all we have to do is tell the code to begin transmission to the device at address 0x20 (or whatever we calculated).
now you’re in the MCP23017 and the subsequent command will tell the chip what register we want to address. 0x0C and 0x0D will connect us to the internal pullup resistors, and different codes will take us to the various internal registers that let us do all kinds of fancy stuff to the chip like changing the active high, active low settings, the polarity of pins etc… Table 1-2 in the data sheet spells out what is addressed. For now all we really need access to are the IODIRA and IODIRB banks. These correspond to the Bank A and Bank B (GPA and GPB) pins but only set the direction. to actually READ or WRITE to GPA and GPB we need another address..
Since we wired our LEDs to bank B we need to set IODIRB to output. to do this we write the address to connect with the IODIRB bank.
since 0x01 is the IODIRB bank. then we select which pins we want selected as input and output and using the binary code (1=input and 0=output) we create an 8-bit word which will tell the register which pins to make outputs and which to make inputs in bank B.
if we wanted half input and half output we would write 1111 0000 or 0xF0 (or 0xF if we wanted 0000 1111) if you wanted every other pin input/output like”: 1010 1010 you would write 0xAA
just imagine all 8 pins are binary digits in an 8bit string.. whatever combination you want just write that hex code next. in our case we want all of bank B to be output so we write 0000 0000 or 0x00
the we close the connection and write Wire.endTransmission();
for bank A set to input we do the same thing;
Wire.write(0x00); this time to connect to the direction register IODIRA
Wire.write(0xFF); since 0xFF is equal to 1111 1111 or ‘all input’
Wire.endTransmission(); close connection.
easy as pie right? next we will look at how to actually use the data coming in.
Reading data in a program
well we go back to the wire library and use the commands ‘request from’ and ‘read’ to get the data we want. so we will use the following code
byte new_val = Wire.read();
Wire.requestFrom requires the address of the MCP23017 and how many bytes you want to read. in this case we want to read the inputs for its single byte (8-bit) to give us the current state (in binary) of the buttons, where 1 = pressed and 0 = not pressed. we follow this command up with the wire.read(); which returns the byte and in this case stores it to new_val.
so now we have a variable new_val with a byte stored in it, what do we do with this? well first, before we do anything, we have to continuously poll the input registers every few miliseconds and wait for the button to be released. if not, the main loop will continue to register that as a new button press every time your loop iterates. your code may be different, but for mine I wanted to wait until the button was released before doing anything.
the easiest way to do this is to add a while loop or possibly an if loop would work that would essentially do this:
while(button != 0)
byte waitfor = Wire.read();
int button = waitfor //this converts the byte into a decimal value.
when button = 0 it’s been released and the code can continue.
now you have some value 1000 0000 or something indicated which button, buttons were pressed. to manage this i used an if statement, but you could also use a switch statement as well.
in this case i pull in all the data in decimal and send it into this function. the returned value of 1, 2, 3, 4 or 0 is then used to do something.
i find its much easier to program with a “when button 1 is clicked do blah blah” rather than “when button 32…” or “when button 01000000 is clicked” etc… but to each his own.
this is about where the tutorial should end, you can use this data to make your project work however you like, but this should get you set up and going with the MCP23017.
hope you enjoyed the tutorial. please leave comments or feedback.