Remote I/O Expander I2C MCP23017

Hello,

I was wondering if anybody here has experience with controlling this I/O expander from Microchip. I've gone through a few code examples and so far no luck. Maybe I'm missing something small and any thoughts would be much appreciated.

Basically I'm trying to set bank A to output, then turn everything on, then output a value that increases slowly.

 *  Arduino analog input 5 - I2C SCL
 *  Arduino analog input 4 - I2C SDA
 *
 ******************************************************************************/

#include <Wire.h>

#define IODIRA    0x00
#define IODIRB    0x01
#define IOPOLA    0x02
#define IOPOLB    0x03
#define GPINTENA    0x04
#define GPINTENB    0x05
#define DEFVALA    0x06
#define DEFVALB   0x07
#define INTCONA    0x08
#define INTCONB   0x09
#define IOCONA      0x0A
#define IOCONB      0x0B
#define GPPUA      0x0C
#define GPPUB      0x0D
#define INTFA      0x0E
#define INTFB      0x0F
#define INTCAPA   0x10
#define INTCAPB   0x11
#define GPIOA      0x12
#define GPIOB      0x13
#define OLATA      0x14
#define OLATB      0x15

#define MCP_0_write    0b01000000
#define MCP_0_read    0b01000001

void setup()
{ 
  Wire.begin(); // join i2c bus (address optional for master)

  // set up the expander configured for output
  
  Wire.beginTransmission(MCP_0_write);
  Wire.send(IODIRA);
  Wire.send(0xff & 0x00);
  Wire.endTransmission();

  Wire.beginTransmission(MCP_0_write);
  Wire.send(GPIOA);
  Wire.send(0xff);
  Wire.endTransmission();

}

int val = 0x00;

void loop()
{
  Wire.beginTransmission(MCP_0_write);
  Wire.send(GPIOA);
  Wire.send(0xff & val++);
  Wire.endTransmission();

  delay(500);

  Wire.beginTransmission(MCP_0_write);
  Wire.send(GPIOA);
  Wire.send(0xff & val++);
  Wire.endTransmission();

  delay(500);
}

It would help if you said what it does do.

However you have got the address wrong. The wire library doesn't use a separate address for read and write but one address for the device and separate calls for read and write. This means the address you should be using is your address shifted to the right by one place for all calls to the library.

I've used the MCP23017 before and it works pretty much flawlessly. Unfortunately don't have the code I used right here, but might remember to come back and post it.

16 general purpose I/O with pin change interrupt and programmable pullups for $1, not bad.

Wow under a pound from Farnell

got to be worth looking at.

Yeah, I've had a shield design sitting around for a while, uses a bunch of these. I call it the Centipede Shield. :slight_smile:

Soon to be prototyped, I think.

I fixed that issue with the expander address. I'm still having some sort of issue with getting it to work. I'm fairly new to I2C and the Wire library.

Overall what I'm trying to do right now is just some rudimentary control of a single MCP23017. What the code does (attempts) is to set bank A to output mode and then set all pins high. The main loop alternately sets all bank A pins low, then high, rinse and repeat. I have leds hooked up to each of the bank A pins, so I should have some physical indication that what I'm doing is right. My code is the same as originally posted above with the exception of the suggested address fix. Still no response from the device. Is there another register I need to configure?

Thanks for the help so far.

You might want to verify that your address pins are actually all connected to ground. The datasheet says they must be externally biased. If you let them hang in the wind they could be anything.

Yep, all of the address pins are biased to ground. I went back and checked all of the connections.

You also remembered to put 4.7K pullups on the SDA and SCL lines?

Yes. I have the 4.7K pullups on both the SCL and SDA lines... but I thought I read somewhere that this is not totally necessary due to the micro having internal pullups. I could be wrong.

At this point I'm beginning to wonder if the IC or the Arduino is shot somehow.

Here are some routines I have used successfully:

#include <Wire.h>

//redefine highByte and lowByte to return byte, not int

#define loByte (byte)lowByte
#define hiByte (byte)highByte


#define opcode B0100000      // address = 000;  7 bits!

#define ioconbyte B00100000      // disable sequential operation mode;  

#define iocon 0x0A
#define iodira 0x00
#define gpioa 0x12
#define gppua 0x0C      //pullups
#define gpintena 0x04
#define defvala 0x06
#define intcona 0x08
#define intfa 0x0E
#define intcapa 0x10
#define ipola 0x02

void MCPstore (byte i2caddress, byte i2cregister,  word i2cdata)    // Store 16 bits in specified register of MCP23017
{
  byte controlbyte  = B0100000 | i2caddress;  // note that bit 0 not used b/c 7-bit opcode for Wire library
  
  Wire.beginTransmission(controlbyte);
  Wire.send(i2cregister);
  Wire.send(loByte(i2cdata));
  Wire.send(hiByte(i2cdata));
  Wire.endTransmission();
}

word MCPread (byte i2caddress, byte i2cregister)  // Read 16 bits from MCP23017
{
  byte controlbyte  = B0100000 | i2caddress;  // note that bit 0 not used b/c 7-bit opcode for Wire library
  word tempdata;
  
  Wire.beginTransmission(controlbyte);
  Wire.send(i2cregister);
  Wire.endTransmission();
  
  Wire.requestFrom(controlbyte,(byte)2);  // otherwise function gets one byte and one int & chokes
  while (Wire.available() < 2) { }
  tempdata = Wire.receive();      // get lower 8 bytes
  tempdata |= (Wire.receive() << 8);  // get upper 8 bytes
  
  return tempdata;
}


void setup()                   
{
  Serial.begin(9600);
  
  Wire.begin();
  
  Wire.beginTransmission(opcode);
  Wire.send(iocon);
  Wire.send(ioconbyte);
  Wire.endTransmission();

  MCPstore (0,iodira,0xffff);  // all input
  MCPstore (0,gppua,0xffff);  // all pullups on
}


void loop()               
{
 
word mcpdata = MCPread(0,gpioa);
byte firstbyte = hiByte(mcpdata);
byte secondbyte = loByte(mcpdata);



Serial.println(firstbyte,HEX);
Serial.println(secondbyte,HEX);
}

The program just displays the input state of GPIOA & GPIOB, but the 16-bit read and store routines should be portable. I have used them to set outputs as well.

Note that with the sequential address mode disabled (or maybe even with it enabled) if you write 8 bits to an A register, the address pointer automatically increments to the B register. So if you set the address pointer to the A register, you can read or write to the B register without a restart just by requesting or sending 16 bits.

Unfortunately the Wire routines do not report whether there was an I2C acknowledge; this would be very helpful in troubleshooting.

And is this:

#define MCP_0_write 0b01000000
#define MCP_0_read 0b01000001

in your code a valid specifier for binary constants? The wiki page says the binary formatter is "Bxxxxxxxx" not "0bxxxxxxxx"!

Thanks for the example code. I've looked over it and it seems to be consistent with the datasheet on how to read/write to the device. I'm still having no luck in getting it to work with my setup, so I think I'll go ahead and order another IC or two just to see if I have a bad one after all.

The code works now. Thanks Chaos! As it turns out I think I may have fried the I2C pins on my first Arduino. I ordered another board and it worked flawlessly!

I'm out 20 bucks but hey, it works! Thanks again.