From 2473d0cdba1e449675f6117140862592d9f5ffc1 Mon Sep 17 00:00:00 2001 From: Nikolay Khabarov <2xl@mail.ru> Date: Mon, 26 Jun 2017 04:14:50 +0300 Subject: [PATCH] migration to a new hardware, adding additional configs --- cnc/config.py | 24 ++++++++++---- cnc/gmachine.py | 71 +++++++++++++++++++++++++++++++--------- cnc/hal_raspberry/hal.py | 7 +++- cnc/hal_virtual.py | 30 ++++++++++------- cnc/pulses.py | 52 +++++++++++++++++++++++------ tests/circles.gcode | 7 ++-- tests/rects.gcode | 3 +- tests/test_gmachine.py | 23 ++++++++++++- tests/test_parser.gcode | 6 ++-- tests/test_pulses.py | 35 +++++++++++++++++--- 10 files changed, 202 insertions(+), 56 deletions(-) diff --git a/cnc/config.py b/cnc/config.py index 64097c5..614a259 100644 --- a/cnc/config.py +++ b/cnc/config.py @@ -1,16 +1,26 @@ # Hardware limitations config STEPPER_PULSE_LENGTH_US = 2 -STEPPER_MAX_VELOCITY_MM_PER_MIN = 1800 # mm per min -STEPPER_MAX_ACCELERATION_MM_PER_S2 = 200 # mm per sec^2 +STEPPER_MAX_ACCELERATION_MM_PER_S2 = 3000 # for all axis, mm per sec^2 -STEPPER_PULSES_PER_MM_X = 400 -STEPPER_PULSES_PER_MM_Y = 400 +MAX_VELOCITY_MM_PER_MIN_X = 30000 # mm per min +MAX_VELOCITY_MM_PER_MIN_Y = 24000 # mm per min +MAX_VELOCITY_MM_PER_MIN_Z = 120 # mm per min +MAX_VELOCITY_MM_PER_MIN_E = 1500 # mm per min + +STEPPER_PULSES_PER_MM_X = 100 +STEPPER_PULSES_PER_MM_Y = 100 STEPPER_PULSES_PER_MM_Z = 400 -STEPPER_PULSES_PER_MM_E = 80 +STEPPER_PULSES_PER_MM_E = 150 + +# invert axises direction +STEPPER_INVERTED_X = True +STEPPER_INVERTED_Y = False +STEPPER_INVERTED_Z = False +STEPPER_INVERTED_E = True TABLE_SIZE_X_MM = 200 -TABLE_SIZE_Y_MM = 300 -TABLE_SIZE_Z_MM = 48 +TABLE_SIZE_Y_MM = 200 +TABLE_SIZE_Z_MM = 220 SPINDLE_MAX_RPM = 10000 EXTRUDER_MAX_TEMPERATURE = 250 diff --git a/cnc/gmachine.py b/cnc/gmachine.py index f9b55d2..d374d32 100644 --- a/cnc/gmachine.py +++ b/cnc/gmachine.py @@ -25,7 +25,6 @@ def __init__(self): # init variables self._velocity = 0 self._spindle_rpm = 0 - self._pause = 0 self._local = None self._convertCoordinates = 0 self._absoluteCoordinates = 0 @@ -47,9 +46,11 @@ def release(self): def reset(self): """ Reinitialize all program configurable thing. """ - self._velocity = 1000 + self._velocity = min(MAX_VELOCITY_MM_PER_MIN_X, + MAX_VELOCITY_MM_PER_MIN_Y, + MAX_VELOCITY_MM_PER_MIN_Z, + MAX_VELOCITY_MM_PER_MIN_E) self._spindle_rpm = 1000 - self._pause = 0 self._local = Coordinates(0.0, 0.0, 0.0, 0.0) self._convertCoordinates = 1.0 self._absoluteCoordinates = True @@ -96,6 +97,14 @@ def __check_delta(self, delta): TABLE_SIZE_Z_MM, 0)): raise GMachineException("out of effective area") + # noinspection PyMethodMayBeStatic + def __check_velocity(self, max_velocity): + if max_velocity.x > MAX_VELOCITY_MM_PER_MIN_X \ + or max_velocity.y > MAX_VELOCITY_MM_PER_MIN_Y \ + or max_velocity.z > MAX_VELOCITY_MM_PER_MIN_Z \ + or max_velocity.e > MAX_VELOCITY_MM_PER_MIN_E: + raise GMachineException("out of maximum speed") + def _move_linear(self, delta, velocity): delta = delta.round(1.0 / STEPPER_PULSES_PER_MM_X, 1.0 / STEPPER_PULSES_PER_MM_Y, @@ -103,10 +112,12 @@ def _move_linear(self, delta, velocity): 1.0 / STEPPER_PULSES_PER_MM_E) if delta.is_zero(): return + velocity_per_axis = abs(delta) * (velocity / delta.length()) self.__check_delta(delta) logging.info("Moving linearly {}".format(delta)) gen = PulseGeneratorLinear(delta, velocity) + self.__check_velocity(gen.max_velocity()) hal.move(gen) # save position self._position = self._position + delta @@ -212,14 +223,19 @@ def _circular(self, delta, radius, velocity, direction): direction, radius, velocity)) gen = PulseGeneratorCircular(circle_end, radius, self._plane, direction, velocity) - hal.move(gen) + self.__check_velocity(gen.max_velocity()) # if finish coords is not on circle, move some distance linearly linear_delta = delta - circle_end + linear_gen = None if not linear_delta.is_zero(): logging.info("Moving additionally {} to finish circle command". format(linear_delta)) - gen = PulseGeneratorLinear(linear_delta, velocity) - hal.move(gen) + linear_gen = PulseGeneratorLinear(linear_delta, velocity) + self.__check_velocity(linear_gen.max_velocity()) + # do movements + hal.move(gen) + if linear_gen is not None: + hal.move(linear_gen) # save position self._position = self._position + circle_end + linear_delta @@ -227,9 +243,10 @@ def home(self): """ Move head to park position """ d = Coordinates(0, 0, -self._position.z, 0) - self._move_linear(d, STEPPER_MAX_VELOCITY_MM_PER_MIN) + self._move_linear(d, MAX_VELOCITY_MM_PER_MIN_Z) d = Coordinates(-self._position.x, -self._position.y, 0, 0) - self._move_linear(d, STEPPER_MAX_VELOCITY_MM_PER_MIN) + self._move_linear(d, min(MAX_VELOCITY_MM_PER_MIN_X, + MAX_VELOCITY_MM_PER_MIN_Y)) def position(self): """ Return current machine position (after the latest command) @@ -294,17 +311,37 @@ def do_command(self, gcode): self._convertCoordinates) # coord = self._position + delta velocity = gcode.get('F', self._velocity) - pause = gcode.get('P', self._pause) radius = gcode.radius(Coordinates(0.0, 0.0, 0.0, 0.0), self._convertCoordinates) # check parameters - if velocity <= 0 or velocity > STEPPER_MAX_VELOCITY_MM_PER_MIN: - raise GMachineException("bad feed speed") - if pause < 0: - raise GMachineException("bad delay") + if velocity <= 0: + raise GMachineException("negative feed speed") # select command and run it if c == 'G0': # rapid move - self._move_linear(delta, STEPPER_MAX_VELOCITY_MM_PER_MIN) + vl = max(MAX_VELOCITY_MM_PER_MIN_X, + MAX_VELOCITY_MM_PER_MIN_Y, + MAX_VELOCITY_MM_PER_MIN_Z, + MAX_VELOCITY_MM_PER_MIN_E) + l = delta.length() + if l > 0: + proportion = abs(delta) / l + if proportion.x > 0: + v = MAX_VELOCITY_MM_PER_MIN_X / proportion.x + if v < vl: + vl = v + if proportion.y > 0: + v = MAX_VELOCITY_MM_PER_MIN_Y / proportion.y + if v < vl: + vl = v + if proportion.z > 0: + v = MAX_VELOCITY_MM_PER_MIN_Z / proportion.z + if v < vl: + vl = v + if proportion.e > 0: + v = MAX_VELOCITY_MM_PER_MIN_E / proportion.e + if v < vl: + vl = v + self._move_linear(delta, vl) elif c == 'G1': # linear interpolation self._move_linear(delta, velocity) elif c == 'G2': # circular interpolation, clockwise @@ -312,6 +349,11 @@ def do_command(self, gcode): elif c == 'G3': # circular interpolation, counterclockwise self._circular(delta, radius, velocity, CCW) elif c == 'G4': # delay in s + if not gcode.has('P'): + raise GMachineException("P is not specified") + pause = gcode.get('P', 0) + if pause < 0: + raise GMachineException("bad delay") hal.join() time.sleep(pause) elif c == 'G17': # XY plane select @@ -394,6 +436,5 @@ def do_command(self, gcode): raise GMachineException("unknown command") # save parameters on success self._velocity = velocity - self._pause = pause logging.debug("position {}".format(self._position)) return answer diff --git a/cnc/hal_raspberry/hal.py b/cnc/hal_raspberry/hal.py index 67304e7..f3aea74 100644 --- a/cnc/hal_raspberry/hal.py +++ b/cnc/hal_raspberry/hal.py @@ -42,6 +42,8 @@ def init(): gpio.clear(BED_HEATER_PIN) # calibration + # TODO remove this from hal and rewrite, check if there is a special g + # command for this gpio.set(STEPPER_DIR_PIN_X) gpio.set(STEPPER_DIR_PIN_Y) gpio.set(STEPPER_DIR_PIN_Z) @@ -73,7 +75,10 @@ def init(): break dma.run(False) # limit velocity at ~10% of top velocity - time.sleep((1 / 0.10) / (STEPPER_MAX_VELOCITY_MM_PER_MIN + time.sleep((1 / 0.10) / (min(MAX_VELOCITY_MM_PER_MIN_X, + MAX_VELOCITY_MM_PER_MIN_Y, + MAX_VELOCITY_MM_PER_MIN_Z, + MAX_VELOCITY_MM_PER_MIN_E) / 60 * max(STEPPER_PULSES_PER_MM_X, STEPPER_PULSES_PER_MM_Y, STEPPER_PULSES_PER_MM_Z))) diff --git a/cnc/hal_virtual.py b/cnc/hal_virtual.py index f8f0065..0f1ed34 100644 --- a/cnc/hal_virtual.py +++ b/cnc/hal_virtual.py @@ -74,22 +74,30 @@ def move(generator): dx, dy, dz, de = 0, 0, 0, 0 mx, my, mz, me = 0, 0, 0, 0 cx, cy, cz, ce = 0, 0, 0, 0 - direction_x, direction_y, direction_z, dire = 1, 1, 1, 1 + direction_x, direction_y, direction_z, direction_e = 1, 1, 1, 1 st = time.time() direction_found = False for direction, tx, ty, tz, te in generator: if direction: direction_found = True - direction_x, direction_y, direction_z, dire = tx, ty, tz, te + direction_x, direction_y, direction_z, direction_e = tx, ty, tz, te + if STEPPER_INVERTED_X: + direction_x = -direction_x + if STEPPER_INVERTED_Y: + direction_y = -direction_y + if STEPPER_INVERTED_Z: + direction_z = -direction_z + if STEPPER_INVERTED_E: + direction_e = -direction_e if isinstance(generator, PulseGeneratorLinear): - assert ((tx < 0 and delta.x < 0) or (tx > 0 and delta.x > 0) - or delta.x == 0) - assert ((ty < 0 and delta.y < 0) or (ty > 0 and delta.y > 0) - or delta.y == 0) - assert ((tz < 0 and delta.z < 0) or (tz > 0 and delta.z > 0) - or delta.z == 0) - assert ((te < 0 and delta.e < 0) or (te > 0 and delta.e > 0) - or delta.e == 0) + assert ((direction_x < 0 and delta.x < 0) + or (direction_x > 0 and delta.x > 0) or delta.x == 0) + assert ((direction_y < 0 and delta.y < 0) + or (direction_y > 0 and delta.y > 0) or delta.y == 0) + assert ((direction_z < 0 and delta.z < 0) + or (direction_z > 0 and delta.z > 0) or delta.z == 0) + assert ((direction_e < 0 and delta.e < 0) + or (direction_e > 0 and delta.e > 0) or delta.e == 0) continue if tx is not None: if tx > mx: @@ -131,7 +139,7 @@ def move(generator): if te > me: me = te te = int(round(te * 1000000)) - ie += dire + ie += direction_e ce += 1 if le is not None: de = te - le diff --git a/cnc/pulses.py b/cnc/pulses.py index 039fc66..9aefc49 100644 --- a/cnc/pulses.py +++ b/cnc/pulses.py @@ -86,7 +86,7 @@ def __iter__(self): (self._acceleration_time_s, self._linear_time_s, max_axis_velocity_mm_per_sec) = self._get_movement_parameters() # helper variable - self._2Vmax_per_a = (2.0 * max_axis_velocity_mm_per_sec + self._2Vmax_per_a = (2.0 * max_axis_velocity_mm_per_sec.find_max() / STEPPER_MAX_ACCELERATION_MM_PER_S2) self._iteration_x = 0 self._iteration_y = 0 @@ -152,7 +152,16 @@ def next(self): # check if direction update: if direction != self._iteration_direction: self._iteration_direction = direction - return (True,) + direction + dir_x, dir_y, dir_z, dir_e = direction + if STEPPER_INVERTED_X: + dir_x = -dir_x + if STEPPER_INVERTED_Y: + dir_y = -dir_y + if STEPPER_INVERTED_Z: + dir_z = -dir_z + if STEPPER_INVERTED_E: + dir_e = -dir_e + return True, dir_x, dir_y, dir_z, dir_e # check condition to stop if tx is None and ty is None and tz is None and te is None: raise StopIteration @@ -204,6 +213,13 @@ def delta(self): """ return self._delta + def max_velocity(self): + """ Get max velocity for each axis. + :return: Vector with max velocity(in mm per min) for each axis. + """ + _, _, v = self._get_movement_parameters() + return v * SECONDS_IN_MINUTE + class PulseGeneratorLinear(PulseGenerator): def __init__(self, delta_mm, velocity_mm_per_min): @@ -253,7 +269,7 @@ def _get_movement_parameters(self): """ return (self.acceleration_time_s, self.linear_time_s, - self.max_velocity_mm_per_sec.find_max()) + self.max_velocity_mm_per_sec) @staticmethod def __linear(i, pulses_per_mm, total_pulses, velocity_mm_per_sec): @@ -431,8 +447,10 @@ def __init__(self, delta, radius, plane, direction, velocity): self._iterations_a += round(radius * apm) self._iterations_b += round(radius * bpm) if direction == CCW: - self._iterations_a = 4 * round(radius * apm) - self._iterations_a - self._iterations_b = 4 * round(radius * bpm) - self._iterations_b + self._iterations_a = (4 * round(radius * apm) + - self._iterations_a) + self._iterations_b = (4 * round(radius * bpm) + - self._iterations_b) arc = delta_angle * radius e2 = delta.e * delta.e @@ -472,19 +490,33 @@ def __init__(self, delta, radius, plane, direction, velocity): self._e_velocity = abs(delta.e) / l * velocity self._r_div_v = radius / circular_velocity self._e_dir = math.copysign(1, delta.e) - self.max_velocity_mm_per_sec = max(circular_velocity, - self._velocity_3rd, self._e_velocity) - self.acceleration_time_s = (self.max_velocity_mm_per_sec + if self._plane == PLANE_XY: + self.max_velocity_mm_per_sec = Coordinates(circular_velocity, + circular_velocity, + self._velocity_3rd, + self._e_velocity) + elif self._plane == PLANE_YZ: + self.max_velocity_mm_per_sec = Coordinates(self._velocity_3rd, + circular_velocity, + circular_velocity, + self._e_velocity) + elif self._plane == PLANE_ZX: + self.max_velocity_mm_per_sec = Coordinates(circular_velocity, + self._velocity_3rd, + circular_velocity, + self._e_velocity) + self.acceleration_time_s = (self.max_velocity_mm_per_sec.find_max() / STEPPER_MAX_ACCELERATION_MM_PER_S2) if l == 0: self.linear_time_s = 0.0 - self.max_velocity_mm_per_sec = 0 + self.max_velocity_mm_per_sec = Coordinates(0, 0, 0, 0) elif STEPPER_MAX_ACCELERATION_MM_PER_S2 * self.acceleration_time_s \ ** 2 > l: self.acceleration_time_s = \ math.sqrt(l / STEPPER_MAX_ACCELERATION_MM_PER_S2) self.linear_time_s = 0.0 - self.max_velocity_mm_per_sec = l / self.acceleration_time_s + v = l / self.acceleration_time_s + self.max_velocity_mm_per_sec = Coordinates(v, v, v, v) else: linear_distance_mm = l - self.acceleration_time_s ** 2 \ * STEPPER_MAX_ACCELERATION_MM_PER_S2 diff --git a/tests/circles.gcode b/tests/circles.gcode index aae03dc..436dc50 100644 --- a/tests/circles.gcode +++ b/tests/circles.gcode @@ -7,10 +7,11 @@ G3 X110 Y110 I-5 J-5 ; full circle in one move G2 X90 Y90 I-10 J-10 ; second half of circle G2 X90 Y70 I-10 J-10 ; quarter of circle G2 X90 Y90 I-10 J10 ; three quoter circle -G3 X90 Y90 Z 20 I-10 J-10 ; spiral +G3 X90 Y90 Z 20 I-10 J-10 F1000 ; spiral +f1800 G2 X92.07 Y85 I-5 J-5 ; small arc G2 X90 Y90 I-7.07 J0; more then 270 degree arc G18 -G2 X90 Y90 K-5 +G2 X90 Y90 K-5 F120 G19 -G2 X90 Y90 K-5 +G2 X90 Y90 K-5 F120 diff --git a/tests/rects.gcode b/tests/rects.gcode index 5f7ab1b..fe5e2b1 100644 --- a/tests/rects.gcode +++ b/tests/rects.gcode @@ -2,7 +2,8 @@ g21 g90 ; move to start position g1x50y50f1800 -z20 +z20f120 +f1800 g91 ; run x100 diff --git a/tests/test_gmachine.py b/tests/test_gmachine.py index 2a02e97..8a79971 100644 --- a/tests/test_gmachine.py +++ b/tests/test_gmachine.py @@ -63,7 +63,7 @@ def test_g0_g1(self): self.assertRaises(GMachineException, m.do_command, GCode.parse_line("G1F-1")) self.assertRaises(GMachineException, - m.do_command, GCode.parse_line("G1F999999")) + m.do_command, GCode.parse_line("G1X100F999999")) self.assertRaises(GMachineException, m.do_command, GCode.parse_line("G1X-1Y0Z0")) self.assertRaises(GMachineException, @@ -71,6 +71,27 @@ def test_g0_g1(self): self.assertRaises(GMachineException, m.do_command, GCode.parse_line("G1X0Y0Z-1")) + def test_feed_rate(self): + m = GMachine() + self.assertRaises(GMachineException, + m.do_command, GCode.parse_line("G1X1F-1")) + m.do_command(GCode.parse_line("G1X100F" + + str(MAX_VELOCITY_MM_PER_MIN_X))) + m.do_command(GCode.parse_line("G1Y100F" + + str(MAX_VELOCITY_MM_PER_MIN_Y))) + m.do_command(GCode.parse_line("G1Z100F" + + str(MAX_VELOCITY_MM_PER_MIN_Z))) + m.do_command(GCode.parse_line("G1E100F" + + str(MAX_VELOCITY_MM_PER_MIN_E))) + s = "G1X0F" + str(MAX_VELOCITY_MM_PER_MIN_X + 1) + self.assertRaises(GMachineException, m.do_command, GCode.parse_line(s)) + s = "G1Y0F" + str(MAX_VELOCITY_MM_PER_MIN_Y + 1) + self.assertRaises(GMachineException, m.do_command, GCode.parse_line(s)) + s = "G1Z0F" + str(MAX_VELOCITY_MM_PER_MIN_Z + 1) + self.assertRaises(GMachineException, m.do_command, GCode.parse_line(s)) + s = "G1E0F" + str(MAX_VELOCITY_MM_PER_MIN_E + 1) + self.assertRaises(GMachineException, m.do_command, GCode.parse_line(s)) + def test_g2_g3(self): m = GMachine() self.assertRaises(GMachineException, diff --git a/tests/test_parser.gcode b/tests/test_parser.gcode index 7b4098c..83efe14 100644 --- a/tests/test_parser.gcode +++ b/tests/test_parser.gcode @@ -21,8 +21,8 @@ g21 g1y1 g1y-1 g90 -g92x100y100z100 +g92x100y100z100f240 m111 -g1x98y98z98 -(head should be in zero position, and last movement with 500 mm/min velocity) +g1x98y98z98f120 +(head should be in zero position) m2 diff --git a/tests/test_pulses.py b/tests/test_pulses.py index e146633..ee82e93 100644 --- a/tests/test_pulses.py +++ b/tests/test_pulses.py @@ -8,7 +8,10 @@ class TestPulses(unittest.TestCase): def setUp(self): - self.v = STEPPER_MAX_VELOCITY_MM_PER_MIN + self.v = min(MAX_VELOCITY_MM_PER_MIN_X, + MAX_VELOCITY_MM_PER_MIN_Y, + MAX_VELOCITY_MM_PER_MIN_Z, + MAX_VELOCITY_MM_PER_MIN_E) def tearDown(self): pass @@ -70,6 +73,14 @@ def __check_circular(self, delta, radius, plane, direction=CW): for direction_i, px, py, pz, pe in g: if direction_i: dx, dy, dz, de = px, py, pz, pe + if STEPPER_INVERTED_X: + dx = -dx + if STEPPER_INVERTED_Y: + dy = -dy + if STEPPER_INVERTED_Z: + dz = -dz + if STEPPER_INVERTED_E: + de = -de dir_requested = True continue if dir_requested: # ignore last change @@ -235,7 +246,8 @@ def test_acceleration_velocity(self): # is correct, since PulseGenerator is responsible for this, check only # one child class. m = Coordinates(TABLE_SIZE_X_MM, 0, 0, 0) - g = PulseGeneratorLinear(m, self.v) + velocity = 1000 + g = PulseGeneratorLinear(m, velocity) i = 0 lx = 0 lt, at, bt = None, None, None @@ -249,8 +261,7 @@ def test_acceleration_velocity(self): bt = px - lx lx = px i += 1 - self.assertEqual(round(60.0 / lt / STEPPER_PULSES_PER_MM_X), - round(self.v)) + self.assertEqual(round(60.0 / lt / STEPPER_PULSES_PER_MM_X), velocity) self.assertGreater(at, lt) self.assertGreater(bt, lt) @@ -261,6 +272,14 @@ def test_directions(self): dir_found = False for direction, px, py, pz, pe in g: if direction: + if STEPPER_INVERTED_X: + px = -px + if STEPPER_INVERTED_Y: + py = -py + if STEPPER_INVERTED_Z: + pz = -pz + if STEPPER_INVERTED_E: + pe = -pe # should be once self.assertFalse(dir_found) dir_found = True @@ -271,6 +290,14 @@ def test_directions(self): dir_found = False for direction, px, py, pz, pe in g: if direction: + if STEPPER_INVERTED_X: + px = -px + if STEPPER_INVERTED_Y: + py = -py + if STEPPER_INVERTED_Z: + pz = -pz + if STEPPER_INVERTED_E: + pe = -pe # should be once self.assertFalse(dir_found) dir_found = True