PCF8574: un expander i2c I/O digitale: Arduino, esp8266 e esp32, I/O e interrupt – Parte 1

Spread the love

PCF8574 un expander i2c I/O digitale: I/O ed interrupt
PCF8574 un expander i2c I/O digitale: I/O ed interrupt

Libreria per l’utilizzo dell’integrato pcf8574 che sfrutta il protocollo i2c usabile sia con Arduino che gli esp8266.

Questo IC puo’ controllare (fino a 8) dispositivi digitali come bottono o led con solamente 2 pins.

Puo’ leggere e scrivere valori digitali con solamente 2 pins perfetto per un esp01 ad esempio.

Ho provato a semplificare l’uso di questo integrato con un set minimo di operazioni.

Come funziona il protocollo I2c

Il protocollo I2C funziona con due fili, l’SDA (dati) e SCL (clock).

Questi pin sono open-drain, e sono in pull-up con delle resistenze.

Di solito c’è un master e uno o più slave sulla linea, anche se possono esserci più master, ma ne parleremo in altra occasione.

Entrambi i master e gli slave possono trasmettere o ricevere dati, pertanto un dispositivo può trovarsi in uno di questi quattro stati: trasmissione master, ricezione master, trasmissione slave, ricezione slave.

IC o modulo

Puoi usare il normale integrato o i vari moduli a disposizione.

pcf8574 IC

Qui trovi l'IC AliExpress

pcf8574 module
Qui trovi il modulo AliExpress

Libreria

Puoi trovare la mia libreria qui .

Download

Fare clic sul pulsante DOWNLOADS nell’angolo in alto a destra, rinominare la cartella non compressa PCF8574.

Verificare che la cartella PCF8574 contenga PCF8574.cpp e PCF8574.h.

Collocare la cartella della libreria PCF8574 come / librerie / cartella.

Potrebbe essere necessario creare la sottocartella librerie se è la tua prima libreria.

Riavvia l’IDE.

Uso

Ho provato a semplificare l’uso di questo integrato con un set minimo di operazioni.

Per il PCF8574P la mappa indirizzi va da 0x20-0x27
per il PCF8574AP la mappa indirizzi va da 0x38-0x3f

Costrutture

Sul costruttora devi passare l’indirizzo i2c che hai configurato tramite i pin A0, A1, A2 (per verificare l’indirizzo ti consiglio di usare questa guida I2cScanner)

PCF8574(uint8_t address);

per l’esp8266 se vuoi specificare i pins SDA e SCL usa questo costruttore:

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

Per gli esp32 puoi passare direttamente il TwoWire così da poter selezionare il secondo canale i2c: [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);

Impostare se il pin è di input o di output ed il valore iniziale

Per ogni pin puoi configurare input o output mode:

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

Puoi gestire il valore iniziale dei vari pins: [updated 06/03/2020]

Puoi definire e gestire se un input pin e INPUT o INPUT_PULLUP

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

Per gli OUTPUT puoi specificare il valore iniziale che viene impostato immediatamente al begin dell’IC: [updated 06/03/2020]

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

per retrocompatibilità il valore dell’OUTPUT di default è HIGH.

come puoi vedere dall’immagine l’IC ha 8 I/O digitali:

PCF8574 schema
pcf8574 pinout

Per leggere tutti i pin digitali in un’unica trasmissione puoi usare questo codice (questo non serve a prevenire troppe letture consecutive all’IC, internamente uso 10millis di tempo di debounce per prevenire un sovraccarico di letture):

	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);

Riduzione uso di memoria

Per assecondare una richiesta specifica di un utente (issue #5) ho creato una define per ridurre l’uso di memoria eliminando la struttura complessa di ritorno alla lettura complessiva, per attivarla devi decommentare la linea nel file .h della libreria:

// #define PCF8574_LOW_MEMORY

Abilitando il low memory guadagni circa 7byte di memoria, ed anche l’output del metodo che fa la lettura complessiva sarà modificato in questa maniera:

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

dove c’è un byte 1110001, e potrai estrarre le stesse informazioni facendo delle operaizoni binarie sul byte stesso, qui un esempio:

   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;

se tu vuoi leggere un input singolo:

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

[aggiornato 13/03/2020] puoi anche forzare la rilettura dall’integrato bypassando la funzione interna di debounce (rimuovendo la latenza)

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

se vuoi scrivere un valore digitale:

PCF8574.digitalWrite(P1, HIGH);

o:

	PCF8574.digitalWrite(P1, LOW);

Interrupt

Puoi inoltre usare un interrupt pin: dovrai inizializzare il pin e passare una funzione di callback che sarà chiamata quando l’interrupt sarà attivato dal PCF8574

// Function interrupt
void keyPressedOnPCF8574();

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

Ricorda che non puoi usare comandi come Serial o Wire all’interno della funzione di interrupt.

Il sistema migliore sarà settare solamente una variabile da leggere all’interno del loop():

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

Bassa latenza

Per le applicationi che richiedono una latenza inferiore ai 10 millisecondi ora ho impostato una define che si può decommentare ed abilitare la latenza ad 1 millisecondo.

Schemi di connessione

Per gli esempi che trovate nella libreria ho usato questo schema di collegamento su una breadboard: 

Arduino pcf8574 IC schema di connessione
Arduino pcf8574 IC schema di connessione

Esempi aggiuntivi

Nel tempo vari contributori mi hanno aiutato a creare nuove features ed esempi. Vado a mostrarveli qui:

Wemos LEDs blink

Dal Giappone nopnop ha create un esempio per il Wemos che permette il blink sequenziale di 8 LED.

/*
 * 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;
}
WeMos D1 esp8266 pcf8574 IC schema di connessione 8 leds
WeMos D1 esp8266 pcf8574 IC schema di connessione 8 leds
Esp32 leds blink usando il canale i2c secondario.

Qui ho creato una variante dell’esempio sopra per mostrare l’utilizzo di un canale secondario i2c presente sugli esp32.

#include "Arduino.h"
/*
 * 	PCF8574 GPIO Port Expand
 *  Blink all led
 *  by Mischianti Renzo <https://mischianti.org>
 *
 *  https://mischianti.org/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(&I2Ctwo, 0x20);
// PCF8574 pcf8574(&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;
}
esp32 pcf8574 IC schema di connessione 8 leds
esp32 pcf8574 IC schema di connessione 8 leds

STM32 gestione in contemporanea di 4 leds e 4 bottoni

Usa PA1 come pin di interruzione e segui le istruzioni del prossimo esempio.

stm32 pcf8574 wiring: 4 LEDs 4 buttons on breadboard
stm32 pcf8574 wiring: 4 LEDs 4 buttons on breadboard

Arduino gestione in contemporanea di 4 leds e 4 bottoni

Ecco un esempio di input ed output contemporaneo, se si ha intenzione di gestire la pressione contemporanea dei bottoni va ridotta la latenza fino a 0 o impostata la define specifica. Altro modo è usare il parametro aggiontivo a true sul 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://mischianti.org>

 https://www.mischianti.org/pcf8574-i2c-digital-i-o-expander-fast-easy-usage/
*/

#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;

}

Links utili

  1. PCF8574 un expander i2c I/O digitale: Arduino, esp8266 e esp32, I/O ed interrupt
  2. PCF8574 un expander i2c I/O digitale: Arduino, esp8266 e esp32, encoder rotativo

Spread the love

13 Risposte

  1. Gianfranco ha detto:

    Grazie per le sue istruzioni MAESTRO!!!
    Mi permette una domanda?
    Come collego con l’IDE i pin pcf8574 con i servi?
    Voglio dire (esempio):

    // Imposta l’indirizzo i2c
    PCF8574 pcf8574 (0x20);
    pcf8574.pinMode (0, OUTPUT);
    e
    Servo servo1;
    servo1.attach (servoPin);
    Dovrebbe essere:
    servo1.attach (pcf8574. ????)

    • Renzo Mischianti ha detto:

      Hehhehehe.. magari maestro…
      Non è usabile direttamente la libreria con il pcf, l’implementazione della gestione dei pin è implicita nell’attach

      uint8_t Servo::attach(int pin, int min, int max)
      {
        if(this->servoIndex < MAX_SERVOS ) {
          pinMode( pin, OUTPUT) ;                                   // set servo pin to output
          servos[this->servoIndex].Pin.nbr = pin;
          // todo min/max check: abs(min - MIN_PULSE_WIDTH) /4 < 128
          this->min  = (MIN_PULSE_WIDTH - min)/4; //resolution of min/max is 4 us
          this->max  = (MAX_PULSE_WIDTH - max)/4;
          // initialize the timer if it has not already been initialized
          timer16_Sequence_t timer = SERVO_INDEX_TO_TIMER(servoIndex);
          if(isTimerActive(timer) == false)
            initISR(timer);
          servos[this->servoIndex].Pin.isActive = true;  // this must be set after the check for isTimerActive
        }
        return this->servoIndex ;
      }
      
      

      andrebbe modificata la libreria Servo per poter usare il pcf8574.
      E comunque va verificato anceh se la potenza del pcf sia sufficiente come segnale.

      Non sembra particolarmente complicata, va modificato solo questo file (per gli avr) ma servirebbe un po’ di tempo.

      Ciao Renzo

  2. Roberto ha detto:

    Intanto grazie per le preziose informazioni e idee.
    Ho tuttavia bisogno di un (forse) piccolo chiarimento in merito a come utilizzare il PIN Interrupt del PCF8574 se il microcontrollore utilizzato è il solo ESP8266-01, dato il limitatissimo numero dei PIN/porte presenti.
    Nel dettaglio i PIN utilizzati per gestire (tra l’altro via software) il bus I2C sono i due GPIO0 e GPIO2, restano quindi sul microcontrollore i soli PIN CH_PD e Reset; se desidero interpretare un input al PCF8574, ad esempio la pressione su di un bottone collegato, come posso innescare la procedura di gestione dell’evento utilizzando appunto la funzione di Interrupt disponibile sul PCF8574? Posso usare ad esempio un collegamento tra quello e il PIN CH_PD? Esiste il modo?

    • Renzo Mischianti ha detto:

      Ciao Roberto,
      credo che il pin EN non sia utilizzabile in quel senso, ma dovrei fare dei test.
      Comunque hai altri due pin,
      ESP-01 modules programming board

      se non hai esigenze particolari ti consiglio di utilizzare il GPIO3 RX che dovrebbe essere anche un pin RTC.

      Ciao RM

      • roberto ha detto:

        In effetti speravo di riservarlo all’interfaccia seriale per il debugging ma oggettivamente a qualcosa bisogna rinunciare. Grazie 1000 per il suggerimento.
        Se ottengo qualche successo lo condivido volentieri.
        Roberto

  3. Alberto Rubinelli ha detto:

    Ciao, sto migrando il mio sistema da arduino ad ESP8266, precisamente usando uno schedino Wemos D1, perche’ mi viene comodo sostituirlo pari passo all’arduino sulla mia scheda.
    La libreria che usavo per i PCF e’ stata abbandonata, quindi non supporta gli ESP. Cercando ho trovato la tua, che leggendo e’ una delle migliori se non la migliore.
    Apparentemente le funzioni sono uguali, e questo mi permette una sostituzione alla pari, ma i trovo incastrato perche’ con quella che usavo dichiaravo l’oggetto senza indirizzo e poi glielo assegnavo con begin(address) mentre con la tua devo dichiarare l’indirizzo all’atto dell’instanza e la funzione begin non accetta indirizzo. Non sto a spiegarti tutta la trafila, ma per me era l’ideale assegnarlo dopo l’indirizzo, usando il begin, perche’ ho tutta una parte di setup che capisce come e’ costituita la scheda e programma di conseguenza i pcf. Non so se sono riuscito a spiegarmi 🙂
    Ci sarebbe modo di modificare la libreria in tal senso ? oppure e’ nata e deve restare cosi’, quindi e’ inutile che mi metta a guardare il codice.
    Grazie anticipatamente

    • Renzo Mischianti ha detto:

      Ciao Alberto,
      per la tua necessità devi cambiare la funzione di begin da così

      bool PCF8574::begin(){
      

      in

      bool PCF8574::begin(uint8_t address){
      _address = address;
      

      non è elegante ma dovrebbe andare (devi cambiare anche l’header).

      Ciao RM

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *