Reaali Robootika.COM

NXT robotimaailm ja programmeerimine C-keeles

Robootika.COM karikad 2011-2012

2011 a. kevad, Robomiku võistlus ja karu päästmine, III koht.

Robomiku, karu päästmine 3. koht

 

2011 a. sügis, Eestisisene FLL (FIRST LEGO League) võistlus, II koht

Eesti FLL (First Lego League) 2 koht

 

2012 a. kevad, robootika Euroopa meistrivõistlus RobotChallenge, II koht

Euroopa MV robootika RobotChallenge 2. koht

 

2012 a. kevad, Eesti parim robootika kool 2012, Robomiku I koht

Eesti parim robootika kool, Robomiku 1. koht

 

2012 a. kevad, Eesti parim robootika kool, rändkarikas

Rändkarikas Eesti parim robootika kool

 

2012 a. sügis, Põhja-Eesti FLL võistluse I koht

Põhja-Eesti FLL (First Lego League) 1 koht

2. 9 klass 2 õppetundi Maadeavastajad robotid

Tiigrihype_logoSee siin on loodud Tiigrihüppe Sihtasutuse programmi ProgeTiiger raames.

Maadeavastaja on selline robot, mis suudab ruumis iseseisvalt ringi liikuda sõltumata sealolevatest takistustest. See projekt õpetab, kuidas kauguse- ja puuteandurit kasutada ning muutujatele väärtusi omistada ja neid omavahel võrrelda.

 

Alljärgnevalt on käsitletud nelja erinevat maadeavastaja robotit. Esimesed on lihtsamad ning järgmised muutuvad keerukamateks.

1.       Maadeavastaja puuteanduriga. Robot on varustatud puuteanduriga, mis tuvastab selle, kui robot on millelegi otsa sõitnud.

2.       Maadeavastaja kauguseanduriga. Sellel robotil on ees kauguseandur. Kui robot näeb ees 20 cm kaugusel takistust, siis robot tagurdab ja keerab kas paremale või vasakule ning jätkab teekonda.

3.       Maadeavastaja pöörava kauguseanduriga. Sellel robotil on peal mootoriga kauguseandur. Kui robot näeb ees 20 cm kaugusel takistust, vaatab robot kaugus­eanduriga paremale ja vasakule ning valib pöördesuuna sõltuvalt sellest, kummal pool on rohkem ruumi.

4.       Maadeavastaja pöörava kauguseanduri ja puuteanduriga. Sellel robotil on peal mootoriga kauguseandur ja madalate ning väikeste objektide tuvastamiseks puuteandur. Kui roboti puuteandur tuvastab madalal takistuse või robot näeb ees 20 cm kaugusel takistust, vaatab robot kauguseanduriga paremale ja vasakule ning valib pöördesuuna sõltuvalt sellest, kummal pool on rohkem ruumi.

Roboti ehituse näol on tegemist tavapärase baasrobotiga, mida tuleb ülesannete edenedes täiuslikumaks ehitada.

Maadeavastaja robot puuteanduriga

clip_image002[4]Robot on ehitatud tavalise sõitva baasrobotina, millele on ette ehitatud puuteanduriga ühendatud tundel. Vähimagi takistuse ilmnemisel peab roboti ees olev tundel vajutama roboti puuteandurit.

Pärast seda, kui puuteandur on vajutatud, peab robot tagurdama ja seejärel juhuslikkuse alusel otsustama, kas keerata paremale või vasakule. Tagurdamine on vajalik, et vastu seina sõitnud robotile ei jääks keeramisel sein ette.

Programmi põhimõte on kõige paremini edasi antav plokkskeemiga, kus rombid tähistavad tsükleid (while, for) või tingimuslauseid (if) ja ristkülikud erinevaid tegevusi.

Programm alustab lõpmatu tsükliga ja puuteandur ei ole esmalt vajutatud. Järgmisena kontrollitakse if-lausega, kas puuteandur on vajutatud: alguses see pole, seega käivitub else-lause ja robot sõidab lihtsalt edasi. Ja programm suundub täitma järgmist tsüklit ehk uuesti kontrollima, kas puuteandur on vajutatud.

Seda tsüklit korratakse ja puuteanduri asendit kontrollitakse 10 000 korda sekundis. Seega võib kindel olla, et siis, kui puuteandur on vajutatud, saab roboti programm sellest väga kiiresti teada ning suundub skeemi järgi paremale ehk JAH suunas (programmis on selleks if-tingimuse esimene pool). Mootorid peatatakse ja tagurdatakse pisut. Seejärel käivitab programm järgmise if-tingimuse, mis kontrollib juhuslikku numbrit 1 või 0 ning vastavalt valikule keerab kas paremale või vasakule.

Näide. Maadeavastaja puuteanduriga

task main()

{

  SetSensorTouch(S1);

 

  //muutuja Rand abil valib robot paremale või vasakule keeramise

  int Rand;

 

  while (TRUE)

  {

    //programm valib juhusliku numbri 1 ja 0 vahel

    Rand = Random(2);

    //Kui puuteandur on vajutatud, siis robot tagurdab ja

    //keerab kas paremale või vasakule

    //kui puuteandur pole vajutatud, sõidab robot otse

    if (Sensor(S1))

    {

      Off(OUT_BC);

      //robot tagurdab

      RotateMotorEx(OUT_BC, 100, 360, 0, TRUE, TRUE);

      if (Rand)

        //robot keerab paremale

        RotateMotorEx(OUT_BC, 100, 335, -50, TRUE, TRUE);

      else

        //robot keerab vasakule

        RotateMotorEx(OUT_BC, 100, 335, 50, TRUE, TRUE);

    }

    else

    {

      //robot sõidab otse

      OnFwdSync(OUT_BC, 100, 0);

    }

  }

}

Maadeavastaja robot kauguseanduriga

clip_image004[4]Robot on ehitatud tavalise sõitva baasrobotina, millele on lisatud kauguseandur.  Kauguseandur võimaldab robotil aru saada, kui see hakkab lähenema takistusele, näiteks seinale. Kui robot on takistusele lähemal kui 20 cm, keerab robot juhuslikkuse alusel kas paremale või vasakule.

Programmi põhimõte on kõige paremini edasiantav järgmise plokkskeemiga. See on väga sarnane eelmisele, puuteanduriga maadeavastaja juhule.

Kui programm alustab ja kauguseandur näeb kaugemale kui 20 cm, suundub programm joonisel EI suunas ning robot alustab otse liikumist. Seda tsüklit korratakse ja kauguseandurit kontrollitakse 10 000 korda sekundis. Seega võib kindel olla, et kui takistus roboti teel ilmneb, saab programm sellest väga kiiresti teada ning suundub joonise järgi paremale ehk JAH suunas. Seejärel keerab robot juhusliku valiku 0 või 1 alusel kas paremale või vasakule.

 

 

Näide. Maadeavastaja kauguseanduriga

task main()

{

  SetSensorLowspeed(S1);

 

  //muutuja Kaugus abil mõõdab robot kaugust takistuseni

  int Kaugus = 20;

  //muutuja Rand abil valib parem/vasak keeramise

  int Rand;

 

  while (TRUE)

  {

    //programm valib juhusliku numbri 1 ja 0 vahel

    Rand = Random(2);

    //Kui robot näeb seina lähemal kui 20 cm,

    //siis keerab paremale või vasakule

    //kui seina näha pole, siis sõidab robot otse edasi

    if (SensorUS(S1) < Kaugus)

    {

      if (Rand)

        //robot keerab paremale

        RotateMotorEx(OUT_BC, 100, 335, -50, TRUE, TRUE);

      else

        //robot keerab vasakule

        RotateMotorEx(OUT_BC, 100, 335, 50, TRUE, TRUE);

    }

    else

    {

      //robot sõidab otse

      OnFwdSync(OUT_BC, 100, 0);

    }

  }

}

Maadeavastaja robot pöörava kauguseanduriga

clip_image006[4]See robot on eelmise maadeavastaja edasiarendus sellise täiendusega, et kauguseandur peab olema ühendatud lisamootori külge, nii et robot saab „näha“ lisaks ettepoole suunale ka paremale ja vasakule. Kauguseandur peaks asuma vähemalt 10 cm kõrgusel, kuna madalamal olles võib põrandalt peegeldunud signaal hakata andurit segama.

Eelmise näitega võrreldes on siin parandatud roboti keeramist. Robot keerab alati selles suunas, kus on rohkem vaba ruumi. Näiteks kui robot jõuab nurka, siis pöörava kauguseanduriga on täiesti kindel, et ta keerab nurgast välja. Juhusliku valiku alusel eelmise näite baasil võib aga tekkida olukord, kus robot ei pääse mõne aja vältel nurgast välja.

Keerava kaugusanduri tööd on hea selgitada alloleva pildi abil. Kui robot on jõudnud eesolevast takistusest 20 cm kaugusele ja seisma jäänud, vaatab robot kauguseanduriga 90 kraadi paremale ja seejärel 180 kraadi vasakule. Robot salvestab mõlemas asendis mõõdetud kaugused muutujatesse ParemPool ja VasakPool ning võrdleb neid seejärel if-tingimuslauses if(ParemPool > VasakPool).  Üks neist on alati suurem ja selle alusel robot otsustabki, kummale poole keerata, et teekonda jätkata. Joonisel on vasakpoolne takistus kaugemal, st. robot pöörab vasakule.

 

clip_image008[4] 

Näide. Pöörava kauguseanduriga maadeavastaja

task main()

{

  SetSensorLowspeed(S1);

 

  //muutuja Kaugus abil mõõdab robot kaugust takistuseni

  int Kaugus=20;

  int ParemPool;

  int VasakPool;

 

  while(TRUE)

  {

    if(SensorUS(S1) < Kaugus)

    {

      //robot jääb seisma

      Off(OUT_BC);

      //kauguseandurit mootor 90 kraadi paremale

      RotateMotor(OUT_A, 25, 90);

      //omistab muutujale ParemPool kauguseanduri väärtuse

      ParemPool = SensorUS(S1);

      //kauguseanduri mootor 180 kraadi vasakule

      RotateMotor(OUT_A, 25, -180);

      //omistab muutujale VasakPool kauguseanduri väärtuse

      VasakPool = SensorUS(S1);

      //robot keerab kauguseanduri jälle keskasendisse

      RotateMotor(OUT_A, 25, 90);

      //kui ParemPool on suurem kui VasakPool,

      //keerab robot paremale

      //kui ei ole suurem,keerab vasakule

      if(ParemPool > VasakPool)

        RotateMotorEx(OUT_BC, 50, 217, 100, TRUE, TRUE);

      else

        RotateMotorEx(OUT_BC, 50, 217, -100, TRUE, TRUE);

    }

    else

    {

      //robot sõidab otse

      OnFwdSync(OUT_BC, 100, 0);

    }

  }

}

Maadeavastaja kauguseanduri ja puuteanduriga

clip_image010[4]See robot on eelmistega võrreldes kõige täiuslikum.

Kõrgemate takistuste korral töötab kauguseandur, kuid madalate takistuste korral, mida kauguseandur ei näe, töötab roboti tundel koos puuteanduriga.

Kasuta eelnevalt ehitatud robotit, millel on kõrgemal mootori peal kauguseandur ning alumises osas tundlaga varustatud puuteandur.

Selle roboti programmi tööpõhimõte on järgmisel plokkskeemil. Robot ootab kas puuteanduri või kauguseanduri rakendumist if-tingimuses if(SensorUS (S1) < Kaugus || Sensor(S2)), nende vahel olevad kaks püstist kriipsu || tähistavad või-märki. See tähendab, et kui ükskõik kumb anduritest rakendub, siis igal juhul muutub if-tingimus tõeseks ja robot jääb seisma.

Seejärel on vaja kindlaks teha, millise anduri rakendumine põhjustas roboti seismajäämise. Siin programmis kontrollitakse puuteandurit: kui see on vajutatud, jäi robot seisma tänu puuteandurile. Robot tagurdab, et keeramisel ei jääks sein segama. Kauguseanduri rakendumise korral robot ei tagurdaks, kuna sel juhul sein roboti keeramist ei segaks. Edasi toimub sarnaselt eelmisele variandile paremale-vasakule kauguseanduriga kauguse mõõtmine ning otsustamine, millises suunas edasi sõita.

Näide. Pöörava kauguseanduri ja puuteanduriga

task main()

{

  SetSensorLowspeed(S1);

  SetSensorTouch(S2);

 

  //muutuja Kaugus abil mõõdab robot kaugust takistuseni

  int Kaugus=20;

  int ParemPool;

  int VasakPool;

 

  while(TRUE)

  {

    if(SensorUS(S1) < Kaugus || Sensor(S2))

    {

      //robot jääb seisma

      Off(OUT_BC);

      //kui puuteandur on vajutatud, siis robot tagurdab

      if(Sensor(S2))

      {

        //robot tagurdab

        RotateMotor(OUT_BC, -50, 360);

      }

      //kauguseanduri mootor 90 kraadi paremale

      RotateMotor(OUT_A, 25, 90);

      //omistab muutujale ParemPool kauguseanduri väärtuse

      ParemPool = SensorUS(S1);

      //kauguseanduri mootor 180 kraadi vasakule

      RotateMotor(OUT_A, 25, -180);

      //omistab muutujale VasakPool kauguseanduri väärtuse

      VasakPool = SensorUS(S1);

      //robot keerab kauguseanduri jälle keskasendisse

      RotateMotor(OUT_A, 25, 90);

      //kui ParemPool on suurem kui VasakPool,

      //keerab robot paremale

      //kui ei ole suurem,keerab vasakule

      if(ParemPool > VasakPool)

        RotateMotorEx(OUT_BC, 50, 217, 100, TRUE, TRUE);

      else

        RotateMotorEx(OUT_BC, 50, 217, -100, TRUE, TRUE);

    }

    else

    {

      //robot sõidab otse

      OnFwdSync(OUT_BC, 100, 0);

    }

  }

}

7. 9 klass 1 õppetund Bluetooth ühenduse abifail

Tiigrihype_logoSee materjal on loodud Tiigrihüppe Sihtasutuse programmi ProgeTiiger raames.

Nii master kui ka slave Bluetooth programmide juures tuleb faili algusesse lisada järgmine rida.

#include "BluetoothCheck.nxc"

Master seadme programmis tuleb lisada programmi algusesse globaalsed muutujad ja task maini sees peab esmalt käivitama Bluetooth ühenduse algatamise.

Näide. Master NXT kasutamine

//MASTER

#include "BluetoothCheck.nxc"

 

const byte BTconn=1;

const string SlaveNXTName = "NXT5";

 

task main()

{

  //käivitatakse BT ühenduse kontroll ja/või loomine

  //kui ühendamine ei õnnestunud siis väljutakse programmist

  if(!BluetoothConnect(BTconn, SlaveNXTName)==NO_ERR)

       Stop(TRUE);

 

  //kuvame kasutajale ühe sekundi jooksul teate

  Wait(SEC_1);

 

  //siit järgneb programmikood mida soovitakse pärast

  //Bluetooth ühnduse loomist käivitada

}

Slave NXT programmis pole vaja globaalseid muutujaid, kuid task maini sees asub Bluetooth-ühenduse kontroll. Fail BluetoothCheck.nxc on tehtud universaalne, st. seda saab kasutadanii masteri kui ka slave’i juures.

 

 

Näide. Slave NXT kasutamine

//SLAVE

 

#include "BluetoothCheck.nxc"

task main ()

{

//käivitatakse BT ühenduse kontroll

//kui ühendamine ei õnnestunud siis väljutakse programmist

if(!BluetoothCheck()==NO_ERR)

       Stop(TRUE);

 

  //siit järgneb programmikood mida soovitakse pärast

  //Bluetooth ühnduse loomist käivitada

}

 

Näide. Bluetooth ühenduse abifail BluetoothCheck.NXC

//See on abifail BluetoothCheck.NXC, mida kasutatakse

//NXT-de vahelise ühenduse loomiseks ja kontrollimiseks

const int YhenduseOotamine = 7;

 

//alamprogramm NXTtootab kuvab kasutajale ootamise lõpuni jäävaid

//sekundeid ning üle ekraani liigub joon vasakult paremale,

//väljendades kasutajale seda, et NXT ikka töötab

task NXTtootab()

{

  long Start=CurrentTick();

  long Finish;

  int i, j, k;

  //muutuja speed määrab joone liikumise kiiruse

  int speed=20;

  while(TRUE)

  {

    j++;

    //k määrab joone lõpppunkti koordinaadi, joone pikkus 20px

    k=j-20;

    //i määrab joone alguspunkti koordinaadi

    i=j%100;

    k%=100;

    //joone alguspunktid

    PointOut(i, 25);

    PointOut(i, 27);

    PointOut(i, 29);

    //joone lõpp-punktid

    PointOut(k, 25, DRAW_OPT_CLEAR);

    PointOut(k, 27, DRAW_OPT_CLEAR);

    PointOut(k, 29, DRAW_OPT_CLEAR);

    //ekraanil kuvatakse yhenduse loomise lõpuni jäävad sekundid

    NumOut(50, LCD_LINE4, YhenduseOotamine-j/(1000/speed));

    //oodatakse ajahetk speed, mis on joone liikumise kiirus

    Wait(speed);

    //arvutatakse välja seni kulunud alamprogrammi aeg

    Finish = (CurrentTick()-Start)/1000;

    //kui alamprogramm on käinud kauem kui programmi

    //ootamine ette näeb, siis katkestatakse alamprogrammi töö

    if(Finish>YhenduseOotamine)

    {

      ClearScreen();

      break;

    }

  }

}

//Funktsioon Bluetooth paaristab master ja slave NXT-d

//seda funktsiooni kasutatakse MASTER NXT korral

int BluetoothConnect(int conn, string name)

{

  //ühenduse loomiseks vajalik struct-muutuja

  CommBTConnectionType args;

  //Slave NXT nimi millega paaristatakse

  args.Name = name;

  //Bluetooth ühenduse pordi number

  args.ConnectionSlot = conn;

  //kas ühendame (TRUE) või mitte (FALSE)

  args.Action = TRUE;

 

  //kontrollitakse kas NXT on paaristatud, kui ei ole,

  //siis käivitatakse alljärgneva if-lause esimene pool

  if (!BluetoothStatus(conn) == NO_ERR)

  {

    //käivitatakse alamprogramm NXTtootab

    StartTask(NXTtootab);

    //ekraanil kuvatakse kiri Yhenduse loomine

    TextOut(0, LCD_LINE1, "Yhenduse");

    TextOut(0, LCD_LINE2, "loomine...");

   

    //Bluetooth paaristamise loomine

    SysCommBTConnection(args);

    //ühenduse õnnestumiseks ootame 7 sekundit

    Wait(YhenduseOotamine*1000);

    //ühenduse loomine õnnestumise kohta kuvatakse teade

    if (BluetoothStatus(conn) == NO_ERR)

    {

      TextOut(0, LCD_LINE1, "Yhendatud");

      TextOut(0, LCD_LINE2, "edukalt!   ");

      TextOut(0, LCD_LINE3, name);

      return NO_ERR;

    }

    //kui ühendumine ei õnnestunud, kuvatakse Yhenduses viga

    else

    {

      ClearLine(LCD_LINE2);

      TextOut(0, LCD_LINE1, "Yhenduses viga");

      NumOut(0, LCD_LINE2, args.Result);

      Wait(SEC_1);

      return -1;

    }

  }

  //kui ühendus oli juba olemas, kuvatakse Yhendus olemas

  else

  {

    TextOut(0, LCD_LINE1, "Yhendus olemas");

    TextOut(0, LCD_LINE2, BTConnectionName(conn));

    return NO_ERR;

  }

}

//alljärgnev protseduur kontrollib programmi käivitamisel,

//kas Bluetooth ühendus on initsialiseeritud

//seda funktsiooni kasutatakse SLAVE NXT korral

int BluetoothCheck()

{

  //SLAVE korral on BTconn alati 0

  const byte BTconn=0;

  if (!BluetoothStatus(BTconn)==NO_ERR)

  {

    TextOut(0, LCD_LINE1, "Kaivita esimesena");

    TextOut(0, LCD_LINE2, "teine NXT.");

    Wait(SEC_3);

    return -1;

  }

  else

  {

    return NO_ERR;

  }

}

 

10. 9 klass 4 õppetundi Ping-Pong kahele üle Bluetoothi

Tiigrihype_logoSee materjal on loodud Tiigrihüppe Sihtasutuse programmi ProgeTiiger raames.

Selle projekti juures saame teada, kuidas nxt-d omavahel üle bluetoothi reaalajas suhtlevad ja kuidas arvutada palli põrkumine ekraanil juhuslikult etteantud arvu põhjal.

 

Ping-pong mäng kahe NXT vahel toimub üle Bluetooth ühenduse, nii et mängijad võivad asuda teineteisest kuni 30 m kaugusel. Kummalgi mängijal on oma NXT, mille ekraanilt saab jälgida kogu mängu kulgu.

Mõlemad mängijad saavad liigutada oma väravat NXT külge ühendatud mootori abil. Mängu alustava mängija NXT-l on küljes ka teine mootor, millest saab sujuvalt muuta mängu kiirust.

Roboti ehitus

NXT külge peab olema mugaval viisil ühendatud mootor koos rattaga, mille abil saab väravat juhtida. Ühel NXT-l peab olema lisatud ka teine mootor, mille abil saab muuta mängu kiirust.

Programmeerimine

Kummagi NXT jaoks on omaette programm, kuid neil on ka mõned ühised programmiosad. Nagu näiteks Bluetooth ühenduse loomine, värava liigutamine ja info kuvamine ekraanil. Seetõttu koosneb antud mängu programm neljast eraldi failist.

1)       NXT 1 ehk mängija 1 programm master_gamer.nxc

2)       NXT 2 ehk mängija 2 programm slave_gamer.nxc

3)       Mõlema NXT jaoks ühised funktsioonid CommonFunctions.nxc

4)       Bluetooth ühenduse programm BluetoothCheck.nxc

Iga fail on jagatud omakorda alametappideks, et lihtsustada arendust ja mängu toimimise põhimõttest arusaamist. Bluetooth ühenduse programmi pole siinkohal eraldi välja toodud ega kirjeldatud, kuna tegemist on standardse ühenduse loomisega, mis on kirjeldatud käesoleva raamatu juhendi Bluetoothi alapeatükis.

Mängija 1 programm ehk master_gamer.nxc fail koosneb järgmistest peamistest funktsioonidest:

1)       Bluetooth ühenduse loomine kahe NXT vahel

2)       Mängu loogika GameLoop

3)       Palli põrkumise ja võitja tuvastamise funktsioon BallCollide

4)       Palli liigutamise protseduur MoveBall

5)       Mängu kiiruse juhtimine ChangeGameSpeed

6)       Bluetooth info saatmine/vastuvõtmine SendReceiveBluetoothInfo

7)       Mängu alginfo initsialiseerimine InitData

8)       Mängu võitja kuvamine ShowWinner

Mängija 2 programm ehk slave_gamer.nxc fail koosneb järgmistest peamistest funktsioonidest:

1)       Bluetooth ühenduse loomine kahe NXT vahel

2)       Mängu loogika GameLoop

3)       Bluetooth info saatmine/vastuvõtmine SendReceiveBluetoothInfo

Ühised funktsioonid ehk CommonFunctions.nxc fail koosneb järgmistest funktsioonidest

1)       Värava liigutamise funktsioon PlayerPosition

2)       Ekraanile kuvamise funktsioon Render

 

clip_image002

Mängija 1 mängu loogika

Mängu käivitamisel kontrollitakse Bluetoothi kaudu teise mängija olemasolu. Kui ka teine mängija on mängu käima pannud, saadetakse teisele mängijale info palli suuruse BallWidth ja värava laiuse GateWidth kohta ja algväärtustatakse mängu parameetrid protseduuriga InitData.  Seejärel pannakse käima mäng protseduuriga GameLoop ja kui mängus tekib võitja, kuvatakse tulemus ekraanil funktsiooniga ShowWinner.

Protseduur GameLoop käib while-tsükli sees seni, kuni tingimus on tõene while(!Winner), ehk kuni võitjat ei ole selgunud. Winner muutuja on vaikimisi võrdne nulliga. Kui tekib võitja, on selle väärtus vastavalt 1 või 2, sõltuvalt võitjast.

Protseduuri GameLoop sees omistatakse muutujale Winner võitja väärtus, mis on palli põrkamise ja võitja tuvastamise funktsiooni BallCollide tagastatav väärtus. Pärast seda käivitatakse palli liigutamise protseduur MoveBall, mille ülesandeks on uute palli koordinaatide omistamine globaalsele muutujale BallPos. Seejärel käivitatakse funktsioon PlayerPosition(GateWidth), mis liigutab väravat ja tagastab väärtuse mängija 1 värava koordinaadiga, mis omistatakse muutujale Player1Pos. Funktsiooni sisendparameeter on värava laius.

Pärast seda omistatakse muutujale GameData kolm erinevat väärtust, palli x ja y koordinaadid ning mängija 1 värava koordinaat. Kuna Bluetooth on kahe NXT vahel võrdlemisi aeglane, siis eelnimetatud parameetrid pannakse kokku üheks muutujaks GameData, et minimeerida Bluetooth ühenduste arvu kahe NXT vahel. Et mängija 2 NXT programmis oleks andmeid lihtsam „lahti võtta“, on koordinaatide x ja y vahele pandud eraldajana kaldkriips ning värava koordinaadi ette sidekriips.

Seejärel käivitatakse funktsioon SendReceiveBluetoothInfo(GameData), mis saadab äsja loodud faili teisele NXT-le ja tagastab väärtuse mängija 2 värava koordinaadiga, mis omistatakse muutujale Player2Pos. Funktsiooni sisendparameeter on GameData.

Viimasena käivitatakse protseduur Render(Player1Pos, Player2Pos, BallPos, GateWidth, BallWidth), mille ülesanne on joonistada ekraanile mõlema värava asukohad ja pall. Selle sisendparameetriteks on esimese ja teise mängija väravate koordinaadid, palli koordinaadid ning palli ja värava laiused.

Seejärel oodatakse 40 ms, mis on ühtlasi ka mängu kiiruseks, ning kogu tsükkel algab uuesti algusest, kuni võitjat ei ole selgunud.

Näide. Protseduur GameLoop ehk mänguloogika

void GameLoop()

{

  //muutujasse Winner salvestatakse mängu staatus

  Winner = BallCollide();

  //liigutatakse palli

  MoveBall();

  //CommonFunctions failist käivitatakse funktsioon

  //mis liigutab player1 värava asukohta, sisendiks värava laius

  Player1Pos = PlayerPosition(GateWidth);

  //luuakse string kolmest parameetrist: palli x ja y

  //koordinaadid ning player1 mängija värava koordinaat

  GameData = StrCat(NumToStr(BallPos.x), "/",

            NumToStr(BallPos.y), "-",

            NumToStr(Player1Pos));

  //vahetatakse Bluetooth infot, edastatakse mängu info

  //tagasi saadakse player2 mängija värava koordinaat

  Player2Pos = SendReceiveBluetoothInfo(GameData);

  //CommonFunctions failist käivitatakse palli ja

  //väravate kuvamise protseduur

  Render(Player1Pos, Player2Pos,BallPos, GateWidth, BallWidth);

  Wait(MS_40);  //oodatakse 40ms, mängu kiirus

}

Mängija 1 palli põrkumine ja võitja tuvastamine

clip_image004Funktsioon BallCollide kannab hoolt selle eest, et pall saaks alati ekraani servadest tagasi põrgatatud ning tuvastatud väravate olemasolu ja palli põrkumine vastu väravat. See funktsioon tagastab võitja numbri. Kui võitjat ei ole, tagastab see funktsioon alati nulli. Kui võitja on mängija 1, tagastab funktsioon vastavalt ühe ja kui võitja on mängija 2, siis kahe.

Käesolevas projektis toimub palli põrkamise kontrollimine täpselt samal viisil, nagu projektis Ping-Pong ühele. Tingimus (BallPos.y > DISPLAY_HEIGHT – BallWidth) saab tõeseks siis, kui pall on jõudnud ekraani ülemisse serva. Kuna pallil on teatud suurus, tuleb kontrollimiseks lahutada palli suurus ekraani kõrgusest. Kui seda mitte teha, kontrollitaks palli alumist serva ekraani kõrgusega ja siis on visuaalselt enamus palli ekraani taha kadunud. Ka parema serva vastu põrkumist kontrollitakse tingimusega (BallPos.x > DISPLAY_WIDTH - BallWidth),kus lahutatakse ekraani laiusest palli laius, et pall ei kaoks põrkumisel ekraani serva taha. Vaata ülemise ja alumise põrke kohta joonist.

Samal moel toimub ka palli kontrollimine vastu alumist (BallPos.y <= 0) ja vasakut serva (BallPos.x <= 2), need on lihtsamad kontrollid, kuna koordinaadid on nii pallil kui ka ekraanil samas kohas. Vasaku serva korral kontrollitakse palli x-koordinaati number 2 suhtes, et jätta väravale 1 piksel ruumi liikumiseks.

 

clip_image006

Vasaku ja parema serva kontrollile lisandub alati ka järgmine kontroll, palli võrdlus värava koordinaadiga (BallPos.y < Player1Pos + GateWidth && BallPos.y > Player1Pos - BallWidth). Palli koordinaate kontrollitakse värava ülemise ja alumise serva suhtes, kuid alumise serva kontrolli korral lahutatakse palli laius, et pall saaks ka ülemise nurgaga väravalt tagasi põrgata. Kui seda lahutamistehet ei kasutaks, oleks pall väravas isegi juhul, kui pool palli on värava kohal. Vaata selle põrke kohta allolevat joonist.

Joonisel on kujutatud kaks palli positsiooni, üks kujutab seda kui pall põrkub vastu ülemist värava serva ja teine, kui palll põrkub vastu alumist värava serva. Mõlemal juhul on tegemist palli nö. viimase piksli põrkumisega.

Kui värava tingimuse kontroll pole täidetud, siis kaotas mängija, kelle poolelt pall välja kukkus, ning funktsioon tagastab võitnud mängija numbri:  return 2.

Peale palli põrkumist muudetakse palli liikumise suund ja omistatakse talle uus koordinaat. Uus koordinaat on ühe piksli võrra suunatud ekraani keskele, et järgmise tsükli käigus ei tuvastataks palli jätkuvalt seina vastas olevaks.

Kui pall on põrganud alumiselt või ülemiselt servalt, siis muudetakse palli liikumise nurk vastupidiseks. Kui enne oli 60 kraadi, siis peale põrkumist on -60 kraadi. Kui aga põrkumine on toimunud parema-vasaku külje suunas, lahutatakse palli põrkumise nurk 180-st. Saadud numbrit kasutatakse palli liikumise protseduuri juures ning selle tehte vajaduse selgitamine toimub seal.

clip_image008

Näide. Palli põrkumine ja võitja tuvastamine

//palli põrkumise funktsioon, tagastab väärtused 0, 1, 2

short BallCollide()

{

  //pall põrkub vastu ülemist serva

  if(BallPos.y > DISPLAY_HEIGHT - BallWidth)

  {

    BallAngleDeg = -BallAngleDeg;

    BallPos.y = DISPLAY_HEIGHT - BallWidth - 1;

  }

  //pall põrkub vastu alumist serva

  else if(BallPos.y <= 0)

  {

    BallAngleDeg = -BallAngleDeg;

    BallPos.y = BallWidth;

  }

  //pall põrkub vastu vasakut serva (Player1)

  if(BallPos.x <= 2)

  {

    //kontrollitakse, kas palli põrkekohal on värav ees

    if (BallPos.y < Player1Pos + GateWidth &&

        BallPos.y > Player1Pos - BallWidth)

    {

      BallAngleDeg = 180 - BallAngleDeg;

      BallPos.x = BallWidth;

    }

    //kui väravat ees ei olnud, siis on mäng läbi

    else

    {

      return 2; //võitis mängija 2

    }

  }

  //pall põrkub vastu paremat serva (Player2)

  else if(BallPos.x > DISPLAY_WIDTH - BallWidth)

  {

    //kontrollitakse, kas palli põrkekohal on värav ees

    if(BallPos.y < Player2Pos + GateWidth &&

      BallPos.y > Player2Pos - BallWidth)

    {

      BallAngleDeg = 180 - BallAngleDeg;

      BallPos.x = DISPLAY_WIDTH - BallWidth - 1;

    }

    //kui väravat ees ei olnud, siis on mäng läbi

    else

    {

      return 1; //võitis mängija 1

    }

  }

  return 0;  //mäng jätkub

}

Mängija 1 pallile uute koordinaatide andmine

Protseduur MoveBall on programmi üks lühemaid, kuid vaatamata sellele kõige olulisem palli korrektse liikumise seisukohast. Antud protseduuri tulemuseks on palli x ja y koordinaadid.

Alguses kutsutakse välja funktsioon ChangeGameSpeed, mille abil saab jooksvalt mängu kiirust muuta. ChangeGameSpeed tagastab ujukomaarvu vahemikus 0,1 kuni lõpmatus. Algne palli liikumise kiirus on 1.

Pall pannakse liikuma täisnurkse kolmnurga siinus ja koosinus funktsioone kasutades. X-telje koordinaat ehk lähiskaatet saadakse koosinuse abil ning y-telje koordinaat ehk vastaskaatet siinuse abil. Vaadates alltoodud valemeid, on hüpotenuus siin programmis kasutusel kiiruse muutujana nimega Speed, mis mängu alguses on võrdne ühega.

Tuletame siinkohal meelde vajalikud valemid.

Täisnurkse kolmnurga lähiskaateti pikkuse arvutamine teravnurga ning hüpotenuusi abil

clip_image010

Täisnurkse kolmnurga vastaskaateti pikkuse arvutamine teravnurga ning hüpotenuusi abil

clip_image012

Valemis toodud tähis teravnurk vastab programmis muutujale BallAngleDeg ehk see on suurus, mis saadakse mängu alguses juhusliku valiku teel ning võib olla vahemikus -60..60 (pall liigub paremale) ja 120..240 (pall liigub vasakule). Need vahemikud tulenevad mängu parameetrite algväärtustamisest ja sellised suurused on valitud seetõttu, et välistada mängujuhuste tekkimine, kus pall hakkab otse üles-alla põrkama ja ei jõuagi seeläbi mängija väravani.

Järgnev joonis näitab, kuidas mängu alghetkel liikumissuunda määrav koordinaatteljestik ekraani suhtes paikneb. Viirutatud alad on need, millistes suundades pall kunagi ei stardi. Palli liikumist toetav joonis on toodud edaspidi, pärast kirjeldusi.

clip_image014

Palli liikumine sirgjooneliselt

Võtame eelduseks, et mängu alguses startis pall keskelt ja suunaga 60 kraadi, seega üles-paremale täpselt punase viirutatud ala servas. Esimese tsükli käigus

x-koordinaat =1 * cos(60) = 0,5 ja

y-koordinaat = 1 * sin(60) = 0,86

ning kuna palli liikumise kiirus on alguses 1, siis jätame selle edaspidi kirjutamata. Ekraani keskkoht on 50x32 pikslit, seega tegelikult on pärast esimest tsüklit x=50,5 ja y=32,86.

Järgmise tsükli käigus (ehk 40 ms pärast) liidetakse kummalegi väärtusele juurde uuesti 0,5 ja 0,86 ja siis on tulemuseks:

x=51

y=33,52

Kuna ekraani koordinaadid on täisarvud, siis programm ümardab eelnimetatud numbrid täisarvudeks ja pall kuvatakse ekraanil koordinaatidega x= 51 ja y= 34.

Palli põrkumine ekraani ülemisest servast

Jätkates eelmist rida, saame ekraani serva jõudnud palli koordinaatideks:

x=68,5

y=64

Nüüd rakendub tehe, mis muudab palli liikumise nurga märgi vastupidiseks -BallAngleDeg = -60. Järgmise tsükli käigus on palli koordinaatide arvutamise nurgaparameetriks -60. Arvutades selle numbriga pallile järgmised koordinaadid, saame:

x=x+cos(-60)=68,5+0,5=69

y=y+sin(-60)=64+(-0,86)=63,13

Seega näeme, et x koordinaat suureneb jätkuvalt, aga y hakkab vähenema. See tähendab, et pall liigub nüüd alla paremale.

Palli põrkumine ekraani paremast servast

Jätkates eelmist rida, tuleb meil arvutada, kas pall jõuab enne alla või põrkub vastu paremat seina. Kuna eelmise näite lõpus oli pall 68,5 piksli kaugusel, jääb sealt ekraani parema servani 31,5 pikslit. Võttes selle aluseks x-telje suunalise kolmnurga küljepikkusena, saame välja arvutada, kui kaugele alla pall selle jooksul jõuab. Tulemus on x=100 (ekraani parem serv) ja y=9,5. Seega põrkub pall enne alumise servani jõudmist vastu paremat serva. Ekraani kõrgus on 64 pikslit, st. pall jõuab paremasse serva 9 piksli juures. Nüüd tuleb palli põrkamise hetk, seega 180-st lahutatakse palli nurk 180-(-60)=240. Pannes selle numbri palli asukoha koordinaatide arvutamise tehtesse, saame:

x=x+cos(240)=100-0,5=99,5

y=y+sin(240)=9,5+(-0,86)=8,6.

Seega saame numbrite põhjal öelda, et pall liigub nüüd jätkuvalt alla, kuid on vahetanud x-teljel suunda ja liigub vasakule.

Palli põrkumine ekraani alumisest servast

Palli x-telje koordinaat on 100 ja y koordinaat 9,5, seega ekraani alumise servani on 9 pikslit. Arvutades välja ekraani allserva jõudva palli x koordinaadi, saame x=94,5 kui y=0, st. pall on alla jõudnud. Nüüd on taas märgimuutmise arvutustehe ja uueks nurga väärtuseks on -240. Kuna positiivseid arvusid on lihtsam mõista, siis võime öelda ka et -240 kraadi = 120 kraadi. Vaata jooniselt.

Palli koordinaadid on:

x=x+cos(120)=94,5+(-0,5)=94

y=y+sin(120)=0+0,86=0,86

Tulemus näitab, et x väheneb ja y suureneb, seega pall liigub üles vasakule.

Sarnase arvutuse kohaselt saab leida ka liikumise pärast vasakpoolse seina vastu põrkumist.

clip_image016

Näide. palli liikumise ja nurga arvutamine

void MoveBall()

{

  //esmalt käivitatakse funktsioon, mille vahendusel

  //saab kiiruse, ehk kolmnurga hüpotenuusi

  Speed = ChangeGameSpeed();

  //arvutatakse x-telje koordinaat

  BallPos.x += cosd(BallAngleDeg) * Speed;

  //arvutatakse y-telje koordinaat

  BallPos.y += sind(BallAngleDeg) * Speed;

}

Mängija 1. Mängu kiiruse juhtimine

Kui mäng tundub liiga lihtne, saab jooksvalt mängu käigus muuta palli liikumise kiirust. Funktsioon ChangeGameSpeed kasutab mängu alguses initsialiseeritud mootor B väärtust MootorCenterB, seda kasutatakse mootori algse positsiooni määramiseks. Sellest lahutatakse mootori hetke pöörded, mis on mängu alguses sama kui algne positsioon, seega tulemus on null. Liidetakse juurde 100, et anda mängule esmane baaskiirus.

Kui keerata mootorit kiiruse vähendamise suunas, siis järgnev if-lause ei võimalda muutujal Kiirus minna väiksemaks kui 10. Enne tagastamist jagatakse väärtus 100-ga, et mootori pööramisel oleks kiiruse muutuse vähim samm 0,1 ühikut return Kiirus/100.

Näide. Mängu kiiruse muutmise funktsioon

float ChangeGameSpeed()

{

  long Mootor;

  float Kiirus;

  Mootor = MotorRotationCount(OUT_B);

  //muutuja Kiirus arvestab mootori algset asukohta

  //+100 liidetakse selleks, et anda mängule baaskiirus

  Kiirus = MootorCenterB - Mootor + 100;

  if (Kiirus <= 10)

    {

      MootorCenterB = Mootor;

      Kiirus = 10;

    }

  //tagastatav kiirus jagatakse 100-ga,

  //et kiiruse muutus oleks võimalikult väike

  return Kiirus/100;

}

Mängija 1. Bluetooth info saatmine/vastuvõtmine

Funktsiooni SendReceiveBluetoothInfo sisendiks on tekstitüüpi muutuja Data, milles sisaldub nii palli kui ka värava koordinaatide info. Funktsioon tagastab vastasmängija värava koordinaadi.

Saadetav andmestik edastatakse teisele NXT-le käsuga SendRemoteString(BTconn, MAILBOX5, Data).  Saatmine toimub aga alles pärast seda, kui ollakse mängija 2 käest korrektselt vastu võtnud värava koordinaatide info. Vastuvõtmise käsk on while-tsükli sees, mis käib seni, kuni tingimus on tõene. Tingimuseks on Bluetoothi kaudu info lugemise käsu ReceiveRemoteNumber tagastatav väärtus null (parametriseeritult ka kui NO_ERR) lugemise õnnestumise korral. Seni, kuni postkastis pole midagi, tagastab ReceiveRemoteNumber väärtuse, mis on nullist erinev (näiteks 64 tähistab tühja postkasti). Seejärel kontrollitakse tingimust 64 != 0, mis on tõene, sest 64 ju ei võrdu nulliga. Kui aga info saadakse, on tingimuseks 0 != 0, mis on väär, sest 0 on võrdne 0-ga. Selle tulemuse saamisel oleme kindlad, et vastasmängija värava koordinaat saadi kätte ning programmikood suundub järgmist käsku täitma.

Näide. Bluetooth info saatmise/vastuvõtmise funktsioon

int SendReceiveBluetoothInfo(string Data)

{

  //MASTER kirjutab ja loeb alati SLAVE mailboxidest

  //saadame info stringina, et kogu pakett korraga saata

  //seeläbi on BT ühendusi vähem ja mäng kiirem

  int PlayerPos;

  //ootab, kuni saab korrektselt teise mängija väravainfo

  while(ReceiveRemoteNumber

        (MAILBOX4, TRUE, PlayerPos)!=NO_ERR);

  //saadab oma värava ja palli info teisele poole

  SendRemoteString(BTconn, MAILBOX5, Data);

  //tagastab teise mängija värava koordinaadi

  return PlayerPos;

}

Mängija 1. Mängu alginfo initsialiseerimine

Protseduur InitData käivitatakse iga kord enne mängu käivitumist. Esmalt algväärtustatakse palli ja väravate koordinaadid, mis sätitakse alati keskele. Mootorite pöörded salvestatakse muutujatesse, et neid hiljem kasutada värava ja kiiruse juhtimiseks.

Seejärel luuakse muutuja RandomStart millele omistatakse juhuslik väärtus vahemikus 0..119 Random(120).  Pärast seda tehakse kaks uut muutujat RandomRight ja RandomLeft, mis omakorda väljendavad kraadide vahemikku paremale-vasakule, kuhu pall võib alustades startida. Vaata selle kohta selgitavat joonist käesolevas projektis, kus punasega märgitud ala viitab piirkondadele, kuhu pall ei saa startida. Seejärel tehakse taas juhuslikkuse alusel valik, kas pall stardib paremale või vasakule. Seda valik toimub ternary-tingimusega,  mille kontrolliks on käsk Random(2) väärtustega 0 või 1 ja vastavalt sellele otsustab programm, kas valida parem või vasak.

Viimase tegevusena saadetakse vastasmängijale info „RESET“, mida teise mängija programm käsitleb kui mängu alustamise käsklus.

Näide. Mängu andmete algväärtustamine

void InitData()

{

  Winner = 0;

  BallPos.x = DISPLAY_WIDTH/2;

  BallPos.y = DISPLAY_HEIGHT/2;

  Player1Pos = DISPLAY_HEIGHT/2 - GateWidth/2;

  Player2Pos = DISPLAY_HEIGHT/2 - GateWidth/2;

  MootorCenter=MotorRotationCount(OUT_A);

  MootorCenterB=MotorRotationCount(OUT_B);

  //luuakse juhuslik arv vahemikus 0..119

  int RandomStart = Random(120);

  //juhulikust arvust tehakse vahemik -60..60,

  //see vastab ekraani parempoolsele suunale

  int RandomRight = RandomStart - 60;

  //juhulikust arvust tehakse vahemik 120..240

  //see vastab ekraani vasakpoolsele suunale

  int RandomLeft = RandomStart + 120;

  //valitakse juhuslikult, kas alustada parem-vasakule

  BallAngleDeg = Random(2) ? RandomRight : RandomLeft;

  //saadetakse Player2-le info mängu alsutamise kohta

  SendRemoteString(BTconn, MAILBOX6, "RESET");

}

Mängija 1. Mängu võitja kuvamine

clip_image018Protseduur ShowWinner käivitatakse pärast seda, kui funktsioon BallCollide tagastab väärtuse 1 või 2, mille järel väljutakse mängu käigus hoidvast while-tsüklist, kuna muutuja Winner omab nullist erinevat väärtust.

ShowWinner kontrollib muutujat Winner. Kui see on võrdne 1-ga, saadetakse mängija 2 postkasti teade, et ta kaotas ja iseendale kuvatakse teade „Sa võitsid“ ning „Vajuta nuppu jätkamiseks“. Vastupidise võidu korral saadetakse mängijale 2 teade „Sa võitsid“ ning iseendale kuvatakse teade „Sa kaotasid“ ning „Vajuta nuppu jätkamiseks“.

Näide. Mängu võitja kuvamise protseduur

void ShowWinner()

{

  string Win = "Sa voitsid";

  string Lose = "Sa kaotasid";

 

  //kui võitja oli Player1

  if(Winner==1)

  {

    //saadetakse Player2-le teade kaotuse kohta

    SendRemoteString(BTconn, MAILBOX6, Lose);

    TextOut(10, LCD_LINE4, Win);

    TextOut(10, LCD_LINE6, "Vajuta nuppu");

    TextOut(10, LCD_LINE7, "jatkamiseks");

  }

  //kui võitja oli Player2

  else if(Winner==2)

  {

    //saadetakse Player2-le teade võidu kohta

    SendRemoteString(BTconn, MAILBOX6, Win);

    TextOut(10, LCD_LINE6, "Vajuta nuppu");

    TextOut(10, LCD_LINE7, "jatkamiseks");

    TextOut(10, LCD_LINE4, Lose);

  }

  while(!ButtonPressed(BTNCENTER, FALSE));

  while(ButtonPressed(BTNCENTER, FALSE));

}

Mängija 2 mängu loogika

Mängu käivitamisel kontrollitakse, kas Bluetooth töötab. Seejärel oodatakse mängija 1 NXT käest tegevuse sünkroniseerimise teadet Handshake. Kui see on kätte saadud, edastatakse teade OK.

Pärast seda jääb mängija 2 programm ootama while(!BallWidth || !GateWidth), kuni on kätte saanud palli ja värava suuruse info. Antud while-tsükli tingimuses kontrollitakse palli ja värava infot. Mõlemad on alguses võrdsed nulliga, kuid tingimuse kontrollis muudetakse hüüumärgiga nende väärtus vastupidiseks ehk üheks. Seega tingimuse kontroll näeb välja selline: „kas 1 või 1 on võrdne 1-ga“. See on ilmselgelt tõene ja tsükkel käivitatakse. Kui aga üks neist on väärtuse saanud, näeb kontroll välja järgmine „kas 1 või 0 on võrdne 1-ga“, mis on samuti tõene. Kui aga mõlemad muutujad on saanud väärtuse, on kontroll stiilis „kas 0 või 0 on võrdne 1-ga“, mis muidugi pole tõene ja väljutakse tsüklist.

Seejärel käivitub lõpmatult mängu loogika protseduur GameLoop. Esmalt käivitatakse funktsioon PlayerPosition(GateWidth), mis liigutab väravat ja tagastab väärtuse mängija 2 värava koordinaadiga, mis omistatakse muutujale Player2Pos. Sisendparameetriks on värava laius. Pärast seda käivitatakse Bluetooth postkastidest lugemise funktsioon SendReceiveBluetoothInfo. Selle sisendparameetriks on mängija 2 värava koordinaat, muutuja Player2Pos, mis edastatakse mängijale 1. Funktsioon tagastab väärtuse palli ja mängija 1 värava koordinaatidega ja omistab selle muutujale GameData.

Muutujast GameData saadakse stringitöötlusega kätte kõik koordinaadid. Esiteks omistatakse muutujale t kaldkriipsu asukoha väärtus antud stringi sees. Järgmise käsuga SubStr(GameData, 0, t) loetakse muutujast GameData kõik kohad kuni kaldkriipsuni ja tulemus salvestatakse palli x-koordinaadiks. Järgmisele muutujale u omistatakse sidekriipsu asukoha väärtus. Käsuga SubStr(GameData, t+1, u-t) loetakse muutjast GameData kõik kohad alates kaldkriipsu positsioonist kuni sidekriipsuni. Parameeter t+1 tähendab seda, et lugemist alustatakse üks koht pärast kaldkriipsu ning parameeter u-t väljendab loetava stringi pikkust. Värava koordinaat saadakse kätte samuti käsuga SubStr(GameData, u+1, v-u), kuid muutuja v tähistab siin kogu stringi pikkust.

Pärast seda käivitatakse protseduur Render(Player1Pos, Player2Pos, BallPos, GateWidth, BallWidth), mille ülesanne on joonistada ekraanile mõlema värava asukohad ja pall. Selle sisendparameetriteks on esimese ja teise mängija väravate koordinaadid, pallikoordinaadid ja palli ning värava laiused.

Viimase käsuna kuvatakse ekraanil võitja info, kuid alles seejärel, kui üks või teine pool võitis.

Näide. Mängu loogika

void GameLoop()

{

  //CommonFunctions failist käivitatakse funktsioon,

  //mis liigutab värava asukohta, sisendiks värava laius

  Player2Pos = PlayerPosition(GateWidth);

  //vahetatakse Bluetooth infot, edastatakse värava info

  //tagasi saadakse player1 värava ja palli koordinaadid

  GameData = SendReceiveBluetoothInfo(Player2Pos);

 

  //stringitöötluse abil taastatakse

  //värava ja palli koordinaadid 

  int t = Pos("/", GameData);

  BallPos.x = StrToNum(SubStr(GameData, 0, t));

  int u = Pos("-", GameData);

  BallPos.y = StrToNum(SubStr(GameData, t+1, u-t));

  int v = StrLen(GameData);

  Player1Pos = StrToNum(SubStr(GameData, u+1, v-u));

 

  //CommonFunctions failist käivitatakse palli ja

  //väravate kuvamise protseduur

  Render(Player1Pos, Player2Pos,

        BallPos, GateWidth, BallWidth);

  //võitja info kuvatakse ekraanil

  TextOut(10, LCD_LINE4, ShowWinner);

 

  Wait(40);

}

Mängija 2. Bluetooth info saatmine/vastuvõtmine

Funktsiooni SendReceiveBluetoothInfo sisendparameetriks on mängija 2 positsioon, mis saadetakse mängijale 1 ning see tagastab väärtuseks Data, milles sisalduvad palli ja mängija 1 värava koordinaadid.

Postkastist 6 loetakse võitja info. Kui antud postkastis sisaldub info, kontrollitakse tingimust TempShowWinner != "" ja sõnumi sisu kuvatakse mängijale. Kui aga sealt saabub info „RESET“, ei kuvata ekraanil mängijale ühtegi sõnumit.

Näide. Bluetooth info saatmine/vastuvõtmine

string SendReceiveBluetoothInfo(int PlayerPos)

{

  string Data;

  //slave loeb oma lokaalsest mailboxist

  //palli ja värava koordinaadid

  ReceiveRemoteString(MAILBOX5, FALSE, Data);

  //SLAVE kirjutab oma värava koordinaadi MAILBOX4-a

  SendResponseNumber(MAILBOX4, PlayerPos);

  string TempShowWinner = "";

  //SLAVE loeb mailbox6-st võitja info

  ReceiveRemoteString(MAILBOX6, TRUE, TempShowWinner);

  //kui saadi võitja info, kuvatakse see ekraanil

  if(TempShowWinner != "")

    ShowWinner = TempShowWinner;

  //kui saadi nullimise info, siis

  //ei kuvata võitja infot ekraanil

  if(TempShowWinner == "RESET")

    ShowWinner = "";

  return Data;

}

Ühised funktsioonid, CommonFunctions

Funktsioon PlayerPos liigutab väravat, selle sisendparameetriks on värava laius ning see tagastab väärtuse värava koordinaat. Selle funktsiooni kirjeldus on esitatud ping-pong ühele mängu juures, seega siin seda enam kordama ei hakata.

Protseduuri Render kasutatakse selleks, et ekraanile joonistada pall ja väravad. Sisendparameetriteks on palli ja väravate koordinaadid ning palli ja värava laiused. Nii pall kui väravad joonistatakse ekraanile nelinurkadena, funktsiooni RectOut abil. Kui selle funktsiooni laiuseks on 0, siis tulemuseks joonistatakse 1 piksli laiune ristkülik.

Näide. Fail CommonFunctions.NXC

int MootorCenter;

 

//funktsioon PlayerPosition liigutab väravat

//antud funktsiooni sisendiks on värava laius ning

//tagastatav väärtus on värava koordinaat

int PlayerPosition(byte Gate)

{

  int PlayerPos;

  //pöörded jagatakse kahega,

  //et värava liikumine poleks nii tundlik

  int Mootor=MotorRotationCount(OUT_A)/2;

  //algsest mootori väärtusest lahutatakse mootoripöörded

  //pööretele liidetakse pool ekraani kõrgusest,

  //et mängu alguses asuks värav keskel

  PlayerPos = (MootorCenter-Mootor)+DISPLAY_HEIGHT/2;

 

  //kui värav on jõudnud ekraani ulatusest välja

  //omistatakse mootori algväärtusele uus suurus

  //värav jõuab ekraani alumisest serva juurest välja

  if (PlayerPos<=0)

  {

    MootorCenter = Mootor-DISPLAY_HEIGHT/2;

    PlayerPos = 1;

  }

  //värav jõuab ekraani ülemise serva juurest välja

  if (PlayerPos>=DISPLAY_HEIGHT-Gate)

  {

    MootorCenter = Mootor+DISPLAY_HEIGHT/2-Gate;

    PlayerPos = DISPLAY_HEIGHT-Gate;

  }

  return PlayerPos;

}

 

//struktuurne muutuja palli koordinaatide hoidmiseks

struct Position

{

  float x;

  float y;

};

//ekraanil kuvamise protseduur, sisendiks on

//*mängijate värava koordinaadid

//*palli koordianaadid

//*palli ja värava suurused

void Render(int PlayerPosition1,

      int PlayerPosition2, Position BallPos,

      byte Gate, byte Ball)

{

  ClearScreen();

  //ekraanile joonistatakse player1 värav

  RectOut(0, PlayerPosition1, 0, Gate);

  //ekraanile joonistatakse player2 värav

  RectOut(DISPLAY_WIDTH - 1, PlayerPosition2, 0, Gate);

  //ekraanile joonistatakse pall

  RectOut(BallPos.x, BallPos.y, Ball, Ball);

}

Mängija 1 programmikood

Mängija 1 jaoks kirjeldatud funktsioonid ja protseduurid kokku panduna annab esimese mängija NXT programmikoodi. See NXT on mängu juht, esimängija, info edastaja ja Bluetooth master.

Näide. Mängija 1 programmikood

//Bluetooth mäng, MASTER NXT, Mängija 1

 

#include "BluetoothCheck.nxc"

#include "CommonFunctions.nxc"

 

const byte BTconn=1;

const string SlaveNXTName = "NXT2";

const byte GateWidth = 10;  //värava laius

const byte BallWidth = 3;   //palli suurus

 

int Player1Pos;     //värava koordinaat

int Player2Pos;     //värava koordinaat

int BallAngleDeg;   //palli põrkenurk

float Speed;        //palli põrkekiirus

string GameData;    //paarilisele saadetavad andmed

Position BallPos;   //palli positsioon

 

//0: no winner, 1: winner Player1, 2: winner Player2

byte Winner;

long MootorCenterB; //mängu kiiruse muutmise mootor

 

//SendReceiveBluetoothInfo saadab ja võtab vastu infot

//funktsioni sisendiks on palli ja värava info

//mis edastatakse teisele NXT-le

//funktsioon tagastab teise mängija värava koordinaadi

int SendReceiveBluetoothInfo(string Data)

{

  //MASTER kirjutab ja loeb alati SLAVE mailboxidest

  //saadame info stringina, et saata kogu pakett korraga

  //seeläbi on BT ühendusi vähem ja mäng kiirem

  int PlayerPos;

  //ootab, kuni saab korrektselt teise mängija väravainfo

  while(ReceiveRemoteNumber

        (MAILBOX4, TRUE, PlayerPos)!=NO_ERR);

  //saadab oma värava ja palli info teisele poole

  SendRemoteString(BTconn, MAILBOX5, Data);

  //tagastab teise mängija värava koordinaadi

  return PlayerPos;

}

 

//mängu kiiruse muutmise funktsioon

float ChangeGameSpeed()

{

  long Mootor;

  float Kiirus;

  Mootor = MotorRotationCount(OUT_B);

  //muutuja Kiirus arvestab mootori algset asukohta

  //+100 liidetakse selleks, et anda mängule baaskiirus

  Kiirus = MootorCenterB - Mootor + 100;

  if (Kiirus <= 10)

    {

      MootorCenterB = Mootor;

      Kiirus = 10;

    }

  //tagastatav kiirus jagatakse 100-ga,

  //et kiiruse muutus oleks võimalikult väike

  return Kiirus/100;

}

 

//palli liikumise ja nurga arvutamine

void MoveBall()

{

  //esmalt käivitatakse funktsioon, mille vahendusel

  //saab kiiruse, ehk kolmnurga hüpotenuusi

  Speed = ChangeGameSpeed();

  //arvutatakse x-telje koordinaat

  BallPos.x += cosd(BallAngleDeg) * Speed;

  //arvutatakse y-telje koordinaat

  BallPos.y += sind(BallAngleDeg) * Speed;

}

 

//palli põrkamise funktsioon, tagastab väärtused 0, 1, 2

//0: mäng jätkub, 1: Player1 võitis, 2: Player2 võitis

short BallCollide()

{

  //pall põrkab vastu ülemist serva

  if(BallPos.y > DISPLAY_HEIGHT - BallWidth)

  {

    BallAngleDeg = -BallAngleDeg;

    BallPos.y = DISPLAY_HEIGHT - BallWidth - 1;

  }

  //pall põrkub vastu alumist serva

  else if(BallPos.y <= 0)

  {

    BallAngleDeg = -BallAngleDeg;

    BallPos.y = BallWidth;

  }

  //pall põrkab vastu vasakut serva (Player1)

  if(BallPos.x <= 2)

  {

    //kontrollitakse, kas palli põrkekohal on värav ees

    if (BallPos.y < Player1Pos + GateWidth &&

        BallPos.y > Player1Pos - BallWidth)

    {

      BallAngleDeg = 180 - BallAngleDeg;

      BallPos.x = BallWidth;

    }

    //kui väravat ees ei olnud, siis on mäng läbi

    else

    {

      //võitis mängija 2

      return 2;

    }

  }

  //pall põrkab vastu paremat serva (Player2)

  else if(BallPos.x > DISPLAY_WIDTH - BallWidth)

  {

    //kontrollitakse, kas palli põrkekohal on värav ees

    if(BallPos.y < Player2Pos + GateWidth &&

        BallPos.y > Player2Pos - BallWidth)

    {

      BallAngleDeg = 180 - BallAngleDeg;

      BallPos.x = DISPLAY_WIDTH - BallWidth - 1;

    }

    //kui väravat ees ei olnud, siis on mäng läbi

    else

    {

      //võitis mängija 1

      return 1;

    }

  }

  //mäng jätkub

  return 0;

}

 

//mängu funktsioonide väljakutsumine

void GameLoop()

{

  //muutujasse Winner salvestatakse mängu staatus

  Winner = BallCollide();

  //liigutatakse palli

  MoveBall();

  //CommonFunctions failist käivitatakse funktsioon,

  //mis liigutab värava asukohta, sisendiks värava laius

  Player1Pos = PlayerPosition(GateWidth);

  //luuakse string kolmest parameetrist:

  //palli x ja y koordinaadid ning player1 värava koordinaat

  GameData = StrCat(NumToStr(BallPos.x), "/",

            NumToStr(BallPos.y), "-",

            NumToStr(Player1Pos));

  //vahetatakse bluetooth infot, edastatakse mängu info

  //tagasi saadakse player2 mängija värava koordinaat

  Player2Pos = SendReceiveBluetoothInfo(GameData);

  //CommonFunctions failist käivitatakse palli

  //ja väravate kuvamise protseduur

  Render(Player1Pos, Player2Pos,

          BallPos, GateWidth, BallWidth);

  //oodatakse 40ms

  Wait(MS_40);

}

 

//võitja kuvamise protseduur

void ShowWinner()

{

  string Win = "Sa voitsid";

  string Lose = "Sa kaotasid";

 

  //kui võitja oli Player1

  if(Winner==1)

  {

    //saadetakse Player2-le teade kaotuse kohta

    SendRemoteString(BTconn, MAILBOX6, Lose);

    TextOut(10, LCD_LINE4, Win);

    TextOut(10, LCD_LINE6, "Vajuta nuppu");

    TextOut(10, LCD_LINE7, "jatkamiseks");

  }

  //kui võitja oli Player2

  else if(Winner==2)

  {

    //saadetakse Player2-le teade võidu kohta

    SendRemoteString(BTconn, MAILBOX6, Win);

    TextOut(10, LCD_LINE6, "Vajuta nuppu");

    TextOut(10, LCD_LINE7, "jatkamiseks");

    TextOut(10, LCD_LINE4, Lose);

  }

  while(!ButtonPressed(BTNCENTER, FALSE));

  while(ButtonPressed(BTNCENTER, FALSE));

}

 

//algse andmestiku initsialiseerimine

void InitData()

{

  Winner = 0;

  BallPos.x = DISPLAY_WIDTH/2;

  BallPos.y = DISPLAY_HEIGHT/2;

  Player1Pos = DISPLAY_HEIGHT/2 - GateWidth/2;

  Player2Pos = DISPLAY_HEIGHT/2 - GateWidth/2;

  MootorCenter=MotorRotationCount(OUT_A);

  MootorCenterB=MotorRotationCount(OUT_B);

  //luuakse juhuslik arv vahemikus 0..119

  int RandomStart = Random(120);

  //juhulikust arvust tehakse vahemik -60..60,

  //mis vastab ekraani parempoolsele suunale

  int RandomRight = RandomStart - 60;

  //juhulikust arvust tehakse vahemik 120..240,

  //mis vastab ekraani vasakpoolsele suunale

  int RandomLeft = RandomStart + 120;

  //valitakse juhuslikult, kas algus on parem-vasak

  BallAngleDeg = Random(2) ? RandomRight : RandomLeft;

  //saadetakse Player2-le info mängu alustamise kohta

  SendRemoteString(BTconn, MAILBOX6, "RESET");

}

task main()

{

  //käivitatakse BT ühenduse kontroll ja/või loomine

  //kui ühendamine ei õnnestunud, väljutakse programmist

  if(!BluetoothConnect(BTconn, SlaveNXTName)==NO_ERR)

    Stop(TRUE);    

 

  //kuvame kasutajale ühe sekundi jooksul teate

  Wait(SEC_1);

 

  string shakemessage = "";

  while(shakemessage != "GO")

  {

    ReceiveRemoteString(MAILBOX1, TRUE, shakemessage);

    SendRemoteString(BTconn, MAILBOX1, "Handshake");

  }

  Wait(20);

  //saadetakse palli suuruse info

  SendRemoteNumber(BTconn, MAILBOX2, BallWidth);

  Wait(20);

  //saadetakse värava laiuse info

  SendRemoteNumber(BTconn, MAILBOX3, GateWidth);

  while(TRUE)

  {

    //nullitakse mängu andmed

    InitData();

    //mäng käib, kuni võitjat pole selgunud

    while(!Winner)

    {

      GameLoop();

    }

    //kui võitja on selgunud, kuvatakse see ekraanil

    ShowWinner();

  }

}

Mängija 2 programmikood

Mängija 2 jaoks kirjeldatud funktsioonid ja protseduurid kokku panduna on teise mängija NXT programmikood. See NXT on rollis Bluetooth slave ja tegeleb ainult info vastuvõtmise ja kuvamisega.

Näide. Mängija 2 programmikood

//SLAVE

#include "BluetoothCheck.nxc"

#include "CommonFunctions.nxc"

 

byte GateWidth;     //värava laius

byte BallWidth;     //palli suurus

int Player1Pos;     //mängija 1 värav

int Player2Pos;     //mängija 2 värav

string GameData;    //vastuvõetud mänguandmed

string ShowWinner;  //võitja kuvamine

Position BallPos;   //palli positsioon

 

//SendReceiveBluetoothInfo saadab ja võtab vastu infot

//funktsiooni sisendiks on värava info,

//mis on vaja edastada teisele NXT-le

//funktsioon tagastab mängija1 värava ja palli koordinaadid

//mängu võiduteade omistatakse globaalsele muutujale ShowWinner

string SendReceiveBluetoothInfo(int PlayerPos)

{

  string Data;

  //slave loeb oma lokaalsest mailboxist

  //palli ja värava koordinaadid

  ReceiveRemoteString(MAILBOX5, FALSE, Data);

  //SLAVE kirjutab oma värava koordinaadi MAILBOX4-a

  SendResponseNumber(MAILBOX4, PlayerPos);

  string TempShowWinner = "";

  //SLAVE loeb mailbox6-st võitja info

  ReceiveRemoteString(MAILBOX6, TRUE, TempShowWinner);

  //kui saadi võitja info, kuvatakse see ekraanil

  if(TempShowWinner != "")

    ShowWinner = TempShowWinner;

  //kui saadi nullimise info, ei kuvata võitja infot

  if(TempShowWinner == "RESET")

    ShowWinner = "";

  return Data;

}

 

//mängu funktsioonide väljakutsumine

void GameLoop()

{

  //CommonFunctions failist käivitatakse funktsioon

  //mis liigutab värava asukohta, sisendiks värava laius

  Player2Pos = PlayerPosition(GateWidth);

  //vahetatakse bluetooth infot, edastatakse värava info

  //tagasi saadakse player1 värava ja palli koordinaadid

  GameData = SendReceiveBluetoothInfo(Player2Pos);

 

  //stringitöötluse abil taastatakse

  //värava ja palli koordinaadid 

  int t = Pos("/", GameData);

  BallPos.x = StrToNum(SubStr(GameData, 0, t));

  int u = Pos("-", GameData);

  BallPos.y = StrToNum(SubStr(GameData, t+1, u-t));

  int v = StrLen(GameData);

  Player1Pos = StrToNum(SubStr(GameData, u+1, v-u));

 

  //CommonFunctions failist käivitatakse

  //palli ja väravate kuvamise protseduur

  Render(Player1Pos, Player2Pos,

          BallPos, GateWidth, BallWidth);

  //võitja info kuvatakse ekraanil

  TextOut(10, LCD_LINE4, ShowWinner);

  Wait(40);

}

task main()

{

  //käivitatakse BT ühenduse kontroll

  //kui ühendamine ei õnnestunud, väljutakse programmist

  if(!BluetoothCheck()==NO_ERR)

    Stop(TRUE);

 

  string shakemessage;

  while(shakemessage != "Handshake")

  {

    ReceiveRemoteString(MAILBOX1, TRUE, shakemessage);

  }

  TextOut(0,LCD_LINE1, "Waiting for bluetooth");

 

  SendResponseString(MAILBOX1, "GO");

  while(!BallWidth || !GateWidth)

  {

    ReceiveRemoteNumber(MAILBOX2, FALSE, BallWidth);

    ReceiveRemoteNumber(MAILBOX3, FALSE, GateWidth);

  }

  MootorCenter = MotorRotationCount(OUT_A);

  //mäng käib, kuni see katkestatakse

  while(TRUE)

  {

    GameLoop();

  }

}

 

9. 9 klass 3 õppetundi Bluetooth puldiauto

Tiigrihype_logoSee materjal on loodud Tiigrihüppe Sihtasutuse programmi ProgeTiiger raames.

Õpime, kuidas programmeerida bluetoothi vahendusel juhitavat puldiautot ning seda nii keeravate rataste ja diferentsiaalveermikuga auto kui ka tavalise roboti korral.

Paljude poiste unistuseks on olnud omada puldiautot, just sellist, mida saaks eemalt juhtida.

Robotimaailmas pole midagi lihtsamat, kui ehitada inimese juhitav robot ja korrektsuse huvides olgu välja toodud, et selliseid roboteid nimetatakse manipulaatoriteks.

Hetkel nimetame seda siiski puldiautoks või robotiks.

Roboti ehitus

Selle roboti ehitamine koosneb kahest iseseisvast komponendist, autost ja juhtpuldist. Auto võib teha kas lihtsa, kasutades baasrobotit, või keerulisema ja nutikama.

Lihtne auto tähendab seda, et auto kummagi vedava ratta küljes on oma mootor. Seega roboti otsesõitmiseks töötavad mootorid samal kiirusel, kuid keeramise korral peab üks mootor liikuma kiiremini ning teine aeglasemalt. Seda robotit nimetame siin peatükis baasrobot puldiautoks.

Nutikas auto eeldab vedava silla juures diferentsiaali kasutamist ja vedavate rataste juhtimist ühe mootoriga. Teine mootor on ühendatud esimeste ratastega, mis on võimelised keerama. See meenutab juba päris autot, millel on samuti neli ratast ning mis keeramiseks pöörab oma esimesi rattaid paremale-vasakule.

Nutikama auto ehitamiseks tasub kulutada aega ja proovide erinevaid variante. Juuresolev pilt on auto altvaates, millel on näha vasakul pool diferentsiaal ja paremal pool keeravad rattad.

Nutikama auto juures tuleb tagumised ehk vedavad rattad ühendada omavahel diferentsiaaliga. See võimaldab autol keerata sellisel viisil, et vedavad rattad ei hakka mööda maad libisedes teineteist takistama. Diferentsiaal võimaldab jagada ratastevahelist kiirust sellisel viisil, et üks ratas sõidab kiiremini kui teine. Sellise auto võib ise välja mõelda või leida internetist juba valmis tehtud näidise.

 

Juhtpult koosneb NXT-st ja kahest mootorist. Üks mootor tuleb panna toimima roolina ning teine mootor on kiiruse jaoks. Kõige sobilikum on pult, kui sellel on peal rool just samasuguses positsioonis nagu autol, ehk „näoga“ juhi poole. Teine mootor võiks olla vertikaalselt ning selle küljes peaks olema nn. käigukang. Kui kang on otse üleval, siis auto seisab, kui lükata ettepoole, hakkab auto edasi liikuma, muutes sujuvalt oma kiirust vastavalt kangi asendile ning kangi tahapoole tõmmates hakkab auto vastavalt tagurpidi liikuma. See kang toimib üheaegselt nii käigukangi kui gaasipedaalina.

Juhtpuldi programmeerimine

Kummagi NXT, nii juhtpuldi kui auto jaoks, on vaja kirjutada oma programm. Siin lahenduses on tarvis ainult ühesuunalist kommunikatsiooni – puldist autole. Auto ei pea juhtpuldile midagi teatama. Seega juhtpuldi NXT on Bluetooth ühenduse korral masteri rollis ning auto oma on rollis slave. See peab olema just niipidi, kuna Bluetoothi korral on master see, kes alustab ühenduse loomist ja signaalide edastamist.

Juhtpult täidab kolme funktsiooni. Esiteks on juhtpult see, mis algatab Bluetooth ühenduse loomise. Teise funktsioonina saadab juhtpult autole käsklused edasisõitmise ning pööramise kohta. Kolmandaks kuvab juhtpult oma ekraanil auto rooli ja gaasipedaali asendid.

Bluetooth ühenduse loomine on tehtud siin standardsel viisil, nagu seda on kirjeldatud käesoleva raamatu juhendis Bluetooth ühenduse loomise osas. Seega on Bluetooth ühenduse loomiseks vajalik lisafail BluetoothCheck.nxc, mida kasutavad nii master kui slave programmid ja seda pole siinkohal rohkem käsitletud.

Info edastamine

Info edastamise alamprogramm saadab autole infot rooli ja pedaali asendite kohta.

Selle alamprogrammi sees on lõpmatuseni käiv while-tsükkel, kuni ta tsükliväliselt lõpetatakse. Tsükli käivitumisel omistatakse muutujale rool väärtus A mootorist, mida kasutatakse roolina. Seejärel kontrollitakse, kas saadud väärtus jääb vahemikku -180 kuni 180. Kui muutuja väärtus on suurem kui 180, siis säilitatakse selles jätkuvalt väärtus 180. See kontroll tagab, et auto rooli juhtimise programm saab kindla numbrivahemiku rooli keeramiseks. Selge on see, et auto rattad ei hakka keerama 360 kraadi, vaid keeravad kokku ca 90 kraadi – 45 kraadi ühele ja 45 teisele poole.

Seejärel omistatakse muutuja rooli väärtus globaalsele muutujale RooliNurk, mida kasutatakse rooli asukoha joonistamiseks. Seejärel saadetakse rooli väärtus Bluetoothi kaudu auto NXT-le käsuga SendRemoteNumber(BTconn, MAILBOX1, rool).

Sama tegevus toimub gaasipedaaliga, kuid selle vahega, et numbrid hoitakse vahemikus -90…90 kraadi ja muutujale PedaaliNurk, millega joonistatakse gaasipedaali asukoht ekraanil, omistatakse väärtus muutujast kiirus.

Näide. Juhtpuldi programm, info edastamine autole

task InfoEdastamine()

{

  //loome muutujad rooli ning gaasipedaali tarvis

  long rool, kiirus;

 

  //käivitame lõpmatu tsükli, mille käigus saadetakse

  //SLAVE-le rooli ning käigukangi asendi info

  while (TRUE)

  {

    //omistame muutujale rool väärtused mootorist A: +/-180

    rool = MotorRotationCount(OUT_A);

    if (rool>180) rool = 180;

    if (rool<-180) rool = -180;

    RooliNurk = rool;

   

    //saadame rooli väärtuse SLAVE NXT-le

    SendRemoteNumber(BTconn, MAILBOX1, rool);

   

    //omistame muutujale kiirus väärtused mootorist B: +/-90

    kiirus = MotorRotationCount(OUT_B);

    if (kiirus>90) kiirus = 90;

    if (kiirus<-90) kiirus = -90;

    PedaaliNurk = kiirus;

   

    //saadame gaasipedaali väärtuse SLAVE NXT-le

    SendRemoteNumber(BTconn, MAILBOX2, kiirus);

  }

}

Rooli kuvamine ekraanil

clip_image002Rooli kuvamisel ekraanil on meelelahutuslik ja õpetlik eesmärk. Ekraanil antakse edasi rooli kujutis, mis keerab kaasa, kui rooli keeratakse.

Alamprogrammi RooliAsukoht käivitamisel luuakse konstant RooliRaadius, mis määrab rooli suuruse ekraanil, ning muutujad xAlg ja yAlg, mis määravad rooli keskkoha ekraanil.

Pärast seda käivitatakse lõpmatu tsükkel, milles kasutusel olev globaalne muutuja RooliNurk  väärtustatakse rooliinfot edastava mootori positsiooniga 1000 korda sekundis.

Rool on kolme roolipulgaga, nende jaoks on vajalikud roolipulkade lõppkoordinaadid x1, x2, x3, ja y1, y2, y3. Roolipulgad joonistatakse ekraanile funktsiooniga LineOut, mille alg-koordinaat on ringjoone keskpunkt ning lõppkoordinaat roolipulga lõpp, mis asub rooli välimisel ringil.

Lõppkoordinaadid leitakse täisnurkse kolmnurga lahendamisega. Kuna rooli läbimõõt on muutumatu, siis roolipulga pikkust on alati ringi raadiuseks.  Muutub aga rooli asend, seega ka nurk rooli algse ehk keskasendi suhtes. Teades rooli raadiust (kolmnurga hüpotenuus) ja rooli pöördenurka (kolmnurga nurk), saab välja arvutada roolipulga välimised koordinaadid siinus ja koosinus teoreeme kasutades.

Teised roolipulgad asuvad keskmise roolipulga suhtes 120 kraadi võrra paremal ja 120 kraadi võrra vasakul. Seega koordinaatide x2, y2 ja x3, y3 leidmiseks vastavalt liidetakse ja lahutatakse rooli pöördenurgast 120 enne siinuse ja koosinuse võtmist.

 

clip_image004

 

Järgmiste valemite abil saab lahendada roolipulkade kuvamise ülesande.

clip_image006

clip_image008

 

Vastaskaatet: x-telje koordinaat

Lähiskaatet: y-telje koordinaat

Teravnurk: rooli pöördenurk kraadides

Hüpotenuus: rooli raadius

Järgnev joonis ilmestab roolipulkade arvutamist, vastaskaatet on võrdne x-telje koordinaadiga ja lähiskaatet on võrdne y-telje koordinaadiga.

Programmi koodis joonistavad järgmised kolm funktsiooni CircleOut valmis rooli välimise ketta. Iga järgmine ring on eelmisest ühe piksli võrra suurem. Seega rooliratta välimise ringi paksus on 3 pikslit. Neljas CircleOut joonistab roolile rooliratta sisemise ringi ja värvib selle seest piksleid täis.

Kolm funktsiooni LineOut joonistavad valmis kolm erinevat roolipulka, mille koordinaadid on eelnevalt välja arvutatud.

Näide. Rooli keeramise kuvamine ekraanil

task RooliAsukoht()

{

  const int RooliRaadius = 25;

  int xAlg=50;

  int yAlg=30;

  int x1, x2, x3;

  int y1, y2, y3;

 

  while(TRUE)

  {

    ClearScreen();

    //arvutatakse välja ülemise roolisamba koordinaat

    x1 = sind(RooliNurk)*RooliRaadius;

    y1 = cosd(RooliNurk)*RooliRaadius;

    //arvutatakse välja parempoolse roolisamba koordinaat

    x2 = sind(RooliNurk+120)*RooliRaadius;

    y2 = cosd(RooliNurk+120)*RooliRaadius;

    //arvutatakse välja vasakpoolse roolisamba koordinaat

    x3 = sind(RooliNurk-120)*RooliRaadius;

    y3 = cosd(RooliNurk-120)*RooliRaadius;

    //joonistatakse välimine rooliratta ring

    CircleOut(xAlg, yAlg, RooliRaadius);

    CircleOut(xAlg, yAlg, RooliRaadius+1);

    CircleOut(xAlg, yAlg, RooliRaadius+2);

    //joonistatakse rooliratta keskmine ring

    //ja värvitakse seest täis

    CircleOut(xAlg, yAlg, 5, DRAW_OPT_FILL_SHAPE);

   

    //kuvame ekraanil rooli ülemise samba

    LineOut(xAlg, yAlg, xAlg+x1, yAlg+y1);

    //kuvame ekraanil rooli parempoolse samba

    LineOut(xAlg, yAlg, xAlg+x2, yAlg+y2);

    //kuvame ekraanil rooli vasakpoolse samba

    LineOut(xAlg, yAlg, xAlg+x3, yAlg+y3);

   

    Wait(80);

  }

}

Gaasipedaali kuvamine ekraanil

Ekraanil kuvatakse väikeste ristkülikutega gaasipedaali asukoht. Kui pedaal on keskel, siis auto seisab, ning vastavalt ülespoole lükates liigub auto edasi ja allapoole tõmmates tagasi.

Muutujaid xAlg ja yAlg kasutatakse esimese ehk siis keskmise ruudu joonistamiseks ekraanile. Muutuja laius määrab joonistatava ruudu laiuse. Muutuja korgus määrab kõrguse pikslites, mis on ühe ruudu alumisest küljest kuni järgmise ruudu alumise küljeni. Muutuja RuutKorgus on kahe ühiku võrra väiksem muutujast korgus, arvestades ruudu külje joonistamise paksuse ja ruutude vahele jääva ühe piksli suuruse vahega.

Globaalsest muutujast PedaaliNurk saabub gaasipedaali asendi info vahemikus -90 .. 90. Programmis jagatakse see kolmega, et saada tulemuseks -30…30, mis on sobilik ekraanile joonistamiseks, kuna ekraani kõrgus on 64 pikslit.

Järgnevalt on kaks for-tsüklit,  esimene neist on kasutusel ülemiste ja teine alumiste ristkülikute joonistamisel. Kogu programmikood on lõpmatu tsükli sees, seega igal ringil käivitatakse mõlemad for-tsüklid ja arvutatakse uuesti ristkülikute asukohad.

For-tsükkel alustab iga kord nullist ja käib kuni numbrini, mis saadakse muutujast GaasiPedaal. Tsükli sees on if-tingimuslause, mis kontrollib, kas for-tsükli numbri jagamisel muutujaga kõrgus on jääk null.  Kuna käesolevas näites on kõrgus=5, siis tulemus on null iga viie ühiku järel. Seega kui GaasiPedaal on maksimaalses asendis, leiab antud if-lause kuus korda nulli (30/5=6). Kui if-lause tingimus on tõene, joonistatakse valmis ristkülik, mille x-koordinaat on xAlg ja y-koordinaat on yAlg, millele on liidetud üks kõrgus (see on vajalik esimese ruudust möödumiseks) ning liidetakse juurde for-tsükli vastav number. Tulemus on see, et iga 5 piksli järel joonistatakse uus ristkülik ja iga ristküliku vahele jääb 1 piksel. Järgmisel joonisel on välja toodud kuidas vastavalt pedaali asendile arvutatakse for-tsükli sees välja ruudu joonistamise asukoht.  

clip_image010

Sarnase loogika kohaselt saab tekitada allapoole joonistatavad ristkülikud, kuid miinusmärgiga.

Näide. Gaasipedaali asendi kuvamine ekraanil

task PedaaliAsukoht()

{

  int xAlg=90;

  int yAlg=28;

  int laius=9;

  int korgus=5;

  int RuutKorgus=korgus - 2;

  while(TRUE)

  {

    //kuvame keskmise ristküliku

    RectOut(xAlg, yAlg, laius, RuutKorgus);

    //jagame gaasipedaali 3-ga,

    //tulemuseks on poole ekraani kõrgus

    int GaasiPedaal=PedaaliNurk/3;

    //kuvame ekraani ülemise poole ristkülikud

    for(int i=0; i<GaasiPedaal; i++)

    {

      //iga uus ristkülik joonistatakse

      //ühiku "kõrgus" järel

      if(i%korgus==0)

        //joonistatakse ristkülik

        RectOut(xAlg, yAlg+korgus+i, laius, RuutKorgus);

    }

    //kuvame ekraani alumisele poolele ristkülikud

    for(int i=0; i>GaasiPedaal; i--)

    {

      if(i%korgus==0)

        //joonistatakse ristkülik

        RectOut(xAlg, yAlg+i, laius, RuutKorgus);

    }

  }

}

Nutika auto programmeerimine

Auto tegeleb Bluetoothi vahendusel info vastuvõtmisega, esimeste rataste pööramisega ning vedavate rataste juhtimisega. Lisaks nimetatud funktsioonidele on autol üks ühekordne ülesanne, mis seisneb pööravate rataste keskasendisse panemises.

Bluetooth ühendus luuakse standardsel viisil, seda on käsitletud juhendis Bluetooth ühenduse loomise juures ja siin rohkem üle ei korrata.

Info vastuvõtmine

Info vastuvõtmine on lõpmatu tsükkel, mis saab suure sagedusega (ca 100 korda sekundis) puldi käest infot ja salvestab selle globaalsetesse muutujatesse Rool ning Kiirus.

Näide. Bluetooth info vastuvõtmine

task InfoVastuvott()

{

  while(TRUE)

  {

    //Rool on vahemikus -180..180

    //Kiirus on vahemikus -90..90

    ReceiveRemoteNumber (MAILBOX1, FALSE, Rool);

    ReceiveRemoteNumber (MAILBOX2, FALSE, Kiirus);

  }

}

Rataste keeramine keskasendisse

Autot käivitades pole teada, millisesse asendisse jäid pööravad rattad, seega enne sõitma hakkamist on tarvis saada need keskasendisse. Keskasendisse keeramine töötab põhimõttel, et rattad keeratakse paremale ja siis vasakule lõpuni välja ning arvutatakse seejärel saadud kraadide vahe.

Programmi alguses keeratakse rattad ühe sekundi jooksul paremale. Sõltuvalt rooli ülekandest, kui kaua läheb aega ühest servast teise jõudmisega, võib seda ootamist kas pikendada või lühendada. Seejärel omistatakse muutujale parem mootori pöörde kraadid. Roolimootor keeratakse vasakule ning muutujale vasak omistatakse teises servas mootori pöörde kraadid.

Seejärel võetakse vasaku ja parema poole vahe, jagatakse see kahega ning lisatakse miinusmärk. Selle tehte tulemusena on programmil teada, kui palju on vaja hetkeasendist keerata tagasi, et rattad asuksid keskel.

Funktsioon PosRegEnable võimaldab kasutada mootorit sellisel moel, et seda saab kraadi täpsusega juhtida ja hoida ettenähtud kraadi peal algse suuna suhtes. Selle omaduse abil on võimalik väga täpselt juhtida keeravaid rattaid. See funktsioon kasutab mootori hoidmiseks PID kontrollerit ning kolm viimast numbrilist parameetrit ongi vastavalt P, I ja D parameetrid. PID kontrollerist on juttu joonejärgija juures, seega siinkohal sellel pikemalt ei peatuta.

Funktsioon PosRegSetAngle võimaldab mootorit keerata täpselt mingi kraadi võrra ja hoida mootori positsiooni selles kindlas asendis. Käesoleval juhul on arvestatud, et mootor on pärast vasakule keeramist igal juhul ühes servas, seega mootorit peab tagasi keerama kraadi võrra, mis arvutati välja ja omistati muutujale RattadKeskel.

Nüüd on rattad keskel, seega võib käivitada rataste keeramise ja edasi-tagasi sõitmise alamprogrammid.

Näide. Rataste keeramine keskele

task RattadKeskele()

{

  long parem, vasak;

 

  //rattad keeratakse paremale lõpuni välja 1 sekundi jooksul

  OnFwd(OUT_C, 40);

  Wait(SEC_1);

  parem = MotorRotationCount(OUT_C);

 

  //rattad keeratakse vasakule lõpuni välja 1 sekundi jooksul

  OnFwd(OUT_C, -40);

  Wait(SEC_1);

  vasak = MotorRotationCount(OUT_C);

  //mootorid lülitatakse välja

  Off(OUT_C);

  //arvutatakse saadud tulemuste vahe ning jagatakse see kahega

  RattadKeskel = -((vasak-parem)/2);

 

  //võimaldatakse kasutada mootorit viisil,

  //et seda saab kraadi kaupa juhtida ja hoida kindlas asendis

  PosRegEnable(OUT_C, 40, 10, 10);

 

  //rattad pööratakse keskasendisse

  PosRegSetAngle(OUT_C, RattadKeskel);

  Wait(SEC_1);

 

  //käivitatakse rataste keeramise ja

  //edasisõitmise alamprogrammid

  StartTask(KeeraRattaid);

  StartTask(RobotSoida);

}

Sõitmine ja keeramine

Roboti edasisõitmise ja keeramise jaoks on omaette alamprogrammid. See on lõpmatult käiv tsükkel, mis annab Bluetooth vastuvõtust saadud kiiruse info edasi vedavatele mootoritele.

Näide. Sõitmise alamprogramm

task RobotSoida()

{

  while(TRUE)

  {

    //roboti edasisõitmine

    OnFwdReg(OUT_B, Kiirus, OUT_REGMODE_SPEED);

  }

}

Rataste keeramise alamprogramm saab info Bluetooth vastuvõtjast globaalse muutujaga Rool. Sellele liidetakse juurde suurus, mis arvutati rataste keskele pööramise funktsioonist. See tagab rataste õige käitumise. Käsuga PosRegSetAngle(OUT_C, Asend) toimub roolirataste pööramine. PosRegSetAngle funktsiooni kasutamine võimaldati RattadKeskele alamprogrammis funktsiooniga PosRegEnable, seetõttu seda siin alamprogrammis rohkem kordama ei pea.

Näide. Keeramise alamprogramm

task KeeraRattaid()

{

  long Asend;

  while(TRUE)

  {

    //roolile liidetakse juurde eelnevalt saadud

    //rataste keskasukoht

    Asend=Rool+RattadKeskel;

    //pööravate rataste mootori juhtimine

    PosRegSetAngle(OUT_C, Asend);

  }

}

Baasroboti auto programmeerimine

Baasrobotist puldiautol on kaks vedavat ratast omaette mootoritega. Kui robot peab otse sõitma, siis mõlemad mootori liiguvad võrdse kiirusega. Kui aga robot peab keerama, siis üks mootoritest liigub aeglasemalt ja teine kiiremini.

Järgnevalt on välja toodud rataste juhtimise alamprogramm. Rataste juhtimise sisendiks saab kasutada Kiirus ja Rool nimelisi globaalseid muutujaid, mis saadakse Bluetoothi kaudu juhtpuldi käest.

Rataste juhtimine on lõpmatu tsükli sees. Esimese arvutustehtega tagatakse, et rooli suund mõjutaks mootoreid vastassuunaliselt. Nimelt mootor B-le liidetakse rooli info ja mootor C-st lahutatakse rooliinfo. Rooli info leitakse tehtega Kiirus*Rool/200. Kiirus korrutatakse rooliga seetõttu, et kui auto sõidab kiiremini, siis rooli mõju peab olema suurem ning aeglase sõidu korral peab rooli mõju olema väiksem. Rooli info jagatakse omakorda 200-ga, kuna Bluetoothi kaudu on rooli muutuja väärtus vahemikus -180..180 ja see on liiga suur vahemik, seega antud jagamistehtega teisendame rooli info auto juhtimiseks sobilikuks vahemikuks.

Järgmine rida MotorB=abs(MotorB)<abs(Kiirus)?MotorB:Kiirus on ternary-tingimuslause,   kus võrreldakse eelnevalt välja arvutatud mootori kiiruse juhtpuldist tuleva kiiruse absoluutväärtuseid. Mootorile antakse edasi neist väiksem suurus.

Selle tingimuslause tulemusena auto keerab ratta aeglustamisega ning keeramine tundub loomulik. Kui see tingimuslause ära jätta, siis keeramise korral auto kiirendaks ühe rattaga ja teisega aeglustaks, mis teeks roboti keeramise veidi ebaloomulikuks.

Näide. Roboti sõitmise ja keeramise alamprogramm

task RobotSoida()

{

  int MotorB, MotorC;

 

  while(TRUE)

  {

    //puldist saadud kiiruse ja rooli info teisendamine

    MotorB = Kiirus + Kiirus*Rool/200;

    MotorC = Kiirus - Kiirus*Rool/200;

    //kiiruse muutmine viisil, et keeramine toimuks ainult

    // ratta aeglustamisega, st. võetakse minimaalne suurus

    MotorB = abs(MotorB) < abs(Kiirus) ? MotorB : Kiirus;

    MotorC = abs(MotorC) < abs(Kiirus) ? MotorC : Kiirus;

   

    //roboti edasisõitmine       

    OnFwdReg(OUT_B, MotorB, OUT_REGMODE_SPEED);

    OnFwdReg(OUT_C, MotorC, OUT_REGMODE_SPEED);

  }

}

Lõplikud programmid

Näide. Juhtpuldi programm

//MASTER. pult

#include "BluetoothCheck.nxc"

 

const byte BTconn=1;

const string SlaveNXTName = "NXT6";

 

int RooliNurk;

int PedaaliNurk;

 

task RooliAsukoht()

{

  int RooliRaadius = 25;

  int xAlg=50;

  int yAlg=30;

  int x1, x2, x3;

  int y1, y2, y3;

  while(TRUE)

  {

    ClearScreen();

    //arvutatakse välja ülemise roolisamba koordinaat

    x1 = sind(RooliNurk)*RooliRaadius;

    y1 = cosd(RooliNurk)*RooliRaadius;

    //arvutatakse välja parempoolse roolisamba koordinaat

    x2 = sind(RooliNurk+120)*RooliRaadius;

    y2 = cosd(RooliNurk+120)*RooliRaadius;

    //arvutatakse välja vasakpoolse roolisamba koordinaat

    x3 = sind(RooliNurk-120)*RooliRaadius;

    y3 = cosd(RooliNurk-120)*RooliRaadius;

    //joonistatakse välimine rooliratta ring

    CircleOut(xAlg, yAlg, RooliRaadius);

    CircleOut(xAlg, yAlg, RooliRaadius+1);

    CircleOut(xAlg, yAlg, RooliRaadius+2);

    CircleOut(xAlg, yAlg, RooliRaadius+3);

    //joonistatakse keskmine rooliratta ring

    //ja värvitakse seest täis

    CircleOut(xAlg, yAlg, 5, DRAW_OPT_FILL_SHAPE);

   

    //kuvame ekraanil rooli ülemise samba

    LineOut(xAlg, yAlg, xAlg+x1, yAlg+y1);

    //kuvame ekraanil rooli parempoolse samba

    LineOut(xAlg, yAlg, xAlg+x2, yAlg+y2);

    //kuvame ekraanil rooli vasakpoolse samba

    LineOut(xAlg, yAlg, xAlg+x3, yAlg+y3);

   

    Wait(80);

  }

}

 

task PedaaliAsukoht()

{

  int xAlg=90;

  int yAlg=28;

  int laius=9;

  int korgus=5;

  int RuutKorgus=korgus - 2;

  while(TRUE)

  {

    //kuvame keskmise ristküliku

    RectOut(xAlg, yAlg, laius, RuutKorgus);

    //jagame gaasipedaali 3-ga, tulemuseks on

    //ligikaudne poole ekraani kõrgus

    int GaasiPedaal=PedaaliNurk/3;

    //kuvame ekraani ülemise poole ristkülikud

    for(int i=0; i<GaasiPedaal; i++)

    {

      //iga ristkülik joonistatakse ühiku "kõrgus" järel

      if(i%korgus==0)

        //joonistatakse ristkülik

        RectOut(xAlg, yAlg+korgus+i, laius, RuutKorgus);

    }

    //kuvame ekraani alumise poole ristkülikud

    for(int i=0; i>GaasiPedaal; i--)

    {

      if(i%korgus==0)

        //joonistatakse ristkülik

        RectOut(xAlg, yAlg+i, laius, RuutKorgus);

    }

  }

}

 

task InfoEdastamine()

{

  //muutujad rooli ja gaasipedaali/käigukangi tarvis

  long rool, kiirus;

 

  //käivitame info edastasmise tsükli, mis saadab

  //100 korda sekundis rooli ning käigukangi asendit

  while (TRUE)

  {

    //omistame muutujale rool

    //väärtused mootorist A: +/-180

    rool = MotorRotationCount(OUT_A);

    if (rool>180) rool = 180;

    if (rool<-180) rool = -180;

    RooliNurk = rool;

    //rooli saadetav info muudetakse märgiliselt vastupidiseks,

    //see sõltub mootori asendist, kui mootor ringi keerata

    //tuleb alljärgnev rida eemaldada

    rool = -rool;

   

    //saadame rooli väärtuse SLAVE NXT-le

    SendRemoteNumber(BTconn, MAILBOX1, rool);

   

    //omistame muutujale kiirus

    //väärtused mootorist B: +/-90

    kiirus = MotorRotationCount(OUT_B);

    if (kiirus>90) kiirus = 90;

    if (kiirus<-90) kiirus = -90;

    PedaaliNurk = kiirus;

    //kiiruse saadetav info muudetakse märgiliselt vastupidiseks,

    //see sõltub mootori asendist, kui mootor ringi keerata

    //tuleb alljärgnev rida eemaldada

    kiirus = -kiirus;

   

    //saadame gaasipedaali väärtuse SLAVE NXT-le

    SendRemoteNumber(BTconn, MAILBOX2, kiirus);

  }

}

 

task main()

{

  //käivitatakse BT ühenduse kontroll ja/või loomine

  //kui ühendamine ei õnnestunud, väljutakse programmist

  if(!BluetoothConnect(BTconn, SlaveNXTName)==NO_ERR)

    Stop(TRUE);    

 

  //kuvame kasutajale ühe sekundi jooksul teate

  Wait(SEC_1);

  ClearScreen();

  //palume rooli ja käigukangi keskasendisse keerata

  TextOut(0, LCD_LINE1, "Palun keera");

  TextOut(0, LCD_LINE2, "rool ja");

  TextOut(0, LCD_LINE3, "gaasipedaal");

  TextOut(0, LCD_LINE4, "keskasendisse.");

  while(!ButtonPressed(BTNCENTER, FALSE));

  while(ButtonPressed(BTNCENTER, FALSE));

  //nullime mootori counterid

  ResetRotationCount(OUT_AB);

 

  StartTask(RooliAsukoht);

  StartTask(PedaaliAsukoht);

  StartTask(InfoEdastamine);

}

Nutika auto programm

clip_image012See programm on mõeldud autole, millel esimesi rattaid keeratakse ühe mootoriga ja tagumised on ühendatud omavahel diferentsiaaliga ning neid juhitakse samuti ühe mootoriga.

Siin on kõik eelnevalt kirjeldatud programmi osad kokku pandud. Auto peal olev NXT on slave-rollis.

 

Näide. Nutika auto programm

//SLAVE. Auto

 

#include "BluetoothCheck.nxc"

 

long Rool;

long Kiirus;

long RattadKeskel;

 

//Bluetooth info vastuvõtu alamprogramm

task InfoVastuvott()

{

  while(TRUE)

  {

    //Rool on vahemikus -180..180

    //Kiirus on vahemikus -90..90

    ReceiveRemoteNumber (MAILBOX1, FALSE, Rool);

    ReceiveRemoteNumber (MAILBOX2, FALSE, Kiirus);

  }

}

 

//roboti edasisõitmise alamprogramm

task RobotSoida()

{

  while(TRUE)

  {

    //roboti edasisõitmine

    OnFwdReg(OUT_B, Kiirus, OUT_REGMODE_SPEED);

  }

}

 

//rataste keeramise alamprogramm

task KeeraRattaid()

{

  long Asend;

  while(TRUE)

  {

    //roolile liidetakse juurde eelnevalt saadud

    //rataste keskasukoht

    Asend=Rool+RattadKeskel;

    //pööravate rataste mootori juhtimine

    PosRegSetAngle(OUT_C, Asend);

  }

}

 

//alamprogramm RattadKeskele sätib

//rattad enne sõitma hakkamist otseks

task RattadKeskele()

{

  long parem, vasak;

 

  //rattad keeratakse paremale

  //lõpuni välja 1 sekundi jooksul

  OnFwd(OUT_C, 40);

  Wait(SEC_1);

  parem = MotorRotationCount(OUT_C);

 

  //rattad keeratakse vasakule

  //lõpuni välja 1 sekundi jooksul

  OnFwd(OUT_C, -40);

  Wait(SEC_1);

  vasak = MotorRotationCount(OUT_C);

  //mootorid lülitatakse välja

  Off(OUT_C);

  //arvutatakse saadud tulemuse vahe

  //ning jagatakse see kahega

  RattadKeskel = -((vasak-parem)/2);

 

  //võimaldatakse kasutada mootorit viisil,

  //et seda saab kraadi kaupa juhtida ja hoida

  PosRegEnable(OUT_C, 40, 10, 10);

 

  //rattad pööratakse keskasendisse

  PosRegSetAngle(OUT_C, RattadKeskel);

  Wait(SEC_1);

 

  //käivitatakse keeramise ja edasisõitmise alamprogrammid

  StartTask(KeeraRattaid);

  StartTask(RobotSoida);

}

task main ()

{

  //käivitatakse BT ühenduse kontroll

  //kui ühendamine ei õnnestunud, väljutakse programmist

  if(!BluetoothCheck()==NO_ERR)

    Stop(TRUE);    

 

  //käivitatakse rataste keskele keeramise alamprogramm

  StartTask(RattadKeskele);

  //käivitatakse Blutooth info vastuvõtu programm

  StartTask(InfoVastuvott);

 

  while (TRUE)

  {

    ClearScreen();

    TextOut(0, LCD_LINE4, FormatNum("Rool:   %d", Rool));

    TextOut(0, LCD_LINE3,

           FormatNum("Kiirus: %d", Kiirus));

    Wait(MS_200);

  }

}

Baasrobotist puldiauto programm

Baasrobotist puldiauto juhtimiseks saab kasutada sama pulti koos programmiga. Ainuke erinevus seisneb vastuvõtu programmis.

Näide. Baasrobotiga auto programm

//SLAVE

#include "BluetoothCheck.nxc"

long Rool, Kiirus;

long RattadKeskel;

 

//Bluetooth info vastuvõtu alamprogramm

task InfoVastuvott()

{

  while(TRUE)

  {

    //Rool on vahemikus -180..180

    //Kiirus on vahemikus -90..90

    ReceiveRemoteNumber (MAILBOX1, FALSE, Rool);

    ReceiveRemoteNumber (MAILBOX2, FALSE, Kiirus);

  }

}

 

//roboti edasisõitmise alamprogramm

task RobotSoida()

{

  int MotorB, MotorC;

 

  while(TRUE)

  {

    //puldist saadud kiiruse ja rooli info teisendamine

    MotorB = Kiirus + Kiirus*Rool/200;

    MotorC = Kiirus - Kiirus*Rool/200;

    //kiiruse muutmine viisil, et keeramine toimuks ainult

    //ratta aeglustamisega, st. võetakse minimaalne suurus

    MotorB = abs(MotorB) < abs(Kiirus) ? MotorB : Kiirus;

    MotorC = abs(MotorC) < abs(Kiirus) ? MotorC : Kiirus;

   

    //roboti edasisõitmine       

    OnFwdReg(OUT_B, MotorB, OUT_REGMODE_SPEED);

    OnFwdReg(OUT_C, MotorC, OUT_REGMODE_SPEED);

  }

}

task main ()

{

  //käivitatakse BT ühenduse kontroll

  //kui ühendamine ei õnnestunud, väljutakse programmist

  if(!BluetoothCheck()==NO_ERR)

    Stop(TRUE);

 

  //käivitatakse Blutooth info vastuvõtu programm

  StartTask(InfoVastuvott);

  //käivitatakse roboti sõitmise alamprogramm

  StartTask(RobotSoida);

 

  while (TRUE)

  {

    ClearScreen();

    TextOut(0, LCD_LINE4,

           FormatNum("Rool:   %d", Rool));  

    TextOut(0, LCD_LINE3,

           FormatNum("Kiirus: %d", Kiirus));

    Wait(MS_200);

  }

}

8. 9 klass 3 õppetundi Koopa kaardistaja robot

Tiigrihype_logoSee materjal on loodud Tiigrihüppe Sihtasutuse programmi ProgeTiiger raames.

Siit projektist omandad teadmise, kuidas kasutada kauguseandureid tundmatu piirkonna kaardistamiseks, kuidas edastada andmeid üle bluetooth ühenduse ja kuidas need inimese jaoks arusaadavaks teha.

 

Kooparobot kaardistab koopa ja selle kõrvalkäikude skeemi ning edastab info Bluetoothi vahendusel teisele, koopast väljas olevale robotile.

Käesoleva projekti raames on eeldatud, et koobas, mille robot läbib, on sirgjooneline ning kaardistada on tarvis kõrvalkäikude asukohad. Robot peab andmed saatma kaardistamise hetkel, et juhul, kui varisemisohtlik koobas peaks sisse langema, oleks osa infot juba edastatud.

Robot jõuab tunneli lõppu, millest annab märku puuteandur roboti ees. Kuna tunnel on sirgjooneline, pole vaja robotil ringi keerata, vaid ta võib sama teed mööda tagurpidi tagasi tulla.

 

 

clip_image002

Roboti ehitus

Robot peab suutma liikuda sirgjoonelises koopas vaid edasi-tagasi, robot ei pea keerama. See tähendab, et robot võib olla lihtne ja ühe mootoriga.

NB! Kui on olemas valmis baasrobot kahe mootoriga, siis otseliikumise tagamiseks võib mootorid omavahel jäiga pulgaga ühendada. See võte on lubatud ning ei riku kuidagi mootoreid, kuid sellisel juhul peab mootorid võrdselt tööle panema näiteks käsuga OnFwd(OUT_BC, 75).

Seega on koopa kaardistamise robot iseenesest lihtne ning põhineb tavalisel baasrobotil, millele on kinnitatud külgedele kauguseandurid, võimaldamaks tuvastada kaugust külgsuunas.

Roboti ette on tarvis kinnitada lüliti, mis rakendub siis, kui robot sõidab vastu seina. See teavitab robotit, et on jõutud koopa lõppu ning tuleb hakata tagasi sõitma.

Seda robotit nimetame edaspidi kooparobotiks või saatjaks. Bluetooth ühenduse osas on see robot masteri rollis.

Teine robot, mis infot vastu võtab, on kasutusel sisuliselt ekraanina, mis kuvab koopast väljasolevale inimesele käikude asukohad. Seda nimetame edaspidi puldirobotiks, juhtpuldiks või vastuvõtjaks. Bluetooth ühenduse osas on see robot slave´i rollis.

Kooparoboti programmeerimine

Programm on vaja kirjutada mõlema roboti jaoks, nii kooparobotile kui ka puldirobotile. Kooparobot sõidab koopas edasi ja tagasi ning edastab mootori ja kauguseandurite signaalid vastuvõtjale. Juhtpult annab kooparobotile käskluse liikumise alustamiseks ning teisendab vastuvõetud signaalid koopa jooniseks.

Alljärgnev diagramm iseloomustab kooparoboti käitumist. Esmalt käivitatakse Bluetooth ühenduse kontroll ja seejärel jääb robot ootama, kuni juhtpult oma valmisolekust märku annab. Kui inimene on juhtpuldil nuppu vajutanud, alustab kooparobot liikumist.

Sõitmise ajal kontrollitakse pidevalt roboti ees oleva lüliti asendit, mille alusel otsustatakse, kas robot on jõudnud koopa lõpuni. Samal ajal edasi liikudes on käivitatud paralleelselt alamprogramm andmete saatmiseks puldirobotile. Roboti koopast välja jõudmise kontroll baseerub mootori pöörete arvu lugemisel. Seega, kui tagasi sõites on mootori pöörded nulli jõudnud, on robot koopast välja jõudnud ja jääb seisma.

clip_image004

Bluetooth ühenduse loomine

Bluetooth ühenduse loomine on tehtud standardsel viisil, nagu seda on kirjeldatud käesoleva raamatu juhendis Bluetooth ühenduse loomise osas.

Stardi ootamine

Alamprogramm StardiOotamine tagab selle, et kooparobot ei hakka enne liikuma, kui puldirobot on talle alustamise kohta käskluse edastanud. Sellega sünkroniseeritakse robotite tegevus ja tagatakse korrektne kaardistamine.

Loodav kahendmuutuja ACK kasutatakse while-tsükli tingimusena. Kui see muutub tõeseks, väljub robot stardi ootamise while-tsüklist ning hakkab edasi sõitma ja koobast kaardistama.

Funktsioon SendRemoteBool(BTconn, MAILBOX6, TRUE) saadab puldirobotile tõeväärtusena TRUE (kolmas parameeter). Seejärel ootab robot 10 ms enne vastuvõtmise kontrollimist ReceiveRemoteBool(MAILBOX5, TRUE, ACK) ja kuvab ekraanile teksti „Ootan starti …“. Kui puldirobot on kätte saanud MAILBOX6 pealt teate TRUE, ootab puldirobot seni, kuni kasutaja vajutab nuppu ehk siis annab stardi. Pärast nupuvajutust saadab puldirobot tagasi MAILBOX5 kaudu väärtuse TRUE, mis omistatakse kooparoboti poolel muutujale ACK. Kui ACK on saanud väärtuse TRUE, väljutakse tsüklist ja alustatakse liikumist ning andurites info edastamist.

Näide. Kaardistaja roboti ootamise protseduur

//Stardi ootamine on vajalik selleks,

//et sünkroniseerida NXT-de tegevus,

//kuna nende programmid käivitatakse erinevatel aegadel.

void StardiOotamine()

{

  bool ACK=FALSE;

  while(!ACK)

  {

    //saadetakse SLAVE NXT-le info,

    //et "ma olen valmis" - TRUE

    SendRemoteBool(BTconn, MAILBOX6, TRUE);

    Wait(MS_10);

    //oodatakse SLAVE NXT pealt stardikäsklust,

    //siis ACK=TRUE

    ReceiveRemoteBool(MAILBOX5, TRUE, ACK);

    TextOut(0, LCD_LINE1, "Ootan starti ...");

    Wait(MS_10);

  }

}

Roboti liikumine koopas edasi ja tagasi

Kooparobot alustab liikumist, kui on saanud selleks vastava käsu puldiroboti käest. Robot sõidab otse edasi OnFwd(OUT_BC, 30) kuni koopa lõpuni, ehk siis hetkeni, kui puuteandur on vajutatud while(!KoopaFinish).

Pärast puuteanduri vajutust käivitub järgmine while-tsükkel, mille tingimuseks on seatud mootori pöörete lugemise nulli jõudmine while(MotorRotationCount(OUT_B)>0).

Seda moodust kasutame selleks, et teada saada, millal robot on tagasi koopast välja jõudnud.

Sõidu alguses on mootori pöörete arv null ja edasi sõites see suureneb. Tagasi sõites aga mootori pöörete arv väheneb.

Näide. Koopas liikumise alamprogramm

//robot liigub koopa lõpuni, kuni puuteandur on vajutatud

//seejärel liigub tagasi, kuni mootori pöörded saavad taas nulli

task move()

{

  //antud tsükkel käib seni, kuni pole vajutatud nuppu

  while(!KoopaFinish)

  {

    //robot sõidab edasi

    OnFwd(OUT_BC, 30);

    //muutujale KoopaFinish omistatakse puuteanduri väärtus

    KoopaFinish = Sensor(S1);    

  }

  //pärast puuteanduri vajutust käivitub järgmine tsükkel,

  //mis käib, kuni mootori pöörete arv on jõudnud 0 või alla selle

  while(MotorRotationCount(OUT_B)>0)

    OnRev(OUT_BC, 100);

  //robot jääb programmi lõppedes seisma

  Off(OUT_BC);

}

Info edastamine Bluetoothi vahendusel

Paralleelselt roboti liikuma hakkamisega käivitatakse alamprogramm saadaInfo, mis edastab infot mootori pöörete, kauguseandurite ja koopa lõppu jõudmise kohta.

Alamprogrammi alguses käivitub lõpmatu while-tsükkel, mille sees toimub info saatmine.  Esmalt loetakse anduritest info ja omistatakse nende väärtused muutujatele. Pärast seda kuvatakse antud info kooparoboti ekraanil. Kuigi seda infot koopas keegi ei näe, on seda hea kasutada roboti testimisel.

Seejärel saadetakse iga info omaette postkasti kaudu teisele NXT-le, kusjuures iga saatmise vahel on 1 ms pikkune paus, mis on piisav, et Bluetooth suudaks neid käskusid iseseisvalt täita.

Näide. Andmete saatmise programm

//alamprogramm saadaInfo saadab ligikaudu

//100 korda/sekundis

//anduritest saabuvat infot vastuvõtjale

task saadaInfo()

{

  while(TRUE)

  {

    //loeme andurist andmed, mootori pöörded

    long MootoriPoorded = MotorRotationCount(OUT_B);

    //loeme anduritest andmed, parem-vasak kauguseandur

    int sensorP = SensorUS(S3);

    int sensorV = SensorUS(S4);

   

    //kuvame ekraanil info andurite olukorra kohta

    TextOut(0, LCD_LINE1,

          FormatNum("Mootor: %5d", MootoriPoorded));

    TextOut(0, LCD_LINE2,

          FormatNum("Parem: %5d", sensorP));

    TextOut(0, LCD_LINE3,

          FormatNum("Vasak: %5d", sensorV));

   

    //saadame mootori pöörete väärtuse

    SendRemoteNumber(BTconn, MAILBOX1, MootoriPoorded);

    Wait(1);

    //saadame parempoolse kauguseanduri info

    SendRemoteNumber(BTconn, MAILBOX2, sensorP);

    Wait(1);

    //saadame vasakpoolse kauguseanduri info

    SendRemoteNumber(BTconn, MAILBOX3, sensorV);

    Wait(1);

    //saadame koopa lõpuinfo,

    //pärast seda enam ei kaardistata

    SendRemoteBool(BTconn, MAILBOX4, KoopaFinish);

    Wait(1);

  }

}

Puldiroboti programmeerimine

Alljärgnev diagramm iseloomustab roboti vastuvõtja-poolset tegevust.

Puldirobot ootab kooparoboti käest teavitust selle kohta, et viimane on valmis liikuma. Kui teavitus käes, kuvatakse kasutajale kiri „Anna start“ ning pärast nupuvajutust saadetakse koopa­robotile liikumise alustamise korraldus. Pärast seda alustab puldirobot info vastuvõtmist ja kaardistamast. Kui kooparobot on lõppu jõudnud, lõpetab puldirobot kaardistamise.

clip_image006

Start käskluse andmine

Alamprogramm StardiAndmine ootab kooparobotilt infot, et see on valmis, ning seejärel saadab omakorda vastuse liikumise alustamiseks.

Tsükkel koos tingimusega while(!ACK) käib seni, kuni puldirobot on saanud kooparoboti käest signaali ReceiveRemoteBool(MAILBOX6, TRUE, ACK), et kooparobot on valmis. Kui muutuja ACK väärtus muutub tõeseks, väljutakse tsüklist.

Seejärel kuvatakse ekraanile kiri „Anna start! Robot on valmis.“ Siis oodatakse kasutajapoolset nupuvajutust ning pärast seda saadetakse kooparobotile stardikäsklus SendResponseBool(MAILBOX5, TRUE), mille tulemusena hakkab kooparobot liikuma ja puldirobot kaardistama.

Näide. Puldirobotilt stardi saatmine

//stardiandmine on vajalik kahe

//NXT tegevuse sünkroniseerimiseks

void StardiAndmine()

{

  bool ACK=FALSE;

  //käivitub tsükkel, millest väljutakse pärast seda, kui

  //slave robot on saanud master-i käest info valmis

  while(!ACK)

  {

    //SLAVE ootab masteri käest infot

    ReceiveRemoteBool(MAILBOX6, TRUE, ACK);

    TextOut(0, LCD_LINE1, "Ootan roboti");

    TextOut(0, LCD_LINE2, "valmisolekut");

    Wait(MS_10);

  }

 

  TextOut(0, LCD_LINE1, "Anna start !");

  TextOut(0, LCD_LINE2, "Robot on valmis.");

 

  //oodatakse kuni kasutaja vajutab nuppu

  while(!ButtonPressed(BTNCENTER, FALSE));

  while(ButtonPressed(BTNCENTER, FALSE));

 

  //SLAVE saadab MASTER NXT-le stardikäskluse

  SendResponseBool(MAILBOX5, TRUE);

}

Bluetooth andmete vastuvõtmine

Pärast stardikäskluse saatmist käivituvad paralleelselt kaks alamprogrammi: Bluetooth ühenduse kaudu andmete vastuvõtmine ja nende andmete põhjal kaardistamine.

Alamprogrammi receiveBTsignal alguses käivitub lõpmatu tsükkel, mille sees olevad neli funktsiooni võtavad vastu Bluetoothi erinevatest postkastidest infot ja salvestavad need muutujatesse.

Näide. Puldirobotil Bluetooth andmete vastuvõtt

//võetakse vastu Bluetooth ühenduse kaudu infot

task receiveBTsignal()

{

  while(TRUE)

  {

    ReceiveRemoteNumber (MAILBOX1, FALSE, MootoriPoorded);

    ReceiveRemoteNumber (MAILBOX2, FALSE, sensorUSParem);

    ReceiveRemoteNumber (MAILBOX3, FALSE, sensorUSVasak);

    ReceiveRemoteBool (MAILBOX4, FALSE, KoopaFinish);

  }

}

Teepikkusega arvestamine

Koopa kaardistamisel peab arvestama asjaoluga, et NXT ekraani kõrgus on 64 punkti. Lisaks on hea ette teada kaardistatava koopa ligikaudset pikkust, sest siis saab selle taandada ekraani pikkuseks.

Kui näiteks koopa pikkus on 64 cm, saaks iga koopas läbitud cm peale kaardistada ühe punkti ekraanil. Koopa pikkus tegelikkuses võib aga olla suvaline number. Ülesande lahendamiseks on vaja teada, mitu sentimeetrit või meetrit koobast vastab ühele ekraanipunktile. See on vaja taandada ühikuteks, mitu kraadi kooparoboti mootori pöördest vastab puldiroboti ühele ekraanipunktile.

Käesoleva ülesande juures on teada kooparoboti ratta diameeter ja see tuleb teisendada ümbermõõduks.

clip_image008

Kuna roboti programm omab infot ainult selle kohta, mitu pööret mootor on teinud, siis tuleb teisendada teadaolev teepikkus ratta pööreteks.

clip_image010

Rattakraadid on suurus, mis väljendab läbitavat teepikkust kraadides valitud rataste korral. Seega, kui tegemist on näiteks ratastega, mille ümbermõõt on 10 cm, siis 1 m pikkuse koopa läbimiseks teevad rattad 10 tiiru ehk kraadides 3600 kraadi.

Kaardistamise sagedus

Eelnevast on teada number, mitu kraadi rattad pööravad kogu teepikkuse jooksul, ja ekraani kõrgus pikslites, seega saab välja arvutada kaardistamise sageduse.  Teisisõnu tähendab kaardistamise sagedus seda, mitme rattapöörde kraadi järel on tarvis kauguseanduritelt lugem võtta ja kaardile kanda.

clip_image012

Alljärgnevalt on näide selle kohta, kuidas lahendada kaardistamise sageduse ülesanne. Selles näites on eeldatud, et mootoripöörded saabuvad Bluetooth vastuvõtu kaudu muutujasse MootoriPoorded.

Muutujale perimeter omistatakse väärtus ratta ümbermõõt. Muutujale turns omistatakse väärtus koopa pikkus mootori pöörded kraadides. Muutujale MAPFrequent omistatakse kaardistamise sageduse väärtus.

Käivitub while-tsükkel tingimusega lõpetada siis, kui jõutakse koopa lõppu while(!KoopaFinish). Pärast seda omistatakse muutujale MotorB väärtus mootori pöörded kraadides MotorB = MootoriPoorded. Tingimusega MotorB > LastMotorB + MAPfrequent kontrollitakse kaardistamise vajadus. Kaardistamise hetkel salvestatakse MotorB väärtus muutujasse LastMotorB. Pärast seda jääb programm taas ootama seda, millal on MotorB väärtus kasvanud suuremaks kui eelmine salvestatud väärtus LastMotorB pluss kaardistamise sagedus.

NB! Järgnev programmikood on fragment suuremast programmist ning iseseisvalt see ei tööta.

Näide. Kaardistamise sageduse seadmine

const int Xekraan = 100;    //ekraani x-telje suurus

const int Yekraan = 64;     //ekraani y-telje suurus

const int teepikkus = 2000; //eeldatav teepikkus mm

const int wheel = 56;       //roboti ratta diameeter mm

 

task kaardista()

{

  //ratta ümbermõõt

  float perimeter = wheel * PI;

  //teepikkuse teisendamine ratta pööramise kraadideks

  float turns = teepikkus / perimeter * 360;

  //ratta pööramise kraadide teisendamine

  //kaardistuse sageduseks

  int MAPfrequent = turns / Yekraan;

 

  long LastMotorB;

  long MotorB;

 

  while (!KoopaFinish)

  {

    MotorB = MootoriPoorded;           

   

    //kaardistamine toimub iga ühiku MAPfrequent järel

    if(MotorB > LastMotorB + MAPfrequent)

    {

      LastMotorB = MotorB;

      PlayTone(TONE_A7, 1);

      //siit edasi on kaardistamise programmikood

    }

  }

}

Kauguseanduri teisendamine ekraanile arusaadavaks

Koopa kaardistamisel võtame eelduseks, et ekraani keskkohale vastab koopa telgjoon. Seega ekraani keskkohast paremale poole peab ilmuma parempoolse kauguseanduri näit (ja parempoolsed käigud) ning vasakule vasakpoolse anduri näit (ja vasakpoolsed käigud). Koopa kaardistamisel eristame ainult kahte pikkust – kas on tegemist koopa seinaga (nö. nullpikkus) või on tegemist käiguga (maksimaalne pikkus).

Kauguseandurist saabub signaal vahemikus 10…250, mis väljendab objekti kaugust andurist sentimeetrites.

Ekraani kogulaius on 100 pikslit, pool laiust on 50. Seega andurist saabuv signaal vahemikus 10..250 tuleb teisendada poole ekraani laiuseks, ehk siis 0..50.

Kõige lihtsam koopa kaardistamise algoritm oleks selline, kui kauguseandurist saadud signaal jagatakse viiega ning tulemus kuvatakse LineOut funktsiooniga ekraanile. Viiega jagamise mõttekus seisneb selles, et siis on teisendatud kauguseanduri maksimaalne näit ekraani maksimaalseks näiduks. Kirjeldatud moodus arvestab sujuva kauguse muutumisega ja siinkohal on ära toodud sellise kaardistaja programmi osa.

Lõplikus programmis kasutame kaardistamiseks täiustatud varianti, kus on elimineeritud kauguseandurist saabuvad vigased signaalid ehk niinimetatud signaali müra.

NB! Järgnev programmikood on fragment suuremast programmist ning iseseisvalt see ei tööta.

Näide. Kauguseanduri teisendamine ekraanile

while (TRUE)

{

  MotorB = MotorRotationCount(OUT_B);         

 

  //kaardistamine toimub iga ühiku MAPfrequent järel

  if(MotorB > LastMotorB + MAPfrequent)

  {

    LastMotorB = MotorB;

    PlayTone(TONE_A7, 1);

   

    //loetakse mõlemast kauguseandurist parameetrid

    sensorP = SensorUS(S3);

    sensorV = SensorUS(S4);

   

    //kauguseanduri näit jagatakse viiega, tulemuseks on näit

    //vahemikus 2...50, mis sobib suurepäraselt ekraani jaoks

    sensorP /= 5;

    sensorV /= 5;

   

    //kauguseanduritest saadud numbrid paigutatakse kas

    //ühele või teisele poole ekraani keskkoha suhtes

    xV=ekraanCenter - sensorV;

    xP=ekraanCenter + sensorP;

   

    //y on kasutusel y-telje arvestuse juures, ehk käigu kaugus

    y++;

   

    //ekraanil kuvatakse käikude asukohad

    LineOut(50, y, xP, y);

    LineOut(50, y, xV, y);

  }

}

Käesoleva projekti juures tasub eeldada, et kõik kõrvalkäigud on igal juhul pikad ning meil pole tarvis arvestada kauguseanduri vahepealsete näitudega. Kaardistamise tulemus peab olema seega piltlikult öeldes 1 või 0 (kas on kõrvalkäik või ei ole). Ülesande lahendamisel tuleb arvestada, et kauguseandur tekitab vigu ning iga väärtus ei väljenda veel kõrvalkäigu asukohta. Seega on vaja leida moodus vigade taandamiseks.

Vigade taandamist saab teha keskmise arvutamisega. Selle tulemusena peab arvestama, et kaardistamisel tekib teatav hilistus. Viide tuleneb sellest, et kaardile kuvatav näit muutub alles pärast näitude keskmistamist. Kui viimase kolme võetud näidu keskmine väljendab kõrvalkäigu asukohta, siis alles seejärel kuvatakse ekraanile joon kõrvalkäigu alguse kohta.

Alljärgnev lahendus arvestab sellega, et koopa kõrvalkäigud on lõpmatu pikkusega ja andurid on ebatäpsed ning tekitavad müra.

Müra elimineerimiseks luuakse kaks massiivi koopaErrorP[] ja koopaErrorV[], kuhu salvestatakse järjest anduritest saabuvad näidud. Need massiivid luuakse pikkusega kolm mälupesa ArrayInit(koopaErrorP, 0, idx) kusjuures konstant idx määrab massiivi pikkuse.

Andurist saadud info korrigeeritakse sellisel viisil, et kõik näidud, mis on üle 50 cm, nende asemel arvestatakse 50 ja kõik alla selle arvestatakse väärtusega null xV = sensorV > 50 ? 50 : 0. Toodud programmifragment on näide ternary tingimuse kasutamisest.

Järgmisena on programmikoodis muutuja i, mille väärtust suurendatakse iga tsükli käigus ühe ühiku võrra. Seejärel kontrollitakse, kas i on sama suur kui massiivi maksimaalne mälupesade arv i = i == idx ? 0 : i. Kui i on võrdne mälupesade arvuga, siis omistatakse i-le väärtus 0. Selle võtte tulemusena muutub i nullist kuni massiivi mälupesade arvuni idx, käesolevas näites 0,1,2,0,1,2 jne.

Vastavalt i väärtusele kirjutatakse massiivi väärtused järjest üle koopaErrorV[i]=xV. Seejärel arvutatakse antud massiivi keskmine xV=ArrayMean(koopaErrorV, NA, NA). Saadud tulemus on niisiis viimase kolme anduri näidu keskmine, kusjuures andurist tulevad näidud on 0 või 50. Seega keskmised saavad olla 0 {0,0,0}, 16 {0,0,50}, 33 {0,50,50}, 50 {50,50,50}.

Järgmise tegevusena kontrollitakse saadud keskmist näitu, kui see on väiksem kui 35, siis on x-telje väärtuseks 3, kui üle 35, siis on x-telje väärtuseks 50 xV=xV < 35 ? 3 : 50.

Saadud tulemus paigutatakse seejärel siis vastavalt andurile kas paremale või vasakule poole ekraanil.

Muutuja y tähistab y-telje koordinaati ning see võib olla maksimaalselt 64. Iga tsükli käigus liidetakse muutujale y üks juurde y++ ning seejärel võrreldakse selle suurust ekraani kõrgusega. Kui y on suurem kui ekraani kõrgus siis omistatakse y suuruseks taas 1 ning alustatakse kaardistamist uuesti ekraani altservast.

Näide. Kauguseandurist tuleva müra tasandamine

task kaardista()

{

  //ratta ümbermõõt

  float perimeter = wheel * PI;

  //teepikkuse teisendamine ratta pööramise kraadideks

  float turns = teepikkus / perimeeter * 360;

  //ratta pööramise kraadide teisendamine kaardistuse sageduseks

  int MAPfrequent = turns / Yekraan;

  //ekraani keskkoht

  int ekraanCenter = Xekraan /2;

 

  const char idx=3; //massiivi algväärtustamise konstant

  long LastMotorB;

  long MotorB;

  int xV, xP, y;

  int sensorP, sensorV;

  int i;

 

  //massiivide loomine, kuhu salvestatakse anduri näidud

  //millest võetakse keskmine, et välistada vigu

  int koopaErrorP[];

  int koopaErrorV[];

 

  //massiivide initsialiseerimine ja massiivi suuruse määramine,

  //mõistlik massiivi suurus võiks olla vahemikus 2..5

  ArrayInit(koopaErrorP, 0, idx);

  ArrayInit(koopaErrorV, 0, idx);

 

  while (!KoopaFinish)

  {

    MotorB = MootoriPoorded;           

   

    //kaardistamine toimub iga ühiku MAPfrequent järel

    if(MotorB > LastMotorB + MAPfrequent)

    {

      LastMotorB = MotorB;

      PlayTone(TONE_A7, 1);

     

      //y on kasutusel y-telje punkti arvestuse juures

      y++;

      if (y>Yekraan)

      {

        y=1;

        ClearScreen();

      }

     

      sensorP = sensorUSParem;

      sensorV = sensorUSVasak;

     

      //salvestame massiivi väärtused, mis jäävad

      //ekraani mõõtude piiridesse, ehk siis 0 või 50

      //kõik üle 50 väärtused arvestatakse kaardistamise suhtes

      //lõpmatuks kauguseks ehk võrdsustatakse 50-ga

      xV = sensorV > 50 ? 50 : 0;

      xP = sensorP > 50 ? 50 : 0;

     

      //i kasutatakse massiivi mälukohtade indeksina

      //idx on massiivi suurus,

      //seega massiiv täidetakse ringiratast

      i++;

      i = i == idx ? 0 : i;

     

      //salvestame x-telje numbrid massiivi

      koopaErrorV[i] = xV;

      koopaErrorP[i] = xP;

     

      //arvutame massiivi x-telje numbrite keskmise

      xV = ArrayMean(koopaErrorV, NA, NA);

      xP = ArrayMean(koopaErrorP, NA, NA);

     

      //korrigeerime ekraanil kuvatavat käikude süsteemi

      //viisil, et kuvatakse ainult väärtused koopa sein

      //(ekraani keskkoht, väärtus 3) või käik (ekraani serv)

      //ekraani keskel on lõpuks 6 piksli laiune joon, st. koobas

      xV = xV < 35 ? 3 : 50;

      xP = xP < 35 ? 3 : 50;

     

      //US anduritest saadud numbrid paigutatakse kas

      //ühele või teisele poole ekraani keskkoha suhtes

      xV=ekraanCenter - xV;

      xP=ekraanCenter + xP;

     

      //ekraanil kuvatakse käikude asukohad

      LineOut(50, y, xP, y);     

      LineOut(50, y, xV, y);     

    }

  }

}

Lõplik programm

Kooparobot

NB! Selle programmi jaoks on vajalik Bluetooth ühenduse loomise fail „BluetoothCheck.NXC“, mis on kirjeldatud käesoleva raamatu juhendis Bluetoothi alapeatükis.

Näide. Kooparoboti programm

//MASTER. Koopakaardistaja programm

#include "BluetoothCheck.nxc"

 

bool KoopaFinish = FALSE; //koopa lõpu määramiseks

 

const byte BTconn=1;

const string SlaveNXTName = "NXT2";

 

//alamprogramm saadaInfo saadab anduritest saabuva info vastuvõtjale

task saadaInfo()

{

  while(TRUE)

  {

    //loeme andurist andmed, mootori pöörded

    long MootoriPoorded = MotorRotationCount(OUT_B);

    //loeme anduritest andmed, parem-vasak kauguseandur

    int sensorP = SensorUS(S3);

    int sensorV = SensorUS(S4);

   

    //kuvame ekraanil info andurite olukorra kohta

    TextOut(0, LCD_LINE1,

        FormatNum("Mootor: %5d", MootoriPoorded));

    TextOut(0, LCD_LINE2,

        FormatNum("Parem: %5d", sensorP));

    TextOut(0, LCD_LINE3,

        FormatNum("Vasak: %5d", sensorV));

   

    //saadame mootori pöörete väärtuse

    SendRemoteNumber(BTconn, MAILBOX1, MootoriPoorded);

    Wait(10);

    //saadame parempoolse kauguseanduri info

    SendRemoteNumber(BTconn, MAILBOX2, sensorP);

    Wait(10);

    //saadame vasakpoolse kauguseanduri info

    SendRemoteNumber(BTconn, MAILBOX3, sensorV);

    Wait(10);

    //saadame koopa lõpuinfo,

    //pärast seda enam ei kaardistata

    SendRemoteBool(BTconn, MAILBOX4, KoopaFinish);

    Wait(10);

  }

}

//robot liigub koopa lõpuni, kuni puuteandur on vajutatud

//seejärel liigub tagasi,

//kuni mootori pöörded saavad taas nulli

task move()

{

  //antud tsükkel käib seni, kuni pole vajutatud nuppu

  while(!KoopaFinish)

  {

    //robot sõidab edasi

    OnFwd(OUT_BC, 30);

    //muutujale KoopaFinish omistatakse

    //puuteanduri väärtus

    KoopaFinish = Sensor(S1);    

  }

  //pärast puuteanduri vajutust käivitub järgmine tsükkel

  //mis käib seni, kuni mootori

  //pöörete arv on jõudnud 0 või alla selle

  while(MotorRotationCount(OUT_B)>0)

    OnRev(OUT_BC, 100);

 

  //robot jääb programmi lõppedes seisma

  Off(OUT_BC);

}

 

//Stardi ootamine on vajalik selleks,

//et sünkroniseerida NXT-de tegevus, kuna nende

//programmid võidakse käivitada erinevatel aegadel.

void StardiOotamine()

{

  bool ACK=FALSE;

  while(!ACK)

  {

    //saadetakse slave-le info, et "olen valmis" - TRUE

    SendRemoteBool(BTconn, MAILBOX6, TRUE);

    Wait(MS_10);

    //oodatakse slave NXT pealt stardikäsklust ACK=TRUE

    ReceiveRemoteBool(MAILBOX5, TRUE, ACK);

    TextOut(0, LCD_LINE1, "Ootan starti ...");

    Wait(MS_10);

  }

}

 

 

task main()

{

  //käivitatakse BT ühenduse kontroll ja/või loomine

  //kui ühendamine ei õnnestunud, väljutakse programmist

  if(!BluetoothConnect(BTconn, SlaveNXTName)==NO_ERR)

  Stop(TRUE);

 

  //kuvame kasutajale ühe sekundi jooksul teate

  Wait(SEC_1);

  ClearScreen();

 

  SetSensorTouch(S1);

  SetSensorLowspeed(S3);

  SetSensorLowspeed(S4);

  ResetRotationCount(OUT_BC);

 

  StardiOotamine();

  ClearScreen();

  //alamprogramm move paneb roboti edasi liikuma

  StartTask(move);

  StartTask(saadaInfo);

}

Puldirobot

NB! Selle programmi jaoks on vajalik Bluetooth ühenduse loomise fail „BluetoothCheck.NXC“, mis on kirjeldatud käesoleva raamatu juhendi osas Bluetoothi alapeatükis.

Näide. Puldiroboti programm

//Puldiroboti programm

 

#include "BluetoothCheck.nxc"

 

const int Xekraan = 100;    //ekraani x-telje suurus

const int Yekraan = 64;     //ekraani y-telje suurus

const int teepikkus = 2000; //eeldatav teepikkus mm

const int wheel = 56;       //roboti ratta diameeter

 

long MootoriPoorded;

int sensorUSParem;

int sensorUSVasak;

bool KoopaFinish;

 

task kaardista()

{

  //ratta ümbermõõt

  float perimeter = wheel * PI;

  //teepikkuse teisendamine ratta pööramise kraadideks

  float turns = teepikkus / perimeter * 360;

  //ratta pööramise kraadide teisendamine

  //kaardistuse sageduseks

  int MAPfrequent = turns / Yekraan;

  //ekraani keskkoht

  int ekraanCenter = Xekraan /2;

  

  const char idx=3; //massiivi algväärtustamise konstant

  long LastMotorB;

  long MotorB;

  int xV, xP, y;

  int sensorP, sensorV;

  int i;

  //massiivide loomine, kuhu salvestatakse anduri näidud

  //millest võetakse keskmine, et välistada vigu

  int koopaErrorP[];

  int koopaErrorV[];

 

  //massiivide init ja massiivi suuruse määramine

  ArrayInit(koopaErrorP, 0, idx);

  ArrayInit(koopaErrorV, 0, idx);

 

  while (!KoopaFinish)

  {

    MotorB = MootoriPoorded;           

   

    //kaardistamine toimub iga ühiku MAPfrequent järel

    if(MotorB > LastMotorB + MAPfrequent)

    {

      LastMotorB = MotorB;

      PlayTone(TONE_A7, 1);

     

      //y on kasutusel y-telje punkti arvestuse juures

      y++;

      if (y>Yekraan)

      {

        y=1;

        ClearScreen();

      }

     

      sensorP = sensorUSParem;

      sensorV = sensorUSVasak;

     

      //salvestame massiivi väärtused, mis jäävad

      //ekraani mõõtude piiridesse, ehk siis 0 või 50

      //kõik üle 50 väärtused arvestatakse lõpmatuseks

      xV = sensorV > 50 ? 50 : 0;

      xP = sensorP > 50 ? 50 : 0;

     

      //i kasutatakse massiivi mälukohtade indeksina

      //idx on massiivi pikkus,

      //massiiv täidetakse ringiratast

      i++;

      i = i == idx ? 0 : i;

     

      //salvestame x-telje numbrid massiivi

      koopaErrorV[i] = xV;

      koopaErrorP[i] = xP;

     

      //arvutame massiivi x-telje numbrite keskmise

      xV = ArrayMean(koopaErrorV, NA, NA);

      xP = ArrayMean(koopaErrorP, NA, NA);

     

      //korrigeerime ekraanil kuvatavat käikude

      //süsteemi viisil, et kuvatakse ainult väärtused

      //koopa sein (ekraani keskkoht) või

      //käik (ekraani serv) ekraani keskkoha väljendab

      //arv 3, sellega tekitatakse ekraani keskele

      //6 punkti laiune joon, väljendades koobast

      xV = xV < 35 ? 3 : 50;

      xP = xP < 35 ? 3 : 50;

     

      //US anduritest saadud numbrid paigutatakse kas

      //ühele või teisele poole ekraani keskkoha suhtes

      xV=ekraanCenter - xV;

      xP=ekraanCenter + xP;

     

      //ekraanil kuvatakse käikude asukohad

      LineOut(50, y, xP, y);     

      LineOut(50, y, xV, y);     

    }

  }

}

 

//võetakse vastu Bluetooth ühenduse kaudu infot

task receiveBTsignal()

{

  while(TRUE)

  {

    ReceiveRemoteNumber (MAILBOX1, FALSE, MootoriPoorded);

    ReceiveRemoteNumber (MAILBOX2, FALSE, sensorUSParem);

    ReceiveRemoteNumber (MAILBOX3, FALSE, sensorUSVasak);

    ReceiveRemoteBool (MAILBOX4, FALSE, KoopaFinish);

  }

}

 

//stardiandmine on vajalik kahe NXT

//omavahelise tegevuse sünkroniseerimiseks

void StardiAndmine()

{

  bool ACK=FALSE;

  //käivitub tsükkel, millest väljutakse pärast seda,

  //kui slave robot on saanud master-i käest info,

  //et see on valmis

  while(!ACK)

  {

    //SLAVE ootab masteri käest infot

    ReceiveRemoteBool(MAILBOX6, TRUE, ACK);

    TextOut(0, LCD_LINE1, "Ootan roboti");

    TextOut(0, LCD_LINE2, "valmisolekut");

    Wait(MS_10);

  }

 

  TextOut(0, LCD_LINE1, "Anna start !");

  TextOut(0, LCD_LINE2, "Robot on valmis.");

 

  //oodatakse, kuni kasutaja vajutab nuppu

  while(!ButtonPressed(BTNCENTER, FALSE));

  while(ButtonPressed(BTNCENTER, FALSE));

  //SLAVE saadab MASTER NXT-le stardikäskluse

  SendResponseBool(MAILBOX5, TRUE);

}

 

task main()

{

  //käivitatakse BT ühenduse kontroll

  //kui ühendamine ei õnnestunud, väljutakse programmist

  if(!BluetoothCheck()==NO_ERR)

    Stop(TRUE);    

 

  StardiAndmine();

  ClearScreen();

 

  StartTask(receiveBTsignal);

  StartTask(kaardista);

}

 

4. 9 klass 2-3 õppetundi Sumorobotid

Tiigrihype_logoSee materjal on loodud Tiigrihüppe Sihtasutuse programmi ProgeTiiger raames.

Tutvume sumoroboti ehitamise ja programmeerimise strateegiatega. Projekti lõpuks saame teada, kuidas ehitada head sumorobotit.

Sumoroboti strateegiad võib jaotada kaheks, ehitus ja programmeerimine

1. Tund. Ehitus-strateegiate tutvustamine, robotite ehitamine

a.        Ehitamise strateegiad

b.       Erinevate sumorobotite ehitamine

2-3 Tund. Programmeerimise strateegiate tutvustamine.

c.        Programmeerimine, testimine

d.       Sumovõistluse läbiviimine

LEGO sumo reeglid

LEGO sumorobotite võistlusel on kindlad reeglid ja standardid. Kui on plaanis võistlustel osaleda, tasub robot ning võistluslaud ehitada kooskõlas alljärgnevate reeglitega.

Sumo laud

Sumo laua läbimõõt on 77 cm, kõrgus maapinnast 2,5 cm. Laua servas oleva valge joone laius on 2,5 cm. Ehituseks sobib 10 mm sile vineer, millele on vajaliku kõrguse saavutamiseks jalad alla pandud.

Sumo robot

Roboti maksimaalsed mõõdud on 15x15 cm, kõrgus ei ole piiratud. Kaalupiirang on 1 kg. Robot võib pärast starti oma mõõtmetelt laieneda.

Start

Pärast programmi käivitamist peavad robotid ootama 5 sekundit enne reaalset liikuma hakkamist.

Shikiri rist

Enne iga sumo matši algust viskab kohtuniklauale shikiri risti, mis jagab sumo laua neljaks veerandiks. Robotid stardivad sumoväljaku vastasveeranditest, stardisuund teineteise suhtes väljapoole suunatud. Täpsemalt vaata selle kohta joonist projekti viimase alalõigu, stardistrateegia juurest. clip_image002

Sumoroboti ehitamise strateegiad

Sumorobotite ehituses on hulk tegureid, mis mõjutavad roboti käitumist. Näiteks veosüsteem ja kaal mõjutavad seda, kui hästi saavutab robot kontakti maapinnaga, andurid mõjutavad vastase leidmist ning platsil püsimist, tõukemehhanism on seotud vastase ründamisega ja kaitsesüsteem sellega, kuidas robot peab vastu erinevatele rünnakutele.

Veosüsteemi valik

Sumoroboti elutähtis komponent on veosüsteem. See määrab füüsiliselt roboti püsimise platsil ja tõukejõu tugevuse. Alljärgnevalt on antud vastused erinevatele veosüsteeme puudutavatele küsimustele, mis tekivad igal sumoroboti ehitajal. Pildid aitavad mõista ühe või teise veosüsteemi omadusi.


 

Rattad või roomikud?

Veosüsteem võib olla nii rataste kui ka roomikutega.

 

Rattad

Roomikud

Manööverdamine

Robot reageerib kiiresti pööramisele.

Robot pöörab suhteliselt aeglaselt.

Liikumiskiirus

Võrreldes roomikutega on võimalik ehitada üsna kiire robot.

Roboti liikumiskiirus on tagasihoidlik, kuna roomikud on praktiliselt vastu velge, väikese diameetriga.

Roomikutele võib ehitada hammasratta ülekande kiiremaks liikumiseks, kuid sellisel juhul tuleb selle töökindlus hoolega läbi mõelda, kuna sellele rakenduvad suured jõud vastase ründamisel.

Püsimine platsil

Sõltub rattakummi mustrist, võib olla väga halvast kuni väga heani.

Kui roomikud on 100% tasapinnaga kontaktis, on platsil püsimine väga hea. Kui roomikud servapidi õhku kerkivad, muutub roboti vastupanuvõime hetkega nõrgaks ning hakkab libisema.

Tõukejõud

Sarnaselt püsimisele platsil, sõltuvad roboti tõukejõu omadused rattakummist ja ehitusest.

Roomikute tõukejõud on väga hea, kui robot saab neile püsivalt toetuda ja ei tõuse õhku.

Näide

Võrdle näiteks tanki mõne samaväärse maastikuautoga. Tank liigub ja manööverdab võrreldes autoga palju aeglasemalt ja kulutab sealjuures märkimisväärselt rohkem energiat kui auto.

Kummid mustriga või siledad?

 

Siledad kummid

Mustriga kummid

Roboti platsil püsimine ja tõukejõud

Sile kumm omab maksimaalselt kontakti sileda maapinnaga, tagades sellega parema haardumise. See tagab nii roboti parema platsil püsimise kui ka suurema tõukejõu võrreldes mustriga kummiga.

Mustriga kummid omavad maapinnaga kontakti vaid mustri väljaulatuvate punktide juurest. Seega mustrilise kummi haardumine siledal pinnal on alati halvem kui siledal kummil.

 

Näide

Mõtle vormelitele, mis sõidavad täiesti siledate kummidega.

Seejärel mõtle offroad maastikuautodele, mis sõidavad võimalikult mustrilise kummiga.

Rataste läbimõõt suur või väike?

 

Suur läbimõõt

Väike läbimõõt

Roboti kiirus

Suurte ratastega robot liigub kiiremini.

Väikeste ratastega robot liigub aeglasemalt, kuid siiski kiiremini kui roomikutega robot.

Roboti tõukejõud

Roboti tõukejõud on mõjutatud vähesel määral, olles suurtel ratastel suurem kui väikestel.

Roboti tõukejõud on väikestel ratastel pisut nõrgem võrreldes suurtega.

Näide

Võrdle autosid, väikeautode rataste läbimõõt on väike ja suuremate ning kiiremate autode rattad suurema läbimõõduga.

 

 

 

clip_image004

 

 

 

 

clip_image006

Vedavad rattad ees või taga?

 

Vedavad rattad ees

Vedavad rattad taga

Tõukejõud

Roboti tõukejõud on hea. Sõltuvalt roboti enda raskuskeskmest on oht, et roboti tagumine osa tõuseb tõukamisel õhku ning sellega võib kaasneda tasakaalukaotus.

Roboti tõukejõud on hea. Sõltumata roboti raskuskeskmest ei saa roboti tagumine osa õhku tõusta. Õhku võib kerkida roboti keskosa, kui näiteks liigendiga sahk on teise roboti all kinni.

Püsimine platsil

Platsil püsimise seisukohast omab tähtsust see, kas vastase roboti sahk suudab vedavate rataste vastaspoolelt (mis võib olla avatud) roboti alla minna. Sellega on kerge robotit tasakaalust välja viia.

Näide

Raskeveokite vedav sild on alati taga. Sumorobot on nagu raskeveok, et alati oleks tagatud hea kontakt maaga.

 

 

 

clip_image008

 

 

 

clip_image010

Andurite valik ja paigutus

Andurid määravad roboti nutikuse vastase leidmisel (Ultrasonic ehk kauguseandur) ja platsil püsimise töökindluse (valguseandur).

Kas üks või kaks valguseandurit serva tuvastamiseks?

 

Üks valguseandur

Kaks valguseandurit

Kirjeldus

Ühe valguseanduriga roboti korral on kõige mõistlikum paigaldada valguseandur roboti ette keskele.

Kahe valguseanduri korral tuleb paigaldada need roboti ette paremasse ja vasakusse nurka.

Platsil püsimise töökindlus

Kui roboti valguseandur tuvastab valge joone, puudub robotil võimekus aru saada, millise suunaga läheneti platsi servale. Robot ei tea, kas läheneti otse või ollakse platsi serva suhtes tugevalt viltu.

Kahe valguseanduri korral tuvastab üks andur alati platsi serva varem kui teine ning selle info alusel on robotil võimekus ette võtta järgmine samm. Platsi poole tagasi liikudes keeratakse ümber parema või vasaku külje sõltuvalt sellest, kumb andur valge joone tuvastas.

 

 

 

clip_image012

 

 

 

 

clip_image014

Valguseandurid otse all või nurgaga ettepoole?

 

Valguseandur otse alla suunatud

Valguseandur on nurga all ettepoole suunatud

Platsil püsimise täpsus

Robot tuvastab valge joone alles siis, kui andur on täpselt joone peal.

See on igati töökindel ja järeleproovitud lahendus, kuid meeles peab pidama, et valge joone tuvastamise andurid peavad asuma võimalikult roboti eesotsas.

Robot tuvastab valge joone 1-2 cm enne seda, kui jõutakse platsi servale liiga lähedale.

Selle lahenduse korral võib tekkida oht, et ettepoole suunatud valguseandur tuvastab vastase robotit valge joonena. See omakorda võib viia roboti ettearvamatu käitumiseni.

Kas kauguseandur on vajalik?

 

Kauguseanduriga

Ilma kauguseandurita

Vastase tuvastamine

Kauguseandur võimaldab tuvastada vastase robotit.

Sellest on kasu ainult siis, kui seda infot osatakse roboti programmi juures õigesti kasutada.

Sõltuvalt roboti liikumise strateegiast on võimalik teha väga edukas sumorobot ilma kauguseandurita.

Rakendamise lihtsus

Kui puudub oskus kauguseandurist saabuvat infot programmis korrektselt kasutada, on tulemuseks kohapeal keerlev robot, milles puudub igasugune mõttekus.

Kui luua hea platsisõidu algoritm, võib kauguseandurita robot võita nii mõnegi kauguseanduriga roboti. Seni kuni kauguseanduriga robot keerutab ja vastast otsib, läheneb talle teine lihtsam robot ja puksib ringist välja.

 

Kas üks või kaks kauguseandurit?

 

Üks kauguseandur

Kaks kauguseandurit

Vastase tuvastamine

Andur tuleb paigaldada roboti esiosasse ja keskele.

Vastase tuvastamise algoritm seisneb tavaliselt selles, et robot keerutab seni, kuni näeb vastast. See töötab üldjuhul edukalt.

Paigaldada roboti esiotsa, kuid väikese nurgaga väljapoole.

Vastase tuvastamine toimub roboti pööramisel, kuid vastase asukoha määramine on tunduvalt täpsem kui ühe anduri korral.

Rakendamise lihtsus

Rakendamine on suhteliselt lihtne, kuid vastase tuvastamine seevastu ebatäpne. Ühe anduriga pole võimalik näiteks üheselt määrata vastase suunda.

Vastase roboti tuvastamine on täpne, kuid kahe anduri rakendamine ja vastase asukoha välja arvutamine vastavalt anduritest saadud infole keeruline.

 

 

 

clip_image016

 

 

 

 

clip_image018


 

Ründemehhanismid

Sumoroboti oluline komponent on tema ründemehhanism. See võib olla nii sahk, tagurpidi pöörlevad rattad või mõni muu täiesti erilaadne tõukur.

Sahk on kõige levinum ründemehhanism roboti juures. Seda on lihtne ehitada, kuid ikkagi tekivad küsimused. Kas sahk peaks olema suur või väike? Millise kaldenurgaga seda teha? Kas sahk peaks liikuma mootoriga üles-alla? Ja küsimus, millele enamik isegi ei mõtle, on saha värvus.

Vastates saha värviga seotud küsimusele, tuleb kõigepealt läbi mõelda, milline sahk ehitatakse - kas selline, mis püüab teise roboti alla minna, või mitte. Kui sahk ehitatakse selline, mis ei püüa teise roboti alla minna, pole selle värv oluline. Kui aga sahk on sellist tüüpi, mis läheb teise roboti alla, tasub see teha valget värvi LEGOst, kuna sellisel juhul tekib võimalus, et vastase roboti valguseandur peab seda platsi valgeks servaks ning hakkab teda ründava roboti eest ise ära tagurdama. Ja kuna tavaliselt ei paigaldata sumoroboti tagumisse otsa valgusandurit, tagurdabki robot ise ringist välja.

Kuna roboti mõõtmed on 15x15 cm, siis on üsna tavaline, et ründamiseks allalastud sahk etteantud mõõtude sisse ei mahu. Seega on saha ehitamisel esimene küsimus, kas sahk peaks ise pärast starti alla kukkuma või võiks mootor selle alla lükata.

Saha ehitamisel tuleb kindlasti mõelda mehaaniline takistuse peale, mis ei võimaldaks sahal maad puudutada väljaspool sumolauda, kui robot on platsi serval. Selliseid juhuseid on võistlustel palju olnud, kus robot on muidu tubli, aga tekitab ise endale kaotuse kuna platsi serva juures ulatub sahk üle serva maad puutuma.

Sahatüübid

Püstine sahk

Madal sahk

Püstise saha eeliseks on tavaliselt suur ja tugev pind. See võib lihtsustada teise roboti väljapressimist.

Madal sahk on tavaliselt lühike, kuna roboti piiratud mõõtude tõttu pole seda lihtsalt võimalik väga pikka teha.

Madal sahk peab olema otsast terav, kuna selle tööpõhimõte seisneb teise roboti serva alla minekus, et teine robot tasakaalust välja viia.

 

 

clip_image020

 

 

 

clip_image022

 

 

 

Mootoriga sahk

Ise allakukkuv sahk

Mootoriga sahk annab parema võimaluse rünnaku juhtimiseks.

Näiteks mootoriga sahal võib olla kogu aeg peal mootori surve, st. et see ei kerki niisama õhku.

Teiseks võib luua programmi, mis tõstab sahka üles, kui vastase robot on väga lähedal ehk siis arvatavalt saha peal. See võimaldab vastase roboti tasakaalust välja viia.

Sellist vastase kontrolli saab teha puuteanduriga.

Sellist sahka on lihtne ehitada ja see võib töötada peaaegu sama hästi kui mootoriga sahk.

Robotiehituse juures tuleb planeerida, et allakukkuva saha taga oleksid toed. Siis puudub sahal võimalus robotit kergitama hakata.

Samuti võib ehitada mehaanilise lukustuse, mis rakendub pärast saha allakukkumist ja takistab sahal üles tõusmast.

 

Saha asemel tagurpidi pöörlevad rattad?

Võistlustel on olnud sumoroboteid, mille etteotsa kinnitatakse tagurpidi pöörlevad rattad. Roboti ründeidee seisneb selles, et vastasega kokku puutudes tõstavad tagurpidi ringikäivad rattad vastase maast lahti. Selle tulemusena kaotab vastane tasakaalu ning teda saab ringist välja tõugata.

Sellise ründemehhanismi juures peab jälgima, et roboti esimene toetuspunkt oleks võimalikult rullikute all. Vastasel korral on oht, et kui rullikud on hästi vastasega haakunud, tõusevad ründaval robotil tagumised rattad õhku.

Teised ründemehhanismi võimalused?

Võib oma vaba fantaasiaga mõelda liikuvate kraananokkade peale, mis roboti käimaminekul üritavad ülalt vastase robotit tasakaalust välja viia.

Võib mõelda tõukemehhanismide peale, mis liiguvad edasi-tagasi nagu poksija käsi, tõugates selle abil vastast tasakaalust ja ringist välja.

Võib mõelda pöörleva elemendi peale, mille eesmärk on samuti vastase tasakaalust välja viimine ning seejärel ringist välja tõukamine.

Kindlasti tasub fantaasial lennata lasta ning tulemuseks võib olla mõni sootuks erinev sumorobot, mis üllatuslikult rabab vastase nõrka kohta ning võidab seeläbi võistluse.

Üks omapärane võimalus nii ründe- kui ka kaitse seisukohast seisneb roboti mõõtmetes. Kuna kõrguse suhtes pole mõõtmed piiratud, on igal LEGO sumo võistlusel olnud roboteid, mis stardihetkel on püsti. Peale starti aga kukutavad need robotid ennast platsile, olles seejärel oluliselt suuremate mõõtmetega kui reeglites ettenähtud. Kui roboteid enne võistlust mõõdetakse, siis tähtis on see, et stardihetkel ja –asendis vastavad roboti mõõtmed ettenähtud 15x15 cm sisse.

Kui aga praktikas vaadata, siis need on küll väga vaatemängulised robotid, kuid pole kunagi võitjateks osutunud. Tõenäoliselt on see seotud sellise suure roboti korral tema suurusest tuleneva aegluse ja kohmakusega.

 

Kaitsemehhanismid

Lisaks ründamisele peab iga robot olema valmis selleks, et teda ennast rünnatakse. See on loomulik, kuna sumovõistlus seisnebki teineteise ründamises ja ringist välja tõukamises.

Valguseandurite kaitsmine ründaja eest?

Sahkade juures käsitlesime teemat, et kui saha eesmärk on teise roboti alla minna, tasub sahk teha valge. Ründeidee seisneb selles, et vastasroboti andur näeb valget plaati ja arvates, et tegemist on ringi servaga, hakkab tagurdama.

Seega on kaitsmise osas vaja lahendust, kuidas vältida vastase roboti ja eriti just valget värvi saha sattumist valguseanduri alla. See on takistatav mehaanilisel moel, ehk siis tuleb ehitada piisavalt madala servaga robot, kuhu vastase sahk nii lihtsalt alla minna ei saa.

Rataste kaitsmine ründaja eest?

Nii nagu on tarvis kaitsta valguseandureid vastase saha eest, ootavad kaitsmist ka roboti rattad.

Kui rattad on ründajale avatud, võib suhteliselt kergesti tekkida olukord, kus vastase sahk sõidab poolviltu roboti ratta alla ning tulemuseks on tasakaalukaotus.

Lahendus on sama mis valguseanduri juures: ehitada võimalikult madala servaga seinad ratta ette, kaitsmaks neid vastase saha ja teiste ründemehhanismide eest.

Roboti kaal

Kaalukamat robotit on raskem ringist välja lükata kui kerget.

Teooria ütleb, et massikese peaks asuma võimalikult madalal, kuid praktikas on olnud roboteid, mis vaatamata kõrgele massikeskmele on saavutanud häid tulemusi. Massikeskme kõrgusest olulisem on jälgida, et massikese asuks vedavate rataste peal.

Kui kõik vajalik on robotile külge pandud, tuleb robot kaalule panna ja vaadata tulemust. Kui see on 100g või rohkem alla lubatud kaalu, tasub lisada robotile kaalutõstvaid komponente, et viia kaal maksimaalseks.

Praktikas on olnud mitmeid juhtumeid, kus kaalutõstmiseks lisatakse robotile külge teine NXT, millel pole küljes ei andureid ega mootoreid ja võib-olla isegi mitte akut, kuid selle abil on viidud roboti kaal maksimumi lähedale.

Kaal tasub jätta 5-10 grammi alla normi, et oleks arvestatud enne võistlust kasutatava kaalu juhusliku veaga.


 

Programmeerimine

Programmeerimine on määrava tähtsusega, sest ehituslikult hea roboti saab kergesti ringist välja tõugata, kui sellel puudub korralik programm.

Sumomängu võidu aluseks on kiirus. Kiire ja ootamatu rünnak on kõige kindlam tee võiduni. Kuni teine robot ennast ründe- või võitlusasendisse sätib, on ootamatu ja jõuline ründaja juba mängu võitnud. Sellisel juhul on võidu eelduseks kiirus ja õige programm.

Järgnevalt erinevatest programmeerimise strateegiatest.

Platsil püsimise strateegia

Sumorobot ühe valguseanduriga

clip_image024See on kõige lihtsam robot eesmärgiga vaid platsil püsida. Sellisel robotil tekib ründevõime alles siis, kui suudetakse vastane tuvastada.

Platsil liikumise esimene algoritm on väga lihtne. Robot sõidab seni, kuni valguseandur näeb valget joont, siis tagurdab, keerab ringi ja sõidab jälle, kuni näeb valget joont. Ja nii lõpmatuseni.

See pole just kuigi võidukas strateegia, kuid esmane platsil püsimine on tagatud.

Kui on soov, et robot ei jääks kogu aeg ühesugusel moel ringi keerama, võib programmi sisse viia juhusliku numbri arvutamise, mille tulemusena robot keerab iga kord ringi suvalises suunas.

Siinkohal on esitatud lihtsa sumoroboti programmikood. Arvestama peab, et sõltuvalt mootorite suunast ja rataste suurustest võivad toodud numbrid erineda nii suuruse kui märgi poolest.

 

Näide. Lihtne sumorobot ühe valguseanduriga

task main()

{

  SetSensorLight(S1);

  while(TRUE)

  {

    //robot kontrollib valguseanduri näitu

    //kui see asub valgel, siis robot tagurdab ja keerab

    if(Sensor(S1) > 50)

    {

      RotateMotor(OUT_BC, -100, 300);

      RotateMotorEx(OUT_BC, 100, 100, 100, TRUE, TRUE);

    }

    //kui valguseandur asub mustal pinnal,

    //sõidab robot lõpmatuseni otse

    else

    {

      OnFwd(OUT_BC, 100);

    }

  }

}

Sumorobot kahe valguseanduriga

clip_image026Kui on võimalus ehitada kahe valguseanduriga robot, muutub programm juba keerukamaks, kuid samas muutub ka robot targemaks.

Kui robot jõuab platsi servale, rakendub igal juhul üks valguseandur juba enne, kui teine andur valge joone tuvastab. Sõltuvalt sellest, kumb andur varem rakendus, on robotil olemas ligikaudne teadmine oma asendi kohta platsi serva suhtes. Väga harva on see servaga täpselt risti, enamasti on robot ikka ühele või teisele poole viltu.

Saadud asendi info põhjal peab programm olema loodud viisil, et robot keerab platsile tagasi alati lühimat teed mööda. See välistab olukorra, kus robot võib juhuslikult keerata näiteks hoopis platsilt välja.

Alljärgnev on näide kahe valguseanduriga sumoroboti programmist Arvestama peab, et sõltuvalt mootorite suunast ja rataste suurustest võivad toodud numbrid erineda nii suuruse kui märgi poolest.

clip_image028

 

Näide. Kahe valguseanduriga sumorobot

task main()

{

  SetSensorLight(S1);

  SetSensorLight(S2);

 

  while(TRUE)

  {

    //robot kontrollib ühe valguseanduri näitu

    //kui see asub valgel, siis robot tagurdab

    //ja keerab mööda lühemat ted platisle tagasi

    if(Sensor(S1) > 50)

    {

      RotateMotor(OUT_BC, 100, 300);

      RotateMotorEx(OUT_BC, 100, 100, 100, TRUE, TRUE);

    }

    //robot kontrollib teise valguseanduri näitu

    //kui see asub valgel, siis robot tagurdab

    //ja keerab teistpidi platsile tagasi

    else if(Sensor(S2) > 50)

    {

      RotateMotor(OUT_BC, 100, 300);

      RotateMotorEx(OUT_BC, 100, 100, -100, TRUE, TRUE);

    }

    //kui valguseandur asub mustal pinnal

    //sõidab robot lõpmatuseni otse

    else

    {

      OnFwd(OUT_BC, 100);

    }

  }

}

Vastase ründamise strateegia

Ründestrateegia seisneb selles, et olles tuvastanud vastase roboti, tuleb seda rünnata seni, kuni see on platsilt välja tõugatud.

Kui sumorobotil on ainult valguseandur, on tema kohtumine vastasega juhuslik. Ründavast robotist saame rääkida alles siis, kui see on võimeline tuvastama platsil teist robotit ning seda ründama.

Kõige levinum võimalus LEGO-robootikas vastase tuvastamiseks on kauguseandur. Kuid võib proovida kasutada ka puuteandurit või valguseandurit.

Vastase tuvastamine kauguseanduriga

clip_image030Kauguseandur on võimeline tuvastama objekte kuni 250 cm kaugusel. Andur tuleb häälestada kindlale kaugusele, millest lähemal asuvaid objekte peetaks vastase robotiks.

Sumolaua läbimõõt on 77 cm (koos valge joonega), roboti suurus 15 cm. Kui eeldada, et mõlemad robotid asuvad platsi servades, siis on robotite omavaheline kaugus 42 cm. Praktikas tasub eeldada, et robotid on teineteisele lähemal ning arvestada kauguseks 40 cm.

 

Lähema kauguse valiku põhjuseid on kaks.

1)       Anduri täpsus vastase tuvastamisel on kauguseanduril parim 30-80 cm.

2)       Kui robot tuvastab vastase liiga kaugelt ja hakkab selle poole sõitma, on vastane tema kohale jõudmise hetkeks suure tõenäosusega juba teises kohas.

Järgnevalt on plokkskeem selle kohta, kuidas käitub kahe valguseanduri ja kauguseanduriga tüüpiline sumorobot.

clip_image032

Allpool on kahe valguseanduri ja ühe kauguseanduriga sumoroboti programmikood. Arvestama peab, et sõltuvalt mootorite suunast ja rataste suurustest võivad toodud numbrid erineda nii suuruse kui märgi poolest.

Näide. Sumorobot kahe valguse- ja ühe kauguseanduriga

task main()

{

  SetSensorLight(S1);

  SetSensorLight(S2);

 

  while(TRUE)

  {

    //robot kontrollib ühe valguseanduri näitu

    //kui see asub valgel, siis robot tagurdab ja keerab

    if(Sensor(S1) > 50)

    {

      RotateMotor(OUT_BC, 100, 300);

      RotateMotorEx(OUT_BC, 100, 100, 100, TRUE, TRUE);

    }

    //robot kontrollib teise valguseanduri näitu

    //kui see asub valgel, siis robot tagurdab ja keerab

    else if(Sensor(S2) > 50)

    {

      RotateMotor(OUT_BC, 100, 300);

      RotateMotorEx(OUT_BC, 100, 100, -100, TRUE, TRUE);

    }

    //robot kontrollib kauguseanduriga vastase kaugust

    //kui vastane on lähemal kui 40cm, siis rünnatakse

    else if(SensorUS(S3) < 40)

    {

      OnFwd(OUT_BC, 100);

    }

    //kui robot pole ei platsi servas ega näe ka vastast,

    //siis lihtsalt keerutab kohapeal, vastast otsides

    else

    {

      OnFwdSync(OUT_BC, 60, 100);

    }

  }

}

Stardistrateegia

Sumoroboti võistluse start on määrava tähtsusega. Hea sumoroboti raund kestab vaid 5-10 sekundit. See tähendab, et vastase robot tuleb välja tõugata enne, kui see on suutnud oma ründega alustada.

Kuidas seda saavutada?

Võistlus algab sellega, et kohtunik viskab platsile shikiri risti, mis jagab platsi mõttes neljaks veerandiks. Robotid peavad asuma vastasveerandites ja liikumissuunaga vastasest eemale, shikiri risti näidatud noole suunas. Seega sõltuvalt sellest, kuhu veerandisse robot satub, on lühem tee vastaseni keerata kas ümber parema või ümber vasaku külje.

 

clip_image034

Roboti programmeerimisel tasub kaaluda varianti, kus vastavalt sellele, millisest kvadrandist robot alustab, on võimalik valida erineva algusega programm.

Kiire võit tuleneks sellest, et robot ei keera alguses mitte kauguseanduri järgi, vaid täiskiirusel täpselt 135 kraadi, sattudes seega vastakuti vastase robotiga. Seejärel täiskäik edasi ning kiire võit võibki olla selle strateegiaga käes.

Kui alguses keerata kauguseanduri järgi, siis tõenäoliselt robot ei keera täiskiirusega, vaid aeglasemalt, et tagada vastase leidmise täpsus. Samuti pole otseliikumise korral tavaolukorras mõistlik edasi liikuda täiskiirusel, kuna sellisel juhul ei suuda robot platsi servas kinni pidada.

Stardis andureid kasutamata on aga võimalik robot programmeerida nii, et see keerab täpselt 135 kraadi ja sõidab täiskiirusel edasi eelnevalt määratud vahemaa. Sellise käitumisega võib suure tõenäosusega eeldada, et vastase robot on veel ennast stardipaigas ringi keeramas ning saab üllatuslikult tabatud.

Alljärgnev on näide sumoroboti programmist, mille käigus valitakse alguses stardisuund ja robot teeb esimese keeramise ning otseliikumise.

See programm eeldab motoriseeritud sahaga robotit ning lisatud on saha allalaskmine pärast starti. Kui on tegemist ilma sahata robotiga, tuleb eemaldada saha mootori allalaskmise alamprogramm MotorDown() ja välja võtta käsk StartTask(MotorADown), mis paneb eelnimetatud alamprogrammi tööle. Pärast seda käivitub roboti tüüpiline platsil sõitmise strateegia.

Alljärgnevas programmis kasutatakse üht olulist muutujat OpponentScanDir, mille toimimine ja vajadus vajab selgitamist. Tegemist on tõeväärtusmuutujaga mille alusel määratakse, kummas suunas pöörates hakkab robot vastast otsima. Selle muutuja väärtus seatakse vastavalt sellele, kumma valguseanduriga tuvastas robot platsi serva.

Miks aga muudetakse selle väärtus vastupidiseks OpponentScanDir=!OpponentScanDir selles tingimuslauses, kus on tuvastatud kauguseanduri abil vastane?

Põhjus seisneb selles, et kui robot kiiresti keerab, võib vabalt juhtuda, et ta keerab nö. vastasest „üle“ ja seega on vastane roboti jaoks uuesti kadunud. Kui robot ei näe vastast, siis toimub kohapeal pöörates vastase otsimine. Kui robot aga korraks nägi vastast, kuid keeras üle, pole mõtet vastast otsides samas suunas edasi pöörata, vaid kõige lühem tee vastaseni on tagasi keerates. Seetõttu on sisse toodud OpponentScanDir muutmine vastupidiseks.

Näide. Sumoroboti programm koos stardivalikuga

//saha mootori allalaskmise alamprogramm

task MotorADown()

{

  while(TRUE)

    OnFwdReg(OUT_A, 100, OUT_REGMODE_SPEED);

}

 

task main()

{

  SetSensorLowspeed(S4);

  SetSensorLight(S3);

  SetSensorLight(S2);

  const int MAXLIGHT = 50;

  const int vasak = 1;

  const int parem = 2;

  const int otse = -1;

  int suund = 0;

 

  //oodatakse, kuni kasutaja on vajutanud nooleklahvi

  //ja sellega valinud, kummale poole robot stardib

  while(!suund)

  {

    if(ButtonPressed(BTNRIGHT, TRUE))

      suund = parem;

    if(ButtonPressed(BTNLEFT, TRUE))

      suund = vasak;

    if(ButtonPressed(BTNCENTER, TRUE))

      suund = otse;

  }

 

  //robot ootab 5 s ja teeb iga sekundi järel heli

  repeat(5)

  {

    Wait(1000);

    PlaySound(SOUND_CLICK);

  }

 

  //robot stardib vastavalt valitud suunas

  switch(suund)

  {

    case vasak:

      RotateMotorEx(OUT_BC, 100, 560, -100, TRUE, FALSE);

      break;

    case parem:

      RotateMotorEx(OUT_BC, 100, 560, 100, TRUE, FALSE);

      break;

  }

  //robot laseb saha alla ja hoiab seda seal mootori jõuga

  StartTask(MotorADown);

  //robot sõidab täiskiirusel otse ja

  //ründab oletatavat vastast

  RotateMotorEx(OUT_BC, -100, 700, 0, FALSE, FALSE);

 

  bool OpponentScanDir = TRUE;

  while(TRUE)

  {

    if(Sensor(S2) > MAXLIGHT)

    {

      //kui 1. valguseandur näeb valget joont,

      //peatatakse mootorid ja tagurdatakse

      Off(OUT_BC);

      RotateMotorEx(OUT_BC, 80, 540, 0, FALSE, TRUE);

      //OpponentScanDir määrab, kummas suunas

      //hakatakse vastast otsides keerutama

      OpponentScanDir = TRUE;

    }

    else if(Sensor(S3) > MAXLIGHT)

    {

      //kui 2. valguseandur näeb valget joont,

      //peatatakse mootorid ja tagurdatakse

      Off(OUT_BC);

      RotateMotorEx(OUT_BC, 80, 540, 0, FALSE, TRUE);

      //OpponentScanDir määrab, kummas suunas

      //hakatakse vastast otsides keerutama

      OpponentScanDir = FALSE;

    }

    else if(SensorUS(S4) < 37)

    {

      //robot on tuvastanud vastase, täiskäik edasi

      OnRevReg(OUT_BC, 89, OUT_REGMODE_SPEED);

      //roboti keeramise suund muudetakse vastupidiseks

      OpponentScanDir = !OpponentScanDir;

    }

    else

    {

      //vastase otsimise suund, sõltuvalt OpponentScanDir

      //väärtusest. Robot keerutab kuni näeb vastast

      if(!OpponentScanDir)

      {

        OnFwdReg(OUT_B, 100, OUT_REGMODE_SPEED);

        OnFwdReg(OUT_C, -100, OUT_REGMODE_SPEED);

      }

      else

      {

        OnFwdReg(OUT_B, -100, OUT_REGMODE_SPEED);

        OnFwdReg(OUT_C, 100, OUT_REGMODE_SPEED);

      }

    }

  }

}

 

1. 9 klass 2 õppetundi Robot puldiauto

Tiigrihype_logoSee materjal on loodud Tiigrihüppe Sihtasutuse programmi ProgeTiiger raames.

Tee oma robotist puldiauto, mida saab juhtida lülitite abil. See projekt annab ülevaate, kuidas toimivad while-tsüklid ja if-tingimuslaused koos lülitite ja mootoritega erinevates kombinatsioonides.

 

Alustuseks vaatame erinevaid võimalikke lähenemisi puldiauto juhtimisele. Esimesed on lihtsamad ning järgmised muutuvad keerukamateks.

Ehitada tuleb tavaline baasrobot, mis on võimeline liikuma edasi-tagasi ja keerama paremale-vasakule. Sellele tuleb lisada kõige pikema juhtmega üks või kaks puuteandurit vastavalt ülesandele, mida kasutatakse roboti juhtimiseks.

1.       Puldiauto ühe lülitiga, 2 varianti.

a.        Robot liigub edasi, kui lüliti on vajutatud ning seisab, kui lüliti on lahti lastud.

b.