New MCP23017 library!

Hi guys,

I have recently been trying to interface to a number of MCP23017 chips and have found that although there is an existing Centipede library for these it doesnt seem to be very easy to set up the addresses for the chips, and it doesn't provide access to the more advanced functions such as pullups etc that the MCP23017 provides easily without lots of I2C reads/writes.

So I have made my own library which does this - it supports everything except Interrupts at present. If it is thought to be useful enough, could people please help me to get it onto its own page at arduino.cc? It also caches register values where possible to save on i2c reads/writes.

Only notes:

The i2c address the constructor expects is just the value of the address pins ie A2, A1, A0 ie a number between 0 and 7 decimal.

You should ensure the Wire library is initiated first ie. Wire.begin() before init-ing the MCP23017.

Call init() before you try to use any of the other functions

The arduino-pin like functions (pinMode, digitalRead, digitalWrite) should work as expected ie you can enable the internal pullup by setting pinMode to INPUT and then digitalWriting that pin HIGH.

Pin 0 is GPB0
Pin 15 is GPA7

I hope it is useful!

Any comments/bugfixes: please message me :slight_smile:

Header file:

/*  MCP23017 library for Arduino 
    Copyright (C) 2009 David Pye    <davidmpye@gmail.com  

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MCP23017_H
#define MCP23017_H

#include "WProgram.h"
#include "Wire.h"
//Register defines from data sheet - we set IOCON.BANK to 0
//as it is easier to manage the registers sequentially.
#define IODIR 0x00
#define IPOL 0x2
#define GPINTEN 0x4
#define DEFVAL 0x6
#define INTCON 0x8
#define IOCON 0x0A
#define GPPU 0x0C
#define INTF 0x0E
#define INTCAP 0x10
#define GPIO 0x12
#define OLAT 0x14

#define I2C_BASE_ADDRESS 0x40

class MCP23017
{
  public:
    //NB the i2c address here is the value of the A0, A1, A2 pins ONLY
    //as the class takes care of its internal base address.
    //so i2cAddress should be between 0 and 7 
    MCP23017(int i2cAddress);
    void init();

    //These functions provide an 'arduino'-like functionality for accessing
    //pin states/pullups etc.
    void pinMode(int pin, int mode);
    void digitalWrite(int pin, int val);
    int digitalRead(int pin);

    //These provide a more advanced mapping of the chip functionality
    //See the data sheet for more information on what they do

    //Returns a word with the current pin states (ie contents of the GPIO register)
    word digitalWordRead();
    //Allows you to write a word to the GPIO register
    void digitalWordWrite(word w);
    //Sets up the polarity mask that the MCP23017 supports
    //if set to 1, it will flip the actual pin value.
    void inputPolarityMask(word mask);
    //Sets which pins are inputs or outputs (1 = input, 0 = output) NB Opposite to arduino's
    //definition for these
    void inputOutputMask(word mask);
    //Allows enabling of the internal 100k pullup resisters (1 = enabled, 0 = disabled)      
    void internalPullupMask(word mask);

    //Interrupt functionality (not yet implemented)


  private:
      void writeRegister(int regaddress, byte val);
      void writeRegister(int regaddress, word val);
      word readRegister(int regaddress);
      //Our actual i2c address
      byte _i2cAddress;

      //Cached copies of the register vales
      word _GPIO, _IODIR, _GPPU;
};
#endif

Main source file:

/*  MCP23017 library for Arduino 
    Copyright (C) 2009 David Pye    <davidmpye@gmail.com  

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "MCP23017.h"

MCP23017::MCP23017(int i2cAddress) {
      _i2cAddress = (I2C_BASE_ADDRESS >>1) | (i2cAddress & 0x07);
      //Default state is 0 for our pins
      _GPIO = 0x0000;
      _IODIR = 0x0000;
      _GPPU = 0x0000;
}

void MCP23017::init() {
      //Set the IOCON.BANK bit to 0 to enable sequential addressing
      //IOCON 'default' address is 0x05, but will
      //change to our definition of IOCON once this write completes.
      writeRegister(0x05, (byte)0x0);      
      //Our pins default to being outputs by default.
      writeRegister(IODIR, (word)_IODIR);
}

void MCP23017::pinMode(int pin, int mode) {
      //Arduino defines OUTPUT as 1, but 
      //MCP23017 uses OUTPUT as 0. (input is 0x1)
      mode = !mode;
      if (mode) _IODIR |= 1 << pin;
      else _IODIR &= ~(1 << pin);
      writeRegister(IODIR, (word)_IODIR);
}

int MCP23017::digitalRead(int pin) {
      _GPIO = readRegister(GPIO);
      if ( _GPIO & (1 << pin)) return HIGH;
      else return LOW;
}

void MCP23017::digitalWrite(int pin, int val) {
      //If this pin is an INPUT pin, a write here will
      //enable the internal pullup
      //otherwise, it will set the OUTPUT voltage
      //as appropriate.
      bool isOutput = !(_IODIR & 1<<pin);

      if (isOutput) {
            //This is an output pin so just write the value
            if (val) _GPIO |= 1 << pin;
            else _GPIO &= ~(1 << pin);
            writeRegister(GPIO, (word)_GPIO);
      }
      else {
            //This is an input pin, so we need to enable the pullup
            if (val) _GPPU |= 1 << pin;
            else _GPPU &= ~(1 << pin);
            writeRegister(GPPU, (word)_GPPU);
      }
}

word MCP23017::digitalWordRead() {
      _GPIO = readRegister(GPIO);
      return _GPIO;
}

void MCP23017::digitalWordWrite(word w) {
      _GPIO = w;
      writeRegister(GPIO, (word)_GPIO);
}

void MCP23017::inputPolarityMask(word mask) {
      writeRegister(IPOL, mask);
}

void MCP23017::inputOutputMask(word mask) {
      _IODIR = mask;
      writeRegister(IODIR, (word)_IODIR);
}

void MCP23017::internalPullupMask(word mask) {
      _GPPU = mask;
      writeRegister(GPPU, (word)_GPPU);
}

//PRIVATE
void MCP23017::writeRegister(int regAddress, byte data) {
      Wire.beginTransmission(_i2cAddress);
      Wire.send(regAddress);
      Wire.send(data);
      Wire.endTransmission();
}

void MCP23017::writeRegister(int regAddress, word data) {
      Wire.beginTransmission(_i2cAddress);
      Wire.send(regAddress);
      Wire.send(highByte(data));
      Wire.send(lowByte(data));
      Wire.endTransmission();
}

word MCP23017::readRegister(int regAddress) {
      word returnword = 0x00;
      Wire.beginTransmission(_i2cAddress);
      Wire.send(regAddress);
      Wire.endTransmission();      
      Wire.requestFrom((int)_i2cAddress, 2);
      //Wait for our 2 bytes to become available
      while (Wire.available() < 2) { }
      //high byte
      returnword = Wire.receive() << 8;
      //low byte
      returnword |= Wire.receive();

      return returnword;
}

The Centipede library doesn't worry about device addresses, you just CS.digitalWrite(54, HIGH); and it'll automatically activate the correct pin on the MCP23017 with address 3. So you can digitalWrite any pin from 0 to 127 and it'll automatically determine device address from that. Maybe my examples need to be clearer....

Aha!

I hadn't picked up on that then :slight_smile:

In which case, all I can offer as an advantage is the read caching then!

It would be quite easy to base a centipede type library on top of this one which would offer the advantage of either using the 'base' MCP23017 functionality, or having the centipede layer to help route things.

What would be handy is to figure out a way to make shiftOut work for these 'expanded' pins!

David

This library is GPL licensed. Anybody who doesn't wish to release their source code is unable to use it.

Generally Arduino libraries are LGPL.

I pondered exactly what my thoughts on this were before I decided on the GPL for this library.

My opinion is that a) sharing code is good b) if I hadn't shared my code, there wouldn't be a library in the first place, so it seems a fair trade that if people want to use it they should share also.

It's also worth highlighting one thing here: You only have to share your source if you distribute your binary (or arduino-programmed devices) - I cant turn up and demand the source for your homebrew weather station because you used my library unless you distribute it.

I would consider making it available under the LGPL if it would be included in the arduino base libraries if that were a requirement - otherwise I think I'll probably leave it under the GPL. If somebody had a particularly strong reason for requesting it to be made available under another licence, I would consider it on an individual basis.

On another note, I was rather disturbed by the lack of licencing headers on the library source code - it would be much more reassuring if these had the LGPL (or appropriate licence) headers on so people knew exactly what licences they were made available under.

Cheers,

David

Hi,
First , sorry for my English, is not my native Language

Can you put some examples for initialitation with this library ¿?

Thanks

MCP23017 buttonMux(0x00); // Assuming the three address pins are all 0
buttonMux.init();

You can use the arduino like syntax of:
buttonMux.pinMode(0, INPUT);
int result = buttonMux.digitalRead(0);

//result now contains the state of pin number 1.

to set pins one at a time. Alternatively, there are functions more like the options in the MCP23017 data sheet.

Have a look at the .h file - it explains each of the functions. There are ones that function like arduino ones - pinMode, digitalRead, digitalRead if you want to work on individual pins, or there are functions to let you read words rather than pins one at a time - up to you!

David

Thank you!

Hi

I have had this working, however now I am getting the following errors on compile and I am not sure why.
The library is the same, I am running IDE 21.

C: \arduino-0021\libraries\MCP23017\MCP23017.cpp: In member function 'void MCP23017::writeRegister(int, word)':
C:\ arduino-0021\libraries\MCP23017\MCP23017.cpp:107: error: call of overloaded 'send(word)' is ambiguous
C:\ arduino-0021\libraries\Wire/Wire.h:54: note: candidates are: void TwoWire::send(uint8_t)
C:\ arduino-0021\libraries\Wire/Wire.h:56: note:                 void TwoWire::send(int)
C:\ arduino-0021\libraries\Wire/Wire.h:57: note:                 void TwoWire::send(char*) <near match>
C:\ arduino-0021\libraries\MCP23017\MCP23017.cpp:108: error: call of overloaded 'send(unsigned int)' is ambiguous
C:\ arduino-0021\libraries\Wire/Wire.h:54: note: candidates are: void TwoWire::send(uint8_t)
C:\ arduino-0021\libraries\Wire/Wire.h:56: note:                 void TwoWire::send(int)
C:\ arduino-0021\libraries\Wire/Wire.h:57: note:                 void TwoWire::send(char*) <near match>

I dont understand the problem really. It appears it is saying the library cannot send a word, however the library clearly is converting the word into bytes before sending it - so I dont understand.
Also I am not sure what has changed either.

I started a new project with the following code, and still got the above errors.
Any ideas?

#include <Wire.h>
#include <MCP23017.h>

void setup()
{
  
}
void loop()
{

}

Most likely its just me doing something stupid, but I dont see what I could have done to cause this.

Cheers
James

I'm not sure why you can't understand what the message is telling you, so I'll break it down.

C: \arduino-0021\libraries\MCP23017\MCP23017.cpp: In member function 'void MCP23017::writeRegister(int, word)':

The first part is the name of the file that was being compiled, in which an error occurred.

Next is some information about where the error occurred. In this case, it is in the writeRegister function that takes two arguments - an int and a word.

C:\ arduino-0021\libraries\MCP23017\MCP23017.cpp:107: error: call of overloaded 'send(word)' is ambiguous

In the writeRegister function, it calls send() with an argument of type word. In the class associated with the send() function, there are two or more overloads that might be the function to call, but none of them EXPLICITLY take a word. So, an implicit cast will need to be performed, and the compiler does not know which one it should select. At this point, we don't (you might, but we don't) know what that class is, or what the possible overloads are.

C:\ arduino-0021\libraries\Wire/Wire.h:54: note: candidates are: void TwoWire::send(uint8_t)
C:\ arduino-0021\libraries\Wire/Wire.h:56: note: void TwoWire::send(int)
C:\ arduino-0021\libraries\Wire/Wire.h:57: note: void TwoWire::send(char*)

OK. Now we know what class send() belongs to, and what the "best" overloads are.

From http://arduino.cc/en/Reference/Word, we see that

A word stores a 16-bit unsigned number, from 0 to 65535. Same as an unsigned int.

So, one would really wonder why MCP23017 needs to adopt the bastardization that Microsoft came up with. What is wrong with the explicit type unsigned int? The explicitly defines what type value we are dealing with, while word attempts to hide that knowledge. Why?

And, how does this help you?

Well, that depends on what you are trying to do. If you just want to understand the message, by now you should.

If you want to solve the problem, you need to either fix the Wire library so that it has a send method that takes an unsigned int argument, and correctly processes it.

Or, you need to revise the MCP23017 library (specifically, the writeRegister() method) so that it converts or casts the value that it has to a type for which there is an existing send() method in the Wire class.

Which would be easiest or most appropriate is not for us to say.

Thanks PaulS
However you are speaking to me like I wrote the library and that I know everything about it - this is not the case. I got it from this post, hence me posting here asking the question.

I'm not sure why you can't understand what the message is telling you

  • because not all people seem to be as clued up with this as you obviously.

I appriciate you posting and helping, however the tone at which your help comes is offensive. This is a forum for people to learn, and obviously not everyone understands every problem that is encountered. Getting a response like yours while is helpful isnt a very nice way to help someone who doesnt understand the problem.

At this point, we don't (you might, but we don't) know what that class is, or what the possible overloads are.

how would I deffinitely know this when its not my library I am attempting to run? All the code for the library is pasted above by the writer, so you have the same info as me.

As far as I know, this is the offending function:

void MCP23017::writeRegister(int regAddress, word data) {
      Wire.beginTransmission(_i2cAddress);
      Wire.send(regAddress);
      Wire.send(highByte(data));
      Wire.send(lowByte(data));
      Wire.endTransmission();
}

And from what I have read in the reference section, highbyte() and lowbyte() take a word and return a byte. And from what I can see of the wire library (Arduino - Home), there is a wire.send to suit a byte input.
Wire.send(value)
value: a byte to send (byte)

This is about as far as my knowledge goes - hence the post.

If I knew the asnwer I wouldnt post now would I?

Thanks for the reply, but cut the attitude toward people who dont know as much would you - its a learning forum.

Just for interests sake, I changed this:

void MCP23017::writeRegister(int regAddress, word data) {
      Wire.beginTransmission(_i2cAddress);
      Wire.send(regAddress);
      Wire.send(highByte(data));
      Wire.send(lowByte(data));
      Wire.endTransmission();
}

To this:

void MCP23017::writeRegister(int regAddress, word data) {
      byte High = highByte(data);
      byte Low = lowByte(data);
      Wire.beginTransmission(_i2cAddress);
      Wire.send(regAddress);
      Wire.send(High);
      Wire.send(Low);
      Wire.endTransmission();
}

And now is compiles.
I dont quite see how they are different?

Not an Arduino expert, but I've been programming for many years. If Arduino allows casting, shouldn't this fix the problem?

void MCP23017::writeRegister(int regAddress, word data) {
      Wire.beginTransmission(_i2cAddress);
      Wire.send(regAddress);
      Wire.send( (byte) highByte(data));
      Wire.send( (byte) lowByte(data));
      Wire.endTransmission();
}

Spaces added for emphasis. Hope this helps/works. :slight_smile:

the tone at which your help comes is offensive.

It certainly wasn't my intention to be offensive. I do not know what your level of experience is, so I tried to explain what each line in the error message was telling you.

Often, people complain that the errors are too cryptic. I was trying to explain exactly what the message was saying.

obviously not everyone understands every problem that is encountered.

Obviously. But, it wasn't clear whether you didn't understand the error message, didn't know how to fix it, or didn't know why you were getting it.

how would I deffinitely know this when its not my library

I did not say that you would definitely know what class was the cause of the problem. I said you might. But, I don't, because I don't have that library.

As far as I know, this is the offending function

Excellent. Now we can both see which class is the root of the problem. The fact that you were able to find that code says that you are as dumb as you were pretending to be. (OK, maybe now I am getting a bit offensive.)

This is about as far as my knowledge goes - hence the post.

But, none of these details were in your first post. Just like any interaction between two people, there are assumptions made on both sides. As events unfold, and more information is shared, the assumptions are (or should be) revised.

Thanks for the reply, but cut the attitude toward people who dont know as much would you - its a learning forum.

I really think this says more about you than it does about me. I'm fine with that.

Yes that type casting will be the same but I dont get why it is needed when the functions highbyte() and lowbyte() apparently return bytes... so surely that means their outputs are bytes... yet the error message is showing it thinks they are words?

PaulS - thanks. Ill leave it at that - no point starting a fight over this. I would have just thought that posting a question in a topic which the code was originally posted would mean you would be able to look above to find the code, and therefore the code in question. I didnt think I would have the explicitly paste the problem if its written above.

Anyway - problem solved it seems, exactly why I am not 100% sure, but its good enough to progress. Whats even more odd is this library was working fine, for more people than just me. I didnt use it for about a month and came back to it and then got compile errors. I have since tried it again on IDE18 and IDE19, but it doesnt work for me in those without the mods. IDE18 was the first IDE I started using with Arduino. Strange - I dont know what would have changed to have caused this.

Regards.