Plusieurs sites web décrivent, de manière plus ou moins détaillée, la procédure pour réaliser un mini traceur à commande numérique, à partir de lecteurs de DVD récupérés sur de vieux ordinateurs et d'une carte Arduino. A quoi sert un tel mini traceur? A rien, si ce n'est à vivre une petite aventure technologique permettant d'acquérir, à peu de frais, des connaissances utiles à la fabrication de machines à commande numérique plus élaborées.
La partie mécanique est construite à partir :
Un lecteur de DVD comporte généralement trois types de moteurs :
On récupère deux moteurs pas à pas et les liaisons glissière correspondantes. La table de la machine est fixée sur la partie mobile d'un des deux éléments, lesquels sont assemblés en équerre pour permettre le déplacement du stylo par rapport à la table. Le stylo est levé ou posé à l'aide du servomoteur.
Pour assembler le mini traceur, il est nécessaire de concevoir et de fabriquer quelques pièces. Trois pieds, la table, le support de stylo, le support de servomoteur, un support de carte et un renfort sont modélisés avec FreeCAD, exportés au format STL puis imprimés en 3D.
Les pièces imprimées en 3D sont fixées à l'aide de vis récupérées sur les lecteurs de DVD. Le stylo est positionné sur son support dans une forme en V et immobilisé avec un élastique.
La partie électrique nécessite :
Les signaux délivrés par la carte Arduino sont de trop faible puissance pour alimenter directement les moteurs pas à pas. On utilise comme intermédiaire un composant électronique, le L293D. Ce double pont en H reçoit les signaux de commande et fournit la puissance nécessaire aux moteurs pas à pas. Ces derniers sont alimentés par quatre conducteurs. Le schéma ci-dessous, réalisé avec le logiciel Fritzing, montre le câblage.
Note : En principe, les circuits de commande et de puissance devraient être séparés. Dans le cas présent, les tensions utilisées pour ces deux circuits sont identiques.
Le code ci-dessous permet de tester les moteurs pas à pas et le servomoteur. Deux des trois dernières lignes du programme doivent être commentées, selon l'élément à tester. Le moniteur série de l'EDI Arduino est utilisé pour envoyer vers la carte des nombres, qui représentent :
Si un moteur ne tourne pas ou s'il tourne dans le mauvais sens, on modifie les numéros associés aux connecteurs.
#include <Servo.h> #include <Stepper.h> // Vitesse de transmission de la liaison USB #define BAUD 9600 // Numéros des connecteurs pour l'axe X #define CX1 8 #define CX2 9 #define CX3 10 #define CX4 11 // Numéros des connecteurs pour l'axe Y #define CY1 2 #define CY2 3 #define CY3 4 #define CY4 5 // Numéro du connecteur lié au servomoteur #define CZ 7 // Paramètres des moteurs pas à pas #define nbPas 20 #define vitesseRotation 250 // Objets associés au servomoteur et aux moteurs Servo servomoteur; Stepper mvtX(nbPas, CX1, CX2, CX3, CX4); Stepper mvtY(nbPas, CY1, CY2, CY3, CY4); void setup() { pinMode(CX1, OUTPUT); pinMode(CX2, OUTPUT); pinMode(CX3, OUTPUT); pinMode(CX4, OUTPUT); pinMode(CY1, OUTPUT); pinMode(CY2, OUTPUT); pinMode(CY3, OUTPUT); pinMode(CY4, OUTPUT); servomoteur.attach(CZ); Serial.begin(BAUD); mvtX.setSpeed(vitesseRotation); mvtY.setSpeed(vitesseRotation); } void loop() { if (Serial.available()) { int nb = Serial.parseInt(); // mvtX.step(nb); // mvtY.step(nb); if (nb!=0) servomoteur.write(nb); } }
Les cartes Arduino sont équipées d'un dispositif de sécurité, appelé "chien de garde" ou "watchdog" en anglais. Ce dispositif réinitialise la carte en cas de problème, qui peut être lié :
Dans le cas du mini traceur, il est possible de rencontrer ce problème. Il est dû à un appel de courant trop important qui fait chuter la tension. Une solution consiste à remplacer le petit fil rouge, entre le connecteur 5 V de la carte Arduino et la plaque d'essai, par une résistance de 1 ou 2 Ω limitant l'appel de courant. Cette résistance peut être complétée par un condensateur, de 100 µF par exemple, placé entre le 5 V et le 0 V, stabilisant la tension.
L'ordinateur communique avec la carte Arduino via une liaison USB. Trois éléments sont donc à mettre en œuvre :
Le mini traceur ne comportant aucun capteur, les données circulant de la carte vers l'ordinateur ne sont guère utiles. Les informations envoyées par l'ordinateur à la carte peuvent être :
Chaque ligne de commande comporte obligatoirement les lettres majuscules G, X, Y et, éventuellement, les lettres I et J. Chaque lettre est suivie d'un nombre positif. Quelques exemples valent mieux qu'un long discours :
Code | Signification |
---|---|
G0 X3 Y9 | Le stylo levé va directement au point d'abscisse 3 mm et d'ordonnée 9 mm. |
G1 X8 Y2 | Le stylo posé va directement au point d'abscisse 8 mm et d'ordonnée 2 mm. |
G2 X8 Y2 I9 J2 | Le stylo posé va au point d'abscisse 8 mm et d'ordonnée 2 mm, en tournant dans le sens horaire autour au point d'abscisse 9 mm et d'ordonnée 2 mm. |
test | Le stylo dessine l'image de test. |
Note : Habituellement en G-code, G0 correspond à une avance rapide, G1 et G2 à une avance de travail.
L'image de test correspond à une version simplifiée de l’Homme de Vitruve, célèbre dessin de Léonard De Vinci. A gauche se trouve l'image programmée et à droite... le résultat obtenu :
Parmi les solutions disponibles sur internet, Grbl est vraisemblablement la plus aboutie. D'autres projets existent, comme Piccolo, GcodeCNCDemo ou TinyCNC. On notera que pour tracer un simple segment, un cercle ou une ellipse, il existe de nombreuses méthodes. Si certaines facilitent la programmation, d'autres, comme les algorithmes de Bresenham ou d'Andres, optimisent le fonctionnement du microcontrôleur.
Le programme ci-dessous a la particularité de proposer une image de test. Il est capable de tracer des arcs de cercle, mais également des arcs d'ellipse. Une ellipse complète s'obtient avec quatre arcs d'ellipse. Le programme, très simplifié, comporte aussi des limites, en particulier :
#include <Servo.h> #include <Stepper.h> #include <math.h> // Vitesse de transmission de la liaison USB #define BAUD 9600 // Numéros des connecteurs pour l'axe X #define CX1 8 #define CX2 9 #define CX3 10 #define CX4 11 // Numéros des connecteurs pour l'axe Y #define CY1 2 #define CY2 3 #define CY3 4 #define CY4 5 // Numéro du connecteur lié au servomoteur #define CZ 7 // Paramètres des moteurs pas à pas #define nbPas 20 #define vitesseRotation 250 float pasParMillimetre=7.143; // Positions du servomoteur (0 à 180) #define positionHaute 70 #define positionBasse 110 // Coordonnées limites du stylo, en mm float Xmin=0.0; float Xmax=35.0; float Ymin=0.0; float Ymax=35.0; // Objets associés au servomoteur et aux moteurs Servo servomoteur; Stepper mvtX(nbPas, CX1, CX2, CX3, CX4); Stepper mvtY(nbPas, CY1, CY2, CY3, CY4); // Position en cours du stylo, en nombre de pas et en mm int x0=0; int y0=0; float X0=0.0; float Y0=0.0; void ligne(float X1, float Y1) { // Limites des coordonnées X1 et Y1 en mm if (X1>=Xmax) { X1=Xmax; } if (X1<=Xmin) { X1=Xmin; } if (Y1>=Ymax) { Y1=Ymax; } if (Y1<=Ymin) { Y1=Ymin; } // Conversion des coordonnées en pas int x1=(int)(X1*pasParMillimetre); int y1=(int)(Y1*pasParMillimetre); // Amplitudes des déplacements en pas int dx=abs(x1-x0); int dy=abs(y1-y0); // Sens de progression du stylo int sx=1; int sy=1; if (x0>x1) { sx=-1; } if (y0>y1) { sy=-1; } // Déplacement du stylo int i; int cumul=0; if (dx>dy) { for (i=0; i<dx; i=i+1) { mvtX.step(sx); cumul=cumul+dy; if (cumul>=dx) { cumul=cumul-dx; mvtY.step(sy); } } } else { for (i=0; i<dy; i=i+1) { mvtY.step(sy); cumul=cumul+dx; if (cumul>=dy) { cumul=cumul-dy; mvtX.step(sx); } } } // Sauvegarde de la nouvelle position x0=x1; y0=y1; X0=X1; Y0=Y1; } void arc(float X1, float Y1, float I, float J) { float R0=sqrt(sq(X0-I)+sq(Y0-J)); float R1=sqrt(sq(X1-I)+sq(Y1-J)); float A0=atan2((Y0-J),(X0-I)); float A1=atan2((Y1-J),(X1-I)); if (A0<0.0) { A0=2.0*PI+A0; } if (A1<0.0) { A1=2.0*PI+A1; } if (A1>=A0) { A1=A1-2.0*PI; } float inc=2.0/(R0+R1); float A; if ((abs(R1-R0)<0.01)||(abs(sin(A1-A0))<0.01)) { // Cercle for (A=A0; A>A1; A=A-inc) { ligne(I+R0*cos(A),J+R0*sin(A)); } } else { // Ellipse float R; float k=(sq(R0/R1)-sq(cos(A1-A0)))/sq(sin(A1-A0)); for (A=A0; A>A1; A=A-inc) { R=R0/sqrt(sq(cos(A-A0))+k*sq(sin(A-A0))); ligne(I+R*cos(A),J+R*sin(A)); } } ligne(X1,Y1); } void leve() { servomoteur.write(positionHaute); delay(300); } void pose() { servomoteur.write(positionBasse); delay(300); } void test() { // Carré leve(); ligne(3.3,0.5); pose(); ligne(31.6,0.5); ligne(31.6,28.8); ligne(3.3,28.8); ligne(3.3,0.5); // Cercle leve(); ligne(0.5,17.5); pose(); arc(34.5,17.5,17.5,17.5); arc(0.5,17.5,17.5,17.5); // Corps leve(); ligne(16.2,23.6); pose(); arc(18.8,23.6,17.5,26.1); ligne(30.2,28.8); ligne(30.5,26.8); ligne(20.7,22.1); ligne(20.3,0.7); ligne(18.4,0.5); ligne(17.5,13.5); ligne(16.7,0.5); ligne(14.7,0.7); ligne(14.4,22.1); ligne(4.5,26.8); ligne(4.8,28.8); ligne(16.2,23.6); // Jambes écartées leve(); ligne(14.5,15.4); pose(); ligne(8.4,3.2); ligne(9.9,2.3); ligne(17.5,13.5); ligne(25.1,2.3); ligne(26.6,3.2); ligne(20.5,15.4); // Bras écartées leve(); ligne(14.4,21.5); pose(); ligne(4.1,21.7); ligne(3.4,23.6); ligne(31.7,23.6); ligne(30.9,21.7); ligne(20.6,21.5); leve(); ligne(0.0,0.0); } void setup() { pinMode(CX1, OUTPUT); pinMode(CX2, OUTPUT); pinMode(CX3, OUTPUT); pinMode(CX4, OUTPUT); pinMode(CY1, OUTPUT); pinMode(CY2, OUTPUT); pinMode(CY3, OUTPUT); pinMode(CY4, OUTPUT); servomoteur.attach(CZ); mvtX.setSpeed(vitesseRotation); mvtY.setSpeed(vitesseRotation); servomoteur.write(positionHaute); Serial.begin(BAUD); Serial.flush(); delay(50); Serial.println("Machine prête."); } void loop() { if (Serial.available()) { String commande=Serial.readString(); commande.trim(); Serial.println(commande); if (commande=="test") { test(); } else { int posG=commande.indexOf('G'); int posX=commande.indexOf('X'); int posY=commande.indexOf('Y'); int posI=commande.indexOf('I'); int posJ=commande.indexOf('J'); String chG=commande.substring(posG+1,posX); String chX=commande.substring(posX+1,posY); String chY=commande.substring(posY+1,posI); String chI=commande.substring(posI+1,posJ); String chJ=commande.substring(posJ+1); chG.trim(); float valX=chX.toFloat(); float valY=chY.toFloat(); float valI=chI.toFloat(); float valJ=chJ.toFloat(); if ((posG!=-1)&&(posX!=-1)&&(posY!=-1)) { if (chG=="0"||chG=="00") { leve(); } else { pose(); } if ((chG=="0")||(chG=="1")||(chG=="00")||(chG=="01")) { ligne(valX, valY); } if ((posI!=-1)&&(posJ!=-1)&&((chG=="2")||(chG=="02"))) { arc(valX, valY, valI, valJ); } } } } delay(50); }
Les programmes installés sur l'ordinateur ont deux missions :
Produire le fichier
Le fichier en G-code peut être créé avec un simple éditeur de texte. Il peut aussi être obtenu avec un des nombreux logiciels disponibles. Parmi ces logiciels, JsCut est une solution très séduisante. Cette application web, écrite en JavaScript, en plus d'être libre et de générer du G-code à partir d'une image au format SVG, permet de simuler le mouvement de l'outil, en l'occurrence celui du stylo.
L'exemple ci-dessous permet de dessiner un atome de lithium.
// Trajectoires G0 X2.1 Y13.8 G2 X16 Y23.6 I17.5 J17.5 G2 X33 Y21.2 I17.5 J17.5 G2 X19 Y11.4 I17.5 J17.5 G2 X2.1 Y13.8 I17.5 J17.5 G0 X3 Y29.3 G2 X21.4 Y22.3 I17.5 J17.5 G2 X32 Y5.6 I17.5 J17.5 G2 X13.6 Y12.7 I17.5 J17.5 G2 X3 Y29.3 I17.5 J17.5 G0 X15.4 Y33.3 G2 X23.7 Y18.3 I17.5 J17.5 G2 X19.6 Y1.8 I17.5 J17.5 G2 X11.3 Y16.7 I17.5 J17.5 G2 X15.4 Y33.3 I17.5 J17.5 // Noyau G0 X14 Y17.5 G2 X21 Y17.5 I17.5 J17.5 G2 X14 Y17.5 I17.5 J17.5 // Electrons G0 X4.5 Y11 G2 X6.5 Y11 I5.5 J11 G2 X4.5 Y11 I5.5 J11 G0 X30.7 Y10.5 G2 X32.7 Y10.5 I31.7 J10.5 G2 X30.7 Y10.5 I31.7 J10.5 G0 X19.1 Y29.8 G2 X21.1 Y29.8 I20.1 J29.8 G2 X19.1 Y29.8 I20.1 J29.8 // Texte G0 X0 Y9 G1 X0 Y0 G1 X6 Y0 G0 X9 Y7 G1 X9 Y6 G0 X9 Y4 G1 X9 Y0
Transférer le fichier
Le G-code peut être transféré vers la carte Arduino avec un logiciel tout prêt ou en faisant tourner quelques lignes de codes. Concernant cette deuxième possibilité, quel langage choisir?
Le code en Python ci-dessous, très basique, permet de réaliser ce transfert.
#!/usr/bin/python3 import serial portUSB = serial.Serial('/dev/ttyUSB0',9600) message = portUSB.readline() print(message.decode('utf-8')) nomFichier = input("Saisir le nom du fichier en G-code : ") Fichier = open(nomFichier,'r') print("") for ligne in Fichier: if ligne != "\n": portUSB.write(ligne.encode('utf-8')) message = portUSB.readline() message = message.strip() print("Commande exécutée : ",message.decode('utf-8')) ligneFin = 'G0X0Y0' portUSB.write(ligneFin.encode('utf-8')) message = portUSB.readline() message = message.strip() print("Commande exécutée : ",message.decode('utf-8'))
Remarques :
Les développements qu'il est possible d'apporter à ce mini-traceur sont presque infinis. La vidéo ci-dessous montre le tracé d'un atome de lithium.