i2c Arduino: come creare una rete, parametri e scanner di indirizzi

Spread the love

Amo il protocollo I2C e quando ho bisogno di un sensore, ogni volta provo a trovarne uno con questo protocollo, ho anche scritto alcune librerie per vari sensori che utilizzano I2C. Quindi voglio scrivere alcuni articoli che spiegano (Arduino, Arduino SAMD MKR, esp8266 e esp32) alcune interessanti funzionalità e cercherò di spiegare come risolvere i problemi che puoi avere quando lavori con più dispositivi I2C.

Arduino i2c protocol
Arduino i2c protocol

In questo primo articolo vedremo come creare una rete con i nostri Arduino UNO e MEGA. Arduino ha una caratteristica interessante che può funzionare sia come Master che come Slave.

Introduzione al protocollo I2C

I2C (Inter-Integrated Circuiteye-squared-C) in alternativa noto come I2C o IIC. È un bus di comunicazione seriale sincronomulti-master, multi-slave, a commutazione di pacchetto, single-ended. È stato inventato nel 1982 da Philips Semiconductors. È ampiamente utilizzato per collegare circuiti integrati periferici a bassa velocità a processori e microcontrollori nelle comunicazioni 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
Capacità 
massima
DriveDirezione
Modalità standard (Sm)100 kbit/s400 pFOpen drainBidirezionale
Modalità veloce (Fm)400 kbit/s400 pFOpen drainBidirezionale
Modalità veloce Plus (Fm+)1 Mbit/s550 pFOpen drainBidirezionale
Modalità ad alta velocità (Hs)1.7 Mbit/s400 pFOpen drainBidirezionale
Modalità ad alta velocità (Hs)3.4 Mbit/s100 pFOpen drainBidirezionale
Modalità ultra veloce (UFm)5 Mbit/sPush-pullUnidirezionale

Interfaccia

Come la comunicazione UART, I2C utilizza solo due fili per trasmettere dati tra 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 standard tra più master e più slave).

I2C è un protocollo di comunicazione seriale, quindi i dati vengono trasferiti bit per bit lungo un unico filo (la linea SDA).

Come SPI, I2C è sincrono, quindi l’uscita dei bit è sincronizzata al campionamento dei bit da un segnale di clock condiviso tra il master e lo slave. Il master controlla sempre il segnale di clock.

Ci saranno più slave e più master e tutti i master potranno comunicare con tutti gli slave.

i2c data packet
i2c data packet
  • Inizio:  La linea SDA passa da un livello di alta tensione a un livello di bassa tensione prima che la linea SCL passi da un livello alto a basso.
  • Stop:  la linea SDA passa da un livello di bassa tensione a un livello di alta tensione dopo che la linea SCL è passata da un livello basso ad alto.
  • Frame di indirizzo:  una sequenza di 7 o 10 bit univoca per ogni slave identifica lo slave quando il master vuole parlargli.
  • Bit di lettura/scrittura:  un singolo bit che specifica se il master sta inviando dati allo slave (livello di bassa tensione) o richiedendo dati da esso (livello di alta tensione).
  • Bit ACK/NACK:  ogni frame in un messaggio è seguito da un bit di riconoscimento/non riconoscimento. Se un frame di indirizzo o un frame di dati è stato ricevuto correttamente, un bit ACK viene restituito al mittente dal dispositivo ricevente.

Connessioni dei dispositivi

i2c wiring one master one slave
i2c wiring one master one slave

Poiché I2C utilizza l’indirizzamento, più slave possono essere controllati da un unico master. È possibile collegare/indirizzare fino a 27 dispositivi slave nel circuito di interfaccia I2C. 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.

i2c wiring one master multiple slave
i2c wiring one master multiple slaves

È possibile collegare più master a un singolo slave o più slave. Il problema con più master nello stesso sistema si verifica quando due master tentano di inviare o ricevere dati contemporaneamente sulla linea SDA. Ogni master deve rilevare se la linea SDA è bassa o alta prima di trasmettere un messaggio per risolvere questo problema. 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, è possibile trasmettere il messaggio. Per collegare più master a più slave

i2c wiring multiple master multiple slave
i2c wiring multiple masters multiple slaves

Distanza

La stragrande maggioranza delle applicazioni utilizza I2C nel modo in cui è stato progettato inizialmente: circuiti integrati periferici collegati direttamente a un processore sulla stessa scheda a circuito stampato e quindi su distanze relativamente brevi inferiori a 30 cm (1 piede) senza connettore. Tuttavia, utilizzando un driver diverso, una versione alternativa di I2C può comunicare fino a 20 metri (possibilmente oltre 100 metri) su CAT5 o altri cavi.

How to con Arduino

Ecco i miei Arduino utilizzati negli esempi Arduino UNO - Arduino MEGA 2560 R3 - Arduino Nano - Arduino Pro Mini

Come tutte le persone sanno per usare il protocollo i 2 c, è necessaria la libreria Wire .

Ogni scheda Arduino ha hardware i2c integrato ma utilizza pin diversi.

BoardI2C / TWI pins
Uno, EthernetA4 (SDA), A5 (SCL)
Mega256020 (SDA), 21 (SCL)
Leonardo2 (SDA), 3 (SCL)
Due20 (SDA), 21 (SCL), SDA1, SCL1

Arduino UNO aveva solo un i2c e i pin sono statici A4 (SDA) e A5 (SCL) e Wire e non può specificare altri pin, quindi la gestione è diventata semplice.

Arduino pcf8574 IC wiring schema 8 leds
Arduino pcf8574 IC wiring schema eight LEDs

Ecco un semplice codice che legge da Wire e stampa su Serial.

#include <Wire.h>

void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
}

void loop() {
  Wire.requestFrom(8, 6);    // request 6 bytes from slave device #8

  while (Wire.available()) { // slave may send less than requested
    char c = Wire.read(); // receive a byte as character
    Serial.print(c);         // print the character
  }
}

È possibile specificare l’indirizzo come parametro della begin.

Wire.begin(0x22);

Rete

Arduino ha la possibilità di autoassegnare un indirizzo I2C in modo da poter creare una rete.

Un master, uno slave

i2c Arduino UNO network master slave
i2c Arduino UNO network master slave

L’esempio più comune che puoi trovare su Internet è questo.

Codice Master

Il master chiede allo slave con indirizzo 0x08, 6 byte,

  Wire.requestFrom(0x08, 6);    // request 6 bytes from slave device with address 0x08

e se c’è qualcosa nel buffer

  while (Wire.available()) { // slave may send less than requested

Legge il byte e scrive sulla console.

    char c = Wire.read(); // receive a byte as character
    Serial.print(c);         // print the character

Ecco lo sketch completo.

#include <Wire.h>

void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
}

void loop() {
  Wire.requestFrom(0x08, 6);    // request 6 bytes from slave device with address 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
  }

  delay(500);
}

Codice Slave

Lo slave ha impostato il proprio indirizzo su 0x08

  Wire.begin(0x08);                // join i2c bus with address 0x08

e quando si riceve una singola richiesta,

  Wire.onRequest(requestEvent); // register event

eseguire un evento che scrive 6 byte (la stringa “ciao”) nel buffer.

  Wire.write("hello "); // respond with message of 6 bytes

Ecco il codice sketch completo.

#include <Wire.h>

void setup() {
  Wire.begin(0x08);                // join i2c bus with address 0x08
  Wire.onRequest(requestEvent); // register event
}

void loop() {
  delay(100);
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
  Wire.write("hello "); // respond with message of 6 bytes
  // as expected by master
}

Ecco l’uscita seriale del master.

Connetti alla porta seriale COM5 a 9600
hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello hello

Inviare una parametro nella request

Se si desidera inviare un parametro al dispositivo slave, è necessario stabilire una connessione, quindi inviare i dati e avvisare che la comunicazione è terminata.

	Wire.beginTransmission(0x08); 	// Start channel with slave 0x08
	Wire.write(GET_NAME);        	// send data to the slave
	Wire.endTransmission();       	// End transmission

Lo slave deve registrare un evento per gestire questa operazione.

	Wire.onReceive(receiveEvent);    // register an event handler for received data

È necessario gestire la ricezione dei dati nella funzione. Ho inviato un’istruzione di lunghezza di un byte, quindi numBytes era uno, perciò devi recuperare solo le informazioni di un byte e convertirle nel formato che ti serve e salvarle nello scope principale dello sketch.

// 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 Master è pronto per richiedere i dati. Ha bisogno di

	Wire.requestFrom(0x08, 14);    // request 14 bytes from slave device 0x08

Lo slave alla richiesta fatta dal master restituisce le relative informazioni.

// 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;
	}
}

Ecco lo sketch completo del master.

/**
 * i2c network: send parameter to client and receive response
 * with data relative to the request. MASTER SKETCH
 *
 * by Renzo Mischianti <www.mischianti.org>
 *
 * https://mischianti.org
 *
 * Arduino UNO <------> Arduino UNO
 * GND					GND
 * A4					A4
 * A5					A5
 *
 */

#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){}

	Wire.beginTransmission(0x08); 	// Start channel with slave 0x08
	Wire.write(GET_NAME);        		// send data to the slave
	Wire.endTransmission();       	// End transmission

    // Now the request

	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(0x08); 	// Start channel with slave 0x08
	Wire.write(GET_AGE);        		// send data to the slave
	Wire.endTransmission();       	// End transmission

    // Now the request

	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
	}
}

void loop() {
}

Ed ecco lo schizzo completo dello 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
 *
 * Arduino UNO <------> Arduino UNO
 * GND					GND
 * A4					A4
 * A5					A5
 *
 */

#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!"));
	}
}

L’output Seriale del master è questa.

ArduinoUNO     
43 

E qui, lo slave.

Received request -> 0
Received request -> 1 
Un master e slaves multipli

Quando si comprende il concetto di base, le modifiche aggiuntive diventano semplici. Ora aggiungeremo un altro dispositivo che funziona come uno slave.

i2c Arduino UNO MEGA network master multiple slave
i2c Arduino UNO MEGA network master multiple slaves

Qui lo sketch dello SLAVE secondario l’Arduino MEGA (o altro Arduino).

/**
 * i2c network: send parameter to client and receive response
 * with data relative to the request. SLAVE SKETCH 2
 *
 * by Renzo Mischianti <www.mischianti.org>
 *
 * https://mischianti.org
 *
 * Arduino UNO <------> Arduino MEGA
 * GND					GND
 * A4					20
 * A5					21
 *
 */

#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(0x09);                // 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("ArduinoMEGA   "); // 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!"));
	}
}

Come puoi vedere, cambio solo l’indirizzo 0x09, il nome “ArduinoMEGA” e l’età 45.

Nello sketch Master, aggiungiamo la nuova richiesta.

/**
 * 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 <------> Arduino UNO
 * GND					GND
 * A4					A4
 * A5					A5
 *
 */

#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.flush();
	Serial.println();

	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(0x09); 	// Start channel with slave 0x09
	Wire.write(GET_NAME);        		// send data to the slave
	Wire.endTransmission();       	// End transmission

	delay(1000); // added to get better Serial print

	Wire.requestFrom(0x09, 14);    // request 14 bytes from slave device 0x09

	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

	Wire.beginTransmission(0x09); 	// Start channel with slave 0x09
	Wire.write(GET_AGE);        		// send data to the slave
	Wire.endTransmission();       	// End transmission

	delay(1000); // added to get better Serial print

	Wire.requestFrom(0x09, 1);    // request 1 bytes from slave device 0x09

	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
	}
}

void loop() {
}

Il risultato sulla Serial del Master diventa così.

ArduinoUNO    
ArduinoMEGA   
43
45

Scanner di indirizzi I2C

Uno degli sketch più utili quando usi i2c è lo scanner di 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
}

La sua logica è fondamentalmente molto semplice, usa i pin SDA e SCL standard e inizia a provare a stabilire una connessione

    Wire.beginTransmission(address);
    error = Wire.endTransmission();

per tutti i 27 indirizzi, quando l’errore è uguale a 0 significa che hai stabilito correttamente una connessione.

Il risultato per l’Arduino master nella connessione multi slave superiore diventa così.

for all 27 address, when an error is equal to 0 mean that you successfully establish a connection.

Il risultato per il master Arduino nella connessione multi slave diventa così.

I2C Scanner
Scanning...
I2C device found at address 0x08  ! 
I2C device found at address 0x09  !
done

Grazie

  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

Spread the love

Lascia un commento

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