Binome2017-9 : Différence entre versions
(40 révisions intermédiaires par 2 utilisateurs non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
+ | <include nopre noesc src="/home/pedago/ppeip/include/video-Robot_Binome09_2017-iframe.html" /> | ||
+ | __TOC__ | ||
+ | <br style="clear: both;"> | ||
Binôme 9 saison 8: Raphaël Bonvalet, Damien Tillaux. | Binôme 9 saison 8: Raphaël Bonvalet, Damien Tillaux. | ||
Ligne 9 : | Ligne 12 : | ||
= Cahier des charges = | = Cahier des charges = | ||
− | [[Fichier:Kiki.jpg]] | + | [[Fichier:Kiki.jpg|middle]] |
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 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. | ||
Ligne 15 : | Ligne 18 : | ||
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. | 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. | ||
− | + | ||
Ligne 21 : | Ligne 24 : | ||
= Réalisation du projet = | = 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 == | == 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. ([https://peip-ima.plil.fr/mediawiki/index.php/BE_2017-2018 BE 2017-2018]) | ||
+ | |||
+ | Voici le schématique sur lequel nous nous sommes basé. | ||
+ | |||
+ | [[Fichier:RobotPCB schem.png|500px|thumb||left|Schématique original]] | ||
+ | <br style="clear: both;"/> | ||
+ | |||
+ | === 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. | ||
+ | |||
+ | [[Fichier:Quireekoo schem.png|500px|thumb|left|Schématique de Quireekoo]] | ||
+ | [[Fichier:Quireekoo pcb.png|500px|thumb|right|PCB de Quireekoo]]] | ||
+ | <br style="clear: both;"/> | ||
+ | |||
+ | == 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 : | ||
+ | |||
+ | [[Fichier:Partie bas.jpg|700px|]] | ||
+ | |||
+ | |||
+ | 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 : | ||
+ | |||
+ | [[Fichier:Partie haut.png|700px|]] | ||
+ | |||
+ | 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 : | ||
+ | |||
+ | [[Fichier:Roue.png|200px|]] | ||
+ | |||
+ | 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 : | ||
+ | |||
+ | [[Fichier:Quireekoo.jpg|200px|]] | ||
+ | |||
+ | == 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. |
Version actuelle datée du 16 juillet 2018 à 15:38
Sommaire
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
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é.
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.
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 :
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 :
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 :
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 :
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.