Mozgásvezérelt szervó készítése az Arduinóval
Egy giroszkóp adataival szeretnél mozgásba hozni egy szervomotort? Megmutatjuk, miként lehetséges ez.
Amennyiben nem kizárólag mikrokontrollerrel dolgoznál, valamiképp össze kell kötnöd az eszközöket. Szerencsére az Arduino lehetővé teszi, hogy az érintkezőtüskéken keresztül információt vigyél át, így néhány sornyi kóddal megvalósítható a byte-áramlás. Cikkünk egy izgalmas projekt keretében demonstrálja, hogyan hajthatsz végre egy két eszközt tartalmazó rendszerben akciókat oly módon, hogy az egyik segítségével méred az adatokat, míg a másikon a beérkező információk alapján aktiválod a csatlakoztatott hardverelemet.
Bevásárlólista
A megvalósításhoz elsőként két Arduinót kell beszerezned, az egyiket a kezedben tartva mozdulataiddal rögzíted majd a valós idejű mozgásadatokat, a másik pedig fogadja a mért értékeket, és ezek alapján mozgatja a szervomotort. Mivel a Genuino 101-es lapka beépítetten tartalmazza a szükséges giroszkópot, javasoljuk, hogy a projektet ezzel a modellel készítsd, a második Arduino már nyugodtan lehet akár egy Uno is.
Ebben az esetben arra kell majd figyelned, hogy a kódok feltöltésekor nemcsak a kommunikációs port cseréje szükséges, hanem az alaplapot is módosítanod kell. Természetesen az összeállításhoz hozzá kell még csapnod egy szervomotort, valamint a két mikrovezérlő közötti kapcsolathoz nélkülözhetetlen kábeleket is. Mi a Grove Starter Kitben található motort használtuk, amelyet a szintén a szett részét képező shieldmodullal csatoltunk a lapkához.
Az összeszereléssel sem kell komolyabban bajlódnod, az I2C-kapcsolat létrehozásához elegendő mindössze megfelelően társítani a kábeleket. A masterként kijelölt, giroszenzort is tartalmazó vezérlő, valamint a salve-ként címkézett második mikrokontroller között a GND, az A4 és az A5 pinek között vezess kábelt. Amennyiben a töltést is szeretnéd átvinni (vagyis csak a fő mikrovezérlő csatlakozik az áramforráshoz), akkor az egyes Arduino 5 voltos portjától is húzz egy kábelt a másik eszközöd Vin feliratú csatlakozójáig. Ha ezekkel megvagy, kapcsold a motort a 8-as pinhe,z és már készen is állsz a programozásra.
Mozgás mérése
A parancsokat osztó, főnökké előléptetett Arduino mikrokontroller programozásával érdemes kezdened az egész folyamatot, aminek első állomásaként - az új kód létrehozását követően - a giroszkóp adatait kell majd olvashatóvá tenned. Ehhez az alaplapok között telepítsd az Intel Curie Boards aktuális verzióját - persze csak akkor, ha ezt még nem tetted meg. Ezzel ugyanis elérhetővé várnak a speciális könyvtárak, amelyekkel egyszerű hozzáférést nyersz a lapkán található komponensekhez.
A setup() előtti üres terület első sorába gépeld be a #include "CurieIMU.h" sort, majd ugorhatsz is a függvényed belsejébe. Itt indítsd el a soros monitort, hogy az adatok lekérdezése lehetséges legyen (Serial.begin(9600);), majd várj egészen addig, amíg a képernyőkiírás elérhetővé nem válik (while (!Serial); ). Ezek után a beépített inerciális mérőrendszer aktiválása is szükséges (CurieIMU.begin();), végül pedig a gyorsulásmérőt kell konfigurálnod (CurieIMU.setGyroRange(250);).
Miután végeztél a felsorolt lépésekkel, átugorhatsz a loop() belsejébe, ahol elsőként hozz létre a három tengelynek egy-egy változót (float gx, gy, gz; ), és ezeknek add is értékül azonnal a giroszkóp által olvasott adatokat (CurieIMU.readGyroScaled(gx, gy, gz);). Amint ezzel megvagy, írd ki a soros monitorra a kapott számokat (mindegyik tengelyre add meg a Serial.print("x:\t"); Serial.print(round(gx)); parancsot), és egy feltöltést követően a felugró ablakban már záporoznak is az eszköz által mért adatok.
Mivel a vezérléshez egy tengely bőven elég lesz, válaszd ki a legszimpatikusabbat, és csak ennek az adatait tartsd meg (nálunk az x nyert). Ha a kiíratásnál egy round() függvényt használsz, akkor egészre kerekítve kapod a számokat, így már csak azt kell kiszűrnöd, hogy ismétlődés esetén ne változzon a képernyődre kiírt szám. Ehhez az include-lista alatt hozz létre egy oldX nevű, 0-ra beállított változót, amelyben eltárolod majd az aktuális kör végén a mérési adatokat, így a következő ciklus elején frissen kapott számot már könnyen összevetheted ezzel a loop()-on belül.
Amennyiben a két érték egyezik, csak egy kis várakoztatást iktass be (if(round(gx)==oldX){delay(100); }), az else ágban pedig mehet a korábban is használt kiíratás annyi csavarral, hogy a feliratok kiküldése után az oldX=round(gx); sorral beállítod az új értéket, majd egy kis késleltetést is beiktatsz (delay(100);).
Üzenet a mestertől
Az adatokat már sikeresen méri mikrovezérlőd, így csupán annyi teendőd maradt, hogy el is küldd azokat a második eszközödre. Ehhez a kódodban ismételten ugorj az elején található include részhez, és helyezd a #include <Wire.h> feliratot a már meglévő beemelésed alá. Ezzel a sorral elérhetővé válnak a kábeles adatküldési funkciók, így egyszerűen végrehajthatod az I2C kommunikációt.
A setupfüggvényt mindössze annyival kell kibővítened, hogy a Wire.begin(); parancs segítségével élesíted a kommunikáció lehetőségét. Ezt követően a loopban a feltételed else ágának késleltetése után helyezd el a Wire.beginTransmission(4); parancsot, amelynek segítségével létrehozod a kapcsolatot, majd a Wire.write(oldX); és a Wire.endTransmission(); parancsokkal továbbíthatod is a mért értéket. Különösen figyelj arra, hogy az adatok byte formátumban száguldanak át az egyik Arduinóról a másikra, ezért az előjelek problémát okozhatnak.
Ennek orvoslására a write sor előtt helyezz el egy feltételvizsgálatot, amelyben azt vizsgálod, hogy az oldX negatív vagy pozitív, és típustól függően küldj egy extra karaktert csatlakoztatott mikrovezérlődnek (if(oldX<0){ Wire.write("m");} else{Wire.write("p");}). Hogy a negatív adatok biztosan ne zavarjanak be, oldX-értékednek vedd az abszolút értékét az abs() függvénnyel, más már nem is hiányzik a mesterkód elkészültéhez.
Fogadott utasítások
A második Arduinón, a setup előtti részen helyezd el a #include <Wire.h> és az #include <Servo.h> sorokat, majd hozz létre egy szám változót, amely a 180 fokos forgatásra képes motor tengelyközepét jelzi majd (int pos = 90;). Nevesítsd magát a motort is a Servo myServo; segítségével. A setup belsejében elsőként csatold a motort a 8-as portra (myServo.attach(8); ), majd nyiss egy kommunikációs csatornát a 4-es pin felületén keresztül (Wire.begin(4);).
Egy extra függvényt is hívj meg itt, amely a fogadáskor érkező adatok kezelésében segít (Wire.onReceive(receiveEvent);), és természetesen a soros monitor kommunikációs csatornájáról se feledkezz meg (Serial.begin(9600);). Ezek után a loopban csupán annyi a teendőd, hogy némi késleltetéssel (delay(100);) folyamatosan frissítesz, elvégre az új esemény lesz az, amely aktiválja a motort. Az adatok kinyeréséhez hozz létre a loop alatt egy void receiveEvent(int howMany) függvényt, és olvasd be elsőként a fogadott karaktert (char c = Wire.read();), majd az érkező számot (int x = Wire.read();).
A szám dimenzióit csökkentsd le, hogy a szervomotor megfelelően képes legyen kezelni a forgást (x=map(x, 0, 250, 0, 90);). Egy feltételben keresd meg azt az esetet, amikor a beérkezett karakter a negatívot jelölő m betűt veszi fel (if(c=='m')), ekkor negatívként írasd ki az x-et (Serial.println(-x); ), majd az eredeti pozícióból vond le az elforgatás mértékét (myServo.write(pos-x);). Ha szeretnéd megtartani ezt a pozíciót, akkor a pos=pos-x; segítségével erre is lehetőséged lesz (amennyiben ezt a sort elhagyod, minden mérés után visszaugrik a 90-es kezdőpontra a motorod).
Az else ágban szintén ezeket a parancsokat használd, csak a kivonást cseréld összeadásra. Ezzel lényegében elkészült a második kód is, és ha mindent megfelelően hajtottál végre, akkor a feltöltést követően már életre is keltheted a motort a masterként jelölt mikrovezérlő mozgatásával.
Kód 1
#include "CurieIMU.h"
#include <Wire.h>
int oldX=0;
void setup() { Wire.begin(); Serial.begin(9600); while (!Serial); CurieIMU.begin(); CurieIMU.setGyroRange(250); }
void loop() {float gx, gy, gz; CurieIMU.readGyroScaled(gx, gy, gz);
if(round(gx)==oldX){delay(100); }
else{Serial.print("X:\t");Serial.print(round(gx)); Serial.println(); oldX=round(gx); delay(100); Wire.beginTransmission(4);
if(oldX<0){ Wire.write("m");}
else{ Wire.write("p"); }
Wire.write(abs(oldX));Wire.endTransmission();} }
Kód 2.
#include <Wire.h>
#include <Servo.h>
int pos = 90; Servo myServo;
void setup() {myServo.attach(8);Wire.begin(4);Wire.onReceive(receiveEvent); Serial.begin(9600); }
void loop(){ delay(100);}
void receiveEvent(int howMany){char c = Wire.read(); int x = Wire.read(); x = map(x, 0, 250, 0, 90);
if(c=='m'){ Serial.println(-x);myServo.write(pos-x);pos=pos-x; }
else{ Serial.println(x); myServo.write(pos+x); pos=pos+x;}}