Két Arduino összekapcsolása I2C-vel

Tartalom:

  • Két Arduino alaplap összekapcsolásának alapelve, technikai megvalósítása (vezetékezés)
  • Master és slave működés, információcsere tulajdonságai
  • Minta forráskód az adatcsere bemutatására
  • Sok master egy I2C buszon?

———————————————————————————

Előfordulhat, hogy két Arduino között kel adatokat cserélni. Az alábbi megoldást az egy interneten megjelenő cikkben találtam. Sajnos a forrást már nem tudom megjelölni. Az összeköttetés létrehozásának alapja, hogy az Arduino panelek A4 és A5 kivezetéseit kössük össze. Nem szabad elfelejteni a föld vezetéket sem, ha az Arduino panelek egymástól független táplálásúak. Ha ugyanarra a tápegységre kötjük mindkettőt, akkor a közös föld egyértelmű.

Az egyik Arduino lesz a mester, aki küldi az adatokat, míg a másik a slave, aki fogadja. Mivel az I2C buszon több slave eszköz is lehet, a slave eszközöknek önálló és megkülönböztethető címmel kell rendelkeznie. Az RTL óra panel esetében pl. ez a cím egy gyárilag beállított cím, amit „huzalozással” meg is lehet változtatni. Az Arduino esetén a címet szoftveresen kell beállítani.

A master-en mindent úgy kell csinálni, mint ahogyan megszoktuk egy akármilyen I2C buszra kötött modul adatainak lekérése, vagy számára történő adatküldés során. A setup() részben meghívjuk a wire.begin() függvényt paraméter nélkül, és ettől máris master-ként viselkedik az Arduino. Az SCK kimeneten óra jelet állít elő, amikor adatokat küld vagy fogad, az SDA kimeneten illetve bemeneten pedig közlekednek az adatok.

A slave-en csak annyi a teendőnk, hogy a wire.begin() függvényt nem paraméter nélkül, hanem a slave címének megadásával hívjuk meg a setup() ban. PL. ha azt írjuk wire.begin(8), akkor ez az Arduino egy 8-as című slave lesz. Nyilván a slave programjában le kell kezelni a master-től érkező adatok fogadását és az adatok küldését is, de az alábbi a példa programból könnyem megérthető. Az adatok fogadása és küldése megszakításokkal működik. Azaz a fő programunk fut, csinálja azt amivel foglalkozik egyébként, és ha adat érkezik a master felől, akkor keletkezik egy magszakítás. Félbemarad az éppen végrehajtott program, és meghívódik egy függvény, amit előre meg kellett írnunk. Ezt a függvényt a setup() ban a Wire.onReceive(függvénynév) függvény paraméterében kell megnevezni. Ez fogadja az adatokat, és amikor vége az adat fogadó függvényünk végrehajtásának, akkor fut tovább a fő programunk. Ha adatot kell küldenünk a master-nak, akkor ezt a master kezdeményezi, megszólítja a 8-as címen lévő eszközt, azaz a slave Arduinonkat. Keletkezik egy megszakítás és meghívódik egy függvény, amit a setup()-ban a Wire.onRequest(függvénynév) függvényben megneveztünk, ami elküldi a master-nek a szükséges adatokat, majd fut tovább a főprogramunk.

Elméletileg az I2C buszon több master is lehet. Sajnos nem találtam arra vonatkozóan semmilyen információt, hogy az I2C busz kezelésére szolgáló függvény könyvtárak tudják-e kezelni a két mastert a buszon. Valószínűnek tartom, hogy nem. Azonban nem kizárt, hogy a wire.begin() (masterterként működik) és a Wire.begin(8) (8-as című slave-ként működik) utasításokat lehet egy programon belül váltogatni, és egy Arduino szerepe egy programon belül váltogatható. Persze adminisztrálni kell, hogy milyen szerepben van a másik Arduino, nehogy ketten egyszerre legyenek masterek. Ezt nem próbáltam ki eddig.

További észrevételem, hogy a Wire.beginTransmission(8);  és a Wire.endTransmission(); programsorok között kiadható Wire.Write()-ok száma korlátozott, 32 byte vihető át egyszerre. Egy programban ennél több infót kellett átvinnem, ezért kénytelen voltam részekre bontani az átvitelt. Ekkor a Wire.beginTransmission(8) után következő buszra kiírt byte egy jelző byte volt, ami a slave-nak megmondta, hogy éppen melyik blokkot viszem át. A slave oldalon az adatok fogadására készített függvény ebből tudta, hogy az érkező adatokkal mely változókat kell tölteni!

Az alábbi példa programban a slave egy analóg bemeneten mért értéket long változóba olvas be. Direkt long, hogy látható legyen, hogyan kell byte-onként átvinni az értéket.  Amikor a master lekérdezte a slave-től az adatot, összerakja a byte-onként érkezett tartalmat egy long változóba és kiírja a soros monitorra, hogy majd lássuk mi jött. A fogadott értékeket visszaküldi a slave-nak, hogy erre is lássunk példát.

A master programja

/*
 * Ez a program demo két arduino I2C porton keresztül történő adatcseréjének bemutatására készült.
 * Ez itt a master programja. A mester másodpercenként adatot kér a slave-től, ami egy -500 és 523 közötti
 * long számot bont fel és byte-onként elküld. A mester programja összerakja a 4 byte-ot egy long
 * változóba és kiírja, valamint a 4 byte-ot azonnal vissza is küldi a slave-nak!
 */
#include<Wire.h>           
byte fogad1;  //ebbe a változóba olvassuk be a slave által küldött 1. byte-ot
byte fogad2;  //ebbe a változóba olvassuk be a slave által küldött 2. byte-ot
byte fogad3;  //ebbe a változóba olvassuk be a slave által küldött 3. byte-ot
byte fogad4;  //ebbe a változóba olvassuk be a slave által küldött 4. byte-ot
void setup()
{
  Serial.begin(9600);       // soros port inicializálása            
  Wire.begin();                 // I2C kommunikáció inicializálása, nincs cím, ezért master
}

void loop()
{
    Wire.requestFrom(8,4);      // a master kér négy bájtot a slave-től
    fogad1 = Wire.read();          //beolvassuk sorban a slave által küldött byte-okat   
    fogad2 = Wire.read();           
    fogad3 = Wire.read();           
    fogad4 = Wire.read();           
    Serial.print("Slave-tol fogadott:");    //kiírjuk a soros portra a fogadott byte-okat
    Serial.print(fogad1);Serial.print(",");Serial.print(fogad2);Serial.print(",");
    Serial.print(fogad3);Serial.print(",");Serial.print(fogad4);Serial.print("->");
    //kiírjuk a soros portra a long-nak összerakott értéket
    Serial.println(byteToLong(fogad1,fogad2,fogad3,fogad4));
    Wire.beginTransmission(8);  // elindít a master egy átvitelt a 8 című slave felé
    Wire.write(fogad1);         // küldi a a byte-okat sorban a slave felé
    Wire.write(fogad2);        
    Wire.write(fogad3);                         
    Wire.write(fogad4);                         
    Wire.endTransmission();     // vége az átvitelnek
    delay(1000);                                     
} 

long byteToLong(long inp1, long inp2, long inp3, long inp4)
//Ez a funkció 4 byte-ból csinál egy long változót és visszaadja az eredményt
{
  //4 byte long változóvá alakítása
  return ((inp1 << 0) & 0xFF) + ((inp2 << 8) & 0xFFFF) + ((inp3 << 16) & 0xFFFFFF) + ((inp4 << 24) & 0xFFFFFFFF);
}


Mint az alábbi példából látható lesz, a slave programja megszakításokkal dolgozik. Fut a loop-ban egy program, és csinálja amit kell. Ezt a programot a master részéről érkező adatkérés, illetve a mastertől küldött adatküldés megszakítja, és mindig a megfelelő függvény kerül meghívásar.


Slave programja:

/*
 * Ez a program demo két arduino I2C porton keresztül történő adatcseréjének bemutatására készült.
 * Ez itt a slave programja. A master adatkérésekor megméri az A0 portra kötött feszültséget
 * beolvassa egy long változóba, azt felbontja 4 byte-ra és byte-onként elküldi a masternek.
 * A mester programja (vissza) küld 4 byte-ot, amit a program fogadást követen long számmá alakít
 */
#include<Wire.h>
byte fogad1;
byte fogad2;
byte fogad3;
byte fogad4;

void setup()
{ 
  Serial.begin(9600);           // soros port inicializálása          
  Wire.begin(8);                // I2C kommunikáció inicializálása 8-as eszköz címmel (mivel slave, címet kell megadni)
  Wire.onReceive(slave_fogad);  //Ezt a funkciót hívja az Arduino, amikor adatot kap a mastertől
  Wire.onRequest(slave_kuld);   //Ezt a funkciót hívja meg az Arduino, amikor a master adatot kér a slave-től
  analogReference(DEFAULT);     //A tápfeszt veszi referenciának (5V)az analóg bemenetre kötött feszültség mérésekor
}

void loop()
{  
}

void slave_fogad ()     
//Ez a függvény akkor indul, amikor a slave értesítést kap, hogy a master adatot fog küldeni
{
  //4 byte-ot várunk a mastertől, és azt olvassuk be
  fogad1 = Wire.read();           
  fogad2 = Wire.read();           
  fogad3 = Wire.read();           
  fogad4 = Wire.read();
  //kiirjuk a soros portra a fogadott byte-okat          
  Serial.print("Mastertől fogadott:");    //Prints in Serial Monitor
  Serial.print(fogad1);Serial.print(",");Serial.print(fogad2);Serial.print(",");
  Serial.print(fogad3);Serial.print(",");Serial.print(fogad4);Serial.print("->");
  //visszaalakítjuk a fogadott 4 byte-ot long számmá és kiirjuk a soros portra
  Serial.println(byteToLong(fogad1,fogad2,fogad3,fogad4));
}

void slave_kuld()                            
//Ez a függvény akkor indul, amikor a master adatot kér a slave-től
{
  long kuldendo=analogRead(A0)-500; //a kipróbáláshoz az analóg bemenetet használjuk -500 és 523 között
  Serial.print("Masternek kuldve:");Serial.println(kuldendo);
  //felbontjuk 4 byte-ra a küldendő long értéket
  byte out1 = (kuldendo & 0xFF);
  byte out2 = ((kuldendo >> 8) & 0xFF);
  byte out3 = ((kuldendo >> 16) & 0xFF);
  byte out4 = ((kuldendo >> 24) & 0xFF);
  Wire.write(out1);              
  Wire.write(out2);
  Wire.write(out3);
  Wire.write(out4);
}

long byteToLong(long inp1, long inp2, long inp3, long inp4)
//Ez a funkció 4 byte-ból csinál egy long változót és visszaadja az eredményt
{
  //4 byte long változóvá alakítása
  return ((inp1 << 0) & 0xFF) + ((inp2 << 8) & 0xFFFF) + ((inp3 << 16) & 0xFFFFFF) + ((inp4 << 24) & 0xFFFFFFFF);
}

Kísérletezgetés közben jöttem rá, hogy nem mindegy hol helyezzük el a slave programjában az adatok írását ( wire.write()-okat ), mert tapasztalatom szerint kizárólag a master által kezdeményezett adatküldés kiszolgáló függvényében lehetnek. Ez a fenti példában ez a slave_kuld() függvény. Kipróbáltam olyan megoldást is, amiben a slave_kuld() függvény csak egy jelzést ad arra, hogy a master adatokat vár. Valahol a program egy másik részén akartam az adatokat elküldeni, nevezetesen a loop() ciklusban, amikor nekem megfelelő, és nem akkor amikor pont kéri a mester a slave_kuld() meghívásával. Sajnos ez így nem működött. Az adatok küldése megtörtént, de a master-hez 4db 255 tartalmú byte érkezett meg. Szóval az slave_kuld() függvénybe kell tenni a wire.write sorokat!! Elképzelhető, hogy valamit nem vettem észre, ezért még nem adom fel, de jelen tudásom szerint, csak így lehet küldeni!

A két Arduino közötti kommunikációt azért kellett megismernem, mert készítettem egy időjárás jelző állomást, aminek a vezérlő programja nem fért el egyetlen Arduino nano alaplapban. Így szükség volt kettőre, melyeknek kommunikálniuk kellett. Ha érdekel, akkor itt találod meg a leírásokat és mindkét alaplap programját.

————————————————————————-

Ugyanezt az időjárás állomást épp tovább fejlesztem. Immár 3 Arduino is működik benne, mert kiegészült egy szélsebesség és csapadékmérővel. Felmerült az igény, hogy a három eszköz közösen használjon egy I2C buszon működő FRAM-ot. Mégpedig úgy, hogy mindenki írhatja és olvashatja is ezt a ram-ot. Ehhez készítettem egy több master-es megoldást. Az Arduino-k sorban adogatják egymásnak a mester funkciót, és nem kell egyetlen mastert kijelölni, ami mindent adminisztrál, és adogatja az adatokat az egyes eszközök között. Itt találsz részletes leírást és forráskódokat!

Mennyire volt hasznos amit olvastál?

Kattints egy csillagra az értékeléshez!

Szövegesen is leírhatod véleményedet!