Search This Blog

Wednesday, March 16, 2011

Arduino XYZ CNC Carving Machine Control Software

Working on control software for the CNC machine.

Wrote a basic program to do linear interpolation from coordinates received over the serial interface.  Now I need to find an easy way to send coords from a graphics program.

Trying to decide what I need to write so that I can communicate with some open source machine control software.  The Arduino can't do a lot on it's own, but it needs to interpret commands.

Looks like some machines support HPGL code.  That is reasonably simple to implement but will require writing several functions.  It is a short term solution

This guy made an HPGL GUI
http://www.linuxcnc.org/component/option,com_kunena/Itemid,20/func,view/catid,31/id,1870/lang,english/
www.securetech-ns.ca/camm-linux.html

Many graphics programs can output HPGL.  This will be for mostly 2D projects, like PCB cutting.

this guy wrote an HPGL interpretor, that i looked at for ideas, but didn't end up trying to use it
http://sensi.org/~svo/motori/

This page has a simple HGL file that I used for test
http://www.winline.com/evalpen_center.html
http://www.winline.com/images/plotfiles/GL-C-O.plt

Wrote this very basic HPGL control layer to interface from basic HPGL commands to control the adafruit motor shield:
The bug i have right now is that if i send the very long coord lists in this file, i fill the Arduino serial buffer and it hoses the data, since the Arduino has to move the motors, reading the data as it comes doesn't quite work. Meanwhile this is what i have so far, that works will with manually entered commands.


 // Stepper Motor Control Program
// For an XYZ stepper motor controller
// Receives serial commands from PC, controls 3 motor CNC machine
// Interprets basic HPGL style instructions
// Tom Wilson 2011

#define INCH 4000 //steps per inch of the machine = threads per inch * steps per rotation
#define RPM 30 //speed of the motors
#define SER_HEADER 'G' // Header tag for serial message

// Adafruit Motor shield library
// modified by tomw to drive 2 shields at once
// This requires a modified library
//with the second having MOTORLATCH on pin 13, renamed MOTORLATCHA
//and all functions renamed to A versions
#include <AFMotor.h>
#include <AFMotorA.h>

//Set up the steps per revolution and location of motors
AF_Stepper motorX(200, 1);
AF_Stepper motorY(200, 2);
AF_StepperA motorZ(200, 2);

// Set up the basic machine position variable
signed long int Xpos = 0;
signed long int Ypos = 0;
signed long int Zpos = 0;

// Set up the destination machine position variable
signed long int newXpos = 0;
signed long int newYpos = 0;
signed long int newZpos = 0;

// Set up global place to keep serial values
signed long int newSerialData;
bool XorY = false;

void setup() {
  Serial.begin(9600); // set up Serial library at 9600 bps
  Serial.println("Stepper Controller Online");

  //set the default speed of the motors
  motorX.setSpeed(RPM); ;
  motorY.setSpeed(RPM); ;
  motorZ.setSpeed(RPM); ;

  delay(1000); // wait for the supply to come up

  //step motors one step and back to power the coils
  motorX.step(1, FORWARD, SINGLE);
  motorY.step(1, FORWARD, SINGLE);
  motorZ.step(1, FORWARD, SINGLE);
  motorX.step(1, BACKWARD, SINGLE);
  motorY.step(1, BACKWARD, SINGLE);
  motorZ.step(1, BACKWARD, SINGLE);

  // Home position
  Xpos = 0;
  Ypos = 0;
  Zpos = 0;
  newXpos = 0;
  newYpos = 0;
  newZpos = 0;

  Serial.print("Current:");
  Serial.print("\tX:");
  Serial.print(Xpos);
  Serial.print("\tY:");
  Serial.print(Ypos);
  Serial.print("\tZ:");
  Serial.println(Zpos);

  Serial.println("Ready for Commands");
}

void loop() {
  while(Serial.available() ) {
    // Loop simply accepts commands from serial and executes them
    readHPGLcommand();               //read the command

  }
}


void readHPGLcommand() {
  // reads serial port for two letter HPGL command and optional coords that follow
  char c;
  bool coord_avail;

  //make sure there is enough data there for form a command, at least "IN;"
  if(Serial.available()>=3){
  
    c = Serial.read();
 
    // Interpret HPGL command, ingnore unsupported commands and whitespace
    //http://paulbourke.net/dataformats/hpgl/    has definitions
    switch( c ) {
      case 'P' :
          switch( Serial.read() ) {
            case 'U' :  // PU Pen Up
              Serial.println("Pen Up  \t/\\ /\\ /\\ /\\");   //lift pen and optionally move to new coords
              zMove(50);
              // expect either nothing or pairs of coords to follow
              XorY = true;
              coord_avail = true;
              while( coord_avail ) {  
                coord_avail = readSerialNumber();
                // hack to alternate reading values as X or Y
                if ( XorY) {
                  newXpos = newSerialData;
                  XorY = false;
                } else {
                  newYpos = newSerialData;
                  XorY = true;
                  Serial.print(newXpos); Serial.print(":"); Serial.print(newYpos); Serial.print("\t>>");
                  linearInterpolationMove( newXpos, newYpos);
                }
              }
              break;
            case 'D' :  // PD Pen Down
              Serial.println("Pen Down\t\\/ \\/ \\/ \\/");  //drop pen and optionally move to new coords
              zMove(0);
              coord_avail = false;
              // expect either nothing or pairs of coords to follow            
              XorY = true;
              coord_avail = true;  //need to make sure the loop goes at least once
              while( coord_avail ) {  
                coord_avail = readSerialNumber();
                // hack to alternate reading values as X or Y
                if ( XorY) {
                  newXpos = newSerialData;
                  XorY = false;
                } else {
                  newYpos = newSerialData;
                  XorY = true;
                  // only valid coord if two values were found
                  Serial.print(newXpos); Serial.print(":"); Serial.print(newYpos); Serial.print("\t>>");
                  linearInterpolationMove( newXpos, newYpos);
                }
              }
  
              break;
            case 'G' :  // PG Page Feed
              Serial.println("Page Feed");
              while( readSerialNumber() ) {}    //keep reading until coords or ';' is found
              // Command doesn't do anything yet
              break;
            case 'T' :  // PG Pen Thickness
              Serial.println("Pen Thickness");
              while( readSerialNumber() ) {}    //keep reading until coords or ';' is found
              // expects one number to be returned, indicating pen
              Serial.print("Pen Thickness Set to:");
              Serial.println(newSerialData);
              // currently not doing anything with this command
              break;
            default :
              Serial.println("WARNING: Unknown command Px ignored");
              while( readSerialNumber() ) {}    //keep reading until coords or ';' is found
              break;
          }
          break;
      case 'I' :
          switch( Serial.read() ) {
            case 'N' :  // IN Initialize
              Serial.println("Initialize");
              while( readSerialNumber() ) {}    //keep reading until coords or ';' is found
              // Home position
              Xpos = 0;
              Ypos = 0;
              Zpos = 0;
              break;
            default :
              Serial.println("WARNING: Unknown command Ix ignored: ");
              while( readSerialNumber() ) {}    //keep reading until coords or ';' is found
              break;
          }
          break;
      case 'S' :
          switch( Serial.read() ) {
            case 'P' :  // SP Select Pen
              Serial.println("Select Pen");
              while( readSerialNumber() ) {}    //keep reading until coords or ';' is found
              // expects one number to be returned, indicating pen
              Serial.print("Pen set to:");
              Serial.println(newSerialData);
              // currently not doing anything with this command
              break;
            default :
              Serial.print("WARNING: Unknown command Sx ignored: ");
              while( readSerialNumber() ) {}    //keep reading until coords or ';' is found
              break;
          }
          break;  
      default :
          //Whitespace or unrecognized letter, ignore it
          break;
    } //cmd switch
  } //if serial
}


bool readSerialNumber() {
//read coords from serial input
//commands end with ';' and lists of coords are separated by ','
//
// Three things can happen and need to differentiate
// 1) no coords at all, just a ; - need to return right away and break the calling while loop
// 2) a coordinate followed by , - need to output the coord and continue
// 3) a coordinate followed by ; - need to output the coord and break the calling while loop

  signed long int coord = 0;   //temp storage for coords from serial
  signed int sign = 1;         //temp storage for coord sign from serial
  char c;
      
       while ( c != ',' && c != ';' /*&& Serial.available()*/){
         c = Serial.read();
         if( c == '-' ) sign = -1; // capture the sign
         if( c >= '0' && c <= '9'){
           coord = (10 * coord) + (c - '0') ; // convert digits to a number
         }
       }
    
      //put the data in the top level variable
      newSerialData = coord * sign;
        
      //detect the end of command ';' after the last coord
      if( c == ';') return false;
      return true;
}


void zMove (signed long int newZ) {
  //calculates steps to move, and moves, to new Z position requested
  signed long int distance = 0;
  signed long int oldZ = Zpos;

  distance = newZ - oldZ;

  if ((distance < 8000) && (distance > -8000)) {
    if (distance >=  1)  motorZ.step(distance, FORWARD, SINGLE);
    if (distance <= -1)  motorZ.step(-1*distance, BACKWARD, SINGLE);
  } else {
    //movement requested is large and likely an error
    Serial.println("ERROR - Z axis out of range");
  }

  Zpos = newZ;  //update machine current position
}




void linearInterpolationMove ( signed long int newX, signed long int newY) {
  float distance = 0;
  int stepnum = 0;
  signed long int nextX;
  signed long int nextY ;
  signed long int oldX = Xpos;
  signed long int oldY = Ypos;

  Serial.print("\t");
  Serial.print(Xpos);
  Serial.print(":");
  Serial.print(Ypos);
  Serial.print("\t --");

  //find the hypotenuse, the total distance to be traveled
  distance = sqrt((newX - oldX)*(newX - oldX) + (newY - oldY)*(newY - oldY) );

  //round to integer number of steps that distance. Step by two to minimize 0 size steps.
  for (stepnum=0; stepnum <= (distance + 0.5); stepnum++) {

    //calculate the nearest integer points along the way
    nextX = oldX + stepnum/distance*(newX-oldX);
    nextY = oldY + stepnum/distance*(newY-oldY);

    //move machine to that new coordinate, if 0 delta, don't move

    if ((distance < 7*INCH) && (distance > -7*INCH)) { //trap crazy value
    /* removed for test
      if ((nextX-Xpos) >=  1)  motorX.step((nextX - Xpos)*INCH/1000, FORWARD, SINGLE);
      if ((nextX-Xpos) <= -1)  motorX.step((Xpos - nextX)*INCH/1000, BACKWARD, SINGLE);
      if ((nextY-Ypos) >=  1)  motorY.step((nextY - Ypos)*INCH/1000, FORWARD, SINGLE);
      if ((nextY-Ypos) <= -1)  motorY.step((Ypos - nextY)*INCH/1000, BACKWARD, SINGLE);
    */

    //update the machine current position
    Xpos = nextX;
    Ypos = nextY;
    } else {
      Serial.println("ERROR!  Distance value too big");
      break;
    }
  }

  //move machine to the exact new coordinate, because rounding makes a small error
  /* removed for test
  if ((newX-Xpos) >=  1)  motorX.step(newX - Xpos, FORWARD, SINGLE);
  if ((newX-Xpos) <= -1)  motorX.step(Xpos - newX, BACKWARD, SINGLE);
  if ((newY-Ypos) >=  1)  motorY.step(newY - Ypos, FORWARD, SINGLE);
  if ((newY-Ypos) <= -1)  motorY.step(Ypos - newY, BACKWARD, SINGLE);
  */

  //update the machine current position
  Xpos = newX;
  Ypos = newY;

  Serial.print("----> \t");
  Serial.print(Xpos);
  Serial.print(":");
  Serial.println(Ypos);
}

2 comments:

  1. Thank you for sharing excellent informations. Your website is so cool. I am impressed by the details that you’ve on this blog. It reveals how nicely you perceive this subject. Bookmarked this website page, will come back for more articles.
    Used CNC Router

    ReplyDelete
  2. To avoid the hassle and post purchase problems, get the assistance of an e-clearing house to eliminate all the attendant worries. The e-clearing house can match your needs with the qualified supplier.
    driver toolkit key

    ReplyDelete