2009
Treball de Recerca
Guia de Programaci贸 de NXC
Carles P茅rez Guallar
1
Carles Ortega Llorens
Introducció Hola, som uns estudiants de segon de batxillerat de l’IES Forat del Vent i em decidit fer una guia de programació per poder guiar a qualsevol que necessiti ajuda amb el llenguatge NXC o vulgui iniciar-se en el món de la programació. Aquesta guia es basa fonamentalment en la guia “Programming LEGO NXT Robots using NXC”, by Daniele Benedettelli. És una traducció de l’anglès al català. També l’hem ampliada gracies als nostres coneixements previs sobre la matèria, fent-la una mica més extensa i senzilla d’entendre. El nostre objectiu és poder transmetre els nostres coneixements i els de l’autor, perquè finalment el lector pugui arribar a iniciar-se i comprendre el món de la programació. Així doncs, volem que el lector sense experiència prèvia pugui entrar en contacte amb la tasca de programar, i no suposi una dificultat molt gran. Creiem que és adient per tots els novells perquè el llenguatge que fem servir, NXC, és de nivell alt i facilita l’aprenentatge. Té una gran similitud amb el C, un llenguatge molt emprat. A més de tot això, gràcies a l’ interacció amb LEGO NXT, podràs veure uns resultats ràpids i fins i tot espectaculars. Cal remarcar que aquesta guia només explica algunes de les ordres més bàsiques, si es vol aprofundir en el tema una de les millors opcions és la lectura de “NeXT Byte Codes (NBC) Programmer’s Guide”, by John Hansen. Aquesta guia explica totes les possibles ordres del llenguatge que farem servir. Gràcies per la vostra elecció.
2
ÍNDEX 1. Introducció............................................................................................................ 1.1. ¿ Què és Brick Command Center?.................................................................... 1.2. El format de BricxCC........................................................................................ 2. Primer contacte amb BricxCC.................................................................................. 2.1. Iniciant per primer cop el programa................................................................. 2.2. L’entorn.......................................................................................................... 2.3. Barra de menús............................................................................................... 2.4. Barra d’eines................................................................................................... 2.5. Templates (plantilles)...................................................................................... 2.6. Status Bar (Barra d’estat)................................................................................. 3. Escrivint el primer programa................................................................................... 3.1. Escrivint el programa....................................................................................... 3.2. Possibles errors............................................................................................... 4. Utilitzant variables................................................................................................. 5. Estructures de control............................................................................................ 5.1. Altres tipus d’estructura de control (do)........................................................... 6. Sensors.................................................................................................................. 6.1. Sensor de tacte............................................................................................... 6.2. Sensor de llum................................................................................................ 6.3. Sensor de so.................................................................................................... 6.4. Sensor d’ultrasò.............................................................................................. 7. Tasques i subrutines.............................................................................................. 7.1. Tasques........................................................................................................... 7.1.1.Tasques en paral·lel................................................................................. 7.1.2.Utilitzant semàfors................................................................................... 7.2. Subrutines....................................................................................................... 7.2.1.Fent servir macros.................................................................................... 8. Fent musica............................................................................................................ 8.1. composant...................................................................................................... 9. Més sobre motors.................................................................................................. 9.1. Frenant suaument.......................................................................................... 9.2. Ordres avançades............................................................................................ 9.3. Control per PID................................................................................................ 10. Més sobre sensors.................................................................................................. 11. Comunicació entre robots...................................................................................... 11.1. Enviant missatge entre Amo-esclau................................................................ 11.2. Enviant nombres............................................................................................ 11.3. Enviant ordres............................................................................................... 12. Més ordres............................................................................................................. 12.1. Temporitzadors............................................................................................. 12.2. Escriure en la pantalla.................................................................................... 13. Bibliografia............................................................................................................. 14. Agraïments............................................................................................................
4 4 4 5 5 6 6 6 9 9 9 10 10 12 13 14 16 17 17 20 21 22 23 23 25 26 27 29 30 31 33 33 33 35 36 38 38 41 43 44 44 45 47
3
Com programar amb NXC 1.Introducció El principal objectiu d’aquesta guia és introduir a l’alumne al món de la programació, és per això que em triat aquest llenguatge, ja que és de nivell alt i els seus resultats es podem veure clarament a través del Lego. Així doncs esperem poder transmetre i fer entendre les nocions bàsiques de la programació, posant com a exemple el llenguatge NXC (Not Exactly C), que és un llenguatge propi del Lego Mindstorm. Per agilitzar i facilitar la programació farem ús de programes ja fets i explicarem el seu funcionament.
1.1 ¿ Què és Bricx Command Center ? Bricx Command Center és un programa lliure (gratuït) que serveix per programar amb NXC, la principal aplicació del qual és la de compilar el que hem escrit. Per tant la funció bàsica del programa és traduir el que escrivim al llenguatge que utilitza el robot, ja que aquest no pot entendre directament les ordres que escrivim.
1.2 El format de BricxCC BricxCC treballa amb fitxers .nxc, aquesta és la extensió pròpia del llenguatge NXC. Aquests fitxers no poden ser llegits directament per el robot, només serveixen per treballar amb altres programes compatibles. Sempre que guardem un programa ens hem d’assegurar d’haver guardat en .nxc, ja que BricxCC ofereix altres extensions però aquestes no són totalment compatibles amb el llenguatge que introduirem més endavant.
4
2. Primer contacte amb BricxCC 2.1 Iniciant per primer cop el programa Després de fer dos clics sobre accés al programa sortirà una finestra amb un títol que diu Searching for the Brick, que serveix per configurar la connexió amb el brick del Lego (l'anomenat totxo).
Apareixen tres apartats en els quals hem de seleccionar la configuració que farem servir per connectar el nostre robot. - Port: hem d’escollir quin port farem servir per connectar el robot a l’ordinador. - Brick Type: hem de seleccionar el model del nostre brick. - Firmware: hem de seleccionar el firmware que farem servir. Important ! La configuració amb la que treballarem serà la següent: - Port: USB o automàtic, preferiblement USB, si establim una connexió amb el robot per bluetooth per tal d’evitar cables és preferible seleccionar automàtic. - Brick Type: NXT - Firmware: Standard Podeu veure a la següent imatge com quedaria. Un cop seleccionat el paràmetres de connexió podem continuar clicant al botó Ok. NOTA: encara que hàgim establert els paràmetres de connexió això no vol dir que el robot estigui sincronitzat amb l’ordinador (connectat), és per això que normalment sortirà un missatge advertint que el Brick no està connectat. Més endavant explicarem com sincronitzar Brick i PC. No cal connectar el Brick a l’ordinador per programar.
5
2.2 L’entorn A primera vista BricxCC té un entorn visual molt semblant al de qualsevol programa. Conté totes les barres de menús típiques dels programadors. A continuació enumerarem i explicarem la funció de cada botó.
2.3 Barra de Menús
En aquest apartat enumerarem els principals menús i les seves accions més característiques, però no anirem més enllà, ja que el seu ús és molt intuïtiu i no cal conèixer totes les ordres per poder fer anar el programa. -
Menú File (Arxiu)
Potser el més conegut, ja que tots els programes conten amb aquest menú. Les opcions són les més bàsiques que podem trobar: nou fitxer, obrir fitxer, guardar, guardar com, tancar fitxer, imprimir, tancar programa...
6
-
Menú Edit (Editar)
Un altre dels clàssics. Aquest està relacionat amb les típiques accions: fer, desfer, copiar, enganxar, tallar, esborrar, preferències...
-
Menú Search (Buscar)
Aquest menú serveix per buscar paraules, ordres o línies de programa i reemplaçar paraules.
-
Menú View (Veure)
Serveix per seleccionar les barres que volem veure.
-
Menú Compile (Compilar)
Conté totes les opcions relacionades amb la compilació del programa i la connexió entre el robot i l'ordinador. Cal esmentar que l’ordre compilar només compila el programa i cal l’ordre download per transmetre el programa al robot.
7
-
Menú Tools (Eines)
És el menú més interessant, ja que conté un munt d’aplicacions preparades específicament per al Lego.
-
Menú Window (Finestra)
Mostra i modifica l’organització dels arxius oberts.
-
Menú Help (Ajuda)
Conté tot tipus d’ajuda interactiva. Potser l’opció més atractiva és la de Index que ens mostra un buscador que possibilita la cerca dels diferents tipus d’ordres característiques del llenguatge i ens explica el seu ús. La guia està, però, en anglès.
8
2.4 Barra d’eines
És una barra plena d’icones, que són accessos ràpids a les diferents opcions dels menús. Aquesta és més útil per treballar, ja que conté les accions més utilitzades, Si passeu el ratolí per sobre podeu veure de quina acció es tracta, sinó ho podeu veure a la imatge que hi ha a continuació.
2.5 Templates (plantilles)
És una finestra que s’obre simultàniament amb el programa i ens permet veure en forma d’esquema totes les ordres que podem escriure. Si es clica sobre l’acció desitjada aquesta immediatament apareix escrita al fitxer en blanc. Les paraules que surten en color marró són les que poden ser substituïdes per un altre nom o una variable. També ens diuen el tipus d’ordre que hem de posar. Normalment és molt útil per recordar algunes accions concretes.
2.6 Status bar (Barra d’estat)
Està situada a l’esquerra de la pantalla. La seva funció és fer d’índex del programa que estem escrivint. Ens mostra en temps real totes les tasques, funcions i procediments que conté el nostre programa. Si cliquem sobre el nom de la tasca, per exemple, ens mourà el cursor al principi d’aquesta, això també passa amb els procediments i les funcions.
9
3. Escrivint el primer programa Un cop ja ens em familiaritzat amb l’entorn arriba l’hora de la veritat: fer un programa. En aquest apartat explicarem les ordres més bàsiques per poder programar el nostre robot. Coses a tenir en compte abans de començar a programar.
Hem de tenir en ment el disseny del robot, per saber quines coses pot fer i quines no.
Hem de tenir clar en quines entrades hem connectat els sensors i en quines els motors. Si no coincideixen amb les del programa el robot no farà res o es tornarà boig.
Preferiblement, s’ha de tindre el robot muntat prèviament per així poder provar el programa, per corregir possibles comportaments no desitjats o errors.
3.1 Escrivint el programa El primer pas i el més senzill és simplement clicar a Menú File – New o a la imatge d’un full en blanc que hi ha a la barra d’eines. Això farà que s’obri un nou fitxer totalment en blanc amb el nom d’Untitled. Aquest és el nom del nostre fitxer i el podrem canviar quan el guardem. A continuació tenim un exemple de programa senzill. #include “NXTDefs.h” task main () { OnFwd(OUT_A,75); OnFwd(OUT_C,75); Wait(4000); OnRev(OUT_AC,75); Wait(4000); Off(OUT_AC); }
// tasca principal // principi de la tasca principal, obre claudàtor // fa girar el motor endavant connectat a la //sortida A al 75 % de potencia // fa girar el motor endavant connectat a la //sortida B al 75 % de potencia // espera 4000 ms = 4 segons // fa girar el motor enrere connectat a la sortida //A i B al 75 % de potencia // espera 4000 ms = 4 segons // apaga els motors connectats a la sortida A i C // final de la tasca principal, tanca claudàtor
Al començament de cada programa sempre posarem la mateixa ordre, #include “NXTDefs.h”, això vol dir que el programa inclogui aquest fitxer, és per això que hem de posar aquest fitxer a la mateixa carpeta que el programa. Aquest fitxer és vital per al funcionament del programa. Després d’això sempre comencem el programa posant la tasca principal, task main(). El robot al encendres el primer que farà serà llegir que hi ha escrit aquí. Més endavant veurem el seu ús. Un cop hem escrit task main fem espai i obrim claudàtor ({). A continuació posem les ordres que volem que faci el nostre robot. Quan acabem d’escriure-les, fem un espai i tanquem claudàtor (}). Això serveix per indicar el fi de la tasca, en aquest cas el fi del programa.
10
Descripció de les ordres: OnFwd ( sortida ,potència ), fa girar el motor cap endavant. Les sigles signifiquen On = engegar, Fwd = Forward = endavant. Ex. OnFwd(OUT_AC,75)
o
Sortida: hem de posar quina sortida o motor ha de fer l’acció, pot ser més d’un. Ex. (OUT_B) o (OUT_AC) o (OUT_ABC).
o
Potència : hem d’escriure la potència la qual ha de donar al motor, en que 0 equival a nul·la i 100 a la màxima.
OnRev( sortida ,potència ), fa girar el motor cap enrere. Les sigles signifiquen On = engegar, Rev = enrere. Ex. OnRev(OUT_AC,75)
o
Sortida: hem de posar quina sortida o motor ha de fer l’acció, pot ser més d’un. Ex. (OUT_B) o (OUT_AC) o (OUT_ABC).
o
Potència : hem d’escriure la potència que ha de donar al motor, en que 0 equival a nul·la i 100 a la màxima.
Wait (temps), fa que el robot s’adormi durant un període de temps. Ex. Wait(3000)
o
Temps: hem d’escriure el temps desitjat, aquest sempre estarà en milisegons (ms). 1000ms = 1s
Off(sortida), apaga la sortida desitjada.
o
Sortida: hem de posar quina sortida o motor ha de parar-se, pot ser més d’un. Ex. (OUT_B) o (OUT_AC) o (OUT_ABC).
Important!
Al final de cada ordre s’ha deposar sempre (;), sinó donarà error.
Només s’ha de posar (;) a les accions, moviment de motors, espera... no a les condicions o tasques.
Cada vegada que obris claudàtors cal tancar-los.
Per treballar amb més comoditat és recomanable treballar amb nivells, com es veu en els exemples.
11
Què fa el programa ? El programa que em vist anteriorment fa que el robot avanci durant quatre segons, desprès fa marxa enrere també durant quatre segons. Un cop fet això el programa s’acaba. Com passem el programa al robot ? 1. Hem de compilar el programa. Fes clic sobre l’icona de compilar o prem F5. Si no hi ha hagut errors pots continuar, sinó més endavant parlarem dels errors. 2. Encén el robot 3.
Un cop compilat el programa ja esta preparat per ser transferit al robot, clica l’icona de descarregar (al costat del de compilar) o prem F6.
4. Si diu que no detecta el robot has de clicar el boto Find Brick, un cop sincronitzat amb el robot pots continuar. 5. Si el robot fa un “bip” vol dir que el programa s’ha transferit correctament. Com executem el programa des del robot ? 1. Accedim a My Files 2. Desprès fem clic sobre Software files 3. Finalment seleccionem el programa i donem a acceptar. 4. Quan el robot acabi el programa podem tornar-lo a executar prement un altre cop el botó taronja.
3.2 Possibles errors El més possible és que quan comencis a programar per el teu compte i compilis et digui que hi ha errors. Aquests es mostren en la part inferior del programa i et diu a la línia en què hi ha l’error. El més comú es: “ ; expected” o “ } expected “ això vol dir que t’has oblidat de posar un ; o }. Per altra banda també hi ha els errors de programació. Simplement el robot no fa el que tu vols, això és un problema del programa que no ha estat escrit correctament.
12
4. Utilitzant variables Què és una variable ? Una variable és un símbol (paraula) que representa un element, en aquest cas un número que farem servir molts cops. Com fem servir variables ? Hi ha dues formes de fer servir variables 1. Al principi del programa, abans de task main, hem d’escriure #define nom de la variable i valor. Ex. #define temps
4000
task main () { OnFwd(OUT_A,75); OnFwd(OUT_C,75); Wait(temps);
// declarem temps com a variable i li donem un //valor.
// temps és substituït per el numero 4000
OnRev(OUT_AC,75); Wait(temps);
// temps és substituït per el numero 4000
Off(OUT_AC); }
2. Al principi del programa, abans de task main, escrivim int nom de la variable, acabem amb punt i coma (;). Més endavant podem donar el valor que vulguem a la variable. Es poden posar més d’una seguida del int, però s’han de separa per comes. Ex. int temps;
// declarem temps com a variables
task main () { temps = 4000 OnFwd(OUT_A,75); OnFwd(OUT_C,75); Wait(temps); OnRev(OUT_AC,75); Wait(temps);
// Durant aquesta tasca temps valdrà 4000
// temps és substituït per el numero 4000
// temps és substituït per el numero 4000
Off(OUT_AC); }
13
5. Estructures de control A vegades vols que una certa part del programa sigui executada només en unes certes condicions. En aquest cas utilitzem les estructures de control. Per exemple, volem que un robot vagi en línia recta i que giri a esquerra aleatòriament. Vegem el programa: #define temps_recte #define temps_gir 250 task main () { while (true) { OnFwd(OUT_AC,75); Wait(temps_recte); if (Random() >= 0) { OnRev(OUT_C,75); } else { OnRev(OUT_A,75); } Wait(temps_gir); } }
500
// obro tasca // mentre que sigui veritat fes el que hi ha // obro condició (while)
// Si Random (numero aleatori) és més gran o // igual a 0 fes això // obro acció (if) // tanco acció (if) // sinó és més gran o igual a 0 fes això // obro acció (else) // tanco acció (else) // tanco condició (while) // tanco tasca
Descripció de les ordres:
while(condició) { tasca...} : mentre que la condició entre parèntesis sigui certa el robot fa la tasca que hi ha dins del while. o
Condició: aquesta pot ser una igualtat, comparació... és molt comú l’ús de true o 1 com a condició, ja que aquests sempre són veritat . Això vol dir que el programa farà un bucle infinit.
if (condició) {tasca...} : si el que hi ha entre parèntesis és cert, farà la tasca escrita. Fins que no acaba la condició no tornarà a comprovar si és certa. o
Condició: normalment es posen comparacions o igualtats. No és com el while, no s’escriu true o 1. També hi ha false, sempre serà fals, o 0 que equival a false.
14
Else {tasca...} : sempre va acompanyat del if. El que fa és que si la condició del if no és certa llavors fa la tasca que hi ha dintre de l’else. És per això que no necessita condició, treballa quan la condició anterior no es compleix.
Random (valor): aquesta ordre posa un numero aleatori. No és molt útil. o
Valor: s’escriu un número. Llavors el número aleatori estarà compres entre el número escrit i 0. També poden haver-hi números negatius. Si no s’escriu res posa un número aleatori qualsevol.
Què fa el programa ?
Entra en el bucle, ja que mentre true sigui veritat farà el que hi ha escrit. I true sempre serà veritat.
Avança en línia recta durant un temps
Escriu un número aleatori, si aquest és més gran o igual a 0 gira a l’esquerra.
Si no es així gira a la dreta
Quan acaba espera un altre temps, i torna al principi de while.
El programa no acabarà mai ja que true serà sempre cert i no sortirà del bucle.
Igualtats i comparacions. == < <= > >= != += -= && ||
Igual que... Més petit que... Més petit o igual que... Més gran que... Més gran o igual que... No igual que... incrementa decrementa i o
Exemples ttt != 3
veritat si ttt no és igual a 3
(ttt >= 5) && (ttt <=10)
fals si ttt esta entre 5 i 10
(aaa == 10) || (bbb=10)
Veritat si aaa o bbb o els dos són igual a 10
15
5.1 Altres tipus d’estructura de control (do) Hi ha una altra forma d’estructura de control, do, i té la següent forma: do { Ordres } while (condició);
Mentre que la condició del while sigui certa farà les ordres. En aquest cas hem de posar (;) al final del while. Exemple: int temps_mov, temps_gir, temps_total;
// declara totes les variables
task main () { temps_total = 0; do
// obre tasca principal // dóna valor a la variable temps_total // mentre que temps total sigui més petit que // 20000 farà això { // obre ordre do temps_mov = Random(1000); // dóna un valor aleatori a la // variable temps_gir = Random(1000); // dóna un valor aleatori a la // variable OnFwd(OUT_AC,75); Wait(temps_mov); OnRev(OUT_C,75); Wait(temps_gir); temps_total += temps_mov // incrementa temps_mov a // temps_total temps_total += temps_gir // incrementa temps_gir a //temps_total } // tanca ordre do while (temps_total > 20000); // dóna condició a do Off(OUT_AC); // apaga els motors } // tanca tasca principal
Què fa el programa ?
Mentre que temps_total sigui més petit que 20000 farà el que hi ha escrit al do. o
Dins el do es van assignant valors aleatoris compresos entre el 1000 i el 0, i es van sumant al temps total.
o
El robot va cap endavant i girant aleatòriament.
Quan temps_total és més gran de 20000 acaba el programa.
16
6. Sensors Els sensors són els aparells que ens permeten interactuar amb l’entorn, agafant dades constantment i enviant-les al robot. Segons el que escrivim al nostre programa podrem controlar les reaccions del nostre robot amb l’entorn. Aquesta és una de les parts més importants del programa, ja que és la que ens permet fer interactuar el Lego amb l’ambient. Per començar a fer servir els sensors, posarem un exemple del sensor de tacte, possiblement el sensor més fàcil d’utilitzar.
6.1 Sensor de tacte Treballa en mode boolea, això vol dir que només dona 0 i 1, 0 quan no està polsat i 1 quan està polsat. Esperant un sensor task main () { SetSensor(IN_1,SENSOR_TOUCH); OnFwd(OUT_AC,75); until (SENSOR_ 1 == 1); Off(OUT_AC); }
// // // // // // //
obro tasca principal configuro el sensor els motors A i B van recte quan sensor_1 sigui 1 continuarà a la següent ordre apaga motors tanca tasca principal
Descripció de les ordres:
SetSensor( port entrada, tipus de sensor ): configura el sensor per al seu ús. Diu al robot que té connectat un sensor d’un tipus concret connectat a una determinada entrada. o
Port entrada: Es posa el numero de la entrada a la qual esta connectat el sensor. Hi ha quatre entrades: IN_1, IN_2, IN_3, IN_4.
o
Tipus de sensor: s’escriu el tipus de sensor que s’ha connectat a l’entrada (amb aquesta ordre només es poden configurar determinats sensors):
SENSOR_TOUCH: sensor de tacte
SENSOR_LIGHT:sensor de llum
SENSOR_ROTATION: sensor de rotació
until(condició): és una estructura de control, significa que faci la tasca anterior fins que la condició de l’until es compleixi. Un cop complerta passarà a la següent ordre. o
Condició: igualtat,comparació...
17
Què fa el programa ?
Configura el sensor de tacte amb l’entrada 1.
El robot va cap endavant, fins que el sensor de tacte és 1.
Quan el sensor de tacte és 1, el robot s’atura.
Important!
Cada sensor està configurat amb un tipus de funcionament.
Aquest funcionament es pot canviar.
Taula de configuració estàndard dels sensors Tipus de sensor Tacte
Mode de funcionament Boolea
Llum (ambiental o per reflex)
Tant per cent (llum que rep)
Ultrasò
Longitud (cm)
Micròfon
Tant per cent (soroll)
Servomotor
Graus de rotació
Significat 0o1 1 és pressionat 0 és no pressionat Del 0 al 100 0 mínim 100 màxim Mesura la longitud en cm Del 0 al 100 0 mínim 100 màxim Mesura els graus que ha rotat el motor 360, una volta
18
Un robot que esquiva obstacles task main() { SetSensorTouch(IN_1); OnFwd(OUT_AC, 75); while (true) { if (SENSOR_1 == 1) { OnRev(OUT_AC, 75); Wait(300); OnFwd(OUT_A, 75); Wait(300); OnFwd(OUT_AC, 75); } } }
// obro tasca // sensor de tacte en l’entrada 1 // // // //
bucle obro bucle si el sensor esta premut continua obro ordre
// tanco ordre (if) // tanco bucle (while) // tanco tasca
Descripció de les ordres:
SetSensorTouch(port entrada): és com l’ordre anterior SetSensor(entrada,sensor), però més simplificada. Cada sensor té la seva ordre especifica. o
Port entrada: es posa el port on hi ha el sensor
Ordres especifiques: o
SetSensorTouch(entrada): sensor de tacte
o
SetSensorUS(entrada): ultrasò
o
SetSensorLowspeed(entrada): també és d’ultrasò, és preferible aquest.
o
SetSensorLight(entrada): sensor de llum.
o
SetSensorSound(entrada): micròfon
Què fa el programa ?
Posa el sensor de tacte a l’entrada 1
El robot va recte i sempre va comprovant el sensor de tacte.
Quan el sensor de tacte sigui 1 farà les ordres assignades. Un cop acabades tornarà a comprovar si el sensor està premut.
Si està premut tornarà a fer les ordres.
Si no esta premut continuarà endavant.
19
6.2 Sensor de llum Hi ha dues formes de fer servir el sensor de llum. Amb la llum reflectant encesa: el sensor té una llum de color vermell i llegeix el reflex que retorna d’aquesta llum. O amb la llum apagada: llegeix la llum ambiental. Normalment es fa servir amb la llum encesa, ja que és més útil i pot servir per seguir línies i distingir colors. Un robot que segueix línies #define llindar 40
// declaro la variable llindar
task main() { // obro tasca principal SetSensorLight(IN_3); // poso el sensor de llum a l’entrada 3 OnFwd(OUT_AC, 75); while (true) // bucle { // obro bucle if (Sensor(IN_3) > llindar) // si sensor més gran que llindar { // obro ordre de la condició OnRev(OUT_C, 75); Wait(100); until(Sensor(IN_3) <= llindar); // fins que llindar sigui més //gran o igual que el que rep //el sensor OnFwd(OUT_AC, 75); } // tanco ordre (if) } // tanco bucle } // tanco tasca
Què fa el programa ?
Defineixo la variable llindar. El llindar és el número que marca el límit entre negre (ralla que ha de seguir) i blanc (el terra).
Posa el sensor de llum en l’entrada 3. Si no s’especifica el contrari, el sensor de llum sempre treballa amb el llum de reflex encès.
Entra en un bucle (while)
Si el que detecta el sensor és més gran que el llindar girarà a la dreta.
Fins que el llindar sigui igual o superior, el robot continua anant recte.
El robot avançarà seguint el llindar blanc-negre, la línia que separa blanc i negre. Pot semblar que faci un zig-zag.
El número del llindar s’ha d’ajustar per a cada lloc, perquè depèn de la llum ambiental.
20
6.3 Sensor de so Aquest sensor és molt semblant al de tacte, encara que funcioni en el mode de tant per cent, podríem descriure el seu ús com un mode boolea. Serveix, normalment, per activar el robot un escolta un soroll fort, com un xiulet o picar de mans. 100 és el màxim i 0 el mínim de so #define llindar 40 #define MIC SENSOR_2
task main() { SetSensorSound(IN_2); while(true) { until(MIC > llindar);
// defineixo llindar // defineixo MIC, aquest substituirà a //SENSOR_2
// obro tasca // sensor de so a l’entrada 2 // obro bucle // fins que el que capta sensor 2 no sigui // més gran que el llindar no continuïs
OnFwd(OUT_AC, 75); Wait(300); until(MIC > llindar); // // Off(OUT_AC); Wait(300); } // } //
fins que el que capta sensor 2 no sigui més gran que el llindar no continuïs
tanco bucle tanco tasca
Què fa el programa ?
Defineix la variable llindar, i MIC (cada vegada que escriu MIC, ho substitueix per SENSOR_2)
Posa el sensor de so a l’entrada 2
Entra en un bucle (while)
Fins que no hi hagi un so més gran que el llindar no començarà a fer res.
Quan això succeeix, el robot va recte fins que hi hagi un altre so que l’aturi.
Quan s’atura espera una estona i torna a esperar un so més fort que el llindar.
21
6.4 Sensor d'ultrasò Aquest sensor treballa com un sonar, llança ones que reboten en els objectes i retornen. Quan retornen el sensor les llegeix i compte els temps que han trigat en retornar, així pot calcular la distància. Quan escrivim un numero treballant amb aquest sensor, el robot l’interpreta com a centímetres. Amb aquest sensor poden fer que el robot eviti un obstacle sense haver xocat prèviament (per evitar-lo havent xocat s’utilitza el sensor de tacte). Esquivant objectes #define PROPER 15
// defineix variable proper
task main() { // obre tasca principal SetSensorLowspeed(IN_4); // sensor ultrasò a l’entrada 4 while(true) { // obro bucle OnFwd(OUT_AC,50); while(SensorUS(IN_4)>PROPER); // mentre que Off(OUT_AC); OnRev(OUT_C,100); Wait(800); } // tanco bucle } // tanco tasca
Què fa el programa ?
Defineix la variable proper, li carregarem 15 cm.
Col·loca el sensor d’ultrasò a l’entrada 4
Entra en un bucle (while)
El robot va recte fins que es troba un objecte a 15 cm.
Llavors es para i gira.
Quan acaba torna a anar recte.
Amb aquest capítol de sensors volíem donar a conèixer el funcionament bàsic. Hi ha molts més modes d’us i diferents tipus de sensors, però com que no s’inclouen en el pack educatiu em decidit de no explicar-los. Més endavant podreu veure els diferents modes dels sensors.
22
7. Tasques i subrutines Fins ara tos els nostres programes només tenien una tasca, però amb NXC podem fer més d’una tasca. També és possible posar subrutines, un tros de codi que pots posar en diferents parts del programa sense necessitat de repetir-lo. Si fas servir tasques i subrutines faràs més fàcil la comprensió del teu programa i serà més compacte. Vegem-ho !
7.1 Tasques Un programa fet amb NXC pot tenir fins a 255 tasques, cadascuna ha de tindre un nom propi. Tot i així la tasca principal sempre ha d’existir, ja que és la primera en ser executada pel robot. Les altres tasques només seran executades quan la tasca principal ho digui. Per veure les tasques farem un programa on el robot anirà fent quadrats, però quan xoqui amb un obstacle (sensor de tacte), el robot reaccionarà. Si féssim aquest programa amb una tasca seria complicat, ja que hauríem d’anar controlant els motors i comprovant els sensors alhora. És per això que és millor utilitzar dos tasques. Vegem el programa : mutex move;
// definim el nom del mutex
task quadrat() { while (true) { Acquire(move); OnFwd(OUT_AC, 75); Wait(1000); OnRev(OUT_C, 75); Wait(500); Release(move); } }
// tasca moure en quadrat // obro tasca quadrat
task sensors() { while (true) { if (SENSOR_1 == 1) { Acquire(move); OnRev(OUT_AC, 75); Wait(500); OnFwd(OUT_A, 75); Wait(500); Release(move); } } }
// tasca sensors // obro tasca sensors
// obro bucle // obro mutex
// tanco mutex // tanco bucle // tanco tasca quadrat
// obro bucle // obro acció (if) // obro mutex
// // // //
tanco tanco tanco tanco
mutex acció (if) bucle tasca sensors
task main() { Precedes(quadrat, sensors); // inicia les dues tasques SetSensorTouch(IN_1); // poso el sensor de tacte a l’entrada 1 }
23
Descripció de les ordres:
mutex nom mutex : és una variable que prové de mutual exclusion. Es fa servir quan volem evitar que les dues tasques intentin accedir al mateix motor alhora, ja que les tasques estan funcionant simultàniament, i si això succeís el robot es tornaria boig. Funciona com un semàfor: controla l’accés als motors. o Nom mutex: hem de posar el nom que volem donar al mutex, pot ser qualsevol.
Acquire (nom mutex) : es posa a l’inici de les accions que volen ser controlades pel mutex, així permetrà establir un ordre. o Nom mutex: s’ha de posar el nom assignat al mutex anteriorment. Release(nom mutex): es posa al final de l’acció que vol ser controlada pel mutex, així permetrà establir un ordre. o Nom mutex: s’ha de posar el nom assignat al mutex anteriorment. Precedes (tasques): serveix per dir quines tasques ha d’executar el programa. o Tasques: posa el nom de les tasques que conte el programa.
Una mica més a fons... El mutex és una ordre que és fa servir quan es fan anar varies tasques. Quan hi ha més d’una tasca, totes aquestes s’executen alhora. Això pot comportar problemes, ja que si diverses tasques volen accedir al control d’un motor en el mateix moment, aquest farà una o altre de manera aleatòria. Per tal d’evitar això posem el mutex. Al principi de l’ordre que compartiran varies tasques, en la part anomenada secció critica compartida, posem acquire i al final release. Així, quan diverses tasques intentin fer servir el mateix motor, aquest esperarà a que la tasca anterior acabi.
24
7.1.1 Tasques en paral·lel Com em vist abans en NXC les tasques s’executen simultàniament o en paral·lel . Això pot causar alguns problemes quan interfereixen entre elles. task comprova() { while (true) { if (SENSOR_1 == 1) { OnRev(OUT_AC, 75); Wait(500); OnFwd(OUT_A, 75); Wait(850); OnFwd(OUT_C, 75); } } } task moure() { while (true) { OnFwd(OUT_AC, 75); Wait(1000); OnRev(OUT_C, 75); Wait(500); } }
// obro tasca comprovar sensors // obro bucle // si sensor 1 és igual a 1 // obro ordre if
// tanco ordre if // tanco bucle // tanco tasca comprovar sensors
// obro tasca moure // obro bucle
// tanco bucle // tanco tasca moure
task main() { SetSensor(IN_1,SENSOR_TOUCH); Precedes(comprova, moure); }
// // // //
obro tasca principal declaro sensors estableixo les tasques tanco tasca principal
Un programa fallit. Considerem el programa anterior. Hi ha una primera tasca que fa que el robot giri en quadrats i la segona tasca que comprova el sensor de tacte. Quan aquest és pres, el robot tira cap enrere i fa un gir de 90 graus.
Aparentment el programa sembla perfecte, però si s’executa podries comprovar un comportament no desitjat. Succeeix el següent: el robot gira a la dreta i en aquest moment xoca amb alguna cosa. En conseqüència, intentarà retrocedir i a continuació tornarà a xocar. Això passa perquè les dues tasques s’executen alhora, i la primera tasca intervé durant la segona.
Seccions crítiques i mutex Una forma de solucionar aquest problema és assegurar que en cada moment el robot només està executant una tasca. I per això farem servir el mutex.
25
7.1.2 Utilitzant semàfors És una alternativa més casolana a la variable mutex. Una tècnica estàndard per solucionar aquest problema és utilitzar una variable per indicar quina tasca controla els motors en cada moment. Les altres tasques no podran accedir als motors fins que la primera ho indiqui, utilitzant la variable. Aquesta variable sol dir-se semàfors. Suposem que el valor 0 indica que cap tasca esta utilitzant el motor. Ara, quan vulguem que una tasca faci alguna cosa amb els motors, farà servir el següent comandament: until (sem == 0); sem = 1; //Acquire(sem); // Fes alguna cosa amb els motors // secció critica sem = 0; //Release(sem);
Primer esperarem fins que cap necessiti el motor. Després, un cop accedim al motor, direm que l’estem controlant posant un 1 al semàfor. Quan aquest acabi tornarem aposar un 0, per indicar que el motor ja esta lliure.Així doncs, quan el sensor de tacte toqui alguna cosa, aquest esperarà fins que s’alliberi el control dels motors. Vegem el programa. int sem; task quadrat() { while (true) { until (sem == 0); sem = 1; OnFwd(OUT_AC, 75); sem = 0; Wait(1000); until (sem == 0); sem = 1; OnRev(OUT_C, 75); sem = 0; Wait(850); } }
// defineixo variable sem (semàfor)
// obro tasca quadrat // obro bucle // fins que sem igual a 0 no continua // poso semàfor a 1, ocupo motors //poso semàfor a 0, perquè alliberi motors // fins que sem igual a 0 no continua // poso semàfor a 1, ocupo motors // poso semàfor a 0, allibero motors // tanco bucle // tanco tasca
task comprova() { SetSensor(IN_1, SENSOR_TOUCH); while (true) { if (SENSOR_1 == 1) { until (sem == 0); sem = 1; OnRev(OUT_AC, 75); Wait(500); OnFwd(OUT_A, 75); Wait(850); sem = 0; } } } task main() { sem = 0; Precedes(quadrat, comprova); }
// obro tasca comprova // defineixo sensor // // // // //
obro bucle si sensor 1 igual a 1, continua obro ordres if continua si semàfor igual a 0 semàfor igual a 1, ocupa motors
// // // //
semàfor igual a 0, allibera motors tanco ordre if tanco bucle tanco tasca comprova
// // // //
obro tasca principal poso semàfor a 0 poso les tasques tanco tasca principal
L’ús dels semàfors és molt útil quan estem escrivint programes molt complicats amb tasques paral·leles.
26
7.2 Subrutines Algunes vegades necessites la mateixa part de codi en diferents lloc en el teu programa. En aquests casos pots posar aquesta part del codi en una subrutina i donar-li un nom. Així podràs executar aquesta part del programa en qualsevol lloc, només l'hauràs de cridar. Vegem un exemple. sub volta(int pwr) { OnRev(OUT_C, pwr); Wait(900); OnFwd(OUT_AC, pwr); } task main() { OnFwd(OUT_AC, 75); Wait(1000); volta(75); Wait(2000); volta(75); Wait(1000); volta(75); Off(OUT_AC); }
// estableixo subrutina volta, amb una variable // obro subrutina
// tanco subrutina
// obro tasca principal
// credo subrutina i poso 75 a la variable // credo subrutina i poso 75 a la variable // credo subrutina i poso 75 a la variable // tanco tasca principal
Descripció de les ordres: Sub (nom de la rutina) (variables): serveix per cridar una part de codi. Cada cop que escrivim el nom de la rutina, farà les ordres que hi ha. o Nom de la rutina: es posa el nom que vols donar a la rutina o Variables: serveix per declarar variables que s’utilitzen dins de la rutina, en aquest cas la potència. Així podrem variar la potència cada cop que fem servir la rutina. Si la subrutina no té variables només es posen dos parèntesis sense res dintre. Quan les subrutines són curtes és millor fer servir les funcions in line. A diferència de les subrutines no són emmagatzemades a part, sinó que són copiades en cada lloc en que són necessàries. Per això ocupen més espai. Poden ser declarades de la següent forma: inline int nom( argument ) { //cos; return x*y; }
Descripció de l’ordre: Té el mateix ús que la subrutina
Nom: nom de la subrutina Arguments: es posen entre parèntesis les variables que pot tenir.
27
Si apliquem aquest tipus de subrutina a l’exemple anterior quedaria així: inline void volta() { OnRev(OUT_C, 75); Wait(900); OnFwd(OUT_AC, 75); } task main() { OnFwd(OUT_AC, 75); Wait(1000); volta(); Wait(2000); volta(); Wait(1000); volta(); Off(OUT_AC); }
// defineixo volta // obro orders volta
// tanco ordres volta
// obro tasca principal
// crido volta // crido volta // crido volta // tanco tasca principal
En el mateix exemple també podem fer servir el temps de gir com una variable, llavors quedaria així: inline void volta(int pwr, int temps)
// defineixo volta I les // seves variables // obro ordres volta
{ OnRev(OUT_C, pwr); Wait(temps); OnFwd(OUT_AC, pwr); } task main() { OnFwd(OUT_AC, 75); Wait(1000); volta(75, 2000); Wait(2000); volta(75, 500); Wait(1000); volta(75, 3000); Off(OUT_AC); }
// tanco ordres volta
// obro tasca principal
// crido volta numero les variables // crido volta numero les variables // crido volta numero les variables // tanco tasca principal
28
7.2.2 Fent servir macros També hi ha una altra forma de fer servir una part del codi en qualsevol lloc. En NXC es poden definir macros. Com abans havíem vist, podem definir constants fent servir #define i posant-hi un nom. Però també la podem utilitzar per definir qualsevol part de codi. A continuació podeu veure l’exemple anterior però fent servir macros. #define volta \ // defineixo volta OnRev(OUT_B, 75); Wait(3400);OnFwd(OUT_AB, 75); task main() { // obro tasca principal OnFwd(OUT_AB, 75); Wait(1000); volta; // crido volta Wait(2000); volta; // crido volta Wait(1000); volta; // crido volta Off(OUT_AB); } // tanco tasca principal
Després de #define es posa el nom que volem donar, i a continuació és posen les ordres. També es pot posar (/) i escriure a sota. És una forma de continuar escrivint en la mateixa línia. També es poden definir les variables com en els exemples anteriors, vegem-ho. #define gira_dreta(s,t) \ //defineixo gira_dreta OnFwd(OUT_A, s);OnRev(OUT_B, s);Wait(t); #define gira_esquerra(s,t) \ // defineixo gira_esquerra OnRev(OUT_A, s);OnFwd(OUT_B, s);Wait(t); #define endavant(s,t) OnFwd(OUT_AB, s);Wait(t);// defineixo endavant #define enrere(s,t) OnRev(OUT_AB, s);Wait(t); // defineixo enrere task main() { enrere(50,10000); endavant(50,10000); gira_esquerra(75,750); endavant(75,1000); enrere(75,2000); endavant(75,1000); gira_dreta(75,750); endavant(30,2000); Off(OUT_AB); }
// // // // // // // // //
obro tasca principal crido enrere i poso número a les variables crido endavant i número variables crido gira_esquerra i número variables crido endavant i número variables crido enrere i poso número a les variables crido endavant i número variables crido gira_dreta i número variables crido endavant i número variables
// tanco tasca principal
Fer servir macros fa que el codi sigui més compacte i llegible, també pots canviar el programa més fàcilment.
29
8. Fent música Una de les característiques del Lego és que incorpora un altaveu, molt útil quan vols saber si passa alguna cosa. Però també pot ser una forma de fer el robot més divertit, pot fer música mentre camina. Reproduint fitxers de so: BricxCC té una utilitat per convertir els fitxers .wav en .rso (llegibles pel Lego). Pots accedir-hi en eines, conversió de so. Després pots emmagatzemar els fitxers .rso en la memòria flash del Lego utilitzant una altra aplicació a eines, NXT explorer, i fer-la sevir amb el comandament següent: PlayFileEx(nom fitxer, volum, repetició? )
Descripció de l’ordre:
PlayFileEx(nom, volum, repetició): serveix per reproduir arxius de so. o
Nom: s’ha d'introduir el nom del fitxer que es vol reproduir. Prèviament ha d’haver sigut emmagatzemat a la memòria del Lego.
o
Volum: s’ha de posar el nivell de volum, va del 0 al 4.
o
Repetició: serveix per dir si vols que ho repeteixi. TRUE o 1 per dir que repeteixi, i FALSE o 0 per dir que només la reprodueixi un cop.
A continuació podem veure un exemple, a més de la implementació d’aquesta nova ordre. També podem veure la utilitat de fer servir macros, com ja vam veure en un altre capítol. #define TIME 200 // defineixo variables #define MAXVOL 7 #define MINVOL 1 #define MIDVOL 3 #define pause_4th Wait(TIME) // defineixo macros #define pause_8th Wait(TIME/2) #define note_4th \ PlayFileEx("! Click.rso",MIDVOL,FALSE); pause_4th #define note_8th \ PlayFileEx("! Click.rso",MAXVOL,FALSE); pause_8th task main() { // obro tasca principal PlayFileEx("! Startup.rso",MINVOL,FALSE);// reprodueixo la cançó Wait(2000); note_4th; // crido macros note_8th; note_8th; note_4th; note_4th; pause_4th; note_4th; note_4th; Wait(100); } // tanco tasca principal
El programa reprodueix la mateixa cançó canviant les variables.
30
8.1 Composant Per dir al robot que faci un to concret pots fer servir l’ordre següent: PlayToneEx(freqüència, duració, volum, repetició?)
Descripció de l’ordre:
PlayToneEx(freqüència,duració,volum, repetició): serveix per dir que vols reproduir un to concret. o
Freqüència: és posa la freqüència del to, també es pot dir el nom de la nota. Per exemple : TONE_B7. Si es posa això reproduirà el si 7.
o
Duració: es posa en ms la duració del so.
o
Volum: es per dir el volum, del 0 al 4.
o
Repetició: serveix per dir si la vols repetir. TRUE o 1 per dir que repeteixi, i FALSE o 0 per dir que només la reprodueixi un cop.
També hi ha una altra ordre per reproduir sons, és molt semblant a l’anterior. PlayTone(freqüència, duració)
Són els mateixos paràmetres que l’anterior, però no inclou repetició ni volum. A continuació pots veure una taula d'equivalències on diu quina freqüència correspon a cada nota musical. So B A# A G# G F# F E D# D C# C
3
4 247 233 220
5 494 466 440 415 392 370 349 330 311 294 277 262
6 988 932 880 831 784 740 698 659 622 587 554 523
7
8
1976 1865 1760 1661 1568 1480 1397 1319 1245 1175 1109 1047
3951 3729 3520 3322 3136 2960 2794 2637 2489 2349 2217 2093
9 7902 7458 7040 6644 6272 5920 5588 5274 4978 4699 4435 4186
14080 13288 12544 11840 11176 10548 9956 9398 8870 8372
També pots crear petites peces musicals molt fàcilment fent servir el piano que incorpora BricxCC en eines. Si vols que el robot faci musica mentre fa alguna acció el més fàcil és que facis dos tasques que es fan simultàniament.
31
A continuaci贸 podeu veure un exemple. task musica() { while (true) { PlayTone(262,400); PlayTone(294,400); PlayTone(330,400); PlayTone(294,400); } }
// obro tasca musica
Wait(500); Wait(500); Wait(500); Wait(500);
task moure() { while(true) { OnFwd(OUT_AC, 75); Wait(3000); OnRev(OUT_AC, 75); Wait(3000); } } task main() { Precedes(musica, moure); }
// // // // // // //
obro bucle reprodueix so durant reprodueix so durant reprodueix so durant reprodueix so durant tanco bucle tanco tasca musica
i i i i
espera espera espera espera
// obro tasca moure // obro bucle
// tanco bucle // tanco tasca moure
// obro tasca principal // poso les tasques // tanco tasca principal
32
9. Més sobre motors A més de les ordres que em vist hi ha un nombre addicional que pots fer servir per controlar els motors amb més precisió.
9.1 Frenar suaument Quan fem servir l’ordre Off() el motor es para immediatament, bloquejant la roda i mantenint la posició. Hi ha una altra forma de parar-los més suaument . Per això utilitzem Float () o Coast (). Les dos fan el mateix, tallen el subministrament elèctric que arriba als motors. A continuació podem veure un exemple. task main() { OnFwd(OUT_AC, 75); Wait(500); Off(OUT_AC); Wait(1000); OnFwd(OUT_AC, 75); Wait(500); Float(OUT_AC); }
// obro tasca principal
// freno amb float // tanco tasca principal
Primer el robot frena com sempre, i després frena suaument.
9.2 Ordres avançades Les ordres que fèiem servir fins ara OnFwd () i OnRev () són simples ordres per moure els motors. Els motors del Lego estan dissenyats per poder ser controlats de manera molt precisa, tant la posició com la velocitat. Si vols que el teu robot vagi completament recte pots sincronitzar un parell de motors, així els dos correran amb les mateixes característiques. A continuació podem veure un conjunt d’ordres . task main() { // OnFwdReg(OUT_AC,50,OUT_REGMODE_IDLE); // Wait(2000); Off(OUT_AC); PlayTone(4000,50); // Wait(1000); OnFwdReg(OUT_AC,50,OUT_REGMODE_SPEED); // Wait(2000); Off(OUT_AC); PlayTone(4000,50); // Wait(1000); OnFwdReg(OUT_AC,50,OUT_REGMODE_SYNC); // Wait(2000); Off(OUT_AC); } //
obro tasca principal utilitzo mode IDLE
reprodueix so utilitzo mode speed
reprodueix so utilitzo mode SYNC
tanco tasca principal
33
Descripció de les ordres:
OnFwdReg(sortida, potència, mode): fa girar cap endavant els motors amb les característiques que se li assignen. o
Sortida: es posa les sortides dels motors que volen fer-se servir.
o
Potència: es posa la potència amb que giraran les rodes. De 0 a 100.
o
Mode: es posa el mode que es vol fer servir:
OUT_REGMODE_IDLE: si es selecciona aquest mode no hi haurà cap regulació.
OUT_REGMODE_SPEED: aquest mode manté la velocitat constant. Si intentes frenar la roda veuràs com incrementa la potencia per mantindre la velocitat.
OUT_REGMODE_SYNC: fa que els motors seleccionats estiguin sincronitzats, girin per igual. Si mentre funciona en aquest mode pares la roda podràs veure com es para l’altra roda.
Atenció ! Aquesta ampliació també es pot fer servir per l’altra ordre, OnRevReg(). Què fa el programa: Ens mostra els diferents tipus d’ús dels motors, per això cada cop que canvia de mode emet un so. Altres ordres semblants
OnFwdSync(port,velocitat,graus_gir): fa girar els motors sincronitzadament amb un grau. o
Port: port on són connectats els motors.
o
Velocitat: que vols donar o potència.
o
Graus_gir: s’indica el tant per cent de gir que vols fer, va de -100 a 100.
RotateMotor(port, potència, graus): serveix per dir els graus que vols que giri el motor, les voltes. o
Port: port on són connectats els motors.
o
Potència: que vols donar o potència.
o
Graus: es posen els graus que ha de girar el motor, les voltes que farà la roda.
34
Atenció! Aquesta ampliació també es pot fer servir per l’altra ordre, OnRevSync().
9.3 Control per PID El programa NXT incorpora un control digital PID (proportional integrative derivative). Serveix per controlar els servomotors amb precisió. Aquest tipus de sensor és un dels més senzills però més eficaç com a controlador de bucle de realimentació tancada, més coneguda com automatització, i s’utilitza molt sovint. Si voleu saber més podeu trobar més informació en la guia de programació en NXC en anglès. Aquí no explicarem el seu ús perquè és molt complex.
35
10. Més sobre sensors En capítols anteriors discutíem sobre els aspectes bàsics dels sensors, però hi ha moltes més ordres que es poden fer servir. La instrucció SetSensor () que vam veure abans, en realitat, fa dues coses: ajusta el tipus de sensor i assigna la forma en que el sensor actua. Definint per separat la manera i el tipus del sensor, podem controlar el seu comportament d'una forma més precisa. El tipus de sensor s'ajusta amb l’instrucció SetSensorType (). Hi ha molts tipus diferents, però ara veurem només els principals: SENSOR_TYPE_TOUCH, que es correspon al sensor de contacte, SENSOR_TYPE_LIGHT_ACTIVE, que es correspon al de llum (amb el led encès), SENSOR_TYPE_SOUND_DB, que és el sensor de sò i SENSOR_TYPE_LOWSPEED_9V, que és el sensor d'ultrasòns. Ajustar el tipus de sensor és especialment important per indicar si el sensor necessita corrent (per exemple per encendre el led al sensor de llum) o per indicar al NXT que el sensor és digital i ha de llegir-se a través del protocol I2C. El mode del sensor s'ajusta amb l’ordre SetSensorMode (). Hi ha vuit maneres diferents. El més important és SENSOR_MODE_RAW. En aquest mode, el valor que s'obté pel sensor aquesta comprès entre o i 1023. És el valor en brut proporcionat pel sensor. Per exemple, per a un sensor de contacte, quan no està premut és proper a 1023. Quan es prem completament val al voltant de 50. Quan es premi parcialment, el valor que s'obtindrà estarà entre 50 i 1000. Per tant, si configures un sensor de contacte en el mode RAW podràs saber si aquesta completa o parcialment premut. Quan es tracta d'un sensor de llum, els valors van des de 300 (molta llum) fins a 800 (molt fosc). Això proporciona una major precisió que si féssim servir SetSensor (). El segon mode és SENSOR_MODE_BOOL. En aquest mode el valor és 0 o 1. Quan el valor RAW mesurat està per sota de 562, el valor és 0. En cas contrari, 1. SENSOR_MODE_BOOL és el mode per defecte del sensor de contacte, però pot ser utilitzat per altres tipus de sensors. SENSOR_MODE_PERCENT converteix el valor RAW en un valor entre 0 i 100. És el mode per defecte del sensor de llum. . SENSOR_MODE_ROTATION s'utilitza per al sensor de rotació com veurem més endavant. Hi ha dues altres maneres interessants: SENSOR_MODE_EDGE i SENSOR_MODE_PULSE. Comptant transicions, és a dir, canvis des d'un valor baix RAW a un altre alt o viceversa. Per exemple, en prémer un sensor de contacte, es canvia d'un valor RAW alt a un altre baix. Quan l'allibera,passa a l'inrevés. . Quan ajusteu el valor del mode del sensor a SENSOR_MODE_PULSE, només es comptabilitzen les transicions de baix a alt. D'aquesta manera, cada vegada que es pressiona i allibera el sensor, compta com a 1 . Quan seleccionar el mode SENSOR_MODE_EDGE, ambdues transicions són comptades, de manera que cada vegada que es prem i allibera el sensor, compta com 2 . Quan comptes polsos o graus, cal posar a 0 el comptador. Per fer-ho has de fer servir l’ordre ClearSensor (), el qual posa a zero el sensor indicat.
36
Vegem un exemple. El següent programa farà servir un sensor de contacte per conduir el robot. Connecta el sensor de contacte al cable més llarg que tinguis. Si prems el sensor ràpidament dues vegades el robot avança. Si prems un cop s'atura. task main() { // obro tasca principal SetSensorType(IN_1, SENSOR_TYPE_TOUCH);// declaro tipus del sensor SetSensorMode(IN_1, SENSOR_MODE_PULSE);// declaro mode del sensor while(true) { // obro bucle ClearSensor(IN_1); // poso a 0 sensor 1 until (SENSOR_1 > 0); // continua si sensor 1 > 0 Wait(500); if (SENSOR_1 == 1) {Off(OUT_AC);} // si sensor 1 = 1 if (SENSOR_1 == 2) {OnFwd(OUT_AC, 75);} // si sensor 1 = 2 } // tanco bucle } // tanco tasca principal
Atenció ! Ajusta primer el tipus de sensor i desprès el mode. Això es vital ja que el tipus de sensor afecta al mode.
37
11. Comunicació entre robots Si tens més d’un NXT aquest capítol és per a tu. Els robots es poden comunicar entre ells per bluetooth. Pots tenir diversos robots fent tasques o fins i tot construir un robot amb dos NXT, així pots utilitzar 6 motors i fins a 8 sensors. Per començar s’han de connectar els robots que vols comunicar entre si a l’ordinador amb el menú de bluetooth, només així pots enviar missatges per connectar els robots. El NXT que comença la connexió es diu Amo, i pots tenir fins a 3 esclaus connectats (els esclaus obeeixen al Amo), que poden estar connectats en la línia 1,2,3. L’Amo sempre està connectat a la línia 0.
11.1 Enviant missatges entre Amo – Esclau A continuació mostrem dos programes, un per a l’Amo i un altre per a l’esclau. Aquests programes bàsics us ensenyaran com de rapida pot ser la comunicació entre dos NXT via Bluetooth. El programa de l’Amo primer comprova si el esclau esta correctament connectat en la línia utilitzant certes ordres que veurem a continuació. Després envia missatges amb un text; quan l’esclau ha rebut el missatge el mostra per la pantalla LCD del NXT. Vegem el programa. //Amo #define BT_CONN 1 #define INBOX 1 #define OUTBOX 5 sub BTCheck(int conn){ if (!BluetoothStatus(conn)==NO_ERR){ TextOut(5,LCD_LINE2,"Error"); Wait(1000); Stop(true); } }
// defineixo variables
// obro i defineixo subrutina // si no connexió = no error // mostra error a la pantalla
// tanco if // tanco subrutina
task main(){ // string in, out, iStr; // int i = 0; // BTCheck(BT_CONN); // while(true){ // iStr = NumToStr(i); // out = StrCat("M",iStr); // TextOut(10,LCD_LINE1,"Master Test"); // TextOut(0,LCD_LINE2,"IN:"); // TextOut(0,LCD_LINE4,"OUT:"); // ReceiveRemoteString(INBOX, true, in); // SendRemoteString(BT_CONN,OUTBOX,out); // TextOut(10,LCD_LINE3,in); // TextOut(10,LCD_LINE5,out); // Wait(100); i++; // } // } //
obro tasca principal defineixo variables de text defineixo variable i poso número crido subrutina obro bucle transformo i en una lletra ajunto M amb la lletra d’iStr mostro missatge mostro missatge mostro missatge em poso en estat de rebre (in) envio text (out) mostro el que he rebut mostro el que envio incremento i tanco bucle tanco tasca principal
38
Descripció de les ordres:
String nom: serveix per declarar variables de text, és com el int però a diferencia d’aquest les variables string substitueixen un missatge o
BluetoothStatus(línia): serveix per verificar la connexió amb la línia indicada via bluetooth amb el altre Lego. o
o
Cua: el NXT pot rebre fins a 10 missatges. S’ha de seleccionar en quina capsa els vols rebre.
o
Eliminar: true si el vols eliminar o false si no. Quan envia un missatge s’emmagatzema. Si envien un altre a la mateixa capsa es quedarà esperant. si el valor era true no esperarà i s’eliminarà. Si era false esperarà fins que sigui eliminat.
o
Missatge: es posa una variable que substituirà el missatge. El missatge que hem rebut serà equivalent a la variable, i el podrem mostrar.
NumToStr(nombre): serveix per convertir un nombre en una variable string. Converteix un numero en una lletra.
Nombre: es posa el número.
StrCat(text,text): ajunta els dos textos. Exemple quedaria texttext. o
Línia: es posa la línia en que s’ha connectat el esclau. Es vol comprovar la connexió.
ReceiveRemoteString(cua, eliminar, missatge): serveix per rebre missatges.
o
Nom: nom de la variable.
Tex: es posa el text.
SendRemoteString(línia,cua,missatge): envia un missatge. o
Línia: selecciona esclau al que se li enviarà el missatge.
o
Cua: selecciona a quina caixa enviarà el missatge.
o
Missatge: es posa el missatge que es vol enviar. S’ha d’escriure entre cometes.
SendResponseString(cua, text): envia un missatge com a resposta de que ha rebut el missatge de text enviat per l’amo.
39
L’esclau també necessita un programa,aquest és molt similar al de l’Amo. Vegem-ho. //esclau #define BT_CONN 1 #define INBOX 5 #define OUTBOX 1 sub BTCheck(int conn){ if (!BluetoothStatus(conn)==NO_ERR){ TextOut(5,LCD_LINE2,"Error"); Wait(1000); Stop(true); } } task main(){ string in, out, iStr; int i = 0; BTCheck(0); while(true){ iStr = NumToStr(i); out = StrCat("S",iStr); TextOut(10,LCD_LINE1,"Slave Test"); TextOut(0,LCD_LINE2,"IN:"); TextOut(0,LCD_LINE4,"OUT:"); ReceiveRemoteString(INBOX, true, in); SendResponseString(OUTBOX,out); TextOut(10,LCD_LINE3,in); TextOut(10,LCD_LINE5,out); Wait(100); i++; } }
// defineixo variables
// obro i defineixo subrutina // si no connexió = no error // mostro error
// tanco ordre if // tanco subrutina // // // // // // // // // // // // // //
obro tasca principal defineixo variables text defineixo variable crido subrutina obro bucle transformo i en una lletra ajunto S i la lletra d’iStr mostro text mostro text mostro text em poso a rebre (in) envio text (out) mostro el que he rebut mostro el que envio
// incremento i // tanco bucle // tanco tasca principal
Que fa el programa ?
Comprova la connexió amb el robot.
Tant esclau com Amo mostren a la pantalla un missatge.
A mesura que passa el temps les lletres van canviant en els dos.
Això és perquè entre ells es van enviant aquest ordre.
Així doncs podem saber que funciona la connexió entre ells.
Si un dels dos està apagat se t’informarà, encara que l’altre robot continuarà enviant missatges que no rebrà ningú i es perdran. Per evitar això fem servir un protocol que veurem al segon apartat.
40
11.2 Enviant nombres Una altra possibilitat es la de enviar nombres. A continuació veurem un programa en el que es posa en practica. //Amo #define BT_CONN 1 #define OUTBOX 5 #define INBOX 1 #define CLEARLINE(L) \ TextOut(0,L," "); sub BTCheck(int conn){ if (!BluetoothStatus(conn)==NO_ERR){ TextOut(5,LCD_LINE2,"Error"); Wait(1000); Stop(true); } }
// defineixo variables
// defineixo macros
// defineixo subrutina // si no connexió = no error // mostra missatge error
// tanco if // tanco subrutina
task main(){ // obro tasca principal int ack; // defineixo variables int i; BTCheck(BT_CONN); // comprovo connexió TextOut(10,LCD_LINE1,"Master sending");// mostro missatge while(true){ // obro bucle i = Random(512); // i = número aleatori entre 512 CLEARLINE(LCD_LINE3); // crido macros NumOut(5,LCD_LINE3,i); // mostro número en la pantalla ack = 0; // variable igual a 0 SendRemoteNumber(BT_CONN,OUTBOX,i); // envio número until(ack==0xFF) { // continua si variable igual a 255 until(ReceiveRemoteNumber(INBOX,true,ack) == NO_ERR); } // tanco until Wait(250); } // tanco bucle } // tanco tasca principal
Descripció de les ordres:
SendRemoteNumber(línia, cua,número): envia un número a l’esclau. o
Línia: línia on es connecta l’esclau.
o
Cua: caixa a la que envia el número.
o
Número: es posa el número desitjat.
ReceiveRemoteNumber(cua, eliminar,número): serveix per rebre números.
SendResponseNumber(cua,número): envia un número com a resposta de que ha rebut la informació de l’Amo.
41
Programa de l’esclau //esclau #define BT_CONN 1 #define OUT_MBOX 1 #define IN_MBOX 5 sub BTCheck(int conn){ if (!BluetoothStatus(conn)==NO_ERR){ TextOut(5,LCD_LINE2,"Error"); Wait(1000); Stop(true); } }
// defineixo variables
// defineixo subrutina // si no connexió = no error // mostra missatge error
// tanca if // tanca subrutina
task main(){ // obre tasca principal int in; // defineixo variable BTCheck(0); // crido subrutina TextOut(5,LCD_LINE1,"Slave receiving");// mostro missatge SendResponseNumber(OUT_MBOX,0xFF); // envio missatge per respondre while(true){ // obro bucle if (ReceiveRemoteNumber(IN_MBOX,true,in) != STAT_MSG_EMPTY_MAILBOX) { TextOut(0,LCD_LINE3," "); // si numero rebut omple la caixa NumOut(5,LCD_LINE3,in); // mostra missatge text SendResponseNumber(OUT_MBOX,0xFF); // envia un numero de resposta } // tanca if Wait(10); } // tanca bucle } // tanca tasca principal
Què fa el programa ?
Comprova la connexió entre Amo-esclau
L’amo envia un número aleatori
L’esclau el rep i com a resposta envia el número 0xFF (255), que allibera l’Amo i en conseqüència pot continuar amb el programa.
Aquest és un dels protocols que s’utilitza per assegurar que no es perdin missatges.
42
11.3 Enviant ordres Una altra característica de la comunicació bluetooth és que l’Amo pot controlar directament els esclaus. Vegem-ho //Amo #define BT_CONN 1 // defineixo variables #define MOTOR(p,s) RemoteSetOutputState(BT_CONN, p, s, \ OUT_MODE_MOTORON+OUT_MODE_BRAKE+OUT_MODE_REGULATED, \ OUT_REGMODE_SPEED, 0, OUT_RUNSTATE_RUNNING, 0) // defineixo macros sub BTCheck(int conn){ // defineixo subrutina if (!BluetoothStatus(conn)==NO_ERR){ // si no connexió = no error TextOut(5,LCD_LINE2,"Error"); // mostra missatge error Wait(1000); Stop(true); } // tanco if } // tanco subrutina task main(){ // obro tasca principal BTCheck(BT_CONN); // crido subrutina RemotePlayTone(BT_CONN, 4000, 100); // l’amo ordena reproduir un so until(BluetoothStatus(BT_CONN)==NO_ERR);// fins que no connexió = no error Wait(110); RemotePlaySoundFile(BT_CONN, "! Click.rso", false); // l'amo ordena // reproduir un fitxer until(BluetoothStatus(BT_CONN)==NO_ERR);// fins que no connexió = no error Wait(500); RemoteResetMotorPosition(BT_CONN,OUT_A,true);// reseteja posició motors // esclau until(BluetoothStatus(BT_CONN)==NO_ERR);// fins que no connexió = no error MOTOR(OUT_A,100); // crida macros, fa moure els motors de l’esclau Wait(1000); MOTOR(OUT_A,0); // crida macros, fa moure els motors de l’esclau } // tanco tasca principal
Descripció de les ordres:
RemotePlayTone(línia,freqüència,durada): serveix per fer que l’esclau faci un so determinat. o Línia: es posa la línia en què està connectat l’esclau. o Freqüència: es posa la freqüència del so. o Durada: es posa la durada del so en ms. RemotePlaySoundFile(línia,arxiu de so,repetició): mana a l’esclau reproduir un arxiu de so. RemoteResetMotorPosition(línia,sortida motor, brelative): serveix per posar a 0 la posició del motor de l’esclau. RemoteSetOutputState(línia, port, velocitat, mode, regmode, turnpct, runstate, tacholimit): serveix per controlar els motors de l’esclau.
Atenció! No connexió = no error és una doble negació, això vol dir que és igual a connexió = error. Quan això succeeix mostra un missatge d’error
43
12. Més ordres En aquest capítol veurem dos tipus d’ordres: els temporitzadors i com escriure textos a la pantalla del NXT.
12.1 Temporitzadors El NXT té un rellotge intern que va funcionant contínuament. Aquest rellotge intern s’incrementa cada milisegon. A continuació podem veure un exemple d’un programa que ens mostra l’ús del temporitzador. task main() { // long t0, time; // t0 = CurrentTick(); // do // { // time = CurrentTick()-t0; // OnFwd(OUT_AC, 75); Wait(Random(1000)); OnRev(OUT_C, 75); Wait(Random(1000)); } // while (time<10000) // // Off(OUT_AC); } //
obro tasca principal defineixo variables de temps dono valor a variable ordre obro ordre do temps = temps actual – temps inici
tanco do fes l’acció anterior mentre temps sigui a 10 segons tanca tasca principal
Descripció de les ordres:
CurrentTick(): serveix per fer ús del temporitzador interior. El valor es va incrementant en una unitat cada milisegon que passa.
Long nom variable: defineix una variable que substitueix un temps.
Explicació del programa
Defineix variables amb long.
Mentre el temps transcorregut sigui menor a 10 segons el robot es mourà aleatòriament.
Quan el valor del temps transcorregut sigui superior a 10 segons el Lego es pararà.
Normalment els rellotges són molt útils com a substitut de l’ordre Wait(). Pots parar el robot un temps determinat posant a cero el rellotge i esperar fins que arribi a un valor en particular. Però també el pots incloure en altres accions.
44
A continuació veurem un programa en que el robot avança fins que el sensor de tacte reacciona o han passat 10 segons. task main() { // obro tasca principal long t3; // defineixo variable temps SetSensor(IN_1,SENSOR_TOUCH); // defineixo sensor t3 = CurrentTick(); // dono valor a variable OnFwd(OUT_AC, 75); until ((SENSOR_1 == 1) || ((CurrentTick()-t3) > 10000)); // fes ordre anterior fins que sensor 1 = 1 o temps inferior a 10 segons Off(OUT_AC); } // tanca tasca principal
Atenció! Perquè es posa sempre CurrentTick() – t0 ? Posem això perquè la primera vegada que agafa CurrentTick() inscriu l’hora que és, i la declarem com a variable. Llavors per saber el temps que portem restem l’hora actual a la que em agafat fa una estona, la variable.
12. 2 Escriure en la pantalla El NXT té una pantalla de 100x64 píxels en blanc i negre, podem fer ús d’aquesta per escriure missatges o mostrar imatges. Vegem un programa. #define X_MAX 99 #define Y_MAX 63 #define X_MID (X_MAX+1)/2 #define Y_MID (Y_MAX+1)/2 task main(){ int i = 1234; TextOut(15,LCD_LINE1,"Display", true); NumOut(60,LCD_LINE1, i); PointOut(1,Y_MAX-1); PointOut(X_MAX-1,Y_MAX-1); PointOut(1,1); PointOut(X_MAX-1,1); Wait(200); RectOut(5,5,90,50); Wait(200); LineOut(5,5,95,55); Wait(200); LineOut(5,55,95,5); Wait(200); CircleOut(X_MID,Y_MID-2,20); Wait(800); ClearScreen(); GraphicOut(30,10,"faceclosed.ric"); Wait(500); ClearScreen(); GraphicOut(30,10,"faceopen.ric"); Wait(1000);
// defineix variables
}
// tanca tasca principal
// defineix macros // // // // // // // //
obro tasca principal declara i dona valor variable mostra text mostra numero mostra punt mostra punt mostra punt mostra punt
// mostra recta // mostra linia entre dos punts // mostra linia entre dos punts // mostra cercle // neteja pantalla // mostra imatge // neteja pantalla // mostra imatge
45
Descripció de les ordres:
TextOut(x,y,missatge,esborrar pantalla): serveix per mostrar una línia de text a la pantalla. o
X: designa la coordenada x
o
Y: designa la coordenada y. Es pot posar LCD_LINE() y a continuació un numero compres entre l’1 i el 8.
o
Missatge: s’escriu el missatge desitjat entre cometes.
o
Esborrar pantalla: es posa true o false, si no és posa res es equivalent a false. Serveix doncs per dir si vols que s’esborri la pantalla un cop mostrada la imatge.
NumOut(x,y,valor,esborrar pantalla): serveix per escriure un número a la pantalla.
PointOut(x,y, esborrar pantalla): serveix per mostrar un punt en la pantalla.
RectOut(x,y,amplada,altura): serveix per mostrar una línia a la pantalla.
LineOut(x1,y1,x2,y2): dibuixa una línia que va des de (x1,y1) fins (x2,y2)
CircleOut(x,y,radi): dibuixa una circumferència.
ClearScreen(): neteja la pantalla.
GraphicOut(x,y,nom del fitxer): mostra un gràfic a la pantalla, l’arxiu ha d’estar en .ric.
46
13. Bibliografia
Aquesta guia de programació és, en part, al català d’altres guies. Entre les quals trobem: -
“Programming LEGO NXT Robots using NXC”, by Daniele Benedettelli. (Anglès)
-
“Tutorial de NXC para programar robots LEGO Mindstorms NXT”, per Daniele Benedettelli, traducció de Victor Gallego. (Castellà)
-
“NeXT Byte Codes (NBC) Programmer’s Guide”, by John Hansen. (Anglès)
Paginà web oficial NXC -
http://bricxcc.sourceforge.net/nbc/
14. Agraïments Al nostre professor i tutor de treball Alfons Valverde per guiar-nos ajudar-nos, i resoldre els dubtes que teníem. Als nostres professors de Programació i Robòtica, Andreu Carré i Alfons Valverde, introduir-nos en aquest fascinant món.
per
Al cap d’estudis Jordi Fernández per donar-nos suport durant la competició de robòtica, i el seu constant interès. A la Universitat de Barcelona per brindar-nos l’oportunitat de participar en el curs de Robòtica amb LEGO. Al nostre company d’equip LEGO Nèstor Campos,que finalment ha fet un altre treball de recerca sensacional, per ajudar-nos amb la programació. Finalment donar les gracies a l’IES Forat del Vent per proporcionar-nos un lloc on treballar i el material i l’ajuda necessària.
47