Reaali Robootika.COM

NXT robotimaailm ja programmeerimine C-keeles

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);

}

 

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.       Robot hakkab liikuma, kui lüliti on alla vajutatud ja lahti lastud (bumped) ning jääb seisma, kui teist korda lüliti alla vajutatakse ja lahti lastakse.

2.       Puldiauto kahe lülitiga, 2 varianti.

a.        Kumbki lüliti juhib ühte mootorit. Lüliti lahti lastud asendis robot seisab, parempoolset lülitit vajutades keerab robot paremale ja vasakpoolsega vasakule. Mõlemat all hoides sõidab robot otse edasi.

b.       Robot hakkab edasi sõitma siis, kui mõlemad lülitid on korraga alla vajutatud ja lahti lastud (bumped). Üksikult kasutades toimivad lülitid roboti paremale-vasakule keeramise juhtimiseks. Teistkordsel üheaegsel vajutamisel (bumped) jääb robot seisma.

Puldiauto ühe lülitiga

Sellel ülesandel on kaks varianti, esimene hästi lihtne ning teine õige pisut keerulisem.

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

Programmi käivitudes on esimene käsk SetSensorTouch, mis on vajalik lülitianduri initsialiseerimiseks, st. selle käsuga me ütleme robotile: „Sinu esimeses pordis asub lüliti andur“. Järgmisena käivitub lõpmatu while-tsükkel (lõpmatu, kuna selle tingimuseks on märgitud TRUE). Lõpmatu tsükli sees on if-else tingimuslause. If tingimus kontrollib, kas lüliti on alla vajutatud. Kui lüliti on alla vajutatud, siis käivitub edasisõitmine OnFwd, kui mitte, siis käivitub else ning mootorid lülitatakse välja.

Kuna tegemist on while-tsükliga, siis if-else lauset seal sees täidetakse 10 000 korda sekundis. See tagab, et robot igal juhul nö. märkab lüliti vajutamist ja meil pole võimalust öelda, et „robot ei jõudnud reageerida minu lüliti vajutusele“.


 

Näide. Lihtne puldiauto ühe lülitiga

task main()

{

  SetSensorTouch(S1);

  while(TRUE)

  {

    //kui lüliti on alla vajutatud, sõidab robot edasi

    //kui lüliti on lahti lastud, jääb robot seisma

    if (Sensor(S1))

      OnFwd(OUT_BC, 100);

    else

      Off(OUT_BC);

  }

}

Variant 2. Robot hakkab liikuma, kui lüliti on alla vajutatud ja lahti lastud (bumped) ning jääb seisma, kui lüliti teist korda alla vajutatakse ja lahti lastakse.

Selle programmi while-tsüklid käituvad eelmise variandiga võrreldes täiesti erinevalt. Alguses käivitub esimene lõpmatu while-tsükkel while(TRUE).  Järgmised kaks käsku on samuti while-tsüklid, mille tingimusena kontrollitakse lüliti asendit. Kui lüliti on lahti lastud (nagu alguses on), siis on koheselt käivitunud esimene tsükkel while(!Sensor(S1)), mis käib seni, kuni nupp on lahti lastud asendis. Täpselt nupu alla vajutamisel väljutakse sellest while-tsüklist, kuna nupu väärtus muutus true-ks, mis omakorda while-tsükli jaoks hüüumärgiga vastupidiseks muudeti. Käivitub teine while-tsükkel, millest programm väljub siis, kui kasutaja nupu lahti laseb.

Alles nüüd käivitub käsk, mis paneb mootorid liikuma ja roboti edasi sõitma. Kuna ükski käsk mootori sõitmist ei lõpeta, töötavad mootorid seni, kuni kasutaja on teist korda nupu alla vajutanud ja lahti lasknud, et käivituks käsk Off mootorite väljalülitamiseks.

Ja alles seejärel jõuab programm ringiga algusesse tagasi kõige esimesena käivitunud while-tsükli täitmisele.

Näide. Puldiauto koos bumped-lülitiga

task main()

{

  SetSensorTouch(S1);

  while(TRUE)

  {

    //programm ootab nupu vajutamist ja lahti laskmist

    while(!Sensor(S1));

    while(Sensor(S1));

    //robot sõidab edasi

    OnFwd(OUT_BC, 100);

    //programm ootab nupu vajutamist ja lahti laskmist

    while(!Sensor(S1));

    while(Sensor(S1));

    //robot jääb seisma

    Off(OUT_BC);

  }

}

Puldiauto kahe lülitiga

clip_image002[4]Sellel ülesandel on kaks varianti, esimene lihtsam ja teine veidi keerulisem.

Variant 1. Kumbki lüliti juhib ühte mootorit. Lahti lastud asendis robot seisab, parempoolset nuppu vajutades keerab robot paremale ja vasakpoolsega vasakule. Mõlemat all hoides sõidab robot otse edasi.

Järgnevalt on välja toodud selle roboti plokkskeem. Sellel skeemil tähendavad rombid while-tsükleid või if-lauseid ning ristkülikud tegevusi nagu näiteks mootorite liigutamine.

Siin on näha, et alguses käivitub lõpmatu while-tsükkel, mille sees on if-else-if-else tingimuslause.

Esimese if-tingimusega kontrollitakse, kas mõlemad lülitid on korraga vajutatud. Kui tingimus vastab tõele, siis robot sõidab otse edasi, kui mitte, siis minnakse kontrollima järgmist if-tingimust. Järgmises if-tingimuses kontrollitakse, kas lüliti 1 on vajutatud. Kui on, siis käivitub mootoritele käsk, et robot peab paremale pöörama. Sellele järgnevaid tingimusi eiratakse ning tsükkel jõuab uuesti algusesse tagasi ning kontrollib uuesti if-tingimust.

Kui esimene lüliti ei ole vajutatud, liigub programm edasi ja kontrollib järgmise else-if tingimusega, kas teine lüliti on vajutatud. Kui on, siis robot pöörab vasakule. Kui aga kumbki lüliti pole vajutatud, käivitub else-lause ja robot seiskab oma mootorid.

Näide. Robot kahe lülitiga

task main()

{

  SetSensorTouch(S1);

  SetSensorTouch(S2);

 

  while(TRUE)

  {

    //kui mõlemad lülitid korraga vajutatud, sõidab otse

    if (Sensor(S2) && Sensor(S2))

      OnFwd(OUT_BC, 50);

    //kui esimene lüliti vajutatud, keerab robot paremale

    else if (Sensor(S2))

      OnFwdSync(OUT_BC, 50, -10);

    //kui teine lüliti on vajutatud, keerab robot vasakule

    else if(Sensor(S1))

      OnFwdSync(OUT_BC, 50, 10);

    //kui lülitid pole vajutatud, sõidab robot otse

    else

      Off(OUT_BC);

  }

}

clip_image004[4]Variant 2. Robot hakkab edasi sõitma siis, kui mõlemad lülitid on korraga alla vajutatud ja lahti lastud. Üksikult kasutades toimivad lülitid paremale-vasakule keeramisena. Teistkordsel üheaegsel vajutamisel jääb robot seisma.

Pärast esimese lõpmatu tsükli käivitumist jääb programm järgmise while-tsükli sisse while(!Sensor(S1) && !Sensor(S2)). Tsükli tingimuseks on kaks lülitiandurit koos && märgiga, mis tähendab seda, et while-tsükkel peatub alles seejärel, kui mõlemad lülitid on korraga alla vajutatud. Seejärel käivitub järgmine while-tsükkel, mis ootab, kuni mõlemad lülitid on lahti lastud.

Seejärel käivitub while-tsükkel, mille tingimuseks on uuesti nende lülitite olek, kuid selle while-tsükli sees asuvad ka paremale-vasakule-otse sõitmise if-else-tingimuslaused.

Siinkohal toimib see programm täpselt samamoodi nagu esimene variant kahe lülitiga robotist.

Kui mõlemad lülitid on korraga alla vajutatud, muutub selle while-tsükli tingimus mittetõeseks ning väljutakse tsüklist. Mootorid seisatakse ja programmis käivitub viimane while-tsükkel, mille sees omakorda uuesti kontrollitakse nende kahe lüliti olekut ja oodatakse, kuni kasutaja need lahti laseb.

See viimane kontroll on vajalik selleks, et programm ei suunduks koheselt järgmisele ringile, vaid ootaks enne ära nuppude lahtilaskmise.

Näide. Robot kahe lülitiga ja bumped funktsiooniga

task main()

{

  SetSensorTouch(S1);

  SetSensorTouch(S2);

  while(TRUE)

  {

    //programm ootab vajutust

    while(!Sensor(S1) && !Sensor(S2));

    //programm ootab nupu lahti laskmist

    while(Sensor(S1) && Sensor(S2));

   

    //kui mõlemad nupud koos vajutatud,

    //väljutakse while-tsüklist

    while(!Sensor(S1) || !Sensor(S2))

    {

      //kui esimene lüliti on vajutatud, keerab paremale

      if (Sensor(S2))

        OnFwdSync(OUT_BC, 50, -10);

      //kui teine lüliti on vajutatud, keerab vasakule

      else if(Sensor(S1))

        OnFwdSync(OUT_BC, 50, 10);

      //kui lülitid pole vajutatud, sõidab robot otse

      else

        OnFwd(OUT_BC, 50);

    }

    //pärast while tsüklist väljumist seisatakse mootorid

    Off(OUT_BC);

    //programm ootab nupu lahti laskmist, see on vajalik,

    //et programm ei suunduks automaatselt uuele ringile

    while(Sensor(S1) && Sensor(S2));

  }

}

6. 9 klass 2 õppetundi: Roboti teekonna kaardistamine

Tiigrihype_logo

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

Ülesande eesmärk

Ehitada ja programmeerida robot, mis suudab ekraanile kuvada oma läbitud teekonna.

Käesoleva ülesande lahendamine kestab 2 robootika tundi, kusjuures ühe tunni pikkuseks on arvestatud 2x45 min.

 

Ülesande lahendamiseks vajalik

Õpilased peavad olema hästi kursis matemaatikaga ja oskama arvutada täisnurkse kolmnurga külgede pikkuseid nii koosinuse kui ka siinuse abil.

Õpilane peab arvesse võtma ülesande lahendamisel järgmisi asjaolusid:

1)      Ratta läbimõõt

2)      Rataste teljevahe

3)      Peab oskama tuletada roboti pöördenurga, kui ülejäänud muutujad on teada

roboti pöördenurk

4)      Peab oskama arvutada kolmnurga lähiskaateti pikkust tervanurga ning hüpotenuusi abil

lähiskaatet

5)      Peab oskama arvutada vastaskaateti pikkust teravnurga ning hüpotenuusi abil

vastaskaatet

6)      Peab oskama aru saada NXT koordinaatteljestikust, mis on mõõtudega 100x64 pikselit

 

Ülesande lahendamise käik

Lahenduse koodi pole mõttekas õpilastele ette anda, tegemist on nii lihtsa koodiga, et selle mahakirjutamine võtaks aega 10 min, kuid tarkus jääks tulemata.

Ülesanne on jaotatud neljaks etapiks mille käigus õpilased jõuavad järk-järgult lahenduseni.

Etapid ja õppetundide jaotus:

1.      Tund

a.      Roboti pöördenurga leidmine rataste pöördenurga abil

b.      Kolmnurga lahendamine, lähis- ja vastaskaatetite leidmine (siinus, koosiinus)

2.      Tund

a.      Roboti pöördenurk ning kolmnurga lahendamine kokku pandud (suunamõõdik ekraanil)

b.      Lõplik ülesande lahendus
 

Roboti pöördenurga leidmine

Selle etapi eesmärk on lasta õpilastel jõuda valemini, mille abil saab teisendada roboti rattakeeramise kraadid roboti enda pööramise kraadideks. See on vajalik, kuna ülesande lahendus baseerub omadusel, et meil on teada igal hetkel rataste omavahelise pöördenurga vahe.

Roboti pööramise illustreeriv näide

Kõige lihtsam on tuua näide seisva robotiga, kus meil on teada et üks ratas seisab ja teine ratas pöörab näiteks 360 kraadi, ehk teeb ühe tiiru. Sellisel juhul saame roboti pöördenurgaks 90 kraadi, eeldusel et rataste teljevahe on 110 mm ja diameeter 56 mm.

 

Õpilased on varasemalt lahendanud ülesandeid, mille käigus nad on pidanud vastupidist ülesannet lahendama, st. et on teada mitu kraadi peab robot pöörama ning õpilased peavad välja arvutama mitu kraadi peab ratas pöörama, et saavutada roboti õige positsioon.

Õpilased on siiani kasutanud valemit:

mootori pöördenurk

 

Käesoleva ülesande käigus peavad õpilased algatuseks avaldama valemist RobotiPöördenurga.

Kui neil on valem õigesti avaldatud, kirjutavad nad programmi, mille abil saab kuvada roboti pöördenurga ekraanile numbriliselt. See annab neile kohest tagasisidet, kas nad on õigesti avaldanud RobotiPöördenurga ning seda ka õigesti rakendada oskavad.

Antud etapi lahendus on alljärgnev programmikood.

//käesolev programm kuvab ekraanil roboti pöördenurga

//roboti mootorite pöörete järgi arvutatakse välja roboti pöördenurk

//ja kuvatakse see ekraanil

//testimiseks tuleb robotit käsitsi liigutada.

 

//Roboti pööramise nurk = (Diameeter * x kraadi)/(Rataste vahe * 2)

 

task main()

{

int RattaDiameeter = 56;  //ratta diameeter

int RatasteVahe = 110;   //ratastevaheline kaugus, teljevahe

int suund;             //mootori pöörete vahe

float BMootor;         //mootori B pöörded

float CMootor;         //mootori C pöörded

int RobotiNurk;        //roboti pööramise nurk

 

while (1)

        {

        //mootorite pöörded loetakse muutujatesse

  BMootor = MotorRotationCount(OUT_B);

  CMootor = MotorRotationCount(OUT_C);

 

        //lahutatakse ühe mootori pöördenurgast teise mootori pöördenurk

        //tulemuseks on ratastevaheline erinevaus pöördenurgas

          suund = BMootor - CMootor;

         

        //siin arvutatakse välja roboti pöördenurk, võttes arvesse rataste läbimõõtu

         //rataste teljevahe ja ratastevaheline kraadide erinevus

          RobotiNurk = (RattaDiameeter * suund)/(RatasteVahe * 2); 

         

               ClearLine(LCD_LINE1);

         

          //ekraani ülemisel real kuvatakse roboti nurk

          NumOut(0, LCD_LINE1, RobotiNurk);

          TextOut(30, LCD_LINE1, "kraadi");

        }

}


 

Kolmnurga lahendamine

Selle etapi eesmärk on lasta õpilastel lahendada kolmnurk, mille kohta on teada üks teravnurk ning hüpotenuus. Leida on tarvis lähiskaateti ja vastaskaateti pikkused.

kolmnurga lahendamineLahendatud kolmnurk tuleb kuvada ekraanil ning peab olema muutumises koos nurga muutmisega. Nurga muutmine teostada noolenuppudega parem-vasak, sammuga 5 kraadi.

Antud etapp on olulise tähtsusega, kuna selle abil jõuavad õpilased ülesande lõpuks äratundmisele, kuidas aitab kolmnurga lahendamine neid robootika liikumisülesande juures teekonna kuvamisel. Neil peaks tekkima side matemaatika õppimise vajaduse ja reaalse elu vahel.

Kolmnurga lähis- ja vastaskaateti pikkuste arvutamise valemid. Vaata joonist.

lähiskaateti arvutamine

vastaskaateti arvutamine

Kolnurk

Kui nad on kolmnurga lahendamisest aru saanud, tuleb selle kohta programm kirjutada. Programm võiks välja näha selline, et hüpotenuus on ette antud (ülesande lahenduses on see samuti mittemuutuv suurus), kuid nurka peab saama muuta ja tulemusena kuvatakse ekraanil kolmnurga lähis- ja vastaskaatetite pikkused.

Kolmnurga lahendamise programmikood.

//käesolev programm lahendab kolmnurga

//ja joonistab selle ekraanile

 

//lähiskaatet = teravnurga koosinu * hüpotenuus

//vastaskaatet = teravnurga siinus * hüpotenuus

 

task main()

{

 

int RobotiNurk=45; //roboti pööramise nurk

int Lkaatet;                   //lähiskaatet

int Vkaatet;                   //vastaskaatet

int Hypotenuus=80; //hüpotenuus

 

while (1)

        {

        ClearScreen();

        TextOut(0, LCD_LINE1, "Sisesta nurk");

        NumOut(85, LCD_LINE1, RobotiNurk);

        if(ButtonPressed(BTNCENTER, FALSE))

               {

               while(ButtonPressed(BTNCENTER, FALSE));

               break;

               }

        if(ButtonPressed(BTNRIGHT, FALSE))

               RobotiNurk += 5;

        while(ButtonPressed(BTNRIGHT, FALSE));

        if(ButtonPressed(BTNLEFT, FALSE))

               RobotiNurk -= 5;

        while(ButtonPressed(BTNLEFT, FALSE));

     

          //arvutatakse välja lähiskaateti pikkus

          Lkaatet = cosd(RobotiNurk) * Hypotenuus;

          //arvutatakse välja vastaskaateti pikkus

          Vkaatet = sind(RobotiNurk) * Hypotenuus;

                 

          TextOut(0, LCD_LINE2, "Lkaat");

          TextOut(50, LCD_LINE2, "Vkaat");

               NumOut(35,LCD_LINE2, Lkaatet);

               NumOut(85,LCD_LINE2, Vkaatet);

            

               //ekraanile joonistatakse lähiskaatet

               LineOut(1, 1, Lkaatet, 1);

               //ekraanile joonistatakse vastaskaatet

               LineOut(Lkaatet, 1, Lkaatet, Vkaatet);

               //ekraanile joonistatakse hüpotenuus

               LineOut(1, 1, Lkaatet, Vkaatet);

               Wait(100);

        }

}


 

Roboti liikumise suunanäidik

Roboti suunanäidik ekraanilSelle etapi eesmärk on panna kokku esimene ja teine etapp, ehk siis ühendada omavahel roboti pöördenurga arvutamine ja kolmnurga lahendamine.

 Etapi lõpptulemusena valmib NXT ekraanile suunanäidik, mis näitab suunda kuhu poole robot sõidab. Seda omadust kasutame lõpplahenduses roboti teekonna kuvamiseks.

Roboti suunanäitamise programmi kood.

//käesolev programm kuvab ekraanile roboti liikumise suuna
 
//Roboti pööramise nurk = (Diameeter * x kraadi)/(Rataste vahe * 2)
//lähiskaatet = teravnurga koosinu * hüpotenuus
//vastaskaatet = teravnurga siinus * hüpotenuus
 
task main()
{
int RattaDiameeter = 56; //ratta diameeter
int RatasteVahe = 110;   //ratastevaheline kaugus, teljevahe
int suund;               //mootori pöörete vahe
float BMootor;           //mootori B pöörded
float CMootor;           //mootori C pöörded
int RobotiNurk;          //roboti pööramise nurk
int Lkaatet;             //lähiskaatet
int Vkaatet;             //vastaskaatet
int Hypotenuus=60;       //hüpotenuus
 
while (1)
        {
        //mootorite pöörded loetakse muutujatesse
  BMootor = MotorRotationCount(OUT_B);
  CMootor = MotorRotationCount(OUT_C);
          suund = BMootor - CMootor; 
          
          //siin arvutatakse välja roboti pöördenurk, võttes arvesse rataste läbimõõtu
          //rataste teljevahe ja ratastevaheline kraadide erinevus
          RobotiNurk = (RattaDiameeter * suund)/(RatasteVahe * 2);
          
          //arvutatakse välja lähiskaateti pikkus
          Lkaatet = cosd(RobotiNurk) * Hypotenuus;
          //arvutatakse välja vastaskaateti pikkus
          Vkaatet = sind(RobotiNurk) * Hypotenuus;
          
          ClearScreen();
          //ekraanile joonistatakse roboti suunanäidik
          LineOut(50, 25, Vkaatet+50, Lkaatet+25);
                  
          //ekraani ülemisel real kuvatakse roboti nurk ning lähis- ja vastaskaatet
          NumOut(0, LCD_LINE1, RobotiNurk);
          NumOut(50,LCD_LINE1, Lkaatet);
               NumOut(80,LCD_LINE1, Vkaatet);   
        Wait(100);
        }
}

 

Lõplik ülesande lahendus

Alljärgnevalt roboti teekonna kaardistamise programmi kommenteeritud kood.

Sisuliselt tekib ekraanile joon hästi paljude pisikeste kolmnurkade lahendamise tulemusena.

Siin on lisandunud eelkirjeldatud etappidega võrreldes kaks olulist võtet.Ülesande lahendus roboti ekraanil

1.      Kaardistamise sageduseks kasutatakse rataste pöörlemist. Kui ükskõik kumb ratas teeb 360 kraadi, ehk ühe täisringi, arvutatakse ja salvestatakse selle hetke roboti olukord ekraanil. See võte on vajalik eelkõige seetõttu, et robot ei suuda ühe rattapöörde jooksul kuigi palju viga teha ning see on piisav et saada küllaltki täpne joon ekraanil. Alati võib proovida ise selle täpsust suurendada, vähendades roboti rataste kraadide arvu mille jooksul toimub kaardistamine.

2.      Kolmnurga hüpotenuusi seadmine. See võimaldab muuta ekraanile kujutatava joone pikkust ja täpsust. Kui hüpotenuus panna liiga lühike, näiteks 2 punkti, siis on kaatetite tulemuseks ainult 1 või 0 ning joone täpsus kannatab olulisel määral. Kui aga hüpotenuus panna liiga pikk, joonistatakse ekraanile korraga väga pikk joon, mis on samuti mõttetu ekraani väikse pinna tõttu.

Ekraanipilt antud programmi tulemusest NXT ekraanil.

//käesolev programm on roboti teekonna kaardistaja

//roboti mootorite pöörete järgi arvutatakse välja roboti teekonna kaart

//ja kuvatakse see ekraanil

 

//task soida on lihtsalt roboti sõitmise ja kaardistamise testimiseks

//task sõida abil sõidab robot südame kujutise ning joonistab selle ka ekraanile

task soida()

{

while(1)

        {

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

        RotateMotorEx(OUT_BC, 40, 360, 15, TRUE, TRUE);

        RotateMotorEx(OUT_BC, 40, 1300, 0, TRUE, TRUE);

        RotateMotorEx(OUT_BC, 40, 1600, -20, TRUE, TRUE);