PCF8574 / PCF8574A
Adding 8 GPIO Pins with Two Wires
A Beginner-to-Practical Guide to the I²C 8-Bit I/O Expander
Contents
An Arduino Nano — or most small microcontrollers — has a limited number of GPIO pins. Real projects quickly run out of them. Consider a project that needs:
● 8 LEDs
● 8 buttons
● An LCD display
● A bank of relays
● A keypad
● Several switches
Wiring all of that directly to the microcontroller would consume nearly every available pin. The PCF8574 solves this by giving you 8 additional digital I/O pins while using only two MCU pins — the I²C bus (SDA and SCL).
|
Arduino │ │ SDA │ SCL ▼ PCF8574 ├── P0 ├── P1 ├── P2 ├── P3 ├── P4 ├── P5 ├── P6 └── P7 |
|
Key idea The PCF8574 doesn't make your microcontroller more powerful. It gives the microcontroller another set of remotely controlled GPIO pins through the I²C bus. |
Keep that sentence in mind — it is the single idea that makes every later section make sense.
The PCF8574 is an 8-bit I/O expander with an I²C interface. “I/O expander” simply means a chip whose entire job is to offer more input/output pins, controlled remotely over a serial bus rather than wired directly to the MCU's own pins.
Important characteristics
● 8 quasi-bidirectional I/O pins (P0–P7)
● Communicates over I²C — only 2 wires needed, shared with other devices
● Address is configurable using 3 hardware pins: A0, A1, A2
● No dedicated direction register like many modern GPIO expanders (e.g. MCP23017)
● Each pin can be used as an input or an output
● Multiple PCF8574 devices can share the same I²C bus at the same time
This distinction causes a lot of confusion, so it deserves its own section. The PCF8574 and PCF8574A are functionally identical, but they occupy different I²C address ranges:
|
Device |
Address range |
|
PCF8574 |
0x20 – 0x27 |
|
PCF8574A |
0x38 – 0x3F |
The three address pins (A0, A1, A2) still give 8 possible combinations, but the two chip variants occupy different base ranges. For example, with all three address pins tied high (A2 A1 A0 = 1 1 1):
|
A2 A1 A0 = 1 1 1
PCF8574 → 0x27 PCF8574A → 0x3F |
|
Why this matters: two modules that look identical, with identical A0/A1/A2 jumper settings, can end up at completely different I²C addresses simply because one uses a PCF8574 and the other a PCF8574A. If your I²C scanner doesn't find the address you expected, check which variant is actually on the board. |
A0, A1 and A2 are hardware address pins. Each one is tied to VCC (logic 1) or GND (logic 0), and together they select where the chip sits within its address range. With 3 pins, there are 2³ = 8 possible combinations:
|
A2 |
A1 |
A0 |
Result |
|
0 |
0 |
0 |
Lowest address |
|
0 |
0 |
1 |
|
|
0 |
1 |
0 |
|
|
0 |
1 |
1 |
|
|
1 |
0 |
0 |
|
|
1 |
0 |
1 |
|
|
1 |
1 |
0 |
|
|
1 |
1 |
1 |
Highest address |
Applied to the actual ranges from Section 3, this gives 8 selectable addresses for the PCF8574 (0x20–0x27) and 8 for the PCF8574A (0x38–0x3F).
Solder jumpers
Many breakout modules bring A0/A1/A2 out to solder-bridge jumpers instead of hard-wiring them, so the address can be changed without redesigning the board:
|
A0 ──●── GND │ └── VCC |
Bridging the jumper one way ties the pin to GND (0); bridging it the other way ties it to VCC (1). This is exactly how you give several identical modules different addresses so they can coexist on one bus.
This tutorial doesn't assume prior I²C experience, so here's the short version. I²C is a 2-wire serial bus:
|
SDA ────────────────┐ │ MCU SCL ────────────────┤ │ PCF8574 |
The real strength of I²C is that many devices can share the same two wires, each distinguished by its own address:
|
┌── PCF8574 Arduino ── I²C ──┼── RTC ├── EEPROM └── Sensor |
Terms worth knowing
● SDA — the data line
● SCL — the clock line
● Pull-up resistors — required on both SDA and SCL for the bus to work reliably (many breakout boards already include them)
● Device address — the 7-bit address that identifies a chip on the bus
● ACK — a bit each device sends back to confirm it received a byte
● Multiple devices on one bus — each device only responds when its own address is sent
That's enough I²C background for this tutorial — a full protocol deep-dive isn't necessary to use the PCF8574 effectively.
This is probably the single most important technical detail to understand about the PCF8574. Its pins are not like an Arduino GPIO. There is no pinMode()-style direction register — instead, every pin behaves as a quasi-bidirectional I/O.
To use a pin as an input, you first write a 1 to it. This releases the pin (weakly pulls it high) so that an external circuit is free to pull it low:
|
Write 1 → release pin → external circuit can pull it LOW |
This is exactly why a button can be wired directly to a PCF8574 pin without any extra components:
|
VCC │ [button] │ P0 │ PCF8574 |
Pressing the button connects P0 to GND, pulling it LOW — which the PCF8574 reports back over I²C. Because inputs work this way, the PCF8574 is particularly convenient for buttons, keypads, DIP switches and LCD control lines.
It's tempting to treat the PCF8574's 8 pins as “8 more Arduino pins”, but their electrical characteristics are different, so this deserves its own section rather than being glossed over.
|
MCU GPIO ≠ PCF8574 GPIO |
The PCF8574's outputs have asymmetric source/sink current capability, and the chip is not designed to directly drive arbitrary loads such as motors or high-current LEDs.
● For LEDs, always use an appropriate current-limiting resistor
● For relays, motors, or higher-current loads, drive them through a transistor or MOSFET rather than the pin directly
● The PCF8574's architecture makes sinking current (pulling a load to GND) noticeably more capable than sourcing it — which is why many circuits built around this chip are designed as active-low
Start as simply as possible: toggle pin P0 on and off.
|
#include <Wire.h>
#define PCF8574_ADDR 0x20
void setup() { Wire.begin(); }
void loop() { Wire.beginTransmission(PCF8574_ADDR); Wire.write(0b11111110); Wire.endTransmission();
delay(500);
Wire.beginTransmission(PCF8574_ADDR); Wire.write(0b11111111); Wire.endTransmission();
delay(500); } |
Each byte written over I²C maps directly onto the 8 pins:
|
Bit: 7 6 5 4 3 2 1 0 │ │ │ │ │ │ │ │ P7 P6 P5 P4 P3 P2 P1 P0 |
0b11111110 clears only bit 0 (P0), driving it LOW while every other pin stays HIGH — that's the whole example.
To read a button wired as shown in Section 6, first release all pins by writing 0xFF, then request one byte back from the chip:
|
Wire.beginTransmission(PCF8574_ADDR); Wire.write(0xFF); Wire.endTransmission();
Wire.requestFrom(PCF8574_ADDR, 1);
uint8_t value = Wire.read(); |
Then test the bit for the pin you care about:
|
if (!(value & (1 << 0))) { // P0 is LOW — button is pressed } |
|
Why the logic looks backwards: 1 means released/HIGH (button not pressed), and 0 means pressed/LOW. It reads as inverted logic at first, but it follows directly from how quasi-bidirectional inputs work (Section 6). |
An I²C scanner sketch is one of the most useful tools you can run before touching any PCF8574 code. It sweeps every possible 7-bit address and reports which ones respond:
|
Scanning... Device found at 0x27 Device found at 0x3F |
This ties directly back to Section 3 — seeing 0x27 tells you it's a PCF8574, while 0x3F tells you it's a PCF8574A, even if the two boards look identical.
This is where the chip becomes far more useful than “8 extra pins”. Because each device is selected by its own address, several can share the same two I²C wires at once:
|
Arduino │ ├── 0x20 PCF8574 ├── 0x21 PCF8574 ├── 0x22 PCF8574 ... └── 0x27 PCF8574 |
...and, on the second address range:
|
0x38 – 0x3F (PCF8574A) |
|
Key idea Combining both variants, you can theoretically fit up to 16 PCF8574/PCF8574A devices on a single I²C bus — 128 extra GPIO pins from just two MCU pins — assuming no address conflicts and staying within the usual I²C electrical limits (bus capacitance, pull-up strength, cable length). |
The PCF8574 shows up in far more places than simple LED-blinking demos:
● 16×2 and 20×4 character LCDs (via an I²C “backpack” — see Section 14)
● Membrane keypads
● Button panels and control panels
● LED status indicators
● Relay banks
● DIP switch banks for configuration
● Rotary encoder interfaces
● General GPIO expansion for Arduino, ESP8266 and ESP32 projects
The 16×2 LCD backpack is worth calling out specifically — most people have already seen a small blue PCF8574 board soldered to the back of an LCD, often without realizing what chip is actually doing the work.
|
Problem |
Likely cause |
|
Device doesn't appear on the scanner |
Wrong I²C wiring (SDA/SCL swapped or not connected) |
|
Found at an unexpected address |
PCF8574 vs PCF8574A confusion (Section 3) |
|
Random / unreliable communication |
Missing or incorrect pull-up resistors |
|
Input always reads HIGH |
Pin never released with a 1 before reading (Section 6) |
|
LED behaves strangely / stays dim |
Output polarity or current-sink/source limitation (Section 7) |
|
Two devices seem to conflict |
Same I²C address — check A0/A1/A2 jumpers |
|
ESP8266 doesn't work, Arduino does |
Wrong SDA/SCL pins for the ESP8266 board variant |
|
LCD doesn't work at all |
Wrong PCF8574 address, or wrong pin mapping for that backpack |
People frequently search for “PCF8574 LCD”, but there are really several layers stacked on top of each other:
|
PCF8574 IC ↓ I²C I/O expander ↓ LCD backpack PCB ↓ 16×2 / 20×4 LCD |
The backpack board that sits between the PCF8574 and the LCD usually adds its own components:
● A potentiometer for LCD contrast
● A backlight-control transistor
● Its own pull-up resistors
● Jumpers (for example, to disable the backlight control)
● A specific P0–P7 → LCD pin mapping, which varies by manufacturer
|
This is why: different LCD libraries sometimes use different pin mappings even though every one of them says it supports “PCF8574” — the chip is identical, but the backpack wiring around it isn't standardized. |
The PCF8574 and PCF8574A turn two I²C wires into 8 extra digital I/O pins, with up to 16 devices shareable on a single bus. The two ideas most worth remembering are: the address-range split between PCF8574 (0x20–0x27) and PCF8574A (0x38–0x3F), and the quasi-bidirectional input behaviour that requires releasing a pin (writing 1) before reading it.
|
Key idea The PCF8574 doesn't make your microcontroller more powerful. It gives the microcontroller another set of remotely controlled GPIO pins through the I²C bus. |

No comments:
Post a Comment