Site icon Renzo Mischianti

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

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

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

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

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
/*
 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
Exit mobile version