Programování Lego robotů pomocí NQC
(verze 3.03 z 2.října 1999) (česká verze 0.99 z 18. dubna 2003)
Mark Overmars
Department of Computer Science Utrecht University P.O.Box 80.089, 3508 TB Utrecht the Netherlands
Tento překlad vznikl částečně jako součást diplomové práce, částečně díky potřebě takovéto publikace dané rozšířením stavebnic Mindstorms v ČR. Nejsem profesionální typograf, překladatel ani programátor. V textu se proto mohou vyskytovat chyby nebo nepřesnosti. Naleznete-li nějaké, můžete mi je zaslat na mou adresu.
Robert Seifert KF PF UJEP České mládeže 8 400 96 Ústí nad Labem e-mail:
[email protected]
Předmluva Stavebnice Lego MindStorms a CyberMaster jsou novými nádhernými hračkami, ze kterých se dá postavit široká škála robotů, a ty pak mohou být programovány k vykonávání různých úloh všech obtížností. Naneštěstí software dodávaný se stavebnicí, ač vizuálně velmi atraktivní, má omezenou funkčnost. Proto může být použit k řešení pouze jednoduchých úloh. K využití celého potenciálu robotu potřebujete jiné programovací prostředí. NQC je programovací jazyk napsaný Davem Baumem a je speciálně určen pro roboty z Lega. Pokud jste nikdy předtím nenapsali program, nemusíte mít strach. NQC je snadné a tento tutoriál Vám poví vše potřebné. Ve skutečnosti je programování Lego robotů mnohem snazší než programování skutečných počítačů, a tak máte šanci stát se snadno programátorem. Aby bylo psaní programů snazší, je zde RCX Command Center. Tento nástroj Vám pomáhá psát programy, posílat je robotu, spouštět je a ukončovat. RCX Command Center funguje podobně jako každý textový editor, ale nabízí i něco navíc. Tento tutoriál používá jako programovací prostředí RCX Command Center (verze 3.0 nebo vyšší)1 . Můžete si jej zdarma stáhnout z adresy http://www.cs.uu.nl/people/markov/lego/ RCX Command Center běží na počítačích PC s operačním systémem Windows (95, 98, NT). Ujistěte se, že jste alespoň jednou spustili software, který přišel se stavebnicí! Tento software obsahuje komponenty, které RCX Command Center využívá. Samotný jazyk NQC však může být využíván i na jiných systémech (Sun, Apple, UNIX a další). Můžete jej stáhnout z www adresy http://www.enteract.com/~dbaum/lego/nqc/ Většina z tohoto tutoriálu je použitelná i pro práci na jiných platformách (za předpokladu, že používáte NQC 2.0 nebo vyšší2 ), pouze přijdete o některé nástroje a barevné zvýraznění syntaxe. V tomto tutoriálu předpokládám, že máte robot ze stavebnice MindStorms. Valná část obsahu se také vztahuje na roboty CyberMaster, ale některé funkce nebudou u těchto robotů dostupné. Také některá jména, např. motorů, jsou odlišná a tak si budete muset příklady trochu upravit, aby pracovaly bezchybně i na CyberMaster robotech.
Poděkování Rád bych na tomto místě poděkoval Davu Baumovi za vývoj NQC. Také velice děkuji Kevinu Saddi za napsání první verze první části tohoto tutoriálu.
1
Vývoj RCX Command Center byl ukončen, Nová verze pod názvem Bricx Command Center je dosažitelná na adrese http://members.aol.com/johnbinder/bricxcc.htm 2 V současnosti je dostupná vyšší verze, která má některé vlastnosti rozšířené oproti popisu v tomto textu.
3
4
Obsah 1 Váš 1.1 1.2 1.3 1.4 1.5 1.6 1.7
první program Stavba robotu . . . . . . . . . . . . Začínáme s RCX Command Center Psaní programu . . . . . . . . . . . Spuštění programu . . . . . . . . . Chyby v programu . . . . . . . . . Nastavení rychlosti . . . . . . . . . Shrnutí . . . . . . . . . . . . . . .
. . . . . . .
7 7 8 8 10 10 11 11
. . . .
13 13 13 14 14
3 Použití proměnných 3.1 Pohyb po spirále . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Náhodná čísla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Shrnutí . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17 17 18 19
2 Zajímavější program 2.1 Zatáčení . . . . . . . 2.2 Opakování příkazů . 2.3 Přidávání komentářů 2.4 Shrnutí . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
. . . . . . .
. . . .
4 Řídící struktury 20 4.1 Příkaz if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.2 Příkaz do . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 4.3 Shrnutí . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 5 Senzory 5.1 Čekání na senzor . . . . . . . 5.2 Práce s dotykovým senzorem 5.3 Světelné senzory . . . . . . . 5.4 Shrnutí . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
23 23 24 25 26
6 Úlohy a podprogramy 6.1 Úlohy . . . . . . . . 6.2 Podprogramy . . . . 6.3 Inline funkce . . . . 6.4 Definování maker . . 6.5 Shrnutí . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
27 27 27 28 28 29
7 Hraní hudby 7.1 Vestavěné zvuky . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2 Hraní hudby . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.3 Shrnutí . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
34 34 35 36
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
5
8 Více o motorech 8.1 Brzdíme něžně . . . . . 8.2 Rozšířené příkazy . . . . 8.3 Změna rychlosti motoru 8.4 Shrnutí . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
9 Více o senzorech 9.1 Typy senzorů a režimy práce . . . . . 9.2 Rotační senzor . . . . . . . . . . . . . 9.3 Připojení více senzorů na jeden vstup 9.4 Děláme si detektor překážek . . . . . . 9.5 Shrnutí . . . . . . . . . . . . . . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
. . . . .
. . . .
37 37 37 38 40
. . . . .
41 41 42 43 44 45
10 Paralelní úlohy 10.1 Špatný program . . . . . . . . . 10.2 Ukončování a restartování úloh 10.3 Používáme semafory . . . . . . 10.4 Shrnutí . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
48 48 49 49 50
11 Komunikace mezi roboty 11.1 Dávání příkazů . . . . . 11.2 Volba vůdce . . . . . . . 11.3 Varování . . . . . . . . . 11.4 Shrnutí . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
53 53 54 55 56
. . . .
59 59 59 61 61
. . . . . .
63 63 63 63 64 64 65
12 Další příkazy 12.1 Časovače . . . . 12.2 Displej . . . . . . 12.3 Použití datalogu 12.4 Shrnutí . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
13 Stručná referenční příručka 13.1 Příkazy . . . . . . . . . . 13.2 Podmínky . . . . . . . . . 13.3 Výrazy . . . . . . . . . . . 13.4 Funkce RCX . . . . . . . 13.5 Konstanty RCX . . . . . . 13.6 Klíčová slova . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
NQC . . . . . . . . . . . . . . . . . . . . . . . .
14 Závěrečné poznámky
. . . .
. . . . . .
. . . .
. . . . . .
. . . .
. . . . . .
. . . .
. . . . . .
. . . .
. . . . . .
. . . .
. . . . . .
. . . .
. . . . . .
. . . .
. . . . . .
. . . .
. . . . . .
. . . .
. . . . . .
. . . .
. . . . . .
. . . .
. . . . . .
. . . .
. . . . . .
. . . .
. . . . . .
. . . .
. . . . . .
. . . .
. . . . . .
. . . .
. . . . . .
68
6
1
Váš první program
V této kapitole si ukážeme, jak napsat první jednoduchý program. Naprogramujeme robota, aby se pohyboval 4 sekundy vpřed, poté 4 sekundy vzad a nakonec aby zastavil. Není to nic světoborného, ale ukážeme si tím základní myšlenku programování a jak je to jednoduché. Předtím ale, než můžeme začít psát program, potřebujeme robota.
1.1
Stavba robotu
Robot, který nás bude provázet tímto tutoriálem, je zjednodušenou verzí přísně tajného robotu popsaného na stránkách 39-46 vaší Constructopedie. Použijeme pouze základní šasi. Odstraňte celý předek se dvěma rameny a dotykovými čidly. Také připojte vodiče odlišně — tak, aby přicházely k RCX zvenčí. To je důležité, aby Váš robot jel správným směrem. Po všech úpravách by měl Váš robot vypadat tak, jako na obrázku 1.
Obrázek 1: Dozer
Ujistěte se také, že je infraport správně připojen k počítači a nastaven na dlouhou vzdálenost. (Možná budete chtít přezkoušet robota pomocí RIS software, abyste se ujistili o jeho správné funkci.) 7
1.2
Začínáme s RCX Command Center
Naše programy budeme psát v RCX Command Center. Spusťte jej dvojklikem na ikonu RcxCC. (Předpokládám, že máte RCX Command Center správně nainstalován. Pokud ne, stáhněte si jej z www stránky (adresu najdete v předmluvě), rozbalte ho a umístěte do adresáře který se Vám hodí.) Program se Vás zeptá, který robot se má vyhledat. Zapněte robota a stiskněte OK. Program jej (většinou) najde automaticky. Poté se na obrazovce objeví uživatelské prostředí jako zde na obrázku 2.
Obrázek 2: Obrazovka RCX Command center Rozhraní se bodobá běžnému textovému editoru, s obvyklými menu, panely a tlačítky pro otevírání a ukládání souborů, tisk, editaci atp. Také je zde ale zvláštní menu pro kompilování a nahrávání programů do robotů a pro získávání informací z robotů. Pro teď jej můžeme ignorovat. Jdeme psát nový program. Tedy stiskněme New File tlačítko pro vytvoření nového, prázdného okna.
1.3
Psaní programu
Nyní do okna napište program uvedený v příkladu 1.13 . 3 POZOR! Jazyk NQC patří mezi tzv. „case sensitiveÿ, a tak záleží na použití velkých a malých písmen.
8
task main() { OnFwd(OUT_A); OnFwd(OUT_C); Wait(400); OnRev(OUT_A+OUT_C); Wait(400); Off(OUT_A+OUT_C); }
Příklad 1.1 - jízda vpřed a vzad
Na první pohled to vypadá velmi složitě, a tak se na něj podíváme zblízka. Program v NQC sestává z úloh (anglicky task ). Náš program má jen jednu úlohu, nazvanou main. Každý program musí mít úlohu nazvanou main (anglicky hlavní), a tato úloha je zpracovávána při stisknutí tlačítka „RUNÿ na RCX. O úlohách se více dočtete v kapitole 6. Úloha se skládá z množství příkazů, také nazývaných programové kroky (anglicky statement). Všechny programové kroky jsou pomocí složených závorek (tedy znaků f a g ) uzavřeny do skupiny, aby bylo jasné, že všechny patří k této úloze. Každý programový krok je ukončen středníkem. Díky tomu je jasné, kde jeden programový krok končí a začíná druhý. Takže v podstatě každá úloha má strukturu jako v příkladu 1.2. task main() { příkaz1; příkaz2; }
Příklad 1.2 - struktura programu v NQC
Program 1.1 má šest programových řádků (kroků). Nyní je probereme jeden po druhém. OnFwd(OUT A); – Tento řádek říká robotu, aby zapnul výstup A (On Forward Output A), tedy motor připojený na výstup označený „Aÿ na RCX pro pohyb vpřed. Bude se pohybovat maximální rychlostí, pokud ji předtím nenastavíte. Později si ukážeme jak toho dosáhnout. OnFwd(OUT C); – Stejný příkaz, pouze pro motor C. Po zpracování těchto dvou řádků běží oba dva motory a robot se pohybuje vpřed. 9
Wait(400); – Nyní je čas chvíli počkat (Wait). řádek říká, aby se počkalo 4 sekundy. Argument4 , tedy číslo mezi závorkami, udává počet „tikůÿ. Každý „tikÿ trvá 1/100 sekundy, takže můžete velice přesně určovat dobu čekání. Teď tedy po 4 sekundy program nic nedělá (tzv. „spíÿ) a robot proto pokračuje v pohybu vpřed. OnRev(OUT A+OUT C); – Robot už ujel dost daleko a tak je čas říci mu, aby jel v opačném směru (On Reverse), tedy zpět. Všimněte si, že můžeme nastavit oba motory najednou s použitím OUT A+OUT C jako argumentu. Podobným způsobem jsme mohli zkombinovat i první dva programové řádky. Wait(400); – Opět se 4 sekundy čeká Off(OUT A+OUT C); – A konečně vypneme (Off ) oba motory. A to je celý program. Točí oběma motory 4 sekundy dopředu, pak 4 sekundy dozadu a nakonec je vypne. Pravděpodobně jste si všimli barevného značení při psaní programu. Barvy se objevují automaticky. Vše. co je v modré barvě, jsou příkazy pro robota, názvy motorů nebo jiných věcí které robot zná. Slovo task je vypsáno tučně, protože je důležitým (rezervovaným) slovem NQC. Další důležitá slova se také objevují tučně, jak později uvidíme. Barvy jsou užitečné, protože už při psaní uvidíme, že jsme neudělali chybu.
1.4
Spuštění programu
Když napíšete program, musíte ho zkompilovat (to znamená přeložit ho do kódu, kterému robot rozumí a který může vykonávat) a zaslat robotovi pomocí infračerveného spojení (proces je nazýván „downloading programuÿ). RCX Command Center má tlačítko, které vše udělá na jedno stisknutí (viz obr. 2). Stiskněte toto tlačítko a, pokud jste neudělali žádnou chybu při přepisování, Váš program bude zkompilován a nahrán do robotu. (Pokud se při překladu (kompilaci) programu vyskytnou chyby, budete na to upozorněni. Bližší informace viz odstavec 1.5. Nyní můžete spustit Váš program. Buď stiskněte zelené tlačítko na RCX, nebo stiskněte tlačítko pro spuštění programu na panelu RCX Command Center (viz obr. 2). Dělá robot to, co jste očekávali? Pokud ne, přívodní kabely jsou pravděpodobně špatně připojeny (podívejte se na obrázek 1).
1.5
Chyby v programu
Při psaní programu přirozeně může dojít k chybám. Překladač chyby rozpozná a ohlásí je na spodní části okna5 podobně jako na obrázku 3. Překladač automaticky označí první chybu (špatně zapsané jméno motoru). Pokud je v hlášeno více chyb, můžeme je rychle zobrazit kliknutím na chybové 4
Někdy též budeme používat termín parametr Autor hovoří o tzv. typo chybách – „přepsání seÿ , tedy psaní „tsakÿ místo „taskÿ atp. Proti logickým chybám – špatnému návrhu programu – Vám sebelepší překladač nepomůže 5
10
Obrázek 3: Příklad chybového hlášení
zprávy. Mějte na paměti, že často jsou chyby uvnitř programu způsobeny chybami na jeho začátku. Takže raději opravte několik prvních chyb a zkompilujte program znovu. Také si všímejte barevného zvýraznění, pomáhá proti překlepům. Například v posledním řádku jsme chybně napsali Of namísto Off. Slovo není zbarveno modře, protože se nejedná o známý příkaz. V programu se také mohou vyskytnout chyby, které překladač nenalezne. Pokud bychom napsali OUT B namísto OUT D v našem příkladu na obr. 3, kompilace by proběhla bez hlášení o chybě, neboť výstup „Bÿ existuje (přestože není v našem robotu využíván). Takže pokud robot vykazuje neočekávané chování, je většinou něco špatně s Vaším programem.
1.6
Nastavení rychlosti
Jak jste si jistě všimli, robot jezdí dost rychle. V základním nastavení se robot pohybuje tak rychle, jak jen může. Pro změnu rychlosti můžete použít příkaz SetPower(výstup,síla), kde „výstupÿ je označení výstupního portu a „sílaÿ je číslo mezi nulou a sedmi. Číslo 7 je pro nejvyšší, nula pro nejnižší rychlost (ale robot se stále bude hýbat). Příklad 1.3 ukazuje novou verzi programu, ve které se robot bude pohybovat pomalu.
1.7
Shrnutí
V této kapitole jste napsali svůj první program v NQC s použitím programu RCX Command Center. Nyní byste měli vědět, jak zapisovat programy, jak je nahrávat do robotů a jak programy spouštět. RCX Command Center má ale mnohem více funkcí. Pro bližší popis si přečtěte dokumentaci k programu. Tento tutoriál se primárně zabývá jazykem NQC a zmiňuje se o funkcích RCX Command Center pouze tehdy, je-li to nezbytně nutné. Dozvěděli jste se některé důležité aspekty jazyka NQC, zejména fakt, že 11
task main() { SetPower(OUT_A+OUT_C,2); OnFwd(OUT_A+OUT_C); Wait(400); OnRev(OUT_A+OUT_C); Wait(400); Off(OUT_A+OUT_C); }
Příklad 1.3 - změna rychlosti
každý program musí mít jednu úlohu nazvanou main, která je vždy spouštěna robotem. Také jste se naučili čtyři nejdůležitější příkazy pro motory: OnFwd(), OnRev(), SetPower() a Off(). Posledním z probraných příkazů je příkaz Wait().
12
2
Zajímavější program
Náš první program nebyl příliš velkolepý. Proto vyzkoušíme něco zajímavějšího. Uděláme to v několika krocích a ukážeme si přitom některé důležité vlastnosti programovacího jazyka NQC.
2.1
Zatáčení
Robota můžete přinutit zatáčet zastavením nebo obrácením směru chodu jednoho z jeho dvou motorů. Příklad 2.1 ukazuje, jak na to. Opište ho, nahrajte do robotu a spusťte. Robot by měl chvíli jet a pak udělat devadesátistupňový obrat vpravo. task main() { OnFwd(OUT_A+OUT_C); Wait(100); OnRev(OUT_C); Wait(85); Off(OUT_A+OUT_C); }
Příklad 2.1 - obrat vpravo
Možná budete muset vyzkoušet trochu jiné číslo než 85 ve druhém příkazu Wait() abyste dosáhli obratu o přesně 90 . To záleží na povrchu, po kterém robot jede. Spíše než změnou jednotlivých hodnot přímo v programu je vhodnější použít pro číslo jméno. V NQC můžeme definovat konstanty způsobem naznačeným v příkladu 2.2. První dva řádky definují dvě konstanty. Ty teď mohou být použity kdekoli v programu. Definování konstant je dobré ze dvou důvodů: Program je čitelnější a je snazší měnit hodnoty konstant. Všimněte si také, že RCX Command Center má pro příkaz #define zvláštní barvu. Jak uvidíme v kapitole 6, můžeme definovat i jiné věci, než jen konstanty.
2.2
Opakování příkazů
Nyní se pokusíme napsat program, který přikáže robotu jezdit po čtverci. Jet po čtverci znamená: jet vpřed, zatočit o 90 stupňů, znovu jet vpřed, opět zatočit o 90 stupňů atd. Mohli bychom čtyřikrát zkopírovat kód programu 2.2, ale výsledku může být dosaženo snáze pomocí příkazu repeat(), jak ukazuje příklad 2.3. Číslo mezi závorkami (v argumentu) příkazu repeat() určuje, kolikrát musí být něco zopakováno. Programové řádky určené k opakování jsou vloženy mezi dvě složené závorky, přesně jako příkazy v úloze. Také si všimněte, jak jsou 13
#define MOVE_TIME #define TURN_TIME
100 85
task main() { OnFwd(OUT_A+OUT_C); Wait(MOVE_TIME); OnRev(OUT_C); Wait(TURN_TIME); Off(OUT_A+OUT_C); }
Příklad 2.2 - využití konstant
jednotlivé programové řádky odsazeny. Není to nutné, ale zvyšuje to přehlednost programu. Jako poslední příklad necháme robot jet desetkrát po čtverci. V příkladu 2.4 je program, který tento úkol řeší. V programu je jeden příkaz repeat() uvnitř druhého. Tuto konstrukci nazýváme vnořený cyklus. Můžete do sebe vkládat repeat cykly, jak se Vám zlíbí. Pouze dávejte pozor na správné uzávorkování a odsazení použité v programu. Úloha začíná s první a končí s poslední složenou závorkou. První repeat cyklus začíná u druhé a končí u páté. Druhý, vnořený repeat cyklus začíná na třetí a končí se čtvrtou závorkou. Jak vidíte, všechny závorky jsou spárované a kód mezi nimi je odsazen.
2.3
Přidávání komentářů
Aby Váš program byl ještě srozumitelnější, je vhodné k němu přidat komentáře. Vložíte-li kdekoliv na řádek dvojznak „//ÿ (dvě lomítka), bude vše za tímto označením překladačem ignorováno a může sloužit jako poznámka či komentář. Víceřádkový komentář může být vložen mezi dvojznaky „/*ÿ a „*/ÿ. Komentáře jsou v RCX Command Center zbarveny zeleně. Hotový program může vypadat třeba jako v příkladu 2.5.
2.4
Shrnutí
V této kapitole jsme se naučili použití příkazu repeat() a použití komentářů. Také jste se seznámili s funkcí složených závorek a s použitím odsazení. S těmito znalostmi již můžete naprogramovat robota k pohybu po libovolné dráze. Je dobrým cvičením vyzkoušet si to a napsat několik variací programů z této části před pokračováním v další kapitole.
14
#define MOVE_TIME #define TURN_TIME
100 85
task main() { repeat(4) { OnFwd(OUT_A+OUT_C); Wait(MOVE_TIME); OnRev(OUT_C); Wait(TURN_TIME); } Off(OUT_A+OUT_C); }
Příklad 2.3 - příklad repeat cyklu
#define MOVE_TIME #define TURN_TIME
100 85
task main() { repeat(10) { repeat(4) { OnFwd(OUT_A+OUT_C); Wait(MOVE_TIME); OnRev(OUT_C); Wait(TURN_TIME); } } Off(OUT_A+OUT_C); }
Příklad 2.4 - vnořený cyklus
15
/*
10 SQUARES Mark Overmars
Tento program nechá robot opsat deset čtverců */ #define MOVE_TIME #define TURN_TIME
100 85
task main() { repeat(10) { repeat(4) { OnFwd(OUT_A+OUT_C); Wait(MOVE_TIME); OnRev(OUT_C); Wait(TURN_TIME); } } Off(OUT_A+OUT_C); }
// čas pro přímý pohyb // čas pro otočení o 90 stupňů
// Udělej 10 čtverců // Každý čteverec má 4 strany // // // //
jeď vpřed čekej pravý pás jede vzad čekej
// Teď vypni motory
Příklad 2.5 - použití komentářů
16
3
Použití proměnných
Proměnné jsou jednou z nejdůležitějších částí každého programovacího jazyka. Proměnné jsou místa v paměti, do kterých můžeme ukládat hodnoty. Můžeme tuto hodnotu použít na různých místech a také ji měnit6 . Nechte mne popsat použití proměnných s pomocí jednoduchého programu.
3.1
Pohyb po spirále
Předpokládejme, že chceme modifikovat program 2.5 tak, aby se robot pohyboval po spirále. Toho můžeme dosáhnout tak, že pokaždé o něco zvětšíme dobu, po kterou pojede robot rovně vpřed. Tedy potřebujeme pokaždé o něco zvýšit hodnotu konstanty MOVE TIME. Ale jak to udělat? MOVE TIME je konstanta a konstanty nemohou být měněny. místo konstanty potřebujeme proměnnou. Proměnné mohou být v NQC snadno definovány, může jich být až 32 7 a mohou mít vlastní jména. Příklad 3.1 ukazuje právě program pro jízdu po spirále. #define TURN_TIME int move_time;
85 // definujeme proměnnou
task main() { move_time = 20; // nastavíme počáteční hodnotu repeat(50) { OnFwd(OUT_A+OUT_C); Wait(move_time); // zde je proměnná použita pro čekání OnRev(OUT_C); Wait(TURN_TIME); move_time += 5; // zvýšíme hodnotu proměnné } Off(OUT_A+OUT_C); }
Příklad 3.1 - jízda po spirále
Zajímavé části kódu jsou okomentovány. Nejprve definujeme proměnnou pomocí klíčového slova int následovaného jménem, které jsme si vybrali. (Obvykle jsou používána malá písmena pro proměnné a velká pro konstanty, ale tato dohoda není závazná a záleží jen na „štábní kultuřeÿ toho kterého programátora. Dodržování těchto konvencí ale činí program srozumitelnějším.) Jméno musí 6
myšleno za běhu programu. To je hlavní rozdíl proti konstantám pevně definovaných příkazem #define. Jejich hodnotu za běhu programu měnit nelze. 7 Tento počet je dán omezeními firmware RCX
17
začínat písmenem, ale může obsahovat číslice a znak „podtržítkoÿ. Žádné jiné symboly nejsou povoleny. (Totéž platí pro konstanty, jména úloh atp.) Podivné slovo int je zkratka za anglické integer, celé číslo. Do této proměnné mohou být uschována pouze celá čísla. Ve druhém okomentovaném řádku přiřazujeme proměnné hodnotu 20. Použijete-li kdekoliv od této chvíle jméno proměnné, znamená to 20. Nyní následuje repeat cyklus, ve kterém používáme proměnnou k určení doby čekání a vždy na konci cyklu zvyšujeme hodnotu této proměnné o 5. Tedy poprvé robot čeká 20 tiků, podruhé 25, potřetí 30 atd. Vedle zvyšování hodnoty proměnné můžeme také násobit proměnnou číslem použitím operátoru *=, odčítat použitím -= a dělit s pomocí /=. (Poznamenejme jen, že výsledek dělení je zaokrouhlen na nejbližší celé číslo.) Také můžete sčítat jednu proměnnou s druhou a vytvářet mnohem komplikovanější výrazy. Několik příkladů je v programu 3.2. int aaa; int bbb,ccc; task main() { aaa = 10; bbb = 20 * 5; ccc = bbb; ccc /= aaa; ccc -= 5; aaa = 10 * (ccc + 3); // aaa je nyní rovno 80 }
Příklad 3.2 - jednoduché matematické výrazy Povšimněte si, jak je ve druhém řádku definováno více proměnných najednou. Také jsme je mohli zkombinovat všechny tři na jeden řádek.
3.2
Náhodná čísla
Ve všech předchozích příkladech jsme přesně určili, co má robot dělat. Vše je ale mnohem zajímavější, když nevíme, co bude robot dělat. Požadujeme určitou nahodilost v jeho pohybech. V NQC můžeme generovat náhodná čísla. Následující program (příklad 3.3) je používá k jízdě robotu náhodným směrem. Robot jede vpřed po náhodnou dobu a pak provede otočení do náhodného směru. Program definuje dvě proměnné a pak jim přiřadí náhodná čísla. Random(60) znamená náhodné číslo mezi 0 a 60 (může to být i 0 i 60). Pokaždé bude hodnota jiná. (Poznamenejme, že při psaní programu jsme se mohli zbavit proměnných použitím konstrukce Wait(Random(60)).) Také zde můžete vidět nový typ cyklu. Místo použití příkazu repeat() jsme napsali while(true). Příkaz while() opakuje příkazy uvedené pod ním do té 18
int move_time, turn_time; task main() { while(true) { move_time = Random(60); turn_time = Random(40); OnFwd(OUT_A+OUT_C); Wait(move_time); OnRev(OUT_A); Wait(turn_time); } }
Příklad 3.3 - náhodný pohyb
doby, dokud je podmínka v závorkách splněna (pravdivá). Speciální slovo true je vždy pravda, takže řádky ve složených závorkách budou opakovány neustále, přesně jak jsme chtěli. Více se o tomto příkazu dočtete v kapitole 4.
3.3
Shrnutí
V této kapitole jste se naučili, jak použít proměnné. Proměnné jsou velice užitečné, ale kvůli omezením daným RCX je jejich využití limitováno. Můžete definovat pouze 32 proměnných a ty mohou obsahovat pouze celá čísla. Ale i to postačuje k řešení mnoha úloh robotiky. Také jsme se naučili vytvářet náhodná čísla, takže můžeme robotovi dát nepředvídatelné chování. Nakonec jsme viděli použití while cyklu k vytvoření nekonečného, stále se opakujícího cyklu.
19
4
Řídící struktury
V předchozích kapitolách jsme viděli příkazy repeat() a while(). Tyto příkazy kontrolují způsob, jakým jsou vykonávány ostatní příkazy v programu. Jsou proto nazývány Řídícími (kontrolními) strukturami. V této kapitole si ukážeme některé další řídící struktury.
4.1
Příkaz if
Někdy potřebujeme, aby určitá část programu byla vykonána pouze v v jisté situaci. V tomto případě použijeme příkaz if()8 . Nechte mě uvést příklad. Opět změníme program se kterým jsme až dosud pracovali, ale nyní to bude novým způsobem. Chceme, aby robot jel nejprve rovně a pak zatočil buď vlevo, nebo vpravo. K tomu opět potřebujeme náhodná čísla. Vezmeme náhodné číslo mezi nulou a jedničkou, tedy to bude buď nula, a nebo jednička. Jestliže bude číslo 0, otočíme se vpravo, jinak se otočíme vlevo. Výsledný kód je v příkladu 4.1. #define MOVE_TIME #define TURN_TIME
100 85
task main() { while(true) { OnFwd(OUT_A+OUT_C); Wait(MOVE_TIME); if (Random(1) == 0) { OnRev(OUT_C); } else { OnRev(OUT_A); } Wait(TURN_TIME); } }
Příklad 4.1 - použití podmínky if Příkaz if() má podobnou strukturu jako while(). Pokud podmínka uvedená v závorkách je pravdivá, provede se část ve složených závorkách. V opačném případě se provede část za klíčovým slovem else9 . Nyní se trochu lépe 8 9
anglicky „ifÿ znamená „ jestližeÿ anglicky „ jinakÿ
20
podíváme na podmínku, kterou jsme použili. Říká Random(1) == 0. To znamená, že Random(1) musí být roven nule, aby byla podmínka splněna (pravdivá). Možná se divíte, proč se používá „==ÿ místo „=ÿ. Důvodem je potřeba rozlišit mezi vkládáním hodnoty do proměnné a testováním rovnosti. Můžeme porovnávat hodnoty několika způsoby. Tady jsou ty nejdůležitější: == < <= > >= !=
je rovno s je menší než je menší nebo rovno než je větší než je větší nebo rovno než není rovno s
Můžete kombinovat jednotlivé podmínky pomocí &&, což znamená „a zároveňÿ, nebo ||, což znamená „neboÿ. Zde jsou některé příklady takových kombinací: true false ttt != 3 (ttt >= 5) && (ttt <= 10) (aaa == 10) || (bbb == 10)
vždy pravda vždy nepravda pravda pokud proměnná ttt není rovna třem pravda pokud hodnota proměnné leží mezi 5 a 10 pravda pokud buď aaa, nebo bbb (nebo oba) jsou rovny 10
Všimněte si, že příkaz if() má dvě části. Část okamžitě za podmínkou, která je vykonána pokud je podmínka pravdivá, a část za klíčovým slovem else, která je vykonána, pokud je podmínka nepravdivá. Klíčové slovo else a následná část nejsou povinné, takže je můžete vynechat, pokud program nemá co dělat v případě nesplnění podmínky.
4.2
Příkaz do
Máme zde další řídící strukturu, cyklus do. Jeho stavba je uvedena v příkladu 4.2. do { příkazy; } while (podmínka);
Příklad 4.2 - struktura cyklu do
21
Příkazy mezi složenými závorkami jsou vykonávány tak dlouho, dokud je podmínka pravdivá (splněna). Podmínka má ten samý tvar, jaký již byl popsán. Příklad 4.3 uvádí takový program. Robot se bude náhodně pohybovat cca 20 sekund a poté se zastaví. int move_time, turn_time, total_time; task main() { total_time = 0; do { move_time = Random(100); turn_time = Random(100); OnFwd(OUT_A+OUT_C); Wait(move_time); OnRev(OUT_C); Wait(turn_time); total_time += move_time; total_time += turn_time; } while (total_time < 2000); Off(OUT_A+OUT_C); }
Příklad 4.3 - využití cyklu do
Všimněte si, že jsme umístili dva příkazy do jednoho řádku. To je povoleno. Můžete umístit tolik příkazů na řádek, kolik chcete, pokud je oddělíte středníky. Takovéto umístění sice zmenšuje soubor, ale nepřidává mu na čitelnosti. Také si uvědomte, že do cyklus má téměř stejné chování jako while cyklus, pouze s tím rozdílem, že while cyklus testuje podmínku před spuštěním příkazů, zatímco v do cyklu jsou příkazy provedeny alespoň jednou10 .
4.3
Shrnutí
V této kapitole jsme se podívali na dvě nové kontrolní struktury: rozhodovací příkaz if() a příkaz cyklu do. Dohromady s příkazy repeat() a while() tvoří skupinu příkazů, které kontrolují běh programu. Je velmi důležité vědět, co přesně dělají. Proto bude lépe, když si sami vyzkoušíte více příkladů předtím, než se pustíte do pokračování. Také jste zjistili, jak vložit více příkazů na jeden řádek.
10
Podmínka je testována až po proběhnutí příkazů.
22
5
Senzory
Jednou z vymožeností Lego robotů je možnost připojit k nim senzory (čidla) a pak na ně nechat roboty reagovat. Předtím, než Vám to budu moci ukázat, musíme k robotu přidat senzor. Postavte tedy čidlo podle vyobrazení na straně 28 ve vaší Constructopedii. Možná budete chtít udělat senzor trochu širší, takže bude vypadat jako tady na obrázku:
Obrázek 4: přední nárazník
Senzor připojte na vstup číslo 1 RCX.
5.1
Čekání na senzor
Začneme s velmi jednoduchým programem, ve kterém robot jede vpřed, dokud do něčeho nenarazí. Příklad 5.1 je oním programem. V programu jsou dva důležité řádky. První řádek programu říká, který typ senzoru používáme. SENSOR 1 je číslo vstupu, ke kterému je senzor připojen. Další dva vstupy se jmenují SENSOR 2 a SENSOR 3. Údaj SENSOR TOUCH říká, že se jedná o dotykový senzor11 . Pro světelné čidlo bychom použili označení SENSOR LIGHT. Poté, co jsme specifikovali typ senzoru, program zapne oba motory a robot se začne pohybovat vpřed. Dalším programovým krokem je velmi užitečná konstrukce. Program v daném místě čeká, dokud podmínka v závorkách nebude splněna. Tato podmínka říká, že hodnota senzoru SENSOR 1 musí 11
Z anglického touch - dotýkat se
23
task main() { SetSensor(SENSOR_1,SENSOR_TOUCH); OnFwd(OUT_A+OUT_C); until (SENSOR_1 == 1); Off(OUT_A+OUT_C); }
Příklad 5.1 - čekání na událost od senzoru
být rovna 1, což znamená, že bylo čidlo stlačeno. Dokud se senzor ničeho nedotýká, je hodnota 0. Takže tento příkaz čeká, dokud se čidlo něčeho nedotkne. Potom vypneme motory a program se ukončí.
5.2
Práce s dotykovým senzorem
Nyní zkusíme naprogramovat robot k vyhýbání se překážkám. Kdykoli robot do něčeho narazí, necháme ho pohnout se maličko zpět, trochu se otočit a pak pokračovat. Program je uveden v příkladu 5.2. task main() { SetSensor(SENSOR_1,SENSOR_TOUCH); OnFwd(OUT_A+OUT_C); while (true) { if (SENSOR_1 == 1) { OnRev(OUT_A+OUT_C); Wait(30); OnFwd(OUT_A); Wait(30); OnFwd(OUT_A+OUT_C); } } }
Příklad 5.2 - vyhýbání se překážkám
Podobně jako v předchozím příkladě, nastavíme nejprve typ senzoru. Poté se robot začne pohybovat vpřed. V nekonečném cyklu neustále testujeme, jestli je senzor stlačen, a pokud ano, pohybujeme se 1/3 sekundy zpět, 1/3 sekundy se otáčíme vpravo a pak opět pokračujeme vpřed. 24
5.3
Světelné senzory
Vedle dotykových senzorů také se stavebnicí MindStorms dostanete světelný senzor. Tento senzor měří množství světla přicházející z určitého směru. Světelný senzor také světlo vysílá. Díky tomu je možno namířit jej na nějaký objekt a změřit jeho odrazivost — množství světla, které se odrazí zpět k senzoru. To je velmi užitečné, když satvíme robota na sledování čáry na podlaze. To je přesně to, o co se pokusíme v následujícím příkladu. Nejprve ale potřebujeme připojit světelný senzor k robotu tak, aby byl uprostřed robotu, vpředu a mířil k zemi. Připojte jej ke vstupu 2. Můžete udělat konstrukci podobnou té na obrázku 5:
Obrázek 5: detail uchycení světelného čidla
Také budeme potřebovat závodní dráhu dodávanou s RIS stavebnicí (Velký list papíru s černým oválem.) Princip programu je následující: Robot se neustále ujišťuje, že je nad dráhou (černou čárou). Kdykoli intenzita odraženého světla vzroste, světelný senzor svítí na bílý papír a je tedy mimo dráhu. Musíme proto upravit směr. Program pro sledování čáry demonstruje příklad 5.3. Je sice velmi jednoduchý, funguje ale pouze při jízdě ve směru hodinových ručiček. Program nejprve nastaví vstup číslo 2 pro světelný senzor. Potom zapne pohyb vpřed a spustí nekonečný cyklus. Kdykoli je hodnota čtená ze světelného senzoru větší než 40 12 , zapneme pohyb zpět na jednom motoru a čekáme, dokud nejsme zpět na dráze. 12 Použili jsme konstantu, takže můžeme hodnotu snadno změnit, jelikož velmi závisí na okolním světle.
25
#define THRESHOLD 40 // prahová hodnota světla task main() { SetSensor(SENSOR_2,SENSOR_LIGHT); OnFwd(OUT_A+OUT_C); while (true) { if (SENSOR_2 > THRESHOLD) { OnRev(OUT_C); Wait(10); until (SENSOR_2 <= THRESHOLD); OnFwd(OUT_A+OUT_C); } } }
Příklad 5.3 - sledování čáry
Jak uvidíte při zkoušení programu, pohyb robota není příliš plynulý. Zkuste přidat příkaz Wait(10) před příkaz until() pro plynulejší pohyb robotu. Ještě zbývá poznamenat, že program nefunguje pro pohyb proti směru hodinových ručiček. Pro následování libovolné křivky je zapotřebí mnohem komplikovanějšího programu.
5.4
Shrnutí
V této kapitole jste viděli, jak pracovat s dotykovými a světelnými čidly. Také jsme se seznámili s příkazem until(), který je užitečný při práci se senzory. V této chvíli Vám doporučuji si samostatně napsat několik programů. Máte nyní všechny potřebné znalosti, abyste své roboty vybavili komplikovaným chováním. Například zkuste nasadit na předek robota dva dotykové senzory, jeden vlevo a jeden vpravo, a naprogramujte ho na pohyb od překážky, do které narazil. Také zkuste naprogramovat robota, aby zůstal uvnitř černě orámované oblasti na podlaze.
26
6
Úlohy a podprogramy
Až doposud všechny naše programy sestávaly z jedné úlohy. NQC programy ale mohou mít několik úloh. Také je možné vložit části kódu do takzvaných podprogramů, které pak můžete opakovaně použít na různých místech programu. Používání úloh a podprogramů činí Váš program srozumitelnějším a kompaktnějším. V této kapitole se podíváme na různé možnosti tohoto použití.
6.1
Úlohy
Program v NQC může sestávat až z deseti úloh. Každá úloha musí mít své (jedinečné) jméno, přičemž jedna se musí jmenovat main. Tato úloha je spouštěna při stisknutí zeleného „runÿ tlačítka na RCX. Ostatní úlohy budou spuštěny, pouze pokud je spustí již probíhající úloha pomocí příkazu start. Od tohoto okamžiku poběží obě úlohy současně (první úloha tedy pokračuje v běhu). Probíhající úloha také může ukončit jinou probíhající úlohu pomocí příkazu stop. Později může být tato úloha znovu spuštěna, ale pak začne probíhat znovu od začátku a nikoli z bodu, kde byla ukončena. Nyní budu demonstrovat použití úloh. Znovu nasaďte na robota dotykový senzor. Chceme napsat program, ve kterém se robot bude pohybovat ve čtvercích podobně jako v příkladu 2.4. Když ale robot narazí do překážky, měl by na ni reagovat. To se obtížně řeší v jedné úloze, protože robot musí dělat dvě věci najednou: jezdit dokola (tedy zapínat a vypínat motory ve správný čas) a hlídat senzory. Proto je lépe použít dvě úlohy. Jednu pro pohyb po čtverci a druhou pro reagování na senzory. Program najdete v příkladu 6.1. Hlavní úloha pouze nastaví typ senzoru a pak spustí obě další úlohy. Poté je běh hlavní úlohy ukončen. Úloha move square řídí robot při jízdě po čtvercích. Úloha check sensors hlídá, zda došlo ke stlačení dotykového čidla. Pokud ano, provede následující akce: Nejprve zastaví běh úlohy move square. To je velmi důležité, protože jinak by se snažily robot řídit obě úlohy. Nyní úloha check sensors přebírá kontrolu nad pohyby robota. Následně robotem trochu zacouvá a maličko pootočí. Nyní můžeme znovu spustit úlohu move square a nechat robot znovu jezdit po čtvercích. Je velmi důležité mít stále na paměti, že úlohy, které spustíte, běží současně. To může vést k neočekávaným výsledkům. Kapitola 10 detailně vysvětluje tyto problémy a ukazuje možná řešení.
6.2
Podprogramy
Někdy používáte jednu část kódu na mnoha místech v programu. V tomto případě můžete tento kousek vložit do podprogramu a pojmenovat ho. Nyní jej můžete spustit zavoláním jeho jména z libovolného místa úlohy. NQC (přesněji řečeno RCX) dovoluje použít maximálně 8 podprogramů. Podívejme se na příklad 6.2. V tomto programu jsme definovali podprogram, který robotem otáčí kolem osy. Hlavní úloha volá tento podprogram třikrát. Všimněte si, jak voláme podprogram napsáním jeho jména se závorkami za jménem, takže to vypadá 27
podobně jako volání příkazů. Jediným rozdílem je, že podprogram nemá žádné parametry a tak mezi závorkami nic není. Nyní je na místě uvést několik varování. Podprogramy se chovají trochu podivně. Například, podprogramy nemohou být volány z jiných podprogramů. Podprogramy mohou být volány z různých úloh, ale nedoporučuje se to. Velmi snadno to může vést k problémům, protože jeden podprogram by mohl být spuštěn ve stejný okamžik dvěma odlišnými úlohami. Tento stav vytváří nežádoucí efekty. Také, voláme-li podprogram z různých úloh, nemůžeme kvůli omezením firmware RCX používat komplikované výrazy. Tedy, pokud velice dobře nevíte co děláte, nikdy nevolejte podprogram z různých úloh!
6.3
Inline funkce
Jak jsem se již zmínil, použití podprogramů ssebou nese některá úskalí. Jejich přínosem je fakt, že jsou uloženy v paměti pouze jednou. To šetří paměť což je velmi užitečné, protože RCX nemá příliš mnoho volné paměti. Pokud jsou ale podprogramy velice krátké13 , je lepší použít inline (vložené) funkce. Ty nejsou ukládány samostatně, ale při překladu jsou vkopírovány na všechna místa, kde mají být použity. To sice stojí více paměti, ale odpadají potíže s vyhodnocováním komplikovaných výrazů atp. Inline funkcí může být neomezený počet14 . Práce s inline funkcemi je naprosto stejná jako s podprogramy. Pouze se při deklaraci použije slovo void namísto sub. (Slovo void je použito, protože totéž slovo používá i jazyk C.) Předchozí příklad zapsaný s použitím inline funkcí tedy bude vypadat podobně jako v příkladu 6.3. Inline funkce mají oproti podprogramům ještě jednu přednost. Mohou mít argumenty (parametry). Argument může být použit k tomu, aby funkci předal hodnotu určité proměnné. Například předpokládejme, že čas otáčení bude v předchozím příkladě předáván jako argument funkce. Příklad 6.4 ukazuje, jak je toho dosaženo. Povšimněte si, jak se v závorkách za jménem funkce definuje argument (argumenty) funkce. V tomto případě je zadáno, že je parametrem celé číslo (máme ještě další volby) a že toto číslo je doba otáčení. Pokud používáme více argumentů, musíme je oddělit čárkou.
6.4
Definování maker
Je tu ještě jeden způsob, jak pojmenovat malé části kódu. V NQC můžete definovat makra (nepleťte si je s makry v RCX Command Center). Způsob, jakým se s pomocí #define definují konstanty, jsme již viděli dříve (viz kapitola 2). Ve skutečnosti takto můžeme nadefinovat libovolnou část kódu. V příkladu 6.5 je ještě jednou tentýž program, nyní zapsaný s použitím maker. Slovo turn around uvedené za příkazem #define zastupuje text uvedený za ním. Nyní, kdekoli vepíšete turn around, bude toto slovo nahrazeno zastupovaným textem. Poznamenejme jen, že text by měl být na jednom řádku. (Ve 13 14
typicky se doporučuje do několika příkazů jsme ale stále limitováni velikostí paměti RCX
28
skutečnosti existují způsoby, jak psát víceřádkové #define příkazy, ale nedoporučuje se to.) Definiční příkaz je ve skutečnosti ještě mocnější. Může totiž pracovat i s argumenty. Například můžeme uvést dobu otáčení jako argument příkazu. Příklad 6.6 ukazuje program, ve kterém definujeme čtyři makra: jedno pro jízdu vpřed, druhé pro couvání, třetí pro otáčení vlevo a poslední pro otáčení robotu vpravo. Každé makro má dva argumenty: rychlost a čas. Pozor! Všechny definice maker uvedené v příkladu 6.6 je nutno zapsat do jednoho řádku! V textu musely být rozděleny, neboť přesáhly délku tisknutelného řádku. Je velice užitečné definovat si takováto makra. Výsledkem je kompaktní a čitelný kód. Také můžete velmi snadno změnit Váš kód, když se například změní připojení k motorům.
6.5
Shrnutí
V této kapitole jsme viděli použití úloh, podprogramů, inline funkcí a maker. Mají různé použití. Úlohy obvykle běží současně a starají se o různé věci, které mají být udělány ve stejnou dobu. Podprogramy jsou užitečné, když je potřeba použít velkou část kódu na několika různých místech jedné úlohy. Inline funkce jsou užitečné, když používáme části kódu na mnoha různých místech v různých úlohách, ale zabírají více paměti. A konečně makra jsou užitečná pro malé části kódu používané na různých místech programu. Makra mohou mít parametry, což ještě zvyšuje jejich užitečnost. Nyní, když jste se propracovali všemi kapitolami až sem, máte všechny znalosti potřebné k programování komplikovaných úloh pro vaše roboty. Následující kapitoly tohoto tutoriálu Vás naučí techniky důležité pouze pro určité aplikace.
29
task main() { SetSensor(SENSOR_1,SENSOR_TOUCH); start check_sensors; start move_square; } task move_square() { while (true) { OnFwd(OUT_A+OUT_C); Wait(100); OnRev(OUT_C); Wait(85); } } task check_sensors() { while (true) { if (SENSOR_1 == 1) { stop move_square; OnRev(OUT_A+OUT_C); Wait(50); OnFwd(OUT_A); Wait(85); start move_square; } } }
Příklad 6.1 - víceúlohový program
30
sub turn_around() { OnRev(OUT_C); Wait(340); OnFwd(OUT_A+OUT_C); } task main() { OnFwd(OUT_A+OUT_C); Wait(100); turn_around(); Wait(200); turn_around(); Wait(100); turn_around(); Off(OUT_A+OUT_C); }
Příklad 6.2 - použití podprogramu
void turn_around() { OnRev(OUT_C); Wait(340); OnFwd(OUT_A+OUT_C); } task main() { OnFwd(OUT_A+OUT_C); Wait(100); turn_around(); Wait(200); turn_around(); Wait(100); turn_around(); Off(OUT_A+OUT_C); }
Příklad 6.3 - inline funkce
31
void turn_around(int turntime) { OnRev(OUT_C); Wait(turntime); OnFwd(OUT_A+OUT_C); } task main() { OnFwd(OUT_A+OUT_C); Wait(100); turn_around(200); Wait(200); turn_around(50); Wait(100); turn_around(300); Off(OUT_A+OUT_C); }
Příklad 6.4 - inline funkce s parametrem
#define turn_around OnRev(OUT_C); Wait(340);OnFwd(OUT_A+OUT_C); task main() { OnFwd(OUT_A+OUT_C); Wait(100); turn_around; Wait(200); turn_around; Wait(100); turn_around; Off(OUT_A+OUT_C); }
Příklad 6.5 - využití makra
32
#define turn_right(s,t) SetPower(OUT_A+OUT_C,s);OnFwd(OUT_A); OnRev(OUT_C);Wait(t); #define turn_left(s,t) SetPower(OUT_A+OUT_C,s);OnRev(OUT_A); OnFwd(OUT_C);Wait(t); #define forwards(s,t) SetPower(OUT_A+OUT_C,s); OnFwd(OUT_A+OUT_C);Wait(t); #define backwards(s,t) SetPower(OUT_A+OUT_C,s); OnRev(OUT_A+OUT_C);Wait(t); task main() { forwards(3,200); turn_left(7,85); forwards(7,100); backwards(7,200); forwards(7,100); turn_right(7,85); forwards(3,200); Off(OUT_A+OUT_C); }
Příklad 6.6 - makra s parametry
33
7
Hraní hudby
RCX má vestavěný reproduktor schopný vydávat zvuky a dokonce hrát jednoduchou hudbu. To je užitečné zejména když chcete, aby Vám RCX řeklo, že se něco děje. Také ale může být zábavné mít robota hrajícího hudbu, zatímco jezdí kolem.
7.1
Vestavěné zvuky
RCX má šest vestavěných zvuků, očíslovaných od 0 do 5. Znějí následovně: 0 1 2 3 4 5
kliknutí klávesy dvojí pípnutí Zvuk se snižující se frekvencí Zvuk se zvyšující se frekvencí Chybový zvuk Rychlý zvyšující se zvuk
Tyto zvuky můžete zahrát pomocí příkazu PlaySound(). Příklad 7.1 ukazuje krátký program, který je postupně zahraje všechny: task main() { PlaySound(0); PlaySound(1); PlaySound(2); PlaySound(3); PlaySound(4); PlaySound(5); }
Wait(100); Wait(100); Wait(100); Wait(100); Wait(100); Wait(100);
Příklad 7.1 - hraní zvuků vestavěných v RCX
Možná se divíte, proč zde jsou všechny ty příkazy pro čekání (příkazy wait()). Důvod je ten, že RCX při provádění příkazu, který hraje zvuk, nečeká na dokončení (doznění zvuku). Okamžitě vykoná další příkaz. RCX má malou zásobní paměť (tzv. buffer) ve které uschovává zvuky čekající na přehrání, ale po chvíli se tento buffer naplní a zvuky se ztratí. V případě zvuků to není tak kritické. Mnohem důležitější je to při hraní hudby, jak uvidíme za chvíli. Ještě zbývá doplnit, že argumentem příkazu PlaySound() musí být konstanta. Nelze tam vložit proměnnou15 15
V nové verzi RCX označované 2.0 již to možné je.!)
34
Tón G# G F# F E D# D C# C B A# A
1 52 49 46 44 41 39 37 35 33 31 29 28
2 104 98 92 87 82 78 73 69 65 62 58 55
3 208 196 185 175 165 156 147 139 131 123 117 110
Oktáva 4 5 415 831 392 784 370 740 349 698 330 659 311 622 294 587 277 554 262 523 247 494 233 466 220 440
6 1661 1568 1480 1397 1319 1245 1175 1109 1047 988 932 880
7 3322 3136 2960 2794 2637 2489 2349 2217 2093 1976 1865 1760
8
4186 3951 3729 3520
Tabulka 1: frekvence různých tónů
7.2
Hraní hudby
Pro tvorbu zajímavější hudby má NQC příkaz PlayTone(). Ten má dva argumenty. První udává frekvenci, druhý délku tónu (v „ticíchÿ dlouhých 1/100 sekundy podobně, jako v příkazu wait). Tabulka 1 zobrazuje užitečné frekvence. Jak jsme již uvedli v odstavci o zvucích, ani zde RCX nečeká, až nota skončí. Takže pokud hrajete hodně not za sebou, přidejte (raději o něco delší) příkaz wait() mezi nimi. Program je v příkladu 7.2. task main() { PlayTone(262,40); PlayTone(294,40); PlayTone(330,40); PlayTone(294,40); PlayTone(262,160); }
Wait(50); Wait(50); Wait(50); Wait(50); Wait(200);
Příklad 7.2 - hraní jednoduché melodie
Velmi snadno se dají vytvářet různé melodie pomocí nástroje RCX Command Center nazvaného RCX Piano. Pokud chcete, aby RCX hrálo hudbu při jízdě, raději pro to použijte zvláštní úlohu. Příklad 7.3 je ukázkou poměrně hloupého programu, ve kterém RCX jezdí vpřed a vzad a stále hraje hudbu. 35
task music() { while (true) { PlayTone(262,40); PlayTone(294,40); PlayTone(330,40); PlayTone(294,40); } }
Wait(50); Wait(50); Wait(50); Wait(50);
task main() { start music; while(true) { OnFwd(OUT_A+OUT_C); Wait(300); OnRev(OUT_A+OUT_C); Wait(300); } }
Příklad 7.3 - jízda za doprovodu hudby
7.3
Shrnutí
V této kapitole jste se naučili, jak přinutit RCX hrát zvuky a hudbu. Také jste viděli, jak použít samostatnou úlohu pro hudbu.
36
8
Více o motorech
Existuje více příkazů pro preciznější kontrolu činnosti motorů. V této kapitole je probereme.
8.1
Brzdíme něžně
Použijete-li příkaz Off(), motor se zastaví okamžitě za pomoci brzdy. V NQC je také možno zastavit motor jemnějším způsobem, bez použití brzdy. K tomu slouží příkaz Float(). Občas to pro Vaše záměry může být výhodnější. Program najdete v příkladu 8.1. Nejprve robot zastaví pomocí brzd, podruhé bez nich. Pořádně si všimněte rozdílu. (Ve skutečnosti je u tohoto robota rozdíl minimální, ale u jiných může být velký16 ) task main() { OnFwd(OUT_A+OUT_C); Wait(200); Off(OUT_A+OUT_C); Wait(100); OnFwd(OUT_A+OUT_C); Wait(200); Float(OUT_A+OUT_C); }
Příklad 8.1 - porovnání rozdílu při brzdění
8.2
Rozšířené příkazy
Příkaz OnFwd() ve skutečnosti dělá dvě věci: zapne motor a nastaví směr otáčení dopředu. Příkaz OnRew() také dělá dvě věci: zapne motor a nastaví směr otáčení dozadu. NQC také obsahuje příkazy pro provedení těchto úkolů samostatně. Pokud si přejete změnit pouze jedno z těchto nastavení (směr otáčení nebo zapnuto/vypnuto), je efektivnější použít tyto zvláštní příkazy. Zabírají méně paměti v RCX, jsou vykonány rychleji a výsledkem jsou plynulejší pohyby. Jedním ze samostatných příkazů je SetDirection() s možnými nastaveními OUT FWD pro pohyb vpřed, OUT REV pro pohyb vzad a OUT TOGGLE, který změní současný směr na opačný. Druhým pak SetOutput() s módy OUT ON pro zapnutí napájení, OUT OFF pro vypnutí napájení a zabrzdění motorku a OUT FLOAT pro vypnutí napájení s pomalým doběhem. V příkladu 8.2 najdete jednoduchý program, který pohybuje robotem vpřed a vzad. 16 vyzkoušejte tentýž program pro Acrobot z Constructopedie. Obecně jakýkoli robot s velkými koly poskytuje dobrý výsledek při porovnávání rozdílů mezi Off() a Float()
37
task main() { SetPower(OUT_A+OUT_C,7); SetDirection(OUT_A+OUT_C,OUT_FWD); SetOutput(OUT_A+OUT_C,OUT_ON); Wait(200); SetDirection(OUT_A+OUT_C,OUT_REV); Wait(200); SetDirection(OUT_A+OUT_C,OUT_TOGGLE); Wait(200); SetOutput(OUT_A+OUT_C,OUT_FLOAT); }
Příklad 8.2 - příkazy pro ovládání motorů
Poznamenejme, že při startu všech programů jsou všechny motory nastaveny pro pohyb vpřed rychlostí 7, takže první dva příkazy v tomto programu nejsou nutné. Existují ještě další příkazy ovládající motory. Jejich názvy jsou zkratkami výše uvedených příkazů. Tabulka 2 podává jejich úplný přehled. On(motory) Off(motory) Float(motory Fwd(motory) Rev(motory) Toggle(motory) OnFwd(motory) OnRev(motory) OnFor(motory,doba) SetOutput(motory, režim)
zapne napájení motorů uvedených v motory vypne napájení motorů a zapne brzdu vypne napájení motorů s pomalým doběhem nastaví motory pro otáčení vpřed (nezapne napájení) nastaví motory pro pohyb vzad (nezapne napájení) přepne směr otáčení pro motory (zepředu dozadu a naopak) nastaví motory pro otáčení vpřed, zapne napájení nastaví motory pro otáčení vzad, zapne napájení zapne napájení pro motory po doba tiků nastaví pro motory jeden z režimů OUT ON, OUT OFF, OUT FLOAT
Tabulka 2: seznam příkazů pro ovládání motorů
8.3
Změna rychlosti motoru
Jak jste si již pravděpodobně všimli, změna rychlosti otáčení motoru příkazem SetPower() nemá velký efekt. Důvodem je skutečnost, že ve skutečnosti měníme krouticí moment, nikoliv rychlost. Rozdíl poznáte, pouze když bude 38
motor velmi zatížen. Ale i tehdy bude rozdíl mezi druhým a sedmým stupněm minimální. Pokud chcete dosáhnout lepších výsledků, klíčem je zapínat a vypínat motor ve velmi krátkých po sobě jdoucích časových intervalech. Krátký program, který to dělá, je uveden v příkladu 8.3. Má jednu úlohu nazvanou run motor která řídí motory. Ta neustále kontroluje proměnnou speed aby určila, jaká je aktuální rychlost. Kladné číslo znamená vpřed, záporné vzad. Pak nastaví motory pro otáčení do správného směru a čeká určitou dobu v závislosti na rychlosti předtím, než motory znovu vypne. Hlavní úloha prostě nastaví rychlost a čeká.
int speed, __speed; task run_motor() { while (true) { __speed = speed; if (__speed > 0) {OnFwd(OUT_A+OUT_C);} if (__speed < 0) {OnRev(OUT_A+OUT_C); __speed = -__speed;} Wait(__speed); Off(OUT_A+OUT_C); } } task main() { speed = 0; start run_motor; speed = 1; Wait(200); speed = -10; Wait(200); speed = 5; Wait(200); speed = -2; Wait(200); stop run_motor; Off(OUT_A+OUT_C); }
Příklad 8.3 - změna rychlosti otáčení motorů
Program může být napsán ještě mnohem lépe, s podporou otáčení atp. Přidáním čekání za příkazem Off() také docílíte lepších výsledků. Vyzkoušejte si to sami. 39
8.4
Shrnutí
V této kapitole jste se dozvěděli o dalších příkazech, sloužících k ovládání motorů: Float(), který motory zastaví jemně, SetDirection() pro nastavení směru otáčení (možnosti jsou OUT FWD, OUT REV a OUT TOGGLE, což je obrácení chodu) a nakonec SetOutput(), který nastavuje režimy výstupu (OUT ON, OUT OFF nebo OUT FLOAT). Viděli jste seznam všech dostupných příkazů ovládajících motory. Také jste se naučili trik, kterým můžete lépe ovládat rychlost motorů.
40
9
Více o senzorech
V kapitole 5 jsme diskutovali základní aspekty použití senzorů. To ale nebylo vše. Se senzory můžete dělat mnohem více. V této kapitole probereme rozdíl mezi typem senzoru a režimem práce senzoru, uvidíme, jak použít rotační senzor (senzor, který není součástí RIS, ale může být zakoupen samostatně a je velmi užitečný), ukážeme si způsob, jak použít více než tři senzory a jak si vyrobit detektor překážek.
9.1
Typy senzorů a režimy práce
Příkaz SetSensor(), se kterým jsme se již seznámili, ve skutečnosti dělá dvě věci: Nastaví typ senzoru a a nastaví režim, ve kterém senzor pracuje. Samostatným nastavením režimu a typu můžete precizněji kontrolovat práci senzoru a to je pro některé aplikace užitečné. Typ senzoru se nastavuje pomocí příkazu SetSensorType(). Celkem máme čtyři různé typy: SENSOR TYPE TOUCH pro dotykový (tlakový) senzor, dále pak SENSOR TYPE LIGHT pro světelný senzor, SENSOR TYPE TEMPERATURE, což je teplotní senzor (tento senzor není součástí RIS, ale je možno jej dokoupit) a SENSOR TYPE ROTATION pro rotační senzor (Také není součásti RIS a také je možno jej dokoupit). Nastavení typu senzoru je obzvlášť důležité kvůli indikaci, zda senzor potřebuje být napájen (např. pro světélko u světelného senzoru). Nastavení jiného typu senzoru, než je ve skutečnosti připojen, pravděpodobně nemá využití a může být spíše nebezpečné (poškození senzoru nebo RCX). Režim práce senzoru může být nastaven příkazem SetSensorMode(). existuje osm různých režimů práce. Nejdůležitější z nich je tzv. přímý režim SENSOR MODE RAW. Při získávání údajů ze senzoru v tomto režimu dostaneme čísla mezi 0 a 1023. Jsou to nezpracované (hrubé) hodnoty vyprodukované přímo senzorem. Co to znamená, záleží na typu senzoru. Například máme-li tlakový senzor, dostáváme hodnoty blízko číslu 1023 pokud není stlačen. Při stlačeném senzoru je hodnota kolem 50, pro částečně stlačený senzor se pak hodnoty pohybují mezi 50 a 1000. Pokud tedy nastavíte tlakový senzor na přímý režim, můžete zjistit, zda je stlačen pouze částečně. Získáváte-li data ze světelného senzoru, dostáváte něco mezi 300 (velmi světlé) do 800 (velmi tmavé). To Vám dává mnohem přesnější hodnoty než použití příkazu SetSensor(). Druhý režim je SENSOR MODE BOOL – (booleovský, binární). V tomto režimu jsou výstupní hodnoty buď 0 nebo 1. Pokud hrubá hodnota přesáhne přibližně číslo 550, je výstupem nula, v opačném případě jednička. SENSOR MODE BOOL je výchozím nastavením pro dotykový senzor. Režimy SENSOR MODE CELSIUS a SENSOR MODE FAHRENHEIT jsou užitečné pouze ve spojení s teplotními čidly a poskytují údaje o teplotě v požadovaných jednotkách (stupních Celsia nebo Fahrenheita). SENSOR MODE PERCENT přemění hrubou hodnotu na číslo mezi 0 a 100. Každá hrubá hodnota pod 400 je přiřazena k hodnotě 100 %. Jak se hrubá hodnota zvyšuje, hodnota v procentech postupně klesá k nule. SENSOR MODE PERCENT je výchozí režim pro světelný senzor. Režim senzoru SENSOR MODE ROTATION se zdá být užitečným pouze pro rotační senzor (viz odstavec 9.2 dále v této kapitole). 41
Dalšími dvěma zajímavými režimy jsou SENSOR MODE EDGE – detektor hran a SENSOR MODE PULSE – detektor pulsů. Počítají přechody, tedy změny z nízkých do vysokých hrubých hodnot nebo naopak. Například, pokud stlačíte dotykové čidlo, způsobíte přechod z z vysokých do nízkých hrubých hodnot. Když ho pak uvolníte, získáte přechod v opačném směru. Pracuje-li senzor v režimu SENSOR MODE PULSE, jsou započítány pouze přechody z nízkých hodnot do vysokých. Takže každý stisk a uvolnění dotykového čidla je počítáno jako jedna událost. Pokud nastavíte senzor do režimu SENSOR MODE EDGE, budou započítány oba přechody (jak stisknutí, tak uvolnění). Tedy každý stisk a uvolnění dotykového senzoru se počítá za dva. Pomocí toho můžete počítat, kolikrát byl stisknut dotykový senzor, nebo jej můžete použít v kombinaci se světelným senzorem ke zjištění kolikrát byla zapnuta a vypnuta (silná) lampa. Přirozeně, počítáme-li věci, měli bychom být schopni nastavit čítač zpět na nulu. K tomu slouží příkaz ClearSensor(). Ten vymaže počítadlo zvoleného senzoru. Nyní se podívejme na příklad 9.1. Program v něm používá dotykový senzor k ovládání robotu. Připojte dotykový senzor pomocí dlouhého kabelu na vstup 1. Pokud stisknete senzor rychle dvakrát za sebou, robot pojede vpřed. Stisknete-li jej jednou, přestane se pohybovat. task main() { SetSensorType(SENSOR_1,SENSOR_TYPE_TOUCH); SetSensorMode(SENSOR_1,SENSOR_MODE_PULSE); while(true) { ClearSensor(SENSOR_1); until (SENSOR_1 >0); Wait(100); if (SENSOR_1 == 1) {Off(OUT_A+OUT_C);} if (SENSOR_1 == 2) {OnFwd(OUT_A+OUT_C);} } }
Příklad 9.1 - režim práce senzoru Všimněte si, že nejprve nastavíme typ senzoru a pak teprve režim práce. Vypadá to, že to je velice důležité, neboť změna typu také ovlivní mód.
9.2
Rotační senzor
Rotační senzor je velice užitečný typ senzoru který naneštěstí není součástí standardního RIS. Může ale být zakoupen samostatně od firmy Lego. Rotační senzor je kostka s otvorem, kterým se dá protáhnout osička. Rotační senzor pak měří úhel, o který je osou otočeno. Přesnost měření je 22,5 . Jedna celá otáčka tedy dává 16 kroků (nebo 16, pokud otáčíte osou opačným směrem). 42
Použitím rotačních senzorů můžete přinutit robot dělat velmi přesně řízené pohyby. Můžete nechat osičkou otočit přesně tolikrát, kolik potřebujete. Pokud potřebujete přesnější kontrolu než na 16 kroků, můžete ji zpřevodovat do rychla a pak ji použít pro počítání kroků. Standardním využitím je mít dva rotační senzory připojeny na dvě kola robotu kontrolovaných samostatnými motory. Pro opravdu přímý pohyb je třeba, aby se obě kola otáčela přesně stejně rychle. Naneštěstí, motory se obvykle neotáčejí stejnou rychlostí. Pomocí rotačních senzorů můžeme vidět, že se jedno kolo otáčí rychleji. Můžete pak dočasně zastavit jeho motor (nejlépe pomocí Float()), dokud oba senzory opět nedávají stejnou hodnotu. Následující program (příklad 9.2) to dělá. Prostě nechá robot jet po přímce. Pro vyzkoušení nasaďte dva rotační senzory na osy kol a připojte vodiče ke vstupům 1 a 3. task main() { SetSensor(SENSOR_1,SENSOR_ROTATION); ClearSensor(SENSOR_1); SetSensor(SENSOR_3,SENSOR_ROTATION); ClearSensor(SENSOR_3); while (true) { if (SENSOR_1 < SENSOR_3) {OnFwd(OUT_A); Float(OUT_C);} else if (SENSOR_1 > SENSOR_3) {OnFwd(OUT_C); Float(OUT_A);} else {OnFwd(OUT_A+OUT_C);} } }
Příklad 9.2 - práce s rotačním senzorem Program nejprve nastaví oba senzory jako rotační senzory, pak resetuje jejich počítadla. Poté spustí nekonečný cyklus. V cyklu testujeme, zda hodnoty čtené ze senzorů jsou stejné. Pokud ano, robot se pohybuje vpřed. Pokud je jedna větší, je správný motor zastaven dokud nejsou obě hodnoty opět stejné. Přirozeně je toto velmi jednoduchý program. Můžete ho rozšířit pro ujetí přesných vzdáleností nebo nechat robot dělat velmi přesné obrátky.
9.3
Připojení více senzorů na jeden vstup
RCX má pouze tři vstupy, tedy k němu můžete připojit pouze tři senzory. Chcete-li postavit komplikovanější roboty (a máte senzory navíc), nemusí Vám to stačit. Naštěstí, pomocí několika triků můžete připojit dva (nebo více) senzorů na jeden vstup. Nejjednodušší je připojit dva dotykové senzory na jeden vstup. Pokud je jeden z nich (nebo oba) stlačen, hodnota bude 1, v opačném případě 0. Nemů43
žete sice rozlišit, který senzor byl stlačen, ale někdy to není nutné. Například když jeden senzor dáte dopředu a druhý na záď robotu, zjistíte, který senzor byl stlačen podle toho, kterým směrem robot jede. Také ale můžete senzory nastavit na přímý režim (viz odstavec 9.1). Nyní můžete získat mnohem více informací. Pokud máte štěstí, každý senzor dává malinko jinou hodnotu je-li stlačen. V tomto případě můžete skutečně rozlišit, který senzor byl stlačen. Pokud jsou stlačeny oba, dostanete mnohem nižší hodnotu (typicky kolem 30), takže můžete zjistit i toto. Také můžete připojit dotykový a světelný senzor na jeden vstup. Nastavte typ na světelný senzor (SENSOR TYPE LIGHT), jinak světelný senzor nebude fungovat, a nastavte režim na přímý. V tomto případě, když je stlačen dotykový senzor, dostanete hodnotu pod 100. Pokud není stlačen, dostanete hodnoty ze světelného senzoru, které nejdou nikdy pod 100. Následující program - příklad 9.3 používá tuto ideu. Robot musí být vybaven světelným senzorem směřujícím dolů, a nárazníkem vpředu, napojeným na dotykový senzor. Oba senzory připojte na vstupní port 1. Robot se bude náhodně pohybovat v osvětleném prostoru. Když světelný senzor zaznamená tmavou čáru (hodnota > 750), trochu zacouvá zpět. Pokud se dotykový senzor něčeho dotkne (hodnota pod 100), udělá totéž. Zde je program: Doufám, že je tento program jasný. Jsou zde dvě úlohy. Úloha moverandom způsobuje náhodný pohyb robotu. Hlavní úloha nejprve započne náhodný pohyb robotu spoštěním úlohy moverandom, nastaví senzory a pak čeká, až se něco stane. Pokud jsou údaje ze senzoru moc nízké (náraz) nebo moc vysoké (mimo světlou oblast), zastaví náhodný pohyb, trochu zacouvá zpět, a opět spustí náhodný pohyb. Také je možno připojit dva světelné senzory na tentýž vstup. Hrubá hodnota je nějakým způsobem závislá na množství světla zachyceného oběma senzory. Závislost ale není zcela jasná a celý systém je obtížně použitelný. Připojení ostatních typů senzorů dohromady s rotačním nebo teplotním čidlem se nezdá být užitečné.
9.4
Děláme si detektor překážek
S pomocí dotykových senzorů může robot reagovat když do něčeho narazí. Bylo by ale mnohem hezčí, kdyby robot mohl reagovat ještě předtím, než do něčeho narazí. Měl by vědět, že se nachází blízko nějaké překážky. Naneštěstí takové senzory nejsou k dispozici. Přesto je tu trik, který k tomuto účelu můžeme použít. Robot je vybaven infraportem, pomocí kterého může komunikovat s počítačem nebo s ostatními roboty. (O komunikaci se podrobněji rozepíšeme v kapitole 11.) Ukázalo se, že světelný senzor, který je přibalen v RIS, je velmi citlivý na infračervené světlo. Můžeme tedy postavit detektor překážek na tomto základě. Idea je, že jedna úloha vysílá infračervené signály a druhá měří kolísání intenzity světla odraženého od objektů. Čím je kolísání vyšší, tím jsme blíže objektu17 . 17
autor se z výukových důvodů dopouští značného zjednodušení celého problému. Ve skutečnosti množství světla odraženého zpět od vysílače závisí mimo vzdálenosti i na barvě, jakosti a materiálu plochy, od níž se signál má odrazit, úhlu, pod kterým paprsek dopadá, konfigu-
44
Abyste mohli úspěšně použít tento princip, musíte umístit světelný senzor nad infraport, co nejblíže k němu, s čidlem směřujícím vpřed. V této konfiguraci senzor zaznamená pouze odražené infračervené světlo. Připojte jej na vstup č. 2. Data ze světelného senzoru budeme číst v přímém režimu, abychom získali co možná nejvyšší přesnost při detekování fluktuací. Program, který nechá robot jet vpřed, dokud se nedostane blízko nějaké překážky, a pak udělá obrat o 90 vpravo najdete v příkladu 9.4. Úloha send signal() každou sekundu vyšle deset infračervených signálů pomocí příkazu SendMessage(0). Úloha check signal() opakovaně ukládá hodnotu získanou ze světelného senzoru. Poté zjišťuje jestli (o něco později) není hodnota alespoň o 200 vyšší, což ukazuje na velké kolísání. Pokud ano, nechá robot udělat devadesátistupňový obrat doprava. Hodnota 200 je volitelná libovolně. Zvolíte-li menší hodnotu, bude robot zatáčet dále od překážky. Pokud zvolíte hodnotu větší, dostane se k nim blíže. Tato hodnota ale také závisí na materiálu stěn a množství světla v místnosti. Měli byste experimentovat s použitím nějakého chytřejšího postupu pro zjištění Vám vyhovujících hodnot. Tato technika je velmi užitečná pro roboty, kteří se musí pohybovat v bludišti. Má ale několik nevýhod. První nevýhodou této techniky je, že funguje pouze v jednom směru. Pravděpodobně budete stále potřebovat dotykové senzory po stranách pro zabránění srážkám. Jinou nevýhodou je to, že robot nemůže komunikovat s počítačem, protože je přenos rušen ať již vlastními signály robotu, nebo signály jiného robotu. Také se může stát, že Vám nebude fungovat dálkové ovládání k televizi.
9.5
Shrnutí
V této kapitole jsme se dozvěděli spoustu dodatečných informacích o senzorech. Viděli jsme jak nastavit typ a režim práce senzoru nezávisle na sobě a jak toho využít k získání většího množství informací. Naučili jsme se jak použít rotační senzor a jak můžeme připojit více senzorů k jednomu vstupu RCX. Nakonec jsme si ukázali trik, jak z infračerveného vysílače RCX a světelného senzoru vytvořit detektor překážek. Všechny tyto znalosti jsou velmi užitečné při konstruování složitějších robotů. Senzory zde vždy hrají klíčovou roli.
raci robota a mnoha dalších faktorech. Berte tuto poznámku jako varování, abyste pak nebyli překvapeni, až Váš robot nepřestane narážet do těžkého sametového závěsu po babičce. Je to prostě proto, že ho „nevidíÿ.
45
int ttt,tt2; task moverandom() { while (true) { ttt = Random(50) + 40; tt2 = Random(1); if (tt2 > 0) { OnRev(OUT_A); OnFwd(OUT_C); Wait(ttt); } else { OnRev(OUT_C); OnFwd(OUT_A);Wait(ttt); } ttt = Random(150) + 50; OnFwd(OUT_A+OUT_C);Wait(ttt); } } task main() { start moverandom; SetSensorType(SENSOR_1,SENSOR_TYPE_LIGHT); SetSensorMode(SENSOR_1,SENSOR_MODE_RAW); while (true) { if ((SENSOR_1 < 100) || (SENSOR_1 > 750)) { stop moverandom; OnRev(OUT_A+OUT_C);Wait(30); start moverandom; } } }
Příklad 9.3 - více senzorů na jeden vstup
46
int lastlevel;
// Pro uschování předchozí intenzity světla
task send_signal() { while(true) {SendMessage(0); Wait(10);} } task check_signal() { while(true) { lastlevel = SENSOR_2; if(SENSOR_2 > lastlevel + 200) {OnRev(OUT_C); Wait(85); OnFwd(OUT_A+OUT_C);} } } task main() { SetSensorType(SENSOR_2, SENSOR_TYPE_LIGHT); SetSensorMode(SENSOR_2, SENSOR_MODE_RAW); OnFwd(OUT_A+OUT_C); start send_signal; start check_signal; }
Příklad 9.4 - detektor překážek
47
10
Paralelní úlohy
Jak už jsme se zmínili dříve, úlohy v NQC běží současně, neboli paralelně. To je velmi užitečné. Dovoluje nám to sledovat senzory v jedné úloze, zatímco druhá se stará o pohyb robota a třetí třeba může hrát hudbu. Paralelní úlohy ale také mohou způsobovat problémy. Jedna úloha může ovlivňovat chování druhé.
10.1
Špatný program
Promyslete si funkci programu uvedeného v příkladu 10.1. V něm jedna úloha pohybuje robotem po čtvercích (jak již jsme to několikrát dělali) a druhá testuje dotykový senzor. Pokud je stlačen, pohne robotem trochu zpět a provede obrat. task main() { SetSensor(SENSOR_1,SENSOR_TOUCH); start check_sensors; while (true) { OnFwd(OUT_A+OUT_C); Wait(100); OnRev(OUT_C); Wait(85); } } task check_sensors() { while (true) { if (SENSOR_1 == 1) { OnRev(OUT_A+OUT_C); Wait(50); OnFwd(OUT_A); Wait(85); OnFwd(OUT_C); } } }
Příklad 10.1 - interferující úlohy
Program na první pohled vypadá perfektně v pořádku. Pokud jej ale spustíte, s největší pravděpodobností se dočkáte neočekávaného chování robotu. Zkuste následující postup: Dotkněte se robotova čidla když zatáčí. Začne couvat, ale okamžitě se zas vrátí k pohybu vpřed a narazí do překážky. Důvodem je 48
vzájemné ovlivňování úloh. Dochází k následujícímu ději: Robot zatáčí vpravo, tedy úloha main právě vykonává příkaz Wait(85) - čekání. Nyní robot narazí. Spustí se couvání, ale ve stejný okamžik je hlavní úloha hotova s čekáním. Spustí proto pohyb vpřed a navede tak robot na překážku. Druhá úloha právě čeká (vykonává příkaz Wait), takže náraz nezaznamená. To je přirozeně chování, které nechceme. Problém je ten, že jsme si neuvědomili, že zatímco druhá úloha čeká, první stále vykonává příkazy, a že její akce interferují s akcemi druhé úlohy18 .
10.2
Ukončování a restartování úloh
Jeden způsob, kterým se dá řešit problém nastíněný v předchozí kapitole je zajistit, aby byl robot v každý okamžik řízen pouze jednou úlohou19 . Toto je přístup, který jsme použili v kapitole 6. Pro osvěžení v příkladu 10.2 znovu uvedeme zmiňovaný program: Rozhodující je, že úloha check sensors pohybuje robotem až poté, co je běh úlohy move square pozastaven. Tato úloha tedy nemůže ovlivňovat oddalování od překážky. Jakmile je couvání od překážky ukončeno, opět se spustí move square. Přestože se jedná o poměrně dobré řešení předchozího problému, má jeden nedostatek. Když restartujeme úlohu move square, začne od začátku. V našem malém příkladu to tak moc nevadí, ale velice často se jedná o nežádoucí chování. Upřednostňovali bychom, kdybychom mohli běh úlohy pozastavit a později pokračovat přesně z toho místa, kde jsme skončili. Naneštěstí toho nelze dosáhnout bez komplikací.
10.3
Používáme semafory
Standardní technikou používanou k řešení tohoto problému je použít proměnnou pro indikaci, která úloha právě ovládá motory. Ostatní úlohy nemají povolen přístup k motorům, dokud první úloha neoznámí (za použití proměnné), že jsou připraveny. Takováto proměnná je často nazývána semafor20 . Nechť sem je takovýto semafor. Předpokládejme, že hodnota 0 znamená, že žádná úloha právě nepoužívá motory. Nyní, kdykoli potřebuje jakákoli úloha dělat cokoli s motory, vykoná nejprve příkazy uvedené v příkladu 10.3. Nejprve tedy čekáme, dokud nikdo motory nepotřebuje. Poté si vyhradíme kontrolu nad motory nastavením semaforu sem na jedničku. Nyní můžeme ovládat motory. Když jsme hotovi, nastavíme semafor sem zpět na nulu. Následující příklad - příklad 10.4 - je vlastně program 10.2 implementovaný pomocí semaforů. Když se dotykový senzor něčeho dotkne, je nastaven semafor a provedeno 18 Převedeno do lidské řeči: Máme dvě úlohy, které se „perouÿ o společný majetek - motory. V momentě, kdy jedna úloha „nedává pozorÿ, tedy čeká nebo vykonává jiný příkaz, může jí druhá motory „ukrástÿ a dát jim vlastní příkazy, většinou opačné než původní. Potvrzuje se tak rčení „dvěma pánům nelze sloužitÿ. 19 úlohy, nebo části kódu, které skutečně řídí robot, tedy přistupují k výstupním zařízením společným pro všechny úlohy (motory, světla, serva. .), se nazývají kritické 20 analogie se semaforem je zřejmá. Máme mnoho vlaků a jen jednu kolej. Semafor indikuje, jestli je kolej volná a další vlak po ní může jet (zelená), nebo zda je kolej ještě obsazena (červená). Podobně máme mnoho úloh přistupujících k motoru a jen jeden motor, Indikujeme pak, zda je motor připraven k použití (obsazení) nebo obsazen.
49
vyhnutí se překážce. Během této procedury musí úloha move square čekat. V momentu, kdy je vyhnutí dokončeno, semafor je nastaven zpět na nulu a úloha move square může pokračovat. Při prohlédnutí programu byste mohli namítnout, že v úloze move square není nutné nastavovat semafor na jedničku a hned zpět na nulu. Je to ale užitečné. Příkaz OnFwd() totiž ve skutečnosti sestává ze dvou příkazů (viz kapitola 8). Nechceme, aby tento sled příkazů byl přerušen nějakou jinou úlohou.
10.4
Shrnutí
V této kapitole jsme zkoumali některé z problémů, které mohou nastat, pokud použijete několik úloh. Vždy dávejte velký pozor na postranní efekty. Na jejich vrub se dá připsat velká část neočekávaného chování. Viděli jsme dva rozdílné způsoby řešení těchto problémů. První řešení zastaví a znovu spustí úlohu, aby bylo zajištěno, že v každý okamžik je robot řízen pouze jednou úlohou. Druhý přístup používá semaforů k řízení přístupu úloh k motorům. To zaručuje, že v každý okamžik běží pouze jedna kritická část jedné úlohy.
50
task main() { SetSensor(SENSOR_1,SENSOR_TOUCH); start check_sensors; start move_square; } task move_square() { while (true) { OnFwd(OUT_A+OUT_C); Wait(100); OnRev(OUT_C); Wait(85); } } task check_sensors() { while (true) { if (SENSOR_1 == 1) { stop move_square; OnRev(OUT_A+OUT_C); Wait(50); OnFwd(OUT_A); Wait(85); start move_square; } } }
Příklad 10.2 - zastavení úlohy
until (sem == 0); sem = 1; // dělej něco s motory sem = 0;
Příklad 10.3 - Funkce semaforu
51
int sem; task main() { sem = 0; start move_square; SetSensor(SENSOR_1,SENSOR_TOUCH); while (true) { if (SENSOR_1 == 1) { until (sem == 0); sem = 1; OnRev(OUT_A+OUT_C); Wait(50); OnFwd(OUT_A); Wait(85); sem = 0; } } } task move_square() { while (true) { until (sem == 0); sem = 1; OnFwd(OUT_A+OUT_C); sem = 0; Wait(100); until (sem == 0); sem = 1; OnRev(OUT_C); sem = 0; Wait(85); } }
Příklad 10.4 - použití semaforů
52
11
Komunikace mezi roboty
Pokud vlastníte více než jedno RCX, je tato kapitola právě pro Vás. Roboti mezi sebou mohou komunikovat přes infraport. S využitím této komunikace můžete přinutit několik robotů spolupracovat, nebo třeba bojovat mezi sebou. Také můžete postavit jednoho velkého robota se dvěma RCX, takže můžete ovládat šest motorů a šest senzorů (nebo dokonce ještě více, použijete-li triky z kapitoly 9. Komunikace mezi roboty vypadá v nejhrubších rysech asi takto: Robot může použít příkaz SendMessage() k vyslání zprávy – čísla v rozsahu 0 až 255 přes infraport. Všechny ostatní roboty tuto zprávu přijmou a uloží ji v paměti21 . Program v robotu se pak dotazuje na hodnotu poslední přijaté zprávy pomocí příkazu Message(). Další akce robotu se pak odvíjejí podle toho, jakou hodnotu měla poslední přijatá zpráva.
11.1
Dávání příkazů
Pokud máte dva nebo více robotů, jeden z nich má většinou vedoucí roli, je vůdcem. Nazýváme ho master 22 . Ostatní roboty mají podřízené úlohy, pouze poslouchají rozkazy. Nazýváme je slaves - otroci. Master robot posílá příkazy slave robotům a ti je vykonávají. Někdy mohou slave roboti mohou poslat informaci zpět k master robotům, například hodnotu ze senzoru. Potřebujete tedy napsat dva programy, jeden pro master robota, druhý pro slave robota/y. Od teď budeme předpokládat, že máte pouze jednoho slave robota. Začneme s velmi jednoduchým příkladem. Slave robot může vykonávat tři jednoduché rozkazy: pohyb vpřed, pohyb vzad a zastavení. Jeho program sestává z jednoduchého cyklu. V tomto cyklu nejprve resetujeme hodnotu poslední zprávy na nulu pomocí příkazu ClearMessage(). Potom čekáme, dokud tato zpráva není různá od nuly (tedy dokud nepřijmeme novou zprávu). Na základě hodnoty přijaté zprávy se pak vykoná jeden ze tří příkazů. Program najdete v příkladu 11.1. Master robot má ještě jednodušší program. Prostě vyšle zprávu odpovídající příkazům a chvíli počká. V programu uvedeném v příkladu 11.2 přikazujeme slave robotu nejprve jet vpřed, poté, po dvou sekundách, jet vzad, a nakonec, také po dvou sekundách, zastavit. Když máte napsány oba programy, potřebujete je nahrát do robotů. Každý program musí být nahrán do jednoho z robotů. Dávejte pozor na to, aby byl vždy zapnutý pouze ten robot, do kterého je nahráván program! Ostatní roboty musí být vypnuté. Máte-li nahrány v obou robotech programy, můžete je zapnout. Zapínejte nejprve slave robot a teprve pak master. Máte-li více slave robotů, musíte nahrát program do každého zvlášť! Nelze nahrát programy najednou. Více se dozvíte v odstavci 11.3. Všechny slave roboty pak budou vykonávat tutéž akci. Aby roboti mohli komunikovat mezi sebou, definovali jsme, takzvaný protokol. Rozhodli jsme, že číslo jedna znamená „Jeď vpřed!ÿ, dvojka „Jeď vzad!ÿ, číslo tři pak „Stop!ÿ. Je velmi důležité definovat tyto protokoly obezřetně, 21 22
ovšem pouze pokud na vysílající robot ”vidí”! anglický výraz pro pán
53
task main() { while (true) { ClearMessage(); until (Message() if (Message() == if (Message() == if (Message() == } }
// SLAVE
!= 1) 2) 3)
0); {OnFwd(OUT_A+OUT_C);} {OnRev(OUT_A+OUT_C);} {Off(OUT_A+OUT_C);}
Příklad 11.1 - slave program
task main() // MASTER { SendMessage(1); Wait(200); SendMessage(2); Wait(200); SendMessage(3); }
Příklad 11.2 - master program
zejména vykonává-li se mnoho komunikace. Pokud máte například více slave robotů, můžete nadefinovat protokol, ve kterém jsou vyslána vždy dvě čísla (s malou prodlevou mezi sebou). První číslo je číslo slave robotu, druhé je skutečný příkaz. Slave robot nejprve otestuje číslo a pak vykoná rozkaz pouze pokud je první číslo „ jehoÿ. (To tedy vyžaduje, aby každý robot měl své číslo uložené např. v konstantě slave robotu - každý robot má tedy mírně odlišný program, neb konstanty budou jiné.)
11.2
Volba vůdce
Jak jsme viděli v předchozím odstavci, při práci s více roboty musíme mít pro každý robot odlišný program. Bylo by mnohem jednodušší, kdybychom mohli nahrát jeden program do všech robotů. Pak ale vznikne otázka: Kdo bude vůdce? Odpověď je snadná: Nechme roboty, ať si zvolí vůdce samy. Jak to ale uděláme? Idea je poměrně jednoduchá. Necháme každý robot čekat náhodně zvolenou dobu a poté vyslat signál. Ten robot, který vyšle signál jako první bude vůdcem. Toto schéma může selhat, pokud by dva roboty čekaly přesně stejnou dobu, ale tento případ je dosti nepravděpodobný. (Můžete vymyslet 54
komplikovanější schéma, které tento případ zachytí a v takovém případě spustí druhou volbu.) Program, který to dělá, najdete v příkladu11.3. Nahrajte tento program do všech robotů (do jednoho po druhém, nikdy ne současně! Proč se dozvíte v následujícím odstavci). Zapněte roboty pokud možno ve stejný okamžik a sledujte, co se stane. Jeden z nich by měl vydávat příkazy a ostatní by je měly vykonávat. V řídkých případech se vůdcem nestane nikdo. Jak již bylo řečeno dříve, tento problém vyžaduje vyřešení komplikovanějšího protokolu.
11.3
Varování
Při práci s více roboty musíme být opatrní. Jsou zde dva problémy: Pokud dva roboty (nebo robot a počítač) zašlou informaci současně, může se informace ztratit. K druhému problém s komunikací dochází, když počítač posílá program několika robotům současně. Začněme s druhým problémem. Když downloadujete program do robotu, robot říká počítači, jestli danou informaci (nebo část programu) obdržel v pořádku. Počítač na to reaguje buď zasláním dalších částí programu, nebo znovuzasláním částí ztracených či poškozených při předchozím přenosu. Pokud jsou zapnuty dva roboty, oba začnou počítači hlásit, zda obdržely data v pořádku. Počítač tomu nerozumí (Neboť neví, že „venkuÿ jsou roboty dva!) a začne produkovat chyby. Jako výsledek pak dostaneme poškozené programy a tím i nefunkční roboty23 . Vždy se ujistěte, že když downloadujete program, je zapnutý pouze jeden robot! Příčinou vedoucí ke vzniku prvního problému je fakt, že robot nemůže vysílat a přijímat v tentýž okamžik. To je dáno konstrukcí RCX a nelze to odstranit. Logicky tedy, pokud dva roboty vysílají v týž okamžik, nemohou se vzájemně „slyšetÿ a data se ztratí. K tomuto jevu nedochází, pokud vysílá pouze jeden robot (jen jeden je master), ale v ostatních případech se může jednat o závažný problém. Představte si například program, ve kterém slave robot zašle zprávu pokaždé, když do něčeho narazí, takže master robot může situaci vyřešit. Pokud ale master robot v týž okamžik vyslal příkaz, zpráva se ztratí24 . Abyste vyřešili tento problém, musíte nadefinovat protokol tak, že když dojde k poruše komunikace, je chyba opravena. Například, pokud pošle master robot příkaz, měl by dostat od slave robotu „potvrzení o přijetíÿ. Pokud toto potvrzení nedostane dostatečně brzy, příkaz pošle znovu. Tento postup vede k části kódu podobné ukázce v příkladu 11.4. Zpráva číslo 255 je v něm použita pro potvrzení příjmu. Někdy, pracujete-li s několika roboty, potřebujete, aby signály přijímal pouze robot, který je velmi blízko vůdci. Toho můžeme dosáhnout zařazením příkazu SetTxPower(TX POWER LO) do programu vůdce. Pak bude mít vysílaný infračervený signál nízkou intenzitu a pouze robot stojící blízko (a čelem) k master robotu jej zachytí. Zejména užitečné je to při stavbě velkého robotu se dvěma 23
přesněji řečeno roboty, které nedělají to, co jste naprogramovali. V takzvaném evolučním programování je to zcela jistě přínosem, ale v tomto kursu se raději přidržme programování klasického. 24 obě z nich
55
RCX. Obdobný příkaz SetTxPower(TX POWER HI) použijte pro nastavení vysílače zpět na daleký dosah.
11.4
Shrnutí
V této kapitole jsme prostudovali některé ze základních aspektů komunikace mezi roboty. Komunikace používá příkazy pro vysílání, mazání a přijímání zpráv. Viděli jsme, jak je definování protokolu důležité pro průběh komunikace. Tyto protokoly hrají klíčovou roli v libovolné formě komunikace mezi počítači. Také jsme viděli několik omezení v komunikaci mezi roboty, což činí potřebu navrhovat dobré protokoly ještě důležitější.
56
task main() { ClearMessage(); Wait(200); Wait(Random(400)); if (Message() > 0) { start slave; } else { SendMessage(1); Wait(400); start master; } }
// Počkej až budou všechny roboty zapnuty // Čekej 0 až 4 sekundy // Někdo druhý byl rychlejší
// Já jsem teď master // ujisti se že to všichni vědí
task master() { SendMessage(1); Wait(200); SendMessage(2); Wait(200); SendMessage(3); } task slave() { while (true) { ClearMessage(); until (Message() if (Message() == if (Message() == if (Message() == } }
!= 1) 2) 3)
0); {OnFwd(OUT_A+OUT_C);} {OnRev(OUT_A+OUT_C);} {Off(OUT_A+OUT_C);}
Příklad 11.3 - výběr vůdce
57
task main() { do { SendMessage(1); ClearMessage(); Wait(10); } while (Message() != 255); }
Příklad 11.4 - potvrzení příjmu
58
12
Další příkazy
NQC má množství dalších příkazů. V této kapitole probereme tři typy: Použití časovačů, příkazy pro kontrolu displeje a použití datalogu RCX.
12.1
Časovače
RCX má čtyři vestavěné časovače. Tyto časovače tikají v intervalech po 1=10 sekundy. Časovače jsou očíslovány od 0 do 3. Můžete vynulovat hodnotu časovače použitím příkazu ClearTimer() a získat aktuální hodnotu časovače příkazem Timer(). Příklad 12.1 demonstruje použití časovače. Program nechá robot jezdit náhodně vpřed a vzad po dobu 20 sekund. task main() { ClearTimer(0); do { OnFwd(OUT_A+OUT_C); Wait(Random(100)); OnRev(OUT_C); Wait(Random(100)); } while (Timer(0)<200); Off(OUT_A+OUT_C); }
Příklad 12.1 - využití časovače
Možná byste chtěli porovnat tento program s tím, který se objevil v kapitole 4, příkladem 4.3, který plnil přesně stejné zadání. Program s časovačem je mnohem jednodušší. Časovače jsou velmi užitečné jako náhrada příkazu Wait(). Můžete nechat robot spát požadovanou dobu tak, že resetujete časovač a pak čekáte, dokud čítač nedosáhne potřebné hodnoty. Také ale můžete reagovat na různé události (například od senzorů), zatímco čekáte. Program v příkladu 12.2 je jednoduchou ukázkou této techniky. Robot bude jezdit, dokud neuplyne 10 sekund, nebo dokud se senzor něčeho nedotkne. Nezapomeňte, že časovače pracují s intervaly dlouhými 1=10 sekundy, zatímco např. příkaz Wait() používá interval 1=100 sekundy.
12.2
Displej
Zobrazení údajů na displeji RCX můžeme ovládat dvěma různými způsoby. Za prvé můžeme zvolit, co se má zobrazit: systémový čas, stav jednoho ze senzorů 59
task main() { SetSensor(SENSOR_1,SENSOR_TOUCH); ClearTimer(3); OnFwd(OUT_A+OUT_C); until ((SENSOR_1 == 1) || (Timer(3) >100)); Off(OUT_A+OUT_C); }
Příklad 12.2 - časovač v čekací smyčce
nebo jednoho z motorů. Tento režim je ekvivalentní použití černého tlačítka na RCX. Pro nastavení typu zobrazení použijte příkaz SelectDisplay(). Příklad 12.3 ukazuje všech sedm možností, jednu po druhé.
task main() { SelectDisplay(DISPLAY_SENSOR_1); SelectDisplay(DISPLAY_SENSOR_2); SelectDisplay(DISPLAY_SENSOR_3); SelectDisplay(DISPLAY_OUT_A); SelectDisplay(DISPLAY_OUT_B); SelectDisplay(DISPLAY_OUT_C); SelectDisplay(DISPLAY_WATCH); }
Wait(100); Wait(100); Wait(100); Wait(100); Wait(100); Wait(100); Wait(100);
// // // // // // //
Vstup 1 Vstup 2 Vstup 3 Výstup A Výstup B Výstup C Systémový čas
Příklad 12.3 - příkazy ovládání displeje
Poznamenejme pouze, že byste v programu nikdy neměli použít konstrukci SelectDisplay(SENSOR 1). Druhým způsobem, jak ovládat displej, je ovládání hodnoty systémových hodin. To může být použito např. k zobrazení diagnostických informací. K tomu použijte příkaz SetWatch(). Příklad 12.4 vám vše osvětlí. Pouze poznamenejme, že argumenty příkazu SetWatch() musí být konstanty25 . 25
Vlastníte-li novou verzi RCX, označovanou RCX 2.0, nebo máte-li starší RCX s nainstalovaným novým firmware, umí Vaše RCX zobrazit na displeji i proměnné. Pak ale nainstalujte NQC verze 2.30 nebo vyšší. NQC verze 2.0 tuto funkci nepodporuje.
60
task main() { SetWatch(1,1); Wait(100); SetWatch(2,4); Wait(100); SetWatch(3,9); Wait(100); SetWatch(4,16); Wait(100); SetWatch(5,25); Wait(100); }
Příklad 12.4 - ovládání systémových hodin
12.3
Použití datalogu
RCX může ukládat hodnoty časovačů, proměnných a data získaná senzory do zvláštní oblasti paměti nazývané datalog. Hodnoty uschované v datalogu nemohou být využívány přímo RCX, ale mohou být čteny vaším počítačem. To je užitečné např. pro zjištění, co se děje ve vašem robotu. RCX Command Center má speciální okno, ve kterém si můžete prohlížet aktuální obsah datalogu. Použití datalogu sestává ze tří kroků: Nejprve musí NQC program definovat velikost datalogu pomocí příkazu CreateDatalog(). To také vymaže jeho současný obsah. Následně mohou být pomocí příkazu AddToDatalog() do datalogu ukládána data. Hodnoty budou ukládány jedna za druhou. (Podíváte-li se na displej RCX, uvidíte, jak se jedna po druhé budou objevovat části kruhu. Když bude kruh celý, je datalog plný.) Je-li dosaženo konce datalogu, nic se nestane. Nové hodnoty prostě nebudou uloženy. Třetím krokem je nahrání datalogu do počítače. K tomu musíte v RCX Command Center zvolit položku Datalog v menu Tools. Stiskněte pak tlačítko označené Upload Datalog, a všechny hodnoty z datalogu se objeví na obrazovce. Můžete je prohlížet, uložit do souboru nebo s nimi dělat něco úplně jiného. Lidé pomocí datalogu vytvořili např. scanner. Příklad 12.5 je ukázkou programu pro robot se světelným čidlem. Robot jede deset sekund, a pětkrát za sekundu ukládá hodnotu získanou ze senzoru do datalogu.
12.4
Shrnutí
V této kapitole jsme si ukázali, jak měnit režim zobrazení na displeji, jak jej použít pro zobrazení diagnostických informací a jak pracovat s datalogem.
61
task main() { SetSensor(SENSOR_2,SENSOR_LIGHT); OnFwd(OUT_A+OUT_C); CreateDatalog(50); repeat (50) { AddToDatalog(SENSOR_2); Wait(20); } Off(OUT_A+OUT_C); }
Příklad 12.5 - práce s datalogem
62
13
Stručná referenční příručka NQC
Zde najdete seznam všech příkazových konstrukcí, příkazů, konstant NQC atp. Většina z nich byla probrána v předchozích kapitolách, zde je uveden pouze stručný popis.
13.1
Příkazy
Tabulka 3 podává přehled příkazů jazyka NQC. Příkaz while (podm) tělo do tělo while (podm) until (podm) tělo break continue repeat (výraz ) tělo if (podm) tělo if (podm) tělo1 else tělo2 start jméno úlohy stop jméno úlohy funkce(parametry) prom = výraz prom += výraz prom -= výraz prom *= výraz prom /= výraz prom |= výraz prom &= výraz return výraz
Popis Vykoná tělo 0 či vícekrát, je-li podm splněna Vykoná tělo 1 či vícekrát, je-li podm splněna Vykonává tělo 0 či vícekrát, dokud není podm splněna Násilně ukončí while/do/until cyklus Přeskočí na další iteraci while/do/until cyklu Opakuje tělo specifikovaný počet opakování Vykoná tělo pokud je podmínka podm splněna Vykoná tělo1 pokud splněna podm, jinak vykoná tělo2 Spustí úlohu jméno úlohy Ukončí běh specifikované úlohy Zavolá funkci s příslušnými parametry Vyhodnotí výraz a přiřadí jej do proměnné prom Vyhodnotí výraz a přičte jej k prom Vyhodnotí výraz a odečte jej od prom Vyhodnotí výraz a násobí jej s proměnnou prom Vyhodnotí výraz a dělí jej proměnnou prom Vyhodnotí výraz a provede bitovou operaci OR s prom Vyhodnotí výraz a provede bitovou operaci AND s prom Návrat z funkce zpět k volajícímu Vyhodnotí výraz
Tabulka 3: příkazy NQC
13.2
Podmínky
Podmínky jsou používány v řídících strukturách pro rozhodování. Ve většině případů znamená podmínka porovnávání mezi dvěma (či více) výrazy. Podmínky jsou uspořádány v tabulce 4.
13.3
Výrazy
Ve výrazech může být použito množství různých hodnot včetně konstant, proměnných a hodnot odečtených ze senzorů. Poznamenejme ještě, že SENSOR 1, SENSOR 2 a SENSOR 3 jsou ve skutečnosti makra, která budou přepsána na SensorValue(0), SensorValue(1), resp. SensorValue(2). Seznam výrazů najdete v tabulce 5. Hodnoty mohou být kombinovány pomocí operátorů. Některé z operátorů smí být použity pouze při vyhodnocování konstantních výrazů, což znamená, 63
Podmínka true false podm1 == podm2 podm1 != podm2 podm1 < podm2 podm1 <= podm2 podm1 > podm2 podm1 >= podm2 ! podmínka podm1 && podm2 podm1 || podm2
Význam Vždy pravda Vždy nepravda Testuje, zda jsou podmínky shodné Testuje různost podmínek Test, zda podm1 je menší než podm2 Test, zda podm1 je menší nebo rovna podm2 Test, zda podm1 je větší než podm2 Test, zda podm1 je větší nebo rovna podm2 Provede logickou negaci podmínky Logické AND dvou podmínek. (Pravda, pokud jsou obě pravdivé) Logické OR dvou podmínek. (Pravda, je-li alespoň jedna pravdivá)
Tabulka 4: podmínky v NQC
Hodnota číslo proměnná Timer(n) Random(n) SensorValue(n) Watch() Message()
Popis Konstantní hodnota (např. „123ÿ) Pojmenovaná proměnná (např. „xÿ) Hodnota z časovače n, kde n je mezi 0 a 3 Náhodné číslo mezi 0 a n Hodnota čtená ze senzoru n, kde n je mezi 0 a 2 Hodnota systémových hodin Hodnota poslední přijaté IR zprávy
Tabulka 5: výrazy v NQC
že jejich operandy musí být buď konstanty, nebo výrazy neobsahující nic jiného než konstanty. Operátory jsou vyjmenovány v následující tabulce (tabulka 6) v pořadí podle priority vyhodnocování (od nejvyšší po nejnižší).
13.4
Funkce RCX
Většina funkcí vyžaduje, aby všechny argumenty byly konstantní výrazy (čísla nebo operace obsahující pouze konstantní výrazy). Výjímkou jsou funkce používající jako parametr senzor, a pak ty, které mohou jako argument použít libovolný výraz. V případě senzorů by mělo být argumentem jméno senzoru: SENSOR 1,SENSOR 2, nebo SENSOR 3. V některých případech máme předdefinovaná jména (např. SENSOR TOUCH) pro příslušné konstanty. Tabulka 7 uvádí funkce RCX.
13.5
Konstanty RCX
Mnoho z hodnot pro funkce RCX má pojmenované konstanty, které pomáhají udržet kód čitelný. Kde je to možné, užívejte pojmenované konstanty namísto samotné hodnoty. Tabulka 8 uvádí konstanty, které můžete použít. 64
Operátor abs() sign() ++ -~ * / % + << >> & ^ | && ||
Popis Absolutní hodnota Znaménko operandu Inkrementace Dekrementace Unární mínus Negace bit po bitu Násobení Dělení Modulo Sčítání Odčítání Bitový posun vlevo Bitový posun vpravo Bitové AND Bitové XOR Bitové OR Logické AND Logické OR
Asociativita
zleva zleva zprava zprava zleva zleva zleva zleva zleva zleva zleva zleva zleva zleva zleva zleva
Omezení
pouze pouze pouze pouze
proměnné proměnné konstanty konstanty
pouze konstanty
pouze konstanty pouze konstanty pouze konstanty pouze konstanty pouze konstanty
Příklad abs(x) sign(x) x++ nebo ++x x-- nebo x--x ~123 x * y x / y 123 % 4 x + y x - z 123 << 4 123 >> 4 x & y 123 ^ 4 x | y 123 && 4 123 || 4
Tabulka 6: Operátory jazyka NQC
13.6
Klíčová slova
Klíčová (rezervovaná) slova jsou slova rezervovaná překladačem jazyka NQC pro vlastní jazyk. Je chybou použít kterékoliv z těchto slov jako jména pro funkci, úlohu nebo proměnnou. Existují následující klíčová slova: sensor, abs, asm, break, const, continue, do, else, false, if, inline, int, repeat, return, sign, start, stop, sub, task, true, void, while.
65
Funkce SetSensor(senzor,typ) SetSensorMode(senzor,režim) SetSensorType(senzor,typ) ClearSensor(senzor ) On(výstupy) Off(výstupy) Float(výstupy) Fwd(výstupy) Rev(výstupy) Toggle(výstupy) OnFwd(výstupy) OnRev(výstupy) OnFor(výstupy,čas)
SetOutput(výstupy,mód) SetDirection(výstupy,směr ) SetPower(výstupy,síla) Wait(čas)
PlaySound(zvuk ) PlayTone(frek,doba)
ClearTimer(časovač ) StopAllTasks() SelectDisplay(mód)
SendMessage(zpráva) ClearMessage() CreateDatalog(velikost) AddToDatalog(hodnota) SetWatch(H,M ) SetTxPower(výkon)
Popis Konfiguruje senzor Nastaví režim práce senzoru Nastaví typ senzoru Vymaže hodnotu senzoru. Zapne jeden či více výstupů Vypne a „zabrzdíÿ jeden či více výstupů Vypne napájení na výstupy, neaplikuje brzdu Nastaví výstupy na směr vpřed Nastaví výstupy na směr vzad Obrátí směr výstupů Zapne výstupy ve směru vpřed Zapne výstupy ve směru vzad Zapne výstupy na určenou dobu v setinách sekundy. Čas smí být výraz nastaví režim výstupu nastaví směr výstupu Nastaví výkon výstupu (0–7), síla smí být výraz. Čeká dobu specifikovanou v setinách sekundy. Čas smí být výraz. Zahraje zvolený zvuk (0–5) Hraje tón zadané frekvence po zadanou dobu (v desetinách sekundy) Vynuluje zadaný časovač (0–3) Zastaví všechny probíhající úlohy Vybere jeden z režimů práce displeje: 0:systémový čas, 1–3 hodnoty senzorů, 4–6 stav výstupů. Režim smí být výraz Vyšle zprávu pomocí IR portu. Zpráva (1-255) smí být výraz Vymaže frontu přijatých zpráv. Vytvoří datalog dané velikosti. Vloží hodnotu do datalogu. Hodnota smí být výraz Nastaví systémový čas Nastaví výkon IR vysílače
Tabulka 7: Funkce RCX
66
Příklad SetSensor(SENSOR 1, SENSOR TOUCH) SetSensorMode(SENSOR 2, SENSOR MODE PERCENT) SetSensorType(SENSOR 2, SENSOR TYPE LIGHT) ClearSensor(SENSOR 1) On(OUT A+OUT C) Off(OUT C) Float(OUT B) Fwd(OUT A) Rev(OUT B) Toggle(OUT C) OnFwd(OUT A) OnRev(OUT B) OnFor(OUT A,200)
SetOutput(OUT A,OUT ON) SetDirection(OUT A,OUT FWD) SetPower(OUT A,6) Wait(x)
PlaySound(SOUND CLICK) PlayTone(440,5)
ClearTimer(0) StopAllTasks() SelectDisplay(3)
SendMessage(x) ClearMessage() CreateDatalog(100) AddToDatalog(Timer(0)) SetWatch(1,30) SetTxPower(TX POWER LO)
Nastavení SetSensor()
senzorů
pro
Režimy pro SetSensorMode()
Typy pro SetSensorType Výstupy pro On(), Off() atp. Režimy pro SetOutput() Směry pro SetDirection() Výkon výstupu pro SetPower() Zvuky pro PlaySound() Režimy pro SelectDisplay()
Přenosový režim pro SetTxPower()
SENSOR TOUCH, SENSOR LIGHT, SENSOR ROTATION, SENSOR CELSIUS, SENSOR FAHRENHEIT, SENSOR PULSE, SENSOR EDGE SENSOR MODE RAW, SENSOR MODE BOOL, SENSOR MODE EDGE, SENSOR MODE PULSE, SENSOR MODE PERCENT, SENSOR MODE CELSIUS, SENSOR MODE FAHRENHEIT, SENSOR MODE ROTATION SENSOR TYPE TOUCH, SENSOR TYPE TEMPERATURE, SENSOR TYPE LIGHT, SENSOR TYPE ROTATION OUT A, OUT B, OUT C OUT ON, OUT OFF, OUT FLOAT OUT FWD, OUT REV, OUT TOGGLE OUT LOW, OUT HALF, OUT FULL SOUND CLICK, SOUND DOUBLE BEEP, SOUND DOWN, SOUND UP, SOUND LOW BEEP, SOUND PAST UP DISPLAY WATCH, DISPLAY SENSOR 1, DISPLAY SENSOR 2, DISPLAY SENSOR 3, DISPLAY OUT A, DISPLAY OUT B, DISPLAY OUT C TX POWER LO, TX POWER HI
Tabulka 8: konstanty RCX
67
14
Závěrečné poznámky
Pokud jste se propracovali tímto tutoriálem až sem, můžete se pokládat za experta v NQC. Pokud jste to ještě neudělali, je čas začít samostatně experimentovat. S pomocí fantazie při stavbě a programování můžete s Lego roboty dokázat úžasné věci. Tento tutoriál zdaleka nepokryl všechny funkce RCX Command Center. Doporučuji Vám pročíst si dokumentaci. NQC je také stále dále vyvíjeno. Novější verze budou pravděpodobně obsahovat rozšířené funkce. V tomto tutoriálu také nebylo diskutováno mnoho programátorských technik a koncepcí. Zejména jsme se nedotkli chování robotů na základě učení, ani dalších aspektů umělé inteligence. Řídit Lego robot je možno dokonce přímo z PC. To vyžaduje, abyste napsali program v jazyku jako je např. Visual Basic, Java nebo Delphi. Také je možno nechat takovýto program kooperovat s programem psaným v NQC a běžícím na RCX. Tato kombinace je velmi mocná. Pokud Vás zajímá tento způsob programování, začněte se stahováním technické dokumentace k rozhraní spirit.ocx z www stránky Lego Mindstorms http://www.legomindstorms.com/ Tento server je perfektním zdrojem pro dodatečné informace. Další důležité startovací body jsou na stránce odkazů mého vlastního webu: http://www.cs.uu.nl/people/markov/lego/ a LUGNET, neoficiální síť uživatelů LEGO: http://www.lugnet.com/ Mnoho informací také můžete najít v diskusních skupinách lugnet.robotics a lugnet.robotics.rcx.nqc na lugnet.com
68