11 - Ecrire un programme pour le robot

Maintenant que les bases de l'Ada sont plus ou moins maitrisées, il est temps de jouer avec le robot.

Sur le robot, il y a déjà plein de packages pré-écrit par les vieux cons du club. Ces packages servent à différentes choses, comme accéder aux images des caméras, déplacer le robot, etc.  Pour une description des packages disponibles, voir la page « bas niveau » dans la partie « software ».

L’arborescence

Le code du robot se trouve dans le répertoire « shix » du repository mercurial « soft ». Ce répertoire se subdivise en plusieurs :
  • contest: Répertoire de base pour tout le haut niveau
      •  camera: les binaires sh4 spécifiques à la carte caméra
      •  main: les binaires sh4 spécifique à la carte master
    • camera: les sources spécifiques à la carte caméra
    • main: les sources spécifiques à la carte master
    • objs-i386:les fichiers objets résultat de la compilation pour i386
      • camera: les fichiers objets x86 spécifique à la carte caméra
      • main : les fichiers objets x86 spécifique à la carte master
    • objs-sh4 : les fichiers objets résultat de la compilation pour sh4
      • camera les fichiers objets sh4 spécifique à la carte caméra
      • main : les fichiers objets sh4 spécifique à la carte master
  • hwinterface : Répertoire de base pour tout le bas niveau
    • camera : tout ce qui concerne la carte caméra
    • i386 : tout ce qui est spécifique x86
    • main: tout ce qui concerne la carte master
    • sh4: tout ce qui est spécifique sh4
  • png_io: une bibliothèque de fonctions pour lire et écrire des png
  • zlib : la bibliothèque zlib en Ada.

 La compilation

Comme c’est un peu le bordel (en fait non, c’est bien rangé, mais y’a beaucoup de répertoire mis en jeu), pour compiler toto.adb qui utilise des packages d’un peu partout, un simple gnatmake toto.adb ne fonctionnera pas : il ne va pas trouver la moitié des fichiers nécessaires. Pour que cela marche, il faut lui rajouter plein d’option pour lui indiquer tout les répertoires à inclure ainsi que les bonnes options de compilation.
Pour simplifier le processus, nous disposons d’un script python makeMakefile.py qui cherche tout les programmes dans shix/contest, shix/contest/main et shix/contest/master et génère un Makefile automatiquement pour tout compiler. En gros, pour compiler, il suffit de se placer dans shix/contest et de taper makeSi on veut compiler uniquement les programmes pour la camera, on va dans shix/contest/camera et on fait make. Pour les programmes de la carte master, on va dans shix/contest/main. Ces make vont générer des executables x86 dans le même répertoire que leur fichier .adb correspondant.
C’est bien beau tout ça, mais comment on cross-compile se demande immédiatement ceux qui ont suivit depuis le début. Et bien c’est facile, on tape make sh4 au lieu de make. Facile, non ? Le résultat d’un make sh4 se trouvera dans le répertoire shix/contest/bin-sh4, bien rangé dans 2 sous-répertoire « main » et « camera ». Ces fichiers sont à lancer depuis le robot (voir la page « Branchement / démarrage du robot ».
Tant qu’on parle des options de make, il existe 3 autres options importantes :
  • make clean : efface tout le résultat de la compilation pour x86.
  • make sh4-clean : efface tout le résultat de la compilation pour sh4.
  • make install : effectue un make sh4 et copie le résultat sur schtroumpf/v12

Application : déplacer le robot

Package utile

Pour mettre en pratique tout ceci, écrivons un petit programme qui déplace le robot : on va lui faire faire des carrés jusqu'à qu’il n’y ait plus de batteries. Pour déplacer le robot, on ne va pas réinventer la roue, on va regarder ce que les vieux ont déjà écrit. En farfouillant un peu, on tombe sur shix/contest/main/move.ads. Ce fichier porte un nom prometteur… En regardant un peu dedans, on s’apperçoit qu’il définit des fonctions assez utile dans notre cas :

procedure Forward (Distance_In_Mm : in Integer);
-- Go forward by a distance in millimeters (may be negative)
procedure Turn (Degrees : in Float);
-- Turn counter clockwise (may be negative)

Version 1

On va donc utiliser ce package pour faire notre programme. Ce programme va devoir déplacer le robot, donc utiliser les moteurs. Les moteurs sont branché sur la carte master, ce sera donc un programme de la carte master. On se place donc dans le répertoire shix/constest/master et on crée le programme test_move.adb

with Move;                use Move;
procedure Test_Move is
begin
  loop
    Forward(1000);
    Turn(90.0);
  end loop;
end Test_Move;

ensuite, on le compile avec un bête make ou make sh4. C’est genial, ca compile !

Version 2

On se précipite pour le lancer et là, c’est le drame : il ne se passe rien, le robot ne bouge pas (ou alors très peu et en faisant n’importe quoi). Que c’est il passé ? C’est simple, « forward » et « turn » donne l’ordre au robot de faire un mouvement, mais n’attendent pas que cet ordre soit executé pour rendre la main. On donne donc l’ordre au robot d’avancer, à peine a-t-il commencé à le faire qu’on lui dit « laisse tomber, tourne plutôt », puis « finalement, non avance », etc, etc.
Pour attendre la fin d’un mouvement, il faut reprendre « move.ads » plus en détails. Là on tombe sur

function Wait_End_Move return End_Move_Record;
function Wait_End_Move(Max_Time : Duration) return End_Move_Record;

Oh! Des fonctions qui permette d’attendre la fin d’un mouvement!. Si on étudie un peu leur valeur de retour, on se rend compte que les anciens ont quand même bien bossé puisqu’ils nous disent même l’état du robot lorsque cette fonction rend la main.
Mettons vite à jour notre programme

with Move;                use Move;
 
procedure Test_Move is
  Reason : End_Move_Record;
begin
  loop
    Forward(1000);
    Reason := Wait_End_Move;
    Turn(90.0);
    Reason := Wait_End_Move;
  end loop;
end Test_Move;

Version 3
Sur le papier, cette version semble pas mal. Seulement, lors de la compilation, on se fait insulter :

     4.   Reason : End_Move_Record;
          |
        >>> warning: variable "Reason" is assigned but never read
     8.     Reason := Wait_End_Move;
            |
        >>> warning: useless assignment to "Reason", value overwritten at line 10

Le deuxième message d’erreur vient du fait que le compilateur se rend compte qu’on fait un truc bizarre: on donne une valeur à une variable, et on l’écrase tout de suite après sans même la lire ! Le 1er message est du même ordre : on declare une variable, on lui donne une valeur, et on la relit jamais. Le compilateur Ada est un peu chiant (en fait, c’est du aux options de compilation) mais il a pas tort que souvent c’est à cause d’un bête oubli qu’on ne lit pas les valeurs de certaines variables. Dans notre cas, c’est tout à fait normal, on fait un programme débile qui ne réagira pas s’il se prend un mur. On le sait et on en assume les conséquences donc on l’indique au compilateur :

with Move;                use Move;
 
procedure Test_Move is
  Reason : End_Move_Record;
  pragma Warnings(Off, Reason);
begin
  loop
    Forward(1000);
    Reason := Wait_End_Move;
    Turn(90.0);
    Reason := Wait_End_Move;
  end loop;
end Test_Move;

Et voilà un beau programme qui fait bouger le robot.
Attention : Le pragma Warnings(Off, Reason) ; utilisé ici l’est parce qu’on sait ce qu’on fait. Il ne fait surtout pas en mettre partout juste parce que le compilo gueule !

Et maintenant, au travail !