
Here I will present a simple to use module to get additional I/O ports. One PCF8574 is controlled by only two signals, and gives us as many as 8 configurable I/Os together with an additional optional interrupt signal. What's more, two pins can drive as many as eight PCF8574s simultaneously, giving us a total of 64 pins, and combined with the alternate address version up to 128 pins! It is also worth knowing that nothing prevents other devices, such as sensors or there RTC clock, from being connected together with the PCF8574 on the I2C bus ... but one step at a time.
The PCF8574 is a so-called IO expander, i.e. a circuit which, when suitably controlled, allows us to obtain additional input/output pins in our project. Specifically, the PCF8574 is controlled via the I2C bus, which is a bus based on two signals (clock and data) and allows us to handle multiple devices simultaneously - multiple chips can be connected to a single I2C line, as long as their addresses do not collide.
The PCF operates at 2.5 V to 6 V, so there should be no problems running it with either the classic Arduino or the ESP8266.
Consider the leads of the PCF8574:

SDA and SCL are the mandatory I2C lines, this is what we control this chip with. P0-P7 are the legs of the circuit we control, they can be in the role of digital inputs and outputs. Pins A0, A1, A2 allow us to change the address of a given PCF piece, so we can connect more of them on one bus:

The PCF offers four GPIO modes of operation:

For more advanced users, it is also useful to provide a more detailed schematic, which also takes into account the line pull-up resistors from I2C:

Out of curiosity, I should add that the catalogue note also proposes a PCB layout for the expander:

Here, however, I will use a ready-made module, which will simplify everything considerably.
Module PCF8574 .
The commercially available PCF8574 module outputs all its legs on goldpins, which relieves us of the need to solder. All IO, interrupt output, address pins and the I2C bus are routed out - and on both sides. The aforementioned pull-up resistors are also on board.



You can get the module for a few zlotys. By importing from abroad you can even get 5 pieces for 10-20 PLN, depending on the promotion.


Demonstration platform .
As a rule, I've done this type of demonstration on an Arduino, but this time I was tempted to use a NodeMCU with an ESP8266. It will come out almost the same anyway, but in my opinion PlatformIO is a bit more convenient than the Arduino IDE, and knowledge of Visual Code is also useful for other languages. Recall the topic where I already presented PlatformIO:
Clock on ESP12 and MAX7219 display - tutorial - part 1, ArduinoOTA, basics .
How to program a Wemos D1 (ESP8266) board in the shape of an Arduino? ArduinoOTA in PlatformIO
Plate used:

I2C scan .
Using the expander is very straightforward, but users often get lost through the I2C addresses. For this reason I always suggest running an I2C scanner to start with. This simple program will verify our connections and display the I2C addresses of the devices connected to the bus. This will tell us if at least hardware-wise the situation is ok, as it is easy to swap SCL with SDA or connect something wrong....
Here is my connection:

Well, and the aforementioned scanner:
Code: C / C++
It works, the layout address is seen:

Now the same thing, but for five expanders with different addresses. The connection is trivial:

Result:
Scanning...
I2C device found at address 0x20
I2C device found at address 0x21
I2C device found at address 0x22
I2C device found at address 0x24
I2C device found at address 0x26
All expanders are seen.
Add the PCF8574 library .
I've discussed adding a library in PlatformIO before, but basically it's done via Libraries, just click through. Type PCF8574 in the search engine and add preferably the same result as in my screenshot (by Renzo Mischianti):

The search engine can also show beforehand a library from a 2x16 LCD controlled by the PCF8574, but we care about the PCF8574 control itself. We will run the LCD another time.
You can see examples of the library when adding it, but I've also put together some simplified demonstrations myself below.
Blink .
The library I propose (by Renzo Mischianti) is characterised by a syntax that is deceptively reminiscent of Arduino pins, i.e. we still have here our famous pinMode, digitalWrite and digitalRead, only that not as global functions, but on an object of the PCF8574 class. To instantiate the PCF8574 we use the constructor, which in my example takes the I2C address of the device and the SDA and SCL pins as arguments:
Code: C / C++
Running the PCF, it will tell us if we have connected it correctly and if the address matches:
Code: C / C++
Setting the mode of operation of the pin looks like I mentioned earlier:
Code: C / C++
Similarly setting the value on the output:
Code: C / C++
Full code:
Code: C / C++
Rather nothing to comment on here, the diode simply blinks:
Obviously the diode connected with a resistor - I gave about 300 ohms to avoid burning the diode.
Binary countdown .
Now let's flick all the LEDs. It's probably not necessary, but I was tempted to fire off some simple animation this way. Let's do a binary countdown.
This is where the first hiccup occurred, because I thought I would expose a byte to the IO pins as I would normally do on a microcontroller, but the function of the PCF8574 class is private....
src\main.cpp: In function 'void loop()':
src\main.cpp:29:33: error: 'bool PCF8574::digitalWriteAllBytes(byte)' is private within this context
29 | pcf8574.digitalWriteAllBytes(x);
Finally, I used the digitalWrite call in the loop... but I leave that to your own interpretation.
Code: C / C++
Works:
PS: Now I see that defining PCF8574_LOW_MEMORY would maybe help and expose a function that accepts a byte, I leave that for you to try out.
Button .
Since there was digitalWrite, we also have digitalRead. What's more, we don't have to connect the pull-up resistor ourselves, because the circuit itself "pulls" the inputs to the logic 1 potential. We can only short them to ground (e.g. with a button) and then we get a low state on it. In this way, we do not need to connect a resistor ourselves.

So I have connected:
- a button between GPIO and ground
- additionally, just for visualisation, an LED on the second GPIO together with a resistor of a few hundred ohms
Running the pins:
Code: C / C++
Looping, reading the GPIO, writing out to the console (on the UART), and setting the second pin:
Code: C / C++
It works, except that a button released here means state 1, so the LED is on, and a button pressed is state 0 - LED off.
Button and interrupt .
What remains to be discussed is the INT pin from the PCF8574. This pin will show a falling edge when something on the input of the PCF8574 changes. This can be used in conjunction with a GPIO interrupt on our NodeMCU to get information that something has happened at the PCF8574, for example someone has pressed a button.
So we connect INT to, say, GPIO5. Now the interrupt needs to be triggered:
Code: C / C++
The interruptHandler function will call when there is a falling edge (FALLING) on D5:
Code: C / C++
Here, for demonstration purposes, I'm just setting a variable, which I then use to check in the main loop whether the PCF pins need to be read again.
Code: C / C++
The operation is quite similar to the previous example, except that the loop only checks the state of the pins when an interrupt is received.
More pins can be handled this way - here is an example from the documentation of the library used:
Code: C / C++
This relieves us from having to do a digitalReadAll scan every refresh.
Can even more outputs/inputs be connected? .
A version of the chip with different addressing is also available on the market, giving us a total of 128 controlled pins:

Summary .
A useful and easy to use module. Everything you need is brought out on the goldpins, and also the address selection is quite wide. With 8 pieces you can drive the whole 64 receivers, will anyone need more?
It's also worth mentioning that the module shown here can do a lot more - there's even an encoder demo in the library used, but I didn't have any on hand to try it out today:
Spoiler:
.
Code: C / C++
But even without this, I can conclude that it is a very useful arrangement. What do you think? Have you used the PCF8574 in your projects and if so for what? .
Cool? Ranking DIY Helpful post? Buy me a coffee.