Amo il protocollo I2C. Quando ho bisogno di un sensore, ogni volta cerco di trovarne uno con questo protocollo, ho anche scritto alcune librerie per vari sensori che utilizzano l’I2C. Quindi voglio scrivere alcuni articoli che spiegano (Arduino, Arduino SAMD MKR, esp8266 e esp32) alcune caratteristiche interessanti, e cercherò di spiegare come risolvere i problemi che si possono avere quando si lavora con più dispositivi I2C.
Utilizzo l’esp8266 in molte situazioni, ed è uno dei dispositivi più economici in commercio. In questo testo, ci addentreremo nelle specifiche del protocollo I2C sull’ESP8266, inclusi i metodi di comunicazione tra dispositivi che operano a diversi livelli di tensione, come 5V e 3.3V. Impareremo anche come manipolare la velocità di comunicazione e utilizzare pin personalizzati per adattarsi alle esigenze dei nostri progetti.
Introduzione al protocollo I2C
Il I2C (Inter-Integrated Circuit, eye-squared-C) è noto anche come I2C o IIC. È un bus di comunicazione seriale monodirezionale a commutazione di pacchetto, multi-master, multi-slave, sincrono. Inventato nel 1982 da Philips Semiconductors. È ampiamente utilizzato per collegare IC periferici a bassa velocità a processori e microcontrollori per la comunicazione a breve distanza, all’interno della scheda. (cit. WiKi)
Velocità
L’I2C supporta 100 kbps, 400 kbps e 3.4 Mbps. Alcune varianti supportano anche 10 Kbps e 1 Mbps.
Modalità | Velocità massima | Capacitance massima | Drive | Direzione |
---|---|---|---|---|
Modalità standard (Sm) | 100 kbit/s | 400 pF | Open drain | Bidirezionale |
Modalità veloce (Fm) | 400 kbit/s | 400 pF | Open drain | Bidirezionale |
Modalità Fast-plus (Fm+) | 1 Mbit/s | 550 pF | Open drain | Bidirezionale |
Modalità ad alta velocità (Hs) | 1.7 Mbit/s | 400 pF | Open drain | Bidirezionale |
Modalità ad alta velocità (Hs) | 3.4 Mbit/s | 100 pF | Open drain | Bidirezionale |
Modalità ultra veloce (UFm) | 5 Mbit/s | Push-pull | Unidirezionale |
Interfaccia
Come la comunicazione UART, l’I2C utilizza solo due fili per trasmettere dati tra dispositivi:
- SDA (Serial Data) – La linea per l’invio e la ricezione di dati tra master e slave.
- SCL (Serial Clock) – La linea che porta il segnale di clock (segnale di clock comune tra più master e più slave).
L’I2C è un protocollo di comunicazione seriale, quindi i dati vengono trasferiti bit per bit lungo un singolo filo (la linea SDA).
Come lo SPI, l’I2C è sincrono, quindi l’emissione dei bit è sincronizzata con il campionamento dei bit da parte di un segnale di clock condiviso tra il master e lo slave. Il segnale di clock è sempre controllato dal master.
Ci possono essere più slave e più master e tutti i master possono comunicare con tutti gli slave.
- Inizio: La linea SDA passa da un livello di tensione alto a un livello di tensione basso prima che la linea SCL passi da alta a bassa.
- Fine: La linea SDA passa da un livello di tensione basso a un livello di tensione alto dopo che la linea SCL passa da bassa ad alta.
- Indirizzo Frame: Una sequenza di 7 o 10 bit unica per ogni slave che identifica lo slave quando il master vuole comunicare con esso.
- Bit di lettura/scrittura: 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).
- Bit ACK/NACK: Ogni frame in un messaggio è seguito da un bit di acknowledge/no-acknowledge. Se un frame di indirizzo o un frame di dati è stato ricevuto con successo, un bit ACK viene restituito 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 (2^7) indirizzi unici. L’uso di indirizzi a 10 bit è raro, ma fornisce 1.024 (2^10) indirizzi unici. Fino a 27 dispositivi slave possono essere collegati/indirizzati nel circuito dell’interfaccia I2C.
Più master possono essere collegati a un singolo slave o a più slave. Il problema con più master nello stesso sistema si verifica quando due master cercano di inviare o ricevere dati allo stesso tempo 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 dovrebbe attendere per inviare il messaggio. Se la linea SDA è alta, allora è sicuro trasmettere il messaggio. Per collegare più master a più slave
esp8266 How To
Innanzitutto, penso che sarebbe meglio se leggessi “WeMos D1 mini (esp8266): piedinatura, caratteristiche e configurazione dell’Arduino IDE“.
Qui trovi alcuni esp8266 Aliexpress esp-12 - Aliexpress esp-07
L’ESP8266 è un microchip Wi-Fi a basso costo con una pila TCP/IP completa e la capacità di microcontroller, prodotto da Espressif Systems a Shanghai, Cina.
Ha un buon set di interfacce:
- SPI
- I²C (implementazione software)
- I²S interfacce con DMA (condivisione pin con GPIO)
- UART su pin dedicati, più un UART solo trasmissione, può essere abilitato su GPIO2
- ADC a 10 bit (ADC ad approssimazione successiva)
Questo dispositivo ha le stesse limitazioni, prima di tutto, non può funzionare come slave, ma puoi scegliere tutti i pin che vuoi per l’I2C.
Come puoi vedere nell’immagine D1 e D2 sono i pin predefiniti per SCL e SDA .
Problemi con lo slave quando non si utilizza un dispositivo esp
A partire dalla versione 2.5.0 del core esp8266, c’è il supporto per la modalità SLAVE, ma non funziona correttamente come spiegato qui.
Puoi provare se il tuo chip funziona come slave aggiungendo queste impostazioni al tuo dispositivo.
Wire.pins(D2, D1); // Pin predefiniti, puoi rimuoverlo
Wire.begin(0x12); // unisciti al bus i2c con indirizzo 0x12
Wire.setClockStretchLimit(1500);
Wire.setClock(10000L); // Max 50kHz
Ed è molto importante che tu possa compilare a 160Mhz CPU.
Sto facendo alcuni test, e con WeMos D1 mini (clone cinese), quando un Arduino UNO tramite convertitore logico, ottengo questo risultato nella scansione dell’indirizzo i2c.
Scanning...
Errore sconosciuto all'indirizzo 0x12
fatto
Trova il dispositivo ma non funziona correttamente. Con Arduino MKR 1010 senza convertitore logico, ho questo risultato.
Scanning...
Trovato dispositivo I2C all'indirizzo 0x25 !
Trovato dispositivo I2C all'indirizzo 0x60 !
Trovato dispositivo I2C all'indirizzo 0x6B !
fatto
Trova un dispositivo con indirizzo 0x25.
Rete master-slave con gestione del clock
Ecco il WeMos che uso per questi esempi WeMos D1 mini - NodeMCU V2 V2.1 V3 - esp01 - esp01 programmer
Puoi ottenere alcuni concetti di base dall’articolo Arduino “i2c Arduino: come creare una rete, parametri e scanner di indirizzi“.
Ecco alcuni micro esp8266 su WeMos D1 mini - NodeMCU V2 V2.1 V3 - esp01 - esp01 programmer
Per la configurazione slave, devi limitare la velocità I2C a MASTER e a SLAVE, il limite su connessione diretta con WeMos D1 mini è 40kHz, quindi devi aggiungere questo comando dopo l’inizio.
Wire.begin(); // join i2c bus (address optional for master)
Wire.setClock(40000L); // Set speed at 40kHz
Per inviare un parametro al dispositivo slave, devi stabilire una connessione, quindi inviare i dati e segnalare che la comunicazione è terminata.
Wire.beginTransmission(0x12); // Avvia canale con slave 0x12
Wire.write(GET_NAME); // invia dati allo slave
Wire.endTransmission(); // Termina la trasmissione
Lo slave deve registrare un evento per gestire questa operazione.
Wire.onReceive(receiveEvent); // registra un gestore di eventi per i dati ricevuti
Nella funzione impostata come parametro, devi gestire la ricezione dei dati, invio un’istruzione di lunghezza di un byte, quindi il numBytes è 1, quindi devi solo recuperare le informazioni di un byte e convertire nel formato di cui hai bisogno e salvarle nell’ambito principale dello sketch.
// funzione che si esegue ogni volta che i dati vengono ricevuti dal master
// questa funzione è registrata come un evento, vedere setup()
void receiveEvent(int numBytes) {
if (numBytes==1){
int requestVal = Wire.read();
Serial.print(F("Richiesta ricevuta -> "));
Serial.println(requestVal);
request = static_cast<REQUEST_TYPE>(requestVal);
}else{
Serial.print(F("Nessun parametro ricevuto!"));
}
}
Ora il Master è pronto per richiedere i dati di cui ha bisogno.
Wire.requestFrom(0x08, 14); // richiede 14 byte dal dispositivo slave 0x08
Lo slave, alla richiesta fatta dal master, restituisce le informazioni relative.
// 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("esp8266 "); // send 14 bytes to master
request = NONE;
break;
case GET_AGE:
Wire.write((byte)45); // send 1 bytes to master
request = NONE;
break;
default:
break;
}
}
Ora lo sketch Master completo:
/**
* i2c network: send parameter to client and receive response
* with data relative to the request
*
* by Renzo Mischianti <www.mischianti.org>
*
* https://mischianti.org
*
* esp8266 <------> esp8266
* GND GND
* D1 D1
* D2 D2
*
*/
#include <Wire.h>
enum REQUEST_TYPE {
NONE = -1,
GET_NAME = 0,
GET_AGE
};
void setup() {
Wire.begin(); // join i2c bus (address optional for master)
Wire.setClock(40000L); // Set speed at 40kHz
Serial.begin(9600); // start serial for output
while (!Serial){}
Serial.println();
Serial.println(F("Starting request!"));
Wire.beginTransmission(0x12); // Start channel with slave 0x12
Wire.write(GET_NAME); // send data to the slave
Wire.endTransmission(); // End transmission
delay(1000); // added to get better Serial print
Wire.requestFrom(0x12, 14); // request 14 bytes from slave device 0x12
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(0x12); // Start channel with slave 0x12
Wire.write(GET_AGE); // send data to the slave
Wire.endTransmission(); // End transmission
delay(1000); // added to get better Serial print
Wire.requestFrom(0x12, 1); // request 1 bytes from slave device 0x12
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 ora lo sketch completo Slave:
/**
* i2c network: send parameter to client and receive response
* with data relative to the request. SLAVE SKETCH
*
* by Renzo Mischianti <www.mischianti.org>
*
* https://mischianti.org
*
* esp8266 <------> esp8266
* GND GND
* D1 D1
* D2 D2
*
*/
#include <Wire.h>
enum REQUEST_TYPE {
NONE = -1,
GET_NAME = 0,
GET_AGE
};
void requestEvent();
void receiveEvent(int numBytes);
REQUEST_TYPE request = NONE;
void setup() {
// Wire.pins(D2, D1);
Wire.begin(0x12); // join i2c bus with address 0x12
// Wire.setClockStretchLimit(1500);
// Wire.setClock(50000L);
Serial.begin(9600); // start serial for output
while (!Serial){}
Serial.println(F("Starting!!"));
// 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("esp8266 "); // send 14 bytes to master
request = NONE;
break;
case GET_AGE:
Wire.write((byte)45); // 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<REQUEST_TYPE>(requestVal);
}else{
Serial.print(F("No parameter received!"));
}
}
Ora il serial output del Master :
17:10:06: Starting request!
17:10:07: esp8266
17:10:09: 45
ed il serial output dello Slave:
17:10:06: Received request -> 0
17:10:08: Received request -> 1
Scanner indirizzi I2C
Uno degli sketch più utili quando si utilizza i2c è lo scanner degli indirizzi. Questo semplice programma cerca di trovare tutti i dispositivi collegati al bus I2C.
#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
}
Rete: un master e più slave (logica 3.3v e 5v)
Quando provi a connettere dispositivi eterogenei come un Arduino UNO probabilmente devi modificare il tuo codice.
Qui il convertitore di livello logico che uso per questo esempio Aliexpress
Qui l'Arduino UNO Arduino UNO - Arduino MEGA 2560 R3 - Arduino Nano - Arduino Pro Mini
Innanzitutto, puoi impostare il limite di estensione del clock e impostare il clock I2C su 40kHz e, cosa molto importante, compilerai lo sketch con 160MHz di velocità del processore.
Wire.begin(0x12); // join i2c bus with address 0x12
// ------ If you have throuble uncomment these lines --------
// ------ and compile with processor speed at 160Mhz --------
Wire.setClockStretchLimit(1500);
Wire.setClock(40000L);
// ----------------------------------------------------------
Clock Stretching
Sebbene il controllo della linea SCL sia dominio dell’I2C master, una funzione opzionale del protocollo consente agli slave di controllarla temporaneamente per rallentare la trasmissione prima che sia pronta ad accettare più dati.
Per estendere l’orologio, il dispositivo slave tiene semplicemente abbassata la linea SCL. In quello stato, il dispositivo master deve attendere che il clock ritorni ad alto prima di riprendere la trasmissione.
Questa funzione a volte è fonte di problemi: non tutti i dispositivi la supportano e un dispositivo master senza supporto per l’estensione dell’orologio genererà errori se un dispositivo slave tenta di mantenere basso l’orologio.
Poi lo sketch diventa come l’altro, qui il master.
/**
* i2c network: send parameter to client and receive response
* with data relative to the request
*
* by Renzo Mischianti <www.mischianti.org>
*
* https://mischianti.org
*
*
* Arduino UNO <------> Logic converter <------> esp8266 Master <------> esp8266 Slave
* GND GND GND GND GND
* 5v HV LV 3.3v
* A4 HV1 LV1 D1 D1
* A5 HV2 LV2 D2 D2
*
*/
#include <Wire.h>
enum REQUEST_TYPE {
NONE = -1,
GET_NAME = 0,
GET_AGE
};
void setup() {
Wire.begin(); // join i2c bus (address optional for master)
Wire.setClock(40000L); // Set speed at 40kHz
Serial.begin(9600); // start serial for output
while (!Serial){}
Serial.println();
Serial.println(F("Starting request!"));
Wire.beginTransmission(0x08); // Start channel with slave 0x08
Wire.write(GET_NAME); // send data to the slave
Wire.endTransmission(); // End transmission
delay(1000); // added to get better Serial print
Wire.requestFrom(0x08, 14); // request 14 bytes from slave device 0x08
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(0x12); // Start channel with slave 0x12
Wire.write(GET_NAME); // send data to the slave
Wire.endTransmission(); // End transmission
delay(1000); // added to get better Serial print
Wire.requestFrom(0x12, 14); // request 14 bytes from slave device 0x12
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(0x08); // Start channel with slave 0x08
Wire.write(GET_AGE); // send data to the slave
Wire.endTransmission(); // End transmission
delay(1000); // added to get better Serial print
Wire.requestFrom(0x08, 1); // request 1 bytes from slave device 0x08
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();
delay(1000); // added to get better Serial print
Wire.beginTransmission(0x12); // Start channel with slave 0x12
Wire.write(GET_AGE); // send data to the slave
Wire.endTransmission(); // End transmission
delay(1000); // added to get better Serial print
Wire.requestFrom(0x12, 1); // request 1 bytes from slave device 0x12
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 qui lo slave Arduino UNO
/**
* i2c network: send parameter to client and receive response
* with data relative to the request. SLAVE SKETCH
*
* by Renzo Mischianti <www.mischianti.org>
*
* https://mischianti.org
*
* Arduino UNO <------> Logic converter <------> esp8266 Master <------> esp8266 Slave
* GND GND GND GND GND
* 5v HV LV 3.3v
* A4 HV1 LV1 D1 D1
* A5 HV2 LV2 D2 D2
*
*/
#include <Wire.h>
enum REQUEST_TYPE {
NONE = -1,
GET_NAME = 0,
GET_AGE
};
void requestEvent();
void receiveEvent(int numBytes);
REQUEST_TYPE request = NONE;
void setup() {
Wire.begin(0x08); // join i2c bus with address #8
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("ArduinoUNO "); // send 14 bytes to master
request = NONE;
break;
case GET_AGE:
Wire.write((byte)43); // 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<REQUEST_TYPE>(requestVal);
}else{
Serial.print(F("No parameter received!"));
}
}
E puoi recuperare il precedente WeMos D1 slave sketch.
Se si tenta di lanciare lo scanner I2C dal master (dopo la configurazione dello slave), si ottiene questo risultato.
22:33:55: Scanning...
22:33:55: I2C device found at address 0x08 !
22:33:55: I2C device found at address 0x12 !
22:33:55: done
Con lo sketch dei parametri Master si ottiene questa uscita seriale.
22:51:28: Starting request!
22:51:29: ArduinoUNO
22:51:31: esp8266
22:51:33: 43
22:51:36: 45
Rete semplice con pin I2C non standard
Con esp8266, non puoi aggiungere una nuova interfaccia I2C, ma puoi utilizzare quasi tutti i pin disponibili per il canale standard, quindi se non puoi usare D6 come SDA e D5 come SCL devi aggiungere questo comando prima di iniziare.
Wire.pins(D6, D5);
Lo sketch diventa così.
/**
* i2c network: send parameter to client and receive response
* with data relative to the request. SLAVE SKETCH
*
* by Renzo Mischianti <www.mischianti.org>
*
* https://mischianti.org
*
* esp8266 <------> esp8266
* GND GND
* D1 D5
* D2 D6
*
*/
#include <Wire.h>
enum REQUEST_TYPE {
NONE = -1,
GET_NAME = 0,
GET_AGE
};
void requestEvent();
void receiveEvent(int numBytes);
REQUEST_TYPE request = NONE;
void setup() {
Wire.pins(D6, D5);
Wire.begin(0x12); // join i2c bus with address 0x12
// ------ If you have throuble uncomment these lines --------
// ------ and compile with processor speed at 160Mhz --------
Wire.setClockStretchLimit(1500);
Wire.setClock(40000L);
// ----------------------------------------------------------
Serial.begin(9600); // start serial for output
while (!Serial){}
Serial.println(F("Starting!!"));
// 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("esp8266 "); // send 14 bytes to master
request = NONE;
break;
case GET_AGE:
Wire.write((byte)45); // 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<REQUEST_TYPE>(requestVal);
}else{
Serial.print(F("No parameter received!"));
}
}
Grazie
- WeMos D1 mini (esp8266): caratteristiche e configurazione dell’Arduino IDE
- WeMos D1 mini (esp8266): SPIFFS Filesystem integrato
- WeMos D1 mini (esp8266): debug sulla seriale secondaria
- WeMos D1 mini (esp8266), i tre tipi di modalità di sospensione per gestire il risparmio energetico
- WeMos D1 mini (esp8266): FileSystem integrato LittleFS
- esp12 esp07 (esp8266): flash, piedinatura, spec e config dell’IDE Arduino
- Firmware and OTA update
- Gestione del firmware
- Aggiornamenti OTA con Arduino IDE
- Aggiornamenti OTA con Web Browser
- Aggiornamenti OTA automatico da server HTTP
- Aggiornamenti firmware non standard
- esp32 e esp8266: file system FAT su memoria SPI flash esterna
- i2c esp8266: how to, rete 5V, 3.3V, velocità e pin personalizzati
- …
- 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