Projet IMA3 P1, 2016/2017, TD2 : Différence entre versions
(→Partie électronique) |
(→Partie informatique) |
||
(80 révisions intermédiaires par 5 utilisateurs non affichées) | |||
Ligne 16 : | Ligne 16 : | ||
=== Le matériel === | === Le matériel === | ||
− | Le matériel pour réaliser notre robot quadripode est relativement simple . Nous aurons besoin d'un accès a un ordinateur équipé d'un système | + | Le matériel pour réaliser notre robot quadripode est relativement simple. Nous aurons besoin d'un accès a un ordinateur équipé d'un système Linux. Pour le quadripode , nous aurons besoin de : |
* Une raspberry Pi ( de préférence une raspberry pi zéro en raison de sa petite taille) | * Une raspberry Pi ( de préférence une raspberry pi zéro en raison de sa petite taille) | ||
− | * 12 micro servos moteurs pour le mouvement des pattes , soit | + | * 12 micro servos moteurs pour le mouvement des pattes , soit 3 servos-moteurs par patte . |
* Un module wifi pour la raspberry Pi. | * Un module wifi pour la raspberry Pi. | ||
* Toutes sortes d'écrous et vis dont nous détaillerons le contenu dans le futur. | * Toutes sortes d'écrous et vis dont nous détaillerons le contenu dans le futur. | ||
Ligne 25 : | Ligne 25 : | ||
* Un câble nappe pour raspberry pi . | * Un câble nappe pour raspberry pi . | ||
− | La liste réalisée est provisoire et pourra subir de légers changement avant le début | + | La liste réalisée est provisoire et pourra subir de légers changement avant le début du projet (les types d'écrous et vis seront détaillés) . Nous aurons en plus des éléments précédents , besoin d'un accès a l'imprimante 3D afin d'imprimer les différentes parties du quadripode . |
== Séance 1 == | == Séance 1 == | ||
=== Partie électronique === | === Partie électronique === | ||
− | La conclusion que nous pouvons apporter après cette première séance de projet est que | + | La conclusion que nous pouvons apporter après cette première séance de projet est que la partie électronique de ce projet ne s’étendra pas sur les 3 séances puisque, suite à des discussions avec les différents professeurs, nous avons vu qu'il était plus adéquat de ne pas utiliser de FPGA pour la construction de l'araignée vu que nous utilisons un grand nombre de moteurs. Nous allons donc utiliser une unique carte Arduino qui contrôlera l'intégralité des moteurs. |
− | Pour répondre au cahier des charges du projet, nous avons donc, quand même, | + | Pour répondre au cahier des charges du projet, nous avons donc, quand même, programmé un FPGA contrôlant un des moteurs afin de manipuler tous les outils mis à notre dispositions. Nous allons donc vous présenter, dans cette partie, comment nous avons fait fonctionner notre moteur grâce au FPGA. |
Avant de pouvoir programmer notre FPGA, nous avons fait des recherches sur la data-sheet du moteur pour déterminer son mode de fonctionnement. | Avant de pouvoir programmer notre FPGA, nous avons fait des recherches sur la data-sheet du moteur pour déterminer son mode de fonctionnement. | ||
Ligne 38 : | Ligne 38 : | ||
[[Image:data-sheet_moteur.jpg|vignette|center|upright=3.4|alt=fonctionnement moteur|Fonctionnement du moteur]] | [[Image:data-sheet_moteur.jpg|vignette|center|upright=3.4|alt=fonctionnement moteur|Fonctionnement du moteur]] | ||
− | Nous pouvons voir grâce à la data-sheet que, pour que notre moteur fonctionne, il faut que le VCC(=5V) soit connecté au | + | Nous pouvons voir grâce à la data-sheet que, pour que notre moteur fonctionne, il faut que le VCC(=5V) soit connecté au câble rouge, la masse au câble marron et le moduleur de largeur d'impulsion (PWM) au câble orange. On remarque également que notre moteur fonctionne à une fréquence de 50Hz (donc une période de 20 ms) et que le sens de rotation dépends du temps à l'état haut du signal. C'est à dire que pour que le moteur tourne dans le sens anti-horaire pour aller à 0 degré, il faut que le duty cycle (rapport cyclique) soit à 1 ms, pour aller dans le sens horaire jusque 180 degrés, il doit être de 2 ms et pour que le moteur s'arrête au centre, le rapport cyclique doit être de 1.5ms. Nous allons donc jouer sur ce rapport cyclique afin de changer le sens de rotation du moteur. |
+ | |||
+ | Le problème qui s'est posé à nous est d'être assez précis pour pouvoir modifier de manière efficace le rapport cyclique. Étant donné que nous souhaitons avoir une précision de 0.1 ms sur une période de 20 ms, il nous faut donc 200 coups d'horloge (20/0.1). Comme nous fonctionnons à 50Hz, la fréquence du FPGA sera de 50*200=10000 Hz. | ||
+ | |||
+ | Pour faire fonctionner le moteur dans les 2 sens, nous devons maintenant "dire" au FPGA que quand notre compteur arrive a 200, il doit passer la valeur de sortie à 1. Celle ci reste à 1 jusqu'a la valeur choisie du compteur en fonction de l'angle que nous souhaitons puis la valeur de sortie passe à 0 jusqu'à ce que le compteur atteigne 200. | ||
+ | |||
+ | |||
+ | [[Image:fpga.jpg|vignette|center|upright=3.4|alt=Schema fpga|Schema bloc du fpga]] | ||
+ | |||
+ | |||
+ | Comme vous pouvez le voir sur l'image ci-dessus, nous avons fixé le BUS_JS à la valeur 200, ce bus est connecté à un comparateur et au compteur afin de compter jusque 200. Nous avons ensuite défini le Configurable Digital IO pour mettre la valeur souhaitée pour le fonctionnement du moteur (comprise entre 10 et 20). Cette valeur est mise dans le second comparateur (avec la valeur 200) et nous envoyons ceci dans le moteur qui nous donne la courbe suivante. | ||
+ | |||
+ | |||
+ | [[Image:graphe.jpg|vignette|center|upright=3.4|alt=courbe fpga|Graphe]] | ||
+ | |||
+ | Nous avons donc, dans la vidéo ci-dessous, mis la valeur 10 et 20 dans le Configurable Digital IO et nous remarquons que le moteur tourne bien dans les 2 sens. | ||
+ | |||
+ | |||
+ | <center><include iframe src="https://www.youtube.com/embed/g1hDQJHcsJw" width="320px" height="320px" frameborder="0" scrolling="yes"/></center> | ||
=== Partie informatique === | === Partie informatique === | ||
+ | |||
+ | Pour l'application web, nous avons décidé d'utiliser Node ainsi que Angular et les modules socket.IO, serialport de Node pour envoyer nos données vers l'arduino via une Raspberry. Nous voulions utiliser ces technologies récentes afin d'avoir une petite expériences avec celles ci et pour pouvoir apprendre à les utiliser. Angular nous permettant de créer une applicaton dynamique aisément. | ||
+ | Cette application web représente un joystick qui nous servira à faire déplacer notre robot. Celui ci devra permettre de dire au robot dans quelle direction il doit se déplacer et à quelle vitesse. De plus, il affichera sur la page web la direction que l'utilisateur est en train de donner. | ||
+ | |||
+ | Nous avons tout d'abord réalisé le joystick avec angular. Celui ci est réalisé grâce à 2 images. Une pour l’arrière plan du joystick qui est fixe et l'autre pour le joystick en lui même qui se déplace lorsque l'utilisateur clic sur celui ci et déplace la souris. Lorsque l'utilisateur reclic sur le joystick, celui ci se repositionne au centre. | ||
+ | |||
+ | Nous utilisons donc 2 événements : | ||
+ | * lors d'un clic : une variable est mise à jour "mouseIsDown" pour savoir si le joystick doit suivre la souris ou si il doit aller se repositionner en (0,0). | ||
+ | * lors d'un mouvement de la souris si "mouseIsDown" est égale à 1 : On va alors faire modifier le css de l'image pour faire en sorte que celle ci suive la souris. Celle ci fût la plus complexe à réaliser car il fallait bien calculer la position de l'image en fonction de celle de la souris mais aussi faire en sorte que l'image ne sorte pas de la zone délimitée par le socle du joystick. Nous avons pour cela utilisé beaucoup de formules de trigonométrie. | ||
+ | |||
+ | La taille du socle de notre joystick étant de 200px x 200px, pour vérifier si nous somme bien dans le cercle, nous testons si la norme entre la position x et la position y est bien inférieur à 100. | ||
+ | Si nous sommes bien dans le cercle, il suffit de donner la position de la souris à l'image. | ||
+ | Cependant, si la souris n'est pas dans les limites du cercle, nous devons recalculer la position de l'image afin qu'elle se positionne dans la même direction que la souris mais à la limite de la zone du joystick. Nous avons donc calculer l’hypoténuse puis l'angle par rapport à 0 sur le cercle trigonomètrique. pour repositionner le joystick au même angle mais avec une norme de 100 (le maximum sur le cercle que nous avons). | ||
+ | |||
+ | |||
+ | |||
+ | main.js : (définition de l'application et de ses dépendances) | ||
+ | |||
+ | angular.module('spiderApp') | ||
+ | .controller('MainCtrl', function () { | ||
+ | this.awesomeThings = [ | ||
+ | 'HTML5 Boilerplate', | ||
+ | 'AngularJS', | ||
+ | 'Karma' | ||
+ | ]; | ||
+ | }); | ||
+ | |||
+ | |||
+ | deplacement.js : (fonctionnement dynamique du joystick de la page web) | ||
+ | |||
+ | angular.module('spiderApp').controller('deplacementCtrl', function($scope) { | ||
+ | $scope.positionX=200; | ||
+ | $scope.positionY=200; | ||
+ | $scope.mouseIsDown=false; | ||
+ | posXstr=$scope.positionX.toString(); | ||
+ | posYstr=$scope.positionY.toString(); | ||
+ | $scope.posjoy = { | ||
+ | "left" : posXstr+"px", | ||
+ | "top" : posYstr+"px", | ||
+ | "position" : "absolute", | ||
+ | "height" : "100px" | ||
+ | }; | ||
+ | var socket = io.connect('http://172.26.79.4:8080'); | ||
+ | $scope.clickDown = function(event){ | ||
+ | if($scope.mouseIsDown==false) | ||
+ | { | ||
+ | $scope.mouseIsDown = true; | ||
+ | $scope.positionX = event.target.offsetLeft+(event.offsetX-50); | ||
+ | $scope.positionY = event.target.offsetTop+(event.offsetY-50); | ||
+ | posXstr=$scope.positionX.toString(); | ||
+ | posYstr=$scope.positionY.toString(); | ||
+ | $scope.posjoy = { | ||
+ | "left" : posXstr+"px", | ||
+ | "top" : posYstr+"px", | ||
+ | "position" : "relative", | ||
+ | "height" : "100px" | ||
+ | }; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | $scope.mouseIsDown = false; | ||
+ | $scope.positionX = 200; | ||
+ | $scope.positionY = 200; | ||
+ | console.log($scope.mouseIsDown); | ||
+ | posXstr=$scope.positionX.toString(); | ||
+ | posYstr=$scope.positionY.toString(); | ||
+ | $scope.posjoy = { | ||
+ | "left" : posXstr+"px", | ||
+ | "top" : posYstr+"px", | ||
+ | "position" : "relative", | ||
+ | "height" : "100px" | ||
+ | }; | ||
+ | } | ||
+ | socket.emit('message', ($scope.positionX-200)+','+(-($scope.positionY-200))+';'); | ||
+ | }; | ||
+ | $scope.moveJoystickOutside = function(event) { | ||
+ | if ($scope.mouseIsDown == true) { | ||
+ | window.setTimeout(function() { | ||
+ | ecartX=parseInt(getComputedStyle(joystick).left, 10); | ||
+ | ecartY=parseInt(getComputedStyle(joystick).top, 10); | ||
+ | //on retranche 200 la marge du boutton araigné jusqu'au bord du div #joystick + 50 pour arriver au centre de l'image | ||
+ | //+20 en Y à cause du footer qui sera toujours de taille constante | ||
+ | positionXProvisoire = event.clientX - ecartX -250; | ||
+ | positionYProvisoire = -(event.clientY - ecartY-330); | ||
+ | console.log("posi prov X: "+positionXProvisoire); | ||
+ | console.log("posi prov Y: "+positionYProvisoire); | ||
+ | //partie dans le cercle | ||
+ | if ( Math.sqrt(Math.pow(positionXProvisoire/100,2)+Math.pow(positionYProvisoire/100,2)) >= 0 && Math.sqrt(Math.pow(positionXProvisoire/100,2)+Math.pow(positionYProvisoire/100,2)) < 1) | ||
+ | { | ||
+ | //50=moitié de la dimension de l'image du bouton avec l'araignée. | ||
+ | //a retrancher car on veut le centre de l'image et pas le coin supérieur gauche. | ||
+ | $scope.positionX=event.clientX-ecartX-50; | ||
+ | $scope.positionY=event.clientY-ecartY-130; | ||
+ | console.log("posi X: "+$scope.positionX); | ||
+ | console.log("posi Y: "+$scope.positionY); | ||
+ | posXstr = $scope.positionX.toString(); | ||
+ | posYstr = $scope.positionY.toString(); | ||
+ | $scope.posjoy = { | ||
+ | "left": posXstr + "px", | ||
+ | "top": posYstr + "px", | ||
+ | "position": "relative", | ||
+ | "height": "100px" | ||
+ | }; | ||
+ | } | ||
+ | //partie en dehors du cercle | ||
+ | else | ||
+ | { | ||
+ | hypothenuse=Math.sqrt(Math.pow(positionXProvisoire,2)+Math.pow(positionYProvisoire,2)); | ||
+ | angle=Math.acos(positionXProvisoire/hypothenuse); | ||
+ | if(-(event.clientY - ecartY-330)<0) | ||
+ | { | ||
+ | $scope.positionX=parseInt((Math.cos(angle)*100)+200); | ||
+ | $scope.positionY=parseInt((Math.sin(angle)*100)+200); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | $scope.positionX=parseInt((Math.cos(angle)*100)+200); | ||
+ | $scope.positionY=parseInt((-Math.sin(angle)*100)+200); | ||
+ | } | ||
+ | posXstr = $scope.positionX.toString(); | ||
+ | posYstr = $scope.positionY.toString(); | ||
+ | $scope.posjoy = { | ||
+ | "left": posXstr + "px", | ||
+ | "top": posYstr + "px", | ||
+ | "position": "relative", | ||
+ | "height": "100px" | ||
+ | }; | ||
+ | } | ||
+ | //attendre un peu pour ne pas embrouiller la transmition du signal | ||
+ | socket.emit('message', ($scope.positionX-200)+','+(-($scope.positionY-200))+';'); | ||
+ | }, 10);//delay en millisecondes | ||
+ | } | ||
+ | }; | ||
+ | }); | ||
+ | |||
+ | Voici le rendu de l'application web, que nous trouvons à l'adresse 172.26.79.4 (Adresse IP de la Raspberry) sur le port 8080 : | ||
+ | |||
+ | [[Fichier:AppliWeb.jpg|thumb|center|500px]] | ||
+ | <br style="clear: both;" /> | ||
== Séance 2 == | == Séance 2 == | ||
− | === Partie | + | === Partie mécanique === |
+ | |||
+ | |||
+ | Le but premier de ce projet n'était pas de faire de la mécanique mais nous devions passer obligatoirement par cette étape pour que l'araignée puisse se déplacer. Cette partie nous a pris énormément de temps en dehors des séances. | ||
+ | |||
+ | - Réalisation des pièces nécessaires à la construction de l'araignée : | ||
+ | |||
+ | Nous avons décidé d'imprimer les pattes de notre araignée avec l'imprimante 3D du fabricarium en nous aidant d'un projet du même type trouvé sur internet : [http://www.instructables.com/id/A-3D-Printed-Quadruped-Robot/]. Cependant, les servos-moteurs mis à notre disposition pour ce projet étant de dimensions différentes de ceux utilisés sur le projet précédemment cité, nous avons du redimmensionner les différents pièces sur Freecad. Voici un aperçu des pièces finales Freecad : | ||
+ | |||
+ | [[Image:Femur.jpg|vignette|center|upright=3.4|alt=Femur|Femur]] | ||
+ | |||
+ | [[Image:Coxa.jpg|vignette|center|upright=3.4|alt=Coxa|Coxa]] | ||
+ | |||
+ | [[Image:Coxa_renforce.jpg|vignette|center|upright=3.4|alt=Coxa renforce|coxa renfoce]] | ||
+ | |||
+ | [[Image:Tibia_sc.jpg|vignette|center|upright=3.4|alt=Tibia|Tibia]] | ||
+ | |||
+ | Nous avons du réaliser de nombreux tests d'impression avant de parvenir à la taille idéale pour l'assemblage des différentes pièces. | ||
+ | Une fois les pièces nécessaires à l'assemblage des pattes imprimées, il nous fallait le corps de l'araignée. Nous avons alors pour cela découpé une forme simple en Plexiglas (Nous remerçions d'ailleurs M. Redon pour son Plexiglas) à l'aide de la découpe laser du Fabricarium. | ||
=== Partie informatique === | === Partie informatique === | ||
+ | |||
+ | Une fois l'application web créée et l'envoi des données possible sur le terminal grâce aux websockets, nous avons dû cette fois rediriger les données sur le port série. Cette partie nous a posé beaucoup de difficultés car nous n'arrivions pas à voir si les données s'envoyaient réellement ou non. Cependant, grâce au module "serial-port" de Node, l'envoi des données était assez simple en ayant bien lu la documentation. Nous n'avions simplement pas vu que le problème se posait du coté de la réception où l'arduino avait du mal à recevoir convenablement le message envoyé sur le port série. | ||
+ | Pour palier ce problème, nous avons élaboré un algorithme extrêmement basique sur arduino afin de pouvoir réaliser la transmission dans les meilleures conditions possibles. Celui ci traitant un à un les caractère afin de pouvoir reconstituer un entier de manière fiable. | ||
+ | |||
+ | |||
+ | server.js (permet d'envoyer le message créé dans deplacement.js sur le port série) | ||
+ | |||
+ | var express = require('express'); | ||
+ | var app = express(); | ||
+ | var server = require('http').Server(app); | ||
+ | var io = require('socket.io')(server); | ||
+ | var SerialPort = require("serialport"); | ||
+ | var port = new SerialPort("/dev/ttyUSB1",{baudRate: 115200, parity : 'none', autoOpen :false}); | ||
+ | app.use(express.static(__dirname+"/../..")); | ||
+ | app.use(express.static(__dirname+"/../../../bower_components/")); | ||
+ | port.open(function (error){ | ||
+ | if(error) | ||
+ | { | ||
+ | console.log('Error while opening the port ' + error); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | io.on('connection', function(socket) { | ||
+ | console.log('new connection'); | ||
+ | socket.on('message', function (message) { | ||
+ | port.write(message, function(err) { | ||
+ | if (err) { | ||
+ | return console.log('Error on write: ', err.message); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | console.log('Position : ' + message); | ||
+ | } | ||
+ | }); | ||
+ | }); | ||
+ | }); | ||
+ | } | ||
+ | }); | ||
+ | server.listen(8080, function() { | ||
+ | console.log('server up and running at 8080 port'); | ||
+ | }); | ||
== Séance 3 == | == Séance 3 == | ||
Ligne 53 : | Ligne 269 : | ||
=== Partie informatique === | === Partie informatique === | ||
+ | |||
+ | envoi des données sur le port série | ||
+ | |||
+ | Configuration de la Raspberry : | ||
+ | |||
+ | * Configuration IP :La Raspberry doit pouvoir communiquer avec l'ordinateur de la salle de projet pour pouvoir avoir accès à l'application web ainsi qu'au réseau de Polytech pour pouvoir installer les différents paquetages de Node. Pour cela, on modifie le fichier /etc/network/interfaces en remplaçant l'IP présente dans le fichier par celle de notre ordinateur de salle de projet et on configure en static. La Raspberry peut ainsi communiquer avec l'ordinateur. Ensuite, on indique le serveur DNS dans le fichier /etc/resolv.conf par : nameserver 193.48.57.34 | ||
+ | La Raspberry peut ainsi communiquer avec le réseau Polytech | ||
+ | |||
+ | * Installation de Node sur la Raspberry et déplacement des fichiers de code. Nous avons été confronté à de nombreuses difficultés pour cette partie. En effet, notre code faisant intervenir de nombreux modules, il a fallu tous les installer et nous avons rencontré des problèmes de version entre node lui même et les modules. Nous sommes finalement parvenu à l'installation en installant des versions compatibles entre elles. On introduit les fichiers de code sur la Raspberry | ||
+ | |||
+ | * Configuration de la Raspberry pour que le serveur se lance au démarrage. Pour pouvoir lancer le serveur au démarrage, on déplace les fichiers d'installation de Node dans /usr/local/sbin ainsi que nos programmes puis on modifie le fichier /etc/rc.local dont le contenu est exécuté à chaque démarrage de la Raspberry. On ajoute alors la ligne : /usr/local/sbin/node /usr/local/sbin/spiderS6/app/scripts/controllers/server.js & | ||
+ | Ainsi, le serveur est lancé à chaque démarrage de la Raspberry. | ||
+ | |||
+ | === Partie mécanique === | ||
+ | |||
+ | ''Assemblage des pièces''' : | ||
+ | |||
+ | Une fois en possession de toutes les pièces, nous avons procédé à l'assemblage. | ||
+ | Nous avons alors, pour chaque patte : | ||
+ | |||
+ | * Placés les servos-moteurs à leur emplacement destiné en les collant pour ne pas qu'ils bougent | ||
+ | * Fixés le tibia sur le coude à l'aide d'une vis d'un coté et de colle de l'autre | ||
+ | * Fixés l'ensemble tibia-coude sur l'épaule à l'aide d'une vis d'un coté et de colle de l'autre | ||
+ | |||
+ | Voici le rendu des pattes assemblées : | ||
+ | |||
+ | [[Fichier:Pattes.jpg|thumb|center|400px]] | ||
+ | <br style="clear: both;" /> | ||
+ | |||
+ | Une fois les pattes assemblées, nous devions les fixer sur le corps. Nous voulions au départ les coller mais même avec de la superglue, elles ne tenaient en raison de la différence de matériau entre les pattes et le corps. Nous avons alors percer le corps à quatres emplacements pour y visser les pattes avec l'aide de M.Thierry Flamen, responsable du labo d'électronique de Polytech (que nous remerçions grandement). | ||
+ | |||
+ | |||
+ | [[Fichier:Percage.jpg|thumb|center|400px]] | ||
+ | <br style="clear: both;" /> | ||
+ | |||
+ | La partie mécanique de notre araignée est ainsi terminée. Malheuresement, nous n'avons pas pris de photo une fois cette phase finie. | ||
== Séance supplémentaire 1 == | == Séance supplémentaire 1 == | ||
=== Partie électronique === | === Partie électronique === | ||
+ | |||
+ | La partie mécanique étant terminée, nous avons pu procéder au câblage des servos-moteurs sur l'arduino et la bread-board. | ||
+ | |||
+ | Voici une photo du rendu de l'araignée câblée : | ||
+ | |||
+ | [[Fichier:Araignée.jpg|thumb|center|400px]] | ||
+ | <br style="clear: both;" /> | ||
=== Partie informatique === | === Partie informatique === | ||
+ | |||
+ | C'est lors de cette séance supplémentaire que nous avons mis en place le code arduino permettant à notre quadripode d'avancer. Nous étions partis à la base sur un système de déplacement par poussée de deux pates opposées en même temps. Cependant, l'araignée était trop instable et tombait souvent. Les tentatives de compensations du poid du quadripode ont rendus le code trop lourd et nous avons donc décidés de changer sa façon d'avancer. Nous avons donc analysés une vidéo d'une mygale qui avance et avons ensuite compris le mécanisme de déplacement des araignés. | ||
+ | Le code réalisé est donc le suivant: | ||
+ | |||
+ | int delai=300; | ||
+ | //val AD | ||
+ | |||
+ | int ADepauleArriere=100; | ||
+ | int ADepauleAvant=40; | ||
+ | int ADcoudeBas=100;//50 | ||
+ | int ADcoudeDroit=30;//100 | ||
+ | int ADpoignetPerpendiculaire=70; | ||
+ | int ADpoignetDroit=90; | ||
+ | |||
+ | //val ARG | ||
+ | int ARGepauleArriere=20; | ||
+ | int ARGepauleAvant=80; | ||
+ | int ARGcoudeBas=80; | ||
+ | int ARGcoudeDroit=140; | ||
+ | int ARGpoignetPerpendiculaire=60; | ||
+ | int ARGpoignetDroit=40; | ||
+ | |||
+ | //VAL AG | ||
+ | int AGepauleArriere=40; | ||
+ | int AGepauleAvant=100; | ||
+ | int AGcoudeBas=110; | ||
+ | int AGcoudeDroit=30;//haut | ||
+ | int AGpoignetPerpendiculaire=90; | ||
+ | int AGpoignetDroit=40; | ||
+ | |||
+ | //VAL ARD | ||
+ | int ARDepauleArriere=70; | ||
+ | int ARDepauleAvant=30; | ||
+ | int ARDcoudeBas=100; | ||
+ | int ARDcoudeDroit=30;//haut | ||
+ | int ARDpoignetPerpendiculaire=140; | ||
+ | int ARDpoignetDroit=180; | ||
+ | int i; | ||
+ | int j; | ||
+ | |||
+ | void setup() { | ||
+ | servoADepaule.attach(2); | ||
+ | servoADcoude.attach(3); | ||
+ | servoADpoignet.attach(4); | ||
+ | servoADpoignet.write(ADpoignetPerpendiculaire); | ||
+ | servoADepaule.write(ADepauleAvant); | ||
+ | servoADcoude.write(ADcoudeBas); | ||
+ | servoARGepaule.attach(5); | ||
+ | servoARGcoude.attach(6); | ||
+ | servoARGpoignet.attach(7); | ||
+ | servoARGpoignet.write(ARGpoignetPerpendiculaire); | ||
+ | servoARGepaule.write(ARGepauleArriere); | ||
+ | servoARGcoude.write(ARGcoudeBas); | ||
+ | servoAGepaule.attach(8); | ||
+ | servoAGcoude.attach(9); | ||
+ | servoAGpoignet.attach(10); | ||
+ | servoAGpoignet.write(AGpoignetPerpendiculaire); | ||
+ | servoAGepaule.write(AGepauleAvant); | ||
+ | servoAGcoude.write(AGcoudeBas); | ||
+ | servoARDepaule.attach(11); | ||
+ | servoARDcoude.attach(12); | ||
+ | servoARDpoignet.attach(13); | ||
+ | servoARDpoignet.write(ARDpoignetPerpendiculaire); | ||
+ | servoARDepaule.write(ARDepauleArriere); | ||
+ | servoARDcoude.write(ARDcoudeBas); | ||
+ | } | ||
+ | |||
+ | void loop() { | ||
+ | servoARDepaule.write(ARDepauleAvant+j); | ||
+ | servoAGepaule.write(AGepauleAvant-j); | ||
+ | servoARGepaule.write(ARGepauleAvant-i); | ||
+ | servoADepaule.write(ADepauleAvant+i); | ||
+ | delay(20); | ||
+ | if(j==50) | ||
+ | { | ||
+ | //on remet en avant AG et ARD | ||
+ | servoAGcoude.write(AGcoudeDroit); | ||
+ | delay(200); | ||
+ | servoAGepaule.write(AGepauleAvant); | ||
+ | delay(200); | ||
+ | servoAGcoude.write(AGcoudeBas); | ||
+ | delay(200); | ||
+ | servoARDcoude.write(ARDcoudeDroit); | ||
+ | delay(200); | ||
+ | servoARDepaule.write(ARDepauleAvant); | ||
+ | delay(200); | ||
+ | servoARDcoude.write(ARDcoudeBas); | ||
+ | delay(200); | ||
+ | j=0; | ||
+ | } | ||
+ | if(i==50) | ||
+ | { | ||
+ | //on remet en avant AD et ARG | ||
+ | servoADcoude.write(ADcoudeDroit); | ||
+ | delay(200); | ||
+ | servoADepaule.write(ADepauleAvant); | ||
+ | delay(200); | ||
+ | servoADcoude.write(ADcoudeBas); | ||
+ | delay(200); | ||
+ | servoARGcoude.write(ARGcoudeDroit); | ||
+ | delay(200); | ||
+ | servoARGepaule.write(ARGepauleAvant); | ||
+ | delay(200); | ||
+ | servoARGcoude.write(ARGcoudeBas); | ||
+ | delay(200); | ||
+ | i=0; | ||
+ | } | ||
+ | i=i+5; | ||
+ | j=j+5; | ||
+ | } | ||
+ | |||
+ | Ce code permet à l'araignée d'avancer ses pates de façon "asynchrone". Il s'agit en réalité juste d'une impression car l'asynchronisme est impossible sous arduino. En effet, nous envoyons de très courts signaux de déplacement aux différents servos moteurs à la suite, donnant une impression d'asynchronisme. | ||
+ | Voici le lien du rendu final du déplacement: | ||
+ | |||
+ | |||
+ | [https://www.youtube.com/watch?v=N7QEBWDCz9Q&feature=youtu.be| Déplacement quadripode] | ||
+ | |||
+ | |||
+ | Une fois que l'araignée avançait, nous avons dû mettre en place le contôle de celle-ci par le biais des informations envoyés par websocket. Dans notre cas, | ||
+ | le joystick envoi deux valeurs séparés par une virgule, la première correspondant à la composante en x et la seconde à la composante en y. Ainsi, nous stockons un à un les caractères reçu sur le port série de l'arduino jusqu'à tomber sur le séparateur. Nous stockons alors la valeur décimale des chiffres lus dans une variable. Il en est de même pour le nombre à droite du séparateur. Le code dans le setup() et dans le loop() devient donc : | ||
+ | |||
+ | int a; | ||
+ | int b; | ||
+ | int aPrint; | ||
+ | int bPrint; | ||
+ | int aMoins,bMoins; | ||
+ | int debut; | ||
+ | char firstVal[50]; | ||
+ | char secVal[50]; | ||
+ | char byteBuffer; | ||
+ | |||
+ | void setup() { | ||
+ | servoADepaule.attach(2); | ||
+ | servoADcoude.attach(3); | ||
+ | servoADpoignet.attach(4); | ||
+ | servoADpoignet.write(ADpoignetPerpendiculaire); | ||
+ | servoADepaule.write(ADepauleAvant); | ||
+ | servoADcoude.write(ADcoudeBas); | ||
+ | servoARGepaule.attach(5); | ||
+ | servoARGcoude.attach(6); | ||
+ | servoARGpoignet.attach(7); | ||
+ | servoARGpoignet.write(ARGpoignetPerpendiculaire); | ||
+ | servoARGepaule.write(ARGepauleArriere); | ||
+ | servoARGcoude.write(ARGcoudeBas); | ||
+ | servoAGepaule.attach(8); | ||
+ | servoAGcoude.attach(9); | ||
+ | servoAGpoignet.attach(10); | ||
+ | servoAGpoignet.write(AGpoignetPerpendiculaire); | ||
+ | servoAGepaule.write(AGepauleAvant); | ||
+ | servoAGcoude.write(AGcoudeBas); | ||
+ | servoARDepaule.attach(11); | ||
+ | servoARDcoude.attach(12); | ||
+ | servoARDpoignet.attach(13); | ||
+ | servoARDpoignet.write(ARDpoignetPerpendiculaire); | ||
+ | servoARDepaule.write(ARDepauleArriere); | ||
+ | servoARDcoude.write(ARDcoudeBas); | ||
+ | |||
+ | delay(2000); | ||
+ | i=0; | ||
+ | j=25; | ||
+ | Serial.begin(115200); | ||
+ | a=0; | ||
+ | b=0; | ||
+ | aMoins=0; | ||
+ | bMoins=0; | ||
+ | i=0; | ||
+ | debut=1; | ||
+ | byteBuffer='a'; | ||
+ | } | ||
+ | |||
+ | void loop() { | ||
+ | if(Serial.available() > 0) | ||
+ | { | ||
+ | while(Serial.available() > 0) | ||
+ | { | ||
+ | delay(10); | ||
+ | byteBuffer = Serial.read(); | ||
+ | if(debut==1) | ||
+ | { | ||
+ | if(byteBuffer==',') | ||
+ | { | ||
+ | debut=0; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | a=10*a; | ||
+ | switch(byteBuffer) | ||
+ | { | ||
+ | case '-': | ||
+ | a=0; | ||
+ | aMoins=1; | ||
+ | break; | ||
+ | case '1': | ||
+ | a=a+1; | ||
+ | break; | ||
+ | case '2': | ||
+ | a=a+2; | ||
+ | break; | ||
+ | case '3': | ||
+ | a=a+3; | ||
+ | break; | ||
+ | case '4': | ||
+ | a=a+4; | ||
+ | break; | ||
+ | case '5': | ||
+ | a=a+5; | ||
+ | break; | ||
+ | case '6': | ||
+ | a=a+6; | ||
+ | break; | ||
+ | case '7': | ||
+ | a=a+7; | ||
+ | break; | ||
+ | case '8': | ||
+ | a=a+8; | ||
+ | break; | ||
+ | case '9': | ||
+ | a=a+9; | ||
+ | break; | ||
+ | default: | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | if(byteBuffer==';') | ||
+ | { | ||
+ | if(aMoins==1) | ||
+ | { | ||
+ | a=-a; | ||
+ | } | ||
+ | if(bMoins==1) | ||
+ | { | ||
+ | b=-b; | ||
+ | } | ||
+ | debut=1; | ||
+ | if(b>0) | ||
+ | { | ||
+ | servoARDepaule.write(ARDepauleAvant+j); | ||
+ | servoAGepaule.write(AGepauleAvant-j); | ||
+ | servoARGepaule.write(ARGepauleAvant-i); | ||
+ | servoADepaule.write(ADepauleAvant+i); | ||
+ | delay(20); | ||
+ | if(j==50) | ||
+ | { | ||
+ | //on remet en avant AG et ARD | ||
+ | servoAGcoude.write(AGcoudeDroit); | ||
+ | delay(200); | ||
+ | servoAGepaule.write(AGepauleAvant); | ||
+ | delay(200); | ||
+ | servoAGcoude.write(AGcoudeBas); | ||
+ | delay(200); | ||
+ | servoARDcoude.write(ARDcoudeDroit); | ||
+ | delay(200); | ||
+ | servoARDepaule.write(ARDepauleAvant); | ||
+ | delay(200); | ||
+ | servoARDcoude.write(ARDcoudeBas); | ||
+ | delay(200); | ||
+ | j=0; | ||
+ | } | ||
+ | if(i==50) | ||
+ | { | ||
+ | //on remet en avant AD et ARG | ||
+ | servoADcoude.write(ADcoudeDroit); | ||
+ | delay(200); | ||
+ | servoADepaule.write(ADepauleAvant); | ||
+ | delay(200); | ||
+ | servoADcoude.write(ADcoudeBas); | ||
+ | delay(200); | ||
+ | servoARGcoude.write(ARGcoudeDroit); | ||
+ | delay(200); | ||
+ | servoARGepaule.write(ARGepauleAvant); | ||
+ | delay(200); | ||
+ | servoARGcoude.write(ARGcoudeBas); | ||
+ | delay(200); | ||
+ | i=0; | ||
+ | } | ||
+ | i=i+5; | ||
+ | j=j+5; | ||
+ | } | ||
+ | aMoins=0; | ||
+ | bMoins=0; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | b=10*b; | ||
+ | switch(byteBuffer) | ||
+ | { | ||
+ | case '-': | ||
+ | b=0; | ||
+ | bMoins=1; | ||
+ | break; | ||
+ | case '1': | ||
+ | b=b+1; | ||
+ | break; | ||
+ | case '2': | ||
+ | b=b+2; | ||
+ | break; | ||
+ | case '3': | ||
+ | b=b+3; | ||
+ | break; | ||
+ | case '4': | ||
+ | b=b+4; | ||
+ | break; | ||
+ | case '5': | ||
+ | b=b+5; | ||
+ | break; | ||
+ | case '6': | ||
+ | b=b+6; | ||
+ | break; | ||
+ | case '7': | ||
+ | b=b+7; | ||
+ | break; | ||
+ | case '8': | ||
+ | b=b+8; | ||
+ | break; | ||
+ | case '9': | ||
+ | b=b+9; | ||
+ | break; | ||
+ | default: | ||
+ | break; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
== Conclusion == | == Conclusion == | ||
+ | |||
+ | En ayant apporté beaucoup de notre temps-libre, nous sommes finalement parvenus à faire fonctionner notre araignée. Cependant, par manque de temps, notre araignée ne remplit pas complètement le cahier des charges. En effet, nous n'utilisons pas les modules XBee puisque nous avons rencontré des problèmes pour les configurer et notre araignée ne se déplace que dans une seule direction et à une vitesse constante. | ||
+ | Ayant sous estimé l'épaisseur et le poids de l'ensemble arduino + bread-board + câbles, nous n'avons pas pu les fixer en dessous du corps comme prévu et cette ensemble traîne donc par terre... | ||
+ | Voici le rendu vidéo de notre projet : | ||
+ | |||
+ | |||
+ | [https://www.youtube.com/watch?v=olmXs5BT4MU| Rendu vidéo final (Youtube)] | ||
+ | |||
+ | |||
+ | Ce projet de Systèmes-communicants nous a tout d'abord permis d'avoir une idée de ce qu'est la filière système communicants. Ensuite il nous a permis de nous améliorer sur la méthodologie de la gestion de projet en terme de gestion du temps, de tests, de répartition des tâches. Enfin, la pluralité disciplinaire de ce projet a permis de satisfaire tous les membres du groupe. | ||
+ | |||
+ | Avec un peu plus de temps, nous aurions pu répondre complètement au cahier des charges, à savoir profiter pleinement de la définition d'un joystick pour contrôler notre araignée. Nous aurions aussi pu réaliser une carte électronique afin de ne pas être encombré par tous les câbles, ce qui aurait enrichi la partie électronique et amélioré l'esthétique de notre projet en évitant que notre araignée ait à tracter des composants. |
Version actuelle datée du 22 juin 2017 à 12:12
Sommaire
Projet IMA3-SC 2016/2017 : Robot quadripode
Cahier des charges
Description du système
Notre projet est de réaliser un robot quadripode contrôlé par notre interface web. Celui ci aura pour objectif de :
- se déplacer sur ses pattes dans les directions indiquées par l'utilisateur.
- Se déplacer à la vitesse demandée par l'utilisateur qui sera plus ou moins rapide.
Chaque pattes possédera 3 articulations afin de faciliter le déplacement du robot. Un des servo-moteur permettra de faire avancer ou reculer la patte, le deuxième permettra au robot de se surélever ou de s'abaisser et le dernier permettra de poser la patte perpendiculaire par rapport au sol.
En fonction du temps que nous prendrons à réaliser notre projet, nous aurons peut être la possibilité d'ajouter une fonctionnalité de vidéo à notre robot. Nous aurons alors la possibilité de voir à travers le robot sur notre interface web.
Le matériel
Le matériel pour réaliser notre robot quadripode est relativement simple. Nous aurons besoin d'un accès a un ordinateur équipé d'un système Linux. Pour le quadripode , nous aurons besoin de :
- Une raspberry Pi ( de préférence une raspberry pi zéro en raison de sa petite taille)
- 12 micro servos moteurs pour le mouvement des pattes , soit 3 servos-moteurs par patte .
- Un module wifi pour la raspberry Pi.
- Toutes sortes d'écrous et vis dont nous détaillerons le contenu dans le futur.
- En option , une caméra compatible raspberry pi , pour transmettre une vidéo en direct sur l'interface web, ainsi que son raccordement.
- Une breadboard pour tester les circuits .
- Un câble nappe pour raspberry pi .
La liste réalisée est provisoire et pourra subir de légers changement avant le début du projet (les types d'écrous et vis seront détaillés) . Nous aurons en plus des éléments précédents , besoin d'un accès a l'imprimante 3D afin d'imprimer les différentes parties du quadripode .
Séance 1
Partie électronique
La conclusion que nous pouvons apporter après cette première séance de projet est que la partie électronique de ce projet ne s’étendra pas sur les 3 séances puisque, suite à des discussions avec les différents professeurs, nous avons vu qu'il était plus adéquat de ne pas utiliser de FPGA pour la construction de l'araignée vu que nous utilisons un grand nombre de moteurs. Nous allons donc utiliser une unique carte Arduino qui contrôlera l'intégralité des moteurs.
Pour répondre au cahier des charges du projet, nous avons donc, quand même, programmé un FPGA contrôlant un des moteurs afin de manipuler tous les outils mis à notre dispositions. Nous allons donc vous présenter, dans cette partie, comment nous avons fait fonctionner notre moteur grâce au FPGA.
Avant de pouvoir programmer notre FPGA, nous avons fait des recherches sur la data-sheet du moteur pour déterminer son mode de fonctionnement.
Nous pouvons voir grâce à la data-sheet que, pour que notre moteur fonctionne, il faut que le VCC(=5V) soit connecté au câble rouge, la masse au câble marron et le moduleur de largeur d'impulsion (PWM) au câble orange. On remarque également que notre moteur fonctionne à une fréquence de 50Hz (donc une période de 20 ms) et que le sens de rotation dépends du temps à l'état haut du signal. C'est à dire que pour que le moteur tourne dans le sens anti-horaire pour aller à 0 degré, il faut que le duty cycle (rapport cyclique) soit à 1 ms, pour aller dans le sens horaire jusque 180 degrés, il doit être de 2 ms et pour que le moteur s'arrête au centre, le rapport cyclique doit être de 1.5ms. Nous allons donc jouer sur ce rapport cyclique afin de changer le sens de rotation du moteur.
Le problème qui s'est posé à nous est d'être assez précis pour pouvoir modifier de manière efficace le rapport cyclique. Étant donné que nous souhaitons avoir une précision de 0.1 ms sur une période de 20 ms, il nous faut donc 200 coups d'horloge (20/0.1). Comme nous fonctionnons à 50Hz, la fréquence du FPGA sera de 50*200=10000 Hz.
Pour faire fonctionner le moteur dans les 2 sens, nous devons maintenant "dire" au FPGA que quand notre compteur arrive a 200, il doit passer la valeur de sortie à 1. Celle ci reste à 1 jusqu'a la valeur choisie du compteur en fonction de l'angle que nous souhaitons puis la valeur de sortie passe à 0 jusqu'à ce que le compteur atteigne 200.
Comme vous pouvez le voir sur l'image ci-dessus, nous avons fixé le BUS_JS à la valeur 200, ce bus est connecté à un comparateur et au compteur afin de compter jusque 200. Nous avons ensuite défini le Configurable Digital IO pour mettre la valeur souhaitée pour le fonctionnement du moteur (comprise entre 10 et 20). Cette valeur est mise dans le second comparateur (avec la valeur 200) et nous envoyons ceci dans le moteur qui nous donne la courbe suivante.
Nous avons donc, dans la vidéo ci-dessous, mis la valeur 10 et 20 dans le Configurable Digital IO et nous remarquons que le moteur tourne bien dans les 2 sens.
Partie informatique
Pour l'application web, nous avons décidé d'utiliser Node ainsi que Angular et les modules socket.IO, serialport de Node pour envoyer nos données vers l'arduino via une Raspberry. Nous voulions utiliser ces technologies récentes afin d'avoir une petite expériences avec celles ci et pour pouvoir apprendre à les utiliser. Angular nous permettant de créer une applicaton dynamique aisément. Cette application web représente un joystick qui nous servira à faire déplacer notre robot. Celui ci devra permettre de dire au robot dans quelle direction il doit se déplacer et à quelle vitesse. De plus, il affichera sur la page web la direction que l'utilisateur est en train de donner.
Nous avons tout d'abord réalisé le joystick avec angular. Celui ci est réalisé grâce à 2 images. Une pour l’arrière plan du joystick qui est fixe et l'autre pour le joystick en lui même qui se déplace lorsque l'utilisateur clic sur celui ci et déplace la souris. Lorsque l'utilisateur reclic sur le joystick, celui ci se repositionne au centre.
Nous utilisons donc 2 événements :
- lors d'un clic : une variable est mise à jour "mouseIsDown" pour savoir si le joystick doit suivre la souris ou si il doit aller se repositionner en (0,0).
- lors d'un mouvement de la souris si "mouseIsDown" est égale à 1 : On va alors faire modifier le css de l'image pour faire en sorte que celle ci suive la souris. Celle ci fût la plus complexe à réaliser car il fallait bien calculer la position de l'image en fonction de celle de la souris mais aussi faire en sorte que l'image ne sorte pas de la zone délimitée par le socle du joystick. Nous avons pour cela utilisé beaucoup de formules de trigonométrie.
La taille du socle de notre joystick étant de 200px x 200px, pour vérifier si nous somme bien dans le cercle, nous testons si la norme entre la position x et la position y est bien inférieur à 100. Si nous sommes bien dans le cercle, il suffit de donner la position de la souris à l'image. Cependant, si la souris n'est pas dans les limites du cercle, nous devons recalculer la position de l'image afin qu'elle se positionne dans la même direction que la souris mais à la limite de la zone du joystick. Nous avons donc calculer l’hypoténuse puis l'angle par rapport à 0 sur le cercle trigonomètrique. pour repositionner le joystick au même angle mais avec une norme de 100 (le maximum sur le cercle que nous avons).
main.js : (définition de l'application et de ses dépendances)
angular.module('spiderApp') .controller('MainCtrl', function () { this.awesomeThings = [ 'HTML5 Boilerplate', 'AngularJS', 'Karma' ]; });
deplacement.js : (fonctionnement dynamique du joystick de la page web)
angular.module('spiderApp').controller('deplacementCtrl', function($scope) { $scope.positionX=200; $scope.positionY=200; $scope.mouseIsDown=false; posXstr=$scope.positionX.toString(); posYstr=$scope.positionY.toString(); $scope.posjoy = { "left" : posXstr+"px", "top" : posYstr+"px", "position" : "absolute", "height" : "100px" }; var socket = io.connect('http://172.26.79.4:8080'); $scope.clickDown = function(event){ if($scope.mouseIsDown==false) { $scope.mouseIsDown = true; $scope.positionX = event.target.offsetLeft+(event.offsetX-50); $scope.positionY = event.target.offsetTop+(event.offsetY-50); posXstr=$scope.positionX.toString(); posYstr=$scope.positionY.toString(); $scope.posjoy = { "left" : posXstr+"px", "top" : posYstr+"px", "position" : "relative", "height" : "100px" }; } else { $scope.mouseIsDown = false; $scope.positionX = 200; $scope.positionY = 200; console.log($scope.mouseIsDown); posXstr=$scope.positionX.toString(); posYstr=$scope.positionY.toString(); $scope.posjoy = { "left" : posXstr+"px", "top" : posYstr+"px", "position" : "relative", "height" : "100px" }; } socket.emit('message', ($scope.positionX-200)+','+(-($scope.positionY-200))+';'); }; $scope.moveJoystickOutside = function(event) { if ($scope.mouseIsDown == true) { window.setTimeout(function() { ecartX=parseInt(getComputedStyle(joystick).left, 10); ecartY=parseInt(getComputedStyle(joystick).top, 10); //on retranche 200 la marge du boutton araigné jusqu'au bord du div #joystick + 50 pour arriver au centre de l'image //+20 en Y à cause du footer qui sera toujours de taille constante positionXProvisoire = event.clientX - ecartX -250; positionYProvisoire = -(event.clientY - ecartY-330); console.log("posi prov X: "+positionXProvisoire); console.log("posi prov Y: "+positionYProvisoire); //partie dans le cercle if ( Math.sqrt(Math.pow(positionXProvisoire/100,2)+Math.pow(positionYProvisoire/100,2)) >= 0 && Math.sqrt(Math.pow(positionXProvisoire/100,2)+Math.pow(positionYProvisoire/100,2)) < 1) { //50=moitié de la dimension de l'image du bouton avec l'araignée. //a retrancher car on veut le centre de l'image et pas le coin supérieur gauche. $scope.positionX=event.clientX-ecartX-50; $scope.positionY=event.clientY-ecartY-130; console.log("posi X: "+$scope.positionX); console.log("posi Y: "+$scope.positionY); posXstr = $scope.positionX.toString(); posYstr = $scope.positionY.toString(); $scope.posjoy = { "left": posXstr + "px", "top": posYstr + "px", "position": "relative", "height": "100px" }; } //partie en dehors du cercle else { hypothenuse=Math.sqrt(Math.pow(positionXProvisoire,2)+Math.pow(positionYProvisoire,2)); angle=Math.acos(positionXProvisoire/hypothenuse); if(-(event.clientY - ecartY-330)<0) { $scope.positionX=parseInt((Math.cos(angle)*100)+200); $scope.positionY=parseInt((Math.sin(angle)*100)+200); } else { $scope.positionX=parseInt((Math.cos(angle)*100)+200); $scope.positionY=parseInt((-Math.sin(angle)*100)+200); } posXstr = $scope.positionX.toString(); posYstr = $scope.positionY.toString(); $scope.posjoy = { "left": posXstr + "px", "top": posYstr + "px", "position": "relative", "height": "100px" }; } //attendre un peu pour ne pas embrouiller la transmition du signal socket.emit('message', ($scope.positionX-200)+','+(-($scope.positionY-200))+';'); }, 10);//delay en millisecondes } }; });
Voici le rendu de l'application web, que nous trouvons à l'adresse 172.26.79.4 (Adresse IP de la Raspberry) sur le port 8080 :
Séance 2
Partie mécanique
Le but premier de ce projet n'était pas de faire de la mécanique mais nous devions passer obligatoirement par cette étape pour que l'araignée puisse se déplacer. Cette partie nous a pris énormément de temps en dehors des séances.
- Réalisation des pièces nécessaires à la construction de l'araignée :
Nous avons décidé d'imprimer les pattes de notre araignée avec l'imprimante 3D du fabricarium en nous aidant d'un projet du même type trouvé sur internet : [1]. Cependant, les servos-moteurs mis à notre disposition pour ce projet étant de dimensions différentes de ceux utilisés sur le projet précédemment cité, nous avons du redimmensionner les différents pièces sur Freecad. Voici un aperçu des pièces finales Freecad :
Nous avons du réaliser de nombreux tests d'impression avant de parvenir à la taille idéale pour l'assemblage des différentes pièces. Une fois les pièces nécessaires à l'assemblage des pattes imprimées, il nous fallait le corps de l'araignée. Nous avons alors pour cela découpé une forme simple en Plexiglas (Nous remerçions d'ailleurs M. Redon pour son Plexiglas) à l'aide de la découpe laser du Fabricarium.
Partie informatique
Une fois l'application web créée et l'envoi des données possible sur le terminal grâce aux websockets, nous avons dû cette fois rediriger les données sur le port série. Cette partie nous a posé beaucoup de difficultés car nous n'arrivions pas à voir si les données s'envoyaient réellement ou non. Cependant, grâce au module "serial-port" de Node, l'envoi des données était assez simple en ayant bien lu la documentation. Nous n'avions simplement pas vu que le problème se posait du coté de la réception où l'arduino avait du mal à recevoir convenablement le message envoyé sur le port série. Pour palier ce problème, nous avons élaboré un algorithme extrêmement basique sur arduino afin de pouvoir réaliser la transmission dans les meilleures conditions possibles. Celui ci traitant un à un les caractère afin de pouvoir reconstituer un entier de manière fiable.
server.js (permet d'envoyer le message créé dans deplacement.js sur le port série)
var express = require('express'); var app = express(); var server = require('http').Server(app); var io = require('socket.io')(server); var SerialPort = require("serialport"); var port = new SerialPort("/dev/ttyUSB1",{baudRate: 115200, parity : 'none', autoOpen :false}); app.use(express.static(__dirname+"/../..")); app.use(express.static(__dirname+"/../../../bower_components/")); port.open(function (error){ if(error) { console.log('Error while opening the port ' + error); } else { io.on('connection', function(socket) { console.log('new connection'); socket.on('message', function (message) { port.write(message, function(err) { if (err) { return console.log('Error on write: ', err.message); } else { console.log('Position : ' + message); } }); }); }); } }); server.listen(8080, function() { console.log('server up and running at 8080 port'); });
Séance 3
Partie électronique
Partie informatique
envoi des données sur le port série
Configuration de la Raspberry :
- Configuration IP :La Raspberry doit pouvoir communiquer avec l'ordinateur de la salle de projet pour pouvoir avoir accès à l'application web ainsi qu'au réseau de Polytech pour pouvoir installer les différents paquetages de Node. Pour cela, on modifie le fichier /etc/network/interfaces en remplaçant l'IP présente dans le fichier par celle de notre ordinateur de salle de projet et on configure en static. La Raspberry peut ainsi communiquer avec l'ordinateur. Ensuite, on indique le serveur DNS dans le fichier /etc/resolv.conf par : nameserver 193.48.57.34
La Raspberry peut ainsi communiquer avec le réseau Polytech
- Installation de Node sur la Raspberry et déplacement des fichiers de code. Nous avons été confronté à de nombreuses difficultés pour cette partie. En effet, notre code faisant intervenir de nombreux modules, il a fallu tous les installer et nous avons rencontré des problèmes de version entre node lui même et les modules. Nous sommes finalement parvenu à l'installation en installant des versions compatibles entre elles. On introduit les fichiers de code sur la Raspberry
- Configuration de la Raspberry pour que le serveur se lance au démarrage. Pour pouvoir lancer le serveur au démarrage, on déplace les fichiers d'installation de Node dans /usr/local/sbin ainsi que nos programmes puis on modifie le fichier /etc/rc.local dont le contenu est exécuté à chaque démarrage de la Raspberry. On ajoute alors la ligne : /usr/local/sbin/node /usr/local/sbin/spiderS6/app/scripts/controllers/server.js &
Ainsi, le serveur est lancé à chaque démarrage de la Raspberry.
Partie mécanique
Assemblage des pièces' :
Une fois en possession de toutes les pièces, nous avons procédé à l'assemblage. Nous avons alors, pour chaque patte :
- Placés les servos-moteurs à leur emplacement destiné en les collant pour ne pas qu'ils bougent
- Fixés le tibia sur le coude à l'aide d'une vis d'un coté et de colle de l'autre
- Fixés l'ensemble tibia-coude sur l'épaule à l'aide d'une vis d'un coté et de colle de l'autre
Voici le rendu des pattes assemblées :
Une fois les pattes assemblées, nous devions les fixer sur le corps. Nous voulions au départ les coller mais même avec de la superglue, elles ne tenaient en raison de la différence de matériau entre les pattes et le corps. Nous avons alors percer le corps à quatres emplacements pour y visser les pattes avec l'aide de M.Thierry Flamen, responsable du labo d'électronique de Polytech (que nous remerçions grandement).
La partie mécanique de notre araignée est ainsi terminée. Malheuresement, nous n'avons pas pris de photo une fois cette phase finie.
Séance supplémentaire 1
Partie électronique
La partie mécanique étant terminée, nous avons pu procéder au câblage des servos-moteurs sur l'arduino et la bread-board.
Voici une photo du rendu de l'araignée câblée :
Partie informatique
C'est lors de cette séance supplémentaire que nous avons mis en place le code arduino permettant à notre quadripode d'avancer. Nous étions partis à la base sur un système de déplacement par poussée de deux pates opposées en même temps. Cependant, l'araignée était trop instable et tombait souvent. Les tentatives de compensations du poid du quadripode ont rendus le code trop lourd et nous avons donc décidés de changer sa façon d'avancer. Nous avons donc analysés une vidéo d'une mygale qui avance et avons ensuite compris le mécanisme de déplacement des araignés. Le code réalisé est donc le suivant:
int delai=300; //val AD int ADepauleArriere=100; int ADepauleAvant=40; int ADcoudeBas=100;//50 int ADcoudeDroit=30;//100 int ADpoignetPerpendiculaire=70; int ADpoignetDroit=90; //val ARG int ARGepauleArriere=20; int ARGepauleAvant=80; int ARGcoudeBas=80; int ARGcoudeDroit=140; int ARGpoignetPerpendiculaire=60; int ARGpoignetDroit=40; //VAL AG int AGepauleArriere=40; int AGepauleAvant=100; int AGcoudeBas=110; int AGcoudeDroit=30;//haut int AGpoignetPerpendiculaire=90; int AGpoignetDroit=40; //VAL ARD int ARDepauleArriere=70; int ARDepauleAvant=30; int ARDcoudeBas=100; int ARDcoudeDroit=30;//haut int ARDpoignetPerpendiculaire=140; int ARDpoignetDroit=180; int i; int j; void setup() { servoADepaule.attach(2); servoADcoude.attach(3); servoADpoignet.attach(4); servoADpoignet.write(ADpoignetPerpendiculaire); servoADepaule.write(ADepauleAvant); servoADcoude.write(ADcoudeBas); servoARGepaule.attach(5); servoARGcoude.attach(6); servoARGpoignet.attach(7); servoARGpoignet.write(ARGpoignetPerpendiculaire); servoARGepaule.write(ARGepauleArriere); servoARGcoude.write(ARGcoudeBas); servoAGepaule.attach(8); servoAGcoude.attach(9); servoAGpoignet.attach(10); servoAGpoignet.write(AGpoignetPerpendiculaire); servoAGepaule.write(AGepauleAvant); servoAGcoude.write(AGcoudeBas); servoARDepaule.attach(11); servoARDcoude.attach(12); servoARDpoignet.attach(13); servoARDpoignet.write(ARDpoignetPerpendiculaire); servoARDepaule.write(ARDepauleArriere); servoARDcoude.write(ARDcoudeBas); } void loop() { servoARDepaule.write(ARDepauleAvant+j); servoAGepaule.write(AGepauleAvant-j); servoARGepaule.write(ARGepauleAvant-i); servoADepaule.write(ADepauleAvant+i); delay(20); if(j==50) { //on remet en avant AG et ARD servoAGcoude.write(AGcoudeDroit); delay(200); servoAGepaule.write(AGepauleAvant); delay(200); servoAGcoude.write(AGcoudeBas); delay(200); servoARDcoude.write(ARDcoudeDroit); delay(200); servoARDepaule.write(ARDepauleAvant); delay(200); servoARDcoude.write(ARDcoudeBas); delay(200); j=0; } if(i==50) { //on remet en avant AD et ARG servoADcoude.write(ADcoudeDroit); delay(200); servoADepaule.write(ADepauleAvant); delay(200); servoADcoude.write(ADcoudeBas); delay(200); servoARGcoude.write(ARGcoudeDroit); delay(200); servoARGepaule.write(ARGepauleAvant); delay(200); servoARGcoude.write(ARGcoudeBas); delay(200); i=0; } i=i+5; j=j+5; }
Ce code permet à l'araignée d'avancer ses pates de façon "asynchrone". Il s'agit en réalité juste d'une impression car l'asynchronisme est impossible sous arduino. En effet, nous envoyons de très courts signaux de déplacement aux différents servos moteurs à la suite, donnant une impression d'asynchronisme. Voici le lien du rendu final du déplacement:
Une fois que l'araignée avançait, nous avons dû mettre en place le contôle de celle-ci par le biais des informations envoyés par websocket. Dans notre cas,
le joystick envoi deux valeurs séparés par une virgule, la première correspondant à la composante en x et la seconde à la composante en y. Ainsi, nous stockons un à un les caractères reçu sur le port série de l'arduino jusqu'à tomber sur le séparateur. Nous stockons alors la valeur décimale des chiffres lus dans une variable. Il en est de même pour le nombre à droite du séparateur. Le code dans le setup() et dans le loop() devient donc :
int a; int b; int aPrint; int bPrint; int aMoins,bMoins; int debut; char firstVal[50]; char secVal[50]; char byteBuffer; void setup() { servoADepaule.attach(2); servoADcoude.attach(3); servoADpoignet.attach(4); servoADpoignet.write(ADpoignetPerpendiculaire); servoADepaule.write(ADepauleAvant); servoADcoude.write(ADcoudeBas); servoARGepaule.attach(5); servoARGcoude.attach(6); servoARGpoignet.attach(7); servoARGpoignet.write(ARGpoignetPerpendiculaire); servoARGepaule.write(ARGepauleArriere); servoARGcoude.write(ARGcoudeBas); servoAGepaule.attach(8); servoAGcoude.attach(9); servoAGpoignet.attach(10); servoAGpoignet.write(AGpoignetPerpendiculaire); servoAGepaule.write(AGepauleAvant); servoAGcoude.write(AGcoudeBas); servoARDepaule.attach(11); servoARDcoude.attach(12); servoARDpoignet.attach(13); servoARDpoignet.write(ARDpoignetPerpendiculaire); servoARDepaule.write(ARDepauleArriere); servoARDcoude.write(ARDcoudeBas); delay(2000); i=0; j=25; Serial.begin(115200); a=0; b=0; aMoins=0; bMoins=0; i=0; debut=1; byteBuffer='a'; } void loop() { if(Serial.available() > 0) { while(Serial.available() > 0) { delay(10); byteBuffer = Serial.read(); if(debut==1) { if(byteBuffer==',') { debut=0; } else { a=10*a; switch(byteBuffer) { case '-': a=0; aMoins=1; break; case '1': a=a+1; break; case '2': a=a+2; break; case '3': a=a+3; break; case '4': a=a+4; break; case '5': a=a+5; break; case '6': a=a+6; break; case '7': a=a+7; break; case '8': a=a+8; break; case '9': a=a+9; break; default: break; } } } else { if(byteBuffer==';') { if(aMoins==1) { a=-a; } if(bMoins==1) { b=-b; } debut=1; if(b>0) { servoARDepaule.write(ARDepauleAvant+j); servoAGepaule.write(AGepauleAvant-j); servoARGepaule.write(ARGepauleAvant-i); servoADepaule.write(ADepauleAvant+i); delay(20); if(j==50) { //on remet en avant AG et ARD servoAGcoude.write(AGcoudeDroit); delay(200); servoAGepaule.write(AGepauleAvant); delay(200); servoAGcoude.write(AGcoudeBas); delay(200); servoARDcoude.write(ARDcoudeDroit); delay(200); servoARDepaule.write(ARDepauleAvant); delay(200); servoARDcoude.write(ARDcoudeBas); delay(200); j=0; } if(i==50) { //on remet en avant AD et ARG servoADcoude.write(ADcoudeDroit); delay(200); servoADepaule.write(ADepauleAvant); delay(200); servoADcoude.write(ADcoudeBas); delay(200); servoARGcoude.write(ARGcoudeDroit); delay(200); servoARGepaule.write(ARGepauleAvant); delay(200); servoARGcoude.write(ARGcoudeBas); delay(200); i=0; } i=i+5; j=j+5; } aMoins=0; bMoins=0; } else { b=10*b; switch(byteBuffer) { case '-': b=0; bMoins=1; break; case '1': b=b+1; break; case '2': b=b+2; break; case '3': b=b+3; break; case '4': b=b+4; break; case '5': b=b+5; break; case '6': b=b+6; break; case '7': b=b+7; break; case '8': b=b+8; break; case '9': b=b+9; break; default: break; } } } } } }
Conclusion
En ayant apporté beaucoup de notre temps-libre, nous sommes finalement parvenus à faire fonctionner notre araignée. Cependant, par manque de temps, notre araignée ne remplit pas complètement le cahier des charges. En effet, nous n'utilisons pas les modules XBee puisque nous avons rencontré des problèmes pour les configurer et notre araignée ne se déplace que dans une seule direction et à une vitesse constante. Ayant sous estimé l'épaisseur et le poids de l'ensemble arduino + bread-board + câbles, nous n'avons pas pu les fixer en dessous du corps comme prévu et cette ensemble traîne donc par terre... Voici le rendu vidéo de notre projet :
Ce projet de Systèmes-communicants nous a tout d'abord permis d'avoir une idée de ce qu'est la filière système communicants. Ensuite il nous a permis de nous améliorer sur la méthodologie de la gestion de projet en terme de gestion du temps, de tests, de répartition des tâches. Enfin, la pluralité disciplinaire de ce projet a permis de satisfaire tous les membres du groupe.
Avec un peu plus de temps, nous aurions pu répondre complètement au cahier des charges, à savoir profiter pleinement de la définition d'un joystick pour contrôler notre araignée. Nous aurions aussi pu réaliser une carte électronique afin de ne pas être encombré par tous les câbles, ce qui aurait enrichi la partie électronique et amélioré l'esthétique de notre projet en évitant que notre araignée ait à tracter des composants.