In this article, I’ll show you how to hook up and control a port expander chip with wiringpi2 for python. It’s really easy, and once set up (with about 3 lines of code) you can control your new ports just the same way as if they were on the Pi itself. This is, so far, my favourite new feature of WiringPi2 for Python – although there are some I have yet to play with.
This is part 3 of my wiringpi2 for python series. If you haven’t read parts 1 & 2 yet, I recommend you read those first.
What is a port expander?
You probably guessed from the name, but a port expander is a chip that gives you more GPIO ports. WiringPi2 has drivers for several port expander chips…
- MCP23017 – 16 ports i2c based
- MCP23S17 – 16 ports spi based
- MCP23008 – 8 ports i2c based
- MCP23S08 – 8 ports spi based
- 74×595 – 8 bit shift register
We’re going to focus on just one – MCP23017
They’re all similar in usage, so I’ll simplify things by only dealing with one. There’s not much price difference between the 8 port and 16 port expanders either, so unless you need a smaller chip, you might as well have 16 ports.
Although spi (10 MHz) is faster than i2c (1.7MHz), at the kind of speeds we’re interested in for general GPIO work, it doesn’t make much difference. The advantage of i2c is that you only need to hook up 2 wires to communicate with the Pi, whereas SPI uses 4.
The expanded GPIO ports are
Pins 21-28 GPA0-GPA7.
Pins 1-8 GPB0-GPB7.
Once you’ve set up the chip (I’ll show you how in a minute) you can use these 16 GPIO ports as either inputs or outputs.
Chip – Pi connection
VDD – 3V3 (P1 header pin 1 or 17)
VSS – GND (P1 header pin 6)
SCL – SCL on Pi (P1 header pin 5)
SDA – SDA on Pi (P1 header pin 3)
NC – Do Not Connect
RESET – 3V3 (P1 header pin 1 or 17)
INTA – Do Not Connect (I think these can be used in WP2, but haven’t played wth them)
INTB – Do Not Connect (I think these can be used in WP2, but haven’t played wth them)
Adding an MCP23017 gives us 16 additional GPIO ports to play with. You could “go to town” with this. But since my purpose here is to show you how to use wiringpi2 with the MCP23017 chip, I’m sticking to our simple “One input, one output” circuit, suitably modified for this chip. We’re using the chip’s internal pull-up. It has no pull-downs.
One input, one output code for MCP23017
Before you use this code, you’ll want to check that your Pi has i2c enabled, or it won’t work. You can find out how to do that here.
Once you’ve done that, you can test which i2c port your chip is set up as…
sudo i2cdetect -y 1 (if you have a rev 2 Pi)
sudo i2cdetect -y 0 (if you have a rev 1 Pi)
If you’ve done your wiring the same as in the diagram above (A0, A1, A2 all to GND) your result should be “20”. If it’s anything else, something is wrong.
Here’s the code to drive the MCP23017 with WiringPi2 for python…
import wiringpi2 as wiringpi from time import sleep pin_base = 65 # lowest available starting number is 65 i2c_addr = 0x20 # A0, A1, A2 pins all wired to GND wiringpi.wiringPiSetup() # initialise wiringpi wiringpi.mcp23017Setup(pin_base,i2c_addr) # set up the pins and i2c address wiringpi.pinMode(65, 1) # sets GPA0 to output wiringpi.digitalWrite(65, 0) # sets GPA0 to 0 (0V, off) wiringpi.pinMode(80, 0) # sets GPB7 to input wiringpi.pullUpDnControl(80, 2) # set internal pull-up # Note: MCP23017 has no internal pull-down, so I used pull-up and inverted # the button reading logic with a "not" try: while True: if not wiringpi.digitalRead(80): # inverted the logic as using pull-up wiringpi.digitalWrite(65, 1) # sets port GPA1 to 1 (3V3, on) else: wiringpi.digitalWrite(65, 0) # sets port GPA1 to 0 (0V, off) sleep(0.05) finally: wiringpi.digitalWrite(65, 0) # sets port GPA1 to 0 (0V, off) wiringpi.pinMode(65, 0) # sets GPIO GPA1 back to input Mode # GPB7 is already an input, so no need to change anything
Early on (lines 4-5) we define…
pin_base = 65
i2c_addr = 0x20
After setting up wiringpi (line 7) in its native pin mode…
…we initialise the MCP23017 chip (line 8) using the two variables we set earlier…
…if you wire all three address pins to GND, the i2c address is 0x20. (I explain how you can change this and use multiple MCP23017 chips, a bit further down.)
If we start with a pin_base of 65 (the lowest available number in wiringpi2) GPA0 is allocated to 65, GPA1 = 66…GPA7=72, GPB0=73, GPB1=74…GPB7=80
You can choose whatever pin_base number you like (above 64)
After that, we’re just setting up GPA0 as an output (line 10)…
wiringpi.pinMode(65, 1) # sets GPA0 to output
wiringpi.digitalWrite(65, 0) # sets GPA0 to 0 (0V, off)
…and GPB7 as an input (line 13) with pull-up enabled (line 14)…
wiringpi.pinMode(80, 0) # sets GPB7 to input
wiringpi.pullUpDnControl(80, 2) # set internal pull-up
The rest of the program is as it was before, except the logic is inverted with a
not (line 21), because the button is now “the other way round”. (Pressing the button connects it to GND, not 3V3, as we did before.)
You can use up to 8 of these MCP23017 chips
If 8 onboard GPIO ports + 16 extra ports is not enough, you can connect up to eight MCP23017 chips to your Pi using different i2c addresses.
There are three address pins on the MCP23017: A0, A1 and A2. They are used to determine the chip’s ID (000 to 111). Each needs to be wired to either 3V3 (1) or 0V (0).
To begin with, I suggest wiring all three pins to GND, for ID 000, which gives us 0x20 as i2c address.
If you want to use more than one of these chips, you can easily do that. Just wire each one’s address pins with a different address, and give it a pin_base value that doesn’t overlap with numbers already in use.
e.g. ID 000, i2c address 0x20, pin_base 65
WiringPi knows this chip has 16 ports, so will allocate WiringPi pins 65-80 to this chip when this command is issued. Our first MCP23017 chip was wired as 000 = 0x20 and we chose 65 as pin_base, so…
wiringpi.mcp23017Setup(pin_base,i2c_addr) …gave the Pi…
To add another MCP23017, connect Pin A0 of the second chip to 3V3 instead of GND.
Now, its ID is 001, i2c address is 0x21. pin_base 81 will allocate wiringpi pins 81-96 to the second chip.
Now we issue another setup command with the second pin base and i2c address in.
Our second chip is now ready to use on wiringpi pins 81 (GPA0) – 96 (GPB7).
Now you can control these pins just like any other WiringPi2 pin – exactly as if they were on the Pi itself.
In this way you can add up to 8 of these chips to give you a potential extra 128 ports. That should be enough for most people. If it’s not, you can have 8 of the SPI variant (MCP23S17) on each of the two SPI lines (CE0 and CE1) for another 256 ports. If you can’t do what you need to do with 384 ports, you might need a different device.
I haven’t gone quite that silly yet, but I have had 3 MCP23017s and an MCP23S17 running at the same time.
Watch out for your 3V3 lines
Expanders are fun, but one word of warning. The 3V3 lines on the Pi (including the GPIO ports) can only give out ~51mA safely. That’s the total maximum for all ports/pins.
If you want to connect up lots of leds and lots of expanders, you’ll need to power them from another source (e.g. the 5V lines or a battery) and use transistors or Darlington arrays to do the switching for you.
You can use 16 standard 5mm leds with 330R resistors as the resistors limit the current to about 3 mA. But you’d be well advised not to use lower value resistors if you want that many LEDs.
That’s all for today. There’s a lot of fun to be had with port expanders. Enjoy :)
What we’ve covered so far in the WiringPi-Python series…
- Raspberry Pi board revision checking with WiringPi2 for Python
- Using the Raspberry Pi’s internal pull-ups and pull-downs with WiringPi2 for Python
- Using hardware PWM with WiringPi2 for Python
There’s some parts of WiringPi2 that I haven’t yet explored. When I have, I’ll write part 4.
If you want to use the same MCP23017 port expander chip directly with i2c instead of WiringPi, Matt Hawkins has done a 3-part series on how to do that here.