I have a had a few requests on how to add more than one MCP23017 port expander to the Arduino via the i2c bus, this chip is a very useful and easy to use component that adds up to 16 digital I/O ports to the Arduino. This demonstration uses two MCP23017’s with three LEDs for output, one RGB LED to loop through a selection of colours, a single colour that blinks on and off, and another RGB LED that is controlled by a four button keypad. I have written the program using the millis() timer rather than the delay() function to maintain the illusion of multi-tasking.

Addressing the MCP23017
The port expander has a three pins, A0, A1, and A2 for which an address can be set, each MCP23017 on your i2c bus must be set to have its own address, this is a three bit address and up to eight expanders can be used. Although I suspect things may noticeably slow down as you add more expansion. There is an SPI version available, the MCP23S07, that may be better for use with larger setups.

The address connections are shown the chart below, where zero is a connection to ground and one is a connection to 5V (or 3.3 volts). The MCP Address column refers to the address used by the Adafruit driver as you will see later.
| chip address |
hardwired address | i2c address |
MCP address |
||
| A2 | A1 | A0 | |||
| 000 | GND | GND | GND | 0x20 | 0 |
| 001 | GND | GND | 5v | 0x21 | 1 |
| 010 | GND | 5v | GND | 0x22 | 2 |
| 011 | GND | 5v | 5v | 0x23 | 3 |
| 100 | 5v | GND | GND | 0x24 | 4 |
| 101 | 5v | GND | 5v | 0x25 | 5 |
| 110 | 5v | 5v | GND | 0x26 | 6 |
| 111 | 5v | 5v | 5v | 0x27 | 7 |
In my circuit I have assigned the first expander address 0x20 and the second 0x21.
Powering the Expanders
The port expander has been designed to run on a supply of 2.5v to 5.5v, so using a 5v supply from the USB port should be OK for a modest number of LED’s, you will need to calculate what your power requirements will be, approx 20mA per LED, so in this demonstration: 7 LED’s x 20mA = 140mA, plus whatever the chip itself is using. Bear in mind that each GPIO pin on the expander can only handle a maximum of current 25mA, and that the maximum total power dissipated must not exceed 700mW (after which point it’ll let the magic smoke out).
A Teensy 3.2 has a 3.3V supply but this is rated at 250mA maximum, the Ardunio UNO looks to be around 450mA on the 5V and only 50mA on the 3.3V output. For running from a battery I would look to use a 6v supply and a buck converter such as this Pololu step-down regulator.
The Hardware
On the left we see the Arduino UNO with the two i2c wires coming from pins A4 – SDA and A5 – SCL, two 4.7K ohm resistors are used for pullup, the port expanders are daisy chained along this bus, using pins 12 – SCL and 13 – SDA. See how the addresses are set on pins 15, 16 and 17, and note the 1K ohm resistor on the reset pin (18), without this resistor the circuit will work for a while then stop. The GPIO pins are connected as appropriate and the button switches do not need pull-up resistors as the port expanders internal pull-ups are turned on in the software.

Also if you are using RGB LED’s you will want to adjust the values I have given here, different colours have different power requirements so different resistor values are required to get a properly balanced colour LED. The RGB LEDs I have used are common anode, 5v is applied to the common and the path to ground to through the expander, this inverts the logic so setting the pin HIGH turns the LED off, and LOW turns it on.
The Software
The Adafruit MCP23017 library assigns each GPIO pin a number as you can see in the following diagram:

On both expanders the RGB LEDs are on ports 8,9 and 10 (to save me writing separate code for each RGB LED), the blink LED on port 7 of the first expander, and the buttons occupy ports 4, 5, 6, and 7 of the second. The first three buttons are used to toggle the red, green and blue in the second RGB LED while the fourth turns them all off. The other two LEDs are just doing things to show that they can do stuff.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
#include <Wire.h> #include <Adafruit_MCP23017.h> // https://github.com/adafruit/Adafruit-MCP23017-Arduino-Library // setup the port expander Adafruit_MCP23017 mcp0; // assign each expander its own mcp name Adafruit_MCP23017 mcp1; const unsigned int onTime = 300; const unsigned int offTime = 1000; unsigned long previousMillis = 0; unsigned long rgbPMillis = 0; const unsigned long rgbOnTime = 200; unsigned long tt = onTime; boolean LEDstate = true; const byte rgbLED[3] = {8, 9, 10}; const byte blinkLED = 7; int count=0; int lastCount=1; // using a structure to group together the button controls. struct MCP_BUTTONS { const byte buttns[4] = {4, 5, 6, 7}; // buttons are attached to these GPIO ports boolean btnState[4] = {false, false, false, false}; boolean btnPressed = false; byte btnKey = 0; }; MCP_BUTTONS btns; // http://forum.arduino.cc/index.php?topic=119261.0 void displayBinary(int count) { char binary[4] = {0}; count += 8; // fix the length to four bytes 3 + 8 = 11 = 0b1011 itoa(count,binary,2); // convert count to a base 2 char array char* string = binary + 1; // remove the most significant (first) bit = 0b011 for(byte i = 0; i < 3; i++) { mcp0.digitalWrite(rgbLED[i],string[i] - '0'); // - '0' converts the bit to HIGH or LOW } } boolean isButtonBeingPressed() { boolean b; // check to see that the button has been released before setting the settings if (btns.btnPressed == true && mcp1.digitalRead(btns.buttns[btns.btnKey]) == true) { btns.btnPressed = false; btns.btnState[btns.btnKey] = !(btns.btnState[btns.btnKey]); // toggle the logic return true; } // read the four buttons, see if one is being pressed for (byte i = 0; i < 4; i++){ b = mcp1.digitalRead(btns.buttns[i]); if (b == false) { btns.btnKey = i; btns.btnPressed = true; break; } } return false; } void doThingWithButton() { boolean b; // the fourth button switches all LEDs off and resets the button state if (btns.btnState[3] == true) { for (byte i = 0; i < 4; i++){ mcp1.digitalWrite(rgbLED[i],true); btns.btnState[i] = false; } return; } for (byte i = 0; i < 3; i++){ b = !(btns.btnState[i]); // invert the logic for common anode LED: HIGH = off, LOW = on. mcp1.digitalWrite(rgbLED[i],b); } } void mcpSetup() { // setup the RGB LEDs - the RGB LEDs are common anode, so the output needs to be high to turn them off. for (byte i = 0; i < 3; i++) { mcp0.pinMode(rgbLED[i], OUTPUT); mcp0.digitalWrite(rgbLED[i], HIGH); // turn LEDS off mcp1.pinMode(rgbLED[i], OUTPUT); mcp1.digitalWrite(rgbLED[i], HIGH); // turn LEDS off } // setup the buttons for (byte i = 0; i < 4; i++) { mcp1.pinMode(btns.buttns[i], INPUT); mcp1.pullUp(btns.buttns[i], HIGH); // activate internal pullup resistor } } void setup() { delay(1000); Serial.begin(9600); Wire.begin(); mcp0.begin(0); // 0 = i2c address 0x20 mcp1.begin(1); // 1 = i2c address 0x21 mcpSetup(); mcp0.pinMode(blinkLED, OUTPUT); // setup the blink led mcp0.digitalWrite(blinkLED, LOW); Serial.println("ready"); } void loop() { mcp0.digitalWrite(blinkLED, LEDstate); // only change the RGB colour when the counter has changed if (count != lastCount) { displayBinary(count); lastCount = count; } boolean btn = isButtonBeingPressed(); if (btn == true) { doThingWithButton(); } unsigned long currentMillis = millis(); // the blinking LED is lit for a sorter time than it is off // I forgot where I got this code from, but will update once I do. if ((currentMillis - previousMillis) >= tt) { if (LEDstate) { tt = offTime; } else { tt = onTime; } LEDstate = !(LEDstate); previousMillis = currentMillis; } // increment the counter every rgbOnTime millis, then reset it when all three bits are true (0b111) if ((currentMillis - rgbPMillis) >= rgbOnTime) { count++; if (count > 7) { count = 0; } rgbPMillis = currentMillis; } } |
Links
- Hobbytronics UK supplier
- Arduino Adafruit MCP23017 driver at GitHub
- MCP23017 datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/21952b.pdf
- LED Resistor Calculator with handy LED colour values guide