Projet IMA3 P3, 2015/2016, TD2

De Wiki de Projets IMA

Projet IMA3-SC 2015/2016 : Cartographie

Cahier des charges

Dans le cadre de notre projet Systèmes Communicants, nous avons décidé de nous orienter vers un système permettant de cartographier une salle à l'aide d'un capteur de distance tournant sur lui-même par un servomoteur. Pour cela, nous avons pensé à utiliser un sonar ultra-sons pour la détection des "murs" de la salle. De cette façon, nous pourrions récupérer les positions de chaque obstacle et ainsi les tracer.

Matériel nécessaire

Par conséquent, afin de pouvoir mener à bien notre projet, nous aurions besoin du matériel suivant :

  • Une nanoboard
  • Une Raspberry Pi
  • Un sonar ultra-sons
  • Un servomoteur

Séance 1

Pour débuter, nous avons d'abord réalisé un schéma représentant les différentes parties du projet, ce qui nous permettra de mieux nous répartir les tâches et comprendre ce qu'on attend de nous lors de ce projet.


légende Schéma global du travail à réaliser

Simulation du fonctionnement

Vidéo simulant le fonctionnement de notre montage : ici

Le résultat escompté de cette simulation : Sonar.jpg


Partie électronique

  • Concernant la partie électronique, nous avons commencé la séance par créer un prototype Arduino pour pouvoir tester le bon fonctionnement du sonar ultra-sons et du servomoteur, et de voir comment s'utiliser ces 2 composants. Nous avons donc réussi à faire tourner le servomoteur en continu pour un angle de 0 à 180° tout en récupérant la distance de l'obstacle le plus proche avec le sonar.


légende Test des différents composants


  • Nous avons ensuite été travailler sur Altium pour pouvoir commencer la conception de notre circuit électronique qui est pour le moment remplacé pour un prototype Arduino. Comme nous ne savions pas par où commencer ce travail, nous avons décidé de nous initier avec le tutoriel décrit dans les ressources mises à disposition pour ce projet. Cela nous a permis de nous familiariser avec les composants logiques disponibles sur Altium et de voir les différentes étapes pour intégrer un schéma sur la FPGA.

légende Initiation aux composants logiques sur Altium


Partie informatique

  • Cette première séance a été dédiée à installer une partie des outils nécessaires pour la partie informatique. Nous avons donc commencé par nous connecter via une liaison série au raspberry pi afin de modifier la configuration réseau nous permettant ainsi de nous connecter au raspberry pi en SSH (secure shell). Nous avons ensuite installé les outils tels que Apache2 un serveur web et libwebsocket une librairie C pour la manipulation des sockets. Quelques problèmes sont survenus notamment au niveau des mises à jour des dépôts raspian mais ont été rapidement corrigés.
  • Le reste de la séance a été dédié à la mise en place du prototype Arduino (cf image ci-dessus) ainsi qu'à sa programmation. Notre programme Arduino est donc le suivant après avoir subi quelques modifications au cours des séances :

Utilisation de la librairie servo.h pour le contrôle du servo-moteur

#include <Servo.h>

Définitions des pins

#define trigger 9
#define echo 7
#define servo 11

Définitions des variables globales utilisées

long lecture_echo; //durée entre l'émission et la réception d'un signal pour le capteur de distance
long cm; //distance en centimètres 
bool sens=1; //sens de rotation du servo-moteur utilisé pour un balayage gauche-droite
bool active=false; //variable état du système permettant son activation et sa désactivation
Servo servo1; //définition du servo-moteur
int pos = 1; //variable indiquant la position du servo de 0 à 180 degrés

Initialisation setup()

pinMode(trigger,OUTPUT); //définition du pin trigger en sortie
pinMode(echo,INPUT);     //définition du pin echo en entrée
digitalWrite(trigger, LOW); // mettre le pin trigger à 0
servo1.attach(servo);    //init servo
Serial.begin(9600);      //initialisation de la liaison série à 96600 bauds

Boucle principale loop() :

Partie réception liaison série

if(Serial.available() > 0) //si le buffer n'est pas vide
   {
     char buffer =Serial.read(); //lecture
         if (buffer=='1')        //on active le système si on reçoit '1'
           active =true;
           else if (buffer='0')  //on désactive le système si on reçoit '0' 
             active = false;
    }

Partie émission, lecture du sonar et contrôle du servo-moteur

if (active)                       //Si le système est actif
  {
         
     if(pos==0) sens =true;       //Si on atteint une "butée" (0 ou 180) du servo on change de sens de rotation
    else if(pos==180) sens=false;
    if (sens) pos++;              //Incrémentation ou décrémentation de la position en fonction du sens de rotation
    else pos--;
     servo1.write(pos);           //on indique la position au servo-moteur 
   
  
     digitalWrite(trigger, HIGH); //Emission d'une onde ultrason sur le sonar
     delayMicroseconds(10);       //Délai minimum entre émission et lecture (cf datasheet du capteur)
     digitalWrite(trigger, LOW);  //Fin d'émission
     lecture_echo = pulseIn(echo, HIGH,100000); //Utilisation d'une interruption afin de récupérer la durée entre l’émission et la réception avec une durée max pour éviter de rendre la fonction bloquante 
     cm = lecture_echo/58;        //Conversion en centimètres 
      Serial.write(cm);           //émission de la distance via la liaison série
       
      Serial.write(pos);          //émission de la position du servo via la liaison série      
  }
delay(100); //temporisation



  • L'envoi et la réception de données se fait relativement simplement sur Arduino avec les fonctions write et read, avec seule contrainte de tester pour cette dernière si il y a vraiment des données à lire.
  • L'asservissement du servo-moteur en position se fait également assez simplement grâce à la librairie servo.h
  • L'utilisation du capteur ultrason requiert quand même de comprendre son fonctionnement afin de pouvoir l'utiliser. En effet, celui-ci ne renvoie pas simplement une distance mais il nous permet, grâce à un émetteur connecté à une broche trigger, d’émettre une impulsion (minimum 10 micro secondes) puis de recevoir le signal sonore répercuté par un obstacle via un micro grâce à la broche echo. On a ainsi la durée de parcours du signal sonore jusqu’à l'obstacle

qu'on divise par deux (car le signal effectue un aller-retour) et qu'on ramène à une distance connaissant la vitesse du son (approximativement 340 m/s car cela dépend de la température et de la pression de l'air).

Séance 2

Partie électronique

  • Après s'être entraînés avec le tutoriel sur Altium, dès le début de la seconde séance nous nous sommes attaqués à la conception du circuit électronique que nous devons réaliser pour remplacer le fonctionnement du programme Arduino. Nous avons donc à assurer la commande du servomoteur ainsi que du sonar parallèlement. Nous nous sommes premièrement intéressé au servomoteur, qui fonctionne avec un signal PWM, généré par la FPGA. Nous avons alors fait des recherches sur le modèle du servomoteur utilisé pour obtenir ses caractéristiques et nous avons trouvé la datasheet suivante :


légende Datasheet du servomoteur HS-422


  • En lisant ce document, nous avons pu remarquer que la rotation du servomoteur s'effectuait en indiquant une certaine période au signal PWM (400 microsecs pour un angle de 45°). Du coup, nous avons réalisé un circuit permettant seulement d'émettre un signal PWM en faisant varier la fréquence de ce signal afin de retrouver la période indiquée. En utilisant l'analyseur logique branché sur une des broches externes de la FPGA, nous avons relevé une fréquence d'environ 50 kHz pour une période proche de 400 microsecs. A la fin nous n'avons pas eu le temps de tester et d'envoyer ce signal sur le servomoteur pour vérifier nos observations. Après avoir discuté avec un professeur, nous nous sommes rendus compte que nous n'avions pas encore réfléchi à l'alimentation du servomoteur, et que celle-ci poserait problème si on l'alimentait seulement avec la FPGA. En effet, le courant délivré par la NanoBoard ne dépasse pas les 10 mA, alors que selon la datasheet, le servomoteur a besoin d'un courant de 150 mA pour fonctionner convenablement. A la prochaine nous devrons donc réfléchir à un éventuel montage à transistors afin de booster le courant délivré par la FPGA.


légende Schéma Altium pour l'émission d'un signal PWM



Partie informatique

  • Cette deuxième séance nous a permis de mettre en place le serveur websocket ainsi que la liaison série entre la Raspberry et l'Arduino. Nous avons donc commencé par récupérer les fichiers d'exemples ainsi que librairie sur la liaison série, afin d'effectuer quelques tests, ainsi que le code source du serveur websocket. Après ces quelques tests, nous avons donc pu intégrer les fonctions d'ouverture, d'écriture et de lecture de la liaison série au serveur websocket.

Notre protocole est simple :

Pour rappel, notre objectif final sera de tracer les contours d'un environnement tel qu'une pièce meublée par exemple. Pour cela, nous devons à chaque angle associer une distance et ainsi, avec l'aide de quelques fonctions trigonométriques simples, dessiner le point correspondant.

Ainsi, dans un sens nous allons demander à l'utilisateur d'envoyer 0 ou 1 correspondant respectivement à l'activation ou la désactivation du système. L'information passe du navigateur internet au serveur websocket qui le transmet via la liaison série de la Raspberry à l'Arduino ou la nanoboard qui traite ensuite cette information.

Dans l'autre sens, l'Arduino ou la nanoboard envoie les données de position et de distance au serveur websocket grâce à la liaison série et le serveur envoie les données au navigateur qui les trace ensuite à l'écran.


Nous avons commencé par apporter une modification à la librairie serial.c fournie afin de rendre la fonction "read" non bloquante. En effet, afin de ne pas bloquer le serveur en attendant une donnée cette modification était nécessaire.

On ajoute donc le flag O_NONBLOCK à la fonction d'ouverture de la liaison série :

int fd=open(device,flags|O_NOCTTY|O_NONBLOCK);


Voici les modifications apportées au code du serveur websocket afin de prendre en charge la liaison série ainsi que l'envoi et la réception des données :

On commence par la définition des librairies utilisées

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libwebsockets.h>
#include <strings.h>
#include <unistd.h>
#include <termios.h>
#include "serial.h"

On définit ensuite le port série de la raspberry à utiliser

#define SERIAL_DEVICE           "/dev/ttyUSB0"

On définit en globale la variable d'ouverture de la liaison série ainsi que les variables de position et de distance

int sd;
unsigned char sonar,position;

Ouverture du port série dans le main

printf("connexion port serie en %s ...    ",SERIAL_DEVICE); 
sd=serialOpen(SERIAL_DEVICE,SERIAL_BOTH);	
serialConfig(sd,B9600); //vitesse de communication à 9600 Bauds
sleep(2); //attente pour la connexion série (nécessaire après tests)
printf("Done !\n");

Fermeture de la liaison série lorsqu'on ferme le serveur

serialClose(sd);


Envoi d'une donnée reçue par le navigateur via la liaison série

case LWS_CALLBACK_RECEIVE:
               // Ici sont traités les messages envoyés par le navigateur
   printf("received data: %s\n",(char *)in);
   if(write(sd,(unsigned char *)in,sizeof(char))!=1){perror("main.write"); exit(-1); } //écriture sur le port série
   break;

Envoi d'une donnée reçu via la liaison série au navigateur

case LWS_CALLBACK_SERVER_WRITEABLE:
               // Ici sont envoyés les messages au navigateur
   if(read(sd,&sonar,sizeof(char))==1){                            //Si on reçoit une distance on reçoit ensuite la position
     char *out=message + LWS_SEND_BUFFER_PRE_PADDING;
     read(sd,&position,sizeof(char));
     sprintf(out,"%03d %03d",sonar,position);                      //formatage des données pour avoir la position et la distance sur 3 chiffres
     libwebsocket_write(wsi,(unsigned char *)out,TAILLE_MESSAGE,LWS_WRITE_TEXT); //envoi au navigateur
     }
   libwebsocket_callback_on_writable(this,wsi);
   break;



La compilation s'effectue en exécutant la commande

gcc -lwebsockets serial.c serveur.c -o serveur 

Séance 3

Partie électronique

  • Lors de cette dernière séance, nous avons finalisé les tests que nous voulions faire sur le fonctionnement du servomoteur. Nous avons donc finalement réaliser un montage suiveur composé d'un AOP plutôt que du montage à transistors que l'on avait prévu de faire. Nous avions mal compris la datasheet puisqu'en fait lorsqu'on parlait de 150 mA à délivrer, c'était en réalité le courant consommé par le servomoteur lorsqu'il tournait à plein régime. Nous avions en réalité seulement besoin de lui fournir une tension continue de 4,8V (ou 6V selon le modèle de moteur). D'où le montage suiveur avec l'AOP, qui permet quand même de protéger le servomoteur et d'éviter les perturbations du signal PWM.

légende Datasheet de l'amplificateur opérationnel TL082


  • Le montage consiste donc à faire passer le signal PWM de la Nanoboard par un AOP avant d'être connecté au moteur. Nous avons utilisé deux générateurs de tension pour alimenter l'AOP en +Vcc et -Vcc, puis une troisième pour simuler l'alimentation de la Nanoboard et s'assurer du bon fonctionnement du moteur avant de l'alimenter directement avec la Nanoboard, puisque nous n'étions pas sûr du bon fonctionnement de la broche d'alimentation de la FPGA. Pour faire ceci, on réutilisera le même schematic Altium que précédemment pour l'émission d'un signal PWM où l'on fait varier le rapport cyclique de ce signal. On utilisera cependant une fréquence de 20 kHz plutôt que 50 kHz pour le signal. Après avoir regardé de nouveau le signal sur un oscilloscope, nous avons pensé que cette fréquence serait plus adaptée à nos besoins.

Le montage réalisé est le suivant :


légende Réalisation des tests pour le moteur


Avec ce montage nous avons alors réussi à faire tourner le servomoteur pas à pas en augmentant le rapport cyclique progressivement. Puisque l'on passe par Altium pour le modifier, ce rapport cyclique est écrit sur 8 bits. Cela nous a permis, en allant jusqu'aux angles limites du servomoteur, de connaître la valeur min et max du rapport cyclique en 8 bits pour aller de 0° à 180°(rotation maximale que peut effectuer le moteur). Nous avons donc fini notre travail sur le test du moteur.

Après avoir compris son fonctionnement, nous en sommes revenus au but de notre projet, c'est-à-dire faire tourner un sonar sur le servomoteur pivotera continuellement pour réaliser une cartographie de l'espace. Nous devons donc trouver un moyen via Altium pour que le servomoteur puisse réaliser cette tâche. Puisque le servomoteur tourne en faisant varier le servomoteur, nous avons alors pensé à faire un compteur puis décompteur une fois que l'angle maximum est atteint, pour que le moteur puisse tourner en continu. Voici le shematic Altium construit pour essayer de répondre à ce besoin:

légende Schéma Altium pour le fonctionnement du servo-moteur


Nous avons aussi par la suite fait un autre schematic, celui-ci pour le fonctionnement du sonar, qui n'a besoin que de récupérer la distance entre chaque obstacle le plus souvent possible. Voici un fichier qui pourrait être utilisé :

légende Schéma Altium pour le sonar


Pour le moment, nous n'avons pas encore pu tester si nos schémas Altium fonctionnaient correctement. Nous essayerons de le faire prochainement.

Fin de la partie électronique

Finalement, pour la partie électronique, nous n'irons pas plus loin que la réalisation de ces deux schémas Altium dont l'un s'occupe de la rotation en continu du servomoteur tandis que l'autre permet d'envoyer la distance mesurée par le sonar. Nous n'avons pas eu le temps de les tester et d'envoyer par la liaison série les données fournies par la FPGA, même si nous pensons que ces schematics paraissent bons sur le principe, mais nous aurions sûrement eu des petites erreurs. Cependant on ne peut s'en rendre compte qu'en testant seulement. Pour la démonstration du projet, on utilisera donc le programme Arduino réalisé auparavant.

Partie informatique

Cette dernière séance à été dédiée à la mise en place de la page HTML permettant l'activation et la désactivation du système ainsi que le cartographie de la pièce. La page HTML est hébergée sur la Raspberry grâce au serveur web Apache2.

L'interface finale est constituée d'un titre "Cartographie Sonar" qui s'affiche en vert ou en rouge en fonction de si le navigateur a pu se connecter au serveur websocket ou non, deux boutons "activer" ou déactiver" et un canvas de 300 sur 300 pixels pour effectuer le tracé.

Interface.png


Pour rendre la page HTML accessible à n'importe quelle machine connectée sur le même réseau que la raspberry, il faut mettre l'intégralité des codes HTML et Javascript dans le dossier /var/www/ (ou /var/www/html/ selon les versions d'Apache2) de la rapberry pour qu'elles soient hébergées par Apache. Le premier fichier "chargé" par Apache et qui représente la racine de la hiérarchie des fichiers HTML se nomme "index.html". N'ayant qu'un seul fichier HTML nous avons mis l'intégralité du code dans ce fichier.


Avant toute chose il a fallu télécharger et installer la librairie jquery.js qui permet de lancer des requêtes HTTP asynchrones mais aussi de faciliter la programmation JavaScript.


Voici comment est constitué notre code index.html (les scripts javascript ont directement été incorporés dedans) :

ATTENTION : l'essentiel de ce code n'est pas exécutable car il été modifié pour que les balises du code HTML n’interfèrent pas avec celles utilisées par le wiki. Le code complet et intact sera joint au wiki et se trouve également sur la Raspberry Pi


Connexion au serveur websocket à l'ip 172.26.79.10 au port 9000

window.WebSocket=(window.WebSocket||window.MozWebSocket);
var websocket=new WebSocket('ws://172.26.79.10:9000','myprotocol');

Changement de couleur du titre en cas d’échec de connexion

websocket.onopen=function(){ $('h1').css('color','green'); };
websocket.onerror=function(){ $('h1').css('color','red'); };

Définition et initialisation à 0 du tableau contenant toutes les distances pour chaque angle (taille 180)

var distances=[];
for (var i=0;i<180;i++) distances[i]=0;

Déclaration et fonction d'initialisation du canvas et de son contexte

var canvas;
var context;
function create_canvas(){
    canvas=document.getElementById("canvas");
    context=canvas.getContext('2d');
}

Traitement des données reçues grâce à l'event websocket.onmessage

websocket.onmessage=function(message){
    distances[parseInt(message.data.substring(4,7))]=parseInt(message.data.substring(0,3));
    draw(distances);
    $('messages').append(('<p',{ text: message.data }));
};
  • Cette fonction permet de stocker une distance à chaque angle grâce au tableau distances tel que distances[angle]=distance avec angle allant de 0 à 180.
  • Le message reçu, contenu dans message.data, est une chaîne de caractères au format "%03d %03d" c'est à dire qu'on a la distance sur trois chiffres suivis d'un espace suivi par la position également sur trois chiffres. On doit donc découper la chaîne pour récupérer indépendamment la distance et la position grâce à la méthode "substring".
  • Néanmoins ces deux données sont toujours du type chaîne de caractères, on les convertit donc en entier grâce à la méthode "parseInt".
  • On redessine tout le tableau dès qu'on reçoit une nouvelle donnée.


Fonction d'envoi de message

function sendMessage(signal){
   websocket.send(signal);
   (signal).val();
}

Fonction de dessin de la cartographie

function draw(distances)
{	
   //effacage du canvas avant de redessiner
   context.clearRect(0, 0,300,300);
   //tracé de tout les points
   context.strokeStyle = "black";
   for(var i=0;i < 180;i++)
   {
     context.fillRect(distances[i]*Math.cos((i*Math.PI)/180)+150,distances[i]*Math.sin((i*Math.PI)/180)+150,3,3);
   }	
}
  • Cette fonction permet de dessiner l'intégralité du tableau distances qui est mis à jour dès qu'on reçoit un message
  • Pour dessiner on fait appel à la méthode fillRect(origineX,origineY,tailleX,tailleY) qui trace un rectangle de 3 sur 3 pixels
  • On détermine les coordonnées x et y à partir de l'angle et de la distance grâce aux fonctions cosinus et sinus tel que x=dist * cos(angle) et y=dist * sin(angle). Il faut donc convertir l'angle i qui est en degrés en radians. On ajoute de plus 150 à chacune de ces coordonnées pour les centrer sur le canvas.


Script HTML sans les balises car sinon reconnu par le wiki comme du script

 body  onload="create_canvas();"                               //init du canvas au chargement (event "onload")
  h1>Cartographie sonar</h1                                    //Titre en rouge ou en vert
   button onclick="sendMessage(1);">Activer</button            //bouton "activer" qui envoie 1 au serveur websocket
   button onclick="sendMessage(0);">Desactiver</button         //bouton "désactiver" qui envoie 0 au serveur websocket
   canvas id="canvas" width="300" height="300" ></canvas       //création du canvas
 /body>


Le tracé une fois effectué ressemble à cela : Trace.png

Démonstration

Voici une démonstration de notre système prototypé avec une Arduino

Nous utilisons ici une Arduino nano ainsi qu'un servo-moteur avec une amplitude maximale plus faible que celle utilisée d'habitude :


Voici l'intégralité de nos codes sources :

Fichier:Sonar.ino.txt Fichier:Serial.c.txt Fichier:Serial.h.txt Fichier:Serveur.c.txt Fichier:Index.txt


Le fichier index.html n'a pas pu être téléversé car nous obtenons l'erreur "Le fichier ne peut pas être téléversé parce qu’il serait détecté comme « text/html » par Internet Explorer, ce qui correspond à un type de fichier interdit car potentiellement dangereux."

Il reste néanmoins présent sur la Raspberry dans le dossier /var/www/ (la version se trouvant dans le home est obsolète ).

Conclusion

  • Pour terminer notre travail, nous pouvons affirmer que cela nous a permis de nous mettre en situation de projet, comme il serait possible de l'être dans une entreprise quelconque. En effet, nous avons commencé par poser un cahier des charges, avec des objectifs et des contraintes imposées par le matériel qu'il fallait gérer. Dans le cadre de cette activité, nous avons également pu appliquer tous nos cours d'informatique et d'électronique de l'année, et donc comprendre l'intérêt de ceux-ci quand on se place dans un cas concret.
  • Concernant la partie électronique, nous avons surtout utilisé Altium pour pouvoir ensuite implémenter les schematics créés sur la Nanoboard, mais aussi réaliser quelques montages électroniques pour comprendre le fonctionnement de nos composants. Le seul souci est que nous n'avons malheureusement pas tester la liaison série entre la Nanoboard et la Raspberry.
  • Côté informatique, l'essentiel des contraintes ont été réalisées. Notamment la liaison série entre le serveur et la partie électronique qui se limite ici à l'Arduino, le serveur websocket et la page web de notre interface de contrôle. Il reste cependant quelques points qui pourraient être perfectionnés, comme par exemple effectuer un angle à 360 degrés au lieu de 180 à l'aide de deux servo-moteurs, augmenter le nombre de points en prenant plus de mesures lors d'une rotation, améliorer le tracé pour le rendre plus lisible et ainsi peut être obtenir un meilleur rendu en reliant tous les points...
  • Malgré le fait que la partie électronique n'a pas été réussite entièrement,dû probablement à un timing assez serré pour pouvoir la terminer, nous sommes contents que le projet ait globalement abouti, puisque à l'aide du programme Arduino, nous arrivons tout de même à réaliser la cartographie d'une pièce sur une interface web, ce qui était quand même l'objectif de notre projet. La partie informatique reste donc complète quant à elle.
  • Bien que ce projet fut court et certainement plus complexe que ce que l'on pouvait faire d'habitude en cours, nous n'en demeurons pas moins satisfaits de ce que proposait ce travail, qui nous a permis de nous donner aperçu de ce qui pouvait se faire en IMA 4.