I2C Expansion for Pi and ESP8266

Pi ExpansionWant 64 GPIO pins on your ESP8266 or Raspberry Pi? Read on.

If like me you are not THAT familiar with I2c, you might find the results of my  experiments interesting and perhaps even useful.

If like me you are not THAT familiar with I2c, you might find the results of my  experiments interesting and perhaps even useful.

PCF8574TSo I bought a couple of these i2c port expanders from China – mainly because I wanted something simple to mess with i2c on the ESP8266. It occurred to me that if I could get these working on a Pi, so I was sure of the addresses and commands etc., then on the ESP, I’d then get the confidence to do something more dramatic on the ESP8266 with i2c.

So ignoring for a minute the interrupt capability, these are pretty basic devices – using up 2 port bits (SDA and SCL) on your Pi or ESP, send an i2c start to them followed by an address then a byte to them – and the outputs light up accordingly. Set the outputs to 255 and read back a byte and you get the state of the pins as inputs. It doesn’t get any easier.

Well, not unless you completely mis-interpret the addressing as I did and spend ages chattering away to the wrong address. Anyway, let’s not dwell on that.

I noted that the outputs are HIGH by default.  Also note that in my experiments I have set the 3 DIP switches to ON (NOT as in the photo above).

Armed with the latest version of Raspbian Jessie on a pi2 or Pi3, connect ground on the device to ground on the Pi, VCC to 3v3 on the Pi, SDA to SDA (blue) on the Pi, SCL to SCL on the Pi. Simples. The boards have built-in pull-up resistors for i2c (which could pose an issue if you parallel a bunch of them up of course) so that’s it – no other new components needed other than a LED for testing. I used a 470r resistor in series with the LED.

Open a terminal on the Pi and type:

sudo pigpiod

That starts the new GPIO library daemon running in the background.

Now here is a short file that will set all the outputs to 0 – running Python…

import pigpio

pi1 = pigpio.pi()
pi1.write(18, 0)


Nice but then my pal Peter Oakes pointed out to me that I’d end up loading the entire Python environment  every time I wanted to change an output.. so I started experimenting with C code… just turning GPIO18 (on the Pi) on and off for starters…. see the line that says gpioWrite(18,0);  that turns the port off. Substitute a “1” to get the opposite effect.   All of this worked a treat.. “b” here ends up containing a handle.

#include <pigpio.h>
#include <stdio.h>

void main()
if (gpioInitialise() < 0)
puts(“erm no”); // pigpio initialisation failed.
gpioSetMode(18, PI_OUTPUT);
// pigpio initialised okay.
gpioWrite(18, 0);

The code above once compiled failed the first time – I realised you must NOT have the daemon running when using this. so a quick reboot later and I was in business.

Oh, here’s how to compile a simple C program like that – make sure it’s a text file, say in your /home/pi directory.

gcc -Wall -pthread -o prog prog.c -lpigpiod_if2 –lrt

See where it says “prog” – change that to the name of your program. Takes seconds.

Anyway, I was just about to set everything up in C for i2c etc. when I discovered… PIGS

sudo pigpiod
pigs w 18 0
pigs w 18 1

Note – no sudo needed for the commands and presumably one would run that daemon (pigpiod) at startup. This looked like a nice simple route – dead easy for Node-Red as you can just issue the commands in an EXEC function and pass the parameters in the payload – so next would be to try i2c….

pigs i2co 1 39 0
pigs i2cwb 0 0 0
pigs i2cwb 0 0 255
pigs i2cwb 0 0 1
pigs i2cwb 0 0 2
pigs i2cc 0

The first command visually returned 0 – hence my use of 0 later in the code as the “handle”. I order, I set the expander to all off, all on, then the first bit only on – then the second bit only on and finally I closed the handle.

Something to note is that I2c lines need pull-up resistors – and this board has them already built in – unfortunately they are 1k pullups – fine if you only have one board, not a lot of use if you want to put several in parallel. After discussion we think that possibly the two relevant resistors might be replaced by 10k in which case you could then run several in parallel (with different addresses) but we’ve not tested that.

Oh, making that daemon permanent… I did that with a command line edit “sudo nano /etc/rc.local” –  and added the line “sudo /usr/bin/pigpiod” – and rebooted…. no problem.

Update November 9, 2016

The final stage of this experiment gives my ESP8266 software the ability to achieve the same thing, losing 2 wires to get 64 new ones (YES, 64), a net benefit of 62 I/O lines, could be worthwhile as the ESP8266 isn’t exactly brimming with IO lines.


Above you see (blue) our ESP-12 board, fastened to an FTDI for power, and wired by jumper to one of the PCF8574T boards – clearly you’d need 8 of them to get 64 lines and I’d be wary as they have pullup resistors on the data lines. I’d remove them on all but one.

With a typical Chinese PCF8574T board which includes pullups, I’ve added new commands to the ESP8266 Home Control software as of my software version 1.6.52 – the xport command.



sets the lowest bit of the first (address 39) expander high (the 0 is a mandatory argument above – see future blogs) whereas:


returns the state of the first (LSB) bit of the first of up to 64 bits.

On power up these devices are HIGH – and the software defaults to high on power up. If you mess with a port bit, you need to set a bit high before you can use it as an input. Here is the datasheet for this chip – and here is a typical Chinese expansion board.  With GPIO4 on our little boards hooked to SCL and GPIO5 hooked to SDA – the new commands work a treat.

In the above photo – address 39 equates to all DIP switches set to ON (that’s high or 7). If you set number 3 to off – that is address 38 (bits 8-15) etc. (simple binary selections – you can make the device work as anything from 32 (all switches OFF) to 39 (all switches ON) but before you go connecting eight of them up – bear in mind the comments about pull-ups above.

I’ve been doing a little more on these as you’ll see in other parts of the blog – but the upshot is – you have to ask yourself if these are worth the money. In my original blog I pointed to an Ebay price of £2.35  – but in fact from AliExpress they are only £1.20 and so I’ve amended the link in the blog accordingly.  However as you’ll see in other blog items – as I’ve learned I’ve realised they are not necessarily the best bet. I’ve now made a simple “Nano i2c peripheral” from a Nano board – and they cost just a few pence more – but you can make  NOT only an 8-bit expander but also get some A/D, some PWM and some A/D thrown in – hell I’m even putting an LCD display driver in just for the sake of it – and I’ll call it the kitchen sink peripheral.

However if you do like the look of these chips, you’ll note they say they work on 100Khz I2c. That of course is true and I’ve not experimented with anything other than close up – lets say less than 250mm away – but I’m currently running them a HELL of a lot faster than that. I’ve only speeded up the clock for writes and reads – note the wide bits around the edges but still – quite nippy.

faster I2c

Hope you found the above useful. For more information on the ESP software – go to the relevant page on the blog. There is of course the main Home Control 2016 page.


50 thoughts on “I2C Expansion for Pi and ESP8266

  1. Here is a small fix to allow support for both the PCF8574 and PCF8574A giving up to 128 io with 8 of each chip.

    else if (os_strcmp(token, “xport”) == 0) {
    if (arg1=2) { morePorts(39-(arg1/8),arg1/8,(arg1&7),arg2); }
    else if (arg1=2) { morePorts(63-(arg1/8),arg1/8,(arg1&7),arg2); }

    1. Before I make any changes – this is what I have.

      else if (os_strcmp(token, “xport”) == 0) {
      if (arg1<64)

      if (argCount==1) { isQuery=1; os_sprintf(strValue, "%d", moreInPorts(39-(arg1/8),arg1/8,(arg1&7))); }
      if (argCount>=2) { morePorts(39-(arg1/8),arg1/8,(arg1&7),arg2); }

      Your code is very different to that and at first glance… “if arg1=2” is definitely not right. Want to re-print what you wanted the revised version to be?

      1. In the first comment the site has taken the section between the greater than and less than symbols and tried to use it as formatting or something. Do I have to escape them for them to show up correctly?
        The pastebin link has the whole code snippet without the site trying to use parts of it for formatting

  2. The STM32F103C8T6 and the Bulepill board have 2x physical I2C interfaces that are completely independent., check pinout diagram: http://reblag.dk/wordpress/wp-content/uploads/2016/07/The-Generic-STM32F103-Pinout-Diagram.pdf
    First is on PB6-PB7 (it looks like it can be muxed to PB8-PB9 but I can’t figure out how), and second is on PB10-PB11, so these can run in different modes if required.
    There is a hardware bug that concerns I2C on this deivce, but both master and slave modes are concerned, it is detailed in this document, section 2.13: http://www.st.com/content/ccc/resource/technical/document/errata_sheet/7d/02/75/64/17/fc/4d/fd/CD00190234.pdf/files/CD00190234.pdf/jcr:content/translations/en.CD00190234.pdf
    Basically, you need to make sure you won’t get interrupted in certain condtions, like between sending STOP and reading data n-1, but there are workarournds and all in all, it is working fine:

  3. Michel – ran out of room to go further into that discussion further up – 1-Wire – well, at one point everyone said I2c master worked but not slave – if anyone cares to try this – happy to revisit once someone confirms the board can do I2c slave and I2c master at the same time – on different wires..

    1. Not on mine – and note – I keep an 8 byte buffer for the status of the various bits (up to 64)- if you use the I2c command this is NOT updated…. just so you’re aware… so if you’re going to use XPORT – don’t use I2c… when you do an XPORT input – it SHOULD update the buffers…

  4. Please note readers, I’ve just updated this blog entry about I2c in line with a new software update …

    1. Hi
      I have just loaded a board with Software version 1.6.52. And i’m having a play around with the new xport command. {i2c_check} sees the port expander now!
      sets the lowest bit of the first (address 39) expander high (the 0 is a mandatory argument above – see future blogs) whereas:
      returns the state of the first (LSB) bit of the first of up to 64 bits.
      I don’t understand the format the “zero is mandatory”.
      i send {xport:0,0} which switches relay#0 on (my relay board has reversed logic).
      i send {xport:1,0} which switches relay#1 on, but relay#0 goes off. Only one relay can be on at a time? am i getting the format correct?
      Many thanks

  5. I’d like to get 4 or 5 of the 128×64 OLED 4-pin I2C displays on my Pi, but the displays only have 2 possible addresses. Do you think one of these I2C expansion solutions would allow me to add more than 2 OLED I2C 4-pin displays on a Pi? I’m sorry for asking such a basic question …

    1. What I think of in I2c expansion, rightly or wrongly – is port expansion. Then using those ports for software I2c would dramatically slow things down. Of course you could have a Pi talk to ESP8266 or whatever – very cheap – and have as many as you wanted.

        1. Using the address selection input of your I2C devices as exclusive chip select like, you can share the I2C bus among numerous devices at the same address, at the price of additional wiring and using the same software libraries as you normally use for a single device, only requiring a thin wrapper in the form of a GPIO toggling above the librariy layer.

          Using the I2C multiplexer, you can also get many I2C devices with the same address running, but this time, you need the MUX, but no additional wiring. Software-wise, you also need a lyaer for switching devices, addressing the I2C MUX chip.

          A good usage is for 9 DoF IMUs (MPU_9250 chips) having only 2 possible selectable I2C addresses, when you want to use them on a body-worn application with one device stuck at each member joint…

  6. Peter, when you said died easy for node-red. Does node-red do bash commands? As i use a script at home to turn on and off my kambrook wall switchs. Only problem is i have to put my password in to get it to work everytime. Good at electronics not great at programming.

    1. Node-Red is a visual system using NodeJS as the base. You don’t use scripts. You use visual modules and on of those is a function module. You should take a look at the Node-Red site for more info.

  7. “Anyone know what “limited Arduino support” means?”

    Pete, with limited I mean STM is not an 8 bit 328 processor. I guess, some libraries that use processor specific registers for the Atmega 328 will not work with STM Arm 32bit M3 processor. Apart from that most of the general Arduino stuff should work OK.

    Have a look at the very good dedicated STM Arduino forum. Roger has done great work in porting Arduino to STM. Again, main supported STM processor is M3 103

    link to STM Arduino forum.


    1. Magic – well nothing I can do now until the boards turn up – I would MUCH rather have a 26 or 32 bit processor than the old 8 bit ones but it is really nice when libraries work – who wants to write libraries for the many displays out there… 2 weeks or so I can get back into this when the boards turn up – the price is astounding.

    2. Pete,
      I ordered 4x of the STM32F103C8T6 “Blue Pill” board at the shop above and received them, they are working fine with the Arduino IDE, at least at the same level of compatibility as the ESP8266.
      I also have a handful of ATMega328 3.3V/5V as general purpose expansion boards. It is a matter of low-level ATMega dependency in the used libraries to use these or the STM32 ones.
      Of course, the STM32 boards are much more powerful and features more I/O lines and more memory, too.
      Be warned that the STM32 boards suffer from a hardware bug in that the USB pull-up resistor does not have the correct value. You can replace it or put a resistor in parallel to fix the problem, though.

        1. STM32 boards – I gave up also – I don’t want to re-invent the wheel and it seems that either the I2c hardware does not work or people cannot get the software to help it. I could of course go down the software route but if doing that you may as well use an Arduino. Just as cheap.

          1. As I said, old 8-bit full Arduino compatibility vs. partial Arduino compatibility and modern 32-bit CPU with tons of peripherals at the same price, you have the choice. I may try to tinker with this board further, looks promising.

            1. Yes that is using the hardware I2c library to talk to the display. That is not the issue- apparently the problem is a SLAVE i2c library – reading around the web the only way to do it is software bit-banging which would be slow.. I’d hoped there would be a standard hardware-supported slave library (to talk to the STM). But apparently not.

                1. That would be nice – it would need to handle the two – ie you might send an I2c command to the board – and have it use I2c peripherals….

                  That and the use of the serial port, PWM and the parallel ports and some drivers for various temperature sensors etc could make that a REALLY useful little board.

                    1. Everything one could wish for – except… for hardware I2c slave which could make the board into a cheap peripheral. Back to square 1 🙂

  8. There’s a comment in here from John.. but I can’t find it! Re: I2c etc – look at the later post – all sorted. Now I know how to get messages back and forth on I2c from ESP to Arduino – the sky is the limit.. Already done the Firmata on Node-Red and the is now fixed – had some connection issues but it is very limited in what it can do – this way, it’s all down to what one cares to program into the Arduino. I can see lots of cheap peripherals coming on – maybe a purchase of some of those £1.32 Arduino micros….

    John pointed us to 2 links – the second one has my interest. Anyone know what “limited Arduino support” means?



  9. I have been using Arduino Min-Pro boards as cheap programmable peripherals and programming them as I2C slaves and even using Firmata sketches on them to control I/O pins remotely. There are versions of Python libraries (PyFirmata, haven’t tested on Pi)) that should run on Pi, load Firmata standard on Arduino (in Arduino standard Examples), connect via Uart. You can then directly control I/O pins on Arduino from Pi Python application. It is not for fast I/O control, but easy and convenient.

    Pro Minis are available in 5v – 16Mhz and 3.3v- 8Mhz. Cheaper now than many dedicated I/O chips. There are also more powerful STM32F103C boards under $2 with limited Arduino IDE support.




    1. This STM looks so good I’ve ordered two. PLEASE tell me there’s programming support in the Arduino IDE !?!?!

  10. Pete,

    Be careful with PCF8574T expanders, from memory they don’t have tri-state buffering and a default after power up is that pins are set for digital output logic HI.
    That initial state is not normally desirable.

    As Andrew has mentioned Microchip 23017 is friendlier, from memory pins are initially set as input or tri-state? Also is available as 28pin DIP.

    1. Thanks for that – the chip looks cheap enough – sadly at £1.90 it is fine – but they want £4.90 to ship it to Spain. For experimental uses I want something already on a cheap board – which is why the PCF8574T. I looked on AliExpress and while there are loads of variations on the chip – no-one seems to have it on a board??

  11. Nice post. It is also worth looking at the MicroChip MCP23017. This is an I2C part that provides 16 IO ports configurable as a mix of inputs and outputs. It has optional internal software controlled pin level pullup and can generate an interrupt on port change. A really useful. I use it with an ESP-07 as part of a DDS Frequency controller.

    1. Oh hell that’s even better…. BUT they’ve done the same as the others – 1k pullups – which means you’re expansion is limited unless you remove the resistors on all but one board. ALSO – on my boards there are pullups for the outputs… they are missing on the AliExpress one – I wonder if that’s an issue..

      DOES look like it is worth getting a couple…

Comments are closed.