PCF8574: un expander i2c I/O digitale: Arduino, esp8266 e esp32, I/O e interrupt – Parte 1
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.
Qui trovi l'IC 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:
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:
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;
}
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;
}
STM32 gestione in contemporanea di 4 leds e 4 bottoni
Usa PA1 come pin di interruzione e segui le istruzioni del prossimo esempio.
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
.
/*
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
- PCF8574 un expander i2c I/O digitale: Arduino, esp8266 e esp32, I/O ed interrupt
- PCF8574 un expander i2c I/O digitale: Arduino, esp8266 e esp32, encoder rotativo
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. ????)
Hehhehehe.. magari maestro…
Non è usabile direttamente la libreria con il pcf, l’implementazione della gestione dei pin è implicita nell’attach
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
Grazie… Mo’ * ci provo!
(*adesso)
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?
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
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
Grazie Roberto,
attendo la tua condivisione. Comunque per il debug ti basta il TX, l’RX è solo per l’invio verso il MIC.
Ciao RM
Dopo alcuni tentativi falliti, sospensioni e riprese del progetto e diverse noie con la parte web della soluzione tecnica, sono riuscito a mettere insieme una versione “stabile” del progetto, utilizzando proprio il GPIO3 RX per la gestione dell’interrupt (grazie 1000 per i suggerimenti).
Mi farebbe piacere condividere la soluzione tecnica e discutere, se vuole, di alcuni miglioramenti per i quali al momento non riesco a trovare una soluzione: come posso condividere con lei i contenuti del progetto?
Ciao Roberto,
ho creato una sezione apposita nel forum per discutere dei vari progetti ed evoluzioni.
Qui la versione inglese che è più movimentata e qui in italiano.
Ciao Renzo
Ho provato a condividere il mio piccolo progetto sul forum (in italiano) ma ricevo un messaggio di errore durante la fase di upload dei contenuti.
Non escludendo che possa dipendere da qualche opzione sbagliata che ho dato, per ora mi devo rassegnare a condividere il contenuto attraverso la pagina di github https://github.com/robertopapi/ESP8266-01s.
Grazie per la disponibilità.
Ciao Roberto,
c’era l’anti spam che lo ha bloccato, probabilmente delle sezioni di codice non gli erano gradite.
Ho bypassato l’anti spam, ora è tutto ben descritto qui.
ESP8266-01s, PCF8574 e interfaccia web: interazione non in real-time
Grazie Renzo
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
Ciao Alberto,
per la tua necessità devi cambiare la funzione di begin da così
in
non è elegante ma dovrebbe andare (devi cambiare anche l’header).
Ciao RM