/**********************************************************************
3-Wire CNC Plotter
Code by lingib https://www.instructables.com/
Last update 14 December 2017
Motor1 is controlled by pins D8,D9
Motor2 is controlled by pins D10,D11
Motor3 is controlled by pins D12,D13
Capital letters are used for all "global" variables.
This software assumes that the G-code has been generated by Inkscape.
The hash tags in the "MENU" represent fractional numbers.
The "MENU" uses "sticky" value for XYZ (i.e. the last value is remembered).
----------
COPYRIGHT
----------
This code is free software: you can redistribute it and/or
modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License. If
not, see .
***************************************************************************/
// -------------------------------
// GLOBALS
// -------------------------------
// ----- Bit set/clear/check/toggle macros
#define SET(x,y) (x |=(1< <- -> <- ->"));
Serial.println(F(" Exit = 'E'"));
// ----- flush the buffer
while (Serial.available() > 0) Serial.read();
// ----- control motors with 'A', 'S', 'K', and 'L' keys
char keystroke = ' ';
while (keystroke != 'E') { //press 'E' key to exit
// ----- check for keypress
if (Serial.available() > 0) {
keystroke = (char) Serial.read();
}
// ----- select task
switch (keystroke) {
case 'a':
case 'A': {
// ----- rotate motor1 CW
for (step = 0; step < steps; step++) {
step_motor(1, CW);
}
keystroke = ' '; //otherwise motor will continue to rotate
break;
}
case 's':
case 'S': {
// ------ rotate motor1 CCW
for (step = 0; step < steps; step++) {
step_motor(1, CCW);
}
keystroke = ' ';
break;
}
case 'k':
case 'K': {
// ----- rotate motor2 CW
for (step = 0; step < steps; step++) {
step_motor(2, CW);
}
keystroke = ' ';
break;
}
case 'l':
case 'L': {
// ----- rotate motor2 CCW
for (step = 0; step < steps; step++) {
step_motor(2, CCW);
}
keystroke = ' ';
break;
}
case 't':
case 'T': {
// ----- rotate motor3 CW
for (step = 0; step < steps; step++) {
step_motor(3, CW);
}
keystroke = ' ';
break;
}
case 'y':
case 'Y': {
// ----- rotate motor3 CCW
for (step = 0; step < steps; step++) {
step_motor(3, CCW);
}
keystroke = ' ';
break;
}
case 'e':
case 'E': {
// ----- exit
Serial.println(F(" "));
Serial.println(F(" Calibration complete ..."));
keystroke = 'E';
break;
}
// ----- default for keystroke
default: {
break;
}
}
}
// ----- assume pen touching the paper at the new calibrate position
Z = 0.0;
calc_target_steps(X, Y, Z); //calculate motor steps at new position
CURRENT_STEPS_M1 = TARGET_STEPS_M1;
CURRENT_STEPS_M2 = TARGET_STEPS_M2;
CURRENT_STEPS_M2 = TARGET_STEPS_M3;
}
// ----------------------------------
// T3 pen up
// ----------------------------------
if (INPUT_STRING.startsWith("T3")) {
//process(F("G00 Z15.0"));
Z = 15;
raise_lower_pen();
}
// ----------------------------------
// T4 pen down
// ----------------------------------
if (INPUT_STRING.startsWith("T4")) {
//process(F("G00 Z0.0"));
Z = 0;
raise_lower_pen();
}
// ----------------------------------
// T5 set scale factor
// ----------------------------------
if (INPUT_STRING.startsWith("T5")) {
START = INPUT_STRING.indexOf('S');
if (!(START < 0)) {
FINISH = START + 6;
SUB_STRING = INPUT_STRING.substring(START + 1, FINISH);
SCALE_FACTOR = SUB_STRING.toFloat();
Serial.print(F("Drawing now ")); Serial.print(SCALE_FACTOR * 100); Serial.println(F("%"));
}
else {
Serial.println(F("Invalid scale factor ... try again. (1 = 100%)"));
}
}
// ----------------------------------
// T6 status report
// ----------------------------------
if (INPUT_STRING.startsWith("T6")) {
Serial.println(F(""));
Serial.println(F(" ---------------------"));
Serial.println(F(" Status Report"));
Serial.println(F(" ---------------------"));
Serial.print(F(" X mm :")); Serial.print("\t"); Serial.println(X);
Serial.print(F(" Y mm :")); Serial.print("\t"); Serial.println(Y);
Serial.print(F(" Z mm :")); Serial.print("\t"); Serial.println(Z);
Serial.print(F(" XOFFSET :")); Serial.print("\t"); Serial.println(XOFFSET);
Serial.print(F(" YOFFSET :")); Serial.print("\t"); Serial.println(YOFFSET);
Serial.print(F(" PEN :")); Serial.print("\t"); Serial.println((PEN_UP)? "Up": "Down");
Serial.print(F(" SCALE :")); Serial.print("\t"); Serial.println(SCALE_FACTOR);
Serial.print(F(" D1 mm :")); Serial.print("\t"); Serial.println(D1);
Serial.print(F(" D2 mm :")); Serial.print("\t"); Serial.println(D2);
Serial.print(F(" D3 mm :")); Serial.print("\t"); Serial.println(D3);
Serial.print(F(" M1 steps:")); Serial.print("\t"); Serial.println(TARGET_STEPS_M1);
Serial.print(F(" M2 steps:")); Serial.print("\t"); Serial.println(TARGET_STEPS_M2);
Serial.print(F(" M3 steps:")); Serial.print("\t"); Serial.println(TARGET_STEPS_M3);
Serial.print(F(" -----------------------"));
Serial.println("");
}
// ----------------------------------
// T7 target test pattern
// ----------------------------------
if (INPUT_STRING.startsWith("T7")) {
target();
}
}
// -------------------------------
// MOVE_TO
// -------------------------------
void move_to(float x, float y) { //x,y are scaled coordinates
// ----- locals
long
X = abs(x),
Y = abs(y);
// ----- draw a line between these "scaled" coordinates
draw_line(LAST_X, LAST_Y, X, Y);
// ----- remember previous values
LAST_X = X;
LAST_Y = Y;
}
// ------------------------------------------------------------------------
// DRAW LINE
// ------------------------------------------------------------------------
/*
This routine assumes that we are talking to a linear XY plotter.
The algorithm automatically maps all "octants" to "octant 0" and
automatically swaps the XY coordinates if dY is greater than dX. A swap
flag determines which motor moves for any combination X,Y inputs. The swap
algorithm is further optimised by realising that dY is always positive
in quadrants 0,1 and that dX is always positive in "quadrants" 0,3.
*/
void draw_line(int x1, int y1, int x2, int y2) { //x1,y1,x2,y2 are scaled values
// ----- locals
int
x = x1, //current X-axis position
y = y1, //current Y-axis position
dy, //line slope
dx,
slope,
longest, //axis lengths
shortest,
maximum,
error, //bresenham thresholds
threshold;
// ----- find longest and shortest axis
dy = y2 - y1; //vertical distance
dx = x2 - x1; //horizontal distance
longest = max(abs(dy), abs(dx)); //longest axis
shortest = min(abs(dy), abs(dx)); //shortest axis
if (longest == 0) { //only Z has changed
plot(x, y);
} else {
// ----- scale Bresenham values by 2*longest
error = -longest; //add offset to so we can test at zero
threshold = 0; //test now done at zero
maximum = (longest << 1); //multiply by two
slope = (shortest << 1); //multiply by two ... slope equals (shortest*2/longest*2)
// ----- initialise the swap flag
/*
The XY axes are automatically swapped by using "longest" in
the "for loop". XYswap is used to decode the motors.
*/
bool XYswap = true; //used for motor decoding
if (abs(dx) >= abs(dy)) XYswap = false;
// ----- pretend we are always in octant 0
/*
The current X-axis and Y-axis positions will now be incremented (decremented) each time
through the loop. These intermediate steps are parsed to the plot(x,y) function which calculates
the number of steps required to reach each of these intermediate coordinates. This effectively
linearises the plotter and eliminates the unwanted curves inherent in string plotters.
*/
for (int i = 0; i < longest; i++) {
// ----- move left/right along X axis
if (XYswap) { //swap
if (dy < 0) {
y--;
} else {
y++;
}
} else { //no swap
if (dx < 0) {
x--;
} else {
x++;
}
}
// ----- move up/down Y axis
error += slope;
if (error > threshold) {
error -= maximum;
// ----- move up/down along Y axis
if (XYswap) { //swap
if (dx < 0) {
x--;
} else {
x++;
}
} else { //no swap
if (dy < 0) {
y--;
} else {
y++;
}
}
}
// ----- plot the next rounded XYZ coordinate
plot(x, y); //multiple plots
}
}
}
//----------------------------------------------------------------------------------------
// PLOT XY
//----------------------------------------------------------------------------------------
void plot(int x, int y) { //x,y are scaled values
// ----- locals
unsigned long
current_time, //system time
previous_time1, //previous system time for motor1
previous_time2, //previous system time for motor2
previous_time3; //previous system time for motor2
long
steps1, //downcounters
steps2,
steps3;
// ----- calculate motor steps required
calc_target_steps(x, y, Z);
steps1 = int(abs(TARGET_STEPS_M1 - CURRENT_STEPS_M1));
steps2 = int(abs(TARGET_STEPS_M2 - CURRENT_STEPS_M2));
steps3 = int(abs(TARGET_STEPS_M3 - CURRENT_STEPS_M3));
// ----- calculate motor delays
calc_delays(steps1, steps2, steps3);
// ----- calculate motor directions
DIRECTION1 = (TARGET_STEPS_M1 > CURRENT_STEPS_M1) ? CCW : CW; //assumes all cords wound CW on drums
DIRECTION2 = (TARGET_STEPS_M2 > CURRENT_STEPS_M2) ? CCW : CW; //assumes all cords wound CW on drums
DIRECTION3 = (TARGET_STEPS_M3 > CURRENT_STEPS_M3) ? CCW : CW; //assumes all cords wound CW on drums
// ----- preload the timers and counters
previous_time1 = micros(); //reset the timer
previous_time2 = micros(); //reset the timer
previous_time3 = micros(); //reset the timer
// ----- now step the motors
while ((steps1 != 0) || (steps2 != 0) || (steps3 != 0)) { //stop when all down-counters equal zero
// ----- prepare motor1
if (steps1 > 0) { //prevent additional step ... it occasionally happens!
current_time = micros();
if (current_time - previous_time1 > DELAY1) {
previous_time1 = current_time; //reset timer
steps1--; //decrement counter1
STEP_M1 = true; //set flag
}
}
// ----- prepare motor2
if (steps2 > 0) { //prevent additional step ... it occasionally happens!
current_time = micros();
if (current_time - previous_time2 > DELAY2) {
previous_time2 = current_time; //reset timer
steps2--; //decrement counter2
STEP_M2 = true; //set flag
}
}
// ----- prepare motor3
if (steps3 > 0) { //prevent additional step ... it occasionally happens!
current_time = micros();
if (current_time - previous_time2 > DELAY3) {
previous_time3 = current_time; //reset timer
steps3--; //decrement counter2
STEP_M3 = true; //set flag
}
}
// ----- now step the motors
if (STEP_M1 || STEP_M2 || STEP_M3) {
step_motors();
STEP_M1 = false; //reset the flags
STEP_M2 = false;
STEP_M3 = false;
}
}
// ----- update step counters
CURRENT_STEPS_M1 += TARGET_STEPS_M1 - CURRENT_STEPS_M1;
CURRENT_STEPS_M2 += TARGET_STEPS_M2 - CURRENT_STEPS_M2;
CURRENT_STEPS_M3 += TARGET_STEPS_M3 - CURRENT_STEPS_M3;
}
//----------------------------------------------------------------------------------------
// STEP MOTORS (step one or more motors)
//----------------------------------------------------------------------------------------
void step_motors() {
// ----- locals
enum {dir1, step1, dir2, step2, dir3, step3}; //define bit positions
byte pattern = PORTB; //read current state PORTB
// ----- set motor directions
//(DIRECTION1 == CCW) ? CLR(pattern, dir1) : SET(pattern, dir1); //normal motor direction
//(DIRECTION2 == CCW) ? CLR(pattern, dir2) : SET(pattern, dir2); //normal motor direction
//(DIRECTION3 == CCW) ? CLR(pattern, dir3) : SET(pattern, dir3); //normal motor direction
(DIRECTION1 == CW) ? CLR(pattern, dir1) : SET(pattern, dir1); //motor windings reversed
(DIRECTION2 == CW) ? CLR(pattern, dir2) : SET(pattern, dir2); //motor windings reversed
(DIRECTION3 == CW) ? CLR(pattern, dir3) : SET(pattern, dir3); //motor windings reversed
PORTB = pattern;
delayMicroseconds(PULSE_WIDTH); //wait for direction lines to stabilise
// ----- create leading edge of step pulse(s)
(STEP_M1) ? SET(pattern, step1) : CLR(pattern, step1); //prepare step pulse
(STEP_M2) ? SET(pattern, step2) : CLR(pattern, step2);
(STEP_M3) ? SET(pattern, step3) : CLR(pattern, step3);
PORTB = pattern; //step the motors
delayMicroseconds(PULSE_WIDTH); //mandatory delay
// ----- create trailing-edge of step-pulse(s)
pattern = CLR(pattern, step1);
pattern = CLR(pattern, step2);
pattern = CLR(pattern, step3);
PORTB = pattern;
}
//----------------------------------------------------------------------------------------
// RAISE_LOWER_PEN
//----------------------------------------------------------------------------------------
/*
For some reason the pen NEVER returns to the start point for the G-code sequence
G00 Z15; G00 Z-1; ... it tends to drift in the same direction ???
Until a solution is found, this work-around fools the plotter into thinking that the
pen is always down by adding|removing steps to|from each motor ... crude but effective.
*/
void raise_lower_pen() {
// ----- prepare motor controllers
DIRECTION1 = (Z > 1) ? CW : CCW;
DIRECTION2 = (Z > 1) ? CW : CCW;
DIRECTION3 = (Z > 1) ? CW : CCW;
STEP_M1 = (PEN_UP && (Z > 1)) || (( !PEN_UP && (Z < 1))) ? false : true;
STEP_M2 = (PEN_UP && (Z > 1)) || (( !PEN_UP && (Z < 1))) ? false : true;
STEP_M3 = (PEN_UP && (Z > 1)) || (( !PEN_UP && (Z < 1))) ? false : true;
// ----- change the pen height by 10mm
for (int i = 0; i < 10 * STEPS_PER_MM; i++) {
step_motors();
delayMicroseconds(DELAY_MIN);
}
// ----- tidy up
PEN_UP = (Z > 1) ? true : false;
Z = 0;
}
//----------------------------------------------------------------------------------------
// STEP MOTOR (step a single motor)
//----------------------------------------------------------------------------------------
void step_motor(int motor, bool dir) {
// ----- locals
enum {dir1, step1, dir2, step2, dir3, step3}; //define bit positions
byte pattern = PORTB; //read current state PORTB
switch (motor) {
case 1:
CLR(pattern, step1); //prevent motors stepping
CLR(pattern, step2);
CLR(pattern, step3);
(dir == CW) ? CLR(pattern, dir1) : SET(pattern, dir1); //set motor direction
//(dir == CW) ? SET(pattern, dir1) : CLR(pattern, dir1); //motor windings reversed
PORTB = pattern;
delayMicroseconds(PULSE_WIDTH); //wait for direction line to stabilise
SET(pattern, step1); //prepare step pulse
PORTB = pattern; //step the motors
delayMicroseconds(PULSE_WIDTH);
CLR(pattern, step1); //generate trailing-edge
PORTB = pattern; //step the motors
delayMicroseconds(DELAY_MIN);
break;
case 2:
CLR(pattern, step1); //prevent motors stepping
CLR(pattern, step2);
CLR(pattern, step3);
(dir == CW) ? CLR(pattern, dir2) : SET(pattern, dir2); //set motor direction
//(dir == CW) ? SET(pattern, dir2) : CLR(pattern, dir2); //motor windings reversed
PORTB = pattern;
delayMicroseconds(PULSE_WIDTH); //wait for direction line to stabilise
SET(pattern, step2); //prepare step pulse
PORTB = pattern; //step the motors
delayMicroseconds(PULSE_WIDTH);
CLR(pattern, step2); //generate trailing-edge
PORTB = pattern; //step the motors
delayMicroseconds(DELAY_MIN);
break;
case 3:
CLR(pattern, step1); //prevent motors stepping
CLR(pattern, step2);
CLR(pattern, step3);
(dir == CW) ? CLR(pattern, dir3) : SET(pattern, dir3); //set motor direction
//(dir == CW) ? SET(pattern, dir3) : CLR(pattern, dir3); //motor windings reversed
PORTB = pattern;
delayMicroseconds(PULSE_WIDTH); //wait for direction line to stabilise
SET(pattern, step3); //prepare step pulse
PORTB = pattern; //step the motors
delayMicroseconds(PULSE_WIDTH);
CLR(pattern, step3); //generate trailing-edge
PORTB = pattern; //step the motors
delayMicroseconds(DELAY_MIN);
break;
}
}
//----------------------------------------------------------------------------
// DRAW ARC CLOCKWISE (G02)
//----------------------------------------------------------------------------
void draw_arc_cw(float x, float y, float i, float j) { //x,y,i,j are scaled values
// ----- ignore arcs with large i,j values
/*
There is a bug, either in Inkscape or this algorithm,which results in spurious
circles whenever the i,j values are large. I suspect the center of the arc lies
outside the drawing area when this occurs ... to be investigated. For now it is
easier to ignore all i,j values which are large as arcs with a large radius
approximate to a straight line.
*/
if ((i < -100) || (i > 100) || (j < -100) || (j > 100)) {
move_to(x, y);
} else {
// ----- variables
float
circleX = LAST_X + i, //circle X coordinate
circleY = LAST_Y + j, //circle Y coordinate
delta_x, //horizontal distance between current X and next X
delta_y, //vertical distance between current Y and next Y
chord, //line_length between lastXY and nextXY
radius, //circle radius
alpha, //interior angle of arc
beta, //fraction of alpha
arc, //subtended by alpha
current_angle, //measured CCW from 3 o'clock
next_angle, //measured CCW from 3 o'clock
newX, //interpolated X coordinate
newY; //interpolated Y coordinate
// ----- calculate arc
delta_x = LAST_X - x;
delta_y = LAST_Y - y;
chord = sqrt(delta_x * delta_x + delta_y * delta_y);
radius = sqrt(i * i + j * j);
alpha = 2 * asin(chord / (2 * radius));
//see construction lines step 11 https://www.instructables.com/id/CNC-Drum-Plotter/
arc = alpha * radius; //radians
// ----- sub-divide alpha
int segments = 1;
if (arc > ARC_MAX) {
segments = (int)(arc / ARC_MAX);
beta = alpha / segments;
} else {
beta = alpha;
}
// ----- calculate current angle
/*
atan2() angles between 0 and PI are CCW +ve from 3 o'clock.
atan2() angles between 2*PI and PI are CW -ve relative to 3 o'clock
*/
current_angle = atan2(-j, -i);
if (current_angle <= 0) current_angle += 2 * PI; //angles now 360..0 degrees CW
// ----- plot intermediate CW coordinates
next_angle = current_angle; //initialise angle
for (int segment = 1; segment < segments; segment++) {
next_angle -= beta; //move CW around circle
if (next_angle < 0) next_angle += 2 * PI; //check if angle crosses zero
newX = circleX + radius * cos(next_angle); //standard circle formula
newY = circleY + radius * sin(next_angle);
move_to(newX, newY);
}
// ----- draw final line
move_to(x, y);
}
}
//----------------------------------------------------------------------------
// DRAW ARC COUNTER-CLOCKWISE (G03)
//----------------------------------------------------------------------------
/*
We know the start and finish coordinates which allows us to calculate the
chord length. We can also calculate the radius using the biarc I,J values.
If we bisect the chord the center angle becomes 2*asin(chord/(2*radius)).
The arc length may now be calculated using the formula arc_length = radius*angle.
*/
void draw_arc_ccw(float x, float y, float i, float j) {
// ----- ignore arcs with large i,j values
/*
There is a bug, either in Inkscape or this algorithm,which results in spurious
circles whenever the i,j values are large. I suspect the center of the arc lies
outside the drawing area when this occurs ... to be investigated. For now it is
easier to ignore all i,j values which are large as arcs with a large radius
approximate to a straight line.
*/
if ((i < -100) || (i > 100) || (j < -100) || (j > 100)) {
move_to(x, y);
} else {
// ----- variables
float
circleX = LAST_X + i, //circle X coordinate
circleY = LAST_Y + j, //circle Y coordinate
delta_x, //horizontal distance between current X and next X
delta_y, //vertical distance between current Y and next Y
chord, //line_length between lastXY and nextXY
radius, //circle radius
alpha, //interior angle of arc
beta, //fraction of alpha
arc, //subtended by alpha
current_angle, //measured CCW from 3 o'clock
next_angle, //measured CCW from 3 o'clock
newX, //interpolated X coordinate
newY; //interpolated Y coordinate
// ----- calculate arc
delta_x = LAST_X - x;
delta_y = LAST_Y - y;
chord = sqrt(delta_x * delta_x + delta_y * delta_y);
radius = sqrt(i * i + j * j);
alpha = 2 * asin(chord / (2 * radius));
//see construction lines step 11 https://www.instructables.com/id/CNC-Drum-Plotter/
arc = alpha * radius; //radians
// ----- sub-divide alpha
int segments = 1;
if (arc > ARC_MAX) {
segments = (int)(arc / ARC_MAX);
beta = alpha / segments;
} else {
beta = alpha;
}
// ----- calculate current angle
/*
tan2() angles between 0 and PI are CCW +ve from 3 o'clock.
atan2() angles between 2*PI and PI are CW -ve relative to 3 o'clock
*/
current_angle = atan2(-j, -i);
if (current_angle <= 0) current_angle += 2 * PI; //angles now 360..0 degrees CW
// ----- plot intermediate CCW coordinates
next_angle = current_angle; //initialise angle
for (int segment = 1; segment < segments; segment++) {
next_angle += beta; //move CCW around circle
if (next_angle > 2 * PI) next_angle -= 2 * PI; //check if angle crosses zero
newX = circleX + radius * cos(next_angle); //standard circle formula
newY = circleY + radius * sin(next_angle);
move_to(newX, newY);
}
// ----- draw final line
move_to(x, y);
}
}
//---------------------------------------------------------------------------
// CALCULATE TARGET MOTOR STEPS (Inkscape)
//---------------------------------------------------------------------------
void calc_target_steps(float x, float y, float z) {
// ----- calculate target diagonals (scaled lengths)
D1 = sqrt(((PX / 2 - GW / 2) + x + XOFFSET) * ((PX / 2 - GW / 2) + x + XOFFSET) + ((PY / 2 - GD / 2) + y + YOFFSET) * ((PY / 2 - GD / 2) + y + YOFFSET) + (PZ - z) * (PZ - z));
D2 = sqrt(((PX / 2 - GW / 2) - x - XOFFSET) * ((PX / 2 - GW / 2) - x - XOFFSET) + ((PY / 2 - GD / 2) + y + YOFFSET) * ((PY / 2 - GD / 2) + y + YOFFSET) + (PZ - z) * (PZ - z));
D3 = sqrt((x + XOFFSET) * (x + XOFFSET) + ((PY / 2 - GD / 2) - y - YOFFSET) * ((PY / 2 - GD / 2) - y - YOFFSET) + (PZ - z) * (PZ - z));
// ----- calculate steps (from frame) to target
TARGET_STEPS_M1 = (long)round((D1 * STEPS_PER_MM));
TARGET_STEPS_M2 = (long)round((D2 * STEPS_PER_MM));
TARGET_STEPS_M3 = (long)round((D3 * STEPS_PER_MM));
}
//---------------------------------------------------------------------------
// CALCULATE DELAYS
// Assigns values to DELAY1, DELAY2, DELAY3 ready for next pen move.
// All motors (that rotate) must finish stepping at the same time.
//---------------------------------------------------------------------------
void calc_delays(long Steps1, long Steps2, long Steps3) {
// ----- locals
float
rotate_time;
long
steps1 = abs(Steps1), //disregard negative numbers
steps2 = abs(Steps2),
steps3 = abs(Steps3),
steps_min,
steps_mid,
steps_max,
delay_mid, //motor with middle number of steps
delay_max; //motor with least number of steps
// ----- find max, min, and middle number of steps
steps_max = max(max(steps1, steps2), steps3);
steps_min = min(min(steps1, steps2), steps3);
steps_mid = steps1 + steps2 + steps3 - steps_max - steps_min;
if (steps_max > 0) { //one or more motors stepping
// ----- calculate the rotation time for the motor with the most steps
rotate_time = (float)(steps_max * DELAY_MIN); //all motors rotate for this period of time
// ----- calculate delay_max
if (steps_min > 0) {
delay_max = (long)(rotate_time / ((float)steps_min));
} else {
delay_max = 0;
}
// ----- calculate delay_mid
if (steps_mid > 0) {
delay_mid = (long)(rotate_time / ((float)steps_mid));
} else {
delay_mid = 0;
}
// ----- assign a delay to each motor
if (steps1 == steps_max) DELAY1 = DELAY_MIN;
if (steps1 == steps_mid) DELAY1 = delay_mid;
if (steps1 == steps_min) DELAY1 = delay_max;
if (steps2 == steps_max) DELAY2 = DELAY_MIN;
if (steps2 == steps_mid) DELAY2 = delay_mid;
if (steps2 == steps_min) DELAY2 = delay_max;
if (steps3 == steps_max) DELAY3 = DELAY_MIN;
if (steps3 == steps_mid) DELAY3 = delay_mid;
if (steps3 == steps_min) DELAY3 = delay_max;
} else { //no motors stepping
DELAY1 = 0;
DELAY2 = 0;
DELAY3 = 0;
}
}
//----------------------------------------------------------------------------
// TARGET test pattern
// Set XOFFSET,YOFFSET=20
//----------------------------------------------------------------------------
void target() {
process(F("G00 Z10.000000"));
process(F("G00 X10.738204 Y7.079330"));
process(F("G01 Z-1.000000"));
process(F("G02 X10.476334 Y5.762817 Z-1.000000 I-3.440214 J-0.000002"));
process(F("G02 X9.730590 Y4.646730 Z-1.000000 I-3.178343 J1.316511"));
process(F("G02 X8.614504 Y3.900985 Z-1.000000 I-2.432599 J2.432598"));
process(F("G02 X7.297991 Y3.639114 Z-1.000000 I-1.316513 J3.178343"));
process(F("G02 X5.981478 Y3.900985 Z-1.000000 I-0.000000 J3.440214"));
process(F("G02 X4.865392 Y4.646730 Z-1.000000 I1.316513 J3.178343"));
process(F("G02 X4.119647 Y5.762817 Z-1.000000 I2.432599 J2.432598"));
process(F("G02 X3.857777 Y7.079330 Z-1.000000 I3.178343 J1.316511"));
process(F("G02 X4.865393 Y9.511928 Z-1.000000 I3.440214 J-0.000001"));
process(F("G02 X7.297991 Y10.519543 Z-1.000000 I2.432598 J-2.432599"));
process(F("G02 X9.730589 Y9.511928 Z-1.000000 I-0.000000 J-3.440214"));
process(F("G02 X10.738204 Y7.079330 Z-1.000000 I-2.432598 J-2.432599"));
process(F("G01 X10.738204 Y7.079330 Z-1.000000"));
process(F("G00 Z10.000000"));
process(F("G00 X12.871670 Y12.653014"));
process(F("G01 Z-1.000000"));
process(F("G01 X1.750981 Y1.532323 Z-1.000000"));
process(F("G00 Z10.000000"));
process(F("G00 X1.750981 Y12.653013"));
process(F("G01 Z-1.000000"));
process(F("G01 X12.871670 Y12.653013 Z-1.000000"));
process(F("G01 X12.871670 Y1.532323 Z-1.000000"));
process(F("G01 X1.750981 Y1.532323 Z-1.000000"));
process(F("G01 X1.750981 Y12.653013 Z-1.000000"));
process(F("G00 Z10.000000"));
process(F("G00 X1.750981 Y12.653014"));
process(F("G01 Z-1.000000"));
process(F("G01 X12.871670 Y1.532323 Z-1.000000"));
process(F("G00 Z10.000000"));
process(F("G00 X0.0000 Y0.0000"));
}