Pi BOAt – Radar de surface
Je n’ai pas pu résister, ce module radar de surface me faisait vraiment trop triper
Au menu, de l’I2C pour le télémètre ultrason SRF02, du ServoBlaster pour le pilotage du servo moteur, du Python pour orchestrer tout ça, et beaucoup de JavaScript et d’HTML 5 (canvas) pour afficher un bel écran radar
Principe de fonctionnement
Un radar c’est quoi ? C’est un truc qui envoi des ondes (sonores, lumineuses, électromagnétiques) et qui attend un retour de cette onde. Si retour il y a, c’est que l’onde a rebondi sur un objet, en mesurant le temps de retour de l’onde, on en déduit à quelle distance se trouve l’objet. En répétant l’opération tout en faisant tourner le radar dans toutes les directions, on peut détecter tous les objets présents aux alentours et les représenter sur un écran
Donc, prenez un capteur à ultrason tel que le SRF02, faites le tourner sur lui même à l’aide d’un servo moteur, et vous avez votre radar
A chaque position d’angle du servo moteur, on note la distance mesurée par le télémètre, on enregistre tout ça dans un registre pour ensuite pouvoir l’afficher. On répète la manip à l’infinie pour avoir un balayage continu
Pour plus d’information sur les télémètres, je vous invite à lire cet article : GPIO – Télémètre à ultrason SRF02
Bien entendu, le temps de réponse et la qualité de mesure des télémètres à ultrason n’étant pas extra-ordinaires, il ne faut pas s’attendre à un haut niveau de précision. Mais, le résultat est, comme vous allez le voir, plus que satisfaisant De plus, il ne faut pas oublier que nous sommes ici pour apprendre et découvrir des principes de fonctionnement. Si votre budget vous le permet, vous pourrez remplacer le télémètre à ultrason par un télémètre laser, et là, vous aurez un radar de surface très précis
Matériel, GPIO et branchements
Au départ, je voulais utiliser le module PCA9685 de chez Adafruit pour piloter le servo moteur, comme dans cet article. Finalement, après avoir testé l’excellente lib ServoBlaster, j’ai décidé de me passer totalement de cette carte et de brancher le servo directement sur le Pi
Au final, j’utilise donc uniquement un télémètre SRF02 et un servo moteur Futaba en les branchant sur le GPIO d’un Pi A+, sans carte intermédiaire
Raspberry Pi A+ | Télémètre SRF02 | Servo Futaba |
Niveau branchement, rien de plus simple, le télémètre se connecte sur les broches I2C du GPIO comme expliqué dans cet article : GPIO – Télémètre à ultrason SRF02
Pour le servo moteur, une seule broche suffit. Nous utiliserons une alimentation séparée pour le servo dont vous pouvez voir le convertisseur de tension sur la droite de la photo ci-dessous.
Pour info, j’utilise le nouvel écran officiel de la fondation Raspberry Pi. Pour le maquettage, c’est vraiment très pratique je trouve
Je ferai un article dédié à cet écran très prochainement
Logiciel, Scripts et affichage
L’architecture logicielle du Pi BOAt n’est pas encore définitive mais le principe général est le suivant :
- Chaque module est piloté par un script Python dédié
- Le script Python inscrit ou lit des données dans un registre au format JSON
- Une page HTML est mise à disposition grâce à un mini serveur web en Python
- Un script JavaScript lit ou écrit dans le registre et gère l’affichage dans la page web
Note : Pour info, à l’heure où je réalise ce prototype, j’utilise un Raspberry Pi A+ avec une Raspbian Lite Jessie (2015-11-21).
script Python radar.py : gestion du servo et du télémètre à ultrason
Pour piloter le servo moteur, nous utiliserons la lib ServoBlaster qui fonctionne très bien et très simplement
Pour l’installer, rien de plus simple, faites un clone du dépôt GIT suivi d’un make pour compiler les sources :
git clone https://github.com/richardghirst/PiBits cd ./PiBits/ServoBlaster/user/ sudo make servod
Il convient enseuite d’activer l’I2C pour pouvoir utiliser le télémètre SRF02. Ceci se fait, à présent, très simplement via la commande raspi-config
Ci-dessous, le script python radar.py dont le rôle est de faire aller et venir le servo d’un extrême à l’autre en continu, tout en prenant les mesures de distance avec le télémètre. A chaque position du servo on associe une distance mesurée, et on enregistre le tout dans un fichier radar.json.
Il y a un paramètre set_pas que vous pouvez ajuster en fonction de la finesse des mesures que vous souhaitez réaliser et de la capacité de votre servo moteur. Pour ma part, le servo moteur dont je dispose n’a une course que de 200 degrés environ. Le pas est converti en pourcentage de cet angle. En choisissant un pas de 1, je prendrais donc 100 mesures sur 200 degrés, soit une mesure tous les 2 degrés d’angle.
Bien entendu, plus vous augmentez le nombre de mesures, plus le balayage sera lent. Après quelques essais, je pense qu’il vaut mieux prendre moins de mesures mais avoir un balayage plus rapide. Une mesure tous les 10 degrés d’angle est largement suffisante pour détecter les objets aux alentours et offre un balayage très rapide.
Ceci est d’autant plus vrai avec un télémètre à ultrason dont le cône de détection est relativement large (plus de 30 degré pour le SRF02). Avec un télémètre laser en revanche, il serait plus pertinent d’avoir un balayage plus fin pour avoir des mesures très précises.
#!/usr/bin/python import RPi.GPIO as GPIO import smbus, time, os, srf02 radar_data_file = "/home/pi/www/data/radar.json" data = [] set_pas = 1 fo = open(radar_data_file, "w") fo.write(str(data)); fo.close() nb = int((100/set_pas)+1) for num in range(0,nb): data.append(0); # SRF02 s = srf02.srf02() # SERVO os.system("sudo /home/pi/PiBits/ServoBlaster/user/servod") p_min = 0 p_mid = 50 p_max = 100 p = p_min pas = set_pas while True: try: if p >= p_max: pas = -set_pas if p <= p_min: pas = set_pas p = p + pas os.system("echo 0="+str(p)+"% > /dev/servoblaster") #time.sleep (0.1) dst = s.getValue() idx = int((100-p)/set_pas) print(str(p)+' - '+str(idx)+' - '+str(dst)) data[idx]=dst fo = open(radar_data_file, "w") fo.write(str(data)); fo.close() except KeyboardInterrupt: os.system("echo 0=50% > /dev/servoblaster") quit()
En exécutant ce script, vous obtiendrez ce résultat :
script JavaScript et page HTML
C’est là que ça devient réellement fun car on va voir le résultat s’afficher à l’écran
Le principe est relativement simple puisque nous avons une simple page web radar.html qui contient deux canvas. Un pour dessiner le fond du radar et un pour afficher les points correspondant aux objets détectés.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Pi BOAt - Radar</title> <link rel="stylesheet" type="text/css" href="css/radar.css" /> <script src="//code.jquery.com/jquery-1.12.0.min.js"></script> <script src="//code.jquery.com/jquery-migrate-1.2.1.min.js"></script> <script src="js/radar.js"></script> </head> <body> <div id="radar"> <canvas id="radar_back" width="500" height="500"></canvas> <canvas id="radar_1" width="500" height="500"></canvas> </div> </body> </html>
Ajoutons à cela une feuille de style radar.css, très basique, pour positionner et styliser les éléments principaux :
body { background-color : #FFF; } div#radar { position : relative; margin : 10px; width : 500px; height : 500px; border : solid 0px #555; } div#radar canvas { position:absolute; left:0px; top:0px; } canvas#radar_back { z-index : 1; } canvas#radar_1 { z-index : 2; }
Enfin, le script le plus important, en JavaScript, radar.js.
Quelques explications s’imposent. Dans un premier temps, le script se charge de dessiner le fond du radar dans le premier canvas. Puis, une fonction appelée toutes les 500ms est chargée de lire le registre radar.json, d’en extraire les données et de dessiner les points correspondants sur le deuxième canvas.
Ce script contient quatre paramètres qu’il convient d’ajuster en fonction de vos besoins :
- radar_width : largeur et hauteur en pixel du radar affiché dans la page web
- max_range : distance maximale en centimètre affichable sur le radar (la distance maximale théorique du SRF02 est de 6 mètres, en pratique, 4 mètres soit 400 cm, c’est bien)
- grid_step : pas de la grille radar en centimètre (permet une meilleure lisibilité des distances)
- angle_range : largeur du balayage en degré (200 degrés dans mon cas)
/**********************/ /* paramètres */ /**********************/ var radar_width = 500; // taille du radar en px var max_range = 400; // distance max en cm var grid_step = 50; // pas de la grille en cm var angle_range = 200; // largeur du balayage en degré /**********************/ Math.radians = function(degrees) { return degrees * Math.PI / 180; }; var radar_timeout; var radar_back, radar_back_ctx; var radar_1, radar_1_ctx; var radar_radius = radar_width/2; // rayon du radar en px var range_ratio = radar_radius/max_range; // ratio px/cm var start_angle = -Math.radians((angle_range/2)+90); var end_angle = -Math.radians(90-(angle_range/2)); function measureConvert (nb_measure, pos, dst) { if(dst > max_range) { dst = max_range; } var angle_deg = -90-(angle_range/2)+Math.round((angle_range/(nb_measure-1))*pos); var angle_rad = Math.radians(angle_deg); var dst_px = Math.round(dst*range_ratio); var x = Math.round(dst_px*Math.cos(angle_rad)+radar_radius); var y = Math.round(dst_px*Math.sin(angle_rad)+radar_radius); var coor = {x:x, y:y, angle:angle_deg} return coor; } $(function() { console.log( "radar ready !" ); initRadar (); drawRadar (); }); function getRadarData () { var data; $.ajax({ async : false, type: "GET", url: 'data/radar.json', cache: false, dataType: "json", success: function (result) { data = result; } }); return data; } function initRadar () { /**********************/ /* Radar Background */ /**********************/ radar_back = document.getElementById("radar_back"); radar_back_ctx = radar_back.getContext("2d"); // background radar_back_ctx.beginPath(); radar_back_ctx.arc(250,250,250,0,Math.PI*2,true); radar_back_ctx.fillStyle = "#000000"; radar_back_ctx.fill(); // center radar_back_ctx.fillStyle = "#FFFFFF"; radar_back_ctx.fillRect(249,249,2,2); // grid radar_back_ctx.strokeStyle = "#888888"; radar_back_ctx.lineWidth = 1; var grid_radius = grid_step; while(grid_radius < max_range) { var grid_radius_px = Math.round(grid_radius*range_ratio); radar_back_ctx.beginPath(); radar_back_ctx.arc(250,250,grid_radius_px,start_angle,end_angle,false); radar_back_ctx.stroke(); grid_radius = grid_radius+grid_step; } /**********************/ /* Radar Layer 1 */ /**********************/ radar_1 = document.getElementById("radar_1"); radar_1_ctx = radar_1.getContext("2d"); } function drawRadar () { console.log('drawRadar'); var measures = getRadarData(); var nb_measure = measures.length; radar_1_ctx.clearRect(0, 0, radar_1.width, radar_1.height); radar_1_ctx.fillStyle = "#00FF00"; radar_1_ctx.strokeStyle = "#00FF00"; radar_1_ctx.lineWidth = 1; radar_1_ctx.beginPath(); for(var i = 0; i < measures.length; i++) { var measure_px = Math.round(measures[i]*range_ratio); var coor = measureConvert (nb_measure, i, measures[i]); radar_1_ctx.fillRect(coor.x-1,coor.y-1,3,3); if(i==0) { radar_1_ctx.moveTo(coor.x,coor.y); } else{ radar_1_ctx.lineTo(coor.x,coor.y); } //console.log (i+" - dst "+measures[i]+" - dst_px "+measure_px+" - x : "+coor.x+" - y : "+coor.y+" - angle : "+coor.angle); } // radar_1_ctx.stroke(); radar_timeout = setTimeout("drawRadar()", 500); }
Pour que la page HTML et le script JavaScript soient accessibles via un navigateur, il convient de les publier grâce à un serveur web (autrement appelé serveur de publication web). Inutile de sortir l’artillerie lourde en installant Apache, nous pouvons mettre en place un serveur web minimaliste grâce une petite commande Python :
python -m SimpleHTTPServer
Et voilà ce que ça donne :
J’ai fais ce test sur une table, sur des distances très courtes (moins de 1 mètre). On distingue bien les trois objets sur l’avant et la gauche du radar. Les points qui apparaissent tout à droite correspondent au dossier d’une chaise qui était contre la table
On observe quelques artefacts inhérents à ce type de télémètre à ultrason. Ce qui confirme l’intuition évoquée plus haut qui dit qu’il vaut mieux avoir un balayage plus rapide quitte à être moins précis
Avec un servo capable de faire un balayage à 360 degrés, ou un petit système de démultiplication, nous pourrions afficher un écran radar complet en modifiant le paramètre angle_range. Encore mieux, avec deux télémètres placés dos à dos sur le servo moteur, on pourrait faire un balayage rapide à 180 degrés tout en ayant une détection à 360 degrés C’est probablement ce que je ferai sur la version finale du Pi BOAt.
Voilà pour ce premier module du Pi BOAt qui expose un principe qui peut être utilisé dans de nombreux autres projets.
Retrouvez la liste de tous les modules du Pi BOAt : Pi BOAt – Description des modules
A très bientôt pour la suite
L’article Pi BOAt – Radar de surface est apparu en premier sur MagdiBlog.