This page is work in progress and is for your entertainment only. Updated 31 October 2021.
I have been having problems with my CNC machine changing direction part way through a move. I have tried new drivers, new Arduino, new CNC V3.0 shield, new stepper motors, adding an earth connection, re-routing the cabling, new USB cables and different power supply. It does all the correct number of steps but can randomly change direction, mainly when doing two axis moves, so I have started writing my own version of GRBL so I could add de-bugging and check each step if I wanted to.
To achieve 3 axis movement I started by working out the length of the move, then dividing that into shorter pieces to work out how many steps for each motor per loop of the program but I kept getting fractions of steps which is not possible. I found Bresenham's Line Algorithm , written in 1962, for finding the coordinates of all pixels in a straight line between two points. This didn't solve my problem but I did work out I needed to calculate the number of steps for all the axis and pick the biggest, doesn't even matter which axis it is. The loop goes from zero to the biggest number and works out how often to send a pulse to each motor, counting each step and continuing until all steps have been moved. The axis with the biggest number of steps gets a pulse sent every time around the loop. The code has comments on very nearly every line explaining what it it doing as well as enough optional serial debugging to watch each step on each axis, although the slows the code down so much as to make feed rates unusable.
It receives one line of G-code at a time over serial and returns 'OK' when it has completed the command. You can control it from the Arduinos IDE serial monitor. Sadly due to the speed of the communication this leaves a slight pause between commands but allows easy debugging. This parses the G-code into its various commands or functions. The G-code commands it supports are as follows
G0 Rapid traverse
G1 Feed traverse
X123.45 Move to absolute X position ( X , Y or Z )
F Feed rate in mm/min
M3 Spindle on
M5 Spindle off
M8 Coolant on
M9 Coolant off
A123.45 Reset current position X to value ( A = X , B = Y or C = Z )
ZEROX Reset current X position to 0.00 ( X , Y or Z )
RUN Runs a program from inside the Arduino software
( TEXT ) Comments between brackets will be ignored
Examples of allowed G-code
G0 X100 Y200 Z10 M3
G1 X200 Y12.70 F127
X2 Y27.54 Z4
g1x10y20z5m3f200
M9 M8
A0 B0 C10
( COMMENT )
All G, M and F codes are modal so it remembers the last command and they do not need including in every line. It doesn't mind upper or lower case or spaces but does need a carriage return between each line Char(13).
In the drop down box below is the Arduino code version 19, just copy and paste it into your Arduino IDE, save it and send it to your Arduino.
( Without warranty and use at your own risk )
// Define Arduino pins for CNC V3.0 shield
#define XstepPin 2
#define YstepPin 3
#define ZstepPin 4
#define XdirPin 5
#define YdirPin 6
#define ZdirPin 7
#define EnablePin 8
#define AbortPin A0
#define SpindlePin 12
#define CoolantPin A3
#define stepsPerMM 200
#define MAXFEED 1000
// Declare veriables
byte stepPin[] = { 0,XstepPin,YstepPin,ZstepPin }; // Step pin for each axis array
byte dirPin[] = { 0,XdirPin ,YdirPin, ZdirPin }; // Direction pin for each axis array
byte STEPSMM[] = { 0,200,200,180 }; // Steps per mm for each axis array
int PULSE = 10 ; // Stepper pulse in microseconds
int USEDPULSE = 0;
byte G = 1; // G1(Feed) or G0(Rapid)
int FEED = 200; // Feed rate in MM/Min
int DMS = 1500; // Delay in microseconds between stepper pulse
int SPEED = 2000; // Delay for aceloration
double ACLEND = 100; // Length of aceloration
double DECSTART = 100; // Start of deceleration
double STEPSX = 100;
double STEPSY = 100;
double STEPSZ = 100;
double TOTALSTEPSX = 100;
double TOTALSTEPSY = 100;
double TOTALSTEPSZ = 100;
double LONGISTSTEPS = 100;
double TOTALSTEPS = 300;
double TARGETSTEPSX = 0;
double TARGETSTEPSY = 0;
double TARGETSTEPSZ = 0;
double NEWX = 0;
double NEWY = 0;
double NEWZ = 0;
double OLDX = 0;
double OLDY = 0;
double OLDZ = 0;
bool XFLAG = false;
bool YFLAG = false;
bool ZFLAG = false;
String SERIALDATA = "";
void setup() {
// Declare pins as output:
pinMode(XstepPin, OUTPUT);
pinMode(XdirPin, OUTPUT);
pinMode(YstepPin, OUTPUT);
pinMode(YdirPin, OUTPUT);
pinMode(ZstepPin, OUTPUT);
pinMode(ZdirPin, OUTPUT);
pinMode(EnablePin, OUTPUT);
pinMode(SpindlePin, OUTPUT);
pinMode(CoolantPin, OUTPUT);
pinMode(AbortPin, INPUT_PULLUP);
// All step pins off
digitalWrite(XstepPin, LOW);
digitalWrite(YstepPin, LOW);
digitalWrite(ZstepPin, LOW);
Serial.begin(115200);
Serial.println ( "Hello from Arduino" );
} // End setup
void loop() {
digitalWrite(EnablePin,HIGH); // Disable stepper drivers
//do { }
//while ( digitalRead(AbortPin) == HIGH );// Abort butten pressed?
//Serial.println ("Abort Button Pressed");
if (Serial.available() > 0) {
SERIALDATA = Serial.readString(); // Get any serial data
SERIALDATA.toUpperCase(); // Make upper case
SERIALDATA.trim(); // Remove white space
if ( SERIALDATA == "POS" ) {
Serial.print ( "Position X" );
Serial.print ( OLDX );
Serial.print ( " Y" );
Serial.print ( OLDY );
Serial.print ( " Z" );
Serial.print ( OLDZ );
Serial.print ( " F" );
Serial.print ( FEED );
Serial.print ( " G" );
Serial.println ( G );
SERIALDATA ="";
}
if ( SERIALDATA == "RUN" ) { PROGRAM(); SERIALDATA =""; }
if ( SERIALDATA == "ZEROX" ) { NEWX = 0;OLDX=0; Serial.println ( "X Zero Position Set" ); SERIALDATA =""; }
if ( SERIALDATA == "ZEROY" ) { NEWY = 0;OLDY=0; Serial.println ( "Y Zero Position Set" ); SERIALDATA =""; }
if ( SERIALDATA == "ZEROZ" ) { NEWZ = 0;OLDZ=0; Serial.println ( "Z Zero Position Set" ); SERIALDATA =""; }
if ( SERIALDATA != "" ) {GCODE ( SERIALDATA );} // Process G-Code
}
} // End main loop
void MOVE(double X, double Y, double Z ) {
digitalWrite(EnablePin,LOW); // Enable stepper drivers
if (X <= 0 ) { // Set direction for 'X' stepper motor. Change < into > to reverse motor direction
digitalWrite(XdirPin, HIGH); } // Rotate clockwise
else
{digitalWrite(XdirPin, LOW); } // Rotate anti-clockwise
if (Y <= 0 ) { // Set direction for 'Y' stepper motor
digitalWrite(YdirPin, HIGH); } // Rotate clockwise
else
{digitalWrite(YdirPin, LOW); } // Rotate anti-clockwise
if (Z <= 0 ) { // Set direction for 'Z' stepper motor
digitalWrite(ZdirPin, HIGH); } // Rotate clockwise
else
{digitalWrite(ZdirPin, LOW); } // Rotate anti-clockwise
if ( G != 0 ){
DMS = int(1000000 / ((stepsPerMM/60) * FEED ));} // Work out pulse delay in microseconds from feed mm/min and steps per mm
else
{DMS = int(1000000 / ((stepsPerMM/60) * MAXFEED ));} // use FEED or MAXFEED depending on G
X = abs(X); Y = abs(Y); Z = abs (Z); // Positave number only in mm to move
TOTALSTEPSX = (X) * STEPSMM[1]; // Work out number of steps required per axis
TOTALSTEPSY = (Y) * STEPSMM[2];
TOTALSTEPSZ = (Z) * STEPSMM[3];
LONGISTSTEPS = 0; // Find the longist number of steps
if ( TOTALSTEPSX > LONGISTSTEPS ) { LONGISTSTEPS = TOTALSTEPSX ;}
if ( TOTALSTEPSY > LONGISTSTEPS ) { LONGISTSTEPS = TOTALSTEPSY ;}
if ( TOTALSTEPSZ > LONGISTSTEPS ) { LONGISTSTEPS = TOTALSTEPSZ ;}
TOTALSTEPS = LONGISTSTEPS; // Count start for acceleration and decceleration
TARGETSTEPSX = LONGISTSTEPS/TOTALSTEPSX; // How meny times around the loop to triger one step
TARGETSTEPSY = LONGISTSTEPS/TOTALSTEPSY; // One or more will always be '1'
TARGETSTEPSZ = LONGISTSTEPS/TOTALSTEPSZ; // The rest will always be bigger than '1'
//Serial.println (" "); // For debugging stepper maths ;-)
//Serial.print("TOTAL STEPS X");
//Serial.print( TOTALSTEPSX );
//Serial.print(" Y");
//Serial.print( TOTALSTEPSY );
//Serial.print(" Z");
//Serial.print( TOTALSTEPSZ );
//Serial.print(" LONGISTSTEPS ");
//Serial.println( LONGISTSTEPS );
STEPSX = 0;
STEPSY = 0;
STEPSZ = 0;
byte A = 4; // Aceleration factor. was 1
SPEED = 3000; // Delay in microsends start speed for aceleration (4000 is 75 mm/min if 200 steps/mm ) was 4000
ACLEND = TOTALSTEPS - abs(SPEED-DMS)/A; // Work out end point of aceleration
if (ACLEND < TOTALSTEPS/2){ ACLEND = TOTALSTEPS/2;} // if longer than half way, make it half way
DECSTART = abs(SPEED-DMS)/A; // Work out start point of decelaration
if (DECSTART > TOTALSTEPS/2){ DECSTART = TOTALSTEPS/2;} // if shorter the half way, make it half way
do { // ******** Run all three stepper motors loop ***************************************************************
TOTALSTEPS--; // How meny steps left
if ( TOTALSTEPS > ACLEND ) { SPEED = SPEED -A;} // Before aceleration finish then Acelerate
if ( TOTALSTEPS < DECSTART ) { SPEED = SPEED +A;} // After deceleration start then Decelerate
USEDPULSE = 0;
if ( TARGETSTEPSY > 0 ) // Moving 'Y' axis ?
{STEPSY++;
if ( STEPSY>=TARGETSTEPSY ) { // Whole step reached ?
digitalWrite(YstepPin, HIGH); // Send pulse to 'X' stepper motor driver
//delayMicroseconds(PULSE); // Delay for stepper pulse
//digitalWrite(YstepPin, LOW);
//USEDPULSE = USEDPULSE + PULSE;
STEPSY = STEPSY - TARGETSTEPSY;
TOTALSTEPSY--;
}// End if
}// End if
//delayMicroseconds(SPEED/2); // Delay for mm/min feed rate
if ( TARGETSTEPSX > 0 ) // Moving 'X' axis ?
{STEPSX++;
if ( STEPSX>=TARGETSTEPSX ) { // Whole step reached ?
digitalWrite(XstepPin, HIGH); // Send pulse to 'X' stepper motor driver
//delayMicroseconds(PULSE); // Delay for stepper pulse
//digitalWrite(XstepPin, LOW);
//USEDPULSE = USEDPULSE + PULSE;
STEPSX = STEPSX - TARGETSTEPSX;
TOTALSTEPSX--;
}// End if
}// End if
if ( TARGETSTEPSZ > 0 ) // Moving 'Z' axis ?
{STEPSZ++;
if ( STEPSZ>=TARGETSTEPSZ ) { // Whole setp reached ?
digitalWrite(ZstepPin, HIGH); // Send pulse to 'X' stepper motor driver
//delayMicroseconds(PULSE); // Delay for stepper pulse
//digitalWrite(ZstepPin, LOW);
//USEDPULSE = USEDPULSE + PULSE;
STEPSZ = STEPSZ - TARGETSTEPSZ;
TOTALSTEPSZ--;
}// End if
}// End if
delayMicroseconds(PULSE); // Delay for stepper pulse was PULSE
digitalWrite(XstepPin, LOW); // Switch all pulses to stepper motoers off
digitalWrite(YstepPin, LOW);
digitalWrite(ZstepPin, LOW);
delayMicroseconds(SPEED); // Delay for mm/min feed rate was SPEED-PULSE
//Serial.print("STEPS left X"); // Display steps left for de-buggin
//Serial.print( TOTALSTEPSX );
//Serial.print(" Y");
//Serial.print( TOTALSTEPSY );
//Serial.print(" Z");
//Serial.print( TOTALSTEPSZ );
//Serial.print(" SPEED");
//Serial.print( SPEED );
//Serial.println (" ");
} while ( TOTALSTEPSX >= 1 or TOTALSTEPSY >= 1 or TOTALSTEPSZ >= 1 );// Any steps left go arond loop again
// SERIALOUTPUT(X,Y,Z);
}// End MOVE
void GCODE ( String BLOCK ) {
String ADDRESS = "";
int L = BLOCK.length(); // How many letters in the G-Code block
double VALUE = 0;
byte LETTER = 0;
NEWX = 0 ; NEWY = 0 ; NEWZ = 0;
XFLAG = false; YFLAG = false; ZFLAG = false; // Clear 'X Y and Z used' flags
BLOCK.toUpperCase(); // Change to all upper case
BLOCK.trim(); // Remove whitespace
int i = 0; // Start form the start !
do{ // Parse BLOCK
if ( BLOCK.charAt(i) == char(40)){ // Check for comment '('
do{ ADDRESS = ADDRESS + BLOCK.charAt(i); i++;} // Get comment
while (BLOCK.charAt(i) != char(41) and i < L ); // Look for end of comment ')'
ADDRESS = ADDRESS + ")"; // Add ')' for display
Serial.println ( ADDRESS ); // Display comment
ADDRESS = " "; // Clear comment
}
do{ ADDRESS = ADDRESS + BLOCK.charAt(i); i++;} // Get ADDRESS
while ((BLOCK.charAt(i) > char(44) and BLOCK.charAt(i) < char(58)) and i < L ); // Repeat while number or no text left
Serial.print ( ADDRESS );
Serial.print ( " " );
LETTER = char( ADDRESS.charAt(0)); // Get Letter in ASCII F=70 G=71 M=77 X=88 Y=89 Z=90 A=65 B=66 C=67
Serial.print ( LETTER );
ADDRESS = ADDRESS.substring(1); // Remove Letter from start of ADDRESS
Serial.print ( " " );
Serial.print ( ADDRESS );
Serial.print ( " " );
VALUE = ADDRESS.toDouble(); // Convert text number in string to a double value
Serial.println ( VALUE );
ADDRESS = ""; // Clear ADDRESS for next time around
// ********************* USE THE INFORMATION **************************
if ( LETTER == 70 and VALUE >= 1 ){ FEED = VALUE; } // 'F' Set feed rate ( mm/min )
if ( FEED > MAXFEED ){ FEED = MAXFEED; } // Check max feed is not exceded
if ( FEED < 200 ){ FEED = 200;} // Check not to slow
if ( LETTER == 88 ){ NEWX = VALUE; XFLAG = true;} // 'X' Set X value and its flag
if ( LETTER == 89 ){ NEWY = VALUE; YFLAG = true;} // 'Y' Set Y value and its flag
if ( LETTER == 90 ){ NEWZ = VALUE; ZFLAG = true;} // 'Z' Set Z value and its flag
if ( LETTER == 71 ){ G = VALUE; } // 'G' Set G 1=feed 0=rapid
if ( LETTER == 77 and VALUE == 3 ){ digitalWrite(SpindlePin, HIGH);Serial.println ( "M3 Spindle ON " );}
if ( LETTER == 77 and VALUE == 5 ){ digitalWrite(SpindlePin, LOW );Serial.println ( "M5 Spindle OFF" );}
if ( LETTER == 77 and VALUE == 8 ){ digitalWrite(CoolantPin, HIGH);Serial.println ( "M8 Coolant ON " );}
if ( LETTER == 77 and VALUE == 9 ){ digitalWrite(CoolantPin, LOW );Serial.println ( "M9 Coolant OFF" );}
if ( LETTER == 65 ){ NEWX = VALUE; OLDX = VALUE; } // 'A' Re-set X to VALUE
if ( LETTER == 66 ){ NEWY = VALUE; OLDY = VALUE; } // 'B' Re-set Y to VALUE
if ( LETTER == 67 ){ NEWZ = VALUE; OLDZ = VALUE; } // 'C' Re-set Z to VALUE
// ********************************************************************
}while ( i < L ); // Any letters left ? go around loop again
// ******************** SEND DATA TO MOVE SUBROUTINE ******************
if ( XFLAG == true or YFLAG == true or ZFLAG == true ){// Any axis data ?
if ( XFLAG == false ){ NEWX = OLDX; } // Any change to 'X' or stay the same
if ( YFLAG == false ){ NEWY = OLDY; } // Any change to 'Y' or stay the same
if ( ZFLAG == false ){ NEWZ = OLDZ; } // Any change to 'Z' or stay the same
Serial.print ( "Distance X" ); // Display distance to go
Serial.print ( NEWX-OLDX );
Serial.print ( " Distance Y" );
Serial.print ( NEWY-OLDY );
Serial.print ( " Distance Z" );
Serial.print ( NEWZ-OLDZ );
MOVE( NEWX-OLDX , NEWY-OLDY , NEWZ-OLDZ ); // Send absolute X Y Z values to the MOVE subroutine
OLDX = NEWX ; OLDY = NEWY ; OLDZ = NEWZ ; // Make the old coordinates into the new one
Serial.println (" ");
}// End if
Serial.println ( "OK" );
} // End GCODE
void PROGRAM(){
GCODE ( "g1z-1 F200" );
//GCODE ( "M3" );
GCODE ( "X36.60Y33.36F600" );
//GCODE ( "Y0x20" );
GCODE ( "x0 y0" );
GCODE ( "g0z5" );
GCODE ( "M5" );
} // End PROGRAM
Universal G-code Sender ( UGS ) will not work with this software so I also had to write a very basic sender from Python version 2. I do have version 3 installed but my IDE ( Geany ) defaults to version 2.
Python needs PySerial to connect to the Arduino so you may need to install a python installer and then PySerial, so for example in a terminal in Linux :-
sudo apt install python-pip
pip install PySerial
You need to set the path for the USB connection and the path for the G-code file in the Python program. For example in Linux, displayed bold red, change to your own path :-
SERIAL = serial.Serial( '/dev/ttyACM0', baudrate = 115200, timeout = 0.1)
OPENFILEPATH = "/home/richard/CNCprog.txt"
You can type a command and press 'Enter' to send it or type 'send' to send the full G-code program which it finds located in the OPENFILEPATH. You may need to press 'Enter' a few times to get all of the messages back from the serial buffer.
Python 2 code in the drop down box below. Copy and paste it into your python IDE and save with a .py extension.
import sys
print(sys.version_info)
import serial
SERIAL = serial.Serial('/dev/ttyACM0',baudrate = 115200, timeout = 0.1)
OPENFILEPATH="/home/richard/CNCprog.txt"
file = open(OPENFILEPATH,"r")
GCODE = file.read()
print ( " " )
print ( GCODE )
print (OPENFILEPATH)
ARDUINODATA = SERIAL.readline()
if ARDUINODATA != "" : print ( ARDUINODATA )
while 1:
# Get input from keyboard
A = raw_input( ">" )
# Do we want to send?
if A == "send" :
print ("Sending program to Arduino")
A = raw_input( "Press Entre" )
for i in range( 0, len(GCODE)-1):
B = GCODE[i:i+1]
print ( i ,B)
SERIAL.write(B)
if B == "\n" :
while True:
ARDUINODATA = SERIAL.readline()
if ARDUINODATA != "" : print ( ARDUINODATA )
if ARDUINODATA[0:2] == "OK" : break
print ("Finished sending program to Arduino")
file.close()
A = ""
# List G-Code?
if A == "list" :
print ( GCODE )
A = ""
# Send input to Arduino
if ( A != "" ):
for i in range( 0, len(A) ):
B = A[i:i+1]
print ( B )
SERIAL.write(B)
# Get any data back from Arduino
ARDUINODATA = SERIAL.readline()
if ARDUINODATA != "" : print ( ARDUINODATA )