Programování Lego robotů pomocí NQC

Transkript

Programování Lego robotů pomocí NQC
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

Podobné dokumenty

Not eXactly C (NXC): Príručka programátora

Not eXactly C (NXC): Príručka programátora binárneho súboru so strojovým kódom. Prekladač (kompilátor) NXC prekladá zdrojový program do NXT bajtkódov, ktoré je možné následne spustiť na kocke NXT. Hoci syntax jazyka a jeho riadiace štruktúr...

Více

Programování jBotBrainu II v Javě a práce s moduly

Programování jBotBrainu II v Javě a práce s moduly II, a je tedy vhodný jak pro pokročilé programátory, tak i pro začátečníky v robotice a mikroprocesorové technice vůbec. Nemusíme se starat o to, na jakém portu je připojeno servo, jak máme nastavi...

Více

Katedra řídicí techniky Využití robota LEGO Mindstorms – návrh a

Katedra řídicí techniky Využití robota LEGO Mindstorms – návrh a digitálního senzoru. V případě analogového senzoru je za vstupem 10bitový A/D převodník a senzor je napájen ze zdroje proudu. V případě digitálního senzoru je komunikace zajištěna pomocí I2C protok...

Více

Programování robotů 1

Programování robotů 1 nebo podobné příkazy). V jazyce C# dnes můžete programovat prakticky cokoliv, co Vás napadne. Od formulářových aplikací pro Windows, přes software do mobilních zařízení, databáze, až po internetové...

Více

Co je nového v SolidWorks 2010

Co je nového v SolidWorks 2010 Otevření dílů a sestav z výkresů............................................................................................................80 Pohledové řezy...........................................

Více

cermark nového časovače

cermark nového časovače dlouho. Nikdo ale neví, jak na tom budeme s technologii za pár let. Objeví se speciální vypínače, senzory, usazení světel, ovládání domu a dalších technických vymožeností. Je velmi důležité myslet ...

Více

Ovládání laboratorního modelu Mindstorms NXT (spike) pomocí PC

Ovládání laboratorního modelu Mindstorms NXT (spike) pomocí PC řízení, měřicí techniky, umělé inteligence a celé řady dalších disciplin. Přestože roboty mohou mít různý vzhled a schopnosti, obvykle se robotem rozumí zařízení (autonomní integrovaný systém) vyko...

Více

Demonstrační cvičení IMS #1

Demonstrační cvičení IMS #1 Priorita (pouze okamžité) Pravděpodobnost (pouze okamžité) Parametry NELZE kombinovat

Více

Ovládání laboratorního modelu robota Mindstorms

Ovládání laboratorního modelu robota Mindstorms období dospěla technika takového rozmachu, ţe se její produkty staly kaţdodenním společníkem téměř kaţdého člověka. Dalo by se říct, ţe uţ máme všechno, jen nemáme a asi ani nikdy nebudeme mít dost...

Více