How to make a DIY Arduino CNC drawing machine at home

how-to-make-a-diy-arduino-cnc-drawing-machine-at-home

Hello, welcome back. In this tutorial, we will learn how to make a DIY Arduino CNC drawing machine at home. This project is mainly based on the L293D motor driver shield and two DVD writers. You can also use two discarded CD ROMs for this. However, the stepper motors must be in working condition. if you don’t have any knowledge of this stepper motor. For that, click this link.

The process of this project

This CNC machine is powered by using two stepper motors and one servo motor. That is, it acts across the X, Y, and Z axes. Also, all of these motors are activated using the L293D motor drive shield and Arduino UNO board. If we want to draw a character through this CNC machine, its G-code must be obtained. The Inkscape software has been used for this. Also, the Arduino program sets the CNC machine to work, and a processing program is used to run the G-code. In this tutorial, how to do all of these are described step by step and by the end of the tutorial, you can easily create a CNC machine at home.

OK let’s do this project step by step. The required components are given below.

Step 1

Firstly, identify these components.

Step 2

Secondly, remove the covers of these DVD writers and get the parts below.

OK, now we will connect these parts as follows.

Step 3

Thirdly, connect the Y-axis stepper motor part to the one DVD writer casing. For that, use the nut and bolt.

Step 4

Then, connect the X-axis stepper motor part to another casing.

Step 5

Next, connect the two casings together. For that, use the pictures below.

Step 6

Ok, now cut the CNC machine writing plate using a DVD writer casing lid.

Step 7

Then, glue the foam board or cardboard piece to the X-axis.

Step 8

After, glue a small piece of pipe to hold the pen. After, hold the pen on.

Step 9

Next, gets the spring and put it into the pen clip. Afterwards, glue it to the top of the pen.

Step 10

Now, attach the servo motor and connect it to the pen with an iron rod.

Step 11

Next, solder the wires for the motors.

Step 12

Then, glue the drawing bed and Arduino UNO board.

Step 13

Now, found the stepper motor coils using a multimeter. For that, change the scale to the diode mode.

Step 14

OK, now connect these stepper motors and the servo motor to the motor driver shield. For that, use the circuit diagram and pictures below.

how-to-make-a-diy-arduino-cnc-drawing-machine-at-home

Step 15

Now, connect this project to the computer and upload the Arduino program. It is as follows.

/*How to make a mini CNC machine using Arduino
   https://srituhobby.com
*/

#include <Servo.h>
#include <AFMotor.h>

AF_Stepper motory(48, 1);
AF_Stepper motorx(48, 2);
Servo servo;

#define penup 115
#define pendown 160
#define Spin 9

//Structures, global variables
struct point {
  float x;
  float y;
  float z;
};

//Current position of plothead
struct point actuatorPos;

//Drawing settings, should be OK
float StepInc = 1;
int StepDelay = 1;
int LineDelay = 0;

//Steps per width and height of the drawing area
float StepsPerMillimeterX = 164.0;
float StepsPerMillimeterY = 250.0;

//Max and min values of the drawing area(mm)
float Xmin = 0;
float Xmax = 38;
float Ymin = 0;
float Ymax = 38;
float Zmin = 0;
float Zmax = 1;

float Xpos = Xmin;
float Ypos = Ymin;
float Zpos = Zmax;

// Set to true to get debug output.
boolean verbose = false;


void setup() {
  Serial.begin( 9600 );
  servo.attach(Spin);
  servo.write(penup);
  delay(100);
  motorx.setSpeed(600);
  motory.setSpeed(600);
  Serial.println("Your CNC machine is ready...");
}

void loop() {
  delay(100);
  char line[512];
  char c;
  int lineIndex;
  bool lineIsComment, lineSemiColon;

  lineIndex = 0;
  lineSemiColon = false;
  lineIsComment = false;

  while (1) {


    while ( Serial.available() > 0 ) {
      c = Serial.read();
      if (( c == '\n') || (c == '\r') ) {             
        if ( lineIndex > 0 ) {                        
          line[ lineIndex ] = '\0';                   
          if (verbose) {
            Serial.print( "Received : ");
            Serial.println( line );
          }
          processIncomingLine( line, lineIndex );
          lineIndex = 0;
        }
        else {
          // Empty or comment line. Skip block.
        }
        lineIsComment = false;
        lineSemiColon = false;
        Serial.println("ok");
      }
      else {
        if ( (lineIsComment) || (lineSemiColon) ) {   
          if ( c == ')' )  lineIsComment = false;     
        }
        else {
          if ( c <= ' ' ) {                           
          }
          else if ( c == '/' ) {                    
          }
          else if ( c == '(' ) {                    
            lineIsComment = true;
          }
          else if ( c == ';' ) {
            lineSemiColon = true;
          }
          else if ( lineIndex >= 512 - 1 ) {
            Serial.println( "ERROR - lineBuffer overflow" );
            lineIsComment = false;
            lineSemiColon = false;
          }
          else if ( c >= 'a' && c <= 'z' ) {        
            line[ lineIndex++ ] = c - 'a' + 'A';
          }
          else {
            line[ lineIndex++ ] = c;
          }
        }
      }
    }
  }
}

void processIncomingLine( char* line, int charNB ) {
  int currentIndex = 0;
  char buffer[ 64 ];                                 
  struct point newPos;

  newPos.x = 0.0;
  newPos.y = 0.0;

  while ( currentIndex < charNB ) {
    switch ( line[ currentIndex++ ] ) {              
      case 'U':
        penUp();
        break;
      case 'D':
        penDown();
        break;
      case 'G':
        buffer[0] = line[ currentIndex++ ];          
        //      buffer[1] = line[ currentIndex++ ];
        //      buffer[2] = '\0';
        buffer[1] = '\0';

        switch ( atoi( buffer ) ) {                  
          case 0:                                   
          case 1:
            // /!\ Dirty - Suppose that X is before Y
            char* indexX = strchr( line + currentIndex, 'X' ); 
            char* indexY = strchr( line + currentIndex, 'Y' );
            if ( indexY <= 0 ) {
              newPos.x = atof( indexX + 1);
              newPos.y = actuatorPos.y;
            }
            else if ( indexX <= 0 ) {
              newPos.y = atof( indexY + 1);
              newPos.x = actuatorPos.x;
            }
            else {
              newPos.y = atof( indexY + 1);
              indexY = '\0';
              newPos.x = atof( indexX + 1);
            }
            drawLine(newPos.x, newPos.y );
            //        Serial.println("ok");
            actuatorPos.x = newPos.x;
            actuatorPos.y = newPos.y;
            break;
        }
        break;
      case 'M':
        buffer[0] = line[ currentIndex++ ];        
        buffer[1] = line[ currentIndex++ ];
        buffer[2] = line[ currentIndex++ ];
        buffer[3] = '\0';
        switch ( atoi( buffer ) ) {
          case 300:
            {
              char* indexS = strchr( line + currentIndex, 'S' );
              float Spos = atof( indexS + 1);
              //         Serial.println("ok");
              if (Spos == 30) {
                penDown();
              }
              if (Spos == 50) {
                penUp();
              }
              break;
            }
          case 114:                                
            Serial.print( "Absolute position : X = " );
            Serial.print( actuatorPos.x );
            Serial.print( "  -  Y = " );
            Serial.println( actuatorPos.y );
            break;
          default:
            Serial.print( "Command not recognized : M");
            Serial.println( buffer );
        }
    }
  }
}

void drawLine(float x1, float y1) {

  if (verbose) {
    Serial.print("fx1, fy1: ");
    Serial.print(x1);
    Serial.print(",");
    Serial.print(y1);
    Serial.println("");
  }

  y1 = y1 * -1;

  
  if (x1 >= Xmax) {
    x1 = Xmax;
  }
  if (x1 <= Xmin) {
    x1 = Xmin;
  }
  if (y1 >= Ymax) {
    y1 = Ymax;
  }
  if (y1 <= Ymin) {
    y1 = Ymin;
  }



  if (verbose) {
    Serial.print("Xpos, Ypos: ");
    Serial.print(Xpos);
    Serial.print(",");
    Serial.print(Ypos);
    Serial.println("");
  }

  if (verbose) {
    Serial.print("x1, y1: ");
    Serial.print(x1);
    Serial.print(",");
    Serial.print(y1);
    Serial.println("");
  }

  
  x1 = (int)(x1 * StepsPerMillimeterX / 2);
  y1 = (int)(y1 * StepsPerMillimeterY / 2);
  float x0 = Xpos;
  float y0 = Ypos;


  long dx = abs(x1 - x0);
  long dy = abs(y1 - y0);
  int sx = x0 < x1 ? StepInc : -StepInc;
  int sy = y0 < y1 ? StepInc : -StepInc;

  long i;
  long over = 0;

  if (dx > dy) {
    for (i = 0; i < dx; ++i) {
      motorx.onestep(sx, MICROSTEP);
      over += dy;
      if (over >= dx) {
        over -= dx;
        motory.onestep(sy, MICROSTEP);
      }
      delay(StepDelay);
    }
  } else {
    for (i = 0; i < dy; ++i) {
      motory.onestep(sy, MICROSTEP);
      over += dx;
      if (over >= dy) {
        over -= dy;
        motorx.onestep(sx, MICROSTEP);
      }
      delay(StepDelay);
    }
  }

  if (verbose) {
    Serial.print("dx, dy:");
    Serial.print(dx);
    Serial.print(",");
    Serial.print(dy);
    Serial.println("");
  }

  if (verbose) {
    Serial.print("Going to (");
    Serial.print(x0);
    Serial.print(",");
    Serial.print(y0);
    Serial.println(")");
  }

  
  delay(LineDelay);
  Xpos = x1;
  Ypos = y1;
}


void penUp() {
  servo.write(penup);
  delay(50);
  Zpos = Zmax;
}

void penDown() {
  servo.write(pendown);
  delay(50);
  Zpos = Zmin;
}

Code explanation

Firstly, library files are included and objects are created for these libraries.

#include <Servo.h>
#include <AFMotor.h>

AF_Stepper motory(48, 1);
AF_Stepper motorx(48, 2);
Servo servo;

Secondly, the servo motor pin, pen up and pen down values are defined.

#define Spin 9
#define penup 115
#define pendown 160

Thirdly, variables are created to help the program.

//Structures, global variables
struct point {
  float x;
  float y;
  float z;
};

//Current position of plothead
struct point actuatorPos;

//Drawing settings, should be OK
float StepInc = 1;
int StepDelay = 1;
int LineDelay = 0;

Next, enter the maximum step values that your X and Y stepper motors can move. For that, use the stepper motor example in the AFmotor library file. Also, these step values depend on your DVD writers. Do not enter the following values.

//Steps per width and height of the drawing area
float StepsPerMillimeterX = 164.0;
float StepsPerMillimeterY = 250.0;

Then, enter the length and width of the drawing area using millimetres.

//Max and min values of the drawing area(mm)
float Xmin = 0;
float Xmax = 38;
float Ymin = 0;
float Ymax = 38;
float Zmin = 0;
float Zmax = 1;

In the setup function,

void setup() {
//The serial monitor is beginning
  Serial.begin( 9600 );
//Sets the servo motor pin
  servo.attach(Spin);
//This code is pulled up the pen
  servo.write(penup);
  delay(100);
//Includes speed of stepper motors
  motorx.setSpeed(600);
  motory.setSpeed(600);
  Serial.println("Your CNC machine is ready...");
}

In the loop function, G-code reading and drawing functions are included. These are described in the program.

  • The complete program of this project – Download
void loop() {
  delay(100);
  char line[512];
  char c;
  int lineIndex;
  bool lineIsComment, lineSemiColon;

  lineIndex = 0;
  lineSemiColon = false;
  lineIsComment = false;

  while (1) {

    // Serial reception - Mostly from Grbl, added semicolon support
    while ( Serial.available() > 0 ) {
      c = Serial.read();
      if (( c == '\n') || (c == '\r') ) {             // End of line reached
        if ( lineIndex > 0 ) {                        // Line is complete. Then execute!
          line[ lineIndex ] = '\0';                   // Terminate string
          if (verbose) {
            Serial.print( "Received : ");
            Serial.println( line );
          }
          processIncomingLine( line, lineIndex );
          lineIndex = 0;
        }
        else {
          // Empty or comment line. Skip block.
        }
        lineIsComment = false;
        lineSemiColon = false;
        Serial.println("ok");
      }
      else {
        if ( (lineIsComment) || (lineSemiColon) ) {   // Throw away all comment characters
          if ( c == ')' )  lineIsComment = false;     // End of comment. Resume line.
        }
        else {
          if ( c <= ' ' ) {                           // Throw away whitepace and control characters
          }
          else if ( c == '/' ) {                    // Block delete not supported. Ignore character.
          }
          else if ( c == '(' ) {                    // Enable comments flag and ignore all characters until ')' or EOL.
            lineIsComment = true;
          }
          else if ( c == ';' ) {
            lineSemiColon = true;
          }
          else if ( lineIndex >= 512 - 1 ) {
            Serial.println( "ERROR - lineBuffer overflow" );
            lineIsComment = false;
            lineSemiColon = false;
          }
          else if ( c >= 'a' && c <= 'z' ) {        // Upcase lowercase
            line[ lineIndex++ ] = c - 'a' + 'A';
          }
          else {
            line[ lineIndex++ ] = c;
          }
        }
      }
    }
  }
}

void processIncomingLine( char* line, int charNB ) {
  int currentIndex = 0;
  char buffer[ 64 ];                                 // Hope that 64 is enough for 1 parameter
  struct point newPos;

  newPos.x = 0.0;
  newPos.y = 0.0;

  while ( currentIndex < charNB ) {
    switch ( line[ currentIndex++ ] ) {              // Select command, if any
      case 'U':
        penUp();
        break;
      case 'D':
        penDown();
        break;
      case 'G':
        buffer[0] = line[ currentIndex++ ];          // /!\ Dirty - Only works with 2 digit commands
        //      buffer[1] = line[ currentIndex++ ];
        //      buffer[2] = '\0';
        buffer[1] = '\0';

        switch ( atoi( buffer ) ) {                  // Select G command
          case 0:                                   // G00 & G01 - Movement or fast movement. Same here
          case 1:
            // /!\ Dirty - Suppose that X is before Y
            char* indexX = strchr( line + currentIndex, 'X' ); // Get X/Y position in the string (if any)
            char* indexY = strchr( line + currentIndex, 'Y' );
            if ( indexY <= 0 ) {
              newPos.x = atof( indexX + 1);
              newPos.y = actuatorPos.y;
            }
            else if ( indexX <= 0 ) {
              newPos.y = atof( indexY + 1);
              newPos.x = actuatorPos.x;
            }
            else {
              newPos.y = atof( indexY + 1);
              indexY = '\0';
              newPos.x = atof( indexX + 1);
            }
            drawLine(newPos.x, newPos.y );
            //        Serial.println("ok");
            actuatorPos.x = newPos.x;
            actuatorPos.y = newPos.y;
            break;
        }
        break;
      case 'M':
        buffer[0] = line[ currentIndex++ ];        // /!\ Dirty - Only works with 3 digit commands
        buffer[1] = line[ currentIndex++ ];
        buffer[2] = line[ currentIndex++ ];
        buffer[3] = '\0';
        switch ( atoi( buffer ) ) {
          case 300:
            {
              char* indexS = strchr( line + currentIndex, 'S' );
              float Spos = atof( indexS + 1);
              //         Serial.println("ok");
              if (Spos == 30) {
                penDown();
              }
              if (Spos == 50) {
                penUp();
              }
              break;
            }
          case 114:                                // M114 - Repport position
            Serial.print( "Absolute position : X = " );
            Serial.print( actuatorPos.x );
            Serial.print( "  -  Y = " );
            Serial.println( actuatorPos.y );
            break;
          default:
            Serial.print( "Command not recognized : M");
            Serial.println( buffer );
        }
    }
  }
}

//lines drawing function
void drawLine(float x1, float y1) {

  if (verbose) {
    Serial.print("fx1, fy1: ");
    Serial.print(x1);
    Serial.print(",");
    Serial.print(y1);
    Serial.println("");
  }

  y1 = y1 * -1;

  //  Bring instructions within limits
  if (x1 >= Xmax) {
    x1 = Xmax;
  }
  if (x1 <= Xmin) {
    x1 = Xmin;
  }
  if (y1 >= Ymax) {
    y1 = Ymax;
  }
  if (y1 <= Ymin) {
    y1 = Ymin;
  }



  if (verbose) {
    Serial.print("Xpos, Ypos: ");
    Serial.print(Xpos);
    Serial.print(",");
    Serial.print(Ypos);
    Serial.println("");
  }

  if (verbose) {
    Serial.print("x1, y1: ");
    Serial.print(x1);
    Serial.print(",");
    Serial.print(y1);
    Serial.println("");
  }

  //  Convert coordinates to steps
  x1 = (int)(x1 * StepsPerMillimeterX / 2);
  y1 = (int)(y1 * StepsPerMillimeterY / 2);
  float x0 = Xpos;
  float y0 = Ypos;

  //  Let's find out the change for the coordinates
  long dx = abs(x1 - x0);
  long dy = abs(y1 - y0);
  int sx = x0 < x1 ? StepInc : -StepInc;
  int sy = y0 < y1 ? StepInc : -StepInc;

  long i;
  long over = 0;

  if (dx > dy) {
    for (i = 0; i < dx; ++i) {
      motorx.onestep(sx, MICROSTEP);
      over += dy;
      if (over >= dx) {
        over -= dx;
        motory.onestep(sy, MICROSTEP);
      }
      delay(StepDelay);
    }
  } else {
    for (i = 0; i < dy; ++i) {
      motory.onestep(sy, MICROSTEP);
      over += dx;
      if (over >= dy) {
        over -= dy;
        motorx.onestep(sx, MICROSTEP);
      }
      delay(StepDelay);
    }
  }

  if (verbose) {
    Serial.print("dx, dy:");
    Serial.print(dx);
    Serial.print(",");
    Serial.print(dy);
    Serial.println("");
  }

  if (verbose) {
    Serial.print("Going to (");
    Serial.print(x0);
    Serial.print(",");
    Serial.print(y0);
    Serial.println(")");
  }

  //  Delay before any next lines are submitted
  delay(LineDelay);
  //  Update the positions
  Xpos = x1;
  Ypos = y1;
}

//Pen up function
void penUp() {
  servo.write(penup);
  delay(50);
  Zpos = Zmax;
}
//Pen down function
void penDown() {
  servo.write(pendown);
  delay(50);
  Zpos = Zmin;
}

Step 16

Then, select board and port. After, upload this code to the CNC machine.

Step 17

OK, now let’s set up the Inkscape software. For that, download and install this software using the link below.

  • Inkscape — Download
  • Then, include the G-code extension into the software. For that, download the file below and insert it into the Inkscape software installation extension folder.
  • G-code extention — Download
  • Now, follow the pictures below.

Step 18

Now, you can draw any picture using Inkscape software. For that, use the template below.

  • Template — Download
  • Next, open this template using Inkscape software and draw any text, image or character in this template. For that, I’ve used the dragon image. You can use any other picture.
  • Dragon image — Download
  • Use the pictures below to get the G-code.

Step 19

Ok, now let’s run the G-code code file. For that, download and install the Processing IDE.

  • Processing IDE — Download
  • Now, open the processing code. It is as follows.
import java.awt.event.KeyEvent;
import javax.swing.JOptionPane;
import processing.serial.*;

Serial port = null;

String portname = null;
boolean streaming = false;
float speed = 0.001;
String[] gcode;
int i = 0;

void openSerialPort(){
  if (portname == null) return;
  if (port != null) port.stop();
  
  port = new Serial(this, portname, 9600);
  
  port.bufferUntil('\n');
}

void selectSerialPort(){
  String result = (String) JOptionPane.showInputDialog(frame,
    "Select the COM port on the Arduino board",
    "Select serial port",
    JOptionPane.QUESTION_MESSAGE,
    null,
    Serial.list(),
    0);
    
  if (result != null) {
    portname = result;
    openSerialPort();
  }
}

void setup(){
  size(500, 120);
  openSerialPort();
}

void draw(){
  background(0);  
  fill(255);
  int y = 24, dy = 12;
  text("Run your CNC machine..", 12, y); y += dy;
  text("p: select serial port", 12, y); y += dy;
  text("g: select G-code file", 12, y); y += dy;
  text("x: stop your CNC machine", 12, y); y += dy;
  y = height - dy;
  text("Powered by SriTu Hobby", 12, y); y -= dy;
  text("current serial port: " + portname, 12, y); y -= dy;
}

void keyPressed(){
  
  if (!streaming) {
    if (key == 'p') selectSerialPort();
  }
  
  if (!streaming && key == 'g') {
    gcode = null; i = 0;
    File file = null; 
    println("Loading file...");
    selectInput("Select a file to process:", "fileSelected", file);
  }
  
  if (key == 'x') streaming = false;
}

void fileSelected(File selection) {
  if (selection == null) {
    println("Window was closed or the user hit cancel.");
  } else {
    println("User selected " + selection.getAbsolutePath());
    gcode = loadStrings(selection.getAbsolutePath());
    if (gcode == null) return;
    streaming = true;
    stream();
  }
}

void stream(){
  if (!streaming) return;
  
  while (true) {
    if (i == gcode.length) {
      streaming = false;
      return;
    }
    
    if (gcode[i].trim().length() == 0) i++;
    else break;
  }
  
  println(gcode[i]);
  port.write(gcode[i] + '\n');
  i++;
}

void serialEvent(Serial p){
  String s = p.readStringUntil('\n');
  println(s.trim());
  
  if (s.trim().startsWith("ok")) stream();
  if (s.trim().startsWith("error")) stream(); // XXX: really?
}
  • After, click the run button. Next, press the “p” button and select the correct COM port.
  • Then, connect the external power supply to the CNC machine. For that, use the circuit diagram above. I’m recommended 8VDC to 12VDC. Also, you must remember to remove the power jumper.
  • Finally, press the “g” button and select the G-code file.

Now we can see how to draw the picture. If you have any issues with this tutorial. Please comment below. The full video guide is given below. So, we will meet in the next tutorial.

How to make a DIY Arduino CNC drawing machine at home

Leave a Reply

Your email address will not be published. Required fields are marked *