Interfacing an I2C GPIO expander (MCP23017) to the Raspberry Pi using C++ (i2cdev)

In this blog entry I will demonstrate how to successfully interface the MCP23017 general purpose input/output (GPIO) expander from Microchip, to the Raspberry Pi over the I2C bus.

You’ll find a similar SPI based tutorial (MCP3008 ADC) available here.

The I2C Bus

The I2C bus is a two wire bus developed by Phillips/NXP (4-wire if you want to include power and ground), that is used to connect a single master device to up to 120+ slave peripheral devices. The master device is typically a microcontroller, and the slave device(s) is/are typically peripheral chips that perform special functions such as GPIO expanders, analog to digital converters, digital to analog converters, real-time clocks, eeproms and many more.

With the I2C bus, you can also connect one master microcontroller device to multiple other microcontroller devices in I2C slave mode. You can even have multiple devices vying to become “masters” on the bus through a process of arbitration. So things can get complicated rather quickly.

The I2C 2 wire bus is made up of a SDA wire for bi-directional data transfer between master and slave devices and a SCL clock signal wire that is driven by the master to the slave(s) to keep communication synchronized.

Because one could have multiple slave devices on the I2C bus, each slave must have a distinct device address. So for example to write a byte ‘a’ to a register ‘b’ located in  a slave device with a device address ‘c’, you would have to send three bytes ‘c’, ‘b’ and ‘a’ in that order. You also have to make sure that  you assert the Read/Write bit found in the same byte as the device address ( byte ‘c’) for either read or write operation.

The original specification for the maximum clock speed/frequency for the I2C bus was 100KHz. Though this has been upgraded to 400KHz, and up to 1MHz in the SMbus (I2C extension) specification. This is still pretty slow.  The Linux i2c-dev driver allows us to set the speed of the I2C bus (Thanks Henrik Bruun for this tip!). This will be demonstrated later in the blog entry.

For more information on I2C bus, the reader is encouraged to take a look at the following links:

  The MCP23017 GPIO Expander Chip

The MCP23017 from Microchip is a neat little chip that comes in 28-PDIP, 28-SOIC and 28-SSOP packages and makes available two additional 8-bit ports. The pin out diagram for the chip is shown in Figure 1.

Figure 1. Pin-out for the MCP23017 I2C GPIO Expander

Figure 1. Pin-out for the MCP23017 I2C GPIO Expander

The MCP23017 has a 7-bit device address. The most significant 4 bits are “0100” , while the three lease significant bits are determined by the voltages on the A2, A1 and A0 pins. This allows us to connect up to 8 GPIO expanders on the same bus giving us a maximum of 16*8 = 128 additional GPIO pins for our projects!!!!

The MCP23017 chip has a set of registers that need to be written, to control the chip’s behaviour. For example the IODIRA & IODIRB registers determine whether the pins on ports A & B respectively are inputs(1) or outputs (0). To read the status of an input pin on port A or B, you need to read the GPIOA or GPIOB registers, and to set an output pin on port A or B to either a high or a low state, you need to write the appropriate value into OLATA or OLATB registers. The chip has many more registers with additional functionality like reversing polarity, enabling internal pull-ups and enabling interrupts.

Finally the IOCON register is also worth noting since it has some special configuration abilities including shuffling around the register addresses  by setting the BANK (bit 7) bit.

At this point I highly encourage the reader to take a good look at the MCP23017 datasheet. The default memory map of the device is shown in Figure 2.

    Figure 2. Default memory map of the MCP23017. BANK Bit of the IOCON register is 0, which is the reset state of this bit.

Figure 2. Default memory map of the MCP23017. BANK Bit of the IOCON register is 0, which is the reset state of this bit.

In order to communicate with this chip, the Raspberry Pi has to send the following data sequences…

To write to a register, the Raspberry Pi must;

  • first write a byte containing 7bit device address (bits 7 downto 1)  and assert the R/W bit (0th bit) for write,
  • write a second byte containing the register address that we want to write to and finally
  • write a third byte containing the data to be written into that register.

To read from a register, the Raspberry Pi must

  • first write a byte containing 7bit device address and assert the R/W bit for write,
  • write a second byte containing the register address that we want to read from.
  • Then send a repeated start condition with a byte containing 7bit device address  again  but this time assert the R/W bit  for read.
  • The data is then shifted out of the slave and into the master.

Notice how for the read we had to resend the device address with the R/W bit asserted for read. This is depicted in Figure 3 taken from Figure 1-1 in the MCP23017 datasheet.

Figure 3.

Figure 3.

Mass writes and reads of all registers on the device are also possible. Please refer to the datasheet for more information


Connecting the Raspberry Pi to the MCP23017 chip

The MCP23017 was connected to the Raspberry Pi as shown in the Fritzing diagram shown in Figure 4. The A2,A1,A0 pins are all connected to ground meaning that our 7-bit device address is really 0b0100000 or 0x20. The active low RESET pin resets the chip whenever it’s set to 0V this is why we decided to set it to VDD (to disable the RESET pin) . The chip’s power pins are connected to the power rails. To making the diagram easier to interpret, I used blue wires for all ground (VSS) wires and red ones for all power  (VDD) wires. The SDA and SCL pins on the Raspberry Pi are connected to the pins with the same names on the MCP23017. Note that the I2C specification requires that these pins have pull-up resistors on them. These resistors are already on the Raspberry Pi so there’s no need to provide them ourselves. Finally I have an LED connected to pin GPA0 through a 470 Ohm resistor in a sourcing configuration (LED Cathode connected to ground) and  a pull-up resistor and push-button to pin GPA7.

Figure 4. Fritzing Diagram illustrating how to hook up the MCP23017 to the Raspberry Pi, an LED and a pushbutton (demo hookup)

Figure 4. Fritzing Diagram illustrating how to hook up the MCP23017 to the Raspberry Pi, an LED and a pushbutton (demo hookup)

The goal of the demo code will be to set GPA7 to an input pin and  GPA0 to an output pin. Then read the state of the GPA7 input pin. If it is in a low state (i.e. the  pushbutton is pressed), then toggle the GPA0 output pin and in turn the LED.

Enabling the I2C device on the Raspberry Pi

If you are using the Raspbian OS on the Raspberry PI, the I2C devices are disabled by default. To enable them, I followed the instructions in this document which can be found along with some examples on this site.The instructions are as follows:

  • SSH into your Raspberry Pi
  • Open the raspi-black-list.conf  file using the following command : “sudo nano /etc/modprobe.d/raspi-blacklist.conf”
  • Comment out the “blacklist i2c-bcm2708” entry by putting a hash # sign in front of it. So it looks like “#blacklist i2c-bcm2708
    • At this point you can also enable SPI device access by putting a hash # sign in front of “blacklist spi-bcm2708“.
  • You then need to save your changes (Ctrl-x in Nano) and reboot using the “sudo reboot” command.
  • Now every time you login you will still need to perform two things to enable I2C:
    • Type “sudo modprobe i2c-dev” and
    • Type “sudo chmod o+rw /dev/i2c*


Alternatively you could write the last two commands above into the “/etc/rc.local” file so that the Raspberry Pi’s two I2C devices are enabled automatically on startup.

Changing the Speed of the I2C bus

To change the speed of the I2C bus you can type in the command line:”sudo modprobe -r i2c_bcm2708 && sudo modprobe i2c_bcm2708 baudrate=400000

The first part of the command (before the &&) removes the I2C driver. The second part of the command reloads the I2C driver  with the new baud rate specified in Hz as shown above. This change can be verified with  dmesg i.e.  “dmesg | grep i2c“. I was able to use this to change the I2C bus speed to 400KHz. You should be able to modify the bus speed to other values as well.

Testing the I2C device from the command line

Once this is completed typing the following command “$ls /dev/i2c*“. This should reveal that two i2c devices are available; “/dev/i2c-0″ and “/dev/i2c-1″. If you have a rev1 Raspberry Pi board then the i2c device on Jumper 1 (the 26 pin header) is “/dev/i2c-0″. If you have a rev2 Raspberry Pi board then the i2c device on jumper 1 is “/dev/i2c-1″. Since I  have a rev2 board I will be primarily playing around with the “/dev/i2c-1″ I2C device.  For more information on the differences between the rev1 & rev 2 boards please check this link and this link . The second link will also help you determine if you have a rev1 or rev2 board if you do not already know.

I highly recommend that you download the i2c-tools package using the following command: “sudo apt-get install i2c-tools”

Once downloaded, make sure that the MCP23017 is  connected to the  Raspberry Pi as shown in Figure 4. Make sure that the A2, A1 and A0 pins are all grounded, making the the 7-bit device address of the MCP23017 “0100000” in binary (which is equivalent of 0x20 hex). Now run the following two commands in sequence:   “i2cdetect -y 0″ followed by “i2cdetect -y 1″. Assuming that all of your connections are correct, the output should look something like what is shown in Figure 5.

Figure 5. output of the i2cdetect command.

Figure 5. output of the i2cdetect command.

The i2cdetect command basically probes the i2c bus of an i2c device (0 for “/dev/i2c-0” and 1 for “/dev/i2c-1“) and returns a listed of device addresses that it was able to find on that bus. From the output in Figure 5, one can determine that the “/dev/i2c-1” device has on its bus an i2c slave whose address happens to be 20 in hexadecimal. This is good news because it means that the MCP23017 chip was detected!!! If you have a rev1 Raspberry Pi the MCP23017 chip would be detected on the i2c-0 device instead.

One last thing before we get to the coding part of the entry! lets toggle the led from the command line! To do this, we will use the “i2cset” command. The “i2cset” command requires the “-y” parameter so that it does not prompt us with a “are you sure you want to continue?” message. The next parameter is the i2cbus/device in use. In our case that is “/dev/i2c-1“, which is denoted simply with 1. The next parameter is the MCP23017’s device address which is 0x20. The next two parameters represent the register address and the data to be written into that register address

To set pin GPA0 (LED) to output and then toggle it, type the following in the command line:

  • i2cset -y 1 0x20 0x00 0x00 #set port A (GPA0-7 pins) as outputs
  • i2cset -y 1 0x20 0x14 0x01 #set GPA0 pin to output high (LED ON)
  • i2cset -y 1 0x20 0x14 0x00 #set GPA0 pin to output low  (LED OFF)

The LED is connected to pin GPA0. To set the pin to an output we need to write “0” to the IODIRA (address 00 in hex) register. This effectively sets all pins on port A  (GPA0-7) to outputs. We can then toggle the LED by setting the GPA0 pin to an output high by writing a “1” to the OLATA register (address  14 hex) followed by writing “0” to the OLATA register. The same effect can be achieved by writing to the GPIOA register (Address 12 hex).

Congratulations!!! you have just controlled the MCP23017 from your Raspberry Pi over the command line!!! Let’s see if we can do the same thing from a C++ user-space program.

C++ Program to control the MCP23017 from the Raspberry PI

In this section I’ll introduce some basic “in progress” C++ code that will help us access the I2C bus. I first created a class called “i2c8Bit” which ideally is capable of communicating with any i2c device that has a series of byte wide registers such as the MCP23017 chip. The class definition is shown below:

#ifndef I2C8BIT_H
#define I2C8BIT_H

#include <string>
#include <stdio.h>
#include <linux/i2c.h> 
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>

class i2c8Bit 
		i2c8Bit(void); // default constructor
		i2c8Bit(unsigned char dev_addr, std::string i2cfilename); 
		//over loaded constructor
		~i2c8Bit(void); // destructor
		int writeReg(unsigned char reg_addr, unsigned char data);
                // function to write byte data into a register of an I2C device 
		int readReg(unsigned char reg_addr, unsigned char &data);
                // function to read byte data from a register of an I2C device

		//private member functions
		int openI2C(); //open an I2C device. Called only in constructors
		int closeI2C(); // close an I2C device. Called only in destructor

		// private member variables
		std::string  i2cFileName; //i2c device name e.g."/dev/i2c-0" or "/dev/i2c-1"
                int i2cDescriptor;  // i2c device descriptor 
		unsigned char deviceAddress; // i2c device address


The class consists of a default constructor, an overloaded constructor that gives the programmer more control over the “i2c8Bit” object and member functions for reading and writing byte wide data into the registers of the I2C device. The class also consists of “openI2C()” and “closeI2C()” member functions. These functions are solely responsible for the opening and closing of the I2C device in use. Notice how they are labelled private since they are only used in the constructors and destructor respectively.

For variables we have a variable to store the I2C device name, either  “/dev/i2c-0″ or /dev/i2c-1″. We also have a variable to store the I2C device descriptor that references an open device and is typically returned by the “open()” system call. Finally, we have a variable for storing the device address which in our case we know is 0x20.

The member function implementation is provided below:

#include "i2c8Bit.h"
#include <iostream>

using namespace std;
 * This is the default constructor for the class. It assigns
 * all private variables to default values and calls the openI2C()
 * function to open the default I2C device "/dev/i2c-0". 
	this->i2cFileName = "/dev/i2c-0"; 
	this->deviceAddress= 0;
        this->i2cDescriptor = -1;
        cout << " Opening I2C Device" << endl;


 * This is the overloaded constructor. It allows the programmer to 
 * specify a custom I2C device & device address
 * The device descriptor is determined by the openI2C() private member 
 * function call.
 * *****************************************************************/

i2c8Bit::i2c8Bit(unsigned char dev_addr, std::string i2c_file_name){
	this->i2cFileName = i2c_file_name;
	this->deviceAddress = dev_addr;
        this->i2cDescriptor = -1; 
        cout << " Opening I2C Device" << endl;
 * This is the class destructor it simply closes the open I2C device
 * by calling the closeI2C() which in turn calls the close() system call
 * *********************************************************************/ 

        cout << " Closing I2C Device" << endl;

 * This function opens the I2C device by simply calling the open system
 * call on the I2C device specified in the i2cFileName string. The I2C
 * device is opened for writing and reading. The i2cDescriptor private 
 * variable is set by the return value of the open() system call.
 * This variable will be used to reference the opened I2C device by the 
 * ioctl() & close() system calls.
 * ********************************************************************/  

int i2c8Bit::openI2C(){
	this->i2cDescriptor = open(i2cFileName.c_str(), O_RDWR);
	if(this->i2cDescriptor < 0){
		perror("Could not open file (1)");

	return i2cDescriptor;

 * This function closes the I2C device by calling the close() system call 
 * on the I2C device descriptor.	
 * *******************************************************************/

int i2c8Bit::closeI2C(){
                int retVal = -1;
		retVal = close(this->i2cDescriptor);
	if(retVal < 0){
		perror("Could not close file (1)");
return retVal;
 *This function writes a byte of data "data" to a specific register 
 *"reg_addr" in the I2C device This involves sending these two bytes 
 *in order to the i2C device by means of the ioctl() command. Since  
 *both bytes are written (no read/write switch), both pieces
 *of information can be sent in a single message (i2c_msg structure)
int i2c8Bit::writeReg(unsigned char reg_addr, unsigned char data){

	unsigned char buff[2];
	int retVal = -1;
	struct i2c_rdwr_ioctl_data packets;
	struct i2c_msg messages[1];

	buff[0] = reg_addr;
	buff[1] = data;

	messages[0].addr = deviceAddress;
	messages[0].flags = 0;
	messages[0].len = sizeof(buff);
	messages[0].buf = buff;

	packets.msgs = messages;
	packets.nmsgs = 1;

	retVal = ioctl(this->i2cDescriptor, I2C_RDWR, &packets);
	if(retVal < 0)
		perror("Write to I2C Device failed");

	return retVal;

 *This function reads a byte of data "data" from a specific register 
 *"reg_addr" in the I2C device. This involves sending the register address 
 *byte "reg_Addr" with "write" asserted and then instructing the 
 *I2C device to read a byte of data from that address ("read asserted"). 
 *This necessitates the use of two i2c_msg structs. One for the register 
 *address write and another for the read from the I2C device i.e. 
 *I2C_M_RD flag is set. The read data is then saved into the reference
 *variable "data". 

int i2c8Bit::readReg(unsigned char reg_addr, unsigned char &data){

    unsigned char *inbuff, outbuff;
	int retVal = -1;
	struct i2c_rdwr_ioctl_data packets;
	struct i2c_msg messages[2];

	outbuff = reg_addr;
	messages[0].addr = deviceAddress;
	messages[0].flags= 0;
	messages[0].len = sizeof(outbuff);
	messages[0].buf = &outbuff;

	inbuff = &data;
	messages[1].addr = deviceAddress;
	messages[1].flags = I2C_M_RD;
	messages[1].len = sizeof(*inbuff);
	messages[1].buf = inbuff;

	packets.msgs = messages;
	packets.nmsgs = 2;

	retVal = ioctl(this->i2cDescriptor, I2C_RDWR, &packets);
	if(retVal < 0)
		perror("Read from I2C Device failed");

	return retVal;

Basically both constructors set the i2C device name, device address and then call the “openI2C()” method which in turn calls the “open()” system call to open the I2C device in question and return a file descriptor (saved in the private variable “i2cDescriptor”). Which  can then be used  to reference the opened I2C device in future operations.

Similarly, the destructor calls the “closeI2C()” method which in turn calls the “close()” system call with the appropriate file descriptor to close the device just before the i2c8Bit object is destroyed due to program exit e.t.c.

The two functions that do all the heavy lifting are the “readReg()” and “writeReg()” functions. Let’s take a closer look at the “writeReg()” function.

The “writeReg()” function takes two parameters; “reg_addr” and “data” which respectively are the register address that we want to right to and the data that we want to write into that register. To send this data to the i2c device we need to call the ioctl() system call with a structure of type “i2c_rdwr_ioctl_data”. This structure in turn  contains one or more “i2c_msg” structures. Each “i2c_msg” structure in turn contains an “addr” field denoting the device address, a “flag” field which is typically set to 0 for default writes, a “len” field containing the number of bytes that will be written and a “buf” field which is a pointer pointing to the buffer used for transferring data .

In this case, we fill the “addr” field with the device address, the “flag” field is zero, the “buf” pointer points to an array of two bytes “buff” containing the register address in the first byte and data to be written into that address in the second byte. and the “len” field is basically 2…alternatively we can use the sizeof() command on our “buff” array as well. We then package this “i2c_msg” structure into the “i2c_rdwr_ioctl_data” structure and pass it into the “ioctl()” system call along with the I2C device descriptor and the “I2C_RDWR” command.

For “readReg()”, we basically do the same thing, but instead of having a single “i2c_msg” structure containing an array of two bytes to be written to the I2C device, we have two “i2c_msg” structures. The first only contains the register address and is setup for a write (“flag” field is zero), while the second i2c_msg structure is setup to read data from the register address specified in the first byte(“flag” field is “I2C_M_RD”). The data received from the I2C device is then read into the “data” variable via the “inbuff” pointer. Since the “data” variable is passed by reference, its directly updated in the function/scope in which “readReg()” is called.

Now let’s take a look at the main function:

 * Main function that demos the use of the i2c8Bit class for use with
 * the MCP23017 chip
 * *******************************************************************/

#include "i2c8Bit.h"
#include <iostream>
using namespace std;

int main(void)
	int i = 0;
	unsigned char pinStatus = 0;
	unsigned char outToggleState = 0;
	i2c8Bit mcp23017(0b00100000,string("/dev/i2c-1"));
	//instantiate i2c8Bit object called mcp23017
	//specify a device address 0x20 and i2c device "/dev/i2c-1" 
        // write data value 0b11111110 into register 00 (IODIRA) 
        // makes GPA0 output, rest of the pins  (GPA1-7) inputs

	while(i < 20) // repeat the following 20 times and then exit;
		//read the GPIOA register

		if( (pinStatus & 0b10000000) == 0){ 
		// test to see if pin GPA7 is equal to zero
		// i.e. if pushbutton is pressed
			cout << "PushButton pressed..toggling LED" << endl;
			outToggleState ^= 1;
                        //update variable to cause LED to toggle
			cout << "Pushbutton not pressed...LED static" << endl;

                //write the value of outToggleState to the OLATA register
                cout << i << "LED state is: "<< (int)outToggleState << endl;
	cout << "exiting" << endl;
//destructor called just before program exit
return 0;			

We basically instantiate an i2c8Bit object called “mcp23017″ with a device address of 0x20 and i2c device name “/dev/i2c-1″. We then set the GPA0 pin to an output and GPA1-7 pins to inputs by writing 0b11111110 to the IODIRA (hex address 00) register.

In the while loop, we read the value of the pins from register GPIOA (hex address 12). We AND the result with a mask that determines if input pin GPA7 is either high or low. If that input pin is in a low state, that implies that the push button (See fig 4.) is pressed. If that is the case,  we then toggle the outToggleState variable and write it to the OLATA (hex address 14) register to cause pin GPA0, and ultimately the LED, to toggle.

Decoding much of the code for the “readReg()” and “writeReg()” functions was the toughest part for me. I basically followed this example, this example and this example. I also found the following link to be very helpful.

All the code discussed in this blog entry can be downloaded from (git) here.

All the code was developed and built natively on the Raspberry Pi using native GNU G++ and the VIM editor. The command I used to compile the code was “g++ -Wall -o mcp23017test i2c8Bit.cpp mcp23017test.cpp” . To run the generated binary, I used “./mcp23017test“.

The test program worked successfully with no problems. The i2c8Bit Class can easily be expanded to create an much more encompassing C++ class for communicating with the MCP23017 chip as well as other I2C chips. The code provided here is only for demonstration purposes. Feel free to use it as you wish (but at your own risk!!).

My only concern with this setup is that the MCP23017 is I2C based, which as communications standards go, is relatively slow. This makes simulating Pulse Width Modulation(PWM) in software inconvenient (if not impossible ..I haven’t tried it myself) to do with the chip. The slow bus speed would limit the PWM frequency and probably introduce a level of jitter.

The MCP23S17 chip is an SPI variant of this chip. The SPI version of the chip can operate at much faster bitrates (up to 10MBps) and I believe the SPI sysfs interface allows for varying the bit rate. This would make things like simulating PWM in software more feasible at higher frequencies. I hope to look at this chip in more detail in the future. Of course if you are only interested in doing PWM, you could look at hardware PWM chips such as the PCA9685 chip. A breakout board for this chip, is available from Adafruit.

I would like to thank the developers of Fritzing for their great tool. I would also like to thank Adafruit for making their Fritzing library of parts (which includes a Raspberry Pi) freely available.

UPDATE: For more information on programming I2C devices in C/C++, please check the following links:






This entry was posted in I2C, Raspberry Pi, Raspberry Pi Peripherals. Bookmark the permalink.