/* Mini CNC Plotter firmware, based in TinyCNC https://github.com/MakerBlock/TinyCNC-Sketches Send GCODE to this Sketch using gctrl.pde https://github.com/damellis/gctrl Convert SVG to GCODE with MakerBot Unicorn plugin for Inkscape available here https://github.com/martymcguire/inkscape-unicorn More information about the Mini CNC Plotter here (german, sorry): http://www.makerblog.at/2015/02/projekt-mini-cnc-plotter-aus-alten-cddvd-laufwerken/ */ #include #include /* Structures, global variables */ struct point { float x; float y; float z; } actuatorPos; // Current position of plothead //struct point actuatorPos; /***************** * *** Defines *** * ***************/ //#define debug // Debug #define LINE_BUFFER_LENGTH 1024 #define LED 13 // LED pin on Arduino Uno #define zeroX 14 // pin finecorsa X #define zeroY 15 // pin finecorsa Y #define ena 2 // enable /*************** * *** Servo *** * *************/ #define penZUp 70 // Servo position for Up #define penZDown 85 // Servo position for Down #define penServoPin 10 // Servo on PWM pin 10 Servo penServo; // create servo object to control a servo /***************** * *** Stepper *** * ***************/ #define stepsPerRevolution_X 20 #define stepsPerRevolution_Y 48 #define rpm_X 500 // Speed #define rpm_Y 200 // Speed #define STEP_Y MICROSTEP // Stepper Command mode (SINGLE, DOUBLE, INTERLEAVE, MICROSTEP) #define STEP_X DOUBLE AF_Stepper myStepperY(stepsPerRevolution_Y,1); // Initialize steppers for Y-axis AF_Stepper myStepperX(stepsPerRevolution_X,2); // Initialize steppers for X-axis /**************************************** * *** Drawing settings, should be OK *** * **************************************/ float StepInc = 1; int StepDelay = 5; int LineDelay = 0; int penDelay = 50; /***************************************** * *** Motor steps to go 1 millimeter. *** * ***************************************/ // Use test sketch to go 100 steps. Measure the length of line. // Calculate steps per mm. Enter here. float StepsPerMillimeterX = 40.8163; //100.0; 4.7619*2 float StepsPerMillimeterY = 108.1081; //6.6667*2; //100.0; /************************************* * *** Drawing robot limits, in mm *** * ***********************************/ // OK to start with. Could go up to 50 mm if calibrated well. float Xmin = 0; float Xmax = 48; float Ymin = 0; float Ymax = 35; //40; float Zmin = 0; float Zmax = 1; float Xpos = Xmin; float Ypos = Ymin; float Zpos = Zmax; // Needs to interpret // G1 for moving // G4 P300 (wait 150ms) // M300 S30 (pen down) // M300 S50 (pen up) // Discard anything with a ( // Discard any other command! void Home() { while (digitalRead(zeroX) == HIGH) { myStepperX.step(1, BACKWARD, STEP_X); //go to home X } while (digitalRead(zeroY) == HIGH) { myStepperY.step(1, BACKWARD, STEP_Y); //go to home Y } digitalWrite(LED, HIGH); } /********************************* * Draw a line from (x0;y0) to (x1;y1). * Bresenham algo from https://www.marginallyclever.com/blog/2013/08/how-to-build-an-2-axis-arduino-cnc-gcode-interpreter/ * int (x1;y1) : Starting coordinates * int (x2;y2) : Ending coordinates **********************************/ void drawLine(float x1, float y1) { #ifdef debug Serial.print("fx1, fy1: "); Serial.print(x1); Serial.print(","); Serial.print(y1); Serial.println(""); #endif // Bring instructions within limits if (x1 >= Xmax) { x1 = Xmax; } if (x1 <= Xmin) { x1 = Xmin; } if (y1 >= Ymax) { y1 = Ymax; } if (y1 <= Ymin) { y1 = Ymin; } #ifdef debug Serial.print("Xpos, Ypos: "); Serial.print(Xpos); Serial.print(","); Serial.print(Ypos); Serial.println(""); Serial.print("x1, y1: "); Serial.print(x1); Serial.print(","); Serial.print(y1); Serial.println(""); #endif // Convert coordinates to steps x1 = (int)(x1*StepsPerMillimeterX); y1 = (int)(y1*StepsPerMillimeterY); 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 dy) { for (i=0; i=dx) { over-=dx; myStepperY.onestep(sy,STEP_Y); //myStepperY.step(1,sy,STEP); } delay(StepDelay); } } else { for (i=0; i=dy) { over-=dy; myStepperX.onestep(sx,STEP_X); //myStepperX.step(1,sx,STEP); } delay(StepDelay); } } #ifdef debug Serial.print("dx, dy:"); Serial.print(dx); Serial.print(","); Serial.print(dy); Serial.println(""); Serial.print("Going to ("); Serial.print(x0); Serial.print(","); Serial.print(y0); Serial.println(")"); #endif // Delay before any next lines are submitted delay(LineDelay); // Update the positions Xpos = x1; Ypos = y1; if ((Xpos==0)&&(Ypos==0)){ digitalWrite(LED, HIGH); } else { digitalWrite(LED, LOW); } } // Raises pen void penUp() { penServo.write(penZUp); delay(penDelay); Zpos=Zmax; //digitalWrite(15, LOW); //digitalWrite(16, HIGH); #ifdef debug Serial.println("Pen up!"); #endif } // Lowers pen void penDown() { penServo.write(penZDown); delay(penDelay); Zpos=Zmin; //digitalWrite(15, HIGH); //digitalWrite(16, LOW); #ifdef debug Serial.println("Pen down."); #endif } 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; // Needs to interpret // G1 for moving // G4 P300 (wait 150ms) // G1 X60 Y30 // G1 X30 Y50 // M300 S30 (pen down) // M300 S50 (pen up) // Discard anything with a ( // Discard any other command! 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 ); 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 ); } } } } /********************************** * void setup() - Initialisations **********************************/ void setup() { // Setup pinMode(LED, OUTPUT); pinMode(zeroX,INPUT_PULLUP); pinMode(zeroY,INPUT_PULLUP); pinMode(ena,INPUT_PULLUP); // Decrease if necessary myStepperX.setSpeed(rpm_X); myStepperY.setSpeed(rpm_Y); // Set & move to initial default position penServo.attach(penServoPin); penServo.write(penZUp); Home(); // Notifications!!! Serial.begin( 9600 ); Serial.println("Paolo's Mini CNC Plotter alive and kicking!"); Serial.print("X range is from "); Serial.print(Xmin); Serial.print(" to "); Serial.print(Xmax); Serial.println(" mm."); Serial.print("Y range is from "); Serial.print(Ymin); Serial.print(" to "); Serial.print(Ymax); Serial.println(" mm."); } /********************** * void loop() - Main loop ***********************/ void loop() { delay(100); char line[ LINE_BUFFER_LENGTH ]; 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 #ifdef debug Serial.print( "Received : "); Serial.println( line ); #endif 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 >= LINE_BUFFER_LENGTH-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; } } } } } }