Tutorial: Maximising your Arduino’s I/O ports
This is chapter forty-one of a series originally titled “Getting Started/Moving Forward with Arduino!” by John Boxall – a series of articles on the Arduino universe. The first chapter is here, the complete series is detailed here. Any files from tutorials will be found here.
Welcome back fellow arduidans!
In this article we discuss how to use the Microchip MCP23017 16-bit serial expander with I2C serial interface. This 28-pin IC offers sixteen inputs or outputs – and up to eight of the ICs can be used on one I2C bus… offering a maximum of 128 extra I/O ports. A few people may be thinking “Why not just get an Arduino Mega2560?” – a good question. However you may have a distance between the Arduino and the end-point of the I/O pins – so with these ICs you can run just four wires instead of a lot more; save board space with custom designs, and preserve precious digital I/O pins for other uses. Plus I think the I2C bus is underappreciated! So let’s get started…
Here is our subject of the article in DIP form:
At this point you should also download yourself a copy of data sheet – it will be referred to several times, and very useful for reference and further reading. Furthermore if you are not familiar with Arduino and the I2C bus, please familiarise yourself with the I2C tutorials parts one and two. The MCP23017 can be quite simple or complex to understand, so the goal of this article is to try and make it as simple as possible. After reading this you should have the knowledge and confidence to move forward with using a MCP23017.
First, let’s look at the hardware basics of this IC. Consider the pinouts:
The sixteen I/O ports are separated into two ‘banks’ – A (on the right) and B (on the left. Pin 9 connects to 5V, 10 to GND, 11 isn’t used, 12 is the I2C bus clock line (Arduino Uno/Duemilanove analogue pin 5, Mega pin 21), and 13 is the I2C bus data line (Arduino Uno/Duemailnove analogue pin 4, Mega pin 20). External pull-up resistors should be used on the I2C bus – in our examples we use 4.7k ohm values. Pin 14 is unused, and we won’t be looking at interrupts, so ignore pins 19 and 20. Pin 18 is the reset pin, which is normally high – therefore you ground it to reset the IC. So connect it to 5V!
Finally we have the three hardware address pins 15~17. These are used to determine the I2C bus address for the chip. If you connect them all to GND, the address is 0×20. If you have other devices with that address or need to use multiple MCP23017s, see figure 1-2 on page eight of the data sheet. You can alter the address by connecting a combination of pins 15~17 to 5V (1) or GND (0). For example, if you connect 15~17 all to 5V, the control byte becomes 0100111 in binary, or 0×27 in hexadecimal.
Next, here is a basic schematic illustrating how to connect an MCP23017 to a typical Arduino board. It contains the minimum to use the IC, without any sensors or components on the I/O pins:
Now to examine how to use the IC in our sketches.
Remember – if you are using Arduino v1.0 or greater – you will need to change all the Wire.send commands in our examples to Wire.write, and use Wire.read() instead of Wire.receive().
As you should know by now most I2C devices have several registers that can be addressed. Each address holds one byte of data that determines various options. With the MCP23017 the registers can be ordered in one of two ways – see tables 1.3 and 1.4 on page nine of the data sheet. In our examples we will use the addresses listed on table 1.4. So the first command to use in void setup() is:
Wire.beginTransmission(0x20); Wire.send(0x12); Wire.send(0x20); // use table 1.4 addressing Wire.endTransmission();
The next is to set the I/O ports as inputs or outputs. First we will work with outputs. When the MCP23017 is turned on or reset, it defaults to inputs so we need to change them. So we use:
Wire.beginTransmission(0x20); Wire.send(0x00); // IODIRA register Wire.send(0x00); // set all of bank A to outputs Wire.send(0x00); // set all of bank B to outputs Wire.endTransmission();
Go back to the data sheet and see table 1.4. Notice how we started with the IODIRA (“I/O direction, bank A”) register at 0×00 and sent two bytes? You can do this without having to separate address the second register. This only works when the registers have sequential addresses, as in this example we wanted a byte to go to 0×00 then 0×01. We sent zero which in binary is 00000000 – each bit refers to one output of the bank and refers to I/O pins 7~0.
So now we are in void loop() or a function of your own creation and want to control some output pins. To control bank A, we use:
Wire.beginTransmission(0x20); Wire.send(0x12); // address bank A Wire.send(??); // value to send Wire.endTransmission();
… replacing ?? with the binary or equivalent hexadecimal or decimal value to send to the register. To calculate the required number, consider each I/O pin from 7 to 0 matches one bit of a binary number – 1 for on, 0 for off. So you can insert a binary number representing the output levels. Or if binary does your head in, convert it to hexadecimal. So for example, you want pins 7 and 1 on. In binary that would be 10000010, in hexadecimal that is 0×82, or 130 decimal. (Using decimals is convenient if you want to display values from an incrementing value or function result).
If you had some LEDs via resistors connected to the outputs, you would have this as a result of sending 0×82:
Now if you want to address all the outputs at once, just send the byte for bank B after bank A. For example, we want bank A to be 11001100 and bank B to be 10001000 – so we send the following:
Wire.beginTransmission(0x20); Wire.send(0xCC); // address bank A Wire.send(0x88); // address bank B Wire.endTransmission();
… with the results as such (bank B on the left, bank A on the right):
You can also just address bank B, if so bank A does not change. Now let’s put all of this output knowledge into a more detailed example. From a hardware perspective we are using a circuit as described above, with the addition of a 560 ohm resistor followed by an LED thence to ground from on each of the sixteen outputs. Here is the sketch (download):
/*
Example 41.1 - Microchip MCP23017 with Arduino
http://tronixstuff.wordpress.com/tutorials > chapter 41
John Boxall | CC by-sa-nc
*/
// pins 15~17 to GND, I2C bus address is 0x20
#include "Wire.h"
void setup()
{
Wire.begin(); // wake up I2C bus
// setup addressing style
Wire.beginTransmission(0x20);
Wire.send(0x12);
Wire.send(0x20); // use table 1.4 addressing
Wire.endTransmission();
// set I/O pins to outputs
Wire.beginTransmission(0x20);
Wire.send(0x00); // IODIRA register
Wire.send(0x00); // set all of bank A to outputs
Wire.send(0x00); // set all of bank B to outputs
Wire.endTransmission();
}
void binaryCount()
{
for (byte a=0; a<256; a++)
{
Wire.beginTransmission(0x20);
Wire.send(0x12); // GPIOA
Wire.send(a); // bank A
Wire.send(a); // bank B
Wire.endTransmission();
delay(100);
}
}
void loop()
{
binaryCount();
delay(500);
}
And here is the example blinking away:
Although that may have seemed like a simple demonstration, it was created show how the outputs can be used. So now you know how to control the I/O pins set as outputs. Note that you can’t source more than 25 mA of current from each pin, so if switching higher current loads use a transistor and an external power supply and so on.
Now let’s turn the tables and work on using the I/O pins as digital inputs. The MCP23017 I/O pins default to input mode, so all we need to do is set the addressing method as such in void setup()
// setup addressing style Wire.beginTransmission(0x20); Wire.send(0x12); Wire.send(0x20); // use table 1.4 addressing Wire.endTransmission();
Then in the void loop() or other function all we do is set the address of the register to read and receive one byte of data. For our next example, we have our basic sketch as described at the start of this article using four normally-open buttons (once again using the ‘button board‘) which are connected to bank B inputs 0~3. Consider the first five lines of void loop() in the following example (download);
/*
Example 41.2 - Microchip MCP23017 with Arduino
http://tronixstuff.wordpress.com/tutorials > chapter 41
John Boxall | CC by-sa-nc
*/
// pins 15~17 to GND, I2C bus address is 0x20
#include "Wire.h"
byte inputs=0;
void setup()
{
Serial.begin(9600);
Wire.begin(); // wake up I2C bus
// setup addressing style
Wire.beginTransmission(0x20);
Wire.send(0x12);
Wire.send(0x20); // use table 1.4 addressing
Wire.endTransmission();
}
void loop()
{
Wire.beginTransmission(0x20);
Wire.send(0x13); // set MCP23017 memory pointer to GPIOB address
Wire.endTransmission();
Wire.requestFrom(0x20, 1); // request one byte of data from MCP20317
inputs=Wire.receive(); // store the incoming byte into "inputs"
if (inputs>0) // if a button was pressed
{
Serial.println(inputs, BIN); // display the contents of the GPIOB register in binary
delay(200); // for debounce
}
}
In this example void loop() sends the GPIOB address (0×13) to the IC. Then using Wire.requestFrom() it asks for one byte of data from the IC – the contents of the register at 0×13. This byte is stored in the variable inputs. Finally if inputs is greater than zero (i.e. a button has been pressed) the result is sent to the serial monitor window and displayed in binary. We display it in binary as this represents the state of the inputs 0~7. Here is an example of pressing the buttons 1, 2, 3 then 4 – three times:
And as we are reading eight inputs at once – you can detect multiple keypresses. The following is an example of doing just that:
As you can see pressing all four buttons returned 1111, or the first and third returned 101. Each combination of highs and lows on the inputs is a unique 8-bit number that can also be interpreted in decimal or hexadecimal. And if you wanted to read all sixteen inputs at once, just request and store two bytes of data instead of one.
For our last example – a demonstration of using bank A as outputs and bank B as inputs. Four LEDs with matching resistors are connected to bank A outputs 0~3, with the buttons connected as per example 41.2. Here is the sketch (download):
/*
Example 41.3 - Microchip MCP23017 with Arduino
http://tronixstuff.wordpress.com/tutorials > chapter 41
John Boxall | CC by-sa-nc
*/
// pins 15~17 to GND, I2C bus address is 0x20
#include "Wire.h"
byte inputs=0;
void setup()
{
Serial.begin(9600);
Wire.begin(); // wake up I2C bus
// setup addressing style
Wire.beginTransmission(0x20);
Wire.send(0x12);
Wire.send(0x20); // use table 1.4 addressing
Wire.endTransmission();
Wire.beginTransmission(0x20);
Wire.send(0x00); // IODIRA register
Wire.send(0x00); // set all of bank A to outputs
Wire.endTransmission();
}
void loop()
{
// read the inputs of bank B
Wire.beginTransmission(0x20);
Wire.send(0x13);
Wire.endTransmission();
Wire.requestFrom(0x20, 1);
inputs=Wire.receive();
// now send the input data to bank A
Wire.beginTransmission(0x20);
Wire.send(0x12); // GPIOA
Wire.send(inputs); // bank A
Wire.endTransmission();
delay(200); // for debounce
}
By now there shouldn’t be any surprises in the last example – it receives a byte that represents bank B, and sends that byte out to bank A to turn on the matching outputs and LEDs. For the curious, here it is in action:
So there you have it… another way to massively increase the quantity of digital I/O pins on any Arduino system by using the I2C bus.
Have fun and keep checking into tronixstuff.com. Why not follow things on twitter, Google+, subscribe for email updates or RSS using the links on the right-hand column, or join our Google Group – dedicated to the projects and related items on this website. Sign up – it’s free, helpful to each other – and we can all learn something.
Like this:
August 26, 2011 - Posted by John Boxall | arduino, education, I2C, microcontrollers | arduino, bus, control, duemilanove, expander, i/o, i2c, input, lesson, lessons, mcp23017, microchip, output, tronixstuff, tutorial, tutorials, uno
23 Comments »
Leave a Reply Cancel reply
YouTube
Visit tronixstuff on YouTube for our range of videosClock Projects
Arduino Tutorials
Click for Detailed Chapter Index
Chapters 0 1 2 3 4
Chapters 5 6 6a 7 8
Chapters 9 10 11 12 13
Ch. 14 - XBee
Ch. 15 - RFID
Ch. 16 - Ethernet
Ch. 17 - GPS part I
Ch. 18 - RGB matrix
Ch. 19 - GPS part II
Ch. 20 - I2C bus part I
Ch. 21 - I2C bus part II
Ch. 22 - AREF pin
Ch. 23 - Touch screen
Ch. 24 - Monochrome LCD
Ch. 25 - Analog buttons
Ch. 26 - Arduino + GSM - part I
Ch. 27 - Arduino + GSM - part II
Ch. 28 - Colour LCD
Ch. 29 - TFT LCD
Ch. 30 - Arduino + twitter
Ch. 31 - Inbuilt EEPROM
Ch. 32 - Infra-red control
Ch. 33 - Control AC via SMS
Ch. 34 - SPI bus part I
Ch. 35 - Video-out
Ch. 36 - SPI bus part II
Ch. 37 - Timing with millis()
Ch. 38 - Thermal Printer
Ch. 39 - NXP SAA1064
Ch. 40 - Push wheel switches
Ch. 40a - Wheel switches II
Ch. 41 - More digital I/O
Ch. 42 - Numeric keypads
Ch. 42a - Keypads II
Ch. 43 - Port Manipulation
Ch. 44 - ATtiny+Arduino
Ch. 45 - Ultrasonic Sensor
Ch. 46 - Analog + buttons IISearch
RSS Feeds
Categories
Previous posts
Archives
- May 2012
- April 2012
- March 2012
- February 2012
- January 2012
- December 2011
- November 2011
- October 2011
- September 2011
- August 2011
- July 2011
- June 2011
- May 2011
- April 2011
- March 2011
- February 2011
- January 2011
- December 2010
- November 2010
- October 2010
- September 2010
- August 2010
- July 2010
- June 2010
- May 2010
- April 2010
- March 2010
Contact information
email - john at tronixstuff dot com
Google Group
Australian Electronics!
Buy and support Silicon Chip - Australia's only Electronics Magazine.Creative Commons
All the original material in this website, unless noted otherwise, is covered under a Creative Commons Attribution-Non Commercial-Share Alike v3.0 license. Please email me if you see any mis-attributions or would like to use my content in different circumstances.on twitter…
- Congratulations @telstra - activated the 1000th LTE base station: bit.ly/LFUjRk 1 day ago
-
Flickr Photos



More Photos Find me on…
Interesting Sites
David L. Jones' eev blog
Freetronics Arduino Geniuses!
Little Bird Electronics Service Powered Electronics!
Silicon Chip magazine Always a great read!
Amazing Arduino Shield Directory
The Amp Hour podcast
EEWeb Elec Engineering Forum
Superhouse.tv High-tech home renovation
Mr Dick Smith OANuclear weapons = global suicide
In a war with nuclear weapons, everybody loses. Please check these out:
Count Down to Zero
The War Game
Threads








Just finished you latest tutorial, which was great as usual, and noticed, what I think is, a conflict in op-code usage.
void setup()
{
Wire.begin(); // wake up I2C bus
// setup addressing style
Wire.beginTransmission(0×20);
======================================
Shouldn’t this op-code be 0x0A for IOCON
Wire.send(0×12);
======================================
Wire.send(0×20); // use table 1.4 addressing
Wire.endTransmission();
void binaryCount()
{
for (byte a=0; a<256; a++)
{
Wire.beginTransmission(0×20);
====================================
Otherwise how do you resolve this conflict
Wire.send(0×12); // GPIOA
====================================
Wire.send(a); // bank A
Wire.send(a); // bank B
Wire.endTransmission();
delay(100);
}
}
Keep up the tutorials.
Hugh
Possibly, however the hardware worked perfectly with the published sketch. I’ve disassembled the hardware so will try your code later when I update the article.
cheers
john
I agree. My reading of the data sheet shows that it’s the BANK bit in the IOCON register that changes the way the registers are mapped.
The BANK bit is set low at Power On Reset, which is why the sketch still works. You can comment these lines out and the sketch still works fine.
If you want to use the alternate address register mapping then the BANK bit needs to be set high. The BANK bit is bit 7, so to change you write 0×80 into address 0x0A. (You need to write 0×00 into 0×05 to revert back, as the mapping has changed.)
Enjoying and learning lots as I work though these. Thanks for putting them out there.
Hi John
Great tutorial , as I know I have some expander chips I thought I would give it a go ,
but as luck would have it, ( bad luck mainly) my chips are all MCP23S17 which I believe are
the SPI version not I2C.
Any chance of you doing a tutorial with this chip ?
Don
Hi Don
No real need, it’s just sending and receiving bytes of data using SPI bus instead of I2C. See my SPI tutorials:
http://tronixstuff.wordpress.com/2011/05/13/tutorial-arduino-and-the-spi-bus/
http://tronixstuff.wordpress.com/2011/06/15/tutorial-arduino-and-the-spi-bus-part-ii/
then section 1.3.3 of the MCP23x17 data sheet.
cheers
john
waiting for your next tutorial.
I’m in holiday mode for a few weeks, next tutorial in October.
You get holidays as well !!!
next thing you will be wanting tea breaks
Don
Well that’s the joy of working for oneself
I have a question – It seems like you can only change the bus address on the last 3 digits, meaning there’s a maximum of 7 unique addresses you can set it to. With only 7 Addresses, and 16 I/O per Chip, that’s a maximum of 112 I/O. For my project, I need about 150 – is there any way to change the 1st-4th bits of the address?
You can use eight MCP23017s at once
which gives you 128 I/O. There is also the MCP23S17 which is identical but has an SPI interface – so you could use a couple of those as well to solve your problem.
Hi, when I google about I2C limited address solution, I found this PCA9545. Can you make tutorial about I2C multiplexer?
Yes, I will. It is on my to-do list – probably in about one month.
John
Hi Snipye, you wrote
“I have a question – It seems like you can only change the bus address on the last 3 digits, meaning there’s a maximum of 7 unique addresses”
Surely its 8 addresses , remember “0″ is also a unique address
000, 001, 010, 011, 100, 101, 110, 111
Don
Thanks for a very interesting tutorial
This is great for home automation…
Keep the good working
I’ve been playing around with this chip but have found it to be intermittently dropping out of communication with with Arduino. I had to remove power from the chip, and then reset the Arduino to recover. Adding a 10k pull up resistor to the Reset pin seems to have fixed it for me.
Cheers.
you’re tutorials are simply great
it’s my first stop when I need to educate myself on a subject to implement it with Arduino
keep it up!
“your tutorials”
in the old days I used to laugh whenever I would see people confusing “your” with “you’re” but I guess after all these years the internet has influenced me as well lol
Thanks for your feedback, I’m happy you can make use of the articles.
cheers
john
example 41.1Question: “From a hardware perspective we are using a circuit as described above, with the addition of a 560 ohm resistor followed by an LED thence to ground from on each of the sixteen outputs” Is this to say the i/o pins are wired PIN > RESISTOR > LED ANODE then LED CATHODE >GND ?
Yes.
Thanks John, I’ve wired up example41p1 using 10K resistors on the 12 and 13 (arduino 4 and 5) and 330ohm resistors on each of the sixteen output pins but I get nothing from the LEDs. I followed the schematic exactly as you have it with the exception of the resistor values. What might I be doing wrong? Any ideas? Thank you again.
Caesar
First use 4.7k resistors as described for the pull-ups on the I2C bus. Then double-check your wiring. The code compiles fine on a fresh v23 installation.