Binome2017-9

De Wiki de bureau d'études PeiP


Vidéo HD


Binôme 9 saison 8: Raphaël Bonvalet, Damien Tillaux.


Projet Quireekoo

Le but du projet Quireekoo est de créer un robot communiquant avec d'autre via infra-rouge. Ce projet à eu lieux lors des bureaux d'étude de PEIP en IMA au S4 2018 de notre cursus pour devenir ingénieur. Voici la transcription phonétique de Quireekoo: /Kirikou/.

Cahier des charges

Kiki.jpg

Quireekoo sera un robot fait de A à Z. Le circuit imprimé sera totalement revu et sera guidé grâce à une ATMEGA-328. Le physique sera totalement design par nous même et son programme sera totalement autonome.

Quireekoo étant petit et vaillant est un véhicule de petite taille. A la défaut de jambe, Quireekoo ce déplace avec 2 servos moteurs continue. A la place des yeux, un sonar pour éviter ses obstacles et ne pas trébucher. Pour ses oreilles nous avons prévu 2 Tsop38238 pour pouvoir repérer et fuir ses ennemis.



Réalisation du projet

Voici les différentes étapes par lesquels nous sommes passés lors de la création de Quireekoo.


Liste des objets récupérés

- sonar - led infra-rouge - 2 servos-moteurs - boitier de pile - 6 piles - 1 bille - 1 tsop


Réalisation de PCB

Idée principale

Nous voulions refaire nous même le PCB de Quireekoo mais nous n'avions pratiquement aucune base sur comment créer un circuit imprimé pour ce genre de projet. Du coups nous avons pris comme modèle, le modèle donnée sur la page principale du projet. (BE 2017-2018)

Voici le schématique sur lequel nous nous sommes basé.

Schématique original


Notre PCB

Notre PCB a été réalisé sur Fritzing. Nous avons créé ce circuit imprimé à notre image pour notre véhicule. Nous avons enlevé le driver de moteur et les sorties moteurs car nous voulions un robot de petite taille. Donc le moyen de déplacement choisi a alors été le servo-moteur. Pour les réceptions infra-rouge nous n'avons prévu que deux sorties pour des Tsop pour pouvoir laisser un plus grande chance au chasseur de ne pas être repéré, pour une approche plus vrai que nature. Pour savoir si un chasseur nous a attrapé, nous utilisons une sonde de Hall.

De plus, en cas de problème nous avons créé toutes les sorties du microcontrôleur. Si un problème apparaît sur une des pins utilisées, nous pourrons la relocaliser sur une autre sortie.

Un objectif supplémentaire était de réaliser un PCB le plus petit possible pour que le véhicule le soit aussi. Ce fut presque un succès, la carte est d'une taille environ de 5cm*5cm. Elle aurait pu être encore plus petite avec plus de travail mais nous ne pouvions pas nous le permettre si nous voulions tout faire.


Voici le schématique et le PCB de Quireekoo.

Schématique de Quireekoo
PCB de Quireekoo
]


Réalisation de la partie mécanique du véhicule

Pour la partie mécanique de Quireekoo on a choisi de le faire sur inkscape afin de pouvoir effectuer une découpe laser. Le robot est composé de deux plaques de plexiglas, une pour le châssis où il y a les servos moteur, la bille et le bloc de pile, et une pour placer le sonar, le circuit imprimée, les Tsop et la sonde de hall.

Voici à quoi ressemble la partie du bas sur inkscape :

Partie bas.jpg


Pour la partie du haut c'était un peu plus compliqué car il fallait trouver comment placer les tsop, la sonde de hall et le sonar. Pour pouvoir fixer ceux-ci on a choisi de faire des trous en forme de rectangles avec une taille égale à la taille des pins de chaque capteurs différents. Après il fallait aussi mettre les trous pour fixer le circuit imprimé au même endroit que ceux qui se trouve sur celui-ci. Pour ce faire, on a utilisé le fichier .svg donné par Fritzing. On a également mis des trous en forme de rectangle sur les côtés pour pouvoir laisser passer les fils.

Voici à quoi ressemble la partie du haut sur inkscape :

Partie haut.png

Pour finir il nous restait les roues à faire. On a opté pour garder les roues données avec les servos moteurs en y rajoutant des partie en plexiglas au pistolet à colle pour rendre celles-ci plus grandes.

Voici à quoi cela ressemble sur inkscape :

Roue.png

Le problème avec cette solution c'est que les roues ne sont pas adhérentes. De ce fait là, on a ajouté une partie d'élastique autour avec de la colle glue.

Voici a quoi le robot ressemble à la fin :

Quireekoo.jpg

Réalisation du code Arduino

Voici maintenant la présentation du code téléversé dans Quireekoo.

Le code

{

 #include <Servo.h>
 #include <IRremote.h>
 
 //déclaration des différentes pins
 
 #define SERVO1 11
 #define SERVO2 13
 #define ECHO 17
 #define TRIGG 18
 #define HALL A2
 #define IR 2
 #define VCCIR1 A0
 #define VCCIR2 A1
 
 //création de la classe Mouv pour le déplacement du robot
 
 class Mouv {
 
         Servo servo1;
         Servo servo2;
         int servo1Pin;
         int servo2Pin;
         
         int etatRotation;
         int etatSonar;
         int etatHall;
         int etatTsop;
         
         long intervalTsop;
         
         long intervalMouvement1;
         long intervalMouvement2;
         long intervalMouvement3;
         long intervalMouvement4;
         
         unsigned long lastMillis;
         
         
   public:
     Mouv(int pin1 , int pin2){
       servo1Pin= pin1;
       servo2Pin = pin2;
       
       etatRotation = 0;
       etatSonar = 0;
       etatHall = 0;
       etatTsop = 0;
       lastMillis = 0;
       
       intervalTsop = 500;
       
       intervalMouvement1 = 1500;
       intervalMouvement2 = 1700;
       intervalMouvement3 = 3200;
       intervalMouvement4 = 3400;
     }
   
     void attacher(){
       servo1.attach(servo1Pin);
       servo2.attach(servo2Pin);
     }
 
     void deplacement(int distanceObstacle , int tsop , int hall, int* attraper){
       unsigned long currentMillis = millis();
       
       if (hall == 1){
         etatHall = 1;
         *attraper = 1;
       }
       else if (distanceObstacle < 200){
         etatSonar = 1;
       }
       else if (tsop==1){
         etatTsop = 1;
         lastMillis = currentMillis;
       }
       
       if (etatHall == 1){
         servo1.write(90);
         servo2.write(90);
       }
       
       else if (etatSonar ==1){
         if (etatRotation == 0){
           etatRotation = random(1,3);
         }
         if(etatRotation == 1){
           servo1.write(180);
           servo2.write(180);
         }
         else{
           servo1.write(0);
           servo2.write(0);
         }
         etatSonar = 0 ;
         lastMillis = currentMillis;
       }
       
       else if (etatTsop == 1){
         if (etatRotation == 0){
           etatRotation = random(1,3);
         }
         
         if(etatRotation == 1){
           servo1.write(180);
           servo2.write(180);
         }
         else{
           servo1.write(0);
           servo2.write(0);
         }
         
         if (currentMillis - lastMillis >= intervalTsop){
           etatTsop = 0;
           lastMillis = currentMillis;
         }
       }   
       
       else{
         etatRotation = 0;
         if (currentMillis - lastMillis < intervalMouvement1){
           servo1.write(180);
           servo2.write(70);
         }
         else if ((currentMillis - lastMillis >= intervalMouvement2) &&  (currentMillis - lastMillis <= intervalMouvement3)){
           servo1.write(98);
           servo2.write(0);
         }
         else{
           servo1.write(180);
           servo2.write(0);
           if (currentMillis - lastMillis >= intervalMouvement4){
             lastMillis = currentMillis;
           }
         }
       }
     }
 };                                                         
 
 
 
 Mouv servo(SERVO1,SERVO2);
 
 //varibable pour le sonnar
 
 long lectureEcho;
 long distance;
 const float SOUND_SPEED = 340.0 / 1000;
 
 //varibable pour la sonde de hall
 
 int* attraper;
 
 //varibable pour les tsop
 
 unsigned long last_millis = 0; 
 long interval = 500;
 
 int broche_reception1 = VCCIR1;
 IRrecv reception_ir1(broche_reception1);
 decode_results decode_ir1;
 
 int broche_reception2 = VCCIR2;
 IRrecv reception_ir2(broche_reception2);
 decode_results decode_ir2; 
 
 //instance pour l'emmission infra-rouge
 
 IRsend emission_ir; 
 
 
 void setup() {
   pinMode(TRIGG,OUTPUT);
   digitalWrite(TRIGG,0);
   pinMode(ECHO,INPUT); 
   pinMode(IR,OUTPUT);
   digitalWrite(IR,1);
 
   servo.attacher();
   
   reception_ir1.enableIRIn(); // démarre la réception
   reception_ir2.enableIRIn();
   
   attraper = malloc(sizeof(int));
   *attraper = 0;
   
 }
 
 
 void loop() {
   servo.deplacement(sonar(),tsop(attraper),hall(),attraper);  
 }
 
 
 int sonar() {
   digitalWrite(TRIGG, 1);
   delayMicroseconds(10);
   digitalWrite(TRIGG,0);
   long measure = pulseIn(ECHO, HIGH);
   
   float distance_mm = measure / 2.0 * SOUND_SPEED;
   return distance_mm; 
 }
 
 int tsop(int * attraper ){
   unsigned long current_millis = millis();
   int etatTsop = 0;
   if ((current_millis - last_millis >= interval)&&(*attraper == 0)){
      emission_ir.sendNEC(1, 32); 
      last_millis = current_millis; 
      reception_ir1.enableIRIn();
      reception_ir2.enableIRIn();
   }
   
   if (reception_ir1.decode(&decode_ir1)) 
   { 
     if(decode_ir1.value == 1){
       etatTsop = 1;
     }
 
     reception_ir1.resume();
     reception_ir2.enableIRIn();
   }
   if (reception_ir2.decode(&decode_ir2)) 
   { 
     if(decode_ir2.value == 1){
       etatTsop = 1;
     } 
     reception_ir2.resume();  
     reception_ir1.enableIRIn();
   }
   return etatTsop;
 }
 
 
 int hall(){
   int etatHall = 0;
   if(digitalRead(HALL) == 0){
     etatHall = 1;
   }
   return etatHall;
 }

}

Description de la structure du code

Fonction sonar()

Cette fonction permet au sonar de calculer la distance séparant le robot d'un obstacle.

{

 int sonar() {
   digitalWrite(TRIGG, 1);
   delayMicroseconds(10);
   digitalWrite(TRIGG,0);
   long measure = pulseIn(ECHO, HIGH);
   
   float distance_mm = measure / 2.0 * SOUND_SPEED;
   return distance_mm; 
 }

}


Fonction tsop()

Cette fonction a deux rôles. Le premier est de lire les entrées des tsop et de renvoyer si un prédateur a été détecté ou non. Le deuxième est d'émettre un signal infra-rouge permettant au prédateur de nous détecter.

{

 int tsop(int * attraper ){
   unsigned long current_millis = millis();
   int etatTsop = 0;
   if ((current_millis - last_millis >= interval)&&(*attraper == 0)){
      emission_ir.sendNEC(1, 32); 
      last_millis = current_millis; 
      reception_ir1.enableIRIn();
      reception_ir2.enableIRIn();
   }
   
   if (reception_ir1.decode(&decode_ir1)) 
   { 
     if(decode_ir1.value == 1){
       etatTsop = 1;
     }
 
     reception_ir1.resume();
     reception_ir2.enableIRIn();
   }
   if (reception_ir2.decode(&decode_ir2)) 
   { 
     if(decode_ir2.value == 1){
       etatTsop = 1;
     } 
     reception_ir2.resume();  
     reception_ir1.enableIRIn();
   }
   return etatTsop;
 }

}

Les particularités de cette fonction sont que, premièrement pour faciliter la programmation, l'émission et la réception ont été mise dans la même fonction car elles demandent toutes les deux la même bibliothèque et les même timer, donc pour éviter tout conflit ceci fut une bonne solution. Deuxièmement, la fonction tsop() contient un pointeur en paramètre. Ce pointeur permet au robot de savoir s'il doit continuer à émettre ou non s'il est attrapé.

L'émission du signal infra-rouge ici se fait toute les 0.5s. En effet si les émissions sont trop proches il y a conflit avec la réception.

Fonction hall()

Cette fonction permet de voir si un prédateur nous a détecté et attrapé grâce à l'émission d'une onde électromagnétique.

{

 int hall(){
   int etatHall = 0;
   if(digitalRead(HALL) == 0){
     etatHall = 1;
   }
   return etatHall;
 }

}


Fonction deplacement()

Pour le déplacement du robot nous avons choisi de créer un classe pour avoir un code plus propre et une fonction loop() ne contenant qu'une seul ligne de code.

La Classe Mouv contient 2 fonctions. La première attacher() permet de lier les instances servo aux pins associées. Elle est appelée dans le setup(). La deuxième fonction est la fonction de déplacement appelée deplacement(). Cette fonction est la seule appelée de la fonction loop().

Cette fonction est alors la fonction principale du robot, car c'est elle qui va décider du déplacement du robot en fonction des différentes entrées des capteurs. Elle contient en paramètre les différentes entrées du sonar, du tsop, de la sonde de hall, mais aussi un pointeur pour pouvoir sauvegarder en mémoire le fait que le robot soit attrapé ou non.

{

     void deplacement(int distanceObstacle , int tsop , int hall, int* attraper){
       unsigned long currentMillis = millis();
       
       if (hall == 1){
         etatHall = 1;
         *attraper = 1;
       }
       else if (distanceObstacle < 200){
         etatSonar = 1;
       }
       else if (tsop==1){
         etatTsop = 1;
         lastMillis = currentMillis;
       }
       
       if (etatHall == 1){
         servo1.write(90);
         servo2.write(90);
       }
       
       else if (etatSonar ==1){
         if (etatRotation == 0){
           etatRotation = random(1,3);
         }
         if(etatRotation == 1){
           servo1.write(180);
           servo2.write(180);
         }
         else{
           servo1.write(0);
           servo2.write(0);
         }
         etatSonnar = 0 ;
         lastMillis = currentMillis;
       }
       
       else if (etatTsop == 1){
         if (etatRotation == 0){
           etatRotation = random(1,3);
         }
         
         if(etatRotation == 1){
           servo1.write(180);
           servo2.write(180);
         }
         else{
           servo1.write(0);
           servo2.write(0);
         }
         
         if (currentMillis - lastMillis >= intervalTsop){
           etatTsop = 0;
           lastMillis = currentMillis;
         }
       }   
       
       else{
         etatRotation = 0;
         if (currentMillis - lastMillis < intervalMouvement1){
           servo1.write(180);
           servo2.write(70);
         }
         else if ((currentMillis - lastMillis >= intervalMouvement2) &&  (currentMillis - lastMillis <= intervalMouvement3)){
           servo1.write(98);
           servo2.write(0);
         }
         else{
           servo1.write(180);
           servo2.write(0);
           if (currentMillis - lastMillis >= intervalMouvement4){
             lastMillis = currentMillis;
           }
         }
       }
     }

}

Cette fonction est divisée en deux partie. La première est la distribution des états du robot en fonction des entrées. Par la suite, la fonction va décider quel déplacement faire en fonction des priorités des différents états. L'état le plus prioritaire est l'état etatHall. Celui ci indique si le robot est attrapé et une fois passé à 1 il ne peut plus changer et le déplacement associé est un sur-place pour le robot. Après il y a l'état associé aux obstacles et si la distance devient inférieur à celle écrite, le robot tourne sur lui même dans une direction aléatoire jusqu'à ne plus percevoir d'obstacle. Le suivant est celui de la détection de prédateur. Ici si un prédateur est détecté, Quireekoo va aussi se tourner dans une direction aléatoire. Mais la différence c'est que ici il va tourner pendant un certain intervalle de temps rentré dans le code. Le dernier déplacement n'est exécuté seulement si aucun des trois autres n'est fait. C'est le déplacement de base du robot. Ce déplacement est juste en zigzag.

Les difficultés rencontrées

Une grande difficulté lors de la programmation de Quireekoo fut l’émission et la réception infra-rouge. En effet la première idée pour l'émission était de juste allumer et éteindre la led infra-rouge à un fréquence de 38 kHz avec les intervalles de temps précis comme utilisé ici pour d'autre fonction avec les currentMillis et lastMillis. Les tsop réagissaient bien à l'émission. Le problème est que si l'on rajoutais des fonctions comme le déplacement ou le sonar, le temps d'arriver jusqu'à l'émission l'intervalle de temps qui permettait une fréquence de 38 kHz était dépassé.

{

 void allumer()
 {
   // check to see if it's time to change the state of the LED
   unsigned long currentMicros = micros();
    
   if((ledState == HIGH) && (currentMicros - previousMicros >= OnTime))
   {
     ledState = LOW;  // Turn it off
     previousMicros = currentMicros;  // Remember the time
     digitalWrite(ledPin, ledState);  // Update the actual LED
   }
   else if ((ledState == LOW) && (currentMicros - previousMicros >= OffTime))
   {
     ledState = HIGH;  // turn it on
     previousMicros = currentMicros;   // Remember the time
     digitalWrite(ledPin, ledState);   // Update the actual LED
   }
 }

}

Une solution fur de passer directement par les interruptions timer. Mais deux problèmes ce sont encore révélés. Le premier était que tous les timer étaient déjà utilisés par la bibliothèque servo.h, donc impossible de faire les interruptions. De plus avec cette façon de faire ou la précédente, il était impossible de faire passer un message à travers l'émission, donc impossible de différencier une proie d'un prédateur.

{

   // initialize timer1 
   noInterrupts();           // disable all interrupts
   TCCR5A = 0;
   TCCR5B = 0;
   TCNT5  = 0;
 
   OCR5A = 421;            // compare match register 16MHz/256/2Hz
   TCCR5B |= (1 << WGM52);   // CTC mode
   TCCR5B |= (1 << CS50);    // 256 prescaler 
   TIMSK5 |= (1 << OCIE5A);  // enable timer compare interrupt
   interrupts();             // enable all interrupts
 }
 
 ISR(TIMER5_COMPA_vect)          // timer compare interrupt service routine
 {
   digitalWrite(ledPin, digitalRead(ledPin) ^ 1);   // toggle LED pin
 }

}

Nous en sommes arrivé à utiliser la bibliothèque IRremote.h. Avec cette bibliothèque, l'émission et la réception infra-rouge se fait très simplement si elles sont séparées. Mais les applications utilisant les même timer, il y a alors conflit pour faire fonctionner les deux en même temps. Cela nous a pris du temps pour trouver une solution car aucune information n'a pu être trouvé sur le web. Le problème était que si nous faisions une émission, les timer de la réception était annulé. Il fallait alors les ré-allouer pour la réception après l'émission avec le ligne de code: reception_ir1.enableIRIn();.

{

if ((current_millis - last_millis >= interval)&&(*attraper == 0)){
      emission_ir.sendNEC(1, 32); 
      last_millis = current_millis; 
      reception_ir1.enableIRIn();
      reception_ir2.enableIRIn();
   }

}

Cette solution a alors été partagée avec les autres groupes qui avaient besoin d'émettre et de réceptionner un signal.

Nos erreurs lors du projet

Au cours de ce projet nous avons fait quelque erreurs. Nous avons alors appris de ces erreurs et allons les décrire.

Erreur lors de la création du circuit imprimé

Pour ce projet nous voulions faire tout le robot par nous même. Nous avons aussi refait le PCB en prenant comme exemple celui donner sur la page du projet qui est aussi celui affiché plus haut. Lorsque nous avions recopié le schéma, nous avons vu le leds de contrôle et avons décidé de les supprimer de notre circuit en pensant qu'elle ne servirait à rien. Ceci fut alors une grande erreur. Car lorsque nous avons reçu le circuit et que nous avons commencé à souder les composants, faire des tests pour savoir si les composant fonctionnaient comme il fallait ou de savoir si les soudures étaient bien faites devint très compliqué. Par chance aucun gros problème majeur n'a été trouvé. Nous savons maintenant que l'utilisation de led de contrôle est primordiale lors de la création de prototype.

Erreur lors de la création de la structure

Quelques problèmes mineurs ont été rencontrés. Lors de la création des trous pour faire rentrer les tsop, le sonar et la sonde de hall, nous avions pris les mesures de la longueurs de pins mais la mesure précise. De ce fait, sans une petite marge de distance, les fils ne passaient pas. Nous avons alors juste limé un peu et le problème fut résolu.

De plus nous n'avions pas prévu de trous ou autres solutions pour maintenir la batterie avec les piles. Nous l'avons alors attachée avec des élastiques. Cela fonctionne mais rend la rend inaccessible.

Nous remercions aussi Vincent de nous avoir passé un des ces supports pour les servos-moteurs, car sans nous nous demandons comment nous aurions pu les attacher.

Erreur de conception

Lorsque nous avons commencé à penser au projet, nous avons pensé à faire un robot le plus petit possible pour être le plus dur à attraper. Pour cela, nous avions décidé d'utiliser des servos-moteurs pour le déplacement. Nous pensions que la vitesse de rotation était suffisante pour avoir un robot plutôt rapide. La vitesse n'est alors pas si lente que ça mais nous nous attendions à mieux. Une question vient alors ce poser: aurait-il mieux fallut mettre des moteurs et non des servos-moteurs? Le défauts des moteurs est que ceci prennent alors plus de place et donc le robot n'est plus aussi petit que la taille voulut au départ. Nous avons alors conclut que les servos-moteurs restaient alors la meilleur solution pour le déplacement.