PCF8574 i2c digital I/O expander: Arduino, esp8266 and esp32, basic I/O and interrupt – Part 1

Spread the love
  • 16
  •  
  •  
  • 23
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
    39
    Shares

Key features of PCF8574 I2C I/O expansion

  • 8 bi-directional data lines
  • Loop-thru feature allows expansion of up to 8 modules / 64 data lines
  • I2C interface with jumper adjustable addresses
  • Interrupt output capability
  • 3.3V and 5V compatible.

A common requirement when working with MCUs is the need to add more digital I/O than the device supports natively.  The  PCF8574 is one of the more popular methods of adding lines as it uses the I2C bus that requires only 2 lines on the MCU.  It provides 8 additional digital I/O lines which are easily expandable up to 64.

I2C Interface

The module has an easy to use I2C interface that can be configured to use any one of eight different I2C addresses if you want to use multiple modules in the same system or if you run into an address conflict with another device.

There are three address jumps (A0-A2) the determines which I2C address to use.  As shipped, these jumpers are all set to the ‘-‘ side which is ground or LOW as shown in the picture.  The ‘+’ side is Vcc or HIGH.

I/O Functionality

The I/O is defined as quasi-bidirectional. A quasi-bidirectional I/O is either an input or output port without using a direction control register.  When set as inputs, the pins act as normal inputs do.  When set as outputs, the PCF8574 device drives the outputs LOW with up to 25mA sink capability but when driving the outputs HIGH, they are just pulled up high with a weak internal pull-up.  That enables an external device to overpower the pin and drive it LOW.

The device powers up with the 8 data lines all set as inputs.

When using the pins as inputs, the pins are set to HIGH by the MCU, which turns on a weak 100 uA internal pull-up to Vcc. They will read as HIGH if there is no input or if the pin is being driven HIGH by an external signal but can be driven LOW by an external signal that can easily override the weak pull-up.

If used as outputs, they can be driven LOW by the MCU by writing a LOW to that pin. A strong pull-down is turned on and stays on to keep the pin pulled LOW. If the pin is driven HIGH by the MCU, a strong pull-up is turned on for a short time to quickly pull the pin HIGH and then the weak 100uA pull-up is turned back on to keep the pin HIGH.

If the pins are set to be outputs and are driven LOW, it is important that an external signal does not also try to drive it HIGH or excessive current may flow and damage the part.

Whenever the internal register is read, the value returned depends on the actual voltage or status of the pin.

The I/O ports are entirely independent of each other, but they are controlled by the same read or write data byte.

Interrupt Output

The interrupt open drain output pin is active LOW.  It is normally pulled HIGH using a pull-up resistor and is driven low by the PCF8574 when any of the inputs change state. This signals the MCU to poll the part to see what is going on. If connecting this pin, enable the internal pull-up resistor on the MCU or add an external pull-up of 10K or so.

If using interrupts with multiple modules, since they are open drain they can be tied together if a single interrupt back to the MCU is desired.

Module Connections

The connections to the module are straight forward.

  1. Supply 3.3 or 5V power and ground.
  2. Connect I2C SCL and SDA lines to same on the MCU.
  3. If used, connect the INT line to an interrupt input on the MCU and use a pull-up resistor.

I write a library to use i2c pcf8574 IC with arduino and esp8266.

So can read and write digital value with only 2 wire (perfect for ESP-01).

I try to simplify the use of this IC, with a minimal set of operation.

How I2c Works

I2C works with it’s two wires, the SDA(data line) and SCL(clock line).

Both these lines are open-drain, but are pulled-up with resistors.

Usually there is one master and one or multiple slaves on the line, although there can be multiple masters, but we’ll talk about that later.

Both masters and slaves can transmit or receive data, therefore, a device can be in one of these four states: master transmit, master receive, slave transmit, slave receive.

Library

You can find my library here.

To download.

Click the DOWNLOADS button in the top right corner, rename the uncompressed folder PCF8574.

Check that the PCF8574 folder contains PCF8574.cpp and PCF8574.h.

Place the PCF8574 library folder your /libraries/ folder.

You may need to create the libraries subfolder if its your first library.

Restart the IDE.

IC or Module

You can use a normal IC or module.

pcf8574 IC

You can buy here

pcf8574 module

You can buy here

Usage

I try to simplify the use of this IC, with a minimal set of operation.

PCF8574P address map 0x20-0x27 PCF8574AP address map 0x38-0x3f

Constructor

On constructor you must pas the address of i2c, you can use A0, A1, A2 pins to change the address, you can find the address value here (to check the adress use this guide I2cScanner)

PCF8574(uint8_t address);

for esp8266 if you want specify SDA e SCL pin use this:

PCF8574(uint8_t address, uint8_t sda, uint8_t scl);

For esp32 you can pass directly che TwoWire, so you can choice the secondary i2c channel: [updated 29/04/2019]

// Instantiate Wire for generic use at 400kHz
TwoWire I2Cone = TwoWire(0);
// Instantiate Wire for generic use at 100kHz
TwoWire I2Ctwo = TwoWire(1);

// Set dht12 i2c comunication with second Wire using 21 22 as SDA SCL
DHT12 dht12(&I2Ctwo);
//DHT12 dht12(&I2Ctwo, 21,22);
//DHT12 dht12(&I2Ctwo, 0x5C);
//DHT12 dht12(&I2Ctwo, 21,22,0x5C);

Input/Output mode and starting value

You must set input/output mode:

	pcf8574.pinMode(P0, OUTPUT);
	pcf8574.pinMode(P1, INPUT);
	pcf8574.pinMode(P2, INPUT);

You can manage initial value of various pin: [updated 06/03/2020]

You can define for input if you want manage an INPUT or INPUT_PULLUP

pcf8574.pinMode(P0, INPUT);
pcf8574.pinMode(P1, INPUT_PULLUP);

And for OUTPUT you can specify the initial value at baginning of IC: [updated 06/03/2020]

pcf8574.pinMode(P6, OUTPUT, LOW);
pcf8574.pinMode(P6, OUTPUT, HIGH);

for backward compatibility default value of OUTPUT is HIGH.

then IC as you can see in the image have 8 digital input/output:

PCF8574 schema
pcf8574 pinout

So to read all analog input in one trasmission you can do (even if I use a 10millis debounce time to prevent too much read from i2c):

	PCF8574::DigitalInput di = PCF8574.digitalReadAll();
	Serial.print("READ VALUE FROM PCF P1: ");
	Serial.print(di.p0);
	Serial.print(" - ");
	Serial.print(di.p1);
	Serial.print(" - ");
	Serial.print(di.p2);
	Serial.print(" - ");
	Serial.println(di.p3);

Low memory

To follow a request (you can see It on issue #5) I create a define variable to work with low memori device, if you decomment this line on .h file of the library:

// #define PCF8574_LOW_MEMORY

Enable low memory props and gain about 7byte of memory, and you must use the method to read all like so:

   byte di = pcf8574.digitalReadAll();
   Serial.print("READ VALUE FROM PCF: ");
   Serial.println(di, BIN);

where di is a byte like 1110001, so you must do a bitwise operation to get the data, operation that I already do in the “normal” mode, here an example:

   p0 = ((di & bit(0)>0)?HIGH:LOW;
   p1 = ((di & bit(1)>0)?HIGH:LOW;
   p2 = ((di & bit(2)>0)?HIGH:LOW;
   p3 = ((di & bit(3)>0)?HIGH:LOW;
   p4 = ((di & bit(4)>0)?HIGH:LOW;
   p5 = ((di & bit(5)>0)?HIGH:LOW;
   p6 = ((di & bit(6)>0)?HIGH:LOW;
   p7 = ((di & bit(7)>0)?HIGH:LOW;

if you want read a single input:

int p1Digital = PCF8574.digitalRead(P1); // read P1

[updated 13/03/2020] you can also force reread of value without debounce

int p1Digital = PCF8574.digitalRead(P1, true); // read P1 without debounce

If you want write a digital value you must do:

PCF8574.digitalWrite(P1, HIGH);

or:

PCF8574.digitalWrite(P1, LOW);

Interrupt

You can also use interrupt pin: You must initialize the pin and the function to call when interrupt raised from PCF8574

// Function interrupt
void keyPressedOnPCF8574();

// Set i2c address
PCF8574 pcf8574(0x39, ARDUINO_UNO_INTERRUPT_PIN, keyPressedOnPCF8574);

Remember you can’t use Serial or Wire on interrupt function.

The better way is to set only a variable to read on loop:

void keyPressedOnPCF8574(){
	// Interrupt called (No Serial no read no wire in this function, and DEBUG disabled on PCF library)
	 keyPressed = true;
}

Low latency

For low latenci application (lesser than 10millis) I add a new define that you can decomment and activate 1millis latency [updated 06/03/2020] .

Connections schema

Simple connection schema

For the examples I use this wire schema on breadboard: 

Breadboard

Additional examples

In the time peoples help me to create new examples, I’m going to add they here:

Wemos LEDs blink

From japan nopnop create an example to blink 8 leds sequentially.

/*
 * PCF8574 GPIO Port Expand
 * http://nopnop2002.webcrow.jp/WeMos/WeMos-25.html
 *
 * PCF8574    ----- WeMos
 * A0         ----- GRD
 * A1         ----- GRD
 * A2         ----- GRD
 * VSS        ----- GRD
 * VDD        ----- 5V/3.3V
 * SDA        ----- GPIO_4(PullUp)
 * SCL        ----- GPIO_5(PullUp)
 *
 * P0     ----------------- LED0
 * P1     ----------------- LED1
 * P2     ----------------- LED2
 * P3     ----------------- LED3
 * P4     ----------------- LED4
 * P5     ----------------- LED5
 * P6     ----------------- LED6
 * P7     ----------------- LED7
 *
 */

#include "Arduino.h"
#include "PCF8574.h"  // https://github.com/xreef/PCF8574_library

// Set i2c address
PCF8574 pcf8574(0x20);

void setup()
{
  Serial.begin(9600);

  // Set pinMode to OUTPUT
  for(int i=0;i<8;i++) {
    pcf8574.pinMode(i, OUTPUT);
  }
  pcf8574.begin();
}

void loop()
{
  static int pin = 0;
  pcf8574.digitalWrite(pin, HIGH);
  delay(1000);
  pcf8574.digitalWrite(pin, LOW);
  delay(1000);
  pin++;
  if (pin > 7) pin = 0;
}
Esp32 leds blink using secondary i2c channel.

Here I create a variant of example to show how to use secondary i2c channel of a esp32.

#include "Arduino.h"
/*
 * 	PCF8574 GPIO Port Expand
 *  Blink all led
 *  by Mischianti Renzo <https://www.mischianti.org>
 *
 *  https://www.mischianti.org/2019/01/02/pcf8574-i2c-digital-i-o-expander-fast-easy-usage/
 *
 *
 * PCF8574    ----- Esp32
 * A0         ----- GRD
 * A1         ----- GRD
 * A2         ----- GRD
 * VSS        ----- GRD
 * VDD        ----- 5V/3.3V
 * SDA        ----- 21
 * SCL        ----- 22
 *
 * P0     ----------------- LED0
 * P1     ----------------- LED1
 * P2     ----------------- LED2
 * P3     ----------------- LED3
 * P4     ----------------- LED4
 * P5     ----------------- LED5
 * P6     ----------------- LED6
 * P7     ----------------- LED7
 *
 */

#include "Arduino.h"
#include "PCF8574.h"  // https://github.com/xreef/PCF8574_library

// Instantiate Wire for generic use at 400kHz
TwoWire I2Cone = TwoWire(0);
// Instantiate Wire for generic use at 100kHz
TwoWire I2Ctwo = TwoWire(1);

// Set i2c address
PCF8574 pcf8574(&amp;I2Ctwo, 0x20);
// PCF8574 pcf8574(&amp;I2Ctwo, 0x20, 21, 22);
// PCF8574(TwoWire *pWire, uint8_t address, uint8_t interruptPin,  void (*interruptFunction)() );
// PCF8574(TwoWire *pWire, uint8_t address, uint8_t sda, uint8_t scl, uint8_t interruptPin,  void (*interruptFunction)());

void setup()
{
  Serial.begin(112560);

  I2Cone.begin(16,17,400000); // SDA pin 16, SCL pin 17, 400kHz frequency

  // Set pinMode to OUTPUT
  for(int i=0;i<8;i++) {
    pcf8574.pinMode(i, OUTPUT);
  }
  pcf8574.begin();
}

void loop()
{
  static int pin = 0;
  pcf8574.digitalWrite(pin, HIGH);
  delay(400);
  pcf8574.digitalWrite(pin, LOW);
  delay(400);
  pin++;
  if (pin > 7) pin = 0;
}

Arduino manage 4 buttons and 4 leds at the same time

Here is an example of simultaneous input and output, if you intend to manage the simultaneous pressure of the buttons, the latency must be reduced to 0 or the specific define set. Another way is to use the adjective parameter to true on the digitalRead.

Arduino pcf8574 esempio con 4 Leds 4 bottoni su breadboard
Arduino pcf8574 esempio con 4 Leds 4 bottoni su breadboard
/*
KeyPressed and leds with interrupt
by Mischianti Renzo <https://www.mischianti.org>
<blockquote class="wp-embedded-content" data-secret="4PUGPRcnmN"><a href="https://www.mischianti.org/2019/01/02/pcf8574-i2c-digital-i-o-expander-fast-easy-usage/">PCF8574 i2c digital I/O expander: Arduino, esp8266 and esp32, basic I/O and interrupt &#8211; Part 1</a></blockquote><iframe title="&#8220;PCF8574 i2c digital I/O expander: Arduino, esp8266 and esp32, basic I/O and interrupt &#8211; Part 1&#8221; &#8212; Renzo Mischianti" class="wp-embedded-content" sandbox="allow-scripts" security="restricted" style="position: absolute; clip: rect(1px, 1px, 1px, 1px);" src="https://www.mischianti.org/2019/01/02/pcf8574-i2c-digital-i-o-expander-fast-easy-usage/embed/#?secret=4PUGPRcnmN" data-secret="4PUGPRcnmN" width="500" height="282" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
*/
#include "Arduino.h"
#include "PCF8574.h"
// For arduino uno only pin 1 and 2 are interrupted
#define ARDUINO_UNO_INTERRUPTED_PIN 2
// Function interrupt
void keyPressedOnPCF8574();
// Set i2c address
PCF8574 pcf8574(0x38, ARDUINO_UNO_INTERRUPTED_PIN, keyPressedOnPCF8574);
unsigned long timeElapsed;
void setup()
{
Serial.begin(115200);
pcf8574.pinMode(P0, INPUT_PULLUP);
pcf8574.pinMode(P1, INPUT_PULLUP);
pcf8574.pinMode(P2, INPUT);
pcf8574.pinMode(P3, INPUT);
pcf8574.pinMode(P7, OUTPUT);
pcf8574.pinMode(P6, OUTPUT, HIGH);
pcf8574.pinMode(P5, OUTPUT);
pcf8574.pinMode(P4, OUTPUT, LOW);
pcf8574.begin();
timeElapsed = millis();
}
unsigned long lastSendTime = 0;        // last send time
unsigned long interval = 4000;          // interval between sends
bool startVal = HIGH;
bool keyPressed = false;
void loop()
{
if (keyPressed){
uint8_t val0 = pcf8574.digitalRead(P0);
uint8_t val1 = pcf8574.digitalRead(P1);
uint8_t val2 = pcf8574.digitalRead(P2);
uint8_t val3 = pcf8574.digitalRead(P3);
Serial.print("P0 ");
Serial.print(val0);
Serial.print(" P1 ");
Serial.print(val1);
Serial.print(" P2 ");
Serial.print(val2);
Serial.print(" P3 ");
Serial.println(val3);
keyPressed= false;
}
if (millis() - lastSendTime > interval) {
pcf8574.digitalWrite(P7, startVal);
if (startVal==HIGH) {
startVal = LOW;
}else{
startVal = HIGH;
}
lastSendTime = millis();
Serial.print("P7 ");
Serial.println(startVal);
}
}
void keyPressedOnPCF8574(){
// Interrupt called (No Serial no read no wire in this function, and DEBUG disabled on PCF library)
keyPressed = true;
}

Usefully links

  1. PCF8574 i2c digital I/O expander: Arduino, esp8266 and esp32, basic I/O and interrupt
  2. PCF8574 i2c digital I/O expander: Arduino, esp8266 and esp32, rotary encoder


Spread the love
  • 16
  •  
  •  
  • 23
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
    39
    Shares

You may also like...

13 Responses

  1. djvov says:

    about ESP8266. Needs to pull-up and pull-down some pins.

    RESET through 10kOm resistor to 3.3v
    CH_PD through 10kOm resistor to 3.3v

    GPIO0 through 10kOm resistor to 3.3v
    GPIO2 through 10kOm resistor to 3.3v
    GPIO15 through 10kOm resistor to GND

    and

    100nF capacitor between 3.3v and GND

    at first, I pul +led to PCF pin, and -led to GND, and led was dimly .

    then I read datasheet

    led was dimly because PCF gives small current when PCF pin is HIGH, need to sinking current. +(anode)Led to 3.3v through 200Om resistor and -(kathode)led to PCF pin, and light led by set pin to LOW.

    your library works! thank you!

  2. HTG says:

    Hi
    Do thank you very much for all the good things you put on this site
    What i need……
    I do need some help , would like to put 6x PCF8574 together….
    But cant find any Sketch that does something like that with this LIB..
    So perhaps you could give an example how to do that??
    I do have 1 working…. but number 2 …. does not want to work…
    Do thank you very much… keep up the good work…..

  3. Philip Chisholm says:

    HI –

    Nice tutorial.

    But…

    I wanted to change pins for the SDA and SCL on an esp32.
    I wanted to use pins 27 and 14 (for esp32 and for esp32cam 12 and 2)

    I tried using the wire.h library and the PCF8574 lib…but for some reason it keep son forcing pins 21 and 22 as the SDA and SCL…and I did read that I need to sort out the .cpp files to make sure it is not being forced to set SDL and SCL pins… on this tutorial https://randomnerdtutorials.com/esp32-i2c-communication-arduino-ide/ but I have no clue how to do that!!

    For testing I was using this code…
    >>> using I2C scanner I get address for PCF as 0x38
    >>> running below sketch – SDA and SCL are using pins 21 and 22 …
    But should be on the forced 27 and 14….
    How to fix??
    Thanks!

    ***

    #include “Wire.h”
    #include “Arduino.h”
    #include “PCF8574.h”

    TwoWire I2Cone = TwoWire(0);

    PCF8574 pcf8574(&I2Cone, 0x38);

    (note your tutorial it think it has typo… line 39 >> PCF8574 pcf8574(&I2Ctwo, 0x20); >>> I think it should be PCF8574 pcf8574(&2Ctwo, 0x20); ) Your editor is added letters…

    void setup(){

    Serial.begin(115200);
    delay (100);

    I2Cone.begin(27,14,100000);

    // Set the pinModes
    pcf8574.pinMode(P0, OUTPUT);
    pcf8574.pinMode(P1, OUTPUT);
    pcf8574.begin();
    }

    void loop(){
    pcf8574.digitalWrite(P0, HIGH);
    pcf8574.digitalWrite(P1, LOW);
    delay(1000);
    pcf8574.digitalWrite(P0, LOW);
    pcf8574.digitalWrite(P1, HIGH);
    delay(1000);
    }

  4. Mikkel Troldtoft says:

    Hi Renzo,
    Thank you for the nice library! However, I’m experiencing some problems. I’m setting several PINs on the PCF8574 to input, but when I measure on them or add a TSOP38238 they act as outputs. I’m a bit in doubt whether they are actually set to HIGH for input with the library or whether I need to add some pullup/pulldown in order to get it to work? Could you please point me in the right direction? 🙂

    Best,
    Mikkel

    • Hi Mikkel,
      I finally release a beta versione, tested and I think stable on this branch
      https://github.com/xreef/PCF8574_library/tree/INPUT_PULLPUP_Test

      This version have some addition features

      Now input are start LOW with INPUT, to have a HIGH value at first must be use INPUT_PULLUP

      pcf8574.pinMode(P0, INPUT);
      pcf8574.pinMode(P1, INPUT_PULLUP);

      For OPUTPUT there is a new parameter and you can specify if start in LOW or HIGH

      pcf8574.pinMode(P6, OUTPUT, LOW);
      pcf8574.pinMode(P6, OUTPUT, HIGH);

      And new define for very low latency connection.

      Bye Renzo

  5. Ray says:

    Hi Renzo,
    Thanks for sharing the examples and explanations of the PCF8574. Was updating an older project (07-2018) and looking for some info and find your page. Good to see that I am on the right path.
    Be well,
    Ray.
    (ps. I guess that the first line of the”Arduino manage 4 buttons and 4 leds at the same time” ino is not needed there)

Leave a Reply

Your email address will not be published. Required fields are marked *