Amo il protocollo I2C, e quando ho bisogno di un sensore, ogni volta cerco di trovarne uno che lo utilizzi. Ho anche scritto alcune librerie per vari sensori che usano l’I2C. Quindi, voglio scrivere alcuni articoli in cui spiego (Arduino, Arduino SAMD MKR, esp8266 ed esp32) alcune funzionalità interessanti e cercherò di spiegare come risolvere i problemi che possono sorgere lavorando con più dispositivi I2C.
esp32 è un buon compromesso tra prezzo e potenza, possiede molte funzionalità e capacità di connettività.
Introduzione al protocollo I2C
I2C (Inter-Integrated Circuit, eye-squared-C) ed è alternativamente noto come I2C o IIC. È un bus di comunicazione seriale sincrono, multi-master, multi-slave, a commutazione a pacchetto, a singolo capo. Inventato nel 1982 da Philips Semiconductors. È ampiamente utilizzato per collegare circuiti integrati periferici a bassa velocità a processori e microcontrollori per comunicazioni a breve distanza all’interno della scheda. (cit. WiKi)
Velocità
I2C supporta 100 kbps, 400 kbps, 3,4 Mbps. Alcune varianti supportano anche 10 Kbps e 1 Mbps.
Modalità | Velocità massima | Capacità massima | Pilotaggio | Direzione |
---|---|---|---|---|
Modalità Standard (Sm) | 100 kbit/s | 400 pF | Drain aperto | Bidirezionale |
Modalità Fast (Fm) | 400 kbit/s | 400 pF | Drain aperto | Bidirezionale |
Modalità Fast Plus (Fm+) | 1 Mbit/s | 550 pF | Drain aperto | Bidirezionale |
Modalità ad alta velocità (Hs) | 1,7 Mbit/s | 400 pF | Drain aperto | Bidirezionale |
Modalità ad alta velocità (Hs) | 3,4 Mbit/s | 100 pF | Drain aperto | Bidirezionale |
Modalità Ultra Fast (UFm) | 5 Mbit/s | Push-pull | Unidirezionale |
Interfaccia
Come la comunicazione UART, l’I2C utilizza solo due fili per trasmettere dati tra i dispositivi:
- SDA (Serial Data) – La linea per il master e lo slave per inviare e ricevere dati.
- SCL (Serial Clock) – La linea che trasporta il segnale di clock (segnale di clock comune tra più master e più slave).
I2C è un protocollo di comunicazione seriale, per cui i dati vengono trasferiti bit per bit lungo un singolo filo (la linea SDA).
Come SPI, l’I2C è sincrono, pertanto l’uscita dei bit è sincronizzata al campionamento dei bit tramite un segnale di clock condiviso tra il master e lo slave. Il segnale di clock è sempre controllato dal master.
Ci saranno più slave e più master, e tutti i master possono comunicare con tutti gli slave.
- Start: La linea SDA passa da un livello di tensione alto a uno basso prima che la linea SCL passi da alto a basso.
- Stop: La linea SDA passa da un livello di tensione basso a uno alto dopo che la linea SCL passi da basso ad alto.
- Address Frame: Una sequenza di 7 o 10 bit univoca per ogni slave che identifica lo slave quando il master desidera comunicare con esso.
- Read/Write Bit: Un singolo bit che specifica se il master sta inviando dati allo slave (livello di tensione basso) o richiedendo dati da esso (livello di tensione alto).
- ACK/NACK Bit: Ogni frame in un messaggio è seguito da un bit di riconoscimento/non riconoscimento. Se un frame di indirizzo o dati viene ricevuto correttamente, viene restituito un bit ACK al mittente dal dispositivo ricevente.
Connessioni dei dispositivi
Poiché l’I2C utilizza l’indirizzamento, più slave possono essere controllati da un singolo master. Con un indirizzo a 7 bit sono disponibili 128 (27) indirizzi univoci. L’utilizzo di indirizzi a 10 bit è raro, ma fornisce 1.024 (210) indirizzi univoci. Fino a 27 dispositivi slave possono essere connessi/indirizzati nel circuito dell’interfaccia I2C.
Più master possono essere collegati a un singolo slave o a più slave. Il problema sorge quando due master tentano di inviare o ricevere dati contemporaneamente sulla linea SDA. Per risolvere questo problema, ogni master deve rilevare se la linea SDA è bassa o alta prima di trasmettere un messaggio. Se la linea SDA è bassa, significa che un altro master ha il controllo del bus e il master deve attendere per inviare il messaggio. Se la linea SDA è alta, allora è sicuro trasmettere il messaggio. Per collegare più master a più slave
ESP32 e I2C
L’ESP32 supporta la comunicazione I2C tramite le sue due interfacce bus I2C. Le utilizzo sempre come master, poiché la modalità slave nell’IDE Arduino non è ancora supportata e ci sono molti problemi a riguardo.
- Modalità standard (100 Kbit/s)
- Modalità fast (400 Kbit/s)
- Fino a 5 MHz, sebbene limitato dalla resistenza di pull-up del SDA
- Modalità di indirizzamento a 7/10 bit
- Modalità di indirizzamento doppio. Gli utenti possono programmare registri di comando per controllare le interfacce I²C, ottenendo così maggiore flessibilità
ESP32 come slave
Al momento, la funzionalità Slave I2C non è implementata nel core Arduino per ESP32 (vedi issue #118).
L’ESP IDF, invece, fornisce solo due funzioni che permettono all’ESP32 di comunicare come dispositivo slave, e sebbene venga specificato che è possibile definire una funzione ISR personalizzata, non esiste alcun esempio su come farlo correttamente (ad esempio, leggendo e cancellando i flag di interrupt).
Esiste una soluzione non standard per permettere all’ESP32 di funzionare come slave, e puoi trovare qui su GitHub la libreria sviluppata a questo scopo, ma in questo articolo non la analizziamo.
Scanner degli indirizzi I2C
Aggiungo questo sketch ora in modo che tu possa testare rapidamente la connessione.
#include <Wire.h>
void setup()
{
Wire.begin();
Serial.begin(9600);
Serial.println("\nI2C Scanner");
}
void loop()
{
byte error, address;
int nDevices;
Serial.println("Scanning...");
nDevices = 0;
for(address = 1; address < 127; address++ )
{
// The i2c_scanner uses the return value of
// the Write.endTransmisstion to see if
// a device did acknowledge to the address.
Wire.beginTransmission(address);
error = Wire.endTransmission();
if (error == 0)
{
Serial.print("I2C device found at address 0x");
if (address<16)
Serial.print("0");
Serial.print(address,HEX);
Serial.println(" !");
nDevices++;
}
else if (error==4)
{
Serial.print("Unknow error at address 0x");
if (address<16)
Serial.print("0");
Serial.println(address,HEX);
}
}
if (nDevices == 0)
Serial.println("No I2C devices found\n");
else
Serial.println("done\n");
delay(5000); // wait 5 seconds for next scan
}
ESP32 come master: uso base
Arduino SAMD Amazon Arduino MKR WiFi 1010
Per questo test utilizzo un Arduino MKR 1010 WiFi come slave; per ulteriori informazioni leggi “i2c Arduino SAMD MKR: interfaccia aggiuntiva SERCOM, rete e scanner degli indirizzi”.
Gli sketch sono gli stessi del precedente articolo: il Master avvia l’interfaccia I2C con i pin predefiniti.
Il mio ESP32 selezionato ESP32 Dev Kit v1 - TTGO T-Display 1.14 ESP32 - NodeMCU V3 V2 ESP8266 Lolin32 - NodeMCU ESP-32S - WeMos Lolin32 - WeMos Lolin32 mini - ESP32-CAM programmer - ESP32-CAM bundle - ESP32-WROOM-32 - ESP32-S
Come si può vedere nell’immagine del pinout, 22 e 21 sono i pin SCL e SDA predefiniti, quindi basta chiamare
Wire.begin(); // join i2c bus (address optional for master)
poi invia una richiesta allo slave
Wire.beginTransmission(0x10); // Start channel with slave 0x10
Wire.write(GET_NAME); // send data to the slave
Wire.endTransmission(); // End transmission
dove 0x10
è l’indirizzo dello slave, lo slave inizializza l’I2C con lo stesso indirizzo
Wire.begin(0x10); // join i2c bus with address 0x10
e attende richieste e dati
// event handler initializations
Wire.onReceive(receiveEvent); // register an event handler for received data
Wire.onRequest(requestEvent); // register an event handler for data requests
in questo caso viene chiamata la funzione di ricezione dati e puoi ottenere i dati inviati
// function that executes whenever data is received by master
// this function is registered as an event, see setup()
void receiveEvent(int numBytes) {
if (numBytes==1){
int requestVal = Wire.read();
Serial.print(F("Received request -> "));
Serial.println(requestVal);
request = static_cast<I2C_REQUEST_TYPE>(requestVal);
}else{
Serial.print(F("No parameter received!"));
}
}
quando i dati vengono ricevuti, il Master apre una richiesta
Wire.requestFrom(0x10, 14); // request 14 bytes from slave device 0x10
while (Wire.available()) { // slave may send less than requested
char c = Wire.read(); // receive a byte as character
Serial.print(c); // print the character
}
con Wire.requestFrom(0x10, 14);
si richiedono 14 byte di dati all’indirizzo 0x10, e si verifica se i dati sono presenti nel buffer; se disponibili, vengono letti.
Ecco lo sketch completo per il Master su ESP32.
/**
* i2c network: send parameter to client and receive response
* with data relative to the request
*
* by Renzo Mischianti <www.mischianti.org>
*
* https://mischianti.org
*
*
* ESP32 <------> Arduino MKR
* GND GND
* 22 12
* 21 11
*
*/
#include <Wire.h>
enum REQUEST_TYPE {
NONE = -1,
GET_NAME = 0,
GET_AGE
};
void setup() {
Wire.begin(); // join i2c bus (address optional for master)
Serial.begin(9600); // start serial for output
while (!Serial){}
Serial.println();
Serial.println(F("Starting request!"));
Wire.beginTransmission(0x10); // Start channel with slave 0x10
Wire.write(GET_NAME); // send data to the slave
Wire.endTransmission(); // End transmission
delay(1000); // added to get better Serial print
Wire.requestFrom(0x10, 14); // request 14 bytes from slave device 0x10
while (Wire.available()) { // slave may send less than requested
char c = Wire.read(); // receive a byte as character
Serial.print(c); // print the character
}
Serial.println();
delay(1000); // added to get better Serial print
Wire.beginTransmission(0x10); // Start channel with slave 0x10
Wire.write(GET_AGE); // send data to the slave
Wire.endTransmission(); // End transmission
delay(1000); // added to get better Serial print
Wire.requestFrom(0x10, 1); // request 1 bytes from slave device 0x10
while (Wire.available()) { // slave may send less than requested
int c = (int)Wire.read(); // receive a byte as character
Serial.println(c); // print the character
}
delay(1000); // added to get better Serial print
Serial.println();
}
void loop() {
}
E lo sketch per lo slave MKR.
/**
* i2c network: send parameter to client and receive response
* with data relative to the request. SLAVE SKETCH 3
*
* by Renzo Mischianti <www.mischianti.org>
*
* https://mischianti.org
*
* Arduino UNO <------> Logic converter <------> Arduino MKR
* GND GND GND GND
* 5v HV LV 3.3v
* A4 HV1 LV1 11
* A5 HV2 LV2 12
*
*/
#include <Wire.h>
enum I2C_REQUEST_TYPE {
NONE = -1,
GET_NAME = 0,
GET_AGE
};
void requestEvent();
void receiveEvent(int numBytes);
I2C_REQUEST_TYPE request = NONE;
void setup() {
Wire.begin(0x10); // join i2c bus with address 0x10
Serial.begin(9600); // start serial for output
while (!Serial){}
// event handler initializations
Wire.onReceive(receiveEvent); // register an event handler for received data
Wire.onRequest(requestEvent); // register an event handler for data requests
}
void loop() {
// delay(100);
}
// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
switch (request) {
case NONE:
Serial.println(F("Not good, no request type!"));
break;
case GET_NAME:
Wire.write("ArduinoMKR "); // send 14 bytes to master
request = NONE;
break;
case GET_AGE:
Wire.write((byte)47); // send 1 bytes to master
request = NONE;
break;
default:
break;
}
}
// function that executes whenever data is received by master
// this function is registered as an event, see setup()
void receiveEvent(int numBytes) {
if (numBytes==1){
int requestVal = Wire.read();
Serial.print(F("Received request -> "));
Serial.println(requestVal);
request = static_cast<I2C_REQUEST_TYPE>(requestVal);
}else{
Serial.print(F("No parameter received!"));
}
}
Qui l’output seriale del Master.
Starting request!
ArduinoMKR
47
Qui l’output seriale dello Slave.
Received request -> 0
Received request -> 1
Collegare dispositivo a 5V
Dispositivi Arduino UNO Arduino UNO - Arduino MEGA 2560 R3 - Arduino Nano - Arduino Pro Mini
Per collegare un dispositivo con livello logico a 5V puoi utilizzare un convertitore di livello logico bidirezionale.
Qui il convertitore di livello logico su Aliexpress
qui uno schema di collegamento semplice.
Interfaccia I2C, cambiare i pin predefiniti e gestire un’interfaccia aggiuntiva
L’inizializzazione predefinita Wire.begin()
può essere anche scritta
Wire.begin(SDA, SCL, 100000L); // SDA = 21, SCL = 22 and frequences = 100Mhz
puoi anche utilizzare la versione “estesa” in questo modo
TwoWire wire0 = new TwoWire(0);
[...]
wire0.begin(SDA, SCL, 100000L); // SDA = 21, SCL = 22 and frequences = 100Mhz
Allo stesso modo puoi inizializzare l’interfaccia I2C secondaria, che è racchiusa in Wire1
Wire1.begin()
oppure
Wire1.begin(18, 19, 100000L); // SDA = 18, SCL = 19 and frequences = 100Mhz
oppure puoi usare la dichiarazione estesa con indice 1 invece di 0.
TwoWire wire1 = new TwoWire(1);
[...]
wire0.begin(18, 19, 100000L); // SDA = 18, SCL = 19 and frequences = 100Mhz
e lo schema di cablaggio diventa
Grazie
- ESP32: piedinatura, specifiche e configurazione dell’Arduino IDE
- ESP32: fileSystem integrato SPIFFS
- ESP32: gestire più seriali e logging per il debug
- ESP32 risparmio energetico pratico
- ESP32 risparmio energetico pratico: gestire WiFi e CPU
- ESP32 risparmio energetico pratico: modem e light sleep
- ESP32 risparmio energetico pratico: deep sleep e ibernazione
- ESP32 risparmio energetico pratico: preservare dati al riavvio, sveglia a tempo e tramite tocco
- ESP32 risparmio energetico pratico: sveglia esterna e da ULP
- ESP32 risparmio energetico pratico: sveglia da UART e GPIO
- ESP32: filesystem integrato LittleFS
- ESP32: filesystem integrato FFat (Fat/exFAT)
- ESP32-wroom-32
- ESP32-CAM
- ESP32: ethernet w5500 con chiamate standard (HTTP) e SSL (HTTPS)
- ESP32: ethernet enc28j60 con chiamate standard (HTTP) e SSL (HTTPS)
- Come usare la scheda SD con l’esp32
- esp32 e esp8266: file system FAT su memoria SPI flash esterna
- Gestione aggiornamenti firmware e OTA
- Gestione del firmware
- Aggiornamento OTA con Arduino IDE
- Aggiornamento OTA con browser web
- Aggiornamenti automatici OTA da un server HTTP
- Aggiornamento del firmware non standard
- Integrare LAN8720 con ESP32 per la connettività Ethernet con plain (HTTP) e SSL (HTTPS)
- Collegare l’EByte E70 (CC1310) ai dispositivi ESP32 c3/s3 ed un semplice sketch di esempio
- ESP32-C3: piedinatura, specifiche e configurazione dell’IDE Arduino
- Integrazione del modulo W5500 su ESP32 con Core 3: supporto nativo ai protocolli Ethernet con SSL e altre funzionalità
- Integrazione del modulo LAN8720 su ESP32 con Core 3: supporto nativo del protocollo Ethernet con SSL e altre funzionalità.
- Dallas DS18B20
- Dallas DS18B20 con ESP32 ed ESP8266: introduzione e modalità parasita
- Dallas DS18B20 con ESP32 ed ESP8266: gate P-MOSFET pull-up e allarmi
- Dallas DS18B20 con ESP32 ed ESP8266: tutte le topologie OneWire, lunghe derivazioni e più dispositivi
- Guida all’I2C su ESP32: comunicazione con dispositivi eterogenei 5v 3.3v, gestione interfacce aggiuntive
- i2c Arduino: come creare una rete, parametri e scanner di indirizzi
- i2c Arduino SAMD MKR: interfaccia aggiuntiva SERCOM, rete e scanner di indirizzi
- i2c esp8266: how to, rete 5v, 3.3v, velocità e scanner di indirizzi
- Guida all’I2C su ESP32: comunicazione con dispositivi eterogenei 5v 3.3v, gestione interfacce aggiuntive