1. Introduction

En mathématiques, il est souvent compliqué et long de dessiner des courbes, comme des paraboles, depuis leur expression. Même une fois qu’on connaît l'allure de la courbe et quelques points par lesquels elle passe, la dessiner à la main est fastidieux et peu précis. Le but de ce projet est de fabriquer et programmer un robot qui le ferait à notre place. Inspiré d’un Thymio, le robot aurait un stylo en son centre et se déplacerait laissant derrière lui le tracé de son déplacement, qu’on programmerait pour être la courbe désirée.

La limitation principale est que le robot ne pourrait que dessiner des courbes continues, si on ne veut pas compliquer la construction en ajoutant un moteur qui peut remonter le stylo pour arrêter de dessiner et se replacer. Une autre limitation est la précision du dessin de la courbe. Pour convertir une courbe en instructions moteurs, on devra la découper en morceau et approximer chaque morceau, le dessin ne sera donc pas parfait, mais donnera une mesure de l’allure de la courbe.
Les enjeux principaux sont de, premièrement construire un robot dessinateur, commandé par un Raspberry-Pi. Ensuite d’écrire un code qui convertirait l’expression d’une courbe en une suite de droites et d’arcs de cercles. Finalement, de convertir les droites et arcs de cercles en commandes pour les moteurs, afin que le robot trace la courbe recherchée.
Afin de décomposer le problème en sous-sections, le premier objectif du robot sera de dessiner des cercles et des polygones. L'objectif final serait d'avoir un code Python qui convertirait une expression de courbe en une suite d'instructions pour le robot. Pendant la réalisation du projet, celui-ci a dû être raccourci, suite à plusieurs problèmes discutés plus loin, et la version finale ne comprend pas la partie traçage de courbes, ni polygones, mais seulement le dessin de cercles.
Résultat final de la construction :IMG_3377.heic

2. Matériel et méthodes

On construira donc un robot, avec une structure en bois sur laquelle on installera un RaspberryPi et un hat Adafruit DC & Stepper Motor HAT qui contrôlera deux moteurs, pour les deux roues du robot. Entre les roues, on aura un stylo, en contact avec le sol, qui marquera le déplacement du robot.

2.1 Matériel

- Un RaspberryPi
- Un Adafruit DC & Stepper Motor HAT
- Deux moteurs petits moteurs
- Deux encodeurs - Deux roues
- Une structure générale rigide du robot (plaque en bois)
- Un stylo
- Une roue folle
- Une alimentation 5V
Optionnel (pour faire un robot à système embarqué) :
- Une batterie 6V (assemblages de piles 1.5V par exemple) pour alimenter les moteurs
- Une batterie portable pour alimenter le Raspberry

2.2 Méthode

2.2.1 Hardware

Nous allons voir ici comment raccorder le Raspberry au Motor HAT et comment raccorder le HAT aux moteurs et aux encodeurs. Le HAT se branche directement aux pins du Raspberry. Pour les détails de l'installation du HAT, un guide complet détaillé se trouve sur le site de Adafruit : https://learn.adafruit.com/adafruit....
Pour alimenter les moteurs, il faut une alimentation entre 5 et 12 Volts et insérer les cables dans l'entrée alimentation du HAT. On peut soit couper une alimentation murale (avec un transformateur), soit alimenter les moteurs avec des batteries (assemblage de 4 piles LR03). J'ai personnellement opté pour les piles afin d'avoir un robot avec le moins de cables possibles qui le retiendraient, et qui dévieraient son parcours.
J'ai ensuite soudé les deux encodeurs et collé les disques magnétiques aux moteurs afin de pouvoir lire la rotation de ces moteurs bon marché et de pouvoir avoir des déplacement précis. J'ai connecté les alimentations des moteurs aux pins de sortie moteurs du HAT, alimenté les encodeurs avec les pins 5V du HAT. Pour les lire, j'ai branché les sorties DATA des encodeurs aux pins GPIO du Raspberry, reportés sur le HAT.
J'ai finalement fixé le Raspberry avec le HAT sur une structure rigide en bois, avec le Raspberry à l'avant et les moteurs avec les roues à l'arrière. Il faut faire attention à bien aligner les roues, pour qu'elles soient parallèles et qu'il n'y ait pas de déviations. J'ai ensuite fait un trou entre les roues pour y mettre le porte-mine qui va tracer le déplacement du robot. Pour finir j'ai fixé la roue folle à l'avant au centre du robot. (cf. photo du résultat final pour plus de détails).

2.2.2 Software

La première étape est de setup le Raspberry pour travailler avec le Adafruit Motor HAT. Pour ceci, il y a quelques librairies à installer et I2C à activer, toute la procédure est précisément décrite sur le site d'Adafruit https://learn.adafruit.com/adafruit....

Pour le code du robot en soit, l'idée générale pour tracer un cercle est de mettre une puissance différente dans un moteur et dans l'autre, pendant un temps donné. Pour contrôler les moteurs, la librairie Adafruit Motorkit nous permet de moduler la puissance des moteurs entre 0 et 1 (fraction de la puissance qui leur est accordée par l'alimentation, 1 = 100%, 0 = 0%).

En faisant tourner les moteurs pendant un temps donné, on a un résultat très aléatoire (après avoir lancé deux fois d'affilée le même code traçant une ligne droite, 8cm la première fois, 10cm la deuxième.) C'est là qu'interviennent les encodeurs. L'axe des moteurs sur lequel est fixé le disque magnétique fait 200 tours quand l'axe principal fait un tour. De plus, le disque magnétique de l'encodeur a 6 régions magnétiques polarisées, ce qui envoie 6 pulses par tour au détecteur. On a donc pour un tour de roue 1200 pulses lus par l'encodeur. Ces informations sont lues sur un des pins GPIO du Raspberry.
L'idée générale va être de, au lieu de faire tourner les moteurs pour une durée déterminée, de les faire tourner jusqu'à que les encodeurs arrivent à un certain nombre de tours, ce qui va grandement augmenter la précision des moteurs. Les encodeurs envoient une suite de signaux avec des tensions HIGH et LOW qui s'alternent très rapidement. Pour éviter de consommer toute la puissance du CPU en vérifiant les changements d'états toutes les millisecondes, on peut utiliser un interrupt, avec la fonction

GPIO.add_event_detect(4, GPIO.FALLING, callback = l_encoder_counter, bouncetime = 1)

, qui va directement détecter sur l'entrée GPIO quand l'état passe de HIGH à LOW, et appeler une fonction quand cela arrive. Cette fonction va augmenter un compteur pour chaque encodeur (gauche et droite) et faire tourner les moteurs tant que la valeur limite des compteurs des encodeurs n'est pas atteinte. Le bounce time = 1 est obligatoire pour pouvoir détecter tous les pulses de l'encodeur, mais même ainsi on en rate quand la roue tourne trop vite.
On a donc, comme code principal :

if name == "main":
   time.sleep(5)
   kit.motor1.throttle = -0.3
   kit.motor4.throttle = 1   #to initiate motor rotation and encoder pulses
   GPIO.add_event_detect(4, GPIO.FALLING, callback = l_encoder_counter, bouncetime = 1)
   GPIO.add_event_detect(24, GPIO.FALLING, callback = r_encoder_counter, bouncetime = 1)

Le time.sleep(5) nous laisse le temps de débrancher les différents câbles après avoir lancé le programme, avant que le robot ne démarre et les deux kit.motor.throttle du début permettent de lancer l'interrupt. Sans eux, les moteurs ne tourneraient que quand le GPIO détecte un pulse de l'encodeur, et ceci n'arrive que quand l'encodeur tourne, ce qui n'arrive que quand les moteurs tournent, etc... Il faut donc initier la boucle. Les valeurs négatives pour les motor throttles permettent de faire tourner un moteur dans le sens inverse (au sens positif). Dans mon cas comme un des moteurs pointait à droite, l'autre à gauche il fallait leur donner des directions opposées pour que le robot avance.

Les deux fonctions encoder_counter sont définies par :

def l_encoder_counter(channel) :
   global l_encoder_count
   if l_encoder_count <= l_max_rotation:
       kit.motor4.throttle = 1
       l_encoder_count += 1
   else :
       kit.motor4.throttle = 0

où on définit l et r_max_rotation comme le nombre de tours de roue qu'on veut faire, compté en tours d'encodeur.
Enfin, on ajoute :

signal.signal(signal.SIGINT, signal_handler)
signal.pause()

avec

def signal_handler(sig, frame):
   GPIO.cleanup()
   sys.exit(0)

pour que les moteurs s'arrêtent une fois les max rotations atteintes.
En modulant les valeurs des motor.throttles et des max_rotations, on peut jouer sur le rayon du cercle et le nombre de tours que le robot va faire.

3. Résultats

Le robot peut dessiner des cercles de différentes tailles, assez précisément. La contrainte principale restante est qu'on doit tenir le cable de l'alimentation du Raspberry pendant le dessin, et faire attention à ce que la tension sur celui-ci ne dévie pas le robot.

4. Discussion

Le premier problème rencontré est que le bouncetime de 1 milliseconde sur l'interrupt est trop long pour détecter tous le pulses de l'encodeur quand le moteur tourne au dessus de 50% de sa puissance max. Ceci nous empêche de pouvoir calculer une constante qui permet de convertir le nombre de pulses des encodeurs en distance parcourue par le robot.
Le deuxième problème est survenu lorsque j'ai essayé de tracer des angles (pour les polygones). L'idée était de faire avancer une roue avec une certaine puissance et de faire reculer l'autre avec la même puissance. Ainsi le robot tournerait sur lui-même et le crayon, se trouvant au entre les deux roues (au niveau des axes des moteurs) ne bougerait pas.
Le problème est que le robot que j'ai construit est trop long, et que ses roues sont trop rapprochées. Ainsi, pour le faire tourner sur lui-même il a besoin de beaucoup de force, et donc de beaucoup d'adhérence des roues sur le sol, ce qu'il n'a pas. Une des solutions serait d'écarter les roues et d'avoir un robot plus rond / carré que long et fin (plus comme un Thymio par exemple). Une autre solution serait d'assez alourdir le robot (entre les roues), pour qu'il ait l'adhérence nécessaire pour tourner sur lui-même.
Le troisième problème rencontré est celui d'avoir un robot raccordé à des fils. En effet, on peut facilement remplacer l'alimentation des moteurs par une alimentation par piles, pour alimenter le Raspberry, les piles fournissent une tension pas assez stable. Une solution serait d'utiliser des piles, mais d'ajouter un régulateur de tension (UBEC), ou de le faire fonctionner sur batterie portable.

5. Conclusion

Le robot peut bel et bien dessiner (des cercles). L'objectif du traçage de courbes n'a pas pu être atteint par manque de temps, celui du traçage de polygones par un problème physique du robot. On pourrait l'améliorer en le faisant fonctionner sans fil (Raspberry sur batterie) et en le construisant plus large.
Pour la partie traçage de courbes, on pourrait essayer d'interpréter une courbe comme une suite de segments et d'arc de cercle, dont on pourrait déterminer le rayon par des rayons de courbure. Pour ceci, il faudrait généraliser le code écrit plus haut comme une fonction ayant pour arguments la puissance à gauche, puissance à droite, nombre de tours d'encodeurs à gauche et à droite. Avec ces 4 arguments, on pourrait exprimer tous les arcs de cercles et segments désirés.
Un objectif bien atteint est l'utilisation des encodeurs afin d'affiner la précision des moteurs. Sans les encodeurs le robot faisait des écarts de 2 centimètres de long en lançant deux fois le même code, quand, après les avoir installés, les encodeurs ont permis de réduire les écarts à moins de 5 millimètres.

Références

Sites WEB et matériel : https://learn.adafruit.com/circuitp... https://www.pololu.com/product/1515 https://www.pololu.com/product/1523 https://learn.adafruit.com/adafruit...