PCF8575: un expander i2c I/O digitale a 16 bit
Il PCF8575 è un expander in I/O digitale a 16 bit per il bus bidirezionale a due linee (I2C) è progettato per il funzionamento da 2,5 a 5,5 V.
Il dispositivo è dotato di porte di ingresso/uscita (I/O) quasi bidirezionali a 16 bit (P07-P00, P17-P10), incluse uscite con capacità di azionamento ad alta tensione per il pilotaggio diretto dei LED. Ogni I/O può essere utilizzato come ingresso o uscita senza l’uso di un segnale di controllo della direzione dei dati. All’accensione, gli I/O sono in pull-up.
- L’expander è funzionante su porta parallela I2C
- Open-Drain interrupt output
- Consumo di corrente in standby di 10μA max
- Compatibile con la maggior parte dei microcontrollori
- Fast I2C Bus fino a 400 kHz
- Indirizzo configurabile da tre pin che consente l’utilizzo di un massimo di otto dispositivi
- Uscite bloccate con capacità di corrente elevata per guidare direttamente i LED
- Fonte corrente tramite pin VCC per attivare l’uscita
- Le prestazioni del Latch-Up superano 100 mA per JESD 78, Classe II
- La protezione ESD supera JESD 22
- Modello a corpo umano 2000-V
- Modello di macchina 200-V
- Modello di dispositivo a carico 1000-V (cit. datasheet)
Connessioni del modulo
La connessione al modulo è diretta.
- Va fornita alimentazione da 2,5 fino a 5 V e terra.
- Collegare le linee I2C SCL e SDA dall’MCU.
- Se utilizzato, collegare la linea INT a un ingresso di interrupt sull’MCU e utilizzare un resistore di pull-up.
Ho scritto una libreria per usare questo IC/modulo sia con Arduino che esp8266 o esp32 all’interno dell’Arduino IDE.
Così si può leggere e scrivere i valori digitali con solo 2 fili (perfetto per ESP-01).
Ho provato a semplificare l’uso di questo IC, riducendo al minimo le 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.
Libreria
Puoi trovare la mia libreria qui.
Download
Fare clic sul pulsante DOWNLOADS nell’angolo in alto a destra, rinominare la cartella non compressa PCF8575.
Verificare che la cartella PCF8575 contenga PCF8575.cpp e PCF8575.h.
Collocare la cartella della libreria PCF8575 come / librerie / cartella.
Potrebbe essere necessario creare la sottocartella librerie se è la tua prima libreria.
Riavvia l’IDE.
IC o modulo
Puoi usare un normale IC ma solo in formato SMD o un modulo.
Puoi trovare il componente SMD su Aliexpress
Puoi trovare il module su Aliexpress - Aliexpress
Uso
Come diveco proma ho provato a semplificare l’uso di questo integrato con un set minimo di operazioni.
PCF8575 address map 0x20-0x27
Sul costruttore devi passare l’indirizzo i2c che hai configurato tramite i pin A0, A1, A2 puoi verificare la configurazione qui (per verificare l’indirizzo ti consiglio di usare questa guida I2cScanner)
PCF8575(uint8_t address);
per l’esp8266 se vuoi specificare i pins SDA e SCL usa questo costruttore:
PCF8575(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:
// Instantiate Wire for generic use at 400kHz
TwoWire I2Cone = TwoWire(0);
// Instantiate Wire for generic use at 100kHz
TwoWire I2Ctwo = TwoWire(1);
// Set pcf8575 i2c comunication with second Wire using 21 22 as SDA SCL
PCF8575 pcf8575(&I2Ctwo);
//PCF8575 pcf8575(&I2Ctwo, 21,22);
//PCF8575 pcf8575(&I2Ctwo, 0x5C);
//PCF8575 pcf8575(&I2Ctwo, 21,22,0x5C);
Per ogni pin puoi configurare input o output mode:
pcf8575.pinMode(P0, OUTPUT);
pcf8575.pinMode(P1, INPUT);
pcf8575.pinMode(P2, INPUT);
come puoi vedere dall’immagine l’IC ha 16 I/O digitali:
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):
PCF8575::DigitalInput di = PCF8575.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);
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 PCF8575_LOW_MEMORY
Abilitando il low memory guadagni circa 14byte di memoria, ed anche l’output del metodo che fa la lettura complessiva sarà modificato in questa maniera:
byte di = pcf8575.digitalReadAll();
Serial.print("READ VALUE FROM PCF: ");
Serial.println(di, BIN);
in di
ci sono 2 byte 1110001 1110001
in un uint16_u
, e potrai estrarre le stesse informazioni facendo delle operazioni 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;
p8 = ((di & bit(8)>0)?HIGH:LOW;
p9 = ((di & bit(9)>0)?HIGH:LOW;
p10 = ((di & bit(10)>0)?HIGH:LOW;
p11 = ((di & bit(11)>0)?HIGH:LOW;
p12 = ((di & bit(12)>0)?HIGH:LOW;
p13 = ((di & bit(13)>0)?HIGH:LOW;
p14 = ((di & bit(14)>0)?HIGH:LOW;
p15 = ((di & bit(15)>0)?HIGH:LOW;
se tu vuoi leggere un input singolo:
int p1Digital = PCF8575.digitalRead(P1); // read P1
se vuoi scrivere un valore digitale:
PCF8575.digitalWrite(P1, HIGH);
o:
PCF8575.digitalWrite(P1, LOW);
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 PCF8575
// Function interrupt
void keyPressedOnPCF8575();
// Set i2c address
PCF8575 pcf8575(0x39, ARDUINO_UNO_INTERRUPT_PIN, keyPressedOnPCF8575);
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 keyPressedOnPCF8575(){
// Interrupt called (No Serial no read no wire in this function, and DEBUG disabled on PCF library)
keyPressed = true;
}
Impostare i nomi dei pin come datasheet invece che sequenziale (Update 27/06/2021)
Come puoi vedere nel pinout dell’IC, P0 è P00, P1 è P01 .. P7 è P07 e quindi non ci sono P8 e P9, invece l’8° pin è P10, il 9° pin è P11, ecc… ho inserito perciò la possibilità di modificare la gestione in questo modo anziché sequenziale, basta decommentare la define qui sotto.
// Define to manage original pinout of pcf8575
// like datasheet but not sequential
// #define NOT_SEQUENTIAL_PINOUT
Qui l’etichette dei pins.
#ifdef NOT_SEQUENTIAL_PINOUT
#define P00 0
#define P01 1
#define P02 2
#define P03 3
#define P04 4
#define P05 5
#define P06 6
#define P07 7
#define P10 8
#define P11 9
#define P12 10
#define P13 11
#define P14 12
#define P15 13
#define P16 14
#define P17 15
#else
#define P0 0
#define P1 1
#define P2 2
#define P3 3
#define P4 4
#define P5 5
#define P6 6
#define P7 7
#define P8 8
#define P9 9
#define P10 10
#define P11 11
#define P12 12
#define P13 13
#define P14 14
#define P15 15
#endif
Schemi di connessione
Per gli esempi che trovate nella libreria ho usato questo schema di collegamento su una breadboard:
Esempi aggiuntivi dal pcf8574 riusabili sul pcf8575
Nel tempo vari contributori mi hanno aiutato a creare nuove features ed esempi. Vado a mostrarveli qui:
Wemos lampeggiamento dei LED
Dal Giappone nopnop ha create un esempio per il Wemos che permette il blink sequenziale di 8 LED.
/*
* PCF8575 GPIO Port Expand
* http://nopnop2002.webcrow.jp/WeMos/WeMos-25.html
*
* PCF8575 ----- 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 "PCF8575.h" // https://github.com/xreef/PCF8575_library
// Set i2c address
PCF8575 pcf8575(0x20);
void setup()
{
Serial.begin(9600);
// Set pinMode to OUTPUT
for(int i=0;i<8;i++) {
pcf8575.pinMode(i, OUTPUT);
}
pcf8575.begin();
}
void loop()
{
static int pin = 0;
pcf8575.digitalWrite(pin, HIGH);
delay(1000);
pcf8575.digitalWrite(pin, LOW);
delay(1000);
pin++;
if (pin > 7) pin = 0;
}
Wemos lampeggiamento dei LED invertito
Qui un nuovo esempio che semplicemente posiziona il polo positivo del led sul VCC mentre quello negativo sul pcf8575, così l’alimentazine dei led non è fornita dal pcf ma dall’alimentatore.
/*
* PCF8575 GPIO Port Expand
* Inverted led test: all led is connected with anodo to the IC
*
* PCF8575 ----- 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
* P8 ----------------- LED8
* P9 ----------------- LED9
* P10 ----------------- LED10
* P11 ----------------- LED11
* P12 ----------------- LED12
* P13 ----------------- LED13
* P14 ----------------- LED14
* P15 ----------------- LED15
*
*/
#include "Arduino.h"
#include "PCF8575.h" // https://github.com/xreef/PCF8575_library
// Set i2c address
PCF8575 pcf8575(0x20);
void setup()
{
Serial.begin(9600);
// Set pinMode to OUTPUT
for(int i=0;i<16;i++) {
pcf8575.pinMode(i, OUTPUT);
}
for(int i=0;i<16;i++) {
pcf8575.digitalWrite(i, HIGH);
}
pcf8575.begin();
}
void loop()
{
static int pin = 0;
pcf8575.digitalWrite(pin, LOW);
delay(1000);
pcf8575.digitalWrite(pin, HIGH);
delay(1000);
pin++;
if (pin > 15) pin = 0;
}
Video dimostrativo
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"
/*
* PCF8575 GPIO Port Expand
* Blink all led
* by Mischianti Renzo <https://mischianti.org>
*
* https://mischianti.org/
*
*
* PCF8575 ----- 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 "PCF8575.h" // https://github.com/xreef/PCF8575_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
PCF8575 pcf8575(&I2Ctwo, 0x20);
// PCF8575 pcf8575(&I2Ctwo, 0x20, 21, 22);
// PCF8575(TwoWire *pWire, uint8_t address, uint8_t interruptPin, void (*interruptFunction)() );
// PCF8575(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++) {
pcf8575.pinMode(i, OUTPUT);
}
pcf8575.begin();
}
void loop()
{
static int pin = 0;
pcf8575.digitalWrite(pin, HIGH);
delay(400);
pcf8575.digitalWrite(pin, LOW);
delay(400);
pin++;
if (pin > 7) pin = 0;
}