Mini traceur CN

Introduction

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.

Partie mécanique

La partie mécanique est construite à partir :

Eléments issus des lecteurs de DVD

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.

Pièces à concevoir et à imprimer en 3D

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.

Partie électrique

La partie électrique nécessite :

Schéma du câblage

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.

Tests des moteurs et du servomoteur

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);
  }
}

Chien de garde

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.

Partie informatique

L'ordinateur communique avec la carte Arduino via une liaison USB. Trois éléments sont donc à mettre en œuvre :

Echanges entre l'ordinateur et la carte Arduino

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 :

CodeSignification
G0 X3 Y9Le stylo levé va directement au point d'abscisse 3 mm et d'ordonnée 9 mm.
G1 X8 Y2Le stylo posé va directement au point d'abscisse 8 mm et d'ordonnée 2 mm.
G2 X8 Y2 I9 J2Le 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.
testLe 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 :

Programme installé sur la carte Arduino

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);
}

Programmes installés sur l'ordinateur

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 :

Conclusion

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.