Curs per a professors d’Introducció a LEGO® Mindstorms NXT i EV3
(versió 4.6)
Començant per un repte senzill que utilitza un sensor de contacte i
un motor, aquesta seqüència didàctica introdueix un rang de tècniques de
programació per mostrar com controlar un actuador mitjançant sensors utilitzant
estructures de control de flux.
Aquestes estructures de programació inclouen blocs espera, bifurcacions, bucles, “flags” o banderes, i el patró de disseny mestre/esclau. A més, també mostra com utilitzar variables, blocs d’usuari amb arguments, matrius i com operar amb elles.
La imatge de la dreta mostra una màquina de comptar colors amb un sensor de Contacte EV3.
El sensor de contacte és el sensor més simple de la plataforma LEGO Mindstorms. Dóna un valor veritat quan es pressiona i un valor fals si no està pressionat. A més, el sensor de contacte es pot configurar per donar un valor veritat quan es xoca, és a dir, quan el sensor es pressiona i just després s’allibera.
Com podem programar un motor per a que arrenqui en pressionar el sensor de contacte? La forma més simple és utilitzar el bloc Espera configurat per utilitzar el sensor de Contacte tal com mostra la imatge següent.
Esperar a pressionar el sensor de Contacte per arrencar un Motor
Quines són les limitacions del programa anterior? Tal com previst, el motor no arrencarà fins que es pressioni el sensor de Contacte, encara que només girarà una rotació... I si volem que el motor giri contínuament? Alguns estudiants poden dir que només cal configurar el bloc Motor per funcionar en el mode “On” en comptes de “On per rotacions”. Si ho provem, veurem que el motor no arriba ni a arrencar! Perquè? Perquè el programa acaba després del bloc Motor i quan un programa acaba, tots els motors es desconnecten. Com cal modificar el programa per fer que el motor giri contínuament? Una opció és posar el bloc Motor dins d’un bucle configurat en “il·limtat”. Per exemple:
Esperar a pressionar el sensor de Contacte per arrencar un motor il·limitadament
Quan s’aturarà el motor del programa anterior? O bé quan es pari manualment o bé quan s’acabi la bateria del maó.
Com podem arrencar o parar un motor utilitzant un sensor de Contacte, per exemple, per arrencar-lo o parar-lo quan es desitgi? Com podem modificar l’exercici anterior per aconseguir-ho?
Els estudiants poden adonar-se de que després d’esperar a pressionar el sensor de Contacte per arrencar el motor, podem tornar a esperar per aturar-lo, perquè les accions són seqüencials, tal com es mostra a continuació.
Esperar a pressionar el sensor de Contacte per arrencar un motor i tornar a esperar per aturar-lo
Després de pressionar per primer cop el sensor de Contacte, el Motor arrencarà i el programa esperarà a que es pressioni novament per aturar-lo. Però funciona el programa? De vegades funciona, però no de forma consistent. Perquè no? El programa s’executa tan ràpidament que quan nosaltres encara estem pressionant el sensor de contacte ell ja està executant el segon bloc Espera i per tant parant el motor. Com a resultat, necessitem pressionar i alliberar el sensor de contacte extremadament ràpid (o tenir molta sort), per fer que funcioni el codi. Com podem solucionar aquest poblema? Cal configurar el bloc Espera amb l’opció “xocat” (o “bumped”).
Esperar a xocar el sensor de Contacte per arrencar un Motor i tornar a esperar per parar-lo
I si ara volem re-arrencar el motor tornant a pressionar el sensor de contacte? (sense haver de tornar a arrencar el programa). La solució és posar el codi anterior dins d’un Bucle il·limitat o Infinit.
Arrencar o parar un motor repetidament mitjançant un sensor de Contacte
En aquest punt pot ser interessant preguntar als alumnes si podem obtenir els mateixos resultats o similars utilitzant un bloc Bifurcació en comptes d’un bloc espera. És una bona oportunitat per reflexionar sobre les diferències entre aquestes estructures de programació.
Arrencar o parar un motor utilitzant un bloc Bifurcació de Contacte
Quines són les diferències principals entre aquest programa i l’anterior? Amb aquesta versió cal pressionar constantment el sensor de contacte per parar el motor. A més, el motor arrencarà tan aviat com arrenqui el programa, si no es pressiona el sensor de contacte. Mantenir el sensor pressionat per parar el motor (o per arrencar-lo si invertim les branques de la bifurcació) pot ser un requeriment per alguns escenaris específics, però la solució anterior utilitzant blocs Espera pot ser més convenient per exemples senzills. Aquesta solució té algun avantatge respecte de la que utilitza blocs Espera? Aquesta solució utilitza un bloc menys i no repeteix cap bloc.
Com podem obtenir la mateixa funcionalitat de la solució del bloc Espera però utilitzant una Bifurcació? La idea és que cada cop que es xoqui el sensor de contacte (és a dir, que es pressioni i s’alliberi) el motor alternarà entre arrencar o parar. La variable lògica flag alterna entre veritat i fals i el seu valor l’utilitza un bloc Bifurcació per determinar si cal arrencar o parar un motor. S’utilitza una variable lògica perquè només tenim dos estats, arrencat i parat, però podríem utilitzar un altre tipus de variable si tinguéssim més de dos estats.
Arrencar o parar un motor amb el sensor de Contacte utlitzant una Variable i un bloc Bifurcació
Encara que aquesta solució és més complexa que la que utilitza una seqüència de dos blocs espera, és molt més versàtil. Però encara té un problema important. Imaginem que volem construir una màquina classificadora i que volem mostrar un missatge en la pantalla del maó EV3 segons l’objecte classificat, o que volem arrencar un codi específic en funció de l’objecte classificat. No només cal que el motor segueixi en marxa, si no que potser cal que altres accions s’executin, per tant, fent que la branca de la Bifurcació trigui bastant més temps a completar-se. I si intentem parar el motor pressionant el sensor de Contacte, haurem d’esperar a que finalitzi la branca. Com podem solucionar aquest problema? Una aproximació és utilitzant la arquitectura del patró de disseny Mestre/Esclau...
Una mica de teoria
Hem abordat aquesta tècnica de programació en la Unitat 10 “Encendre o no encendre la llum”.
En l’exemple següent, el bucle mestre utilitza una variable lògica anomenada “Flag” (bandera) per informar el bucle esclau del motor per arrencar o parar el motor.
Arrencar o parar un motor amb el sensor de Contacte utilitzant el patro de disseny Mestre/Esclau
Seguim amb l’exemple que el motor que volem arrencar o parar forma part d’una màquina classificadora. Com a mesura de seguretat, volem que el motor pari si el sensor de Color no detecta cap color (és a dir, que no hi han més elements per classificar). O potser volem que el programa s’aturi completament.
En el primer exemple, el que utilitza blocs Espera, com pot el programa “o bé esperar fins que es pressioni el sensor de Contacte o bé a que el sensor de Color no detecti cap color per parar el motor”? Donat que el bloc Espera només es pot associar a un sensor, com podem resoldre el problema?
Suggeriment: Tal com hem vist en la Unitat 11 “Com esperar a que es compleixin una o més condicions de sensors”, un bloc Espera és simplement la implementació d’un bucle buit associat a un sensor.
Implementació del bloc Espera associat al sensor de Contacte utilitzant un bucle buit
La resposta és, fer explícit el bucle i associar-lo als dos sensors, tal com es mostra a continuació.
Arrencar o parar un motor utilitzant el sensor de Contacte i el de Color, utilitzant un bucle buit
Tal com hem comentat anteriorment, aquest programa funciona perfectament perquè les accions, arrencar o parar un motor, s’executen molt ràpidament, però si cal executar seqüències de programa més llargues i que requereixin més temps, cal una solució millor.
Quina és la solució? Una aproximació és modificar el bucle mestre de la solució del patró de disseny Mestre/Esclau tal com es mostra a continuació.
Si no es detecta cap color posar el flag a fals (i.e. para el motor) en el bucle Mestre,
en cas contrari conmutar el flag si es xoca el sensor de Contacte
Cal observar que per parar el programa, podem utilitzar blocs d’interrupció de bucle “Loop Interrupt”, assegurant que es para abans el motor, o qualsevol part del programa principal dins del Bucle Esclau.
Una altra solució és arrencar diferents seqüències en paral·lel i utilitzar variables per controlar el flux del programa:
Una nova seqüència paral·lela para els Bucles Mestre i Esclau si no es detecta cap color utilitzant la nova variable “stop”
Potser caldrà afegir algun codi específic en cadascuna de les branques “Veritat” de les Bifurcacions lògiques connectades a la variable “stop” abans de sortir dels bucles Mestre i Esclau...
Donat que el bucle mestre conté vàries seqüències paral·leles per controlar els diferents processos que succeeixen al mateix temps, hem d’anar amb molta cura per evitar condicions de carrera, tal com veurem en el següent exercici. Una condició de carrera pot succeir quan diferents parts del programa del bucle mestre intenten modificar a l’hora la variable del bucle esclau. Per obtenir més informació sobre les condicions de carrera mireu la Unitat 10 “Encendre o no encendre la llum”.
Com a exercici final, i per acabar de comprendre el poder de la solució del patró de disseny Mestre/Esclau, ampliarem l’últim exemple de l’exercici anterior afegint les següents funcions a la nostra màquina de classificació de color.
1. Comptar els elements classificats per color (blau, verd, groc, i vermell).
2. Mostrar el comptador de cada color en la pantalla del maó EV3.
3. Pausar/arrancar la màquina si es pressiona el sensor de Contacte.
4. Parar la màquina O BÉ si no es detecta cap color durant un període de temps raonable O BÉ si un dels colors classificats arriba a un nombre màxim.
Comptar elements és una tasca relativament ràpida, però mostrar un missatge a la pantalla del maó durant el temps suficient per a que es pugui llegir requereix més temps que la resta de tasques. A més, mentre es mostra un missatge, la màquina classificadora pot anar trobant més elements i fins i tot arribar al màxim nombre d’elements classificats per un color.
Com podem implementar aquestes funcionalitats? Hem de canviar tot el programa anterior o només una part? Només cal canviar la última seqüència del programa anterior. Afegirem un nou bucle a aquesta seqüència anomenat “MasterColor”.
Seguidament abordarem aquestes funcionalitats una per una.
Per comptar cadascun dels colors diferents, cal emmagatzemar en algun lloc la informació. La solucio més eficient és emmagatzemar el comptador de cada color en una matriu numérica. En aquest exemple, comptarem blau, verd, groc, i vermell, i anomenarem la matriu numèrica “SortedBGYR”.
També necessitarem una variable, “ArrayIndex”, per emmagatzemar l’índex de la matriu corresponent al color detectat per la Bifurcació associada al sensor de Color. Per exemple:
· Blau => 0
· Verd => 1
· Groc => 2
· Vermell => 3
Inicialitzar les variables, restablir el temporitzador, i establir el valor de ArrayIndex segons el color
Observar que si no es detecta cap color ArrayIndex s’estableix a -1. Aquest és també el cas per defecte. Com a resultat, qualsevol color que no sigui o bé blau, verd, groc, o bé vermell també s’establirà ArrayIndex a -1. A més, abans d’entrar dins del bucle, s’inicialitzen les dues variables, SortedBGYR i ArrayIndex, així com el temporitzador.
Una mica de teoria
Una bifurcació amb múltiples branques s’anomena Estructura de Casos i de fet una bifurcació de dues branques és l’estructura de casos més simple. Tal com mostra l’exemple anterior, la Bifurcació associada al sensor de Color conté 5 branques, una per a cada color de l’element que volem classificar. Dins de la bifurcació cada color es representa internament per un nombre sencer de 0 (cap color) a 7 (negre), però no cal que l’usuari sigui conscient d’aquesta representació interna perquè cada cop que cal triar un color la interfície mostra un menú amb tots ells, tal com mostra la següent captura de pantalla.
Els índexs de color d’una Bifurcació de Color
D’una altra banda, el primer element d’una matriu numèrica és “0”. Per tant, donat que la nostra variable ArrayIndex emmagatzemarà el nombre del color, la pregunta és: hi ha una forma més simple d’omplir la nostra variable ArrayIndex? Si volem utilitzar els 7 colors possibles de la Bifurcació associada al sensor de Color, no cal utilitzar una bifurcació. Només cal utilitzar un bloc sensor de Color i connectar l’endoll de sortida de Color a la variable ArrayIndex, tal com mostra la imatge a continuació.
Solució on la sortida del sensor de Color actualitza la variable ArrayIndex directament
Seguidament cal alimentar el bloc Bifurcació o Estructura de Casos amb el valor de ArrayIndex.
Si ArrayIndex és igual a -1, cal comprovar el temporitzador i, si és necessari, parar la màquina classificadora, mostrar el missatge “EXIT”, i fer que la llum d’estat del maó perpellegi en vermell.
Cas -1: si no es detecta cap color, sortir del programa
Observar l’ús del bloc Bifurcació de Temporitzador establert a 10 segons per deixar suficient temps per a que es pugui detectar el proper color. Si no es detecta cap color amb 10 segons, el programa es para. El cas fals és buit.
Observar també que si s’emmagatzema el color directament en la variable ArrayIndex, el cas numèric previ haurà de ser “0” en comptes de “-1”. A més el cas per defecte haurà de ser qualsevol altre nombre diferent de “0”.
Si ArrayIndex és qualsevol nombre que no sigui “-1” (en el nostre exemple 0, 1, 2 ó 3) el comptador adequat s’incrementarà.
Cas 0 (per defecte): Incrementar el comptador del color especificat
Què passarà si es detecta un color inesperat? Tal com s’ha mencionat anteriorment, es cridarà el cas per defecte de la Bifurcació del sensor de Color (en el nostre cas “No Color”) i la màquina classificadora tractarà aquest cas com si no hagués detectat cap element. Si això passa durant més de 10 segons el programa s’aturarà.
El pas següent és mostrar el contingut de la matriu en la pantalla del maó EV3.
Una opció és crear un My Block (també anomenat bloc d’usuari) que agafi com a entrada la matriu que es vol imprimir i anar recorrent els diferents índexs per mostrar-los en la pantalla del maó. Observar que només el primer bloc Display neteja la pantalla.
PrintArrayBGYR: Un My Block que utilitza una matriu numèrica com a valor d’entrada
En el cas que es vulguin utilitzar tots els colors detectats pel sensor de Color, aleshores caldrà afegir els colors absents i canviar els índexs de color per aparellar-los amb aquells de la representació interna.
Afegir el My Block, PrintArrayBGYR, al final del bucle.
My Block PrintArrayBGYR afegit al final del programa
Aquesta aproximació fa que sigui molt més fàcil llegir el programa principal, i ajuda a depurar-lo. També permet utilitzar aquest codi en llocs diferents al llarg del programa principal. Per exemple, també podem utilitzar-lo per inicialitzar el missatge de la pantalla del maó.
My Block PrintArrayBGYR afegit al principi del programa
Seguidament, incorporem la variable flag (bandera) de l’Exercici 4 per pausar/arrencar la màquina quan es pressiona el sensor de Contacte.
Afegirem un bloc Bifurcació Lògica a l’inici del bucle per establir ArrayIndex igual a -2 si el flag és fals.
Si el flag és fals, establir ArrayIndex igual a -2, altrament establir-lo com abans
També necessitem afegir el cas -2 al final del bloc Bifurcació o Estructura de Casos per mostrar el missatge “PAUSE” i establir la llum d’estat del maó a taronja.
Mostrar el missatge “PAUSE” i establir la llum d’estat del maó a taronja
Finalment, cal parar la màquina quan o bé no detecta cap color o bé quan un dels colors classificats assoleix el nombre màxim. L’exemple a continuació mostra una estratègia simple per parar la màquina quan el nombre d’elements arriba a cinc.
Si hi han cinc elements de qualsevol color para la màquina, altrament neteja el missatge de la pantalla del maó
Si el nombre màxim d’elemens encara no s’ha assolit, s’esborra el missatge “PAUSE” i es reinicialitza el temporitzador. Perquè és important reinicialitzar el temporitzador? Perquè altrament si no es detecta cap color, la màquina classificadora pararà immediatament.
La imatge següent mostra la seqüència completa del programa del bucle MasterColor.
Seqüència completa del programa incorporant el bucle MasterColor
El següent vídeo mostra un exemple de màquina classificadora per comptar colors. Per incrementar la possibilitat de que el sensor de color llegeixi correctament el color, s’ha modificat el bucle esclau per mantenir cada element sota el sensor de Color durant un segon.
Coneixements adquirits: Amb aquesta unitat didàctica els estudiants aprendran a utilitzar el sensor de Contacte per controlar un motor o un programa principal. També aprendran a afegir més condicions i a controlar el flux del programa utilitzant diferents tècniques de programació i les seves implicacions a l’hora d’utilitzar blocs Espera, Bifurcacions o Bucles, i estratègies específiques per sincronitzar seqüències diferents de flux de programa, utilitzant variables i el patró de disseny Mestre/Esclau, que poden ser molt útils per complementar l’arquitectura de la Màquina d’Estats, estudiada anteriorment. Els alumnes també entendran com utilitzar bucles que han de funcionar a velocitats diferents i l’ús de variables i temporitzadors per tal de controlar aquests bucles. Finalment, també aprendran a utilitzar i a operar amb matrius i com mostrar missatges i controlar la llum d’estat de la pantalla del maó.
Podeu també trobar aquesta unitat didàctica a la pàgina web de legoengineering.com.