Robotépítés kezdőknek

Robotépítés kezdőknek

Akadálykikerülő Robot v3.0

2018. január 10. - FizikusRobotBlog

ak3-1.JPG

Az akadálykikerülő robot második verziója egy Sharp infravörös távolságérzékelő szenzorral érzékelte a körülötte lévő tárgyakat. A szenzor a robot elején, a talajhoz viszonyítva kb. 8-9 cm magasan lett elhelyezve és vízszintesen pásztázott. Az ennél alacsonyabb akadályokat nem tudta érzékelni.

A robot harmadik verziójában, a fenti hiányosságok kiküszübölésére, egy mechanikus ütközésérzékelővel látom el a robotot, ami két mikrokapcsolót használ az alacsony akadályokkal történő ütközések érzékelésére.

Mikrokapcsolós ütközésérzékelő

Próbanyákból, egyenes és derékszögű tüskesorból és két mikrokapcsolóból készítettem el az alábbi képen látható mechanikus ütközésérzékelőt. A mikrokapcsolók fém lapátjaira vékony, ívesen meghajlított plexicsíkokat ragasztottam.

ak3-2.JPG

 

Ütközés detektálás

Miután elkészült az ütközésérzékelő szenzor, már csak azt kellene kitalálni hogy hogyan detektáljuk az Arduinóval az ütközéskor létrejövő gombnyomást.

Erre alapvetően két megoldás lehetséges:

  • Az ún. “polling” módszerrel történő gombnyomás érzékelés
  • Megszakítások használatával történő gombnyomás érzékelés

Az ún. “polling” módszer esetén a programban létrehozunk egy ciklust, amelyben a mikrovezérlő folyamatosan ellenőrzi a gomb állapotát, hogy le van-e nyomva, vagy nincs. Amennyiben a gomb le van nyomva, a mikrovezérlő végrehajtja az adott eseményhez tartozó feladatot. Ennél a módszernél a mikrovezérlő folyamatosan egy aktív várakozó ciklust hajt végre amíg a gombnyomásra vár, és mással nem tud eközben foglalkozni, csak pazarolja a processzoridőt. Ez akkor kellemetlen amikor más, sok processzoridőt igénylő feladatot is szeretnénk végrehajtani eközben.

Mennyivel egyszerűbb lenne, ha a mikrovezérlő végezné a számításokat, ahelyett hogy folyton a gombnyomásra figyelne, és a gomb szólna a mikrovezérlőnek ha gombnyomás történt.

Megszakítások használatakor pont ez történik. Amikor lenyomjuk a gombot, akkor az egy hardveresen generált jelet küld a mikrovezérlőnek, ami azonnal reagál erre az eseményre, félbehagyja az éppen futó feladatot (ezért hívjuk megszakításnak), és végrehajtja a megszakítást kiszolgáló eljárást, majd visszatér a főprogramhoz, és ott folytatja a félbehagyott feladat végrehajtását ahol abbahagyta. Megszakítások használatakor a mikrovezérlő nem várakozik feleslegesen.

Megpróbálom egy példán keresztül kicsit érthetőbben szemléltetni a két módszer közötti különbséget.

Tegyük fel, hogy éppen kedvenc filmünket akarjuk otthon megnézni DVD-n. Tegyük fel azt is hogy éhesek is vagyunk, és rendeltünk egy pizzát.

Ha a pizzafutárt a “polling” módszerrel akarjuk beengedni, akkor az azt jelenti, hogy néhány percenként odamegyünk az ajtóhoz, kinyitjuk, és megnézzük hogy megjött-e már a pizzánk. Látható, hogy ilyenkor a folyamatos ajtónyitogatás mellett nem nagyon tudunk filmet nézni.

Ha megszakítások használatával akarjuk beengedni a pizzafutárt, akkor kényelmesen leülünk kedvenc fotelünkbe, és elkezdjük nézni a filmet. Ha a pizzafutár megérkezik, küld egy megszakítás kérelmet (becsenget), ekkor mi megállítjuk a filmet, és végrehajtjuk a megszakítást kiszolgáló utasításokat (kinyitjuk az ajtót, fizetünk és átvesszük a pizzát). Ha ezzel készen vagyunk, akkor folytathatjuk azt a tevékenységet amit a csengetés előtt végeztünk (mehetünk vissza filmet nézni, pontosan onnantól nézve tovább ahol megállítottuk).

Megszakítások

A megszakítás pontosan az amit a neve is mutat, a mikrovezérlőn futó program megszakítása. Hagyományosan a mikrovezérlő az utasításokat sorrendben, egymást követően hajtja végre. A mikrovezérlő lépései a főprogram által előre meghatározottak, a ciklusok és a feltételes elágazások kicsit megkavarják a folyamatot, de a mikrovezérlő lépései adottak. A mikrovezérlő egyszerre csak egy feladatot tud kezelni, a rendszer órajel felfutó élére egy új műveletet végez el.

A megszakítások olyan külső vagy belső események, amelyek a fő program folyásával/futásával nincsenek szinkronban, és amikor bekövetkeznek, az őket kiszolgáló rutinok a mikrovezérlő teljes figyelmét megkapják. Azt előre nem tudjuk megmondani, hogy mikor fognak bekövetkezni a megszakítások, csak azt definiáljuk, hogy a mikrovezérlő ezeket az eseményeket hogyan szolgálja ki.

Amikor egy megszakításkérő jel érkezik, a mikrovezérlő egy előre megadott memóriahelyre ugrik, és elkezdi az ott lévő utasításokat végrehajtani (a neve megszakítás kiszolgáló rutin – Interrupt Service Routine ISR). Amikor ez a kódrészlet lefutott, a normál futás folytatódik tovább, a mikrovezérlő visszamegy és folytatja azt a megkezdett feladatot, amit a megszakítás előtt végzett.

ak3-3.jpg

A megszakítások többféle különböző forrásból érkezhetnek, mindegyiket különbözőképpen kell kiszolgálni, ezért mindegyik megszakításhoz külön ISR rutin tartozik. Azért hogy a mikrovezérlő tudja hogy melyik esemény melyik ISR-t aktiválja, szükség van egy ún. vektor táblára. A programszámláló (Program Counter) egy mutató, ami azt jelzi, hogy melyik a következő végrehajtandó utasítás. A Program Counter-nek is tudnia kell, hogy az egyes ISR-ek hol találhatók a program memóriában, hogy oda tudjon ugorni ha egy megszakítás érkezik.

ak3-4.jpg

A fenti ábrát megnézve láthatjuk, hogy mit is csinál a programszámláló amikor egy megszakításkérés érkezik. Megkeresi a vektortáblában hogy az adott megszakítás kiszolgálásához tartozó rutin (ISR) hol található, odaugrik, végrehajtja az ISR-ben lévő utasításokat, majd visszatér a főprogramhoz.

Megszakítások használata Arduino-val

Ha a mechanikus ütközésérzékelőben lévő mikrokapcsolókat akarjuk használni, akkor a polling módszer nem a legjobb megoldás, mert miközben a robot halad előre elég sok feladattal kell a mikrovezérlőnek foglalkoznia (mérni kell a Sharp érzékelővel, értékelni kell a mért adatokat, és ezek függvényében el kell dönteni, hogy merre menjen tovább a robot).

Megszakítások használatával lenne elegáns megoldani a problémát, mert ez nem terheli feleslegesen a mikrovezérlőt, csak akkor kér figyelmet ha valamelyik mikrokapcsoló nekiütközik valaminek.

Az Arduino ATMega328-as mikrovezérlője kétféle, az IC lábaihoz kapcsolódó megszakítással rendelkezik. Ezek a “külső” és az ún. “pin change” megszakítások.

Az ATMega328-as két külső megszakítással rendelkezik (INT0 és INT1). Az INT0 fixen az Arduino digitális 2-es lábára (PIN2), az INT1 pedig a digitális 3-as (PIN3) lábára van kötve.

A lábakon lévő jel négyfajta különböző állapotváltozásra tud megszakítást kiváltani (ha a megszakítás aktiválva van, akkor ezeket az állapotváltozásokat a mikrovezérlő automatikusan érzékeli):

  1. a láb alacsony logikai értéket vesz fel
  2. a láb logikai értékének bármilyen változása
  3. lefutó él (a láb logikai értéke magasról alacsonyra változik)
  4. felfutó él (a láb logikai értéke alacsonyról magasra változik)

Az ún. “pin change” megszakítás (lábak állapotának változása által kiváltott megszakítás) ezzel ellentétben nincs fix lábakhoz kötve, ezért több lábra is beállítható (a GND és a tápfeszültség lábakat leszámítva, az ATMega328-as esetén akár mind a maradék 24 lábra is beállítható). Ezt a megszakítást a lábak állapotának bármilyen változása kiváltja (fel- vagy lefutó él), ezért a megszakítást kezelő programrészleten belül kell megvizsgálni hogy melyik lábon és hogy milyen irányú átmenet történt, majd ennek megfelelően kezelni a változást. A pin change megszakítás kezelése nem olyan egyszerű mint a külső megszakításoké, ezért a robotnál csak a külső megszakításokat fogom használni, és ennek ismertetésére térek csak ki.

 

Külső megszakítások használata Arduino-val

A megszakításokat az attachInterrupt() paranccsal lehet aktiválni és beállítani.

attachinterrupt(parameter1, parameter2, parameter3)

A függvénynek három paramétert kell megadni:

  1. parameter1: melyik külső megszakításra figyeljen a mikrovezérlő (a megszakítás számát kell megadni (0 vagy 1), NEM a digitális láb számát)
  2. parameter2: megszakítás esetén melyik függvény utasításai fussanak le
  3. parameter3: a megszakításhoz tartozó lábon levő jel melyik változása aktiválja a megszakítást
  • LOW = ha a bemenet alacsony logikai állapotban van
  • RISING = ha a bemenet logikai alacsony állapotból magasra vált (felfutó él)
  • FALLING = ha a bemenet logikai magas állapotból alacsonyra vált (lefutó él)
  • CHANGE = ha a bemenet logikai állapota megváltozott (logikai alacsony állapotból magasra, vagy logikai magas állapotból alacsonyra változott)

Három fontos dolgot említenék még meg a megszakítások esetén meghívott függvényekkel kapcsolatban:

  1. a függvény nem kérhet semmilyen külső paramétert, és nem adhat vissza semmilyen értéket válaszul
  2. mivel a megszakítás a program futásával aszinkron módon, bármikor bekövetkezhet a függvény által használt változókat volatile típusúnak kell megadni
  3. az ISR-nek a lehető leggyorsabbnak kell lennie, mert sok a mikrokontroller által végrehajtott feladatban kritikus az időzítés. Ha egy hosszú ISR rutint használunk és egy megszakítás kérelem érkezik amikor épp egy időzítésre érzékeny rutin végrehajtása történik, akkor a promramunk elszállhat.

 

A robot programja

A vezérlőprogram szinte ugyanaz mint a második verzióé. A különbség csak annyi, hogy az ütközésérzékelő szenzoron lévő mikrokapcsolók jeleit külső megszakításként használva, ha előremenet, vagy kanyarodás közben az érzékelő akadályba ütközik, akkor a robot azonnal megáll, hátratolat, majd elfordul az akadállyal ellentétes irányba.

A fenti algoritmus folyamatábrán ábrázolva így néz ki:

ak3-5.jpg

A programban megadtam, hogy melyik külső megszakításokat fogom használni (az alábbi peldábán a PIN2-hez tartozó INT0-ás interruptot mutatom be). A kapcsolásban a mikrokapcsoló közvetlenül a GND-re és az Arduino lábára van kötve. A lábhoz tartozó belső felhúzóellenállás be van kapcsolva, ezért a lábon alapból logikai magas szint van. Gombnyomás esetén a mikrokapcsoló záródik és a láb alacsony logikai szintre kerül, ezért a programban azt állítom be, hogy az adott láb lefutó élre adjon megszakítást.

attachInterrupt(0, megszakitas1, FALLING);   // INT0 (PIN2), lefuto el

A megszakítás bármikor bekövetkezhet, ezért a gombnyomás jelzésére létrehozok egy volatile típusú változót.

volatile int kapcsoloBal = LOW;   // Bal oldali utkozest jelzo valtozo

Amikor az adott megszakítás megérkezik, a mikrovezérlő a hozzá tartozó megszakítás kiszolgáló rutinban lévő utasításokat hajtja vérge. A bal oldali ütközést jelző, a PIN2-re (INT0) kötött gomb lenyomása esetén lefutó megszakitas1() függvény azonnal megállítja a robotot és aktiválja az ütközést jelző kapcsoloBal változót (értékét HIGH-ra álítja).

void megszakitas1()    // Bal kapcsolohoz tartozo megszakitasrutin
{
 if (kapcsoloBal == LOW)   // ha a bal oldali utkozest jelzo valtozo torolve van
  {
   Stop();     // Stop
   kapcsoloBal = HIGH;    // ha a bal oldali utkozest jelzo valtozo aktivalasa
  }
}

A főprogram mielőtt elindítaná a robotot előre, folyamatosan megvizsgálja az ütközést jelző változók értékét. Ha ütközést érzékel, akkor megállítja a robotot, hátratolat, majd elfordul 45 fokot az akadállyal ellentétes irányba, majd törli az ütközésjelző változót.

if (kapcsoloBal == HIGH)   // ha tortent utkozes a bal oldalon
 {
   Hatra(100);     // Hatramenet teljes sebesseggel (100%)
   delay(1000);    // 1 masodperc hosszan
   FordulasJobbra45();   // Fordulas jobbra 45 fokot
   kapcsoloBal = LOW;    // Bal oldali utkozest jelzo valtozo torlese
 } 

 

A jobb oldali ütközést jelző, a PIN3-hoz tartozó INT1-es megszakítást is a fentiekhez hasonlóan kell beállítani.

 

A robot Arduino 1.0 kódja:

/* *********************************************************************
Sharp szenzoros AkadalyKiKerulo Robot utkozeserzekelovel
Arduino 1.0 kod by: Fizikus
************************************************************************ */

#include <Servo.h>

#define M1 4 //Pin4 : Motor1 forgasirany (Bal Motor)
#define M1 4 //Pin4 : Motor1 forgasirany (Bal Motor)
#define EN1 5 //Pin5 : Motor1 sebesseg (PWM)
#define EN2 6 //Pin6 : Motor2 sebesseg (PWM)
#define M2 7 //Pin7 : Motor2 forgasirany (Jobb Motor)

Servo fejSzervo; // mikroszervo inicializalasa Szervo neven

int SharpSzenzor = 5; //Sharp szenzor az analog A5-re kotve
int SharpIR; //Celtargy tavolsag (elore nezve)
int SharpIRbal; //Celtargy tavolsag (balra nezve)
int SharpIRjobb; //Celtargy tavolsag (jobbra nezve)
int SzervoPin = 10; // Mikroszervo a D10 digitalis labra kotve
int Bal = 150; //Balra nezo szervopoziciohoz tartozo ertek
int Kozep = 90; //Kozepre nezo szervopoziciohoz tartozo ertek
int Jobb = 30; //Jobbra nezo szervopoziciohoz tartozo ertek

int Balkapcsolo = 2; //Bal oldali mikrokapcsolo Pin2-re kotve
int Jobbkapcsolo = 3; //Jobb oldali mikrokapcsolo Pin3-ra kotve
volatile int kapcsoloBal = LOW; // Bal oldali utkozest jelzo valtozo
volatile int kapcsoloJobb = LOW; // Jobb oldali utkozest jelzo valtozo

 

void setup() //Beallitasok
{
  pinMode(EN1, OUTPUT); // Motor1 sebesseg lab: kimenet
  pinMode(EN2, OUTPUT); // Motor2 sebesseg lab: kimenet
  pinMode(M1, OUTPUT); // Motor1 forgasirany lab: kimenet
  pinMode(M2, OUTPUT); // Motor2 forgasirany lab: kimenet
  digitalWrite(2, HIGH); //Pin2-hoz tartozo belso felhuzoellenallas bekapcs.
  attachInterrupt(0, megszakitas1, FALLING); //INT0 (Pin2), lefuto el
  digitalWrite(3, HIGH); //Pin3-hoz tartozo belso felhuzoellenallas bekapcs.
  attachInterrupt(1, megszakitas2, FALLING); //INT1 (Pin3), lefuto el
  fejSzervo.attach(10); // Mikroszervo a D10 digitalis labra kotve  
  Szervo.write(Kozep); // Szervo: elore nez
}

void loop() // Foprogram - vegtelen ciklus
{
 if (kapcsoloBal == HIGH) // ha tortent utkozes a bal oldalon
 {
  Hatra(100); // Hatramenet 1s-ig
  delay(1000);
  FordulasJobbra45(); // fordulas jobbra 45 fokot
  kapcsoloBal = LOW; // Bal oldali utkozest jelzo flag torlese
 }

 if (kapcsoloJobb == HIGH) // ha tortent utkozes a jobb oldalon
 {
  Hatra(100); // Hatramenet 1s-ig
  delay(1000);
  FordulasBalra45(); // fordulas balra 45 fokot
  kapcsoloJobb = LOW; // Jobb oldali utkozest jelzo flag torlese
 }

 SharpIR = Tavmeres(SharpSzenzor); // Tavolsagmeres
 if ( SharpIR > 20 ) // ha a robot elott 20cm-en belul nincs akadaly
  {
   Elore(100); // teljes gozzel elore!
  }
 else // Ha 20cm-en belul akadaly van
 {
  Stop(); // Stop
  fejSzervo.write(Bal); // Szervo: balra nez
  delay(500); // Varakozas 0.5s-ig
  SharpIRbal = Tavmeres(SharpSzenzor); // Bal oldali tavolsagmeres
  fejSzervo.write(Kozep); // Szervo: elore nez
  delay(500); // Varakozas 0.5s-ig
  fejSzervo.write(Jobb); // Szervo: jobbra nez
  delay(500); // Varakozas 0.5s-ig
  SharpIRjobb = Tavmeres(SharpSzenzor); // Jobb oldali tavolsagmeres
  fejSzervo.write(Kozep); // Szervo: elore nez
  if (SharpIRbal < 20 && SharpIRjobb < 20) // ha mindket tavolsag kisebb mint 20cm
  {
   Hatra(100); // Hatramenet 1s-ig
   delay(100); // Varakozas 1s-ig
   Stop(); // Robot stop
   Hatraarc(); // 180fokos fordulas
  }
 else
 {
  if (SharpIRbal > SharpIRjobb) // ha a bal oldali tavolsag nagyobb mint a jobb oldali
   {
    FordulasBalra45(); // fordulas balra 45 fokot
    delay(100); // Varakozas 0.1s-ig
   }
   else // ha a jobb oldali tavolsag nagyobb mint a bal oldal
   {
    FordulasJobbra45(); // fordulas jobbra 45 fokot
    delay(100); // Varakozas 0.1s-ig
    }
   }
  }
 delay (500); // Varakozas 0.5s-ig
}


void megszakitas1() //Bal kapcsolohoz tartozo megszakitasrutin
{
 if (kapcsoloBal == LOW) // ha a bal oldali utkozest jelzo flag torolve van
 {
  Stop(); //Stop
  kapcsoloBal = HIGH; // a bal oldali utkozest jelzo flag aktivalasa
 }
}

 

void megszakitas2()  //Jobb kapcsolohoz tartozo megszakitasrutin
{
 if (kapcsoloJobb == LOW)  // ha a jobb oldali utkozest jelzo flag torolve van
 {
  Stop();   //Stop  kapcsolo
  Jobb = HIGH;  // a jobb oldali utkozest jelzo flag aktivalasa
 }
}

int Tavmeres(int adcPin)
{
int tavolsag = 0;
int adc = analogRead(adcPin);    // az adcPin-re kotott szenzor ertekenek a beolvasasa
tavolsag = (6400/(adc+1))-4;    // a beolvasott ertekbol a tavolsag kiszamitasa cm-ben
return tavolsag;
}

void MotorBal(int sebesseg)
{      
  if (sebesseg>0)   // Eloremenet
  {
    digitalWrite(M1,HIGH);   // M1 irany (elore)
    analogWrite(EN1,sebesseg*255/100); // M1 sebesseg (PWM)
   }
   else   // Hatramenet
   {
    digitalWrite(M1,LOW);  // M1 irany (Hatra)
    analogWrite(EN1,abs(sebesseg)*255/100);  // M1 sebesseg (PWM)
  }

 
void MotorJobb(int sebesseg)
{
  if (sebesseg>0)   // Eloremenet
  {
    digitalWrite(M2,HIGH);   // M2 irany (elore)
    analogWrite(EN2,sebesseg*255/100);  // M2 sebesseg (PWM)
  }
  else
  {
    digitalWrite(M2,LOW);    // M2 irany (hatra)
    analogWrite(EN2,abs(sebesseg)*255/100);  // M2 sebesseg (PWM)
  }
}

void Stop() 

 MotorBal(0);    // Bal motor: stop
 MotorJobb(0);    // Jobb motor: stop }

void Elore(int sebesseg)
 {
  MotorBal(sebesseg);    // Bal motor: elore
  MotorJobb(sebesseg);    // Jobb motor: elore }

void Hatra(int sebesseg) 
{  
 MotorBal(-sebesseg);    // Bal motor: hatra
 MotorJobb(-sebesseg);    // Jobb motor: hatra
}

void Hatraarc() 
{      
 MotorBal(100);    // Bal motor: elore
 MotorJobb(-100);    // Jobb motor: hatra
 delay(1250);        // Varakozas 1.25s-ig
 MotorBal(0);    // Bal motor: stop
 MotorJobb(0);    // Jobb motor: stop
}


void FordulasBalra45() 
{    
  MotorBal(-100);    // Bal motor: hatra
  MotorJobb(100);    // Jobb motor: elore
  delay(400);        // Varakozas 0.4s-ig
  MotorBal(0);    // Bal motor: stop
  MotorJobb(0);    // Jobb motor: stop
}


void FordulasJobbra45() 
{    
  MotorBal(100);    // Bal motor: elore
  MotorJobb(-100);    // Jobb motor: hatra
  delay(400);        // Varakozas 0.4s-ig
  MotorBal(0);    // Bal motor: stop
  MotorJobb(0);    // Jobb motor: stop
}

Végül egy kis videó, ami az ütközésérzékelőt mutatja működés közben:

Üdvözlettel: Fizikus

A bejegyzés trackback címe:

https://robotepites.blog.hu/api/trackback/id/tr6413562989

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Nincsenek hozzászólások.
süti beállítások módosítása