
Travaux pratiques de l'option systèmes temps-réel
Programmation du robot LEGO Mindstorm(TM)
Isabelle PUAUT
Liens rapides
Introduction
Le LEGO
Mindstorm Robotics Invention System (RIS 2.0) est constitué d'un
ensemble de pièces LEGO et de l'unité RCX (unité de commande
du robot, voir photo à gauche du texte). La brique RCX est un système
programmable autonome bâti autour d'un micro-contrôleur Hitachi
H8/3932. La brique RCX peut être utilisée pour contrôler
des actionneurs (moteurs, générateur de sons) et lire des entrées
en provenance de capteurs (capteur de lumière, détecteurs de contact).
D'autres capteurs et actionneurs peuvent être ajoutés (capteurs
de pression, de température, de rotation, etc.). La brique RCX possède
également un petit écran LCD, utile pour afficher de l'information,
et un émetteur - récepteur infrarouge, utile pour télécharger
des programmes et communiquer avec les autres RCX. L'unité RCX est conçue
pour pouvoir facilement être attachée à d'autres briques
LEGO.
BrickOS (appelé précédemment
LegOS) est un petit système d'exploitation pour la brique RCX. BrickOS
est généré par compilation croisée sous Linux ou
Windows 95/98/2000/XP. Les programmes BrickOS peuvent être développés
en C ou C++ et compilés en code natif pour le micro-contrôleur
Hitachi H8. BrickOS fournit un ordonnancement multitâche préemptif,
de la gestion dynamique de la mémoire, des sémaphores POSIX ainsi
que des accès aux capteurs, actionneurs, écran LCD, communication
infrarouge. A noter que BrickOS n'est pas un système pour application
temps-réel strict, mais offre des fonctionnalités similaires à
de nombreux systèmes temps-réel embarqués du commerce.
La version de BrickOS utilisée lors des TPs est la version la plus récente
de BrickOS à la date de création des TPs (septembre 2003), à
savoir la version 0.2.6.10.
Installation et mise en route de BrickOS
Cette section présente les étapes d'installation de BrickOS (Version 0.2.6.10)
nécessaires pour démarrer les TPs. Il a été testé
dans un environnement Windows XP Édition Familiale, sous XP professionnel
et sous Windows 2000. Il fonctionne pour un kit "Robotics Invention System
2.0", pour lequel la communication infrarouge est réalisée
par USB.
Pour utiliser BrickOS, il faut disposer :
- d'une version de cygwin (a été testé avec les versions
de compilateurs 3.2.2 et 3.3.1).
- d'un pilote pour la tour infrarouge, servant à télécharger
les programmes sur le RCX.
- d'un compilateur croisé Hitachi H8.
- du .tgz de BrickOS.
Tous les élements sauf 4 sont déjà installés sur
les machines du réseau étudiant (voir ici
pour une documentation complète d'installation).
Décompression et compilation de BrickOS
- Ouvrez un shell Cygwin
- Placez vous à la racine de votre répertoire personnel
cd H:
- Récupérer BrickOS ici
et le dé-tarer
gunzip brickos-0.2.6.10.tgz
tar xf brickos-0.2.6.10.tar
Vous obtenez alors un répertoire /brickos-0.2.6.10/
- Rendez vous dans ce répertoire: cd brickos-0.2.6.10
- Tapez ./configure, de manière à localiser
le compilateur croisé et à configurer la compilation de BrickOS.
- Tapez make realclean
- Tapez make, pour obtenir votre système BrickOS, vos démos prêtes
à l'emploi, et vos utilitaires de téléchargement vers le robot.
NB : le fichier configure, présent à la racine des sources
BrickOS, recherche le compilateur croisé à partir du répertoire
racine cygwin. Si le compilateur croisé a été installé
ailleurs qu'à cet emplacement, le fichier configure devra être
modifié en conséquence.
Décontraction
- Mettre en route le robot (bouton rouge On-Off)
- Se placer dans le répertoire de démonstration (cd demo)
- Télécharger BrickOS sur le RCX (firmdl3 ../boot/brickOS.srec).
L'opération prend quelques minutes.
- Télécharger un programme de démonstration sur le RCX
en utilisant la commande dll (par exemple dll helloworld.lx)
- A vous de jouer (appuyer sur le bouton vert Run)...
- Vous pouvez maintenant attaquer les travaux pratiques !
TP 1 : Prise en main du robot
Montage du robot
- Construire le robot en y attachant au minimum les deux moteurs, le capteur
de lumière et un par-chocs (par exemple un Roverbot à pare-choc
double, page 10 et 11 du manuel LEGO, en y ajoutant un détecteur de
lumière). Eviter les chenilles ou les robots à quatre roues
munies de pneus épais (trop de frottements). Le capteur de lumière
doit être orienté vers le sol (détection de lignes au
sol).
- Travailler dans le répertoire demo, ou créer un répertoire
TP en reprenant le contenu du répertoire demo, en particulier son fichier
Makefile. Pour compiler un nouveau programme P, ajouter P.lx dans la ligne
PROGRAMS=
Développement d'un petit programme
- Écrire un programme très simple qui utilise le robot comme
outil de mesure et d'affichage de la lumière perçue au sol.
Le programme lira régulièrement la valeur du capteur de lumière,
qui sera associé au slot 2, et l'affichera sur l'écran LCD.
On produira un son d'autant plus aigu que la lumière détectée
est claire.
- Modifier le programme y ajouter une fonction de déplacement autonome
(le robot ira toujours en avant, à faible vitesse, sans se soucier
des obstacles se trouvant sur son passage).
Remarques
- On notera que les valeurs obtenues en lisant le capteur de lumière
dépendent des conditions d'éclairage ambiantes ainsi que de
l'état de chargement des piles. Avant d'utiliser ce capteur dans la
suite des travaux pratiques, il faudra donc effectuer des mesures, les valeurs
correspondant aux "noir" et au "clair" pouvant changer
d'un jour à un autre et d'un robot à un autre ...
- On utilisera la documentation en-ligne et les exemples fournis dans le répertoire
demo pour savoir comment lire les valeurs des capteurs et commander
les moteurs.
- On notera les différences entre les modes actif et passif
du capteur de lumière, activés respectivement par les fonctions
ds_active et ds_passive, sur les résultats retournés.
TP2 : Ordonnancement par plans statiques cycliques
L'objectif de ce TP est de mettre en place un séquenceur au dessus de
BrickOS. Le séquenceur "joue" un plan de manière cyclique,
une table étant générée statiquement pour décrire
les dates et durées d'exécution des fonctions à exécuter
dans le plan. Lorsque la construction d'un tel plan est faisable, il n'y a alors
pas besoin de multitâche. Une seule tâche (le séquenceur)
exécute les calculs en appelant les fonctions prévues dans le
plan. Les fonctions devront nécessairement se terminer en un temps borné.
Développement du séquenceur
On utilisera par exemple les déclarations C suivantes pour décrire
un plan statique :
#define TIME_UNIT 10 // Unité de temps (ms) servant à exprimer
les durées et dates de démarrage
typedef struct {
time_t date_debut; // Date de démarrage de la tâche dans
le cycle (en TIME_UNIT)
time_t WCET; // Durée d'exécution au pire-cas de la tâche
(en TIME_UNIT)
void (*fonction)(void); // Fonction à exécuter
} schedule_elt_t;
typedef struct {
int nb_elt; // Nombre d'éléments par cycle
schedule_elt_t *schedule; // Tableau des tâches à exécuter
dans un cycle
time_t t_cycle; // Durée du cycle (en TIME_UNIT)
} schedule_t;
Il est conseillé de ne pas définir de valeurs de TIME_UNIT inférieures
à 10, à cause de l'activité système générée
par BrickOS.
Écrire le code du séquenceur. Ce dernier sera composé
d'une boucle infinie attendant la date prévue de démarrage de
chaque fonction, puis l'appelant. On se servira de l'appel BrickOS get_system_up_time()
pour récupérer le temps système (en ms) depuis le démarrage
de BrickOS. Le séquenceur devra fonctionner quelle que soit la valeur
de TIME_UNIT.
On pourra tester le séquenceur en lui faisant effectuer une action simple
toutes les secondes (par exemple émettre un son).
Développement d'un programme de navigation (aléatoire)
Développer un programme réalisant quatre fonctions différentes,
qui seront chacune codée dans une fonction C différente :
- contrôle_moteur. Cette fonction est la seule à contrôler
les moteurs. Elle envoie régulièrement des ordres aux moteurs
selon des commandes de pilotage établies par les fonctions. La fonction
contrôle_moteur s'exécutera plusieurs fois si la commande
à appliquer a une durée importante.
- avancer : cette fonction génère des commandes pour
faire avancer le robot.
- changer_direction : cette fonction choisit de changer la direction
du robot avec une certaine probabilité (on utilisera pour cela la fonction
BrickOS random).
- eviter_obstacle : cette fonction génère une commande
de changement de direction (par exemple, une marche arrière) quand
le robot rencontre un obstacle.
Les trois dernières fonctions communiquent leurs commandes de pilotage
à la fonction contrôle_moteur en utilisant une variable
partagée commande_pilotage. On utilisera par exemple le code
C donné ci-dessous :
// Directions possibles du véhicule, à compléter si
besoin
typedef enum dir_t {tourner_gauche, tourner_droite, avancer, reculer, arreter,
inactif};
// Structure de donnée globale pour commander le véhicule
*/
struct commande_pilotage {
int privilege; // Privilège pour générer une commande
de pilotage (voir ci-dessous)
dir_t direction; // Direction demandée pour le véhicule
int speed; // Vitesse demandée
time_t duree; // Durée (en TIME_UNIT) pendant laquelle on veut
appliquer la commande
}
Pour éviter que les fonctions génèrent des commandes de
pilotages contradictoires, on instaure un système de privilège
entre les différentes fonctions pour la génération des
commandes. La fonction ayant le plus de privilège pour générer
une commande de pilotage sera eviter_obstacle, suivie de changer_direction
puis avancer. Pour vérifier si l'on peut changer la structure
de donnée globale, on pourra écrire une fonction changer_commande(int
privilege, t_dir dir, int speed, time_t duree), qui changera la structure
de donnée globale uniquement quand il n'existe pas de commande plus privilégiée
en cours d'exploitation.
La fonction de contrôle moteur, à chacune de ses exécutions,
modifiera la valeur du champ duree dans la commande de pilotage. Une
fois ce champ nul, les moteurs seront arrêtés, le privilège
de la commande sera positionnée à une valeur plancher, et la direction
sera positionnée à inactif. Ainsi, les autres fonctions
pourront générer de nouvelles commandes de pilotage.
Établir un plan statique cyclique non préemptif pour l'exécution
des 4 fonctions. La fonction contrôle_moteur s'exécutera
plusieurs fois par cycle.
Questions
- A t'on besoin d'un sémaphore pour protéger la structure de
données partagée ? Pourquoi ?
Détection des dépassements d'échéance
Ajouter au programme précédent un test de dépassement
d'échéances (l'échéance d'une fonction sera ici
simplement la date de début de la fonction suivante dans le plan). On
utilisera deux moyens complémentaires :
- Vérification lors de la fin d'exécution d'une fonction que
la date de démarrage de la suivante dans le plan n'est pas dépassée.
- Ajout d'une tâche supplémentaire (chien de garde, ou watchdog
task) chargée d'effectuer la vérification. La tâche sera
plus prioritaire que le séquenceur, et sera lancée par l'appel
BrickOS execi. Pour faire communiquer et synchroniser la tâche
de chien de garde et la fonction de contrôle moteur, on pourra utiliser
des variables globales, sémaphores et/ou utiliser des fonctions d'attente
d'événements (wait_event). L'échéance
à respecter par une tâche sera communiquée à la
tâche chien de garde par une structure de donnée (doit-on utiliser
un sémaphore pour la protéger ?)
- Question : Quels sont les éventuels problèmes pouvant
survenir quand on utilise ces deux méthodes de détection des
dépassements des échéances ?
TP 3 : Ordonnancement préemptif à priorités
Une alternative à l'utilisation de plans statiques cycliques est d'utiliser
un ordonnancement préemptif à priorités. Chacune des fonctions
est alors une tâche, l'ordonnanceur de BrickOS se chargeant de leur attribuer
le processeur (ordonnancement à priorité, avec partage de temps
par time-slicing à priorité égale).
- Réimplanter le système du TP 2 en utilisant maintenant un
ordonnancement préemptif à priorités. On utilisera la
fonction execi pour la création de tâche, et on ne se
souciera pas du dépassement d'échéance. A noter que les
priorités des tâches dans BrickOS sont assignées à
la création des tâches et ne peuvent pas être modifiées.
- Les tâches contrôle_moteur, avancer et
changer_direction seront périodiques. Il n'y a pas de
moyen explicite dans BrickOS de lancer une tâche périodiquement.
Pour le faire, il faudra utiliser la fonction msleep(). Le moyen
utilisé pour lancer une tâche périodiquement devra
être indépendant de la durée d'exécution de
la tâche, les tâches les moins prioritaires subissant une
gigue au démarrage.
- La tâche eviter_obstacle devant réagir uniquement
quand le véhicule rencontre un obstacle, elle sera implantée
sous la forme d'une tâche apériodique. On utilisera la fonction
wait_event() pour la réveiller en cas de contact avec
un obstacle.
- Les périodes et priorités des tâches seront attribuées
selon leur importance au sein du système. Si vous estimez que deux
tâches ont le même degré d'importance, vous pouvez
leur attribuer la même priorité (dans ce cas, BrickOS utilise
un ordonnancement par tranches de temps - timeslicing).
- Ajouter au système une tâche apériodique forçant
le robot de suivre une ligne noire tracée au sol. Pour autoriser des
changements de direction du robot, la ligne comportera de nombreux virages
et croisements.
Questions
TP 4 : Le challenge
L'objectif de ce dernier TP est de réussir à relever un défi
(voir ci-dessous). Vous pouvez proposer d'autres défis, sous réserve
que :
- le défi proposé soit approuvé par l'ensemble des étudiants
et du professeur
- le défi soit réalisable (montage du robot, développement
du logiciel, épreuve) dans un temps raisonnable (pas plus de 3 créneaux
de TP)
Sauf objection, les épreuves seront ouvertes au public.
Mai 2004 (INSA, option STR) : Course de robots
On a une piste (créée par le prof) dont on donne les caractéristiques
(largeur de la piste, rayon de courbure minimal des virages, nombre de croisements
maximal). L'objectif est de faire deux tours de la piste le plus rapidement
possible, et dans tous les cas en temps limité. Pour les sorties de piste,
on mesurera la distance parcourue depuis le départ. Cette idée
a été exploitée à l'INSA en 2004, voir quelques
photos.
Avril 2006 (IFSIC, option STR) : Robot ménager
L'objectif est de "faire le ménage" dans une zone délimitée
par un trait noir. Cinq objets, disposés aléatoirement dans la
zone, devront être sortis de la zone par le robot, le plus rapidement
possible. Un objet sera considéré comme sorti de la zone quand
il n'aura aucune surface de contact avec la ligne qui délimite le territoire.
Descriptif de la zone et des objets
- La ligne de séparation de la zone sera de 2.5 cm de large, de couleur
noire
- La zone sera entièrement blanche (pas de lignes noires internes à
la zone, la seule ligne noire sera la ligne délimitant la zone)
- Les objets seront des bouteilles en matière plastique, format 1 litre,
partiellement remplies d'eau. Un prototype sera amené en TP
- La forme de la zone pourra être quelconque
- Les objets seront disposés de manière aléatoire à
l'intérieur de la zone, au même endroit pour toutes les équipes
- Un objet est considéré comme "nettoyé" quand
il est totalement à l'extérieur de la zone
Contraintes sur le robot
- Chaque robot devra utiliser exclusivement le contenu d'une boite
- Les objets seront détectés en utilisant un ou deux détecteurs
de contacts.
- La largeur du pare-chocs servant à détecter le contact avec
un objet sera au maximum de 18cm Il n'y a pas de contrainte sur la hauteur
du pare-chocs
- Il est possible de faire sortir le robot de la zone, par contre il devra
être dans la zone en fin de compétition. Un robot est "dans
la zone" si son détecteur de lumière est à l'intérieur
de la zone.
Règles du jeu et appréciation des performances
- 15 mn seront laissée à l'ensemble des candidats pour régler
leur logiciel (détecteur de lumière sensible aux conditions
d'éclairage ambiantes) et effectuer les dernières modifications
de leur logiciel (à leur risques et périls)
- Une fois le robot lancé, il est interdit de le manipuler pour le
remettre dans la zone.
- Le robot démarrera avec le détecteur de lumière au
milieu de la ligne noire, en direction de l'intérieur de la zone. L'emplacement
exact du départ, qui sera le même pour l'ensemble des équipes,
sera fixé par l'enseignant
- La compétition s'arrêtera soit quand tous les objets auront
été nettoyés, soit au terme d'un délai de 5mn
- Un classement sera effectué en fonction du nombre d'objets déménagés
avec succès de la zone. Le vainqueur sera l'équipe ayant nettoyé
les 5 objets dans le minimum de temps. Les autres seront classés selon
le nombre d'objets nettoyés et à nombre d'objets égal
au temps
- Les 7 points du TP seront attribués de la manière suivante
:
- 3 points pour le code de commande du robot, appréciés
par l'enseignant (stratégie, mise en application des vues en cours,
structure du code)
- 3 points de performance : 3 points pour le vainqueur s'il a réussi
à tout nettoyer, 2.5 points pour les équipes ayant réussi
à tout nettoyer moins rapidement que le vainqueur, 2 points pour
les équipes ayant nettoyé entre 2 et 4 objets, 1.5 points
pour les équipes ayant nettoyé un seul objet, 1 point de
consolation pour les équipes dont le robot est toujours dans la
zone en fin de compétition mais sans n'avoir rien sorti
- 1 points de style (pour le fun)
Logistique
- Assignation des robots.
Chaque binôme utilisera un robot unique pendant les TPs. Chaque binôme
sera responsable de son robot pendant toute la durée des TPs. Le contenu
de la boite de pièces ne devra pas être mélangé
avec celui d'une autre boîte.
- Stockage des robots.
Les robots, une fois montés, seront stockés dans l'armoire blindée
au fond de la salle de TP. Les robots seront rangés sans la boîte
de pièces associée, pour éviter les pertes de pièces.
Sauf situation exceptionnellement exceptionnelle, vous n'aurez pas accès
à la boîte de pièces après le premier TP.
- Gestion des batteries.
Lors de la première séance, vous aurez des batteries chargées
pour votre robot. Si elles viennent à se décharger en cours
de séance, elles seront échangées contre des batteries
chargées et mises à recharger (chargeurs dans l'armoire). Pour
gérer convenablement le stock de batteries, ne jamais mélanger
un jeu de batteries avec celui d'un autre robot.
- Emprunt des robots entre les séances de TPs.
Le placard contenant les robots sera fermé à clé. Il
n'est pas prévu de pouvoir emprunter de robots en dehors des séances
de TP.
- Problèmes matériels.
Signalez tout problème avec votre robot (problème de contact
des batteries, capteurs ou moteurs défectueux) au plus tôt.
Liens utiles
Foire aux questions
- Si vous obtenez le message d'erreur "Delete firmware failed" lors
du chargement de BrickOS, cela signifie que BrickOS est déjà
chargé. Il faut donc l'effacer, en utilisant une des deux possibilités
suivantes : enlever les piles ou bien appuyer simultanément sur les
deux boutons On-Off et Prgm.
- Si votre robot ne répond plus, il vous suffira d'enlever les piles
et de recharger BrickOS.
- Si vous obtenez le message "Tower not responding" en essayant
de télécharger des programmes sur votre robot, c'est certainement
que votre variable d'environnement RCX n'est pas initialisée à
USB (par défaut, considère une transmission par ligne série).
Une autre cause peut être que vous avez lancé le shell avant
de modifier vos variables d'environnement, et qu'elles n'ont donc pas encore
été prises en compte.
Isabelle Puaut, dernière mise à jour en juillet 2004 (puaut@irisa.fr)