Guida all’I2C su ESP32: comunicazione con dispositivi eterogenei 5v 3.3v, gestione interfacce aggiuntive e scanner

Spread the love

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.

Protocollo I2C esp32

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
PilotaggioDirezione
Modalità Standard (Sm)100 kbit/s400 pFDrain apertoBidirezionale
Modalità Fast (Fm)400 kbit/s400 pFDrain apertoBidirezionale
Modalità Fast Plus (Fm+)1 Mbit/s550 pFDrain apertoBidirezionale
Modalità ad alta velocità (Hs)1,7 Mbit/s400 pFDrain apertoBidirezionale
Modalità ad alta velocità (Hs)3,4 Mbit/s100 pFDrain apertoBidirezionale
Modalità Ultra Fast (UFm)5 Mbit/sPush-pullUnidirezionale

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

  1. ESP32: piedinatura, specifiche e configurazione dell’Arduino IDE
  2. ESP32: fileSystem integrato SPIFFS
  3. ESP32: gestire più seriali e logging per il debug
  4. ESP32 risparmio energetico pratico
    1. ESP32 risparmio energetico pratico: gestire WiFi e CPU
    2. ESP32 risparmio energetico pratico: modem e light sleep
    3. ESP32 risparmio energetico pratico: deep sleep e ibernazione
    4. ESP32 risparmio energetico pratico: preservare dati al riavvio, sveglia a tempo e tramite tocco
    5. ESP32 risparmio energetico pratico: sveglia esterna e da ULP
    6. ESP32 risparmio energetico pratico: sveglia da UART e GPIO
  5. ESP32: filesystem integrato LittleFS
  6. ESP32: filesystem integrato FFat (Fat/exFAT)
  7. ESP32-wroom-32
    1. ESP32-wroom-32: flash, piedinatura, specifiche e configurazione dell’Arduino IDE
  8. ESP32-CAM
    1. ESP32-CAM: piedinatura, specifiche e configurazione dell’Arduino IDE
    2. ESP32-CAM: upgrade CamerWebServer con gestione della luce flash
  9. ESP32: ethernet w5500 con chiamate standard (HTTP) e SSL (HTTPS)
  10. ESP32: ethernet enc28j60 con chiamate standard (HTTP) e SSL (HTTPS)
  11. Come usare la scheda SD con l’esp32
  12. esp32 e esp8266: file system FAT su memoria SPI flash esterna
  13. Gestione aggiornamenti firmware e OTA
    1. Gestione del firmware
      1. ESP32: flash del firmware binario compilato (.bin)
      2. ESP32: flash del firmware e filesystem (.bin) con strumenti grafici
    2. Aggiornamento OTA con Arduino IDE
      1. Aggiornamenti OTA su ESP32 con Arduino IDE: filesystem, firmware e password
    3. Aggiornamento OTA con browser web
      1. Aggiornamenti OTA su ESP32 tramite browser web: firmware, filesystem e autenticazione
      2. Aggiornamenti OTA su ESP32 tramite browser web: caricamento in HTTPS (SSL/TLS) con certificato autofirmato
      3. Aggiornamenti OTA su ESP32 tramite browser web: interfaccia web personalizzata
    4. Aggiornamenti automatici OTA da un server HTTP
      1. Aggiornamento automatico Firmware OTA dell’ESP32 dal server
      2. Aggiornamento automatico Firmware OTA dell’ESP32 dal server con controllo della versione
      3. Aggiornamento automatico Firmware OTA dell’ESP32 in HTTPS (SSL/TLS) con certificato autofirmato affidabile
    5. Aggiornamento del firmware non standard
      1. Aggiornamento firmware e filesystem ESP32 dalla scheda SD
      2. Aggiornamento firmware e filesystem ESP32 con client FTP
  14. Integrare LAN8720 con ESP32 per la connettività Ethernet con plain (HTTP) e SSL (HTTPS)
  15. Collegare l’EByte E70 (CC1310) ai dispositivi ESP32 c3/s3 ed un semplice sketch di esempio
  16. ESP32-C3: piedinatura, specifiche e configurazione dell’IDE Arduino
  17. Integrazione del modulo W5500 su ESP32 con Core 3: supporto nativo ai protocolli Ethernet con SSL e altre funzionalità
  18. Integrazione del modulo LAN8720 su ESP32 con Core 3: supporto nativo del protocollo Ethernet con SSL e altre funzionalità.
  19. Dallas DS18B20
  20. Guida all’I2C su ESP32: comunicazione con dispositivi eterogenei 5v 3.3v, gestione interfacce aggiuntive

  1. i2c Arduino: come creare una rete, parametri e scanner di indirizzi
  2. i2c Arduino SAMD MKR: interfaccia aggiuntiva SERCOM, rete e scanner di indirizzi
  3. i2c esp8266: how to, rete 5v, 3.3v, velocità e scanner di indirizzi
  4. Guida all’I2C su ESP32: comunicazione con dispositivi eterogenei 5v 3.3v, gestione interfacce aggiuntive

Spread the love

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *