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.
[Updated 25/11/2012]
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.
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. So before using we need to set whether each bank is an input or an output. First, we’ll examine setting them as outputs. So to set bank A to outputs, we use:
Wire.beginTransmission(0x20); Wire.write(0x00); // IODIRA register Wire.write(0x00); // set all of bank A to outputs Wire.endTransmission();
Then to set bank B to outputs, we use:
Wire.beginTransmission(0x20); Wire.write(0x01); // IODIRB register Wire.write(0x00); // set all of bank B to outputs Wire.endTransmission();
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.write(0x12); // address bank A Wire.write(??); // value to send Wire.endTransmission();
To control bank B, we use:
Wire.beginTransmission(0x20); Wire.write(0x13); // address bank B Wire.write(??); // 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 status of each output pin. Or if binary does your head in, convert it to hexadecimal. Or a decimal number. 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:
For example, we want bank A to be 11001100 and bank B to be 10001000 – so we send the following (note we converted the binary values to decimal):
Wire.beginTransmission(0x20); Wire.write(0x12); // address bank A Wire.write(204); // value to send Wire.endTransmission(); Wire.beginTransmission(0x20); Wire.write(0x13); // address bank B Wire.write(136); // value to send Wire.endTransmission();
… with the results as such (bank B on the left, bank A on the right):
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
/* 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
// set I/O pins to outputs Wire.beginTransmission(0x20); Wire.write(0x00); // IODIRA register Wire.write(0x00); // set all of bank A to outputs Wire.endTransmission();
Wire.beginTransmission(0x20); Wire.write(0x01); // IODIRB register Wire.write(0x00); // set all of bank B to outputs Wire.endTransmission(); }
void binaryCount()
{
for (byte a=0; a<256; a++)
{
Wire.beginTransmission(0x20);
Wire.write(0x12); // GPIOA
Wire.write(a); // bank A
Wire.endTransmission();
Wire.beginTransmission(0x20); Wire.write(0x13); // GPIOA Wire.write(a); // bank B Wire.endTransmission(); } }
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 we just need to initiate the I2C bus. 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
/* 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
}
void loop()
{
Wire.beginTransmission(0x20);
Wire.write(0x13); // set MCP23017 memory pointer to GPIOB address
Wire.endTransmission();
Wire.requestFrom(0x20, 1); // request one byte of data from MCP20317
inputs=Wire.read(); // 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
/* 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
}
void loop()
{
Wire.beginTransmission(0x20);
Wire.write(0x13); // set MCP23017 memory pointer to GPIOB address
Wire.endTransmission();
Wire.requestFrom(0x20, 1); // request one byte of data from MCP20317
inputs=Wire.read(); // 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
}
}
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.
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
Sorry, the comment form is closed at this time.
YouTube
Visit tronixstuff on YouTube for our range of videosClock Projects
Zero - blinky the clock
One - DMD clock
Two - Single digit clock
Three - Pillow clock
Four - Scrolling text clockArduino 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 - RDM-630
Ch. 15a - RFID - ID-20
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 II
Ch. 47 - Internet-controlled relays
Ch. 48 - MSGEQ7 Spectrum Analyzer
Arduino Due - first look
Ch. 49 - KTM-S1201 LCD modules
Ch. 50 - ILI9325 colour TFT LCD modules
Ch. 51 - MC14489 LED display driver IC
Search
RSS Feeds
Categories
Previous posts
Archives
- May 2013
- April 2013
- March 2013
- February 2013
- January 2013
- October 2012
- September 2012
- June 2012
- 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…
- Australia’s Slow-Motion Car Crash theglobalmail.org/blog/australia… via @TheGlobalMail 15 minutes ago
- Thanks everyone - logic probe kit review won - altronics.com.au/index.asp?area… 1 hour ago
- @Sqrt_2GM_over_R No, only single-ended ADC 1 hour ago
- What do you want next - a logic probe kit review or an Arduino ADC tutorial? 3 hours ago
- RT @nostarch: Thanks to @lisperati, No Starch ebooks can now be bought instantly with Bitcoin via Cointagion.com! #bitcoin #NoDRM 3 hours ago
Flickr Photos



More PhotosFind me on…
Interesting Sites
David L. Jones' eev blog
Freetronics Arduino Geniuses!
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







