Aller au contenu


Information tutoriel

  • Ajouté le: août 04 2010 05:37
  • Date Updated: sept. 15 2014 12:01
  • Lectures: 16414
 


* * * * *
1 Notes

Programmer les déplacements de Roby en C++

Posté par Philippe-RX on août 04 2010 05:37
Introduction


Ce tutoriel a pour but de passer en revue toutes les techniques de programmation pour réaliser un code professionnel dans Roby.
L'idée est d'apprendre en suivant de la pratique intéressante : Nous allons voir comment faire pour contrôler Roby à travers le PC, à travers un Joystick relié au PC et avec une manette nunchuk Wii directement relié à Roby sans oublier que le robot devra garder un mode "automatique" c'est-à-dire le mode fonce dans les murs.

Attention, je ne parlerais pas de Roby télécommandée sans fil dans ce tutoriel, ce n'est pas plus compliqué mais demande pas mal d'investissement financier quand on veut faire quelque chose de propre et standard.
De plus, vous devez avoir un Roby complet hardware. C'est-à-dire qui a déjà fonctionné avec le code de robocop. Pas grave s'il n'a pas de détecteur à ultrason. Je n'en ai pas moi-même.

J'ai essayé de rédiger de tutoriel pour qu'il, en plus de vous apprendre des choses et de vous amuser, vous donne de l'expérience. Pour vous la donner, je partage la mienne. Donc si des fois vous trouvez que je raconte ma vie ce n'est pas vraiment faux mais c'est fait exprès...

Le tutoriel est terminé à 30%. (j'ai décidé d'ajouter de nouveaux chapitres)

Les nouveaux chapitres que j'ai prévu (le niveau en programmation sera + élevé attention) dans l'ordre de priorités:
 
  • Refactoring du code actuel :
    Pour partir sur un code de très bonne qualité.
  • Calculer sa vitesse réelle et la sauvegarder :
    En calculant sa vitesse réelle et en l'enregistrant dans une EEPROM on pourra déterminer + ou - précisément l'angle effectué pendant un pivotement et la distance parcourue pendant tant de temps.
  • Apprendre à Roby à doser sa vitesse :
    De façon à simplifier la programmation future, nous surchargerons les méthodes du Châssis pour accepter des vitesses en m/s. On en profitera pour établir des graphiques sur Open Office Calc.
  • Faire des virages et des déplacements réalistes : partie 1 :
    Dans ce chapitre, notre principale préoccupation sera d'exécuter un arc de cercle. Niveau math, le niveau seconde suffit mais niveau programmation ça sera un peu + poussé. (Quoi que, avec ce qu'on aura vu avant...) On fera un test avec le détecteur de proximité, on cherchera à ce que Roby fasse un virage (de 1/4 de tour) à l'approche du mur pour qu'il soit parallèle mur et le plus proche SANS le toucher.
  • Partie 2 : Mouvements réalistes au Joystick :
    Maintenant que Roby sait tracer un arc de cercle, nous verrons comment améliorer le code du Joystick de manière à prendre en compte cette nouvelle fonctionnalité et d'ajouter le support du contrôle de vitesse.
  • Calculer sa position :
    Si j'ai de la chance (et assez d'argent de poche) j'aurais fait un tuto sur les principales méthodes de sans fil adaptées pour Roby. Donc calculer sa position et l'afficher sur le PC c'est pas mal. On peut aussi tracer une carte de la pièce sur le pc grâce aux données recueillies par Roby. (Roby mission mars pour bientôt)
  • Revenir sur ses "pas" :
    Quand j'y repense, c'est pas vraiment le but du chapitre. L'objectif réel est de voir comment mettre en mémoire les différentes mouvements en vue du chapitre suivant.
  • Mémoriser une trajectoire :
    Ce chapitre sera fatiguant. Ce qu'on cherchera à faire, c'est de transférer des trajectoires depuis le PC. Objectif : Roby robot de surveillance.

Il manque un chapitre : Contrôler Roby avec une manette wii Classic mais je crois que je vais l'abandonner.
En revanche, ils ne viendront pas vite et je ne garantis pas de tous les faire...

  Préparer Roby


Révélation

Désolé de vous décevoir si vous prenez Roby comme votre nouveau chien de compagnie ou autre mais, il faut que je vous le dise... Il n'est qu'un objet.

La vérité cachée derrière Roby

En vérité roby est un individu issu de l'espèce Roby qui est une sous-espèce des Robot qui est lui même une sous-espèce des Objet.
On appelle cette sorte "d'espèce" une classe en programmation.

Objet > Robot > Roby
Il faut comprendre : Objet est la classe mère de Robot et Robot est la classe mère de Roby.
Et Roby est une classe fille de Robot et Robot est une classe fille de Objet.

Image IPBPar convention dans de nombreux langages informatiques, les noms des classes prennent une majuscule à la première lettre. Ce n'est qu'une convention ce n'est donc pas toujours le cas, le contre exemple le plus courant et frappant est la classe string en C++. De plus, pour vous aider, je mets les noms de classes en italique.

roby votre Roby

En clair à partir de maintenant nous allons distinguer roby l'objet et Roby la classe.
Les Roby ont des propriétés et des comportements spécifiques ils ont tous les mêmes propriétés mais pas forcément les même valeurs par exemple tous les roby ont un châssis pour avancer.
Le roby est et sera toujours votre robot (jusqu'à qu'il aille au paradis des robots). Ce roby est un Roby donc il possède toutes les propriétés et comportements des Roby, c'est logique.

Ces propriétés sont nommés propriétés (ou attributs) en programmation.
Les comportements dont je vous parlais sont des méthodes, des actions que roby peut faire.

Malheureusement pour roby, il sera le seul Roby. Mais ce n'est pas un problème pour nous puisqu'on va programmer qu'un seul Roby à la fois : roby.
Image IPBSi vous êtes déjà perdu. Recommencez le tuto, vous finirez par comprendre.

Repartir de zéro

Nous allons maintenant passer à un peu de pratique pour concrétiser ce qu'on vient de voir.

Code mini-minimal

Nous savons que le code Arduino se présente sous cette forme :
Code : C
void setup()
{
}

void loop()
{
}

Je ne vous expliquerais pas pourquoi c'est comme ça, en tout cas pas dans ce tutoriel. (Note de relecture : Je me suis trahis, j'ai expliqué pourquoi dans la citation de moi-même en milieu de chapitre). Ce que je veux vous dire en fait c'est qu'Arduino compile votre code comme du C++ et non pas du C.
Rassurez vous, on ne vous a jamais menti. Le logiciel compile en utilisant un compilateur qui ne prend pas en charge entièrement le C++. De nombreux principes ne sont donc pas présents, par exemple les exceptions pour des raisons qu'on ne va pas chercher à comprendre mais, je vous assure que les raisons sont bonnes la raison qui vient le + souvent c'est parce que des fonctionnalités supplémentaires alourdirais le poids de notre programme et ne rentrerait pas dans nos CI.

Bref, passons à la déclaration de la classe Roby.

Déclarer Roby

On déclare une classe de cette façon en C++ :
Code : C
 
class NomDeLaClasse {
};
// Ne jamais oublier le point virgule
// Source de très nombreuses erreurs.


Donc dans notre cas nous aurons :
Code : C
 
class Roby {
//attributs et méthodes ici.
};
void setup() {
}
void loop() {
}

En fait, pour tout vous dire on essaye de programmer roby mais, il n'est pas encore créé.

On va donc créer l'objet à partir de la classe. Ce procédé s'appelle l'instanciation.

Instancier roby, un Roby

Il existe deux types d'instanciation très différentes.
 
  • L'instanciation statique
  • L'instanciation dynamique.

Voici un exemple d'instanciation statique :
Code : C
 
Servo servog; // Déclaration + Instanciation
servog.attach(servoPin); // Appel d'une méthode


Exemple d'instanciation dynamique :
Code : C
Servo * servog; // Déclaration de l'objet (d'un pointeur sur l'objet)
servog = new Servo(); // Instanciation
servog->attach(servoPin); // Appel d'une méthode


Image IPBLe code avec l'instanciation statique va compiler sans erreur mais pas celui de l'instanciation dynamique. Comme je le disais, le c++ n'est pas entièrement supporté par le compilateur. N'ayez crainte, j'ai trouvé une solution pour faire marcher ce genre de code. Je vous en reparlerais plus tard.

En gros ces deux types vont différencier en 3 points : la façon de déclarer/instancier, la façon d'appeler des méthodes & lire les attributs et puis la vie de l'objet.

La vie de roby le Roby

Vie statique ou dynamique

Notre roby est un objet et comme tout le monde le sait. Aucun objet n'est éternel. Notre roby va donc mourir. Bien sûr on ne va pas se charger de le tuer. (Quoi que je ferais peut être un tuto sur un module d'autodestruction) Note de relecture : Peut être aussi sur la gestion de perte de périphérique. C'est à dire gérer la perte d'un périphérique. On utilisera le mot clef delete là.
C'est sa naissance qui pose problème.
Le roby peut naitre à sa déclaration via une instanciation statique ou de façon différée via une instanciation dynamique.
La question est : Comment choisir entre ces deux là ?

J'ai testé les deux méthodes et voici mon retour d'expérience :

Citation de : Philippe, le rédacteur de ce tutoriel

Citation


L'instanciation de roby de façon dynamique m'a posé un problème majeur. Celui de ne pas compiler.
L'opérateur new n'était pas déclaré causant ainsi une erreur de compilation. Je me suis donc tourné vers la solution instanciation statique.

J'ai donc recodé mes appels et mon instanciation. Cette fois ci le code compilais mais les servomoteurs ne réagissaient pas comme je le voulais.
Je pense que la cause vient du fait que roby soit née avant l'initialisation d'arduino. Comme le montre le code "arduino-0018/hardware/arduino/cores/arduino/main.cpp" du core d'arduino :
Code : C
#include <WProgram.h>

int main(void)
{
init();

setup();

for (;<img src='http://www.robot-maker.com/public/style_emoticons/<#EMO_DIR#>/wink.gif' class='bbc_emoticon' alt=';)' />
loop();

return 0;
}
Pour que l'objet roby puisse être utilisé dans la fonction setup() et loop() je n'avais pas le choix : je devais déclarer roby dans l'espace global. Malheureusement l'instanciation a du se dérouler AVANT l'appel de la fonction init(); Nécessaire pour le bon fonctionnement de roby. Donc le choix d'une instanciation statique était impossible.


Image IPBBien sûr, je ne crois pas à 100% que ce soit l'origine de mes problèmes de Servomoteurs. En tout cas mon passage à l'instanciation dynamique a résolu le problème. Ce qu'il faut retenir de cette expérience c'est que l'espace global peut poser des problèmes et qu'on ne fait pas un choix entre l'instanciation dynamique et statique au pif. Pour + d'information là dessus je vous conseille d'aller voir des cours de C++.

Apprendre à Arduino à créer roby

Quoi qu'il en soit, créer (instancier) roby de façon dynamique vous apportera + d'expérience. On va donc faire comme ça. Comme je veux utiliser la façon puriste d'instancier notre objet c'est à dire avec le mot clef "new". Nous allons "trafiquer" le compilateur en redéfinissant l'opérateur new.
Image IPBnew et delete sont en fait des opérateurs en C++. Ils sont un poil + spéciaux. Image IPB
Comme ça devient super compliqué je vous demande de me faire confiance en copiant collant le code.



Code à mettre une fois pour toute avant d'utiliser le mot clef new :
Code : C

void * operator new(size_t size) {
return malloc(size);
}


Code final de départ Image IPB

Donc notre code final de départ pour roby est :
Code : C
void * operator new(size_t size) {
return malloc(size);
}

class Roby {
// Rien encore
};

Roby * roby;

void setup() {
roby = new Roby();
}

void loop() {
// Rien encore
}

Quand je compile ça, j'ai : Binary sketch size: 992 bytes. Image IPB

Si vous voulez : Le code complet.

Vous pouvez l'uploader dans votre Roby si vous voulez, mais vous verrez que ça ne fera rien puisque nous avons mis aucune méthode dans Roby. Image IPB

Voilà donc, roby notre objet prêt à être programmé orienté objet pour notre plus grand bonheur.
Vous verrez dans le prochain tutoriel comment arriver au même comportement que dans le tuto de robocop mais avec une programmation orientée objet.

Roby a ses propres objets.

Roby peut maintenant être programmé en orienté objet et nous l'avons prouvé. Le code minimal du chapitre précédent compilait et marche très bien dans Roby. (Programmé pour rien faire...)

L'objectif de ce chapitre est d'arriver au même point que robocop sur la détection d'obstacles. c'est à dire cette vidéo : Roby, détection d'obstacles

Analyse de Roby

Roby dispose d'actionneurs et de capteurs. Avant de le programmer faisons un inventaire de ce qu'il a.

Inventaire des périphériques de Roby

Cela va être facile, on va simplement faire l'inventaire de ce qu'il y a sur le schéma de robocop.

Image IPB

Moi je compte :
 
  • 2 capteurs UPDD ;
  • 2 servomoteurs ;
  • 1 interrupteur à levier double ;
  • Une plaque d'essai ;
  • Une batterie 9V ;
  • Un coupleur de 4 piles AA soit 6v total.

Donc son Roby, mon Roby et votre Roby ont tout ça.

Ok, bon vous êtes d'accord avec moi. Tous les périphériques cités ne sont pas tous programmables j'entends par là qu'il y a des choses comme le coupleur à pile, la plaque d'essai qui ne sont pas programmables ou qu'on ne peut rien recevoir d'eux (à part de l'alimentation). Donc ce qui nous reste ce sont 2 capteurs UPDD et 2 servomoteurs.

Ce qu'on peut faire

Ce qu'on peut faire et ce qu'on ne va pas faire c'est ça :
Code : C
 
// Code de Roby
class Roby {
Servo servog;
Servo servod;
UPDD upddg;
UPDD upddd;
};

Pourquoi est-ce mal de faire ça ? Tout simplement parce qu'on peut faire ça mieux. Je m'explique,

Séparer les couches d'abstraction.

Roby est une classe à très haut niveau d'abstraction. C'est-à-dire qu'elle s'éloigne de loin du matériel. Concrètement, quand vous demandez à roby d'avancer, roby lui, n'a pas à savoir comment marche les servomoteurs ou autre. Ce qu'il doit savoir faire en revanche c'est bouger son popotin qui est son châssis ! Donc Roby n'a pas de servomoteurs il a un Châssis qui lui a des Servomoteur.

Pour les UPDD c'est kif kif. Roby n'a pas à savoir qu'il joue avec des capteurs UPDD, il pourrait très bien fonctionner avec des boutons poussoirs comme antennes. Donc hop. Roby n'a pas d'UPDD il a un PareChocs qui lui a les deux UPDD.

Voilà un exemple pour vous faire comprendre l'intérêt:
Votre petite sœur arrive dans la pièce et écrase votre Roby. Par chance seul le PareChocs est cassé. Donc vous aller réparer le PareChocs c'est normal. Mais le fonctionnement ne sera pas le même vous aller donc forcément vouloir modifier le code de votre roby. Mais modifier le code de Roby n'as PAS de sens ! Il faut modifier le code du PareChocs pas celui de Roby !

Bref on a donc ce code :
Code : C
void * operator new(size_t size) {
return malloc(size);
}

class PareChocs {

};
class Chassis {

};

class Roby {
PareChocs * parechocs;
Chassis * chassis;
// J'ai choisis instanciation dynamique par habitude

// public: signifie que tout ce qui est en dessous est public
// trop long & compliqué pour être expliqué ici, voir un cours
// sur le c++
public:
// Constructeur : c'est la fonction qui est appelé lors
// de la création de l'objet
Roby() {
// Création des objets de Roby
parechocs = new PareChocs();
chassis = new Chassis();
}
};

Roby * roby;

void setup() {
// Création de Roby
roby = new Roby();
}

void loop() {
// Rien encore
}


A ce point là j'ai :
Binary sketch size: 1030 bytes

Implémentation

C'est la partie que je trouve généralement la plus intéressante parce qu'elle permet de voir qui sont les gens patients. Image IPB
Bref, dans ce chapitre la partie implémentation a déjà été faite, je veux dire qu'elle a déjà été faite dans le tuto Roby de robocop. On va reprendre le code et le recaser dans notre roby orienté objet !

Ce qui donne pour moi :
Code : C
#include <Servo.h>

#define AVANT 1
#define ARRIERE 0
#define GAUCHE 2
#define DROITE 3

#define SERVOG 1
#define SERVOD 0

#define UPDDG 3
#define UPDDD 4

#define VMAX 5

void * operator new(size_t size) {
return malloc(size);
}

class PareChocs {
public:
byte choc(int updd) {
// On retourne l'état du pin.
// S'il est haut c'est que c'est un choc.
return digitalRead(updd);
}

PareChocs() {
// A la création du PareChocs les pins sont prets ;p
pinMode(UPDDG, INPUT); 
pinMode(UPDDD, INPUT);
}
};

class Chassis {
// Le chassis a deux servomoteurs
Servo servog;
Servo servod;

// Fonction du tuto de robocop avec mes valeurs
int getNeutral(int s)
{
if(s == SERVOG)
return 94;
else
return 94;
}

// Fonction du tuto de robocop
void handleS(int s, int speed)
{
//Vitesse :
// 0 -> 0; 1 -> 1; 2 -> 3; 3 -> 8; 4 -> 10; v => 5 -> 30
int tab[6] = {0,1,3,8,10,30};
int pos = getNeutral(s);

int acc;
if (speed < 0)
acc = - tab[abs(speed)];
else
acc = tab[speed];

if(s == SERVOG)
servog.write(pos += acc);
else
servod.write(pos -= acc);
}

public:
// Le chassis se déplace grâce à la fonction
// du tutoriel de robocop
void move(int direction) {
int m1 = 0, m2 = 0;
switch(direction) {
case DROITE: m1 = 1; m2 = -1; break;
case GAUCHE: m1 = -1; m2 = 1; break;
case AVANT: m1 = 1; m2 = 1; break;
case ARRIERE: m1 = -1; m2 = -1; break;
}
handleS(SERVOG, m1*VMAX);
handleS(SERVOD, m2*VMAX); 
}

Chassis() {
// Lors de la création du chassis, l'attachement aux
// pins de l'arduino se fera automatiquement
servog.attach(10);
servod.attach(9);
}

};

class Roby {
PareChocs * parechocs;
Chassis * chassis;


byte detecterColision(int updd) {
// Roby demande au Parechocs s'il y a eu un choc.
// Il transmet directement le résultat.
return parechocs->choc(updd);
}

void contourner(int sens) {
// On contourne en reculant 500ms
// Puis en avant dans le sens demandé
// pendant 800ms
chassis->move(ARRIERE);
delay(500);

chassis->move(sens);
delay(800);
}

public:

Roby() {
// A la création de roby, Roby va créer ses périphériques
// c'est à dire son PareChocs et son Chassis
parechocs = new PareChocs();
chassis = new Chassis();

// Quand les objets de Roby sont créés tout est pret
chassis->move(AVANT);
}

void agir()
{
// Si on détecte une colision à gauche
// on tourne à droite
// Sinon, si on détecte une colision à droite
// on tourne à gauche
// Sinon, on demande au chassis d'avancer !

if(detecterColision(UPDDG))
contourner(DROITE);
else if(detecterColision(UPDDD))
contourner(GAUCHE);
else
chassis->move(AVANT);
}
};

// Roby est déclaré dans l'espace global pour pouvoir
// être utilisé dans les deux fonctions
Roby * roby;

void setup() {
// Création de Roby
roby = new Roby();
}

// Roby agira jusqu'à épuisement de l'alimentation ;p
void loop() {
roby->agir();
delay(15);
}


Le code complet final : Code complet.

J'aime tellement la programmation.
Le code est plutôt pas mal. Mais on trouvera des améliorations dans les prochains chapitres.

Je vois qu'il y a encore des gens qui doutent de l'utilité de ma version c++ de Roby. Il est vrai qu'en fin de compte le comportement de Roby est le même jusqu'à présent et nous a couté du boulot mais vous verrez que dans les chapitres suivants, cette version nous simplifiera la vie.

  Roby contrôlé avec un périphérique

Parler à Roby avec la liaison série.

Maintenant que Roby est orienté objet, la programmation de celui-ci va être plus facile.
Il n'y a rien à modifier sur le robot. Image IPB Vous aurez seulement besoin du câble usb pour le programmer.

Connecter Roby

Nous allons connecter Roby au PC via le câble USB. La communication va se faire par la liaison série.

Image IPBAnticipation à une question que vous allez vous poser : Faut il, oui ou non retirer la batterie 9v de l'arduino ? On sait que l'alimentation via USB délivre 5v, la batterie 9v. Selon la documentation du hardware, la tension d'entrée recommandée est entre 7V et 12V. Ici, en fait la carte arduino choisit son alimentation, je cite : "The Arduino Duemilanove can be powered via the USB connection or with an external power supply. The power source is selected automatically.". Donc vous êtes libres de laisser la batterie 9v ou non. De toute façon, si vous la retirez, les 5v de l'usb suffira largement, la tension délivrée par le pin 5v de la carte sera juste plus faible. Seul le survoltage est dangereux pour la carte.

Branchez votre Roby via usb et continuez le chapitre. (éteignez les servomoteurs si vous ne voulez pas gâcher de la batterie)

Lui parler

Pour effectuer un dialogue avec le robot, il est nécessaire d'engager la conversation.

Bonjour !

Cet engagement est fait via l'appel de begin() de la bibliothèque Serial. Concrètement ça donne ça :
Code : C
 
Serial.begin(vitesseDuDialogue);


Je vous recommande la vitesse 9600 bauds (en réalité cela représente un débit mais par analogie avec la conversation je garderais vitesse), tout simplement car beaucoup de gens utilisent celle-ci et de toute façon à moins d'avoir beaucoup de choses à dire on ne verra aucune différence. L'important c'est de choisir une "vitesse" standard qu'on peut trouver dans la liste en bas à droite de la fenêtre Serial Monitor du logiciel Arduino.

Pour que roby puisse nous parler, on peut utiliser les fonctions println() et print() de la bibliothèque Serial.
Voilà les liens de la doc sur ces fonctions :
http://www.arduino.cc/en/Serial/Print
http://www.arduino.c.../Serial/Println

Ce n'est pas ce qui nous intéresse ici. Ce que nous voulons c'est donner des ordres à Roby et lui, il n'a pas à discuter. On va donc apprendre à Roby à recevoir nos ordres.

Enseigner la discipline

La première chose que nous allons faire, c'est apprendre à Roby comment s'arrêter et séparer deux modes. Je vous avais dit que je voulais que Roby puisse garder un mode automatique mais, vu qu'on ne peut pas le laisser en mode automatique pendant qu'on le contrôle, on va devoir transformer le mode automatique en fonction qu'on pourra lui ordonner de faire.

Donc c'est simple, on va commencer par renommer la méthode agir() en agirAutomatiquement() car c'est plus correct.
Ensuite, on va ajouter la propriété modeAutomatique booléenne à Roby puisque maintenant les Roby peuvent être en mode automatique ou non. (Il faut aussi l'initialiser dans le constructeur de Roby, moi je mets : au départ Roby n'est pas en mode automatique)
Et finalement, Roby devra agirAutomatiquement seulement s'il est en mode automatique, cela va de soi.

Ce qui nous donne le code suivant :
Code : C
 
//[...]
class Roby {
PareChocs * parechocs;
Chassis * chassis;


byte detecterColision(int updd) {
return parechocs->choc(updd);
}

void contourner(int sens) {
chassis->move(ARRIERE);
delay(500);

chassis->move(sens);
delay(800);
}

public:
// Déclaration de l'atribut modeAutomatique
// Un puriste C++ mettrait bool à la place
// de byte. Mais je n'ai pas envie de vous
// perdre, je garde byte car vous le
// connaissez bien.
byte modeAutomatique;

Roby() {
parechocs = new PareChocs();
chassis = new Chassis();

// le mode automatique est désactivé
// à la construction de Roby
modeAutomatique = false;
}

void agirAutomatiquement() {
if(detecterColision(UPDDG))
contourner(DROITE);
else if(detecterColision(UPDDD))
contourner(GAUCHE);
else
chassis->move(AVANT);
}
};

Roby * roby;

void setup() {
roby = new Roby();

Serial.begin(9600);
}

void loop() {
// Si roby est en mode automatique
// roby agit automatiquement
// Sinon, ben rien...
if(roby->modeAutomatique)
roby->agirAutomatiquement();

delay(15);
}


Image IPBLe code n'est pas parfait. J'ai mis une propriété publique ce qui viole la règle de l'encapsulation, il y a des noms anglais mélangés avec des noms français et des byte à la place de bool.
Mais ce n'est pas la mort ici, vous pourrez l'améliorer à la fin.


Après ça, on va apprendre deux choses à Roby. S'Arrêter et Avancer. Contrairement à ce qu'on pourrait croire, Roby ne sait pas avancer. Il sait en revanche qu'il doit demander au Châssis d'avancer dans la fonction agirAutomatiquement() donc on va créer nos deux nouvelles méthodes publiques :
Code : C

void arreter() {
chassis->move(-1);
// va forcer la fonction de robocop
// à mettre la vitesse 0
}

void bouger(byte direction)
{
// Si Roby veut bouger. Il sait le faire
// Il sait maintenant qu'il faut bouger le chassis
chassis->move(direction);
}

void agirAutomatiquement() {
if(detecterColision(UPDDG))
contourner(DROITE);
else if(detecterColision(UPDDD))
contourner(GAUCHE);
else
bouger(AVANT);
}


J'en ai profité pour améliorer agirAutomatiquement(). Maintenant que Roby sait bouger() et s'arreter() on peut lui ordonner de faire ça.
Il faut maintenant que roby interprète les ordres.

Image IPBAjouter aussi un appel à arreter() dans le constructeur de Roby. Comme ça on sera sûr qu'il sera arrêté au départ. Si vous le faites pas, il se peut que les roues tournent n'importe comment au départ.

Écouter les ordres via la liaison série

Nous allons utiliser la liaison série exclusivement pour envoyer des ordres à Roby. Concrètement, la liaison série est un suite d'octets discontinue. Dans un soucis de simplicité, les ordres seront représentés par des octets, un octet pour un ordre distinct. Quand au moins un octet reçu n'est pas encore traité on dit qu'il y a disponibilité de données en anglais : disponible = available.

On teste la disponibilité de données du côté de Roby comme ça :
Code : C

if (Serial.available() > 0) {
// Ce qu'il faut faire si des données sont disponibles
}


Je vous ai dit que la liaison série est une suite d'octets. Un octet peut prendre une valeur entre 0 et 255 comprises. Pour faire encore plus simple on va utiliser une lettre et sa correspondance ascii pour représenter un ordre.
Pour le moment je vois 6 ordres pour Roby :
 
  • Avancer
  • Reculer
  • Aller à gauche
  • Aller à droite
  • S'arreter
  • Changer de mode (automatique ou non)

On va tous leur attribuer une lettre (un octet différent) moi je propose ça :

  Ordre Lettre ASCII (DEC) Avancer f (comme fordward : avant en anglais) 102 Reculer b (comme backward : arrière ou retardé en anglais) 98 Aller à gauche l (comme left : gauche en anglais) 108 Aller à droite r (comme right : droite en anglais) 114 S'Arrêter s (comme stop) 115 Changer mode m comme mode 115


Les valeurs ascii viennent de là : http://www.arduino.c...ence/ASCIIchart

Maintenant que nous avons défini tout ça, nous allons apprendre à Roby à reconnaitre les ordres.

Traiter les ordres

Nous allons maintenant voir comment traiter les ordres. Comme l'arduino se supporte pas les threads nous ne pouvons pas faire n'importe comment. Il faut que le traitement des ordres soit exécuté le plus régulièrement possible pour qu'on puisse envoyer quand on veut des ordres.
Image IPBOn verra qu'on ne pourra pas donner des ordres n'importe quand. Il y aura des moments où on devra attendre pour que roby nous obéisse. Un support des threads nous aurait permis un contrôle à tout instant.


Pour récupérer l'ordre on utilise le code :
Code : C

byte ordre = Serial.read(); // read = lire



Pour savoir quel ordre l'arduino va traiter, on utilise un switch (une structure conditionnelle du C)
Ce qui nous donne le code du loop() suivant :
Code : C
 
void loop() {

if (Serial.available() > 0) {
switch(Serial.read()) {
case 'f': // avancer
break;
case 'b': // reculer
break;
case 'l': // gauche
break;
case 'r': // droite
break;
case 's': // stop
break;
case 'm': // mode
break;
default: // ordre inconnu
break;
}
}

if(roby->modeAutomatique)
roby->agirAutomatiquement();

delay(15);
}


Il ne reste plus qu'à implémenter les ordres.

Implémentation des ordres

Les 5 premiers sont très faciles, il suffit de demander à Roby de bouger ou non.

Ordres de déplacement

Ce qui donne :
Code : C
switch(Serial.read()) {
case 'f': // avancer
roby->bouger(AVANT);
break;
case 'b': // reculer
roby->bouger(ARRIERE);
break;
case 'l': // gauche
roby->bouger(GAUCHE);
break;
case 'r': // droite
roby->bouger(DROITE);
break;
case 's': // stop
roby->arreter();
break;
case 'm': // mode
// reste à faire
break;
default: // ordre inconnu
break;
}


Il nous nous reste à implémenter l'ordre de changement de mode.

Ordre de changement de mode

Là encore, la simplicité est déconcertante grâce à notre approche orientée objet de Roby.
Il suffit de placer modeAutomatique de roby à vrai pour passer en mode automatique.
Et de la placer à faux si on veut passer en mode manuel.

Pour changer de mode dans les deux cas, c'est-à-dire si on est en manuel on passe à automatique et si on est en automatique passer à manuel. Il suffit d'affecter à modeAutomatique la négation de lui-même. Cela se traduit dans le code par :
Code : C

case 'm': // mode
roby->arreter(); // On s'arrête et on change de mode
roby->modeAutomatique = !roby->modeAutomatique;
break;


C'est fini, il est temps de tester Image IPB

Si vous voulez, voilà le code complet : Le code complet.

Contrôler à partir du Serial Monitor

Envoyez le programme dans votre Roby. Et lancez le Serial Monitor.
Une fois la fenêtre ouverte, vous pourriez envoyer des ordres à Roby !
Essaye "m" (mode automatique) "s" (l'arrêter) "b" reculer. etc...

Amusez vous bien.

Image IPBSi Roby ne répond pas à vos ordres, vous avez surement oublié d'ajouter Serial.begin(9600); dans la fonction setup(). Avant ou après la création de roby, cela n'a pas d'importance.
S'il ne répond toujours pas, vérifier qu'en bas à droite de la fenêtre Serial Monitor, le débit soit réglée à 9600 bauds


Image IPBS'il y a encore un problème postez un SOS sur le forum.

Voila donc ce chapitre terminé. Dans le prochain nous verrons le langage python et nous contrôlerons Roby avec un Joystick.

Voici mes recommandations pour améliorer la qualité du code si vous souhaitez (pour les programmeurs qui s'y connaissent) :
- Passez modeAutomatique en private. Créez son getter isModeAutomatique() et mettez l'ordre du changement de mode dans une méthode switchModeAutomatique() dans Roby ;
- Retirez les defines et mettez des constantes de classes ;
- Remplacer les byte par bool si on travaille avec des booléens.

Joystick & Python.

Ce chapitre fait l'objet de quelque chose de plus concret. En effet, jusqu'à maintenant nous nous occupions que de programmation. Ce sera encore le cas ici sauf que nous allons introduire un nouveau périphérique : le Joystick.

Nous allons nous éloigner de la programmation de l'Arduino et créer un logiciel "pilote" pour Joystick pour donner des ordres à l'arduino via une liaison série. Bien sûr, ce tutoriel est applicable à tout système qui accepte les communications via la liaison série.

Image IPBLe tutoriel est fait à la base pour l'utilisation d'un Joystick mais il reste très bien compatible avec des manettes de jeu. Même un volant. Je me concentre sur le Joystick car c'est simplement la seule manette de jeu pour PC que j'ai actuellement.

Image IPBPour les curieux : J'ai utilisé un Saitek Cyborg Evo Force mais ça marche avec n'importe quel Joystick

Image IPBCe chapitre n'est pas intégralement terminé, je dois faire une vidéo et ajouter des détails d'installation pour Windows.

Installation des outils nécessaires

Nous allons utiliser dans ce chapitre, le langage interprété Python donc il vous faudra l'interpréteur.
De plus, nous utiliserons les bibliothèques pySerial pour la connexion série et pyGame qui est une bibliothèque pour faire des Jeux. pyGame est en fait un binding de la SDL c'est à dire que pyGame est une sorte d'intermédiaire entre python et les fonctions de la SDL qui est une bibliothèque pour faire des jeux en C++.
Nous n'allons pas faire de jeu, ce que nous allons faire c'est utiliser les fonctions de haut niveau de la SDL sur les manettes de Jeu. La programmation sera donc grandement simplifiée et rapide.

Installation sur Linux

Dans toute bonne distribution Linux, python est déjà installé et peut être aussi pySerial.
pyGame en revanche ne devrait pas l'être.

Tapez ces commandes dans les distributions basés sur debian :
Code : Console

sudo apt-get python-serial python-pygame


Tous les outils sont installés.

Pour les autres distributions, cherchez pySerial et pyGame dans les dépôts de votre distribution.

Installation sur Windows

[en cours de rédaction]

Utiliser le module Joystick de pyGame

Le but de cette partie est d'afficher les données recueillies d'un Joystick sur la console. Nous verrons aussi comment transformer ça en ordres à envoyer via la liaison série.

Code de base pyGame

J'utilise rarement la SDL maintenant et encore moins python, cherchons ensemble un code de base pour le joystick.
Google : "pygame joystick example". Premier résultat je trouve ça:
http://archives.seul...5/msg00132.html
Code : Python
import pygame
from pygame import locals

pygame.init()

pygame.joystick.init() # main joystick device system

try:
j = pygame.joystick.Joystick(0) # create a joystick instance
j.init() # init instance
print 'Enabled joystick: ' + j.get_name()
except pygame.error:
print 'no joystick found.'


while 1:
for e in pygame.event.get(): # iterate over event stack
print 'event : ' + str(e.type)
if e.type == pygame.locals.JOYAXISMOTION: # 7
x , y = j.get_axis(0), j.get_axis(1)
print 'x and y : ' + str(x) +' , '+ str(y)
elif e.type == pygame.locals.JOYBALLMOTION: # 8
print 'ball motion'
elif e.type == pygame.locals.JOYHATMOTION: # 9
print 'hat motion'
elif e.type == pygame.locals.JOYBUTTONDOWN: # 10
print 'button down'
elif e.type == pygame.locals.JOYBUTTONUP: # 11
print 'button up'

Récupérer le code.
Trop facile. J'ai testé le code, il marche bien. Même si l'auteur se plaint que le code ne marche pas intégralement pour lui sur OSX, si ça marche chez nous tant mieux pour nous. Ce n'est pas un drame si on ne comprend pas bien le python et encore ce code là. Ce qui est sûr c'est que ce code est bien adapté à nos besoins. Testons le code maintenant chez vous.

Tester le code

Testons d'abord le code qu'on a trouvé.

Sauvegardez le fichier dans un fichier finissant par .py exemple : pilote.py.

Exécutez le en tapant dans la console une fois dans le dossier du fichier (Je vous conseille le dossier du projet Arduino) :
Code : Console

python ./pilote.py


Le programme détectera automatiquement le premier joystick et lira les informations qu'il recevra. Par exemple, si vous bougez un axe ou que vous appuyez sur un bouton, le programme affichera dans la console votre action.

Si le programme affiche que vous n'avez pas de joystick, vérifiez que votre joystick est bien branché. Si ce problème persiste sous linux, vérifiez que votre utilisateur a les droits sur l'usb ou simplement, exécutez le programme avec les droits root.
Code : Console

sudo python ./pilote.py


Comme le programme n'a pas de moyen de s'arrêter lui même, quittez le de force avec la combinaison Ctrl + C dans la console.

Trouver les périphériques utiles

On peut trouver plusieurs petits périphériques sur une manette de jeu, 4 au total :
 
  • Les axes ;
  • Les boules ;
  • Les chapeaux ;
  • Les boutons.

Tous les joysticks ont des compositions différentes de ces périphériques. En tout cas la plupart ont au moins 2 axes. On les utilisera pour contrôler Roby (ou autre, un bras robotisé basé arduino par exemple).

Ce qu'on cherche, c'est récupérer l'état un périphérique particulier. Par exemple la valeur d'un axe ou si un bouton est pressé ou non.

Récupérer l'état d'un axe

Les axes m'intéressent particulièrement puisque ce sont eux que nous allons utiliser pour contrôler Roby.
On va utiliser 2 axes, un pour avancer ou reculer et un autre pour aller à gauche ou à droite.

Ce que vous devez savoir, c'est qu'à chaque fois que vous modifiez l'état de quelque chose sur le joystick, le joystick génère un évènement récupérable dans le code python. Quand un axe est bougé, le joystick génère un évènement pygame.locals.JOYAXISMOTION. Après cet événement on se trouve dans cette partie du code :
Code : Python
if e.type == pygame.locals.JOYAXISMOTION: # 7
#ICI
elif e.type == pygame.locals.JOYBALLMOTION:
print 'ball motion'


C'est donc ici qu'on va vérifier l'état des axes pour déterminer si l'axe est suffisamment à gauche, par exemple, pour envoyer l'ordre à Roby d'aller à gauche. Ce que je vous propose, c'est de donner priorité aux événements "rotation de roby" et de donner l'ordre d'arrêt de Roby "s" quand le manche de l'axe du joystick est en position centré.

Votre code, si vous utilisez un joystick devrait ressembler à ça :
Code : Python
 
#!/usr/bin/python

print 'Roby commande par un Joystick'

import pygame
from pygame import locals

pygame.init()

pygame.joystick.init() # démare la gestion des joystick

try:
j = pygame.joystick.Joystick(0) # récupère le premier joystick
j.init() # init instance
print 'joystick utilise: ' + j.get_name()
except pygame.error:
print 'aucun joystick trouve.'


while 1:
for e in pygame.event.get(): # iterate over event stack
if e.type == pygame.locals.JOYAXISMOTION:
# changez à tâtons les valeurs 3 et 1 jusqu'à ce
# que les bonnes directions s'affichent sur l'écran
x , y = j.get_axis(3), j.get_axis(1)
if x < -0.5:
print("gauche")
elif x > 0.5:
print("droite")
else:
if y < -0.5:
print("avant")
elif y > 0.5:
print("arriere")
else:
print("stop");
elif e.type == pygame.locals.JOYBALLMOTION:
print 'ball motion'
elif e.type == pygame.locals.JOYHATMOTION:
print 'hat motion'
elif e.type == pygame.locals.JOYBUTTONDOWN:
print 'button down'
elif e.type == pygame.locals.JOYBUTTONUP:
print 'button up'

Récupérer le code.
L'idée est simple, utilisez ce code pour trouver les bons numéros des axes. Vous avez les bons numéros quand vous gagnez le pactole avez les bonnes directions affichées sur la console lorsque vous bougez le manche. Ainsi, une fois que les valeurs seront correctes et que nous aurions appris à envoyer des ordres à Roby, nous remplacerons les print() (les affichages) par des envois d'ordres.

Récupérer l'état d'un bouton
à venir.

Récupérer l'état d'un chapeau ou boule
Consultez la doc de pyGame.

Envoyer et Recevoir avec pySerial

Nous avons vu comment récupérer les états des périphériques de notre manette de jeu. Maintenant, intéressons nous à comment envoyer des données via la liaison série.

Utilisation de pySerial

Pour utiliser la bibliothèque pySerial dans son code python, il est nécessaire de l'importer (comme toutes les bibliothèques python).

L'importation se fait via l'instruction
Code : Python

import serial


en haut du code.

Vous devez maintenant identifier le port série que vous allez utiliser sur votre ordinateur, celui de l'arduino. L'équipe d'Arduino a pensé à nous et nous a simplifié cette tâche. Il suffit d'ouvrir le logiciel arduino et d'aller sur Menu -> Tools -> Serial Port et de mémoriser ou noter le port série affiché, celui de votre arduino.

Dans mon cas c'est /dev/ttyUSB0, voyons comment se connecter en python.

La connexion au port série s'effectue avec ce code :
Code : Python
 
liaison = serial.Serial('/dev/ttyUSB0', 9600)

/dev/ttyUSB0 étant mon port série et
9600 le débit que j'ai choisis sur mon arduino dans Serial.init(); Il est important qu'il soit le même.

Ensuite, envoyer ou recevoir des données est trivial.

Recevoir des données
De la même façon que sur l'arduino, en python on utilise la méthode read(). Bien qu'on ne va pas l'utiliser dans ce tutoriel je vous l'explique quand même si jamais vous comptez récupérer des informations depuis l'arduino. Par exemple, il serait intéressant de récupérer les états des capteurs de Roby pour signaler à l'utilisateur que Roby est bloqué. (Bien sûr, on peut aussi programmer Roby pour ignorer les ordres d'avancer si il est bloqué)

Voici les 3 façons de récupérer des infos depuis la liaison série avec pySerial.
Code : Python
 
octet = ser.read() # lit un octet
chaine = ser.read(10) # lit jusqu'à 10 octets
ligne = ser.readline() # lit jusqu'à un retour à la ligne (\n)


Envoyer des données
L'envoi des données nous intéresse puisque, c'est comme cela que nous allons envoyer des ordres à Roby.

Toujours de la même façon que du côté arduino, nous allons utiliser la méthode write().
write() prend en argument une chaine de caractères mais vous pouvez très bien envoyer qu'une lettre.

Exemple, envoyer l'ordre d'avancer à Roby :
Code : Python
 
liaison.write("f")


Pour envoyer d'autres ordres, changez la lettre et consultez le tableau récapitulatif des ordres pour Roby dans le chapitre précédent.

Fusion des codes

On sait récupérer les données du Joystick et envoyer des données à Roby. Il nous reste qu'à fusionner les deux codes pour obtenir un fichier python exécutable qui fera les deux opérations pour qu'on puisse enfin contrôler notre Roby depuis notre manette.

Mon code final pour Joystick

Voilà mon code final pour mon Joystick, il est compatible avec tous les roby programmés avec le code du chapitre précédent. Il faut juste ajuster quelques valeurs s'il ne marche pas avec votre Joystick. Toutes les indications sont dans les commentaires.

C'est sûr que des connaissances en python serait un avantage considérable pour réaliser cette fusion de code mais toute personne ayant déjà vu un peu de la programmation devrait être capable d'adapter mon code final pour lui.

Code : Python
 
#!/usr/bin/python

print 'Roby commande par un Joystick'

import pygame
import serial
from pygame import locals

pygame.init()

pygame.joystick.init() # démare la gestion des joystick

ser = serial.Serial('/dev/ttyUSB0', 9600)

try:
j = pygame.joystick.Joystick(0) # récupère le premier joystick
j.init() # init instance
print 'joystick utilise: ' + j.get_name()
except pygame.error:
print 'aucun joystick trouve.'


while 1:
for e in pygame.event.get(): # iterate over event stack
if e.type == pygame.locals.JOYAXISMOTION:
x , y = j.get_axis(3), j.get_axis(1)
if x < -0.5:
ser.write('l')
print("gauche")
elif x > 0.5:
ser.write('r')
print("droite")
else:
if y < -0.5:
ser.write('f')
print("avant")
elif y > 0.5:
ser.write('b')
print("arriere")
else:
ser.write('s')
print("stop");
elif e.type == pygame.locals.JOYBALLMOTION:
print 'ball motion'
elif e.type == pygame.locals.JOYHATMOTION:
print 'hat motion'
elif e.type == pygame.locals.JOYBUTTONDOWN:
ser.write('d')
print 'button down'
elif e.type == pygame.locals.JOYBUTTONUP:
ser.write('d')
print 'button up'


Récupérer le code.

Code final pour Manette de jeu
En attente d'une contribution ou d'un don généreux d'une manette de jeu.

Nous avons donc vu dans ce chapitre quelques notions de Python, comment recevoir des données d'une manette de jeu avec pygame et comment envoyer des informations via la liaison série en python.

Roby avec un Nunchuk, facile !

Les périphériques de wiimotes et non les wiimotes en eux-même, disposent d'une liaison I2C pour communiquer avec la wiimote. Cette liaison I2C est standard et très facile à utiliser surtout avec une carte Arduino.

Le but de ce chapitre est de connecter le périphérique pour wiimote le plus connu, c'est-à-dire le Nunchuk, à Roby. Toujours dans l'idée de nous faciliter la vie et de rester dans de la programmation de haut niveau (proche de notre langage) nous allons utiliser une bibliothèque pour éviter de traiter matériel avec l'I²C. Ce tutoriel est donc facile. Je vous conseille d'avoir suivi les 3 premiers chapitres avant de commencer celui là. Le 4ème n'est pas obligatoire.

Je commencerais le chapitre avec le code arduino Roby en fin du 3ème chapitre. Si vous ne l'avez pas, récupérez le.

J'ajouterais aux objectifs le fait que Roby devra conserver toutes ses fonctionnalités précédentes. Je veux dire, le mode automatique ou non et la possibilité de le diriger via le port série (Serial Monitor & Joystick).

Connecter un Nunchuk à Roby

La première étape de ce merveilleux chapitre sera de connecter le Nunchuk à Roby. Pour cela, on peut utiliser une petite plaque pour quelques dollars spécialement faite pour connecter un Nunchuk à Arduino.

Par plaque, je veux dire celles comme celles-ci, les wiichuck :

Image IPB

A mon avis, j'ai trouvé mieux pour connecter ça à l'arduino sans payer quoi que ce soit ou couper le câble du Nunchuk.
Je vous propose de suivre ma solution pour ne pas payer un centime !

4 bouts de câbles magiques
Seuls 4 connexions sont utilisés pour utiliser un Nunchuk.
 
  • Vcc, l'alimentation ;
  • Ground, le 0v ;
  • Data, la connexion pour les données ;
  • CLK, c'est à dire Clock, l'horloge.

Certains reconnaîtrons bien Data et CLK, les câbles de l'I²C. Bref, passons.

Il y a 6 connecteurs sur le connecteur Nunchuk. Par conséquent 2 ne seront pas utilisés. Ce sont ceux du milieu.

Voilà le plan top-secret comment on va câbler.

On va couper 2 câbles de 8cm et 2 câbles de 7cm (souple c'est mieux).
Dénudez toutes les extrémités de câbles de 1,2cm environ.
Si votre fil est multibrins, tenez avec une pince une extrémité du câble et tourner le câble pour torsader l'extrémité. Faites ceci sur toutes les extrémités.

Les connexions

Une fois les 4 câbles préparés, insérez une extrémité de chaque câble dans le connecteur nunchuk (dans les trous qui ne sont pas prévus pour ça à la base) en mettant les 2 câbles les plus long dans les connecteurs supérieurs (clk et ground).

Image IPB
(Image de yum9me)

Il ne reste plus qu'à brancher les câbles à l'arduino, vous devez le faire de la façon suivante :
 
  • Ground <-> arduino analog in 2
  • VCC <-> arduino analog in 3
  • Data <-> arduino analog in 4
  • Clk <-> arduino analog in 5

Si vous vous trompez, notre programme ne marchera pas.

Pour éviter que la nunchuk se débranche avec trop de vibrations, rabattez l'extrémité du câble sur Roby et fixez le avec du ruban adhésif.

Recevoir les événements du Nunchuck

Nous allons utiliser une bibliothèque d'abstraction matérielle disponible sur le site officiel de l'arduino.
La bibliothèque s'appelle WiiChuckClass, il s'agit d'une classe facilement utilisable, ce qui correspond à notre souhait.

Installer la bibliothèque WiiChuckClass
Téléchargez la bibliothèque ici et enregistrez le fichier sous le nom de WiiChuck.h dans le dossier du projet Arduino, c'est le dossier du code de Roby.

Une fois ceci de fait, rouvrez le projet Roby dans Arduino (s'il était déjà ouvert, fermez le). A présent un nouvel onglet est visible, c'est celui de WiiChuck.h le fichier de la bibliothèque WiiChuckClass.

Bien que la bibliothèque soit maintenant accessible il manque l'inclusion dans le code de Roby. C'est simple, il suffit d'ajouter
Code : C
#include <Wire.h> // dépendance
#include "WiiChuck.h"


Voilà, il faut maintenant nous intéresser aux méthodes de cette classe.

Utiliser WiiChuckClass

Nous devons commencer par créer un objet qui représentera notre Nunchuk.
Dans l'espace global du code, à côté de la déclaration de Roby :
Code : C

WiiChuck chuck = WiiChuck();


Pour commencer la connexion au Nunchuk, une méthode doit être appelée. Elle a pour rôle de commencer le dialogue. On met donc ce code dans la fonction setup().
Code : C
chuck.begin();

Le fonctionnement de la classe nous impose d'appeler une méthode spéciale pour mettre à jour les données récupérées du Nunchuk. En fait, nous n'obtenons pas de données en temps réel mais presque réel (exactement comme les capteurs updd, ~15ms de décalage dans mon cas, cela dépend du delay() dans la fonction loop()) si le temps de rafraichissement des données est très court. Nous allons donc appeler cette méthode dans la fonction loop() et mettrons le code relatif au Nunchuk après la gestion des ordres par liaison série et avant la gestion du mode automatique. Cela, de manière à séparer les sections.

Traiter les évènements en ordres pour Roby

Le Nunchuk embarque un stick analogique, 2 boutons : C et Z, et d'un accéléromètre tri-dimensionnel. Que de choses utiles donc.
Nous allons nous intéresser au stick, le stick donnera les ordres de mouvement à Roby et les boutons on peut s'en servir pour lancer le mode automatique ou le retirer si on veut.

Récupérer les données du stick analogique

La différence entre en stick directionnel et un stick analogique est que le stick analogique supporte plusieurs intensités d'inclinaison du stick. Concrètement, on pourrait avancer Roby + ou - vite en fonction de l'inclinaison. Ce n'est pas l'objet de ce chapitre. Ce qu'on va faire c'est juste détecter si on doit faire bouger Roby ou non.

On récupère les données du stick avec les fonctions readJoyX() et readJoyY() respectivement, pour obtenir l'inclinaison gauche/droite et l'inclinaison haut/bas.

Ces fonctions retournent un entier (int) par défaut, ils sont signés.
Lorsque readJoyX() < 0 le stick est à gauche sinon, il est à droite (ou pile au milieu si = 0).
Lorsque readJoyY() < 0 le stick est en bas sinon, il est en haut (ou pile au milieu si = 0).

Il est très difficile d'avoir son stick pile au milieu. Par exemple, la Wiimote recalibre le stick des Nunchuk à chaque connexion. C'est pour cela qu'il ne faut jamais toucher au Nunchuk lors du démarrage d'une wiimote. (Sous peine de voir son personnage foncer dans une direction)
On va donc considérer que le stick est centré lorsque -50 < x < 50 et -50 < y < 50.
50 est une valeur que j'ai choisis car après plusieurs tests, le script marche plutôt pas mal avec ces valeurs. Bien évidemment, vous pouvez adapter ces valeurs pour vos besoins.

Passons maintenant au code.
Code : C
void loop() {

if (Serial.available() > 0) {
switch(Serial.read()) {
case 'f':
roby->bouger(AVANT);
break;
case 'b':
roby->bouger(ARRIERE);
break;
case 'l':
roby->bouger(GAUCHE);
break;
case 'r':
roby->bouger(DROITE);
break;
case 's':
roby->arreter();
break;
case 'm':
roby->arreter();
roby->modeAutomatique = !roby->modeAutomatique;
break;
default:
break;
}
}

chuck.update(); // on actualise les données du nunchuk

if(chuck.readJoyX() < -50) // si le stick est trop à gauche
roby->bouger(GAUCHE); // on va à gauche
else if(chuck.readJoyX() > 50) //sinon si il est trop à droite
roby->bouger(DROITE); // on va à droite
else if(chuck.readJoyY() > 50) // sinon si le stick est en avant
roby->bouger(AVANT); // on avance...
else if(chuck.readJoyY() < 50) // devinez
roby->bouger(ARRIERE);

if(roby->modeAutomatique)
roby->agirAutomatiquement();

delay(15);
}


J'ai envie de dire : trop facile. Le code est vraiment logique. A partir de maintenant, Roby peut pleinement être contrôlé avec un Nunchuk. Il suffit d'uploader dans l'arduino ce nouveau code.

Vous pouvez aussi vouloir lancer le mode automatique depuis les boutons du Nunchuk.

Les boutons C et Z

Quand le bouton C est appuyé la méthode cPressed() vaut true.
Quand le bouton Z est appuyé la méthode zPressed() vaut true.

Du coup le code pour mettre Roby en mode automatique est simple.
Code : C

if(chuck.zPressed()) { // si bouton z appuyé
roby->modeAutomatique = true; // on met en mode auto
} else if(chuck.cPressed()) { // si c est appuyé
roby->modeAutomatique = false; // mode auto false
roby->arreter(); // et on profite pour l'arrêter
}


Récupérer le code complet.

Le chapitre est maintenant terminé.

Parmi tous les autres, il est pour moi le plus amusant. Nunchuk à la main, on peut enfin contrôler Roby à côté de lui sans PC. Bien que ce n'est pas encore de la communication sans fil, ce système donne l'impression de tenir la laisse d'un chien. Enfin, oubliez pas que Roby est toujours un objet.

  Mouvements avancés


Refactoring

Cette étape est facultative mais je vous la recommande fortement. L'idée est simple, nous allons nous concentrer pour améliorer la qualité du code (sa cohérence, sa facilité de compréhension & de maintenabilité). Nous allons apporter de nombreuses modifications dans les prochains chapitres c'est pour cela qu'il vaut mieux améliorer la qualité du code maintenant.

Je suis conscient que les non programmeurs peuvent être très "déconnectés" sur ce chapitre. Dans le cas où ce chapitre vous est trop dur, vous pouvez prendre le code final et continuer.

Image IPBChapitre non terminé

Programmation modulaire

La première chose que l'on va faire, c'est séparer les classes différentes dans des fichiers différents. Roby commence à devenir un gros projet et on n'a pas forcément envie de voir le code du PareChocs dans le même fichier lorsqu'on veut simplement modifier le Chassis.
C'est à cela que la programmation modulaire sert. De plus, on sépare encore mieux les couches d'abstraction.

La méthode

Le processus de séparation du code en plusieurs fichiers est exactement la même qu la procédure de création de bibliothèques bien qu'à la place de mettre les 2 fichiers (en tête et source) dans un sous dossier on les garde dans le dossier du projet Roby.

Si vous ne savez pas faire ça, vous pouvez consulter le tutoriel sur la création de bibliothèque Arduino sur le site.

Cela implique qu'on doit réitérer ce processus autant de fois qu'on a de classes dans le fichier .pde Roby. C'est à dire 3 fois.
On se retrouvera donc avec 8 fichiers après cette étape : Roby.pde, WiiChuck.h, Chassis.h, Chassis.cpp, PareChocs.h, PareChocs.cpp, Roby.h, Roby.cpp.
On peut aussi arriver à 10 fichiers si on décide de séparer l'implémentation de la déclaration dans le fichier WiiChuck et d'ajouter un fichier pour les defines.

Image IPBPour des raisons techniques, vous ne pouvez pas créer de fichier .cpp avec le nom du projet. Par exemple si votre projet est Roby.pde, vous ne pouvez pas créer un fichier Roby.cpp. J'ai été confronté à ce problème et j'ai choisis de changer le nom du projet par : RobyProject.pde

Image IPBFaites attention lors de la création des fichiers, les erreurs viennent vite et les explications données par l'IDE ne sont pas toujours claire quand on fait du C++. Procédez étape par étape.

Quelques principes OO

Pattern Strategy

Notre code pour Roby est maintenant de bonne qualité et facilement extensible pour ajouter toutes nos futures fonctionnalités.

 

  • Heure actuelle : sept. 07 2014 08:10