new library - MCP23016 IO expander

Hi all,

The MCP23016 is a 16 ports I/O expander that works on the i2c bus. Where you normally have to do some bitwise operations in your main code to write or read only one pin of the expander, this library provides the user a interface just like the interface you use for the normal I/O port of an arduino. This library uses the Two Wire library as base to communicate through the I2C bus. The rest of the class is really straight forward and simplistic, but it provides just the things you need to keep your code clean and tidy!

I've put all the info on:

In the future i hope to add support for the MCP23017 and MCP23018 chips, but because I don't have them I can't test with them now.

This is my first library that I've written for the arduino. Because of that it might that I've forgotten something that is pretty standard here when posting a new library.

all comments are welcome!


just a few thoughts: (code not tested)


// Public Methods //////////////////////////////////////////////////////////////

bool IOexpander::init(uint8_t address)
  addr = address;
  port0 = 0;
  port1 = 0;
  regPort0 = 0xFF;
  regPort1 = 0xFF;

  return refresh();

private setDDR() <<< can be used in pinModePort too
  Wire.send(0x06);   // #define REGISTERNAME 0x06 ??
  Wire.send(regPort0 );  // set DDR Port0
  Wire.send(regPort0 );  // set DDR Port1

void IOexpander::pinModePort(uint8_t port, bool mode)
  if(port > 1) return; // we only have two ports
	//DDR as input = 1
	if(port == 0)
		regPort0 = 0xFF;    use #define  ALL_INPUT 0xFF ??
		regPort1 = 0xFF;
	//DDR as output 0
	if(port == 0)
		regPort0 = 0x00;   [color=red]use #define ALL_OUTPUT 0x00 ??  (are these consistent with the existing pinMode - INPUT / OUTPUT?[/color]
		regPort1 = 0x00;

bool IOexpander::digitalRead(uint8_t port,uint8_t pin)
The Arduino digitalRead() returns an int so I would conform to that  => see wiring_digital.c in the Arduino distribution.

uint8_t IOexpander::sendData()
  Wire.send(0x00); //select register 1-0
  Wire.send(port0); //write to register 1-0
  Wire.send(port1); //write to register 1-1
  [color=red]return [/color]Wire.endTransmission();    << use the return value of this one   // allways true is not functional

Hai robtillaart,

Thanks for reviewing my code, good thoughts about the return value of the wire.endTransmission, I did look if the send function had a return value but didn't check this one.

I thought about defining the registers, and set them in the init function. So new versions of this chip can easily be added.

i'll implement your points soon,


I've updated my library: new version at :

changes made:

  • Returns fixed
  • default values defined
  • variable register values depending on the used model (not yet tested on MCP23017 and MCP23018)

If I include this lib in a project i also have to include the wire.h library. I don't understand why that is because i've included it already in my c file, and the compiler comes with the error code that its not included in that file? any thoughts?


Think the class name of the class should become MCP23XXX as IOexpander is too generic imho.

tiny addition, allows version checking

#define MCP23XXX_VERSION “0.2”

uint8_t regPort0;
uint8_t regPort1;
uint8_t port0;
uint8_t port1;

Consider making array’s of these, makes it also easier if in the future a 4 port chip arrives.
uint8_t regPort[2];
uint8_t port[2];

The logic in the functions become simpler e.g.

bool IOexpander::pinMode(uint8_t port,uint8_t pin, bool mode)
  // guard out of range params
  if(port > 1 || pin > 7) return false;

  uint8_t temp = 1 << pin;

  if (mode == INPUT) 
    regPort[port] |= temp;
    temp = ~temp;
    regPort[port] &= temp;
  //send new config to IODIR0 and 1
  Wire.beginTransmission(addr);  	// START TRANSMISSION
  Wire.send(regPort0);  			// DDR Port0 IODIR0
  Wire.send(regPort1);  			// DDR Port1 IODIR1

  return (Wire.endTransmission() == 0)

good point of the array. But I don't see what the advantage is of the version defined? what can you do with that? I'll wrote the version in the comment?

I use the MCP23018 extensively, so I'll give this a try.

A couple suggestions...

  • Definitely agree on previous post that the library should be named for the specific chip family.
  • Any chance you could host on github or something like that? Would make it much easier to submit patches.
  • Abstract the Wire.begin/send/end stuff into a private function to avoid so much duplication.
  • Is it a good idea to call Wire.begin() from a chip-specific driver? If you have a handful of different drivers EACH making a begin() call, will that mess something up? In my own code, I call Wire.begin() in setup(), and then each driver's own begin() afterward.
  • Why take only a 'bool' for pinModePort? Seems like an artificial constraint that an entire port be either input or output. The chip supports some 'pins' being input and others output, why not support this in software?
  • When you READ the port values you store them in (portX), which is the same place you're use when WRITING to them. Yet you are reading from the ports themselves, not the latches. I think this will give inconsistent results. To reliably read back the values you've written to outputs, I think you have to read from the latches.

In such a library, I would like to see support for...

  • Pull-up registers (Do you have them on 23016?)
  • Writing 8 bits to a whole port at once. This is how I usually use it.

- Writing 16 bits to the whole chip at once. One thing I use it for is to drive a 12-pin 4-digit 7-segment LED, so I know all 12 bits at a time to display a digit.

Btw, I think you need Wire.h in your .PDE because that's how Arduino build system knows which libraries to link in. It's a clever, simple approach (like much in the Arduino software). Other build systems require you to configure the project and separately define which libraries to link in. Here, it just picks it up from the includes. Nice.


Came accross the centipede shield - - it uses the MCP23017. Their lib may also be an additional inspiration.

Hi jc000,

thanks for looking in! I agree with you on most of your'e points and i'll changes them. I'll look into the github, never herd of it before.

about the reading from the general purpose registers to the portX : i'll read the complete register, write it to portX, and in digitalRead i'll check a specific pin. and if i understand you're right, you're thinking of the situation where i'll read back the outputs from the GP register while they didn't become high (shortcut?) and when i'll write that port again i'll write a LOW because I've read that before? In case i'll read them from the latches i won't have this problem, only its not my intension to read my outputs, its just that they are in the GP register. I don't think that it affects my inputs? Is there a reason to read back the outputs your're writing? if so, i'll read the inputs from the GP and the outputs from the latches, and I've to think of a function that checks the effect of the output pin (if the pin comes high indeed). if not, i'll only read the input ports from the GP and i don't effect the output pins (in portX)

The mcp23016 doesn't have pullup resistors but it might be handy to add the support for that.

@robtillaart when i'll started this project i looked if there was work from others, at that point i didn't know there were other chips like this one. This lib can be indeed some extra inspiration.

Ok, I should have been a lot more specific about my latches comment. Consider the following sequence of events:

  1. Set port 0 to outputs, port 1 to inputs
  2. Write B11111111 to port 0. This will also writes 0 to port 1, but that's pr'y OK.
  3. Read port 1. This will also read GP0 into the 'port0' member variable. Now 'port0' contains WHATEVER the GP0 register had in it.
  4. Try to write a 0 to the LSB of port 0.

You would EXPECT this to write B11111110 to port 0. But in my experience this may or may not happen. In step 3, you filled the 'port0' member with the contents of GP0 as a side-effect of filling the 'port1' member from GP1, which is what you were really trying to do.

The GPx registers seem not to reliably contain the last value written to output pins. The OLATx registers do, so in my code I always read from the latches when I need the values last set to the pins. Now in your code, you buffer the values in RAM so you wouldn't really need to go to the latches at all. Instead, when you read, you could just remove any bits that were set to outputs, so you'd only update 'port0' and 'port1' for bits whose corresponding pins were set to inputs.

WRT the internal-pullups, yes I am reading the 23016 datasheet, and I see it is a much simpler chip. I use the pullups all the time, to avoid having to add resistor networks to the system for the same purpose.


okay, I see the problem! First i'll implement your's and robtillaart comments before I'll introduce new things like the pullups.

thanks for now!