Arduino és az I2C/TWI protokoll

Az I2C/TWI egy kis sebességű soros kommunikációs protokoll. Az I2C hálózat egy vagy több master eszközből és egy vagy több slave (szolga) eszközből áll. Nagy távolságú adatátvitelre az I2C protokoll nem javasolt.

Az I2C/TWI busz két vezetékből és a hozzájuk tartozó felhúzó ellenállásokból áll. Erre az érpárra csatlakozik az összes I2C eszköz. A két vezeték neve SDA és SCL.

Ezek szükségesek lehetnek a tanuláshoz:

A felhúzó ellenállások értéke az I2C busz feszültségétől, hosszától és a buszon lévő eszközök számától függ. Az ellenállás értéke 1K és 10K között legyen, ökölszabály alapján használjunk 4,7k értékű ellenállásokat. Ha az I2C busz hosszú vagy több eszköz kapcsolódik a buszhoz, vagy alacsonyabb a busz feszültsége, csökkenthetjük a felhúzóellenállások értékét.

Az SDA adatvezeték az aktuális adatok oda-vissza küldésére szolgál. Az SCL vonal a kommunikációs időzítéshez használt órajelet hordozza. Az órajelet mindig az aktuális master generálja.

Arduino és az I2C/TWI protokoll

Az I2C eszközök különböző feszültségszinteken működhetnek. A 3,3 V-on működő I2C eszközök megsérülhetnek, ha az Arduinohoz kapcsolódnak. Ilyen esetben szintillesztő áramkört kell alkalmazni.

Az I2C eszközök különböző feszültségszinteken működhetnek.

A master eszközök adatokat küldhetnek és fogadhatnak. A slave eszközök válaszolnak, a master kérésére. Az I2C buszon történő adatküldés során egyszerre csak egy eszköz tud adatot küldeni. Minden slave eszköznek rendelkeznie kell egy egyedi I2C-címmel, amely azonosítja az I2C buszon. Az I2C cím lehetővé teszi, hogy a master adatokat küldjön a buszon egy bizonyos slave eszköznek.

Az alábbi táblázatban láthatjuk, hol helyezkednek el az I2C/TWI érintkezők az egyes Arduino kártyákon.

TáblaI2C/TWI érintkezők
UNO, NanoA4 (SDA), A5 (SCL)
Mega256020 (SDA), 21 (SCL)
Leonardo 2 (SDA), 3 (SCL)

Kiváló eszközök a gyakorláshoz:

Az I2C busz használatához szükségünk van a Wire könyvtárra, ezt az Arduino vázlat elején hozzá kell adni az alábbi módon.

#include <Wire.h>

A Wire könyvtár a következő függvényeket tartalmazza. Vegyük sorra őket.

A Wire.begin() funkció vezeti be a Wire könyvtárat, és elindítja a kommunikációt. Az eszközök vezérlőként(Master) vagy perifériaként(Slave) csatlakoznak az I2C buszhoz. Minden slave eszköznek rendelkeznie kell egy 7 bites címmel, amivel a master azonosítja az I2C buszon. Az opcionális paraméter a slave eszköz címe. Ha ez a paraméter nincs megadva, az eszköz masterként csatlakozik a buszhoz. Opcionálisan a master eszköznek is lehet címe.

#include "Wire.h"

void setup()
{
  Wire.begin();  // Ha nincs paraméter, az eszköz masterként csatlakozik
}

void loop()
{
  
}
Wire.begin(0x90);   // I2C address

Vissza a tartalomjegyzékhez.

A Wire.end() leállítja az I2C kommunikációt és letiltja a Wire könyvtárat. Ha újra használni szeretnénk a Wire könyvtárat, ismét meg kell hívni a Wire.begin() függvényt. A Wire.end() funkció nem volt elérhető a Wire könyvtár eredeti verziójában, és előfordulhat, hogy egyes verziókban nem működik.

Vissza a tartalomjegyzékhez.

A Wire.beginTransmission() funkció indítja az írási folyamatot. Egy fontos paramétere van, a slave eszköz címe, amivel a master az I2C buszon beszélni akar. Mielőtt a master írna, a Wire.beginTransmission() segítségével elküldjük a slave eszköz címét. Ha a slave eszköz jelen van az I2C buszon, megkezdődhet az írás. A Wire.beginTransmission() függvényt a Wire.write(), majd a Wire.endTransmission() függvény követi.

Wire.beginTransmission(address);

Vissza a tartalomjegyzékhez.

A Wire.write() funkció adatokat ír az I2C buszon. Adatokat ír egy slave eszközről a master eszköz kérésére, vagy bájtokat állít sorba az átvitelhez a masteren a slave eszközre küldéshez. A Wire.write() funkciónak egy vagy két paramétere lehet.

Wire.write(value);
Wire.write(string);
Wire.write(data, length);

A value paraméter értéke egy egyetlen bájt. A string paraméter egy karakterlánc. A data paraméter a küldendő bájtok tömbje, a length paraméter a data tömbben szereplő bájtok száma.

#include <Wire.h>

byte mybyte = 0;

char mystring[] = "mystring";
char mychar[] = {'a','b','c'};

void setup()
{
  Wire.begin();
}

void loop()
{
  Wire.beginTransmission(6);
  Wire.write(mybyte);

  Wire.write(mystring);
  Wire.write(mychar, sizeof(mychar));  
  Wire.endTransmission();

  delay(1000);
}

Mielőtt a master írna a slave eszközre, a Wire.beginTransmission() segítségével megkezdjük a kommunikációt. Wire.write() függvény tárolja/sorba állítja a küldendő adatbájtokat egy pufferben. A puffer mérete 32byte. Ha a küldendő adat nem fér bele a pufferbe, az átviteli hibát okoz.

A master eszközön a Wire.endTransmission() elküldi a puffer tartalmát, majd leállítja az írási folyamatot.

Hogy a slave eszköz küldeni tudjon, meg kell várnia a master kérését. A Wire könyvtár tartalmaz egy függvényt erre a célra, ez pedig a Wire.onRequest().

Vissza a tartalomjegyzékhez.

A Wire.endTransmission() a master eszközön elküldi a Wire.write() függvény által tárolt puffer tartalmát, majd leállítja az írási folyamatot.

Wire.endTransmission();

A Wire.endTransmission() függvénynek lehet egy opcionális paramétere.

Wire.endTransmission(bool stop);

Ha a paraméter true, a Wire.endTransmission() az átvitel után stop üzenetet küld, felszabadítva az I2C buszt. A true az alapértelmezett érték.

Ha a paraméter false, a Wire.endTransmission() az átvitel után újraindítási üzenetet küld, így az I2C busz foglalt marad. Ez lehetőséget biztosít, hogy az aktuális master eszköz további üzeneteket küldjön és megakadályozza, hogy egy másik master átvegye az irányítást az I2C buszon.

A Wire.endTransmission() függvénynek a visszatérési értéke hasznos lehet például hibakereséskor. ezek az értékek a következők lehetnek.

Ha a visszatérési értéke „0„, azt jelenti az átvitel sikeresen megtörtént. Az „1” azt jelzi, hogy az adatok túl hosszúak ahhoz, hogy elférjenek az átviteli pufferben.

2” érték a fogadott NACK a slave cím továbbításakor. Minden küldött byte után egy nyugtázó bit kerül elküldésre. Ezt az AKC/NACK bitet használja a slave eszköz annak jelzésére, hogy sikeresen fogadta-e az előző byte-ot. A slave cím elküldése után, amikor a master elküldi a slave címét, amellyel kommunikálni akar, egy slave, amely felismeri a címét, ACK-t küld. Ez közli a masterrel, hogy az elérni kívánt slave valójában a buszon van. Ha egyetlen slave eszköz sem ismeri fel a címet, az eredmény NACK lesz. Ebben az esetben a master megszakítja a kommunikációt, mivel nincs kivel beszélni.

Amikor a visszatérési érték “3” az a fogadott NACK adatátviteli hibát jelzi. Ha a slave eszköz sikeresen fogadta az előző byte-ot, akkor ACK-t küld. “4” érték egyéb hibát jelent. Az “5” értéket időtúllépéskor kapjuk.

byte error = Wire.endTransmission();
switch (error) 
{
  case 1:
    Serial.println("Success");
    break;
  case 2:
    Serial.println("Data too long");
    break;
  case 3:
    Serial.println("NACK on transmit of address");
    break;
  case 4:
    Serial.println("NACK on transmit of data");
    break;
  case 5:
    Serial.println("Timeout");
    break;

}

Vissza a tartalomjegyzékhez.

A Wire.requestFrom() funkciót a Master eszköz arra használja, hogy bájtokat kérjen egy Slave eszköztől. A bájtok ezután lekérhetők a Wire.available() és a Wire.read() függvényekkel. A Wire.requestFrom() függvénynek legalább két patamétere van.

Wire.requestFrom(address, quantity);

Az első paraméter az address, a slave eszköz 7 bites címe, amelytől a master eszköz bájtokat kér. A második paraméter a quantity, ez adja meg a kérhető bájtok számát.

Opcionálisan megadhatunk egy harmadik paramétert is.

Wire.requestFrom(address, quantity, stop);

A harmadik paraméter a stop. Az értéke true vagy false lehet. Az alapértelmezett érték igaz. Ha a stop paraméter értéke igaz, akkor a requestFrom() a kérés után stop üzenetet küld, felszabadítva az I2C buszt.

Ha hamis a requestFrom() stop paramétere, a kérés után újraindítási üzenetet küld, és az I2C busz nem szabadul fel, így a kapcsolat aktív marad. Ez megakadályozza, hogy egy másik master eszköz kommunikációt kezdeményezzen amig az aktuális master be nem fejezi a kérést.

Vissza a tartalomjegyzékhez.

Arduino Mega 2560 mikrokontroller kártya

Arduino Mega 2560
Arduino Mega 2560 – hirdetés

A Wire.read() funkció beolvassa azt a bájtot, amelyet a Wire.requestFrom() függvény által a master eszköz lekért egy perifériaeszközről.

char mychar = Wire.read();

Vissza a tartalomjegyzékhez.

A Wire.available() függvény a Wire.read() függvény által lekérhető bájtok számát adja vissza. A Wire.available() funkciót a master eszközön a Wire.requestFrom() függvény után…

#include <Wire.h>

void setup()
{
  Wire.begin();
  Serial.begin(9600);
}

void loop()
{
  Wire.requestFrom(0x90, 5);
  while (Wire.available())
  {
    char mychar = Wire.read();
    Serial.print(mychar);
  }
  delay(1000);
}

… vagy a slave eszközön az Wire.onReceive() által regisztrált eseménykezelő függvényen belül kell meghívni.

Vissza a tartalomjegyzékhez.

A Wire.onReceive() funkció regisztrál egy eseménykezelő függvényt, amelyet akkor kell meghívni, amikor egy slave eszköz kérést kap egy master eszköztől. A Wire.onReceive() függvénynek egy paramétere van.

Wire.onReceive(handler);

A handler paraméter az az eseménykezelő függvény, amelyet akkor kell meghívni, amikor a slave eszköz adatot fogad. Ebben az eseménykezelőben kerül meghívásra a Wire.available() függvény.


#include <Wire.h>

void setup()
{
  Wire.begin(0x90);
  Wire.onReceive(myHandler);
  Serial.begin(9600);
}

void loop() 
{
  delay(1000);
}

void myHandler(readbytes)
{
  while (Wire.available())
  {
    char mychar = Wire.read();
    Serial.print(mychar);
  }
}

Vissza a tartalomjegyzékhez.

A Wire.onRequest() funkció regisztrál egy eseménykezelő függvényt a slave eszközön, ez az eseménykezelő akkor fut le, amikor a master eszköz adatokat kér.

#include <Wire.h>

void setup()
{
  Wire.begin(0x90);
  Wire.onRequest(myRequestHandler);
}

void loop()
{
  delay(1000);
}

void myRequestHandler()
{
  Wire.write("from slave");
}

Vissza a tartalomjegyzékhez.

A Wire.setClock() függvény módosítja az I2C kommunikáció órajel frekvenciáját. A Wire.setClock() függvénynek egy paramétere van, ez a clockFrequency. Ez a paraméter a kívánt kommunikációs órajel értéke (hertzben).

Wire.setClock(clockFrequency);

Az elfogadott értékek: 100000 Hz (normál mód) és 400000 Hz (gyors mód). Egyes eszközök támogatják az 10000 Hz (alacsony sebességű mód), 1000000 Hz (gyors mód plusz) és 3400000 Hz (nagy sebességű mód) frekvenciákat is.

Vissza a tartalomjegyzékhez.

A Wire.setWireTimeout() függvény beállítja a kommunikációhoz az időkorlátot master módban. A Wire.setWireTimeout() függvény paraméterek nélkül az alapértelmezett időtúllépést állítja be.

Wire.setWireTimeout();

A Wire.setWireTimeout() függvénynek adhatunk két paramétert is.

Wire.setWireTimeout(timeout, reset_on_timeout);

A „timeout” paraméter az időtúllépés értéke mikroszekundumban, ha nulla, akkor az időtúllépés ellenőrzése le van tiltva. A „reset_on_timeout” paraméter ha igaz, akkor az I2C hardver automatikusan visszaáll időtúllépéskor és újraindul a kommunikáció az I2C buszon.

Ha valami hiba van az I2C buszon előfordulhatnak lefagyások, egyéb anomáliák az I2C kommunikáció közben. Ez a funkció hasznos lehet az I2C busz teszteléskor.

Vissza a tartalomjegyzékhez.

Ha az időtúllépés történik, egy jelző kerül beállításra, amelyet le lehet kérdezni a Wire.getWireTimeoutFlag() függvény segítségével. A jelzőt manuálisan lehet törölni a Wire.clearWireTimeoutFlag() függvénnyel.

A jelző akkor is törlődik, ha a Wire.setWireTimeout() függvény meghívásra kerül.

bool myFlag = Wire.getWireTimeoutFlag();

Vissza a tartalomjegyzékhez.

A Wire.clearWireTimeoutFlag() függvény hívása törli az időtúllépést jelző zászlót, és visszatér a jelző aktuális értékével.

bool myFlag = Wire.clearWireTimeoutFlag();

Vissza a tartalomjegyzékhez.

Lássunk egy egyszerű példát az I2C busz használatára. Csatlakoztassunk egy PCF8591 ADC/DAC modult az Arduino táblához. Az ADC és a DAC felbontása is 8 bites, értéke 0-255 lehet.

Az alábbi példában szereplő modul fedélzetén található egy LDR fényérzékelő, ez az ADC0. csatorna; egy termisztor, ADC1. csatorna; valamint egy potméter, ADC3. csatorna; a bemenetek teszteléséhez. Az ADC2 csatorna nem tartalmaz perifériát, ez csak ki van vezetve a fejlécre. Az analóg kimenetre egy ledet kapcsoltak. A bemeneteket és a kimenetkimenetet használhatjuk más célra is. A modul két 10K felhúzó ellenállást tartalmaz az I2C buszhoz.

Csatlakoztassunk egy PCF8591 ADC/DAC modult az Arduino táblához

A következő példakód a DAC csatorna tesztelését mutatja. A PCF8591 fedélzetén található leden láthatjuk az eredményt.

#include "Wire.h"


const char PCF8591 (0x48);
 
void setup()
{
  Wire.begin();
}
 
void loop()
{  
  for(int i=0; i<256; i++) 
  { 
    Wire.beginTransmission(PCF8591);
    Wire.write(0x40);             // vezérlő bájt - DAC indítása 
    Wire.write(i);                // DAC-nak küldendő érték 
    Wire.endTransmission();
    delay(15);
  }
  
  for(int i=255; i>=0; --i) 
  { 
    Wire.beginTransmission(PCF8591);
    Wire.write(0x40);
    Wire.write(i);
    Wire.endTransmission(); 
    delay(15);
  } 
}

A második példával a bemeneteket tesztelhetjük. töltsük fel az Arduino UNO-ra majd nyissuk meg a soros monitort…

#include "Wire.h"


const char PCF8591 (0x48);
byte adcvalue0, adcvalue1, adcvalue2, adcvalue3;
 
void setup()
{
  Wire.begin();
  Serial.begin(9600);
}
 
void loop()
{
  Wire.beginTransmission(PCF8591);
  Wire.write(0x04);
  Wire.endTransmission();
  Wire.requestFrom(PCF8591, 5);   // 5 byte adat kérés a PCF8591-ról
 
  adcvalue0=Wire.read();          // korábbi mérés, elavult adat.
  adcvalue0=Wire.read();          // a második adat kiolvasás friss mérés
  adcvalue1=Wire.read();
  adcvalue2=Wire.read();
  adcvalue3=Wire.read();
 
  Serial.print(adcvalue0);        // LDR
  Serial.print(", ");
  Serial.print(adcvalue1);        // termisztort
  Serial.print(", ");
  Serial.print(adcvalue2);        // üres
  Serial.print(", ");
  Serial.print(adcvalue3);        // potmeter
  Serial.println();
 
  delay(500);
}
UNO R3 Mikrocontroller USB-Kábellel
UNO R3 Mikrocontroller USB-Kábellel hirdetés


Erőteljes Arduino tábla az Arduino Mega2560 breadboard adapterrel.

Arduino Mega2560 Rev3 Mikrokontroller
Erőteljes Arduino tábla az Arduino Mega2560 breadboard adapterrel – hirdetés