From 2bd03b97f0d6be93893a2b8f2f50770f6d1df00b Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 29 May 2021 15:09:43 -0400 Subject: [PATCH 01/51] docs: Fix malformed link in beaglebone.md Signed-off-by: Kevin O'Connor --- docs/beaglebone.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/beaglebone.md b/docs/beaglebone.md index 114dbf6a..3c6c8e0c 100644 --- a/docs/beaglebone.md +++ b/docs/beaglebone.md @@ -5,11 +5,10 @@ Building an OS image ==================== Start by installing the -[Debian 9.9 2019-08-03 4GB SD IoT] -(https://beagleboard.org/latest-images) image. -One may run the image from either a micro-SD card or from -builtin eMMC. If using the eMMC, install it to eMMC now by -following the instructions from the above link. +[Debian 9.9 2019-08-03 4GB SD IoT](https://beagleboard.org/latest-images) +image. One may run the image from either a micro-SD card or from +builtin eMMC. If using the eMMC, install it to eMMC now by following +the instructions from the above link. Then ssh into the beaglebone machine (ssh debian@beaglebone -- password is "temppwd") and install Klipper by running the following From 77bc5e438851c8a8a8f160fd112e43f460a54241 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 29 May 2021 21:29:00 -0400 Subject: [PATCH 02/51] stepper: Improve get_past_commanded_position() Pass a print_time instead of a clock to get_past_commanded_position(). Fix calculation on steppers that home and use an inverted direction pin. Signed-off-by: Kevin O'Connor --- klippy/kinematics/extruder.py | 4 +--- klippy/stepper.py | 12 +++++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/klippy/kinematics/extruder.py b/klippy/kinematics/extruder.py index ea5161a9..6f2138ae 100644 --- a/klippy/kinematics/extruder.py +++ b/klippy/kinematics/extruder.py @@ -152,9 +152,7 @@ class PrinterExtruder: 1., pressure_advance, 0., start_v, cruise_v, accel) def find_past_position(self, print_time): - mcu = self.stepper.get_mcu() - clock = mcu.print_time_to_clock(print_time) - return self.stepper.get_past_commanded_position(clock) + return self.stepper.get_past_commanded_position(print_time) def cmd_M104(self, gcmd, wait=False): # Set Extruder Temperature temp = gcmd.get_float('S', 0.) diff --git a/klippy/stepper.py b/klippy/stepper.py index ab981e96..d6800a94 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -112,10 +112,12 @@ class MCU_stepper: return self._tag_position def set_tag_position(self, position): self._tag_position = position - def get_past_commanded_position(self, clock): + def get_past_mcu_position(self, print_time): + clock = self._mcu.print_time_to_clock(print_time) ffi_main, ffi_lib = chelper.get_ffi() - sq = self._stepqueue - mcu_pos = ffi_lib.stepcompress_find_past_position(sq, clock) + return ffi_lib.stepcompress_find_past_position(self._stepqueue, clock) + def get_past_commanded_position(self, print_time): + mcu_pos = self.get_past_mcu_position(print_time) return mcu_pos * self._step_dist - self._mcu_position_offset def set_stepper_kinematics(self, sk): old_sk = self._stepper_kinematics @@ -139,12 +141,12 @@ class MCU_stepper: return params = self._get_position_cmd.send([self._oid]) last_pos = params['pos'] + if self._invert_dir: + last_pos = -last_pos ret = ffi_lib.stepcompress_set_last_position(self._stepqueue, last_pos) if ret: raise error("Internal error in stepcompress") mcu_pos_dist = last_pos * self._step_dist - if self._invert_dir: - mcu_pos_dist = -mcu_pos_dist self._mcu_position_offset = mcu_pos_dist - self.get_commanded_position() def set_trapq(self, tq): ffi_main, ffi_lib = chelper.get_ffi() From c0d860487a70a05d44973d53a981e935ce7b3ad0 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 1 May 2021 00:27:43 -0400 Subject: [PATCH 03/51] stepper: Remove set_tag_position() code Have callers store the stepper positions in a dict. Signed-off-by: Kevin O'Connor --- docs/Code_Overview.md | 11 +++++------ klippy/extras/delta_calibrate.py | 6 +++--- klippy/extras/endstop_phase.py | 17 ++++++++--------- klippy/extras/gcode_move.py | 10 ++++------ klippy/extras/homing.py | 25 +++++++++++++++---------- klippy/extras/manual_probe.py | 6 +++--- klippy/kinematics/cartesian.py | 4 ++-- klippy/kinematics/corexy.py | 4 ++-- klippy/kinematics/corexz.py | 4 ++-- klippy/kinematics/delta.py | 4 ++-- klippy/kinematics/hybrid_corexy.py | 4 ++-- klippy/kinematics/hybrid_corexz.py | 4 ++-- klippy/kinematics/none.py | 2 +- klippy/kinematics/polar.py | 8 ++++---- klippy/kinematics/rotary_delta.py | 4 ++-- klippy/kinematics/winch.py | 6 +++--- klippy/stepper.py | 9 ++------- 17 files changed, 62 insertions(+), 66 deletions(-) diff --git a/docs/Code_Overview.md b/docs/Code_Overview.md index f7d14463..167b7c18 100644 --- a/docs/Code_Overview.md +++ b/docs/Code_Overview.md @@ -331,12 +331,11 @@ Useful steps: seconds) to a cartesian coordinate (in millimeters), and then calculate the desired stepper position (in millimeters) from that cartesian coordinate. -4. Implement the `calc_tag_position()` method in the new kinematics - class. This method calculates the position of the toolhead in - cartesian coordinates from the position of each stepper (as - returned by `stepper.get_tag_position()`). It does not need to be - efficient as it is typically only called during homing and probing - operations. +4. Implement the `calc_position()` method in the new kinematics class. + This method calculates the position of the toolhead in cartesian + coordinates from the position of each stepper. It does not need to + be efficient as it is typically only called during homing and + probing operations. 5. Other methods. Implement the `check_move()`, `get_status()`, `get_steppers()`, `home()`, and `set_position()` methods. These functions are typically used to provide kinematic specific checks. diff --git a/klippy/extras/delta_calibrate.py b/klippy/extras/delta_calibrate.py index a2e0d361..4301c89a 100644 --- a/klippy/extras/delta_calibrate.py +++ b/klippy/extras/delta_calibrate.py @@ -233,9 +233,9 @@ class DeltaCalibrate: toolhead = self.printer.lookup_object('toolhead') toolhead.flush_step_generation() kin = toolhead.get_kinematics() - for s in kin.get_steppers(): - s.set_tag_position(s.get_commanded_position()) - kin_pos = kin.calc_tag_position() + kin_spos = {s.get_name(): s.get_commanded_position() + for s in kin.get_steppers()} + kin_pos = kin.calc_position(kin_spos) # Convert location to a stable position delta_params = kin.get_calibration() stable_pos = tuple(delta_params.calc_stable_position(kin_pos)) diff --git a/klippy/extras/endstop_phase.py b/klippy/extras/endstop_phase.py index 8e70be74..a2b2b548 100644 --- a/klippy/extras/endstop_phase.py +++ b/klippy/extras/endstop_phase.py @@ -102,17 +102,16 @@ class EndstopPhase: self.name, phase, self.endstop_phase)) return delta * self.step_dist def handle_home_rails_end(self, homing_state, rails): + kin_spos = homing_state.get_stepper_trigger_positions() + orig_pos = kin_spos.get(self.name) + if orig_pos is None: + return for rail in rails: stepper = rail.get_steppers()[0] - if stepper.get_name() != self.name: - continue - orig_pos = rail.get_tag_position() - offset = self.get_homed_offset(stepper) - pos = self.align_endstop(orig_pos) + offset - if pos == orig_pos: - return False - rail.set_tag_position(pos) - return True + if stepper.get_name() == self.name: + offset = self.get_homed_offset(stepper) + kin_spos[self.name] = self.align_endstop(orig_pos) + offset + return class EndstopPhases: def __init__(self, config): diff --git a/klippy/extras/gcode_move.py b/klippy/extras/gcode_move.py index 306b4b35..77087162 100644 --- a/klippy/extras/gcode_move.py +++ b/klippy/extras/gcode_move.py @@ -247,12 +247,10 @@ class GCodeMove: steppers = kin.get_steppers() mcu_pos = " ".join(["%s:%d" % (s.get_name(), s.get_mcu_position()) for s in steppers]) - for s in steppers: - s.set_tag_position(s.get_commanded_position()) - stepper_pos = " ".join(["%s:%.6f" % (s.get_name(), s.get_tag_position()) - for s in steppers]) - kin_pos = " ".join(["%s:%.6f" % (a, v) - for a, v in zip("XYZ", kin.calc_tag_position())]) + cinfo = [(s.get_name(), s.get_commanded_position()) for s in steppers] + stepper_pos = " ".join(["%s:%.6f" % (a, v) for a, v in cinfo]) + kinfo = zip("XYZ", kin.calc_position(dict(cinfo))) + kin_pos = " ".join(["%s:%.6f" % (a, v) for a, v in kinfo]) toolhead_pos = " ".join(["%s:%.6f" % (a, v) for a, v in zip( "XYZE", toolhead.get_position())]) gcode_pos = " ".join(["%s:%.6f" % (a, v) diff --git a/klippy/extras/homing.py b/klippy/extras/homing.py index a1167e2e..f6dead84 100644 --- a/klippy/extras/homing.py +++ b/klippy/extras/homing.py @@ -46,8 +46,8 @@ class HomingMove: # Note start location self.toolhead.flush_step_generation() kin = self.toolhead.get_kinematics() - for s in kin.get_steppers(): - s.set_tag_position(s.get_commanded_position()) + kin_spos = {s.get_name(): s.get_commanded_position() + for s in kin.get_steppers()} start_mcu_pos = [(s, name, s.get_mcu_position()) for es, name in self.endstops for s in es.get_steppers()] @@ -80,9 +80,10 @@ class HomingMove: for s, name, spos in start_mcu_pos] if probe_pos: for s, name, spos, epos in self.end_mcu_pos: - md = (epos - spos) * s.get_step_dist() - s.set_tag_position(s.get_tag_position() + md) - movepos = list(kin.calc_tag_position())[:3] + movepos[3:] + sname = s.get_name() + if sname in kin_spos: + kin_spos[sname] += (epos - spos) * s.get_step_dist() + movepos = list(kin.calc_position(kin_spos))[:3] + movepos[3:] self.toolhead.set_position(movepos) # Signal homing/probing move complete try: @@ -107,10 +108,13 @@ class Homing: self.printer = printer self.toolhead = printer.lookup_object('toolhead') self.changed_axes = [] + self.kin_spos = {} def set_axes(self, axes): self.changed_axes = axes def get_axes(self): return self.changed_axes + def get_stepper_trigger_positions(self): + return self.kin_spos def _fill_coord(self, coord): # Fill in any None entries in 'coord' with current toolhead position thcoord = list(self.toolhead.get_position()) @@ -155,12 +159,13 @@ class Homing: # Signal home operation complete self.toolhead.flush_step_generation() kin = self.toolhead.get_kinematics() - for s in kin.get_steppers(): - s.set_tag_position(s.get_commanded_position()) - ret = self.printer.send_event("homing:home_rails_end", self, rails) - if any(ret): + kin_spos = {s.get_name(): s.get_commanded_position() + for s in kin.get_steppers()} + self.kin_spos = dict(kin_spos) + self.printer.send_event("homing:home_rails_end", self, rails) + if kin_spos != self.kin_spos: # Apply any homing offsets - adjustpos = kin.calc_tag_position() + adjustpos = kin.calc_position(self.kin_spos) for axis in homing_axes: movepos[axis] = adjustpos[axis] self.toolhead.set_position(movepos) diff --git a/klippy/extras/manual_probe.py b/klippy/extras/manual_probe.py index 69ce79e9..83c9aa2f 100644 --- a/klippy/extras/manual_probe.py +++ b/klippy/extras/manual_probe.py @@ -82,9 +82,9 @@ class ManualProbeHelper: return self.last_kinematics_pos self.toolhead.flush_step_generation() kin = self.toolhead.get_kinematics() - for s in kin.get_steppers(): - s.set_tag_position(s.get_commanded_position()) - kin_pos = kin.calc_tag_position() + kin_spos = {s.get_name(): s.get_commanded_position() + for s in kin.get_steppers()} + kin_pos = kin.calc_position(kin_spos) self.last_toolhead_pos = toolhead_pos self.last_kinematics_pos = kin_pos return kin_pos diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index e2c21900..6c44c120 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -51,8 +51,8 @@ class CartKinematics: dca = self.dual_carriage_axis rails = rails[:dca] + self.dual_carriage_rails + rails[dca+1:] return [s for rail in rails for s in rail.get_steppers()] - def calc_tag_position(self): - return [rail.get_tag_position() for rail in self.rails] + def calc_position(self, stepper_positions): + return [stepper_positions[rail.get_name()] for rail in self.rails] def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): rail.set_position(newpos) diff --git a/klippy/kinematics/corexy.py b/klippy/kinematics/corexy.py index 451a88aa..33f5fcf5 100644 --- a/klippy/kinematics/corexy.py +++ b/klippy/kinematics/corexy.py @@ -36,8 +36,8 @@ class CoreXYKinematics: self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.) def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] - def calc_tag_position(self): - pos = [rail.get_tag_position() for rail in self.rails] + def calc_position(self, stepper_positions): + pos = [stepper_positions[rail.get_name()] for rail in self.rails] return [0.5 * (pos[0] + pos[1]), 0.5 * (pos[0] - pos[1]), pos[2]] def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): diff --git a/klippy/kinematics/corexz.py b/klippy/kinematics/corexz.py index 698e4c08..dbd5c372 100644 --- a/klippy/kinematics/corexz.py +++ b/klippy/kinematics/corexz.py @@ -36,8 +36,8 @@ class CoreXZKinematics: self.axes_max = toolhead.Coord(*[r[1] for r in ranges], e=0.) def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] - def calc_tag_position(self): - pos = [rail.get_tag_position() for rail in self.rails] + def calc_position(self, stepper_positions): + pos = [stepper_positions[rail.get_name()] for rail in self.rails] return [0.5 * (pos[0] + pos[2]), pos[1], 0.5 * (pos[0] - pos[2])] def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): diff --git a/klippy/kinematics/delta.py b/klippy/kinematics/delta.py index 0d17ed19..104d2122 100644 --- a/klippy/kinematics/delta.py +++ b/klippy/kinematics/delta.py @@ -92,8 +92,8 @@ class DeltaKinematics: def _actuator_to_cartesian(self, spos): sphere_coords = [(t[0], t[1], sp) for t, sp in zip(self.towers, spos)] return mathutil.trilateration(sphere_coords, self.arm2) - def calc_tag_position(self): - spos = [rail.get_tag_position() for rail in self.rails] + def calc_position(self, stepper_positions): + spos = [stepper_positions[rail.get_name()] for rail in self.rails] return self._actuator_to_cartesian(spos) def set_position(self, newpos, homing_axes): for rail in self.rails: diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py index 74657e25..43cf7dd9 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -37,8 +37,8 @@ class HybridCoreXYKinematics: self.limits = [(1.0, -1.0)] * 3 def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] - def calc_tag_position(self): - pos = [rail.get_tag_position() for rail in self.rails] + def calc_position(self, stepper_positions): + pos = [stepper_positions[rail.get_name()] for rail in self.rails] return [pos[0] + pos[1], pos[1], pos[2]] def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py index 85f81ce9..47aa430e 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -37,8 +37,8 @@ class HybridCoreXZKinematics: self.limits = [(1.0, -1.0)] * 3 def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] - def calc_tag_position(self): - pos = [rail.get_tag_position() for rail in self.rails] + def calc_position(self, stepper_positions): + pos = [stepper_positions[rail.get_name()] for rail in self.rails] return [pos[0] + pos[2], pos[1], pos[2]] def set_position(self, newpos, homing_axes): for i, rail in enumerate(self.rails): diff --git a/klippy/kinematics/none.py b/klippy/kinematics/none.py index fdc3d4c4..ff3c57a9 100644 --- a/klippy/kinematics/none.py +++ b/klippy/kinematics/none.py @@ -9,7 +9,7 @@ class NoneKinematics: self.axes_minmax = toolhead.Coord(0., 0., 0., 0.) def get_steppers(self): return [] - def calc_tag_position(self): + def calc_position(self, stepper_positions): return [0, 0, 0] def set_position(self, newpos, homing_axes): pass diff --git a/klippy/kinematics/polar.py b/klippy/kinematics/polar.py index baa3a11c..ef8c0d97 100644 --- a/klippy/kinematics/polar.py +++ b/klippy/kinematics/polar.py @@ -38,10 +38,10 @@ class PolarKinematics: self.axes_max = toolhead.Coord(max_xy, max_xy, max_z, 0.) def get_steppers(self): return list(self.steppers) - def calc_tag_position(self): - bed_angle = self.steppers[0].get_tag_position() - arm_pos = self.rails[0].get_tag_position() - z_pos = self.rails[1].get_tag_position() + def calc_position(self, stepper_positions): + bed_angle = stepper_positions[self.steppers[0].get_name()] + arm_pos = stepper_positions[self.rails[0].get_name()] + z_pos = stepper_positions[self.rails[1].get_name()] return [math.cos(bed_angle) * arm_pos, math.sin(bed_angle) * arm_pos, z_pos] def set_position(self, newpos, homing_axes): diff --git a/klippy/kinematics/rotary_delta.py b/klippy/kinematics/rotary_delta.py index f61ed34a..1eb050ba 100644 --- a/klippy/kinematics/rotary_delta.py +++ b/klippy/kinematics/rotary_delta.py @@ -79,8 +79,8 @@ class RotaryDeltaKinematics: self.set_position([0., 0., 0.], ()) def get_steppers(self): return [s for rail in self.rails for s in rail.get_steppers()] - def calc_tag_position(self): - spos = [rail.get_tag_position() for rail in self.rails] + def calc_position(self, stepper_positions): + spos = [stepper_positions[rail.get_name()] for rail in self.rails] return self.calibration.actuator_to_cartesian(spos) def set_position(self, newpos, homing_axes): for rail in self.rails: diff --git a/klippy/kinematics/winch.py b/klippy/kinematics/winch.py index 58737b58..11475d24 100644 --- a/klippy/kinematics/winch.py +++ b/klippy/kinematics/winch.py @@ -29,10 +29,10 @@ class WinchKinematics: self.set_position([0., 0., 0.], ()) def get_steppers(self): return list(self.steppers) - def calc_tag_position(self): + def calc_position(self, stepper_positions): # Use only first three steppers to calculate cartesian position - spos = [s.get_tag_position() for s in self.steppers[:3]] - return mathutil.trilateration(self.anchors[:3], [sp*sp for sp in spos]) + pos = [stepper_positions[rail.get_name()] for rail in self.steppers[:3]] + return mathutil.trilateration(self.anchors[:3], [sp*sp for sp in pos]) def set_position(self, newpos, homing_axes): for s in self.steppers: s.set_position(newpos) diff --git a/klippy/stepper.py b/klippy/stepper.py index d6800a94..9b7263c1 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -31,7 +31,7 @@ class MCU_stepper: "Stepper dir pin must be on same mcu as step pin") self._dir_pin = dir_pin_params['pin'] self._invert_dir = dir_pin_params['invert'] - self._mcu_position_offset = self._tag_position = 0. + self._mcu_position_offset = 0. self._reset_cmd_tag = self._get_position_cmd = None self._active_callbacks = [] ffi_main, ffi_lib = chelper.get_ffi() @@ -108,10 +108,6 @@ class MCU_stepper: if mcu_pos >= 0.: return int(mcu_pos + 0.5) return int(mcu_pos - 0.5) - def get_tag_position(self): - return self._tag_position - def set_tag_position(self, position): - self._tag_position = position def get_past_mcu_position(self, print_time): clock = self._mcu.print_time_to_clock(print_time) ffi_main, ffi_lib = chelper.get_ffi() @@ -258,9 +254,8 @@ class PrinterRail: self.endstops = [] self.add_extra_stepper(config) mcu_stepper = self.steppers[0] + self.get_name = mcu_stepper.get_name self.get_commanded_position = mcu_stepper.get_commanded_position - self.get_tag_position = mcu_stepper.get_tag_position - self.set_tag_position = mcu_stepper.set_tag_position self.calc_position_from_coord = mcu_stepper.calc_position_from_coord # Primary endstop position mcu_endstop = self.endstops[0][0] From 55be26097f6a287a73db523f4d11556add8970e6 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 1 Jun 2021 11:28:07 -0400 Subject: [PATCH 04/51] config: Minor indentation changes to skr configs Signed-off-by: Kevin O'Connor --- config/generic-bigtreetech-skr-2.cfg | 2 +- config/generic-bigtreetech-skr-mini-e3-v2.0.cfg | 2 +- config/generic-bigtreetech-skr-mini-mz.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/generic-bigtreetech-skr-2.cfg b/config/generic-bigtreetech-skr-2.cfg index fae82a15..218badea 100644 --- a/config/generic-bigtreetech-skr-2.cfg +++ b/config/generic-bigtreetech-skr-2.cfg @@ -104,7 +104,7 @@ aliases: EXP1_1=PC5, EXP1_3=PB1, EXP1_5=PE10, EXP1_7=PE12, EXP1_9=, EXP1_2=PB0, EXP1_4=PE9, EXP1_6=PE11, EXP1_8=PE13, EXP1_10=<5V>, # EXP2 header - EXP2_1=PA6, EXP2_3=PE7, EXP2_5=PB2, EXP2_7=PC4, EXP2_9=, + EXP2_1=PA6, EXP2_3=PE7, EXP2_5=PB2, EXP2_7=PC4, EXP2_9=, EXP2_2=PA5, EXP2_4=PA4, EXP2_6=PA7, EXP2_8=, EXP2_10= # See the sample-lcd.cfg file for definitions of common LCD displays. diff --git a/config/generic-bigtreetech-skr-mini-e3-v2.0.cfg b/config/generic-bigtreetech-skr-mini-e3-v2.0.cfg index d9212d34..848b8d52 100644 --- a/config/generic-bigtreetech-skr-mini-e3-v2.0.cfg +++ b/config/generic-bigtreetech-skr-mini-e3-v2.0.cfg @@ -126,7 +126,7 @@ pins: !PA14 [board_pins] aliases: # EXP1 header - EXP1_1=PB5, EXP1_3=PA9, EXP1_5=PA10, EXP1_7=PB8, EXP1_9=, + EXP1_1=PB5, EXP1_3=PA9, EXP1_5=PA10, EXP1_7=PB8, EXP1_9=, EXP1_2=PA15, EXP1_4=, EXP1_6=PB9, EXP1_8=PB15, EXP1_10=<5V> # See the sample-lcd.cfg file for definitions of common LCD displays. diff --git a/config/generic-bigtreetech-skr-mini-mz.cfg b/config/generic-bigtreetech-skr-mini-mz.cfg index 05d34617..e632690f 100644 --- a/config/generic-bigtreetech-skr-mini-mz.cfg +++ b/config/generic-bigtreetech-skr-mini-mz.cfg @@ -129,7 +129,7 @@ pins: !PA14 [board_pins] aliases: # EXP1 header - EXP1_1=PB5, EXP1_3=PA9, EXP1_5=PA10, EXP1_7=PB8, EXP1_9=, + EXP1_1=PB5, EXP1_3=PA9, EXP1_5=PA10, EXP1_7=PB8, EXP1_9=, EXP1_2=PA15, EXP1_4=, EXP1_6=PB9, EXP1_8=PB15, EXP1_10=<5V> # See the sample-lcd.cfg file for definitions of common LCD displays. From b5e4f3d20453b2720fbb2e21b1585637048271f8 Mon Sep 17 00:00:00 2001 From: Stefan Dej Date: Wed, 2 Jun 2021 16:45:27 +0200 Subject: [PATCH 05/51] gcode: Update gcode descriptions (#4335) Add help description to HELP Add help description to RESPOND Add help description to MEASURE_AXES_NOISE, TEST_RESONANCES and SHAPER_CALIBRATE Add help description to PAUSE, RESUME, CLEAR_PAUSE and CANCEL_PRINT Add help description to GET_POSITION Add help description to SET_RETRACTION and GET_RETRACTION Signed-off-by: Stefan Dej --- klippy/extras/firmware_retraction.py | 10 ++++++---- klippy/extras/gcode_move.py | 5 ++++- klippy/extras/pause_resume.py | 17 +++++++++++++---- klippy/extras/resonance_tester.py | 17 +++++++++++------ klippy/extras/respond.py | 4 +++- klippy/gcode.py | 1 + 6 files changed, 38 insertions(+), 16 deletions(-) diff --git a/klippy/extras/firmware_retraction.py b/klippy/extras/firmware_retraction.py index 81058257..bcecf8f2 100644 --- a/klippy/extras/firmware_retraction.py +++ b/klippy/extras/firmware_retraction.py @@ -16,8 +16,10 @@ class FirmwareRetraction: + self.unretract_extra_length) self.is_retracted = False self.gcode = self.printer.lookup_object('gcode') - self.gcode.register_command('SET_RETRACTION', self.cmd_SET_RETRACTION) - self.gcode.register_command('GET_RETRACTION', self.cmd_GET_RETRACTION) + self.gcode.register_command('SET_RETRACTION', self.cmd_SET_RETRACTION, + desc=self.cmd_SET_RETRACTION_help) + self.gcode.register_command('GET_RETRACTION', self.cmd_GET_RETRACTION, + desc=self.cmd_GET_RETRACTION_help) self.gcode.register_command('G10', self.cmd_G10) self.gcode.register_command('G11', self.cmd_G11) @@ -28,7 +30,7 @@ class FirmwareRetraction: "unretract_extra_length": self.unretract_extra_length, "unretract_speed": self.unretract_speed, } - + cmd_SET_RETRACTION_help = ("Set firmware retraction parameters") def cmd_SET_RETRACTION(self, gcmd): self.retract_length = gcmd.get_float('RETRACT_LENGTH', self.retract_length, minval=0.) @@ -41,7 +43,7 @@ class FirmwareRetraction: self.unretract_length = (self.retract_length + self.unretract_extra_length) self.is_retracted = False - + cmd_GET_RETRACTION_help = ("Report firmware retraction paramters") def cmd_GET_RETRACTION(self, gcmd): gcmd.respond_info("RETRACT_LENGTH=%.5f RETRACT_SPEED=%.5f" " UNRETRACT_EXTRA_LENGTH=%.5f UNRETRACT_SPEED=%.5f" diff --git a/klippy/extras/gcode_move.py b/klippy/extras/gcode_move.py index 77087162..ecdadc43 100644 --- a/klippy/extras/gcode_move.py +++ b/klippy/extras/gcode_move.py @@ -34,7 +34,8 @@ class GCodeMove: gcode.register_command(cmd, func, False, desc) gcode.register_command('G0', self.cmd_G1) gcode.register_command('M114', self.cmd_M114, True) - gcode.register_command('GET_POSITION', self.cmd_GET_POSITION, True) + gcode.register_command('GET_POSITION', self.cmd_GET_POSITION, True, + desc=self.cmd_GET_POSITION_help) self.Coord = gcode.Coord # G-Code coordinate manipulation self.absolute_coord = self.absolute_extrude = True @@ -239,6 +240,8 @@ class GCodeMove: speed = gcmd.get_float('MOVE_SPEED', self.speed, above=0.) self.last_position[:3] = state['last_position'][:3] self.move_with_transform(self.last_position, speed) + cmd_GET_POSITION_help = ( + "Return information on the current location of the toolhead") def cmd_GET_POSITION(self, gcmd): toolhead = self.printer.lookup_object('toolhead', None) if toolhead is None: diff --git a/klippy/extras/pause_resume.py b/klippy/extras/pause_resume.py index 0a2226b9..229bd6f4 100644 --- a/klippy/extras/pause_resume.py +++ b/klippy/extras/pause_resume.py @@ -15,10 +15,14 @@ class PauseResume: self.pause_command_sent = False self.printer.register_event_handler("klippy:connect", self.handle_connect) - self.gcode.register_command("PAUSE", self.cmd_PAUSE) - self.gcode.register_command("RESUME", self.cmd_RESUME) - self.gcode.register_command("CLEAR_PAUSE", self.cmd_CLEAR_PAUSE) - self.gcode.register_command("CANCEL_PRINT", self.cmd_CANCEL_PRINT) + self.gcode.register_command("PAUSE", self.cmd_PAUSE, + desc=self.cmd_PAUSE_help) + self.gcode.register_command("RESUME", self.cmd_RESUME, + desc=self.cmd_RESUME_help) + self.gcode.register_command("CLEAR_PAUSE", self.cmd_CLEAR_PAUSE, + desc=self.cmd_CLEAR_PAUSE_help) + self.gcode.register_command("CANCEL_PRINT", self.cmd_CANCEL_PRINT, + desc=self.cmd_CANCEL_PRINT_help) webhooks = self.printer.lookup_object('webhooks') webhooks.register_endpoint("pause_resume/cancel", self._handle_cancel_request) @@ -51,6 +55,7 @@ class PauseResume: self.sd_paused = False self.gcode.respond_info("action:paused") self.pause_command_sent = True + cmd_PAUSE_help = ("Pauses the current print") def cmd_PAUSE(self, gcmd): if self.is_paused: gcmd.respond_info("Print already paused") @@ -66,6 +71,7 @@ class PauseResume: else: self.gcode.respond_info("action:resumed") self.pause_command_sent = False + cmd_RESUME_help = ("Resumes the print from a pause") def cmd_RESUME(self, gcmd): if not self.is_paused: gcmd.respond_info("Print is not paused, resume aborted") @@ -76,8 +82,11 @@ class PauseResume: % (velocity)) self.send_resume_command() self.is_paused = False + cmd_CLEAR_PAUSE_help = ( + "Clears the current paused state without resuming the print") def cmd_CLEAR_PAUSE(self, gcmd): self.is_paused = self.pause_command_sent = False + cmd_CANCEL_PRINT_help = ("Cancel the current print") def cmd_CANCEL_PRINT(self, gcmd): self.cmd_PAUSE(gcmd) if not self.sd_paused: diff --git a/klippy/extras/resonance_tester.py b/klippy/extras/resonance_tester.py index b78305e1..14efcd61 100644 --- a/klippy/extras/resonance_tester.py +++ b/klippy/extras/resonance_tester.py @@ -97,11 +97,14 @@ class ResonanceTester: self.gcode = self.printer.lookup_object('gcode') self.gcode.register_command("MEASURE_AXES_NOISE", - self.cmd_MEASURE_AXES_NOISE) + self.cmd_MEASURE_AXES_NOISE, + desc=self.cmd_MEASURE_AXES_NOISE_help) self.gcode.register_command("TEST_RESONANCES", - self.cmd_TEST_RESONANCES) + self.cmd_TEST_RESONANCES, + desc=self.cmd_TEST_RESONANCES_help) self.gcode.register_command("SHAPER_CALIBRATE", - self.cmd_SHAPER_CALIBRATE) + self.cmd_SHAPER_CALIBRATE, + desc=self.cmd_SHAPER_CALIBRATE_help) self.printer.register_event_handler("klippy:connect", self.connect) def connect(self): @@ -159,7 +162,7 @@ class ResonanceTester: else: calibration_data[axis].add_data(new_data) return calibration_data - + cmd_TEST_RESONANCES_help = ("Runs the resonance test for a specifed axis") def cmd_TEST_RESONANCES(self, gcmd): # Parse parameters if len(self.test.get_supported_axes()) > 1: @@ -197,7 +200,8 @@ class ResonanceTester: helper, axis, data) gcmd.respond_info( "Resonances data written to %s file" % (csv_name,)) - + cmd_SHAPER_CALIBRATE_help = ( + "Simular to TEST_RESONANCES but suggest input shaper config") def cmd_SHAPER_CALIBRATE(self, gcmd): # Parse parameters axis = gcmd.get("AXIS", None) @@ -241,7 +245,8 @@ class ResonanceTester: gcmd.respond_info( "The SAVE_CONFIG command will update the printer config file\n" "with these parameters and restart the printer.") - + cmd_MEASURE_AXES_NOISE_help = ( + "Measures noise of all enabled accelerometer chips") def cmd_MEASURE_AXES_NOISE(self, gcmd): meas_time = gcmd.get_float("MEAS_TIME", 2.) for _, chip in self.accel_chips: diff --git a/klippy/extras/respond.py b/klippy/extras/respond.py index 0e228108..0adbc4bf 100644 --- a/klippy/extras/respond.py +++ b/klippy/extras/respond.py @@ -19,7 +19,8 @@ class HostResponder: self.default_prefix = config.get('default_prefix', self.default_prefix) gcode = self.printer.lookup_object('gcode') gcode.register_command('M118', self.cmd_M118, True) - gcode.register_command('RESPOND', self.cmd_RESPOND, True) + gcode.register_command('RESPOND', self.cmd_RESPOND, True, + desc=self.cmd_RESPOND_help) def cmd_M118(self, gcmd): msg = gcmd.get_commandline() umsg = msg.upper() @@ -33,6 +34,7 @@ class HostResponder: else: msg = '' gcmd.respond_raw("%s %s" % (self.default_prefix, msg)) + cmd_RESPOND_help = ("Echo the message prepended with a prefix") def cmd_RESPOND(self, gcmd): respond_type = gcmd.get('TYPE', None) prefix = self.default_prefix diff --git a/klippy/gcode.py b/klippy/gcode.py index 3abc70aa..97621cb9 100644 --- a/klippy/gcode.py +++ b/klippy/gcode.py @@ -325,6 +325,7 @@ class GCodeDispatch: msg = self.printer.get_state_message()[0] msg = msg.rstrip() + "\nKlipper state: Not ready" raise gcmd.error(msg) + cmd_HELP_help = "Report the list of available extended G-Code commands" def cmd_HELP(self, gcmd): cmdhelp = [] if not self.is_printer_ready: From 9f4a0dc77f3d9f38e49d0f1aa56b7db58ae1c989 Mon Sep 17 00:00:00 2001 From: TheBugLebowsky Date: Wed, 2 Jun 2021 16:48:24 +0200 Subject: [PATCH 06/51] config: Modify printer-tronxy-x5sa-pro-2020.cfg (#4330) Invert filament_switch_sensor switch_pin Signed-off-by: Morgan Barresi --- config/printer-tronxy-x5sa-pro-2020.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/printer-tronxy-x5sa-pro-2020.cfg b/config/printer-tronxy-x5sa-pro-2020.cfg index 63af6392..b09fffc1 100644 --- a/config/printer-tronxy-x5sa-pro-2020.cfg +++ b/config/printer-tronxy-x5sa-pro-2020.cfg @@ -112,7 +112,7 @@ pin: PD6 pause_on_runout: True runout_gcode: M25 -switch_pin: PA15 +switch_pin: !PA15 [output_pin beeper] pin: PB0 From c148f17ea3391e3720961270a12eb0645f688e12 Mon Sep 17 00:00:00 2001 From: Paul McGowan Date: Wed, 2 Jun 2021 10:51:45 -0400 Subject: [PATCH 07/51] neopixel: add sync parameter to prevent waking toolhead (#4339) neopixel: add sync param to prevent waking toolhead dotstar: refactor to match neopixel methods and add sync parameter Signed-off-by: Paul McGowan --- docs/G-Codes.md | 9 +++++-- klippy/extras/dotstar.py | 50 ++++++++++++++++++++++++++------------- klippy/extras/neopixel.py | 10 ++++++-- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 273870d5..38e7e389 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -272,7 +272,7 @@ The following command is available when a [neopixel config section](Config_Reference.md#neopixel) or [dotstar config section](Config_Reference.md#dotstar) is enabled: - `SET_LED LED= RED= GREEN= BLUE= - WHITE= [INDEX=] [TRANSMIT=0]`: This sets the LED + WHITE= [INDEX=] [TRANSMIT=0] [SYNC=1]`: This sets the LED output. Each color `` must be between 0.0 and 1.0. The WHITE option is only valid on RGBW LEDs. If multiple LED chips are daisy-chained then one may specify INDEX to alter the color of just @@ -281,7 +281,12 @@ The following command is available when a to the provided color. If TRANSMIT=0 is specified then the color change will only be made on the next SET_LED command that does not specify TRANSMIT=0; this may be useful in combination with the INDEX - parameter to batch multiple updates in a daisy-chain. + parameter to batch multiple updates in a daisy-chain. By default, the + SET_LED command will sync it's changes with other ongoing gcode commands. + This can lead to undesirable behavior if LEDs are being set while the + printer is not printing as it will reset the idle timeout. If careful + timing is not needed, the optional SYNC=0 parameter can be specified to + apply the changes instantly and not reset the idle timeout. ## Servo Commands diff --git a/klippy/extras/dotstar.py b/klippy/extras/dotstar.py index 08c370fa..6c7182a2 100644 --- a/klippy/extras/dotstar.py +++ b/klippy/extras/dotstar.py @@ -11,6 +11,7 @@ class PrinterDotstar: def __init__(self, config): self.printer = config.get_printer() name = config.get_name().split()[1] + self.mutex = self.printer.get_reactor().mutex() # Configure a software spi bus ppins = self.printer.lookup_object('pins') data_pin_params = ppins.lookup_pin(config.get('data_pin')) @@ -26,17 +27,29 @@ class PrinterDotstar: red = config.getfloat('initial_RED', 0., minval=0., maxval=1.) green = config.getfloat('initial_GREEN', 0., minval=0., maxval=1.) blue = config.getfloat('initial_BLUE', 0., minval=0., maxval=1.) - red = int(red * 255. + .5) - blue = int(blue * 255. + .5) - green = int(green * 255. + .5) color_data = [0xff, blue, green, red] * self.chain_count self.color_data = [0, 0, 0, 0] + color_data + [0xff, 0xff, 0xff, 0xff] - self.printer.register_event_handler("klippy:connect", self.send_data) + self.update_color_data(red, green, blue) + self.old_color_data = bytearray([d ^ 1 for d in self.color_data]) # Register commands + self.printer.register_event_handler("klippy:connect", self.send_data) gcode = self.printer.lookup_object('gcode') gcode.register_mux_command("SET_LED", "LED", name, self.cmd_SET_LED, desc=self.cmd_SET_LED_help) + def update_color_data(self, red, green, blue, white=None, index=None): + red = int(red * 255. + .5) + blue = int(blue * 255. + .5) + green = int(green * 255. + .5) + color_data = [0xff, blue, green, red] + if index is not None: + self.color_data[index*4:(index+1)*4] = color_data + else: + self.color_data[4:-4] = color_data * self.chain_count def send_data(self, print_time=None): + old_data, new_data = self.old_color_data, self.color_data + if new_data == old_data: + return + minclock = 0 if print_time is not None: minclock = self.spi.get_mcu().print_time_to_clock(print_time) @@ -50,20 +63,25 @@ class PrinterDotstar: red = gcmd.get_float('RED', 0., minval=0., maxval=1.) green = gcmd.get_float('GREEN', 0., minval=0., maxval=1.) blue = gcmd.get_float('BLUE', 0., minval=0., maxval=1.) - transmit = gcmd.get_int('TRANSMIT', 1) - red = int(red * 255. + .5) - blue = int(blue * 255. + .5) - green = int(green * 255. + .5) - color_data = [0xff, blue, green, red] + white = 0.0 #dotstar's dont have white yet index = gcmd.get_int('INDEX', None, minval=1, maxval=self.chain_count) - if index is not None: - self.color_data[index*4:(index+1)*4] = color_data - else: - self.color_data[4:-4] = color_data * self.chain_count - # Send command - if transmit: + transmit = gcmd.get_int('TRANSMIT', 1) + sync = gcmd.get_int('SYNC', 1) + def reactor_bgfunc(print_time): + with self.mutex: + self.update_color_data(red, green, blue, index=index) + if transmit: + self.send_data(print_time) + def lookahead_bgfunc(print_time): + reactor = self.printer.get_reactor() + reactor.register_callback(lambda et: reactor_bgfunc(print_time)) + if sync: + #Sync LED Update with print time and send toolhead = self.printer.lookup_object('toolhead') - toolhead.register_lookahead_callback(self.send_data) + toolhead.register_lookahead_callback(lookahead_bgfunc) + else: + #Send update now (so as not to wake toolhead and reset idle_timeout) + lookahead_bgfunc(None) def load_config_prefix(config): return PrinterDotstar(config) diff --git a/klippy/extras/neopixel.py b/klippy/extras/neopixel.py index e7785d97..1af0def4 100644 --- a/klippy/extras/neopixel.py +++ b/klippy/extras/neopixel.py @@ -119,6 +119,7 @@ class PrinterNeoPixel: white = gcmd.get_float('WHITE', 0., minval=0., maxval=1.) index = gcmd.get_int('INDEX', None, minval=1, maxval=self.chain_count) transmit = gcmd.get_int('TRANSMIT', 1) + sync = gcmd.get_int('SYNC', 1) # Update and transmit data def reactor_bgfunc(print_time): with self.mutex: @@ -128,8 +129,13 @@ class PrinterNeoPixel: def lookahead_bgfunc(print_time): reactor = self.printer.get_reactor() reactor.register_callback(lambda et: reactor_bgfunc(print_time)) - toolhead = self.printer.lookup_object('toolhead') - toolhead.register_lookahead_callback(lookahead_bgfunc) + if sync: + #Sync LED Update with print time and send + toolhead = self.printer.lookup_object('toolhead') + toolhead.register_lookahead_callback(lookahead_bgfunc) + else: + #Send update now (so as not to wake toolhead and reset idle_timeout) + lookahead_bgfunc(None) def load_config_prefix(config): return PrinterNeoPixel(config) From 27f8cf025ef348eb3fd16cc32a0d738e2effa9af Mon Sep 17 00:00:00 2001 From: Michael Rose Date: Wed, 2 Jun 2021 09:11:19 -0600 Subject: [PATCH 08/51] z_tilt: expose an 'applied' status to allow macros to skip QGL/Z_TILT_ADJUST (#4313) This is useful for macros that'd like to skip QGL if already leveled, e.g.: ``` {% if not printer.quad_gantry_level.applied %} QUAD_GANTRY_LEVEL {% endif %} ``` Signed-off-by: Michael Rose --- docs/Status_Reference.md | 14 ++++++++++++++ klippy/extras/quad_gantry_level.py | 8 +++++++- klippy/extras/z_tilt.py | 23 ++++++++++++++++++++++- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 01fc4271..ab14b7f7 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -218,6 +218,13 @@ is defined): template expansion, the PROBE (or similar) command must be run prior to the macro containing this reference. +# quad_gantry_level + +The following information is available in the `quad_gantry_level` object +(this object is available if quad_gantry_level is defined): +- `applied`: True if the gantry leveling process has been run and completed + successfully. + # query_endstops The following information is available in the `query_endstops` object @@ -316,3 +323,10 @@ object is always available): state. Possible values are: "ready", "startup", "shutdown", "error". - `state_message`: A human readable string giving additional context on the current Klipper state. + +# z_tilt + +The following information is available in the `z_tilt` object (this +object is available if z_tilt is defined): +- `applied`: True if the z-tilt leveling process has been run and completed + successfully. diff --git a/klippy/extras/quad_gantry_level.py b/klippy/extras/quad_gantry_level.py index 43173e15..8b10918f 100644 --- a/klippy/extras/quad_gantry_level.py +++ b/klippy/extras/quad_gantry_level.py @@ -33,6 +33,7 @@ class QuadGantryLevel: if len(self.probe_helper.probe_points) != 4: raise config.error( "Need exactly 4 probe points for quad_gantry_level") + self.z_status = z_tilt.ZAdjustStatus(self.printer) self.z_helper = z_tilt.ZAdjustHelper(config, 4) gantry_corners = config.get('gantry_corners').split('\n') try: @@ -54,6 +55,7 @@ class QuadGantryLevel: cmd_QUAD_GANTRY_LEVEL_help = ( "Conform a moving, twistable gantry to the shape of a stationary bed") def cmd_QUAD_GANTRY_LEVEL(self, gcmd): + self.z_status.reset() self.retry_helper.start(gcmd) self.probe_helper.start_probe(gcmd) def probe_finalize(self, offsets, positions): @@ -114,7 +116,9 @@ class QuadGantryLevel: speed = self.probe_helper.get_lift_speed() self.z_helper.adjust_steppers(z_adjust, speed) - return self.retry_helper.check_retry(z_positions) + return self.z_status.check_retry_result( + self.retry_helper.check_retry(z_positions)) + def linefit(self,p1,p2): if p1[1] == p2[1]: # Straight line @@ -124,6 +128,8 @@ class QuadGantryLevel: return m,b def plot(self,f,x): return f[0]*x + f[1] + def get_status(self, eventtime): + return self.z_status.get_status(eventtime) def load_config(config): return QuadGantryLevel(config) diff --git a/klippy/extras/z_tilt.py b/klippy/extras/z_tilt.py index c4c58c9e..f8a8c718 100644 --- a/klippy/extras/z_tilt.py +++ b/klippy/extras/z_tilt.py @@ -66,6 +66,22 @@ class ZAdjustHelper: curpos[2] += first_stepper_offset toolhead.set_position(curpos) +class ZAdjustStatus: + def __init__(self, printer): + self.applied = False + printer.register_event_handler("stepper_enable:motor_off", + self._motor_off) + def check_retry_result(self, retry_result): + if retry_result == "done": + self.applied = True + return retry_result + def reset(self): + self.applied = False + def get_status(self, eventtime): + return {'applied': self.applied} + def _motor_off(self, print_time): + self.reset() + class RetryHelper: def __init__(self, config, error_msg_extra = ""): self.gcode = config.get_printer().lookup_object('gcode') @@ -123,6 +139,7 @@ class ZTilt: self.retry_helper = RetryHelper(config) self.probe_helper = probe.ProbePointsHelper(config, self.probe_finalize) self.probe_helper.minimum_points(2) + self.z_status = ZAdjustStatus(self.printer) self.z_helper = ZAdjustHelper(config, len(self.z_positions)) # Register Z_TILT_ADJUST command gcode = self.printer.lookup_object('gcode') @@ -130,6 +147,7 @@ class ZTilt: desc=self.cmd_Z_TILT_ADJUST_help) cmd_Z_TILT_ADJUST_help = "Adjust the Z tilt" def cmd_Z_TILT_ADJUST(self, gcmd): + self.z_status.reset() self.retry_helper.start(gcmd) self.probe_helper.start_probe(gcmd) def probe_finalize(self, offsets, positions): @@ -159,7 +177,10 @@ class ZTilt: adjustments = [x*x_adjust + y*y_adjust + z_adjust for x, y in self.z_positions] self.z_helper.adjust_steppers(adjustments, speed) - return self.retry_helper.check_retry([p[2] for p in positions]) + return self.z_status.check_retry_result( + self.retry_helper.check_retry([p[2] for p in positions])) + def get_status(self, eventtime): + return self.z_status.get_status(eventtime) def load_config(config): return ZTilt(config) From 703418de01a550fe59c7cbe7ae73815947c35021 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 3 Jun 2021 14:07:56 -0400 Subject: [PATCH 09/51] docs: Update G-Codes.md now that SET_VELOCITY_LIMIT can exceed config Signed-off-by: Kevin O'Connor --- docs/G-Codes.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 38e7e389..a885aef1 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -161,8 +161,7 @@ The following standard commands are supported: MINIMUM and/or at or below the supplied MAXIMUM. - `SET_VELOCITY_LIMIT [VELOCITY=] [ACCEL=] [ACCEL_TO_DECEL=] [SQUARE_CORNER_VELOCITY=]`: Modify - the printer's velocity limits. Note that one may only set values - less than or equal to the limits specified in the config file. + the printer's velocity limits. - `SET_HEATER_TEMPERATURE HEATER= [TARGET=]`: Sets the target temperature for a heater. If a target temperature is not supplied, the target is 0. From 93b9a85d19147ee57f6e79b1661e33df21aa1f20 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 28 Apr 2021 20:49:37 -0400 Subject: [PATCH 10/51] endstop_phase: Add support for reporting phase information via get_status() Signed-off-by: Kevin O'Connor --- docs/Status_Reference.md | 14 +++++++++++ klippy/extras/endstop_phase.py | 46 +++++++++++++++++++++++----------- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index ab14b7f7..17a759f5 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -36,6 +36,20 @@ The following information is available in the `display_status` object `virtual_sdcard.progress` if no recent `M73` received). - `message`: The message contained in the last `M117` G-Code command. +# endstop_phase + +The following information is available in the +[endstop_phase](Config_Reference.md#endstop_phase) object: +- `last_home..phase`: The phase of the stepper motor at + the end of the last home attempt. +- `last_home..phases`: The total number of phases + available on the stepper motor. +- `last_home..mcu_position`: The position (as tracked by + the micro-controller) of the stepper motor at the end of the last + home attempt. The position is the total number of steps taken in a + forward direction minus the total number of steps taken in the + reverse direction since the micro-controller was last restarted. + # fan The following information is available in diff --git a/klippy/extras/endstop_phase.py b/klippy/extras/endstop_phase.py index a2b2b548..ed6c5960 100644 --- a/klippy/extras/endstop_phase.py +++ b/klippy/extras/endstop_phase.py @@ -117,6 +117,7 @@ class EndstopPhases: def __init__(self, config): self.printer = config.get_printer() self.tracking = {} + self.last_home_info = {} # Register handlers self.printer.register_event_handler("homing:home_rails_end", self.handle_home_rails_end) @@ -124,21 +125,30 @@ class EndstopPhases: self.gcode.register_command("ENDSTOP_PHASE_CALIBRATE", self.cmd_ENDSTOP_PHASE_CALIBRATE, desc=self.cmd_ENDSTOP_PHASE_CALIBRATE_help) - def lookup_rail(self, stepper, stepper_name): + def lookup_stepper(self, stepper, stepper_name): mod_name = "endstop_phase %s" % (stepper_name,) m = self.printer.lookup_object(mod_name, None) if m is not None: - return (None, m.phase_history) + return {"get_phase": None, "is_rail": False, + "phase_history": m.phase_history} for driver in TRINAMIC_DRIVERS: mod_name = "%s %s" % (driver, stepper_name) m = self.printer.lookup_object(mod_name, None) if m is not None: - return (m.get_phase, [0] * (m.get_microsteps() * 4)) + return {"get_phase": m.get_phase, "is_rail": False, + "phase_history": [0] * (m.get_microsteps() * 4)} return None - def update_rail(self, info, stepper): + def update_stepper(self, stepper, is_rail): + stepper_name = stepper.get_name() + if stepper_name not in self.tracking: + info = self.lookup_stepper(stepper, stepper_name) + self.tracking[stepper_name] = info + info = self.tracking[stepper_name] if info is None: return - get_phase, phase_history = info + if is_rail: + info["is_rail"] = True + get_phase = info["get_phase"] if get_phase is None: return try: @@ -146,16 +156,19 @@ class EndstopPhases: except: logging.exception("Error in EndstopPhases get_phase") return + phase_history = info["phase_history"] phase = convert_phase(driver_phase, driver_phases, len(phase_history)) phase_history[phase] += 1 + self.last_home_info[stepper.get_name()] = { + 'phase': phase, 'phases': len(phase_history), + 'mcu_position': stepper.get_mcu_position() + } def handle_home_rails_end(self, homing_state, rails): for rail in rails: - stepper = rail.get_steppers()[0] - stepper_name = stepper.get_name() - if stepper_name not in self.tracking: - info = self.lookup_rail(stepper, stepper_name) - self.tracking[stepper_name] = info - self.update_rail(self.tracking[stepper_name], stepper) + is_rail = True + for stepper in rail.get_steppers(): + self.update_stepper(stepper, is_rail) + is_rail = False cmd_ENDSTOP_PHASE_CALIBRATE_help = "Calibrate stepper phase" def cmd_ENDSTOP_PHASE_CALIBRATE(self, gcmd): stepper_name = gcmd.get('STEPPER', None) @@ -167,6 +180,8 @@ class EndstopPhases: raise gcmd.error("Stats not available for stepper %s" % (stepper_name,)) endstop_phase, phases = self.generate_stats(stepper_name, info) + if not info["is_rail"]: + return configfile = self.printer.lookup_object('configfile') section = 'endstop_phase %s' % (stepper_name,) configfile.remove_section(section) @@ -176,7 +191,7 @@ class EndstopPhases: "The SAVE_CONFIG command will update the printer config\n" "file with these parameters and restart the printer.") def generate_stats(self, stepper_name, info): - get_phase, phase_history = info + phase_history = info["phase_history"] wph = phase_history + phase_history count = sum(phase_history) phases = len(phase_history) @@ -200,10 +215,13 @@ class EndstopPhases: self.gcode.respond_info( "No steppers found. (Be sure to home at least once.)") return - for stepper_name, info in sorted(self.tracking.items()): - if info is None: + for stepper_name in sorted(self.tracking.keys()): + info = self.tracking[stepper_name] + if info is None or not info["is_rail"]: continue self.generate_stats(stepper_name, info) + def get_status(self, eventtime): + return { 'last_home': dict(self.last_home_info) } def load_config_prefix(config): return EndstopPhase(config) From 6266e7c259f6974254b7253d6bf028063fb9d03c Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 29 May 2021 17:22:46 -0400 Subject: [PATCH 11/51] atsamd: Fix pll reference in samd51 config_dpll() Signed-off-by: Kevin O'Connor --- src/atsamd/samd51_clock.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/atsamd/samd51_clock.c b/src/atsamd/samd51_clock.c index 2f5062c2..046f5744 100644 --- a/src/atsamd/samd51_clock.c +++ b/src/atsamd/samd51_clock.c @@ -58,10 +58,10 @@ static void config_dpll(uint32_t pll, uint32_t mul, uint32_t ctrlb) { OSCCTRL->Dpll[pll].DPLLCTRLA.reg = 0; - while (OSCCTRL->Dpll[0].DPLLSYNCBUSY.reg & OSCCTRL_DPLLSYNCBUSY_ENABLE) + while (OSCCTRL->Dpll[pll].DPLLSYNCBUSY.reg & OSCCTRL_DPLLSYNCBUSY_ENABLE) ; OSCCTRL->Dpll[pll].DPLLRATIO.reg = OSCCTRL_DPLLRATIO_LDR(mul - 1); - while (OSCCTRL->Dpll[0].DPLLSYNCBUSY.reg & OSCCTRL_DPLLSYNCBUSY_DPLLRATIO) + while (OSCCTRL->Dpll[pll].DPLLSYNCBUSY.reg & OSCCTRL_DPLLSYNCBUSY_DPLLRATIO) ; OSCCTRL->Dpll[pll].DPLLCTRLB.reg = ctrlb | OSCCTRL_DPLLCTRLB_LBYPASS; OSCCTRL->Dpll[pll].DPLLCTRLA.reg = OSCCTRL_DPLLCTRLA_ENABLE; From bd65c37ed5caac04857493518b07eda083659977 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 4 Jun 2021 17:03:46 -0400 Subject: [PATCH 12/51] atsamd: Add support for 25Mhz crystals Needed for the Duet3 Mini 5+ board. Reported by @lukeslaboratory. Signed-off-by: Kevin O'Connor --- src/atsamd/Kconfig | 2 ++ src/atsamd/samd51_clock.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/atsamd/Kconfig b/src/atsamd/Kconfig index 44ded6ca..5684ff6f 100644 --- a/src/atsamd/Kconfig +++ b/src/atsamd/Kconfig @@ -89,6 +89,8 @@ choice prompt "Clock Reference" config CLOCK_REF_X32K bool "32.768Khz crystal" + config CLOCK_REF_X25M + bool "25Mhz crystal" if MACH_SAMD51 config CLOCK_REF_INTERNAL bool "Internal clock" endchoice diff --git a/src/atsamd/samd51_clock.c b/src/atsamd/samd51_clock.c index 046f5744..cf7402e5 100644 --- a/src/atsamd/samd51_clock.c +++ b/src/atsamd/samd51_clock.c @@ -97,6 +97,8 @@ config_dfll(uint32_t dfllmul, uint32_t ctrlb) #if CONFIG_CLOCK_REF_X32K DECL_CONSTANT_STR("RESERVE_PINS_crystal", "PA0,PA1"); +#elif CONFIG_CLOCK_REF_X25M +DECL_CONSTANT_STR("RESERVE_PINS_crystal", "PB22,PB23"); #endif // Initialize the clocks using an external 32K crystal @@ -123,6 +125,33 @@ clock_init_32k(void) gen_clock(CLKGEN_48M, GCLK_GENCTRL_SRC_DPLL1); } +// Initialize the clocks using an external 25M crystal +static void +clock_init_25m(void) +{ + // Enable XOSC1 + uint32_t freq_xosc = 25000000; + uint32_t val = (OSCCTRL_XOSCCTRL_ENABLE | OSCCTRL_XOSCCTRL_XTALEN + | OSCCTRL_XOSCCTRL_IPTAT(3) | OSCCTRL_XOSCCTRL_IMULT(6)); + OSCCTRL->XOSCCTRL[1].reg = val; + while (!(OSCCTRL->STATUS.reg & OSCCTRL_STATUS_XOSCRDY1)) + ; + + // Generate 120Mhz clock on PLL0 (with XOSC1 as reference) + uint32_t p0div = 10, p0mul = DIV_ROUND_CLOSEST(FREQ_MAIN, freq_xosc/p0div); + uint32_t p0ctrlb = OSCCTRL_DPLLCTRLB_DIV(p0div / 2 - 1); + config_dpll(0, p0mul, p0ctrlb | OSCCTRL_DPLLCTRLB_REFCLK_XOSC1); + + // Switch main clock to 120Mhz PLL0 + gen_clock(CLKGEN_MAIN, GCLK_GENCTRL_SRC_DPLL0); + + // Generate 48Mhz clock on PLL1 (with XOSC1 as reference) + uint32_t p1div = 50, p1mul = DIV_ROUND_CLOSEST(FREQ_48M, freq_xosc/p1div); + uint32_t p1ctrlb = OSCCTRL_DPLLCTRLB_DIV(p1div / 2 - 1); + config_dpll(1, p1mul, p1ctrlb | OSCCTRL_DPLLCTRLB_REFCLK_XOSC1); + gen_clock(CLKGEN_48M, GCLK_GENCTRL_SRC_DPLL1); +} + // Initialize clocks from factory calibrated internal clock static void clock_init_internal(void) @@ -167,6 +196,8 @@ SystemInit(void) // Init clocks if (CONFIG_CLOCK_REF_X32K) clock_init_32k(); + else if (CONFIG_CLOCK_REF_X25M) + clock_init_25m(); else clock_init_internal(); From 080b7f68ca793bde549ae2ab210d901cedf3119c Mon Sep 17 00:00:00 2001 From: Luke's Lab Date: Fri, 2 Apr 2021 14:22:23 -0500 Subject: [PATCH 13/51] config: Add generic-duet3-mini.cfg Adds Duet3 Mini Config with support for expansion header Signed-off-by: Luke Ashley Signed-off-by: Kevin O'Connor --- config/generic-duet3-mini.cfg | 148 ++++++++++++++++++++++++++++++++++ test/configs/samd51p20.config | 4 + test/klippy/printers.test | 4 + 3 files changed, 156 insertions(+) create mode 100644 config/generic-duet3-mini.cfg create mode 100644 test/configs/samd51p20.config diff --git a/config/generic-duet3-mini.cfg b/config/generic-duet3-mini.cfg new file mode 100644 index 00000000..cb2dfadb --- /dev/null +++ b/config/generic-duet3-mini.cfg @@ -0,0 +1,148 @@ +# This file contains common pin mappings for the Duet3 Mini 5+. To use +# this config, the firmware should be compiled for the ATSAMD51P20 +# with a "25Mhz crystal", "16KiB bootloader", and USB communication. + +# To flash the board, double tap the board's reset button to enter the +# bootloader and then run: make flash FLASH_DEVICE=/dev/ttyACM0 + +# See docs/Config_Reference.md for a description of parameters. + + +# Pins for reference: +# Driver Step Pins - 0:PC26, 1:PC25, 2:PC24, 3:PC19, 4:PC16, 5:PC30, 6:PC18 +# Driver Dir pins - 0:PB3, 1:PB29, 2:PB28, 3:PD20, 4:PD21, 5:PB0, 6:PA27 +# Driver Enable - !PC28 +# Uart addresses - 0:0 1:1 2:2 3:3 4:!0 5:!1 6:!2 | "!" is for inverted select pin +# Thermistor Pins - T0:PC0, T1:PC1, T2:PC2 +# Vssa Sense:PB4 | Vref Sense:PB5 +# Current Sense resistor for drivers - .076ohm +# SPI lines:{PD11, PC7} -> Shared SerCom#7, SPIMosi:PC12, SPIMiso:PC15, SPISCLK:PC13 +# Vin Monitor:PC3, uses 11:1 voltage divider +# LED's - Diag:PA31, Act:PA30 +# 12864 LCD - LCDCSPIN:PC6, ENCA:PC11, ENCB:PD1, ENCSW:PB9, LCD A0:PA2, LCDBeep:PA0, LCD Neopixel Out:PB12 (shared with IO3.out) +# Neopixel Out - PA8 +# Serial0 - TX:PB25, RX:PB24 (USB) +# Serial1 - TX:PB31, RX:PB30 +# SBC SPISS pin:PA6, SBCTfrReady:PA3, SerComPins:{PA4, PA5, PA6, PA7} +# CAN Pins - TX:PB14 RX:PB15 +# Heaters, Fan outputs - {Out0:PB17 Out1:PC10 Out2:PB13 Out3:PB11 Out4:PA11, Out5:PB2, Out6:PB1} | Out6 is shared with VFD_Out +# GPIO_out - {IO1:PB31 IO2:PD9 IO3:PB12 IO4:PD10} IO4 is shared with PSON +# GPIO_in - {IO1:PB30 IO2:PD8 IO3:PB7 IO4:PC5 IO5:PC4 IO6:PC31} +# Driver Diag - {D0:PA10, D1:PB8, D2:PA22, D3:PA23, D4:PC21, D5:PB10, D6:PA27} +# Mux Pin - PD0 +# EXP headers only support 12864 LCD's + +[stepper_x] +#driver0 +step_pin: PC26 +dir_pin: !PB3 +enable_pin: !PC28 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PC31 +position_endstop: 0 +position_max: 450 + +[tmc2209 stepper_x] +uart_pin: PA1 +tx_pin: PA0 +select_pins: PD0 +uart_address: 0 +run_current: 1 +sense_resistor: 0.056 + +[stepper_y] +#driver1 +step_pin: PC25 +dir_pin: PB29 +enable_pin: !PC28 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PC4 +position_endstop: 0 +position_max: 450 + +[tmc2209 stepper_y] +uart_pin: PA1 +tx_pin: PA0 +select_pins: PD0 +uart_address: 1 +run_current: 1 +sense_resistor: 0.056 + +[stepper_z] +#driver2 +step_pin: PC24 +dir_pin: PB28 +enable_pin: !PC28 +microsteps: 16 +rotation_distance: 8 +endstop_pin: PC5 +position_endstop: 0 +position_max: 400 + +[tmc2209 stepper_z] +uart_pin: PA1 +tx_pin: PA0 +select_pins: PD0 +uart_address: 2 +run_current: 1 +sense_resistor: 0.056 + +[adc_scaled vref_scaled] +vref_pin: PB5 +vssa_pin: PB4 + +[extruder] +#driver3 +step_pin: PC19 +dir_pin: PD20 +enable_pin: !PC28 +microsteps: 16 +rotation_distance: 33.500 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PB13 # out2 +sensor_type: ATC Semitec 104GT-2 +pullup_resistor: 2200 +sensor_pin: vref_scaled:PC1 +control: pid +pid_Kp: 30.089 +pid_Ki: 2.229 +pid_Kd: 101.550 +min_temp: 0 +max_temp: 285 + +[tmc2209 extruder] +uart_pin: PA1 +tx_pin: PA0 +uart_address: 3 +select_pins: PD0 +run_current: 1 +sense_resistor: 0.056 + +[heater_bed] +heater_pin: PB17 #out1 +sensor_type: NTC 100K beta 3950 +sensor_pin: vref_scaled:PC0 +control: pid +pullup_resistor: 2200 +pid_Kp: 61.049 +pid_Ki: 2.339 +pid_Kd: 398.344 +min_temp: 0 +max_temp: 130 + +[heater_fan heatbreak_fan] +pin: PB11 + +[fan] +pin: PA11 + +[mcu] +serial: /dev/ttyACM0 + +[printer] +kinematics: cartesian +max_velocity: 350 +max_accel: 3000 diff --git a/test/configs/samd51p20.config b/test/configs/samd51p20.config new file mode 100644 index 00000000..09f15fa8 --- /dev/null +++ b/test/configs/samd51p20.config @@ -0,0 +1,4 @@ +# Base config file for Atmel SAMD51P20 ARM processor +CONFIG_MACH_ATSAMD=y +CONFIG_MACH_SAMD51P20=y +CONFIG_CLOCK_REF_X25M=y diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 33c70bf3..1c837e0f 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -107,6 +107,10 @@ DICTIONARY sam4e8e.dict CONFIG ../../config/generic-duet2.cfg CONFIG ../../config/generic-duet2-duex.cfg +# Printers using the samd51 +DICTIONARY samd51p20.dict +CONFIG ../../config/generic-duet3-mini.cfg + # Printers using the lpc176x DICTIONARY lpc176x.dict CONFIG ../../config/generic-azteeg-x5-mini-v3.cfg From e0e8615aaf9ce122f673665d811d17e019ee23df Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sun, 6 Jun 2021 12:01:03 -0400 Subject: [PATCH 14/51] docs: Fix missing backtick in API_Server.md Signed-off-by: Kevin O'Connor --- docs/API_Server.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/API_Server.md b/docs/API_Server.md index 42885f9b..6e43ce3e 100644 --- a/docs/API_Server.md +++ b/docs/API_Server.md @@ -170,7 +170,7 @@ gcode: When the PANELDUE_BEEP gcode macro is executed, Klipper would send something like the following over the socket: `{"action": "run_paneldue_beep", -"params": {"frequency": 300, "duration": 1.0}} +"params": {"frequency": 300, "duration": 1.0}}` ### objects/list From 4596a244e38740f578bab7b13e80cdb902b9c865 Mon Sep 17 00:00:00 2001 From: Dmitry Butyugin Date: Sun, 6 Jun 2021 19:04:53 +0200 Subject: [PATCH 15/51] resonance_tester: Set max_accel ignoring the limit from the config (#4174) Signed-off-by: Dmitry Butyugin --- docs/Measuring_Resonances.md | 12 +--------- klippy/extras/resonance_tester.py | 38 +++++++++++++++++++------------ 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/docs/Measuring_Resonances.md b/docs/Measuring_Resonances.md index 481f5cea..739d090f 100644 --- a/docs/Measuring_Resonances.md +++ b/docs/Measuring_Resonances.md @@ -139,17 +139,7 @@ noisy imbalanced fans on a 3D printer. ## Measuring the resonances -Now you can run some real-life tests. In `printer.cfg` add or replace the -following values: -``` -[printer] -max_accel: 10000 -max_accel_to_decel: 10000 -``` -(after you are done with the measurements, revert these values to their old, -or the newly suggested values). - -Run the following command: +Now you can run some real-life tests. Run the following command: ``` TEST_RESONANCES AXIS=X ``` diff --git a/klippy/extras/resonance_tester.py b/klippy/extras/resonance_tester.py index 14efcd61..f90ca28f 100644 --- a/klippy/extras/resonance_tester.py +++ b/klippy/extras/resonance_tester.py @@ -32,25 +32,30 @@ class VibrationPulseTest: return ['x', 'y'] def get_start_test_points(self): return self.probe_points - def prepare_test(self, toolhead, gcmd): + def prepare_test(self, gcmd): self.freq_start = gcmd.get_float("FREQ_START", self.min_freq, minval=1.) self.freq_end = gcmd.get_float("FREQ_END", self.max_freq, minval=self.freq_start, maxval=200.) self.hz_per_sec = gcmd.get_float("HZ_PER_SEC", self.hz_per_sec, above=0., maxval=2.) - # Attempt to adjust maximum acceleration and acceleration to - # deceleration based on the maximum test frequency. - max_accel = self.freq_end * self.accel_per_hz - toolhead.cmd_SET_VELOCITY_LIMIT(self.gcode.create_gcode_command( - "SET_VELOCITY_LIMIT", "SET_VELOCITY_LIMIT", - {"ACCEL": max_accel, "ACCEL_TO_DECEL": max_accel})) - def run_test(self, toolhead, axis, gcmd): + def run_test(self, axis, gcmd): + toolhead = self.printer.lookup_object('toolhead') X, Y, Z, E = toolhead.get_position() if axis not in self.get_supported_axes(): raise gcmd.error("Test axis '%s' is not supported", axis) vib_dir = (1, 0) if axis == 'x' else (0., 1.) sign = 1. freq = self.freq_start + # Override maximum acceleration and acceleration to + # deceleration based on the maximum test frequency + systime = self.printer.get_reactor().monotonic() + toolhead_info = toolhead.get_status(systime) + old_max_accel = toolhead_info['max_accel'] + old_max_accel_to_decel = toolhead_info['max_accel_to_decel'] + max_accel = self.freq_end * self.accel_per_hz + self.gcode.run_script_from_command( + "SET_VELOCITY_LIMIT ACCEL=%.3f ACCEL_TO_DECEL=%.3f" % ( + max_accel, max_accel)) input_shaper = self.printer.lookup_object('input_shaper', None) if input_shaper is not None and not gcmd.get_int('INPUT_SHAPING', 0): input_shaper.disable_shaping() @@ -58,23 +63,26 @@ class VibrationPulseTest: else: input_shaper = None gcmd.respond_info("Testing frequency %.0f Hz" % (freq,)) - _, max_accel = toolhead.get_max_velocity() while freq <= self.freq_end + 0.000001: t_seg = .25 / freq - accel = min(self.accel_per_hz * freq, max_accel) - V = accel * t_seg + accel = self.accel_per_hz * freq + max_v = accel * t_seg toolhead.cmd_M204(self.gcode.create_gcode_command( "M204", "M204", {"S": accel})) L = .5 * accel * t_seg**2 nX = X + sign * vib_dir[0] * L nY = Y + sign * vib_dir[1] * L - toolhead.move([nX, nY, Z, E], V) - toolhead.move([X, Y, Z, E], V) + toolhead.move([nX, nY, Z, E], max_v) + toolhead.move([X, Y, Z, E], max_v) sign = -sign old_freq = freq freq += 2. * t_seg * self.hz_per_sec if math.floor(freq) > math.floor(old_freq): gcmd.respond_info("Testing frequency %.0f Hz" % (freq,)) + # Restore the original acceleration values + self.gcode.run_script_from_command( + "SET_VELOCITY_LIMIT ACCEL=%.3f ACCEL_TO_DECEL=%.3f" % ( + old_max_accel, old_max_accel_to_decel)) # Restore input shaper if it was disabled for resonance testing if input_shaper is not None: input_shaper.enable_shaping() @@ -116,7 +124,7 @@ class ResonanceTester: toolhead = self.printer.lookup_object('toolhead') calibration_data = {axis: None for axis in axes} - self.test.prepare_test(toolhead, gcmd) + self.test.prepare_test(gcmd) test_points = self.test.get_start_test_points() for point in test_points: toolhead.manual_move(point, self.move_speed) @@ -133,7 +141,7 @@ class ResonanceTester: if axis in chip_axis or chip_axis in axis: chip.start_measurements() # Generate moves - self.test.run_test(toolhead, axis, gcmd) + self.test.run_test(axis, gcmd) raw_values = [] for chip_axis, chip in self.accel_chips: if axis in chip_axis or chip_axis in axis: From be4fb7f128728da687bbee5a0dda3d1b12e82ef3 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 4 Jun 2021 22:24:37 -0400 Subject: [PATCH 16/51] endstop_phase: Unify phase conversion code Introduce a new PhaseCalc class and use that for both homing and ENDSTOP_PHASE_CALIBRATE. Signed-off-by: Kevin O'Connor --- klippy/extras/endstop_phase.py | 169 +++++++++++++++++---------------- 1 file changed, 88 insertions(+), 81 deletions(-) diff --git a/klippy/extras/endstop_phase.py b/klippy/extras/endstop_phase.py index ed6c5960..5c762b2e 100644 --- a/klippy/extras/endstop_phase.py +++ b/klippy/extras/endstop_phase.py @@ -8,23 +8,65 @@ import stepper TRINAMIC_DRIVERS = ["tmc2130", "tmc2208", "tmc2209", "tmc2660", "tmc5160"] -def convert_phase(driver_phase, driver_phases, phases): - return (int(float(driver_phase) / driver_phases * phases + .5) % phases) +# Calculate the trigger phase of a stepper motor +class PhaseCalc: + def __init__(self, printer, name, phases=None): + self.printer = printer + self.name = name + self.phases = phases + self.tmc_module = None + # Statistics tracking for ENDSTOP_PHASE_CALIBRATE + self.phase_history = self.last_phase = self.last_mcu_position = None + self.is_primary = self.stats_only = False + def lookup_tmc(self): + for driver in TRINAMIC_DRIVERS: + driver_name = "%s %s" % (driver, self.name) + module = self.printer.lookup_object(driver_name, None) + if module is not None: + self.tmc_module = module + if self.phases is None: + self.phases = module.get_microsteps() * 4 + break + if self.phases is not None: + self.phase_history = [0] * self.phases + def convert_phase(self, driver_phase, driver_phases): + phases = self.phases + return (int(float(driver_phase) / driver_phases * phases + .5) % phases) + def calc_phase(self, stepper): + mcu_pos = stepper.get_mcu_position() + if self.tmc_module is None: + phase = mcu_pos % self.phases + else: + try: + driver_phase, driver_phases = self.tmc_module.get_phase() + except Exception as e: + msg = "Unable to get stepper %s phase: %s" % (self.name, str(e)) + logging.exception(msg) + raise self.printer.command_error(msg) + if stepper.is_dir_inverted(): + driver_phase = (driver_phases - 1) - driver_phase + phase = self.convert_phase(driver_phase, driver_phases) + self.phase_history[phase] += 1 + self.last_phase = phase + self.last_mcu_position = mcu_pos + return phase +# Adjusted endstop trigger positions class EndstopPhase: def __init__(self, config): self.printer = config.get_printer() self.name = config.get_name().split()[1] - # Register event handlers - self.printer.register_event_handler("klippy:connect", - self.handle_connect) - self.printer.register_event_handler("homing:home_rails_end", - self.handle_home_rails_end) - self.printer.load_object(config, "endstop_phase") # Obtain step_distance and microsteps from stepper config section sconfig = config.getsection(self.name) self.step_dist = stepper.parse_step_distance(sconfig) self.phases = sconfig.getint("microsteps", note_valid=False) * 4 + self.phase_calc = PhaseCalc(self.printer, self.name, self.phases) + # Register event handlers + self.printer.register_event_handler("klippy:connect", + self.phase_calc.lookup_tmc) + self.printer.register_event_handler("homing:home_rails_end", + self.handle_home_rails_end) + self.printer.load_object(config, "endstop_phase") # Read config self.endstop_phase = None trigger_phase = config.get('trigger_phase', None) @@ -37,7 +79,7 @@ class EndstopPhase: if p >= ps: raise config.error("Invalid trigger_phase '%s'" % (trigger_phase,)) - self.endstop_phase = convert_phase(p, ps, self.phases) + self.endstop_phase = self.phase_calc.convert_phase(p, ps) self.endstop_align_zero = config.getboolean('endstop_align_zero', False) self.endstop_accuracy = config.getfloat('endstop_accuracy', None, above=0.) @@ -55,16 +97,6 @@ class EndstopPhase: " stepper phase adjustment" % (self.name,)) if self.printer.get_start_args().get('debugoutput') is not None: self.endstop_phase_accuracy = self.phases - self.phase_history = [0] * self.phases - self.get_phase = None - def handle_connect(self): - # Check for trinamic driver with get_phase() method - for driver in TRINAMIC_DRIVERS: - driver_name = "%s %s" % (driver, self.name) - module = self.printer.lookup_object(driver_name, None) - if module is not None: - self.get_phase = module.get_phase - break def align_endstop(self, pos): if not self.endstop_align_zero or self.endstop_phase is None: return pos @@ -76,19 +108,7 @@ class EndstopPhase: full_step = microsteps * self.step_dist return int(pos / full_step + .5) * full_step + phase_offset def get_homed_offset(self, stepper): - if self.get_phase is not None: - try: - driver_phase, driver_phases = self.get_phase() - except Exception as e: - msg = "Unable to get stepper %s phase: %s" % (self.name, str(e)) - logging.exception(msg) - raise self.printer.command_error(msg) - if stepper.is_dir_inverted(): - driver_phase = (driver_phases - 1) - driver_phase - phase = convert_phase(driver_phase, driver_phases, self.phases) - else: - phase = stepper.get_mcu_position() % self.phases - self.phase_history[phase] += 1 + phase = self.phase_calc.calc_phase(stepper) if self.endstop_phase is None: logging.info("Setting %s endstop phase to %d", self.name, phase) self.endstop_phase = phase @@ -113,11 +133,11 @@ class EndstopPhase: kin_spos[self.name] = self.align_endstop(orig_pos) + offset return +# Support for ENDSTOP_PHASE_CALIBRATE command class EndstopPhases: def __init__(self, config): self.printer = config.get_printer() self.tracking = {} - self.last_home_info = {} # Register handlers self.printer.register_event_handler("homing:home_rails_end", self.handle_home_rails_end) @@ -125,62 +145,45 @@ class EndstopPhases: self.gcode.register_command("ENDSTOP_PHASE_CALIBRATE", self.cmd_ENDSTOP_PHASE_CALIBRATE, desc=self.cmd_ENDSTOP_PHASE_CALIBRATE_help) - def lookup_stepper(self, stepper, stepper_name): - mod_name = "endstop_phase %s" % (stepper_name,) - m = self.printer.lookup_object(mod_name, None) - if m is not None: - return {"get_phase": None, "is_rail": False, - "phase_history": m.phase_history} - for driver in TRINAMIC_DRIVERS: - mod_name = "%s %s" % (driver, stepper_name) + def update_stepper(self, stepper, is_primary): + stepper_name = stepper.get_name() + phase_calc = self.tracking.get(stepper_name) + if phase_calc is None: + # Check if stepper has an endstop_phase config section defined + mod_name = "endstop_phase %s" % (stepper_name,) m = self.printer.lookup_object(mod_name, None) if m is not None: - return {"get_phase": m.get_phase, "is_rail": False, - "phase_history": [0] * (m.get_microsteps() * 4)} - return None - def update_stepper(self, stepper, is_rail): - stepper_name = stepper.get_name() - if stepper_name not in self.tracking: - info = self.lookup_stepper(stepper, stepper_name) - self.tracking[stepper_name] = info - info = self.tracking[stepper_name] - if info is None: + phase_calc = m.phase_calc + else: + # Create new PhaseCalc tracker + phase_calc = PhaseCalc(self.printer, stepper_name) + phase_calc.stats_only = True + phase_calc.lookup_tmc() + self.tracking[stepper_name] = phase_calc + if phase_calc.phase_history is None: return - if is_rail: - info["is_rail"] = True - get_phase = info["get_phase"] - if get_phase is None: - return - try: - driver_phase, driver_phases = get_phase() - except: - logging.exception("Error in EndstopPhases get_phase") - return - phase_history = info["phase_history"] - phase = convert_phase(driver_phase, driver_phases, len(phase_history)) - phase_history[phase] += 1 - self.last_home_info[stepper.get_name()] = { - 'phase': phase, 'phases': len(phase_history), - 'mcu_position': stepper.get_mcu_position() - } + if is_primary: + phase_calc.is_primary = True + if phase_calc.stats_only: + phase_calc.calc_phase(stepper) def handle_home_rails_end(self, homing_state, rails): for rail in rails: - is_rail = True + is_primary = True for stepper in rail.get_steppers(): - self.update_stepper(stepper, is_rail) - is_rail = False + self.update_stepper(stepper, is_primary) + is_primary = False cmd_ENDSTOP_PHASE_CALIBRATE_help = "Calibrate stepper phase" def cmd_ENDSTOP_PHASE_CALIBRATE(self, gcmd): stepper_name = gcmd.get('STEPPER', None) if stepper_name is None: self.report_stats() return - info = self.tracking.get(stepper_name) - if info is None: + phase_calc = self.tracking.get(stepper_name) + if phase_calc is None or phase_calc.phase_history is None: raise gcmd.error("Stats not available for stepper %s" % (stepper_name,)) - endstop_phase, phases = self.generate_stats(stepper_name, info) - if not info["is_rail"]: + endstop_phase, phases = self.generate_stats(stepper_name, phase_calc) + if not phase_calc.is_primary: return configfile = self.printer.lookup_object('configfile') section = 'endstop_phase %s' % (stepper_name,) @@ -190,8 +193,8 @@ class EndstopPhases: gcmd.respond_info( "The SAVE_CONFIG command will update the printer config\n" "file with these parameters and restart the printer.") - def generate_stats(self, stepper_name, info): - phase_history = info["phase_history"] + def generate_stats(self, stepper_name, phase_calc): + phase_history = phase_calc.phase_history wph = phase_history + phase_history count = sum(phase_history) phases = len(phase_history) @@ -216,12 +219,16 @@ class EndstopPhases: "No steppers found. (Be sure to home at least once.)") return for stepper_name in sorted(self.tracking.keys()): - info = self.tracking[stepper_name] - if info is None or not info["is_rail"]: + phase_calc = self.tracking[stepper_name] + if phase_calc is None or not phase_calc.is_primary: continue - self.generate_stats(stepper_name, info) + self.generate_stats(stepper_name, phase_calc) def get_status(self, eventtime): - return { 'last_home': dict(self.last_home_info) } + lh = { name: {'phase': pc.last_phase, 'phases': pc.phases, + 'mcu_position': pc.last_mcu_position} + for name, pc in self.tracking.items() + if pc.phase_history is not None } + return { 'last_home': lh } def load_config_prefix(config): return EndstopPhase(config) From 87e12c0430cffce03004f92250a2ffe6885442bf Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 7 Jun 2021 21:35:42 -0400 Subject: [PATCH 17/51] config: Add motor power definition to generic-bigtreetech-skr-2.cfg Add a pin definition to enable motor power and add a warning about initial faulty SKR2 boards. Reported by @FHeilmann. Signed-off-by: Kevin O'Connor --- config/generic-bigtreetech-skr-2.cfg | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/config/generic-bigtreetech-skr-2.cfg b/config/generic-bigtreetech-skr-2.cfg index 218badea..c4195106 100644 --- a/config/generic-bigtreetech-skr-2.cfg +++ b/config/generic-bigtreetech-skr-2.cfg @@ -9,6 +9,10 @@ # See docs/Config_Reference.md for a description of parameters. +# Note: The initial revision of this board has a flaw that can cause +# damage to itself and other boards. Be sure to verify the board is +# not impacted by this flaw before using it. + [stepper_x] step_pin: PE2 dir_pin: PE1 @@ -84,6 +88,10 @@ pin: PB7 #[heater_fan fan2] #pin: PB5 +[output_pin motor_power] +pin: PC13 +value: 1 + [mcu] serial: /dev/serial/by-id/usb-Klipper_Klipper_firmware_12345-if00 From 4dfe01a2cd619143e2e39ad6b46e66a4dc62e0b8 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Thu, 3 Jun 2021 18:14:44 -0400 Subject: [PATCH 18/51] bed_mesh: move the "fade offset" out of the ZMesh This is a temporary offset applied to the mesh used to fade toward a "target". For clarity, apply the fade offset when the final z calculation is made. SIgned-off-by: Eric Callahan --- klippy/extras/bed_mesh.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index 11a8a748..4a2a3b61 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -118,8 +118,6 @@ class BedMesh: "bed_mesh: ERROR, fade_target lies outside of mesh z " "range\nmin: %.4f, max: %.4f, fade_target: %.4f" % (min_z, max_z, err_target)) - if self.fade_target: - mesh.offset_mesh(self.fade_target) min_z, max_z = mesh.get_z_range() if self.fade_dist <= max(abs(min_z), abs(max_z)): self.z_mesh = None @@ -132,7 +130,7 @@ class BedMesh: else: self.fade_target = 0. self.z_mesh = mesh - self.splitter.initialize(mesh) + self.splitter.initialize(mesh, self.fade_target) # cache the current position before a transform takes place gcode_move = self.printer.lookup_object('gcode_move') gcode_move.reset_last_position() @@ -152,9 +150,9 @@ class BedMesh: else: # return current position minus the current z-adjustment x, y, z, e = self.toolhead.get_position() - z_adj = self.z_mesh.calc_z(x, y) + max_adj = self.z_mesh.calc_z(x, y) factor = 1. - max_adj = z_adj + self.fade_target + z_adj = max_adj - self.fade_target if min(z, (z - max_adj)) >= self.fade_end: # Fade out is complete, no factor factor = 0. @@ -707,9 +705,11 @@ class MoveSplitter: self.move_check_distance = config.getfloat( 'move_check_distance', 5., minval=3.) self.z_mesh = None + self.fade_offset = 0. self.gcode = gcode - def initialize(self, mesh): + def initialize(self, mesh, fade_offset): self.z_mesh = mesh + self.fade_offset = fade_offset def build_move(self, prev_pos, next_pos, factor): self.prev_pos = tuple(prev_pos) self.next_pos = tuple(next_pos) @@ -723,7 +723,8 @@ class MoveSplitter: self.axis_move = [not isclose(d, 0., abs_tol=1e-10) for d in axes_d] def _calc_z_offset(self, pos): z = self.z_mesh.calc_z(pos[0], pos[1]) - return self.z_factor * z + self.z_mesh.mesh_offset + offset = self.fade_offset + return self.z_factor * (z - offset) + offset def _set_next_move(self, distance_from_prev): t = distance_from_prev / self.total_move_length if t > 1. or t < 0.: @@ -766,7 +767,6 @@ class ZMesh: self.probed_matrix = self.mesh_matrix = None self.mesh_params = params self.avg_z = 0. - self.mesh_offset = 0. logging.debug('bed_mesh: probe/mesh parameters:') for key, value in self.mesh_params.items(): logging.debug("%s : %s" % (key, value)) @@ -802,7 +802,7 @@ class ZMesh: (self.mesh_y_count - 1) def get_mesh_matrix(self): if self.mesh_matrix is not None: - return [[round(z + self.mesh_offset, 6) for z in line] + return [[round(z, 6) for z in line] for line in self.mesh_matrix] return [[]] def get_probed_matrix(self): @@ -851,12 +851,6 @@ class ZMesh: # z step distances self.avg_z = round(self.avg_z, 2) self.print_mesh(logging.debug) - def offset_mesh(self, offset): - if self.mesh_matrix: - self.mesh_offset = offset - for y_line in self.mesh_matrix: - for idx, z in enumerate(y_line): - y_line[idx] = z - self.mesh_offset def get_x_coordinate(self, index): return self.mesh_x_min + self.mesh_x_dist * index def get_y_coordinate(self, index): From 3fcce4bb4b2087d975ff8e877afb111585e2bcfc Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Thu, 3 Jun 2021 14:16:43 -0400 Subject: [PATCH 19/51] bed_mesh: implement BED_MESH_OFFSET The BED_MESH_OFFSET gcode can be used to apply X and/or Y offsets the the mesh lookup. This allows printers with independent extruders to correct the Z adjustment when changing a tool. Signed-off-by: Eric Callahan --- klippy/extras/bed_mesh.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index 4a2a3b61..d513db8c 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -93,6 +93,9 @@ class BedMesh: self.gcode.register_command( 'BED_MESH_CLEAR', self.cmd_BED_MESH_CLEAR, desc=self.cmd_BED_MESH_CLEAR_help) + self.gcode.register_command( + 'BED_MESH_OFFSET', self.cmd_BED_MESH_OFFSET, + desc=self.cmd_BED_MESH_OFFSET_help) # Register transform gcode_move = self.printer.load_object(config, 'gcode_move') gcode_move.set_move_transform(self) @@ -230,9 +233,20 @@ class BedMesh: gcmd.respond_raw("mesh_map_output " + json.dumps(outdict)) else: gcmd.respond_info("Bed has not been probed") - cmd_BED_MESH_CLEAR_help = "Clear the Mesh so no z-adjusment is made" + cmd_BED_MESH_CLEAR_help = "Clear the Mesh so no z-adjustment is made" def cmd_BED_MESH_CLEAR(self, gcmd): self.set_mesh(None) + cmd_BED_MESH_OFFSET_help = "Add X/Y offsets to the mesh lookup" + def cmd_BED_MESH_OFFSET(self, gcmd): + if self.z_mesh is not None: + offsets = [None, None] + for i, axis in enumerate(['X', 'Y']): + offsets[i] = gcmd.get_float(axis, None) + self.z_mesh.set_mesh_offsets(offsets) + gcode_move = self.printer.lookup_object('gcode_move') + gcode_move.reset_last_position() + else: + gcmd.respond_info("No mesh loaded to offset") class BedMeshCalibrate: @@ -767,6 +781,7 @@ class ZMesh: self.probed_matrix = self.mesh_matrix = None self.mesh_params = params self.avg_z = 0. + self.mesh_offsets = [0., 0.] logging.debug('bed_mesh: probe/mesh parameters:') for key, value in self.mesh_params.items(): logging.debug("%s : %s" % (key, value)) @@ -828,6 +843,8 @@ class ZMesh: msg = "Mesh X,Y: %d,%d\n" % (self.mesh_x_count, self.mesh_y_count) if move_z is not None: msg += "Search Height: %d\n" % (move_z) + msg += "Mesh Offsets: X=%.4f, Y=%.4f\n" % ( + self.mesh_offsets[0], self.mesh_offsets[1]) msg += "Mesh Average: %.2f\n" % (self.avg_z) rng = self.get_z_range() msg += "Mesh Range: min=%.4f max=%.4f\n" % (rng[0], rng[1]) @@ -851,6 +868,10 @@ class ZMesh: # z step distances self.avg_z = round(self.avg_z, 2) self.print_mesh(logging.debug) + def set_mesh_offsets(self, offsets): + for i, o in enumerate(offsets): + if o is not None: + self.mesh_offsets[i] = o def get_x_coordinate(self, index): return self.mesh_x_min + self.mesh_x_dist * index def get_y_coordinate(self, index): @@ -858,8 +879,8 @@ class ZMesh: def calc_z(self, x, y): if self.mesh_matrix is not None: tbl = self.mesh_matrix - tx, xidx = self._get_linear_index(x, 0) - ty, yidx = self._get_linear_index(y, 1) + tx, xidx = self._get_linear_index(x + self.mesh_offsets[0], 0) + ty, yidx = self._get_linear_index(y + self.mesh_offsets[1], 1) z0 = lerp(tx, tbl[yidx][xidx], tbl[yidx][xidx+1]) z1 = lerp(tx, tbl[yidx+1][xidx], tbl[yidx+1][xidx+1]) return lerp(ty, z0, z1) From b513d085a51251b0715461a473ada59254271350 Mon Sep 17 00:00:00 2001 From: Eric Callahan Date: Thu, 3 Jun 2021 15:00:01 -0400 Subject: [PATCH 20/51] docs: Add documentation for BED_MESH_OFFSET Signed-off-by: Eric Callahan --- docs/Bed_Mesh.md | 11 +++++++++++ docs/G-Codes.md | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/docs/Bed_Mesh.md b/docs/Bed_Mesh.md index a36f9244..bdc1aa6c 100644 --- a/docs/Bed_Mesh.md +++ b/docs/Bed_Mesh.md @@ -430,3 +430,14 @@ probing the "Probe" points will refer to both the tool and nozzle locations. `BED_MESH_CLEAR` This gcode may be used to clear the internal mesh state. + +### Apply X/Y offsets + +`BED_MESH_OFFSET [X=] [Y=]` + +This is useful for printers with multiple independent extruders, as an offset +is necessary to produce correct Z adjustment after a tool change. Offsets +should be specified relative to the primary extruder. That is, a positive +X offset should be specified if the secondary extruder is mounted to the +right of the primary extruder, and a positive Y offset should be specified +if the secondary extruder is mounted "behind" the primary extruder. diff --git a/docs/G-Codes.md b/docs/G-Codes.md index a885aef1..2db1f491 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -433,6 +433,10 @@ The following commands are available when the supplied name from persistent memory. Note that after SAVE or REMOVE operations have been run the SAVE_CONFIG gcode must be run to make the changes to peristent memory permanent. +- `BED_MESH_OFFSET [X=] [Y=]`: Applies X and/or Y + offsets to the mesh lookup. This is useful for printers with + independent extruders, as an offset is necessary to produce + correct Z adjustment after a tool change. ## Bed Screws Helper From c19d1fbb5900630447b0e955a61e9f82dd1ff502 Mon Sep 17 00:00:00 2001 From: Sergey Alirzaev Date: Wed, 19 May 2021 19:35:13 +0300 Subject: [PATCH 21/51] Makefile: Include debug symbols in the .elf Signed-off-by: Sergey Alirzaev --- Makefile | 4 ++-- src/pru/pru.lds | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index a2e95785..af74951a 100644 --- a/Makefile +++ b/Makefile @@ -29,10 +29,10 @@ dirs-y = src cc-option=$(shell if test -z "`$(1) $(2) -S -o /dev/null -xc /dev/null 2>&1`" \ ; then echo "$(2)"; else echo "$(3)"; fi ;) -CFLAGS := -I$(OUT) -Isrc -I$(OUT)board-generic/ -std=gnu11 -O2 -MD -g \ +CFLAGS := -I$(OUT) -Isrc -I$(OUT)board-generic/ -std=gnu11 -O2 -MD \ -Wall -Wold-style-definition $(call cc-option,$(CC),-Wtype-limits,) \ -ffunction-sections -fdata-sections -CFLAGS += -flto -fwhole-program -fno-use-linker-plugin +CFLAGS += -flto -fwhole-program -fno-use-linker-plugin -ggdb3 OBJS_klipper.elf = $(patsubst %.c, $(OUT)src/%.o,$(src-y)) OBJS_klipper.elf += $(OUT)compile_time_request.o diff --git a/src/pru/pru.lds b/src/pru/pru.lds index db36803c..48f71d6d 100644 --- a/src/pru/pru.lds +++ b/src/pru/pru.lds @@ -3,7 +3,9 @@ SECTIONS { /* binutils on the PRU doesn't support --gc-sections so manually * discard the .compile_time_request section */ + /* and GDB extended debugging information */ /DISCARD/ : { *( .compile_time_request ) + *( .gnu.debug* ) } } From a3455168405c2f544bba849445ee2346d8b14a35 Mon Sep 17 00:00:00 2001 From: Clark Tomlinson Date: Tue, 8 Jun 2021 21:15:52 -0400 Subject: [PATCH 22/51] config: Add ezboard configuration (#4337) Signed-off-by: Clark Tomlinson --- config/generic-th3d-ezboard-lite-v1.2.cfg | 131 ++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 config/generic-th3d-ezboard-lite-v1.2.cfg diff --git a/config/generic-th3d-ezboard-lite-v1.2.cfg b/config/generic-th3d-ezboard-lite-v1.2.cfg new file mode 100644 index 00000000..67b8b656 --- /dev/null +++ b/config/generic-th3d-ezboard-lite-v1.2.cfg @@ -0,0 +1,131 @@ +# This file contains common pin mappings for the TH3D EZBoard Lite v1.2. +# To use this config, the firmware should be compiled for the +# LPC1769 with USB communication. + +# The "make flash" command does not work on this board. Instead, +# after running "make", copy the generated "out/klipper.bin" file to a +# file named "firmware.bin" on an SD card and then restart the board +# with that SD card. + + +# See docs/Config_Reference.md for a description of parameters. + +[mcu] +serial: /dev/serial/by-id/usb-Klipper_lpc1768_1BB0FF16871C4AAF0F5D7C5DC62000F5-if00 + +[printer] +kinematics: cartesian +max_velocity: 500 +max_accel: 3000 +max_z_velocity: 5 +max_z_accel: 100 + +[stepper_x] +step_pin: P2.0 +dir_pin: P1.16 +enable_pin: !P1.17 +rotation_distance: 72 +endstop_pin: ^P1.24 +position_endstop: 0 +position_max: 350 + +[tmc2208 stepper_x] +uart_pin: P0.5 +tx_pin: P0.4 +run_current: 0.600 +hold_current: 0.500 +stealthchop_threshold: 999999 + +[stepper_y] +step_pin: P2.1 +dir_pin: P1.10 +enable_pin: !P1.9 +rotation_distance: 72 +endstop_pin: ^P1.25 +position_endstop: 0 +position_max: 350 + +[tmc2208 stepper_y] +uart_pin: P0.11 +tx_pin: P0.10 +run_current: 0.600 +hold_current: 0.500 +stealthchop_threshold: 999999 + +[stepper_z] +step_pin: P2.2 +dir_pin: P1.15 +enable_pin: !P1.14 +rotation_distance: 16 +endstop_pin: probe:z_virtual_endstop +position_endstop: 0.5 +position_max: 400 + +[tmc2208 stepper_z] +uart_pin: P0.20 +tx_pin: P0.19 +run_current: 0.700 +hold_current: 0.600 +stealthchop_threshold: 999999 + +[extruder] +step_pin: P2.3 +dir_pin: P1.4 +enable_pin: !P1.8 +roration_distance: 13.5744 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: P2.7 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: P0.23 +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 210 + +[tmc2208 extruder] +uart_pin: P0.21 +tx_pin: P0.22 +run_current: 0.800 +hold_current: 0.700 +stealthchop_threshold: 999999 + +[heater_bed] +heater_pin: P2.5 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: P0.24 +control: pid +# tuned for stock hardware with 50 degree Celsius target +pid_Kp: 54.027 +pid_Ki: 0.770 +pid_Kd: 948.182 +min_temp: 0 +max_temp: 130 + +[fan] +pin: P2.6 + +#[safe_z_home] +# home_xy_position: 175,175 # Change coordinates to the center of your print bed +# speed: 50 +# z_hop: 10 +# z_hop_speed: 5 + +#[bltouch] +#sensor_pin: P1.26 +#control_pin: P2.4 + +#[filament_switch_sensor my_sensor] +#switch_pin: P1.27 + +###################################################################### +# 128x64 Full Graphic Creality CR10 / ENDER 3 stockdisplay +###################################################################### + +[board_pins] +aliases: + # EXP1 header + EXP1_1=P1.31, EXP1_3=P3.26, EXP1_5=P3.25, EXP1_7=P0.16, EXP1_9=, + EXP1_2=P1.30, EXP1_4=, EXP1_6=P0.15, EXP1_8=P0.18, EXP1_10=<5V> From f00281d1e670e00ebcef06076abf2116c3f2d461 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 8 Jun 2021 21:29:52 -0400 Subject: [PATCH 23/51] config: Fixes for generic-th3d-ezboard-lite-v1.2.cfg Signed-off-by: Kevin O'Connor --- config/generic-th3d-ezboard-lite-v1.2.cfg | 34 +++++++++++------------ test/klippy/printers.test | 1 + 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/config/generic-th3d-ezboard-lite-v1.2.cfg b/config/generic-th3d-ezboard-lite-v1.2.cfg index 67b8b656..49661361 100644 --- a/config/generic-th3d-ezboard-lite-v1.2.cfg +++ b/config/generic-th3d-ezboard-lite-v1.2.cfg @@ -7,11 +7,10 @@ # file named "firmware.bin" on an SD card and then restart the board # with that SD card. - # See docs/Config_Reference.md for a description of parameters. [mcu] -serial: /dev/serial/by-id/usb-Klipper_lpc1768_1BB0FF16871C4AAF0F5D7C5DC62000F5-if00 +serial: /dev/serial/by-id/usb-Klipper_lpc1768_00000000000000000000000000000000-if00 [printer] kinematics: cartesian @@ -24,7 +23,8 @@ max_z_accel: 100 step_pin: P2.0 dir_pin: P1.16 enable_pin: !P1.17 -rotation_distance: 72 +microsteps: 16 +rotation_distance: 40 endstop_pin: ^P1.24 position_endstop: 0 position_max: 350 @@ -40,7 +40,8 @@ stealthchop_threshold: 999999 step_pin: P2.1 dir_pin: P1.10 enable_pin: !P1.9 -rotation_distance: 72 +microsteps: 16 +rotation_distance: 40 endstop_pin: ^P1.25 position_endstop: 0 position_max: 350 @@ -56,8 +57,9 @@ stealthchop_threshold: 999999 step_pin: P2.2 dir_pin: P1.15 enable_pin: !P1.14 -rotation_distance: 16 -endstop_pin: probe:z_virtual_endstop +microsteps: 16 +rotation_distance: 8 +endstop_pin: ^P1.26 position_endstop: 0.5 position_max: 400 @@ -72,7 +74,8 @@ stealthchop_threshold: 999999 step_pin: P2.3 dir_pin: P1.4 enable_pin: !P1.8 -roration_distance: 13.5744 +microsteps: 16 +rotation_distance: 34.406 nozzle_diameter: 0.400 filament_diameter: 1.750 heater_pin: P2.7 @@ -97,7 +100,6 @@ heater_pin: P2.5 sensor_type: EPCOS 100K B57560G104F sensor_pin: P0.24 control: pid -# tuned for stock hardware with 50 degree Celsius target pid_Kp: 54.027 pid_Ki: 0.770 pid_Kd: 948.182 @@ -107,12 +109,6 @@ max_temp: 130 [fan] pin: P2.6 -#[safe_z_home] -# home_xy_position: 175,175 # Change coordinates to the center of your print bed -# speed: 50 -# z_hop: 10 -# z_hop_speed: 5 - #[bltouch] #sensor_pin: P1.26 #control_pin: P2.4 @@ -120,12 +116,14 @@ pin: P2.6 #[filament_switch_sensor my_sensor] #switch_pin: P1.27 -###################################################################### -# 128x64 Full Graphic Creality CR10 / ENDER 3 stockdisplay -###################################################################### +######################################## +# EXP1 / EXP2 (display) pins +######################################## [board_pins] aliases: # EXP1 header EXP1_1=P1.31, EXP1_3=P3.26, EXP1_5=P3.25, EXP1_7=P0.16, EXP1_9=, - EXP1_2=P1.30, EXP1_4=, EXP1_6=P0.15, EXP1_8=P0.18, EXP1_10=<5V> + EXP1_2=P1.30, EXP1_4=, EXP1_6=P0.15, EXP1_8=P0.18, EXP1_10=<5V> + +# See the sample-lcd.cfg file for definitions of common LCD displays. diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 1c837e0f..668b8687 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -118,6 +118,7 @@ CONFIG ../../config/generic-bigtreetech-skr-e3-turbo.cfg CONFIG ../../config/generic-bigtreetech-skr-v1.1.cfg CONFIG ../../config/generic-bigtreetech-skr-v1.3.cfg CONFIG ../../config/generic-bigtreetech-skr-v1.4.cfg +CONFIG ../../config/generic-th3d-ezboard-lite-v1.2.cfg CONFIG ../../config/generic-mks-sgenl.cfg CONFIG ../../config/generic-re-arm.cfg CONFIG ../../config/generic-smoothieboard.cfg From 31fcd491fd9fd99f81599558b5256cfaaa078da0 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 9 Jun 2021 14:46:56 -0400 Subject: [PATCH 24/51] serialhdl: Support prepending a warn_prefix to error and log messages Signed-off-by: Kevin O'Connor --- klippy/mcu.py | 3 ++- klippy/msgproto.py | 33 +++++++++++++------------ klippy/serialhdl.py | 60 ++++++++++++++++++++++++++------------------- 3 files changed, 55 insertions(+), 41 deletions(-) diff --git a/klippy/mcu.py b/klippy/mcu.py index 6fb66247..f03fd16d 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -415,7 +415,8 @@ class MCU: if self._name.startswith('mcu '): self._name = self._name[4:] # Serial port - self._serial = serialhdl.SerialReader(self._reactor) + wp = "mcu '%s': " % (self._name) + self._serial = serialhdl.SerialReader(self._reactor, warn_prefix=wp) self._baud = 0 self._canbus_iface = None canbus_uuid = config.get('canbus_uuid', None) diff --git a/klippy/msgproto.py b/klippy/msgproto.py index c3c0cda9..cdecd8e7 100644 --- a/klippy/msgproto.py +++ b/klippy/msgproto.py @@ -105,8 +105,8 @@ class Enumeration: def encode(self, out, v): tv = self.enums.get(v) if tv is None: - raise error("Unknown value '%s' in enumeration '%s'" % ( - v, self.enum_name)) + raise error("Unknown value '%s' in enumeration '%s'" + % (v, self.enum_name)) self.pt.encode(out, tv) def parse(self, s, pos): v, pos = self.pt.parse(s, pos) @@ -221,7 +221,8 @@ class UnknownFormat: class MessageParser: error = error - def __init__(self): + def __init__(self, warn_prefix=""): + self.warn_prefix = warn_prefix self.unknown = UnknownFormat() self.enumerations = {} self.messages = [] @@ -231,6 +232,8 @@ class MessageParser: self.version = self.build_versions = "" self.raw_identify_data = "" self._init_messages(DefaultMessages) + def _error(self, msg, *params): + raise error(self.warn_prefix + (msg % params)) def check_packet(self, s): if len(s) < MESSAGE_MIN: return 0 @@ -277,7 +280,7 @@ class MessageParser: mid = self.messages_by_id.get(msgid, self.unknown) params, pos = mid.parse(s, MESSAGE_HEADER_SIZE) if pos != len(s)-MESSAGE_TRAILER_SIZE: - raise error("Extra data at end of message") + self._error("Extra data at end of message") params['#name'] = mid.name return params def encode(self, seq, cmd): @@ -302,10 +305,10 @@ class MessageParser: msgname = parts[0] mp = self.messages_by_name.get(msgname) if mp is None: - raise error("Unknown command: %s" % (msgname,)) + self._error("Unknown command: %s", msgname) if msgformat != mp.msgformat: - raise error("Command format mismatch: %s vs %s" % ( - msgformat, mp.msgformat)) + self._error("Command format mismatch: %s vs %s", + msgformat, mp.msgformat) return mp def create_command(self, msg): parts = msg.strip().split() @@ -314,7 +317,7 @@ class MessageParser: msgname = parts[0] mp = self.messages_by_name.get(msgname) if mp is None: - raise error("Unknown command: %s" % (msgname,)) + self._error("Unknown command: %s", msgname) try: argparts = dict(arg.split('=', 1) for arg in parts[1:]) for name, value in argparts.items(): @@ -330,14 +333,14 @@ class MessageParser: raise except: #logging.exception("Unable to extract params") - raise error("Unable to extract params from: %s" % (msgname,)) + self._error("Unable to extract params from: %s", msgname) try: cmd = mp.encode_by_name(**argparts) except error as e: raise except: #logging.exception("Unable to encode") - raise error("Unable to encode: %s" % (msgname,)) + self._error("Unable to encode: %s", msgname) return cmd def fill_enumerations(self, enumerations): for add_name, add_enums in enumerations.items(): @@ -366,7 +369,7 @@ class MessageParser: msgtype = 'output' self.messages.append((msgtag, msgtype, msgformat)) if msgtag < -32 or msgtag > 95: - raise error("Multi-byte msgtag not supported") + self._error("Multi-byte msgtag not supported") msgid = msgtag & 0x7f if msgtype == 'output': self.messages_by_id[msgid] = OutputFormat(msgid, msgformat) @@ -396,7 +399,7 @@ class MessageParser: raise except Exception as e: logging.exception("process_identify error") - raise error("Error during identify: %s" % (str(e),)) + self._error("Error during identify: %s", str(e)) def get_version_info(self): return self.version, self.build_versions def get_messages(self): @@ -410,12 +413,12 @@ class MessageParser: if name not in self.config: if default is not self.sentinel: return default - raise error("Firmware constant '%s' not found" % (name,)) + self._error("Firmware constant '%s' not found", name) try: value = parser(self.config[name]) except: - raise error("Unable to parse firmware constant %s: %s" % ( - name, self.config[name])) + self._error("Unable to parse firmware constant %s: %s", + name, self.config[name]) return value def get_constant_float(self, name, default=sentinel): return self.get_constant(name, default, parser=float) diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py index 9294b120..59482a41 100644 --- a/klippy/serialhdl.py +++ b/klippy/serialhdl.py @@ -13,11 +13,12 @@ class error(Exception): class SerialReader: BITS_PER_BYTE = 10. - def __init__(self, reactor): + def __init__(self, reactor, warn_prefix=""): self.reactor = reactor + self.warn_prefix = warn_prefix # Serial port self.serial_dev = None - self.msgparser = msgproto.MessageParser() + self.msgparser = msgproto.MessageParser(warn_prefix=warn_prefix) # C interface self.ffi_main, self.ffi_lib = chelper.get_ffi() self.serialqueue = None @@ -55,7 +56,10 @@ class SerialReader: hdl = self.handlers.get(hdl, self.handle_default) hdl(params) except: - logging.exception("Exception in serial callback") + logging.exception("%sException in serial callback", + self.warn_prefix) + def _error(self, msg, *params): + raise error(self.warn_prefix + (msg % params)) def _get_identify_data(self, eventtime): # Query the "data dictionary" from the micro-controller identify_data = "" @@ -64,7 +68,8 @@ class SerialReader: try: params = self.send_with_response(msg, 'identify_response') except error as e: - logging.exception("Wait for identify_response") + logging.exception("%sWait for identify_response", + self.warn_prefix) return None if params['offset'] == len(identify_data): msgdata = params['data'] @@ -84,10 +89,10 @@ class SerialReader: completion = self.reactor.register_callback(self._get_identify_data) identify_data = completion.wait(self.reactor.monotonic() + 5.) if identify_data is None: - logging.info("Timeout on connect") + logging.info("%sTimeout on connect", self.warn_prefix) self.disconnect() return False - msgparser = msgproto.MessageParser() + msgparser = msgproto.MessageParser(warn_prefix=self.warn_prefix) msgparser.process_identify(identify_data) self.msgparser = msgparser self.register_response(self.handle_unknown, '#unknown') @@ -112,7 +117,7 @@ class SerialReader: except ValueError: uuid = -1 if uuid < 0 or uuid > 0xffffffffffff: - raise error("Invalid CAN uuid") + self._error("Invalid CAN uuid") uuid = [(uuid >> (40 - i*8)) & 0xff for i in range(6)] CANBUS_ID_ADMIN = 0x3f0 CMD_SET_NODEID = 0x01 @@ -120,18 +125,19 @@ class SerialReader: set_id_msg = can.Message(arbitration_id=CANBUS_ID_ADMIN, data=set_id_cmd, is_extended_id=False) # Start connection attempt - logging.info("Starting CAN connect") + logging.info("%sStarting CAN connect", self.warn_prefix) start_time = self.reactor.monotonic() while 1: if self.reactor.monotonic() > start_time + 90.: - raise error("Unable to connect") + self._error("Unable to connect") try: bus = can.interface.Bus(channel=canbus_iface, can_filters=filters, bustype='socketcan') bus.send(set_id_msg) except can.CanError as e: - logging.warn("Unable to open CAN port: %s", e) + logging.warn("%sUnable to open CAN port: %s", + self.warn_prefix, e) self.reactor.pause(self.reactor.monotonic() + 5.) continue bus.close = bus.shutdown # XXX @@ -145,19 +151,21 @@ class SerialReader: if got_uuid == bytearray(uuid): break except: - logging.exception("Error in canbus_uuid check") - logging.info("Failed to match canbus_uuid - retrying..") + logging.exception("%sError in canbus_uuid check", + self.warn_prefix) + logging.info("%sFailed to match canbus_uuid - retrying..", + self.warn_prefix) self.disconnect() def connect_pipe(self, filename): - logging.info("Starting connect") + logging.info("%sStarting connect", self.warn_prefix) start_time = self.reactor.monotonic() while 1: if self.reactor.monotonic() > start_time + 90.: - raise error("Unable to connect") + self._error("Unable to connect") try: fd = os.open(filename, os.O_RDWR | os.O_NOCTTY) except OSError as e: - logging.warn("Unable to open port: %s", e) + logging.warn("%sUnable to open port: %s", self.warn_prefix, e) self.reactor.pause(self.reactor.monotonic() + 5.) continue serial_dev = os.fdopen(fd, 'rb+', 0) @@ -166,11 +174,11 @@ class SerialReader: break def connect_uart(self, serialport, baud, rts=True): # Initial connection - logging.info("Starting serial connect") + logging.info("%sStarting serial connect", self.warn_prefix) start_time = self.reactor.monotonic() while 1: if self.reactor.monotonic() > start_time + 90.: - raise error("Unable to connect") + self._error("Unable to connect") try: serial_dev = serial.Serial(baudrate=baud, timeout=0, exclusive=True) @@ -178,7 +186,8 @@ class SerialReader: serial_dev.rts = rts serial_dev.open() except (OSError, IOError, serial.SerialException) as e: - logging.warn("Unable to open serial port: %s", e) + logging.warn("%sUnable to open serial port: %s", + self.warn_prefix, e) self.reactor.pause(self.reactor.monotonic() + 5.) continue stk500v2_leave(serial_dev, self.reactor) @@ -238,7 +247,7 @@ class SerialReader: cmd, len(cmd), minclock, reqclock, nid) params = completion.wait() if params is None: - raise error("Serial connection closed") + self._error("Serial connection closed") return params def send(self, msg, minclock=0, reqclock=0): cmd = self.msgparser.create_command(msg) @@ -276,15 +285,16 @@ class SerialReader: return '\n'.join(out) # Default message handlers def _handle_unknown_init(self, params): - logging.debug("Unknown message %d (len %d) while identifying", - params['#msgid'], len(params['#msg'])) + logging.debug("%sUnknown message %d (len %d) while identifying", + self.warn_prefix, params['#msgid'], len(params['#msg'])) def handle_unknown(self, params): - logging.warn("Unknown message type %d: %s", - params['#msgid'], repr(params['#msg'])) + logging.warn("%sUnknown message type %d: %s", + self.warn_prefix, params['#msgid'], repr(params['#msg'])) def handle_output(self, params): - logging.info("%s: %s", params['#name'], params['#msg']) + logging.info("%s%s: %s", self.warn_prefix, + params['#name'], params['#msg']) def handle_default(self, params): - logging.warn("got %s", params) + logging.warn("%sgot %s", self.warn_prefix, params) # Class to send a query command and return the received response class SerialRetryCommand: From ecbfa762425b81031357c68158fb5939f6d24675 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 9 Jun 2021 15:02:56 -0400 Subject: [PATCH 25/51] mcu: Raise config_error (not protocol error) on pin enumeration errors Signed-off-by: Kevin O'Connor --- klippy/mcu.py | 31 ++++++++++++++++++++----------- klippy/msgproto.py | 24 ++++++++++++++++-------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/klippy/mcu.py b/klippy/mcu.py index f03fd16d..3f88c3e5 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import sys, os, zlib, logging, math -import serialhdl, pins, chelper, clocksync +import serialhdl, msgproto, pins, chelper, clocksync class error(Exception): pass @@ -548,17 +548,26 @@ class MCU: raise error("MCU '%s' CRC does not match config" % (self._name,)) # Transmit config messages (if needed) self.register_response(self._handle_starting, 'starting') - if prev_crc is None: - logging.info("Sending MCU '%s' printer configuration...", - self._name) - for c in self._config_cmds: + try: + if prev_crc is None: + logging.info("Sending MCU '%s' printer configuration...", + self._name) + for c in self._config_cmds: + self._serial.send(c) + else: + for c in self._restart_cmds: + self._serial.send(c) + # Transmit init messages + for c in self._init_cmds: self._serial.send(c) - else: - for c in self._restart_cmds: - self._serial.send(c) - # Transmit init messages - for c in self._init_cmds: - self._serial.send(c) + except msgproto.enumeration_error as e: + enum_name, enum_value = e.get_enum_params() + if enum_name == 'pin': + # Raise pin name errors as a config error (not a protocol error) + raise self._printer.config_error( + "Pin '%s' is not a valid pin name on mcu '%s'" + % (enum_value, self._name)) + raise def _send_get_config(self): get_config_cmd = self.lookup_query_command( "get_config", diff --git a/klippy/msgproto.py b/klippy/msgproto.py index cdecd8e7..7391a6f4 100644 --- a/klippy/msgproto.py +++ b/klippy/msgproto.py @@ -86,12 +86,14 @@ class PT_progmem_buffer(PT_string): class PT_buffer(PT_string): pass -MessageTypes = { - '%u': PT_uint32(), '%i': PT_int32(), - '%hu': PT_uint16(), '%hi': PT_int16(), - '%c': PT_byte(), - '%s': PT_string(), '%.*s': PT_progmem_buffer(), '%*s': PT_buffer(), -} +class enumeration_error(error): + def __init__(self, enum_name, value): + self.enum_name = enum_name + self.value = value + error.__init__(self, "Unknown value '%s' in enumeration '%s'" + % (value, enum_name)) + def get_enum_params(self): + return self.enum_name, self.value class Enumeration: is_int = False @@ -105,8 +107,7 @@ class Enumeration: def encode(self, out, v): tv = self.enums.get(v) if tv is None: - raise error("Unknown value '%s' in enumeration '%s'" - % (v, self.enum_name)) + raise enumeration_error(self.enum_name, v) self.pt.encode(out, tv) def parse(self, s, pos): v, pos = self.pt.parse(s, pos) @@ -115,6 +116,13 @@ class Enumeration: tv = "?%d" % (v,) return tv, pos +MessageTypes = { + '%u': PT_uint32(), '%i': PT_int32(), + '%hu': PT_uint16(), '%hi': PT_int16(), + '%c': PT_byte(), + '%s': PT_string(), '%.*s': PT_progmem_buffer(), '%*s': PT_buffer(), +} + # Lookup the message types for a format string def lookup_params(msgformat, enumerations={}): out = [] From f3bd4e6acffadb4e927f69d3944da92579707a4b Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 17 Feb 2021 17:48:03 -0500 Subject: [PATCH 26/51] probe: Call add_stepper() earlier in boot process Signed-off-by: Kevin O'Connor --- klippy/extras/bltouch.py | 5 +++-- klippy/extras/probe.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/klippy/extras/bltouch.py b/klippy/extras/bltouch.py index 6415ce1a..3dc46044 100644 --- a/klippy/extras/bltouch.py +++ b/klippy/extras/bltouch.py @@ -28,6 +28,8 @@ class BLTouchEndstopWrapper: self.printer = config.get_printer() self.printer.register_event_handler("klippy:connect", self.handle_connect) + self.printer.register_event_handler('klippy:mcu_identify', + self.handle_mcu_identify) self.position_endstop = config.getfloat('z_offset') self.stow_on_each_sample = config.getboolean('stow_on_each_sample', True) @@ -45,7 +47,6 @@ class BLTouchEndstopWrapper: pin = config.get('sensor_pin') pin_params = ppins.lookup_pin(pin, can_invert=True, can_pullup=True) mcu = pin_params['chip'] - mcu.register_config_callback(self._build_config) self.mcu_endstop = mcu.setup_pin('endstop', pin_params) # output mode omodes = {'5V': '5V', 'OD': 'OD', None: None} @@ -72,7 +73,7 @@ class BLTouchEndstopWrapper: desc=self.cmd_BLTOUCH_STORE_help) # multi probes state self.multi = 'OFF' - def _build_config(self): + def handle_mcu_identify(self): kin = self.printer.lookup_object('toolhead').get_kinematics() for stepper in kin.get_steppers(): if stepper.is_active_axis('z'): diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index a8fc36a6..700a8955 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -280,8 +280,9 @@ class ProbeEndstopWrapper: pin = config.get('pin') pin_params = ppins.lookup_pin(pin, can_invert=True, can_pullup=True) mcu = pin_params['chip'] - mcu.register_config_callback(self._build_config) self.mcu_endstop = mcu.setup_pin('endstop', pin_params) + self.printer.register_event_handler('klippy:mcu_identify', + self._handle_mcu_identify) # Wrappers self.get_mcu = self.mcu_endstop.get_mcu self.add_stepper = self.mcu_endstop.add_stepper @@ -291,7 +292,7 @@ class ProbeEndstopWrapper: self.query_endstop = self.mcu_endstop.query_endstop # multi probes state self.multi = 'OFF' - def _build_config(self): + def _handle_mcu_identify(self): kin = self.printer.lookup_object('toolhead').get_kinematics() for stepper in kin.get_steppers(): if stepper.is_active_axis('z'): From 05c2d51a1239a13a7dfeec9cfbb1ea38b7b32aa0 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 4 Feb 2021 10:07:13 -0500 Subject: [PATCH 27/51] trsync: Introduce new "trigger synchronization" support Separate out the stepper stopping code from endstop.c into its own trsync.c code file. Signed-off-by: Kevin O'Connor --- klippy/mcu.py | 67 +++++++++-------- src/Makefile | 3 +- src/endstop.c | 89 +++++----------------- src/stepper.c | 29 ++++++-- src/stepper.h | 2 - src/trsync.c | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/trsync.h | 19 +++++ 7 files changed, 299 insertions(+), 110 deletions(-) create mode 100644 src/trsync.c create mode 100644 src/trsync.h diff --git a/klippy/mcu.py b/klippy/mcu.py index 3f88c3e5..dc0011c9 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -18,10 +18,10 @@ class MCU_endstop: self._pullup = pin_params['pullup'] self._invert = pin_params['invert'] self._reactor = mcu.get_printer().get_reactor() - self._oid = self._home_cmd = self._requery_cmd = self._query_cmd = None + self._oid = self._home_cmd = self._query_cmd = None + self._ts_oid = self._trsync_start_cmd = self._trsync_trigger_cmd = None self._mcu.register_config_callback(self._build_config) - self._min_query_time = self._last_sent_time = 0. - self._next_query_print_time = self._end_home_time = 0. + self._min_query_time = self._last_sent_time = self._end_home_time = 0. self._trigger_completion = self._home_completion = None def get_mcu(self): return self._mcu @@ -34,23 +34,31 @@ class MCU_endstop: def get_steppers(self): return list(self._steppers) def _build_config(self): + # Setup config + self._ts_oid = self._mcu.create_oid() + self._mcu.add_config_cmd("config_trsync oid=%d" % (self._ts_oid,)) + self._mcu.add_config_cmd("trsync_trigger oid=%d reason=0" + % (self._ts_oid,), on_restart=True) self._oid = self._mcu.create_oid() - self._mcu.add_config_cmd( - "config_endstop oid=%d pin=%s pull_up=%d stepper_count=%d" % ( - self._oid, self._pin, self._pullup, len(self._steppers))) + self._mcu.add_config_cmd("config_endstop oid=%d pin=%s pull_up=%d" + % (self._oid, self._pin, self._pullup)) self._mcu.add_config_cmd( "endstop_home oid=%d clock=0 sample_ticks=0 sample_count=0" - " rest_ticks=0 pin_value=0" % (self._oid,), on_restart=True) - for i, s in enumerate(self._steppers): - self._mcu.add_config_cmd( - "endstop_set_stepper oid=%d pos=%d stepper_oid=%d" % ( - self._oid, i, s.get_oid()), is_init=True) + " rest_ticks=0 pin_value=0 trsync_oid=0 trigger_reason=0" + % (self._oid,), on_restart=True) + # Lookup commands cmd_queue = self._mcu.alloc_command_queue() + self._trsync_start_cmd = self._mcu.lookup_command( + "trsync_start oid=%c report_clock=%u report_ticks=%u" + " expire_reason=%c", cq=cmd_queue) + self._trsync_trigger_cmd = self._mcu.lookup_command( + "trsync_trigger oid=%c reason=%c", cq=cmd_queue) + self._stepper_stop_cmd = self._mcu.lookup_command( + "stepper_stop_on_trigger oid=%c trsync_oid=%c", cq=cmd_queue) self._home_cmd = self._mcu.lookup_command( "endstop_home oid=%c clock=%u sample_ticks=%u sample_count=%c" - " rest_ticks=%u pin_value=%c", cq=cmd_queue) - self._requery_cmd = self._mcu.lookup_command( - "endstop_query_state oid=%c", cq=cmd_queue) + " rest_ticks=%u pin_value=%c trsync_oid=%c trigger_reason=%c", + cq=cmd_queue) self._query_cmd = self._mcu.lookup_query_command( "endstop_query_state oid=%c", "endstop_state oid=%c homing=%c next_clock=%u pin_value=%c", @@ -59,24 +67,28 @@ class MCU_endstop: triggered=True): clock = self._mcu.print_time_to_clock(print_time) rest_ticks = self._mcu.print_time_to_clock(print_time+rest_time) - clock - self._next_query_print_time = print_time + self.RETRY_QUERY self._min_query_time = self._reactor.monotonic() self._last_sent_time = 0. self._home_end_time = self._reactor.NEVER self._trigger_completion = self._reactor.completion() - self._mcu.register_response(self._handle_endstop_state, - "endstop_state", self._oid) + self._mcu.register_response(self._handle_trsync_state, + "trsync_state", self._ts_oid) + report_ticks = self._mcu.seconds_to_clock(0.100) + self._trsync_start_cmd.send([self._ts_oid, clock, report_ticks, 0], + reqclock=clock) + for s in self._steppers: + self._stepper_stop_cmd.send([s.get_oid(), self._ts_oid]) self._home_cmd.send( [self._oid, clock, self._mcu.seconds_to_clock(sample_time), - sample_count, rest_ticks, triggered ^ self._invert], - reqclock=clock) + sample_count, rest_ticks, triggered ^ self._invert, + self._ts_oid, 0], reqclock=clock) self._home_completion = self._reactor.register_callback( self._home_retry) return self._trigger_completion - def _handle_endstop_state(self, params): - logging.debug("endstop_state %s", params) + def _handle_trsync_state(self, params): + logging.debug("trsync_state %s", params) if params['#sent_time'] >= self._min_query_time: - if params['homing']: + if params['can_trigger']: self._last_sent_time = params['#sent_time'] else: self._min_query_time = self._reactor.NEVER @@ -93,17 +105,12 @@ class MCU_endstop: last = self._mcu.estimated_print_time(self._last_sent_time) if last > self._home_end_time or self._mcu.is_shutdown(): return False - # Check for resend - eventtime = self._reactor.monotonic() - est_print_time = self._mcu.estimated_print_time(eventtime) - if est_print_time >= self._next_query_print_time: - self._next_query_print_time = est_print_time + self.RETRY_QUERY - self._requery_cmd.send([self._oid]) def home_wait(self, home_end_time): self._home_end_time = home_end_time did_trigger = self._home_completion.wait() - self._mcu.register_response(None, "endstop_state", self._oid) - self._home_cmd.send([self._oid, 0, 0, 0, 0, 0]) + self._trsync_trigger_cmd.send([self._ts_oid, 0]) + self._mcu.register_response(None, "trsync_state", self._ts_oid) + self._home_cmd.send([self._oid, 0, 0, 0, 0, 0, 0, 0]) for s in self._steppers: s.note_homing_end(did_trigger=did_trigger) if not self._trigger_completion.test(): diff --git a/src/Makefile b/src/Makefile index 2f2ab521..50f893c7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,7 +1,8 @@ # Main code build rules src-y += sched.c command.c basecmd.c debugcmds.c -src-$(CONFIG_HAVE_GPIO) += initial_pins.c gpiocmds.c stepper.c endstop.c +src-$(CONFIG_HAVE_GPIO) += initial_pins.c gpiocmds.c stepper.c endstop.c \ + trsync.c src-$(CONFIG_HAVE_GPIO_ADC) += adccmds.c src-$(CONFIG_HAVE_GPIO_SPI) += spicmds.c thermocouple.c src-$(CONFIG_HAVE_GPIO_I2C) += i2ccmds.c diff --git a/src/endstop.c b/src/endstop.c index 96f8e805..77915398 100644 --- a/src/endstop.c +++ b/src/endstop.c @@ -1,6 +1,6 @@ // Handling of end stops. // -// Copyright (C) 2016-2019 Kevin O'Connor +// Copyright (C) 2016-2021 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -9,30 +9,17 @@ #include "board/irq.h" // irq_disable #include "command.h" // DECL_COMMAND #include "sched.h" // struct timer -#include "stepper.h" // stepper_stop +#include "trsync.h" // trsync_do_trigger struct endstop { struct timer time; struct gpio_in pin; uint32_t rest_time, sample_time, nextwake; - uint8_t flags, stepper_count, sample_count, trigger_count; - struct stepper *steppers[0]; + struct trsync *ts; + uint8_t flags, sample_count, trigger_count, trigger_reason; }; -enum { ESF_PIN_HIGH=1<<0, ESF_HOMING=1<<1, ESF_REPORT=1<<2 }; - -static struct task_wake endstop_wake; - -static void -stop_steppers(struct endstop *e) -{ - e->flags = ESF_REPORT; - uint8_t count = e->stepper_count; - while (count--) - if (e->steppers[count]) - stepper_stop(e->steppers[count]); - sched_wake_task(&endstop_wake); -} +enum { ESF_PIN_HIGH=1<<0, ESF_HOMING=1<<1 }; static uint_fast8_t endstop_oversample_event(struct timer *t); @@ -68,7 +55,7 @@ endstop_oversample_event(struct timer *t) } uint8_t count = e->trigger_count - 1; if (!count) { - stop_steppers(e); + trsync_do_trigger(e->ts, e->trigger_reason); return SF_DONE; } e->trigger_count = count; @@ -79,28 +66,10 @@ endstop_oversample_event(struct timer *t) void command_config_endstop(uint32_t *args) { - uint8_t stepper_count = args[3]; - struct endstop *e = oid_alloc( - args[0], command_config_endstop - , sizeof(*e) + sizeof(e->steppers[0]) * stepper_count); + struct endstop *e = oid_alloc(args[0], command_config_endstop, sizeof(*e)); e->pin = gpio_in_setup(args[1], args[2]); - e->stepper_count = stepper_count; - e->sample_count = 1; } -DECL_COMMAND(command_config_endstop, - "config_endstop oid=%c pin=%c pull_up=%c stepper_count=%c"); - -void -command_endstop_set_stepper(uint32_t *args) -{ - struct endstop *e = oid_lookup(args[0], command_config_endstop); - uint8_t pos = args[1]; - if (pos >= e->stepper_count) - shutdown("Set stepper past maximum stepper count"); - e->steppers[pos] = stepper_oid_lookup(args[2]); -} -DECL_COMMAND(command_endstop_set_stepper, - "endstop_set_stepper oid=%c pos=%c stepper_oid=%c"); +DECL_COMMAND(command_config_endstop, "config_endstop oid=%c pin=%c pull_up=%c"); // Home an axis void @@ -113,6 +82,7 @@ command_endstop_home(uint32_t *args) e->sample_count = args[3]; if (!e->sample_count) { // Disable end stop checking + e->ts = NULL; e->flags = 0; return; } @@ -120,45 +90,26 @@ command_endstop_home(uint32_t *args) e->time.func = endstop_event; e->trigger_count = e->sample_count; e->flags = ESF_HOMING | (args[5] ? ESF_PIN_HIGH : 0); + e->ts = trsync_oid_lookup(args[6]); + e->trigger_reason = args[7]; sched_add_timer(&e->time); } DECL_COMMAND(command_endstop_home, "endstop_home oid=%c clock=%u sample_ticks=%u sample_count=%c" - " rest_ticks=%u pin_value=%c"); - -static void -endstop_report(uint8_t oid, struct endstop *e) -{ - irq_disable(); - uint8_t eflags = e->flags; - e->flags &= ~ESF_REPORT; - uint32_t nextwake = e->nextwake; - irq_enable(); - - sendf("endstop_state oid=%c homing=%c next_clock=%u pin_value=%c" - , oid, !!(eflags & ESF_HOMING), nextwake, gpio_in_read(e->pin)); -} + " rest_ticks=%u pin_value=%c trsync_oid=%c trigger_reason=%c"); void command_endstop_query_state(uint32_t *args) { uint8_t oid = args[0]; struct endstop *e = oid_lookup(oid, command_config_endstop); - endstop_report(oid, e); + + irq_disable(); + uint8_t eflags = e->flags; + uint32_t nextwake = e->nextwake; + irq_enable(); + + sendf("endstop_state oid=%c homing=%c next_clock=%u pin_value=%c" + , oid, !!(eflags & ESF_HOMING), nextwake, gpio_in_read(e->pin)); } DECL_COMMAND(command_endstop_query_state, "endstop_query_state oid=%c"); - -void -endstop_task(void) -{ - if (!sched_check_wake(&endstop_wake)) - return; - uint8_t oid; - struct endstop *e; - foreach_oid(oid, e, command_config_endstop) { - if (!(e->flags & ESF_REPORT)) - continue; - endstop_report(oid, e); - } -} -DECL_TASK(endstop_task); diff --git a/src/stepper.c b/src/stepper.c index 83da362d..8c822d66 100644 --- a/src/stepper.c +++ b/src/stepper.c @@ -1,6 +1,6 @@ // Handling of stepper drivers. // -// Copyright (C) 2016-2019 Kevin O'Connor +// Copyright (C) 2016-2021 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -11,7 +11,8 @@ #include "board/misc.h" // timer_is_before #include "command.h" // DECL_COMMAND #include "sched.h" // struct timer -#include "stepper.h" // command_config_stepper +#include "stepper.h" // stepper_event +#include "trsync.h" // trsync_add_signal DECL_CONSTANT("STEP_DELAY", CONFIG_STEP_DELAY); @@ -44,6 +45,7 @@ struct stepper { struct gpio_out step_pin, dir_pin; uint32_t position; struct move_queue_head mq; + struct trsync_signal stop_signal; // gcc (pre v6) does better optimization when uint8_t are bitfields uint8_t flags : 8; }; @@ -192,7 +194,7 @@ DECL_COMMAND(command_config_stepper, "config_stepper oid=%c step_pin=%c dir_pin=%c invert_step=%c"); // Return the 'struct stepper' for a given stepper oid -struct stepper * +static struct stepper * stepper_oid_lookup(uint8_t oid) { return oid_lookup(oid, command_config_stepper); @@ -290,11 +292,11 @@ command_stepper_get_position(uint32_t *args) } DECL_COMMAND(command_stepper_get_position, "stepper_get_position oid=%c"); -// Stop all moves for a given stepper (used in end stop homing). IRQs -// must be off. -void -stepper_stop(struct stepper *s) +// Stop all moves for a given stepper (caller must disable IRQs) +static void +stepper_stop(struct trsync_signal *tss, uint8_t reason) { + struct stepper *s = container_of(tss, struct stepper, stop_signal); sched_del_timer(&s->time); s->next_step_time = 0; s->position = -stepper_get_position(s); @@ -309,6 +311,17 @@ stepper_stop(struct stepper *s) } } +// Set the stepper to stop on a "trigger event" (used in homing) +void +command_stepper_stop_on_trigger(uint32_t *args) +{ + struct stepper *s = stepper_oid_lookup(args[0]); + struct trsync *ts = trsync_oid_lookup(args[1]); + trsync_add_signal(ts, &s->stop_signal, stepper_stop); +} +DECL_COMMAND(command_stepper_stop_on_trigger, + "stepper_stop_on_trigger oid=%c trsync_oid=%c"); + void stepper_shutdown(void) { @@ -316,7 +329,7 @@ stepper_shutdown(void) struct stepper *s; foreach_oid(i, s, command_config_stepper) { move_queue_clear(&s->mq); - stepper_stop(s); + stepper_stop(&s->stop_signal, 0); } } DECL_SHUTDOWN(stepper_shutdown); diff --git a/src/stepper.h b/src/stepper.h index 8e27b789..db29eed0 100644 --- a/src/stepper.h +++ b/src/stepper.h @@ -4,7 +4,5 @@ #include // uint8_t uint_fast8_t stepper_event(struct timer *t); -struct stepper *stepper_oid_lookup(uint8_t oid); -void stepper_stop(struct stepper *s); #endif // stepper.h diff --git a/src/trsync.c b/src/trsync.c new file mode 100644 index 00000000..3bf7aa66 --- /dev/null +++ b/src/trsync.c @@ -0,0 +1,200 @@ +// Handling of synchronized "trigger" dispatch +// +// Copyright (C) 2016-2021 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "basecmd.h" // oid_alloc +#include "board/gpio.h" // struct gpio +#include "board/irq.h" // irq_disable +#include "board/misc.h" // timer_read_time +#include "command.h" // DECL_COMMAND +#include "sched.h" // struct timer +#include "trsync.h" // trsync_do_trigger + +struct trsync { + struct timer report_time, expire_time; + uint32_t report_ticks; + struct trsync_signal *signals; + uint8_t flags, trigger_reason, expire_reason; +}; + +enum { TSF_CAN_TRIGGER=1<<0, TSF_REPORT=1<<2 }; + +static struct task_wake trsync_wake; + +// Activate a trigger (caller must disable IRQs) +void +trsync_do_trigger(struct trsync *ts, uint8_t reason) +{ + uint8_t flags = ts->flags; + if (!(flags & TSF_CAN_TRIGGER)) + return; + ts->trigger_reason = reason; + ts->flags = (flags & ~TSF_CAN_TRIGGER) | TSF_REPORT; + // Dispatch signals + while (ts->signals) { + struct trsync_signal *tss = ts->signals; + trsync_callback_t func = tss->func; + ts->signals = tss->next; + tss->next = NULL; + tss->func = NULL; + func(tss, reason); + } + sched_wake_task(&trsync_wake); +} + +// Timeout handler +static uint_fast8_t +trsync_expire_event(struct timer *t) +{ + struct trsync *ts = container_of(t, struct trsync, expire_time); + trsync_do_trigger(ts, ts->expire_reason); + return SF_DONE; +} + +// Report handler +static uint_fast8_t +trsync_report_event(struct timer *t) +{ + struct trsync *ts = container_of(t, struct trsync, report_time); + ts->flags |= TSF_REPORT; + sched_wake_task(&trsync_wake); + ts->report_time.waketime += ts->report_ticks; + return SF_RESCHEDULE; +} + +void +command_config_trsync(uint32_t *args) +{ + struct trsync *ts = oid_alloc(args[0], command_config_trsync, sizeof(*ts)); + ts->report_time.func = trsync_report_event; + ts->expire_time.func = trsync_expire_event; +} +DECL_COMMAND(command_config_trsync, "config_trsync oid=%c"); + +// Return the 'struct trsync' for a given trsync oid +struct trsync * +trsync_oid_lookup(uint8_t oid) +{ + return oid_lookup(oid, command_config_trsync); +} + +// Add a callback to invoke on a trigger +void +trsync_add_signal(struct trsync *ts, struct trsync_signal *tss + , trsync_callback_t func) +{ + irqstatus_t flag = irq_save(); + if (tss->func || !func) + shutdown("Can't add signal that is already active"); + tss->func = func; + tss->next = ts->signals; + ts->signals = tss; + irq_restore(flag); +} + +// Disable trigger and unregister any signal handlers (caller must disable IRQs) +static void +trsync_clear(struct trsync *ts) +{ + sched_del_timer(&ts->report_time); + sched_del_timer(&ts->expire_time); + struct trsync_signal *tss = ts->signals; + while (tss) { + struct trsync_signal *next = tss->next; + tss->func = NULL; + tss->next = NULL; + tss = next; + } + ts->signals = NULL; + ts->flags = ts->trigger_reason = ts->expire_reason = 0; +} + +void +command_trsync_start(uint32_t *args) +{ + struct trsync *ts = trsync_oid_lookup(args[0]); + irq_disable(); + trsync_clear(ts); + ts->flags = TSF_CAN_TRIGGER; + ts->report_time.waketime = args[1]; + ts->report_ticks = args[2]; + if (ts->report_ticks) + sched_add_timer(&ts->report_time); + ts->expire_reason = args[3]; + irq_enable(); +} +DECL_COMMAND(command_trsync_start, + "trsync_start oid=%c report_clock=%u report_ticks=%u" + " expire_reason=%c"); + +void +command_trsync_set_timeout(uint32_t *args) +{ + struct trsync *ts = trsync_oid_lookup(args[0]); + irq_disable(); + uint8_t flags = ts->flags; + if (flags & TSF_CAN_TRIGGER) { + sched_del_timer(&ts->expire_time); + ts->expire_time.waketime = args[1]; + sched_add_timer(&ts->expire_time); + } + irq_enable(); +} +DECL_COMMAND(command_trsync_set_timeout, "trsync_set_timeout oid=%c clock=%u"); + +static void +trsync_report(uint8_t oid, uint8_t flags, uint8_t reason, uint32_t clock) +{ + sendf("trsync_state oid=%c can_trigger=%c trigger_reason=%c clock=%u" + , oid, !!(flags & TSF_CAN_TRIGGER), reason, clock); +} + +void +command_trsync_trigger(uint32_t *args) +{ + uint8_t oid = args[0]; + struct trsync *ts = trsync_oid_lookup(oid); + irq_disable(); + trsync_do_trigger(ts, args[1]); + sched_del_timer(&ts->report_time); + sched_del_timer(&ts->expire_time); + ts->flags = 0; + uint8_t trigger_reason = ts->trigger_reason; + irq_enable(); + trsync_report(oid, 0, trigger_reason, 0); +} +DECL_COMMAND(command_trsync_trigger, "trsync_trigger oid=%c reason=%c"); + +void +trsync_task(void) +{ + if (!sched_check_wake(&trsync_wake)) + return; + uint8_t oid; + struct trsync *ts; + foreach_oid(oid, ts, command_config_trsync) { + if (!(ts->flags & TSF_REPORT)) + continue; + uint32_t time = timer_read_time(); + irq_disable(); + uint8_t trigger_reason = ts->trigger_reason, flags = ts->flags; + ts->flags = flags & ~TSF_REPORT; + irq_enable(); + + trsync_report(oid, flags, trigger_reason, time); + } +} +DECL_TASK(trsync_task); + +void +trsync_shutdown(void) +{ + uint8_t oid; + struct trsync *ts; + foreach_oid(oid, ts, command_config_trsync) { + trsync_clear(ts); + } +} +DECL_SHUTDOWN(trsync_shutdown); diff --git a/src/trsync.h b/src/trsync.h new file mode 100644 index 00000000..c7cc9b54 --- /dev/null +++ b/src/trsync.h @@ -0,0 +1,19 @@ +#ifndef __TRSYNC_H +#define __TRSYNC_H + +#include // uint16_t + +struct trsync_signal; +typedef void (*trsync_callback_t)(struct trsync_signal *tss, uint8_t reason); + +struct trsync_signal { + struct trsync_signal *next; + trsync_callback_t func; +}; + +struct trsync *trsync_oid_lookup(uint8_t oid); +void trsync_do_trigger(struct trsync *ts, uint8_t reason); +void trsync_add_signal(struct trsync *ts, struct trsync_signal *tss + , trsync_callback_t func); + +#endif // trsync.h From 2559a2dd5ad4c5e8341aeddb6e5a59967867cbd7 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 11 Feb 2021 20:43:36 -0500 Subject: [PATCH 28/51] pollreactor: Move C pollreactor code from serialqueue.c to its own file Signed-off-by: Kevin O'Connor --- klippy/chelper/__init__.py | 3 +- klippy/chelper/pollreactor.c | 179 ++++++++++++++++++++++++++++ klippy/chelper/pollreactor.h | 20 ++++ klippy/chelper/serialqueue.c | 225 ++++------------------------------- 4 files changed, 226 insertions(+), 201 deletions(-) create mode 100644 klippy/chelper/pollreactor.c create mode 100644 klippy/chelper/pollreactor.h diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py index 4d199126..07ee190d 100644 --- a/klippy/chelper/__init__.py +++ b/klippy/chelper/__init__.py @@ -18,6 +18,7 @@ COMPILE_ARGS = ("-Wall -g -O2 -shared -fPIC" SSE_FLAGS = "-mfpmath=sse -msse2" SOURCE_FILES = [ 'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'itersolve.c', 'trapq.c', + 'pollreactor.c', 'kin_cartesian.c', 'kin_corexy.c', 'kin_corexz.c', 'kin_delta.c', 'kin_polar.c', 'kin_rotary_delta.c', 'kin_winch.c', 'kin_extruder.c', 'kin_shaper.c', @@ -25,7 +26,7 @@ SOURCE_FILES = [ DEST_LIB = "c_helper.so" OTHER_FILES = [ 'list.h', 'serialqueue.h', 'stepcompress.h', 'itersolve.h', 'pyhelper.h', - 'trapq.h', + 'trapq.h', 'pollreactor.h', ] defs_stepcompress = """ diff --git a/klippy/chelper/pollreactor.c b/klippy/chelper/pollreactor.c new file mode 100644 index 00000000..eb12e5a8 --- /dev/null +++ b/klippy/chelper/pollreactor.c @@ -0,0 +1,179 @@ +// Code for dispatching timer and file descriptor events +// +// Copyright (C) 2016-2021 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // fcntl +#include // ceil +#include // poll +#include // malloc +#include // memset +#include "pollreactor.h" // pollreactor_alloc +#include "pyhelper.h" // report_errno + +struct pollreactor_timer { + double waketime; + double (*callback)(void *data, double eventtime); +}; + +struct pollreactor { + int num_fds, num_timers, must_exit; + void *callback_data; + double next_timer; + struct pollfd *fds; + void (**fd_callbacks)(void *data, double eventtime); + struct pollreactor_timer *timers; +}; + +// Allocate a new 'struct pollreactor' object +struct pollreactor * +pollreactor_alloc(int num_fds, int num_timers, void *callback_data) +{ + struct pollreactor *pr = malloc(sizeof(*pr)); + memset(pr, 0, sizeof(*pr)); + pr->num_fds = num_fds; + pr->num_timers = num_timers; + pr->must_exit = 0; + pr->callback_data = callback_data; + pr->next_timer = PR_NEVER; + pr->fds = malloc(num_fds * sizeof(*pr->fds)); + memset(pr->fds, 0, num_fds * sizeof(*pr->fds)); + pr->fd_callbacks = malloc(num_fds * sizeof(*pr->fd_callbacks)); + memset(pr->fd_callbacks, 0, num_fds * sizeof(*pr->fd_callbacks)); + pr->timers = malloc(num_timers * sizeof(*pr->timers)); + memset(pr->timers, 0, num_timers * sizeof(*pr->timers)); + int i; + for (i=0; itimers[i].waketime = PR_NEVER; + return pr; +} + +// Free resources associated with a 'struct pollreactor' object +void +pollreactor_free(struct pollreactor *pr) +{ + free(pr->fds); + pr->fds = NULL; + free(pr->fd_callbacks); + pr->fd_callbacks = NULL; + free(pr->timers); + pr->timers = NULL; + free(pr); +} + +// Add a callback for when a file descriptor (fd) becomes readable +void +pollreactor_add_fd(struct pollreactor *pr, int pos, int fd, void *callback + , int write_only) +{ + pr->fds[pos].fd = fd; + pr->fds[pos].events = POLLHUP | (write_only ? 0 : POLLIN); + pr->fds[pos].revents = 0; + pr->fd_callbacks[pos] = callback; +} + +// Add a timer callback +void +pollreactor_add_timer(struct pollreactor *pr, int pos, void *callback) +{ + pr->timers[pos].callback = callback; + pr->timers[pos].waketime = PR_NEVER; +} + +// Return the last schedule wake-up time for a timer +double +pollreactor_get_timer(struct pollreactor *pr, int pos) +{ + return pr->timers[pos].waketime; +} + +// Set the wake-up time for a given timer +void +pollreactor_update_timer(struct pollreactor *pr, int pos, double waketime) +{ + pr->timers[pos].waketime = waketime; + if (waketime < pr->next_timer) + pr->next_timer = waketime; +} + +// Internal code to invoke timer callbacks +static int +pollreactor_check_timers(struct pollreactor *pr, double eventtime, int busy) +{ + if (eventtime >= pr->next_timer) { + // Find and run pending timers + pr->next_timer = PR_NEVER; + int i; + for (i=0; inum_timers; i++) { + struct pollreactor_timer *timer = &pr->timers[i]; + double t = timer->waketime; + if (eventtime >= t) { + busy = 1; + t = timer->callback(pr->callback_data, eventtime); + timer->waketime = t; + } + if (t < pr->next_timer) + pr->next_timer = t; + } + } + if (busy) + return 0; + // Calculate sleep duration + double timeout = ceil((pr->next_timer - eventtime) * 1000.); + return timeout < 1. ? 1 : (timeout > 1000. ? 1000 : (int)timeout); +} + +// Repeatedly check for timer and fd events and invoke their callbacks +void +pollreactor_run(struct pollreactor *pr) +{ + double eventtime = get_monotonic(); + int busy = 1; + while (! pr->must_exit) { + int timeout = pollreactor_check_timers(pr, eventtime, busy); + busy = 0; + int ret = poll(pr->fds, pr->num_fds, timeout); + eventtime = get_monotonic(); + if (ret > 0) { + busy = 1; + int i; + for (i=0; inum_fds; i++) + if (pr->fds[i].revents) + pr->fd_callbacks[i](pr->callback_data, eventtime); + } else if (ret < 0) { + report_errno("poll", ret); + pr->must_exit = 1; + } + } +} + +// Request that a currently running pollreactor_run() loop exit +void +pollreactor_do_exit(struct pollreactor *pr) +{ + pr->must_exit = 1; +} + +// Check if a pollreactor_run() loop has been requested to exit +int +pollreactor_is_exit(struct pollreactor *pr) +{ + return pr->must_exit; +} + +int +fd_set_non_blocking(int fd) +{ + int flags = fcntl(fd, F_GETFL); + if (flags < 0) { + report_errno("fcntl getfl", flags); + return -1; + } + int ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + if (ret < 0) { + report_errno("fcntl setfl", flags); + return -1; + } + return 0; +} diff --git a/klippy/chelper/pollreactor.h b/klippy/chelper/pollreactor.h new file mode 100644 index 00000000..97d7d3a9 --- /dev/null +++ b/klippy/chelper/pollreactor.h @@ -0,0 +1,20 @@ +#ifndef POLLREACTOR_H +#define POLLREACTOR_H + +#define PR_NOW 0. +#define PR_NEVER 9999999999999999. + +struct pollreactor *pollreactor_alloc(int num_fds, int num_timers + , void *callback_data); +void pollreactor_free(struct pollreactor *pr); +void pollreactor_add_fd(struct pollreactor *pr, int pos, int fd, void *callback + , int write_only); +void pollreactor_add_timer(struct pollreactor *pr, int pos, void *callback); +double pollreactor_get_timer(struct pollreactor *pr, int pos); +void pollreactor_update_timer(struct pollreactor *pr, int pos, double waketime); +void pollreactor_run(struct pollreactor *pr); +void pollreactor_do_exit(struct pollreactor *pr); +int pollreactor_is_exit(struct pollreactor *pr); +int fd_set_non_blocking(int fd); + +#endif // pollreactor.h diff --git a/klippy/chelper/serialqueue.c b/klippy/chelper/serialqueue.c index 77d547bb..38f65199 100644 --- a/klippy/chelper/serialqueue.c +++ b/klippy/chelper/serialqueue.c @@ -12,10 +12,8 @@ // clock times, prioritizes commands, and handles retransmissions. A // background thread is launched to do this work and minimize latency. -#include // fcntl #include // // struct can_frame -#include // ceil -#include // poll +#include // fabs #include // pthread_mutex_lock #include // offsetof #include // uint64_t @@ -26,184 +24,11 @@ #include // pipe #include "compiler.h" // __visible #include "list.h" // list_add_tail +#include "pollreactor.h" // pollreactor_alloc #include "pyhelper.h" // get_monotonic #include "serialqueue.h" // struct queue_message -/**************************************************************** - * Poll reactor - ****************************************************************/ - -// The 'poll reactor' code is a mechanism for dispatching timer and -// file descriptor events. - -#define PR_NOW 0. -#define PR_NEVER 9999999999999999. - -struct pollreactor_timer { - double waketime; - double (*callback)(void *data, double eventtime); -}; - -struct pollreactor { - int num_fds, num_timers, must_exit; - void *callback_data; - double next_timer; - struct pollfd *fds; - void (**fd_callbacks)(void *data, double eventtime); - struct pollreactor_timer *timers; -}; - -// Allocate a new 'struct pollreactor' object -static void -pollreactor_setup(struct pollreactor *pr, int num_fds, int num_timers - , void *callback_data) -{ - pr->num_fds = num_fds; - pr->num_timers = num_timers; - pr->must_exit = 0; - pr->callback_data = callback_data; - pr->next_timer = PR_NEVER; - pr->fds = malloc(num_fds * sizeof(*pr->fds)); - memset(pr->fds, 0, num_fds * sizeof(*pr->fds)); - pr->fd_callbacks = malloc(num_fds * sizeof(*pr->fd_callbacks)); - memset(pr->fd_callbacks, 0, num_fds * sizeof(*pr->fd_callbacks)); - pr->timers = malloc(num_timers * sizeof(*pr->timers)); - memset(pr->timers, 0, num_timers * sizeof(*pr->timers)); - int i; - for (i=0; itimers[i].waketime = PR_NEVER; -} - -// Free resources associated with a 'struct pollreactor' object -static void -pollreactor_free(struct pollreactor *pr) -{ - free(pr->fds); - pr->fds = NULL; - free(pr->fd_callbacks); - pr->fd_callbacks = NULL; - free(pr->timers); - pr->timers = NULL; -} - -// Add a callback for when a file descriptor (fd) becomes readable -static void -pollreactor_add_fd(struct pollreactor *pr, int pos, int fd, void *callback - , int write_only) -{ - pr->fds[pos].fd = fd; - pr->fds[pos].events = POLLHUP | (write_only ? 0 : POLLIN); - pr->fds[pos].revents = 0; - pr->fd_callbacks[pos] = callback; -} - -// Add a timer callback -static void -pollreactor_add_timer(struct pollreactor *pr, int pos, void *callback) -{ - pr->timers[pos].callback = callback; - pr->timers[pos].waketime = PR_NEVER; -} - -// Return the last schedule wake-up time for a timer -static double -pollreactor_get_timer(struct pollreactor *pr, int pos) -{ - return pr->timers[pos].waketime; -} - -// Set the wake-up time for a given timer -static void -pollreactor_update_timer(struct pollreactor *pr, int pos, double waketime) -{ - pr->timers[pos].waketime = waketime; - if (waketime < pr->next_timer) - pr->next_timer = waketime; -} - -// Internal code to invoke timer callbacks -static int -pollreactor_check_timers(struct pollreactor *pr, double eventtime, int busy) -{ - if (eventtime >= pr->next_timer) { - // Find and run pending timers - pr->next_timer = PR_NEVER; - int i; - for (i=0; inum_timers; i++) { - struct pollreactor_timer *timer = &pr->timers[i]; - double t = timer->waketime; - if (eventtime >= t) { - busy = 1; - t = timer->callback(pr->callback_data, eventtime); - timer->waketime = t; - } - if (t < pr->next_timer) - pr->next_timer = t; - } - } - if (busy) - return 0; - // Calculate sleep duration - double timeout = ceil((pr->next_timer - eventtime) * 1000.); - return timeout < 1. ? 1 : (timeout > 1000. ? 1000 : (int)timeout); -} - -// Repeatedly check for timer and fd events and invoke their callbacks -static void -pollreactor_run(struct pollreactor *pr) -{ - double eventtime = get_monotonic(); - int busy = 1; - while (! pr->must_exit) { - int timeout = pollreactor_check_timers(pr, eventtime, busy); - busy = 0; - int ret = poll(pr->fds, pr->num_fds, timeout); - eventtime = get_monotonic(); - if (ret > 0) { - busy = 1; - int i; - for (i=0; inum_fds; i++) - if (pr->fds[i].revents) - pr->fd_callbacks[i](pr->callback_data, eventtime); - } else if (ret < 0) { - report_errno("poll", ret); - pr->must_exit = 1; - } - } -} - -// Request that a currently running pollreactor_run() loop exit -static void -pollreactor_do_exit(struct pollreactor *pr) -{ - pr->must_exit = 1; -} - -// Check if a pollreactor_run() loop has been requested to exit -static int -pollreactor_is_exit(struct pollreactor *pr) -{ - return pr->must_exit; -} - -static int -set_non_blocking(int fd) -{ - int flags = fcntl(fd, F_GETFL); - if (flags < 0) { - report_errno("fcntl getfl", flags); - return -1; - } - int ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK); - if (ret < 0) { - report_errno("fcntl setfl", flags); - return -1; - } - return 0; -} - - /**************************************************************** * Serial protocol helpers ****************************************************************/ @@ -354,7 +179,7 @@ message_queue_free(struct list_head *root) struct serialqueue { // Input reading - struct pollreactor pr; + struct pollreactor *pr; int serial_fd, serial_fd_type, client_id; int pipe_fds[2]; uint8_t input_buf[4096]; @@ -479,7 +304,7 @@ update_receive_seq(struct serialqueue *sq, double eventtime, uint64_t rseq) } } sq->receive_seq = rseq; - pollreactor_update_timer(&sq->pr, SQPT_COMMAND, PR_NOW); + pollreactor_update_timer(sq->pr, SQPT_COMMAND, PR_NOW); // Update retransmit info if (sq->rtt_sample_seq && rseq > sq->rtt_sample_seq @@ -504,12 +329,12 @@ update_receive_seq(struct serialqueue *sq, double eventtime, uint64_t rseq) sq->rtt_sample_seq = 0; } if (list_empty(&sq->sent_queue)) { - pollreactor_update_timer(&sq->pr, SQPT_RETRANSMIT, PR_NEVER); + pollreactor_update_timer(sq->pr, SQPT_RETRANSMIT, PR_NEVER); } else { struct queue_message *sent = list_first_entry( &sq->sent_queue, struct queue_message, node); double nr = eventtime + sq->rto + sent->len * sq->baud_adjust; - pollreactor_update_timer(&sq->pr, SQPT_RETRANSMIT, nr); + pollreactor_update_timer(sq->pr, SQPT_RETRANSMIT, nr); } } @@ -554,7 +379,7 @@ handle_message(struct serialqueue *sq, double eventtime, int len) sq->last_ack_seq = rseq; else if (rseq > sq->ignore_nak_seq && !list_empty(&sq->sent_queue)) // Duplicate Ack is a Nak - do fast retransmit - pollreactor_update_timer(&sq->pr, SQPT_RETRANSMIT, PR_NOW); + pollreactor_update_timer(sq->pr, SQPT_RETRANSMIT, PR_NOW); } else { // Data message - add to receive queue struct queue_message *qm = message_fill(sq->input_buf, len); @@ -580,7 +405,7 @@ input_event(struct serialqueue *sq, double eventtime) int ret = read(sq->serial_fd, &cf, sizeof(cf)); if (ret <= 0) { report_errno("can read", ret); - pollreactor_do_exit(&sq->pr); + pollreactor_do_exit(sq->pr); return; } if (cf.can_id != sq->client_id + 1) @@ -595,7 +420,7 @@ input_event(struct serialqueue *sq, double eventtime) report_errno("read", ret); else errorf("Got EOF when reading from device"); - pollreactor_do_exit(&sq->pr); + pollreactor_do_exit(sq->pr); return; } sq->input_pos += ret; @@ -635,7 +460,7 @@ kick_event(struct serialqueue *sq, double eventtime) int ret = read(sq->pipe_fds[0], dummy, sizeof(dummy)); if (ret < 0) report_errno("pipe read", ret); - pollreactor_update_timer(&sq->pr, SQPT_COMMAND, PR_NOW); + pollreactor_update_timer(sq->pr, SQPT_COMMAND, PR_NOW); } static void @@ -691,7 +516,7 @@ retransmit_event(struct serialqueue *sq, double eventtime) sq->bytes_retransmit += buflen; // Update rto - if (pollreactor_get_timer(&sq->pr, SQPT_RETRANSMIT) == PR_NOW) { + if (pollreactor_get_timer(sq->pr, SQPT_RETRANSMIT) == PR_NOW) { // Retransmit due to nak sq->ignore_nak_seq = sq->receive_seq; if (sq->receive_seq < sq->retransmit_seq) @@ -771,7 +596,7 @@ build_and_send_command(struct serialqueue *sq, uint8_t *buf, double eventtime) out->sent_time = eventtime; out->receive_time = sq->idle_time; if (list_empty(&sq->sent_queue)) - pollreactor_update_timer(&sq->pr, SQPT_RETRANSMIT + pollreactor_update_timer(sq->pr, SQPT_RETRANSMIT , sq->idle_time + sq->rto); if (!sq->rtt_sample_seq) sq->rtt_sample_seq = sq->send_seq; @@ -886,7 +711,7 @@ static void * background_thread(void *data) { struct serialqueue *sq = data; - pollreactor_run(&sq->pr); + pollreactor_run(sq->pr); pthread_mutex_lock(&sq->lock); check_wake_receive(sq); @@ -910,15 +735,15 @@ serialqueue_alloc(int serial_fd, char serial_fd_type, int client_id) goto fail; // Reactor setup - pollreactor_setup(&sq->pr, SQPF_NUM, SQPT_NUM, sq); - pollreactor_add_fd(&sq->pr, SQPF_SERIAL, serial_fd, input_event + sq->pr = pollreactor_alloc(SQPF_NUM, SQPT_NUM, sq); + pollreactor_add_fd(sq->pr, SQPF_SERIAL, serial_fd, input_event , serial_fd_type==SQT_DEBUGFILE); - pollreactor_add_fd(&sq->pr, SQPF_PIPE, sq->pipe_fds[0], kick_event, 0); - pollreactor_add_timer(&sq->pr, SQPT_RETRANSMIT, retransmit_event); - pollreactor_add_timer(&sq->pr, SQPT_COMMAND, command_event); - set_non_blocking(serial_fd); - set_non_blocking(sq->pipe_fds[0]); - set_non_blocking(sq->pipe_fds[1]); + pollreactor_add_fd(sq->pr, SQPF_PIPE, sq->pipe_fds[0], kick_event, 0); + pollreactor_add_timer(sq->pr, SQPT_RETRANSMIT, retransmit_event); + pollreactor_add_timer(sq->pr, SQPT_COMMAND, command_event); + fd_set_non_blocking(serial_fd); + fd_set_non_blocking(sq->pipe_fds[0]); + fd_set_non_blocking(sq->pipe_fds[1]); // Retransmit setup sq->send_seq = 1; @@ -966,7 +791,7 @@ fail: void __visible serialqueue_exit(struct serialqueue *sq) { - pollreactor_do_exit(&sq->pr); + pollreactor_do_exit(sq->pr); kick_bg_thread(sq); int ret = pthread_join(sq->tid, NULL); if (ret) @@ -979,7 +804,7 @@ serialqueue_free(struct serialqueue *sq) { if (!sq) return; - if (!pollreactor_is_exit(&sq->pr)) + if (!pollreactor_is_exit(sq->pr)) serialqueue_exit(sq); pthread_mutex_lock(&sq->lock); message_queue_free(&sq->sent_queue); @@ -995,7 +820,7 @@ serialqueue_free(struct serialqueue *sq) message_queue_free(&cq->stalled_queue); } pthread_mutex_unlock(&sq->lock); - pollreactor_free(&sq->pr); + pollreactor_free(sq->pr); free(sq); } @@ -1085,7 +910,7 @@ serialqueue_pull(struct serialqueue *sq, struct pull_queue_message *pqm) pthread_mutex_lock(&sq->lock); // Wait for message to be available while (list_empty(&sq->receive_queue)) { - if (pollreactor_is_exit(&sq->pr)) + if (pollreactor_is_exit(sq->pr)) goto exit; sq->receive_waiting = 1; int ret = pthread_cond_wait(&sq->cond, &sq->lock); From 1865080a0759b318a6bd9141aa7695de943736d5 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 17 Feb 2021 22:20:47 -0500 Subject: [PATCH 29/51] msgblock: Move message manipulation code from serialqueue.c to new msgblock.c Signed-off-by: Kevin O'Connor --- klippy/chelper/__init__.py | 4 +- klippy/chelper/msgblock.c | 150 +++++++++++++++++++++++++++++++++++ klippy/chelper/msgblock.h | 45 +++++++++++ klippy/chelper/serialqueue.c | 149 +--------------------------------- klippy/chelper/serialqueue.h | 34 +------- 5 files changed, 201 insertions(+), 181 deletions(-) create mode 100644 klippy/chelper/msgblock.c create mode 100644 klippy/chelper/msgblock.h diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py index 07ee190d..55f394ef 100644 --- a/klippy/chelper/__init__.py +++ b/klippy/chelper/__init__.py @@ -18,7 +18,7 @@ COMPILE_ARGS = ("-Wall -g -O2 -shared -fPIC" SSE_FLAGS = "-mfpmath=sse -msse2" SOURCE_FILES = [ 'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'itersolve.c', 'trapq.c', - 'pollreactor.c', + 'pollreactor.c', 'msgblock.c', 'kin_cartesian.c', 'kin_corexy.c', 'kin_corexz.c', 'kin_delta.c', 'kin_polar.c', 'kin_rotary_delta.c', 'kin_winch.c', 'kin_extruder.c', 'kin_shaper.c', @@ -26,7 +26,7 @@ SOURCE_FILES = [ DEST_LIB = "c_helper.so" OTHER_FILES = [ 'list.h', 'serialqueue.h', 'stepcompress.h', 'itersolve.h', 'pyhelper.h', - 'trapq.h', 'pollreactor.h', + 'trapq.h', 'pollreactor.h', 'msgblock.h' ] defs_stepcompress = """ diff --git a/klippy/chelper/msgblock.c b/klippy/chelper/msgblock.c new file mode 100644 index 00000000..cc91d455 --- /dev/null +++ b/klippy/chelper/msgblock.c @@ -0,0 +1,150 @@ +// Helper code for the Klipper mcu protocol "message blocks" +// +// Copyright (C) 2016-2021 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // offsetof +#include // malloc +#include // memset +#include "msgblock.h" // message_alloc +#include "pyhelper.h" // errorf + + +/**************************************************************** + * Serial protocol helpers + ****************************************************************/ + +// Implement the standard crc "ccitt" algorithm on the given buffer +uint16_t +msgblock_crc16_ccitt(uint8_t *buf, uint8_t len) +{ + uint16_t crc = 0xffff; + while (len--) { + uint8_t data = *buf++; + data ^= crc & 0xff; + data ^= data << 4; + crc = ((((uint16_t)data << 8) | (crc >> 8)) ^ (uint8_t)(data >> 4) + ^ ((uint16_t)data << 3)); + } + return crc; +} + +// Verify a buffer starts with a valid mcu message +int +msgblock_check(uint8_t *need_sync, uint8_t *buf, int buf_len) +{ + if (buf_len < MESSAGE_MIN) + // Need more data + return 0; + if (*need_sync) + goto error; + uint8_t msglen = buf[MESSAGE_POS_LEN]; + if (msglen < MESSAGE_MIN || msglen > MESSAGE_MAX) + goto error; + uint8_t msgseq = buf[MESSAGE_POS_SEQ]; + if ((msgseq & ~MESSAGE_SEQ_MASK) != MESSAGE_DEST) + goto error; + if (buf_len < msglen) + // Need more data + return 0; + if (buf[msglen-MESSAGE_TRAILER_SYNC] != MESSAGE_SYNC) + goto error; + uint16_t msgcrc = ((buf[msglen-MESSAGE_TRAILER_CRC] << 8) + | (uint8_t)buf[msglen-MESSAGE_TRAILER_CRC+1]); + uint16_t crc = msgblock_crc16_ccitt(buf, msglen-MESSAGE_TRAILER_SIZE); + if (crc != msgcrc) + goto error; + return msglen; + +error: ; + // Discard bytes until next SYNC found + uint8_t *next_sync = memchr(buf, MESSAGE_SYNC, buf_len); + if (next_sync) { + *need_sync = 0; + return -(next_sync - buf + 1); + } + *need_sync = 1; + return -buf_len; +} + +// Encode an integer as a variable length quantity (vlq) +static uint8_t * +encode_int(uint8_t *p, uint32_t v) +{ + int32_t sv = v; + if (sv < (3L<<5) && sv >= -(1L<<5)) goto f4; + if (sv < (3L<<12) && sv >= -(1L<<12)) goto f3; + if (sv < (3L<<19) && sv >= -(1L<<19)) goto f2; + if (sv < (3L<<26) && sv >= -(1L<<26)) goto f1; + *p++ = (v>>28) | 0x80; +f1: *p++ = ((v>>21) & 0x7f) | 0x80; +f2: *p++ = ((v>>14) & 0x7f) | 0x80; +f3: *p++ = ((v>>7) & 0x7f) | 0x80; +f4: *p++ = v & 0x7f; + return p; +} + + +/**************************************************************** + * Command queues + ****************************************************************/ + +// Allocate a 'struct queue_message' object +struct queue_message * +message_alloc(void) +{ + struct queue_message *qm = malloc(sizeof(*qm)); + memset(qm, 0, sizeof(*qm)); + return qm; +} + +// Allocate a queue_message and fill it with the specified data +struct queue_message * +message_fill(uint8_t *data, int len) +{ + struct queue_message *qm = message_alloc(); + memcpy(qm->msg, data, len); + qm->len = len; + return qm; +} + +// Allocate a queue_message and fill it with a series of encoded vlq integers +struct queue_message * +message_alloc_and_encode(uint32_t *data, int len) +{ + struct queue_message *qm = message_alloc(); + int i; + uint8_t *p = qm->msg; + for (i=0; i &qm->msg[MESSAGE_PAYLOAD_MAX]) + goto fail; + } + qm->len = p - qm->msg; + return qm; + +fail: + errorf("Encode error"); + qm->len = 0; + return qm; +} + +// Free the storage from a previous message_alloc() call +void +message_free(struct queue_message *qm) +{ + free(qm); +} + +// Free all the messages on a queue +void +message_queue_free(struct list_head *root) +{ + while (!list_empty(root)) { + struct queue_message *qm = list_first_entry( + root, struct queue_message, node); + list_del(&qm->node); + message_free(qm); + } +} diff --git a/klippy/chelper/msgblock.h b/klippy/chelper/msgblock.h new file mode 100644 index 00000000..cce1b2ee --- /dev/null +++ b/klippy/chelper/msgblock.h @@ -0,0 +1,45 @@ +#ifndef MSGBLOCK_H +#define MSGBLOCK_H + +#include // uint8_t +#include "list.h" // struct list_node + +#define MESSAGE_MIN 5 +#define MESSAGE_MAX 64 +#define MESSAGE_HEADER_SIZE 2 +#define MESSAGE_TRAILER_SIZE 3 +#define MESSAGE_POS_LEN 0 +#define MESSAGE_POS_SEQ 1 +#define MESSAGE_TRAILER_CRC 3 +#define MESSAGE_TRAILER_SYNC 1 +#define MESSAGE_PAYLOAD_MAX (MESSAGE_MAX - MESSAGE_MIN) +#define MESSAGE_SEQ_MASK 0x0f +#define MESSAGE_DEST 0x10 +#define MESSAGE_SYNC 0x7E + +struct queue_message { + int len; + uint8_t msg[MESSAGE_MAX]; + union { + // Filled when on a command queue + struct { + uint64_t min_clock, req_clock; + }; + // Filled when in sent/receive queues + struct { + double sent_time, receive_time; + }; + }; + uint64_t notify_id; + struct list_node node; +}; + +uint16_t msgblock_crc16_ccitt(uint8_t *buf, uint8_t len); +int msgblock_check(uint8_t *need_sync, uint8_t *buf, int buf_len); +struct queue_message *message_alloc(void); +struct queue_message *message_fill(uint8_t *data, int len); +struct queue_message *message_alloc_and_encode(uint32_t *data, int len); +void message_free(struct queue_message *qm); +void message_queue_free(struct list_head *root); + +#endif // msgblock.h diff --git a/klippy/chelper/serialqueue.c b/klippy/chelper/serialqueue.c index 38f65199..517a435c 100644 --- a/klippy/chelper/serialqueue.c +++ b/klippy/chelper/serialqueue.c @@ -24,159 +24,16 @@ #include // pipe #include "compiler.h" // __visible #include "list.h" // list_add_tail +#include "msgblock.h" // message_alloc #include "pollreactor.h" // pollreactor_alloc #include "pyhelper.h" // get_monotonic #include "serialqueue.h" // struct queue_message - -/**************************************************************** - * Serial protocol helpers - ****************************************************************/ - -// Implement the standard crc "ccitt" algorithm on the given buffer -static uint16_t -crc16_ccitt(uint8_t *buf, uint8_t len) -{ - uint16_t crc = 0xffff; - while (len--) { - uint8_t data = *buf++; - data ^= crc & 0xff; - data ^= data << 4; - crc = ((((uint16_t)data << 8) | (crc >> 8)) ^ (uint8_t)(data >> 4) - ^ ((uint16_t)data << 3)); - } - return crc; -} - -// Verify a buffer starts with a valid mcu message -static int -check_message(uint8_t *need_sync, uint8_t *buf, int buf_len) -{ - if (buf_len < MESSAGE_MIN) - // Need more data - return 0; - if (*need_sync) - goto error; - uint8_t msglen = buf[MESSAGE_POS_LEN]; - if (msglen < MESSAGE_MIN || msglen > MESSAGE_MAX) - goto error; - uint8_t msgseq = buf[MESSAGE_POS_SEQ]; - if ((msgseq & ~MESSAGE_SEQ_MASK) != MESSAGE_DEST) - goto error; - if (buf_len < msglen) - // Need more data - return 0; - if (buf[msglen-MESSAGE_TRAILER_SYNC] != MESSAGE_SYNC) - goto error; - uint16_t msgcrc = ((buf[msglen-MESSAGE_TRAILER_CRC] << 8) - | (uint8_t)buf[msglen-MESSAGE_TRAILER_CRC+1]); - uint16_t crc = crc16_ccitt(buf, msglen-MESSAGE_TRAILER_SIZE); - if (crc != msgcrc) - goto error; - return msglen; - -error: ; - // Discard bytes until next SYNC found - uint8_t *next_sync = memchr(buf, MESSAGE_SYNC, buf_len); - if (next_sync) { - *need_sync = 0; - return -(next_sync - buf + 1); - } - *need_sync = 1; - return -buf_len; -} - -// Encode an integer as a variable length quantity (vlq) -static uint8_t * -encode_int(uint8_t *p, uint32_t v) -{ - int32_t sv = v; - if (sv < (3L<<5) && sv >= -(1L<<5)) goto f4; - if (sv < (3L<<12) && sv >= -(1L<<12)) goto f3; - if (sv < (3L<<19) && sv >= -(1L<<19)) goto f2; - if (sv < (3L<<26) && sv >= -(1L<<26)) goto f1; - *p++ = (v>>28) | 0x80; -f1: *p++ = ((v>>21) & 0x7f) | 0x80; -f2: *p++ = ((v>>14) & 0x7f) | 0x80; -f3: *p++ = ((v>>7) & 0x7f) | 0x80; -f4: *p++ = v & 0x7f; - return p; -} - - -/**************************************************************** - * Command queues - ****************************************************************/ - struct command_queue { struct list_head stalled_queue, ready_queue; struct list_node node; }; -// Allocate a 'struct queue_message' object -static struct queue_message * -message_alloc(void) -{ - struct queue_message *qm = malloc(sizeof(*qm)); - memset(qm, 0, sizeof(*qm)); - return qm; -} - -// Allocate a queue_message and fill it with the specified data -static struct queue_message * -message_fill(uint8_t *data, int len) -{ - struct queue_message *qm = message_alloc(); - memcpy(qm->msg, data, len); - qm->len = len; - return qm; -} - -// Allocate a queue_message and fill it with a series of encoded vlq integers -struct queue_message * -message_alloc_and_encode(uint32_t *data, int len) -{ - struct queue_message *qm = message_alloc(); - int i; - uint8_t *p = qm->msg; - for (i=0; i &qm->msg[MESSAGE_PAYLOAD_MAX]) - goto fail; - } - qm->len = p - qm->msg; - return qm; - -fail: - errorf("Encode error"); - qm->len = 0; - return qm; -} - -// Free the storage from a previous message_alloc() call -static void -message_free(struct queue_message *qm) -{ - free(qm); -} - -// Free all the messages on a queue -void -message_queue_free(struct list_head *root) -{ - while (!list_empty(root)) { - struct queue_message *qm = list_first_entry( - root, struct queue_message, node); - list_del(&qm->node); - message_free(qm); - } -} - - -/**************************************************************** - * Serialqueue interface - ****************************************************************/ - struct serialqueue { // Input reading struct pollreactor *pr; @@ -426,7 +283,7 @@ input_event(struct serialqueue *sq, double eventtime) sq->input_pos += ret; } for (;;) { - int len = check_message(&sq->need_sync, sq->input_buf, sq->input_pos); + int len = msgblock_check(&sq->need_sync, sq->input_buf, sq->input_pos); if (!len) // Need more data return; @@ -581,7 +438,7 @@ build_and_send_command(struct serialqueue *sq, uint8_t *buf, double eventtime) len += MESSAGE_TRAILER_SIZE; buf[MESSAGE_POS_LEN] = len; buf[MESSAGE_POS_SEQ] = MESSAGE_DEST | (sq->send_seq & MESSAGE_SEQ_MASK); - uint16_t crc = crc16_ccitt(buf, len - MESSAGE_TRAILER_SIZE); + uint16_t crc = msgblock_crc16_ccitt(buf, len - MESSAGE_TRAILER_SIZE); buf[len - MESSAGE_TRAILER_CRC] = crc >> 8; buf[len - MESSAGE_TRAILER_CRC+1] = crc & 0xff; buf[len - MESSAGE_TRAILER_SYNC] = MESSAGE_SYNC; diff --git a/klippy/chelper/serialqueue.h b/klippy/chelper/serialqueue.h index 62af9aaf..17e14316 100644 --- a/klippy/chelper/serialqueue.h +++ b/klippy/chelper/serialqueue.h @@ -2,43 +2,11 @@ #define SERIALQUEUE_H #include "list.h" // struct list_head +#include "msgblock.h" // MESSAGE_MAX #define MAX_CLOCK 0x7fffffffffffffffLL #define BACKGROUND_PRIORITY_CLOCK 0x7fffffff00000000LL -#define MESSAGE_MIN 5 -#define MESSAGE_MAX 64 -#define MESSAGE_HEADER_SIZE 2 -#define MESSAGE_TRAILER_SIZE 3 -#define MESSAGE_POS_LEN 0 -#define MESSAGE_POS_SEQ 1 -#define MESSAGE_TRAILER_CRC 3 -#define MESSAGE_TRAILER_SYNC 1 -#define MESSAGE_PAYLOAD_MAX (MESSAGE_MAX - MESSAGE_MIN) -#define MESSAGE_SEQ_MASK 0x0f -#define MESSAGE_DEST 0x10 -#define MESSAGE_SYNC 0x7E - -struct queue_message { - int len; - uint8_t msg[MESSAGE_MAX]; - union { - // Filled when on a command queue - struct { - uint64_t min_clock, req_clock; - }; - // Filled when in sent/receive queues - struct { - double sent_time, receive_time; - }; - }; - uint64_t notify_id; - struct list_node node; -}; - -struct queue_message *message_alloc_and_encode(uint32_t *data, int len); -void message_queue_free(struct list_head *root); - struct pull_queue_message { uint8_t msg[MESSAGE_MAX]; int len; From f938caa0d23f91c6fbf416334441e64e6739bbc7 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 17 Feb 2021 22:29:38 -0500 Subject: [PATCH 30/51] msgblock: Add msgblock_decode() Add function that can parse a simple VLQ encoded message to an array of integers. Signed-off-by: Kevin O'Connor --- klippy/chelper/msgblock.c | 33 +++++++++++++++++++++++++++++++++ klippy/chelper/msgblock.h | 1 + 2 files changed, 34 insertions(+) diff --git a/klippy/chelper/msgblock.c b/klippy/chelper/msgblock.c index cc91d455..3c848b13 100644 --- a/klippy/chelper/msgblock.c +++ b/klippy/chelper/msgblock.c @@ -85,6 +85,39 @@ f4: *p++ = v & 0x7f; return p; } +// Parse an integer that was encoded as a "variable length quantity" +static uint32_t +parse_int(uint8_t **pp) +{ + uint8_t *p = *pp, c = *p++; + uint32_t v = c & 0x7f; + if ((c & 0x60) == 0x60) + v |= -0x20; + while (c & 0x80) { + c = *p++; + v = (v<<7) | (c & 0x7f); + } + *pp = p; + return v; +} + +// Parse the VLQ contents of a message +int +msgblock_decode(uint32_t *data, int data_len, uint8_t *msg, int msg_len) +{ + uint8_t *p = &msg[MESSAGE_HEADER_SIZE]; + uint8_t *end = &msg[msg_len - MESSAGE_TRAILER_SIZE]; + while (data_len--) { + if (p >= end) + return -1; + *data++ = parse_int(&p); + } + if (p != end) + // Invalid message + return -1; + return 0; +} + /**************************************************************** * Command queues diff --git a/klippy/chelper/msgblock.h b/klippy/chelper/msgblock.h index cce1b2ee..2d2967eb 100644 --- a/klippy/chelper/msgblock.h +++ b/klippy/chelper/msgblock.h @@ -36,6 +36,7 @@ struct queue_message { uint16_t msgblock_crc16_ccitt(uint8_t *buf, uint8_t len); int msgblock_check(uint8_t *need_sync, uint8_t *buf, int buf_len); +int msgblock_decode(uint32_t *data, int data_len, uint8_t *msg, int msg_len); struct queue_message *message_alloc(void); struct queue_message *message_fill(uint8_t *data, int len); struct queue_message *message_alloc_and_encode(uint32_t *data, int len); From 620f77ddb79de683befb458f43c30435759ac0e2 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 15 Feb 2021 19:18:51 -0500 Subject: [PATCH 31/51] msgblock: Add clock estimation helper functions Signed-off-by: Kevin O'Connor --- klippy/chelper/__init__.py | 2 +- klippy/chelper/msgblock.c | 26 ++++++++++++++++++++++++ klippy/chelper/msgblock.h | 8 ++++++++ klippy/chelper/serialqueue.c | 38 +++++++++++++++++++++--------------- klippy/chelper/serialqueue.h | 5 ++++- klippy/clocksync.py | 4 ++-- klippy/serialhdl.py | 4 ++-- 7 files changed, 65 insertions(+), 22 deletions(-) diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py index 55f394ef..4a8728fb 100644 --- a/klippy/chelper/__init__.py +++ b/klippy/chelper/__init__.py @@ -162,7 +162,7 @@ defs_serialqueue = """ void serialqueue_set_receive_window(struct serialqueue *sq , int receive_window); void serialqueue_set_clock_est(struct serialqueue *sq, double est_freq - , double last_clock_time, uint64_t last_clock); + , double conv_time, uint64_t conv_clock, uint64_t last_clock); void serialqueue_get_stats(struct serialqueue *sq, char *buf, int len); int serialqueue_extract_old(struct serialqueue *sq, int sentq , struct pull_queue_message *q, int max); diff --git a/klippy/chelper/msgblock.c b/klippy/chelper/msgblock.c index 3c848b13..e6cb298b 100644 --- a/klippy/chelper/msgblock.c +++ b/klippy/chelper/msgblock.c @@ -181,3 +181,29 @@ message_queue_free(struct list_head *root) message_free(qm); } } + + +/**************************************************************** + * Clock estimation + ****************************************************************/ + +// Extend a 32bit clock value to its full 64bit value +uint64_t +clock_from_clock32(struct clock_estimate *ce, uint32_t clock32) +{ + return ce->last_clock + (int32_t)(clock32 - ce->last_clock); +} + +// Convert a clock to its estimated time +double +clock_to_time(struct clock_estimate *ce, uint64_t clock) +{ + return ce->conv_time + (int64_t)(clock - ce->conv_clock) / ce->est_freq; +} + +// Convert a time to the nearest clock value +uint64_t +clock_from_time(struct clock_estimate *ce, double time) +{ + return (int64_t)((time - ce->conv_time)*ce->est_freq + .5) + ce->conv_clock; +} diff --git a/klippy/chelper/msgblock.h b/klippy/chelper/msgblock.h index 2d2967eb..43ee9532 100644 --- a/klippy/chelper/msgblock.h +++ b/klippy/chelper/msgblock.h @@ -34,6 +34,11 @@ struct queue_message { struct list_node node; }; +struct clock_estimate { + uint64_t last_clock, conv_clock; + double conv_time, est_freq; +}; + uint16_t msgblock_crc16_ccitt(uint8_t *buf, uint8_t len); int msgblock_check(uint8_t *need_sync, uint8_t *buf, int buf_len); int msgblock_decode(uint32_t *data, int data_len, uint8_t *msg, int msg_len); @@ -42,5 +47,8 @@ struct queue_message *message_fill(uint8_t *data, int len); struct queue_message *message_alloc_and_encode(uint32_t *data, int len); void message_free(struct queue_message *qm); void message_queue_free(struct list_head *root); +uint64_t clock_from_clock32(struct clock_estimate *ce, uint32_t clock32); +double clock_to_time(struct clock_estimate *ce, uint64_t clock); +uint64_t clock_from_time(struct clock_estimate *ce, double time); #endif // msgblock.h diff --git a/klippy/chelper/serialqueue.c b/klippy/chelper/serialqueue.c index 517a435c..78115e09 100644 --- a/klippy/chelper/serialqueue.c +++ b/klippy/chelper/serialqueue.c @@ -50,8 +50,7 @@ struct serialqueue { // Baud / clock tracking int receive_window; double baud_adjust, idle_time; - double est_freq, last_clock_time; - uint64_t last_clock; + struct clock_estimate ce; double last_receive_sent_time; // Retransmit support uint64_t send_seq, receive_seq; @@ -483,9 +482,7 @@ check_send_command(struct serialqueue *sq, double eventtime) // Check for stalled messages now ready double idletime = eventtime > sq->idle_time ? eventtime : sq->idle_time; idletime += MESSAGE_MIN * sq->baud_adjust; - double timedelta = idletime - sq->last_clock_time; - uint64_t ack_clock = ((uint64_t)(timedelta * sq->est_freq) - + sq->last_clock); + uint64_t ack_clock = clock_from_time(&sq->ce, idletime); uint64_t min_stalled_clock = MAX_CLOCK, min_ready_clock = MAX_CLOCK; struct command_queue *cq; list_for_each_entry(cq, &sq->pending_queues, node) { @@ -508,11 +505,9 @@ check_send_command(struct serialqueue *sq, double eventtime) struct queue_message *qm = list_first_entry( &cq->ready_queue, struct queue_message, node); uint64_t req_clock = qm->req_clock; + double bgoffset = MIN_REQTIME_DELTA + MIN_BACKGROUND_DELTA; if (req_clock == BACKGROUND_PRIORITY_CLOCK) - req_clock = (uint64_t)( - (sq->idle_time - sq->last_clock_time - + MIN_REQTIME_DELTA + MIN_BACKGROUND_DELTA) - * sq->est_freq) + sq->last_clock; + req_clock = clock_from_time(&sq->ce, sq->idle_time + bgoffset); if (req_clock < min_ready_clock) min_ready_clock = req_clock; } @@ -521,20 +516,20 @@ check_send_command(struct serialqueue *sq, double eventtime) // Check for messages to send if (sq->ready_bytes >= MESSAGE_PAYLOAD_MAX) return PR_NOW; - if (! sq->est_freq) { + if (! sq->ce.est_freq) { if (sq->ready_bytes) return PR_NOW; sq->need_kick_clock = MAX_CLOCK; return PR_NEVER; } - uint64_t reqclock_delta = MIN_REQTIME_DELTA * sq->est_freq; + uint64_t reqclock_delta = MIN_REQTIME_DELTA * sq->ce.est_freq; if (min_ready_clock <= ack_clock + reqclock_delta) return PR_NOW; uint64_t wantclock = min_ready_clock - reqclock_delta; if (min_stalled_clock < wantclock) wantclock = min_stalled_clock; sq->need_kick_clock = wantclock; - return idletime + (wantclock - ack_clock) / sq->est_freq; + return idletime + (wantclock - ack_clock) / sq->ce.est_freq; } // Callback timer to send data to the serial port @@ -819,12 +814,23 @@ serialqueue_set_receive_window(struct serialqueue *sq, int receive_window) // serial port void __visible serialqueue_set_clock_est(struct serialqueue *sq, double est_freq - , double last_clock_time, uint64_t last_clock) + , double conv_time, uint64_t conv_clock + , uint64_t last_clock) { pthread_mutex_lock(&sq->lock); - sq->est_freq = est_freq; - sq->last_clock_time = last_clock_time; - sq->last_clock = last_clock; + sq->ce.est_freq = est_freq; + sq->ce.conv_time = conv_time; + sq->ce.conv_clock = conv_clock; + sq->ce.last_clock = last_clock; + pthread_mutex_unlock(&sq->lock); +} + +// Return the latest clock estimate +void +serialqueue_get_clock_est(struct serialqueue *sq, struct clock_estimate *ce) +{ + pthread_mutex_lock(&sq->lock); + memcpy(ce, &sq->ce, sizeof(sq->ce)); pthread_mutex_unlock(&sq->lock); } diff --git a/klippy/chelper/serialqueue.h b/klippy/chelper/serialqueue.h index 17e14316..62e70403 100644 --- a/klippy/chelper/serialqueue.h +++ b/klippy/chelper/serialqueue.h @@ -30,7 +30,10 @@ void serialqueue_pull(struct serialqueue *sq, struct pull_queue_message *pqm); void serialqueue_set_baud_adjust(struct serialqueue *sq, double baud_adjust); void serialqueue_set_receive_window(struct serialqueue *sq, int receive_window); void serialqueue_set_clock_est(struct serialqueue *sq, double est_freq - , double last_clock_time, uint64_t last_clock); + , double conv_time, uint64_t conv_clock + , uint64_t last_clock); +void serialqueue_get_clock_est(struct serialqueue *sq + , struct clock_estimate *ce); void serialqueue_get_stats(struct serialqueue *sq, char *buf, int len); int serialqueue_extract_old(struct serialqueue *sq, int sentq , struct pull_queue_message *q, int max); diff --git a/klippy/clocksync.py b/klippy/clocksync.py index ccead0f6..f32ed3cd 100644 --- a/klippy/clocksync.py +++ b/klippy/clocksync.py @@ -54,7 +54,7 @@ class ClockSync: freq = 1000000000000. if pace: freq = self.mcu_freq - serial.set_clock_est(freq, self.reactor.monotonic(), 0) + serial.set_clock_est(freq, self.reactor.monotonic(), 0, 0) # MCU clock querying (_handle_clock is invoked from background thread) def _get_clock_event(self, eventtime): self.serial.raw_send(self.get_clock_cmd, 0, 0, self.cmd_queue) @@ -116,7 +116,7 @@ class ClockSync: new_freq = self.clock_covariance / self.time_variance pred_stddev = math.sqrt(self.prediction_variance) self.serial.set_clock_est(new_freq, self.time_avg + TRANSMIT_EXTRA, - int(self.clock_avg - 3. * pred_stddev)) + int(self.clock_avg - 3. * pred_stddev), clock) self.clock_est = (self.time_avg + self.min_half_rtt, self.clock_avg, new_freq) #logging.debug("regr %.3f: freq=%.3f d=%d(%.3f)", diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py index 59482a41..147bb624 100644 --- a/klippy/serialhdl.py +++ b/klippy/serialhdl.py @@ -200,9 +200,9 @@ class SerialReader: self.serialqueue = self.ffi_main.gc( self.ffi_lib.serialqueue_alloc(self.serial_dev.fileno(), 'f', 0), self.ffi_lib.serialqueue_free) - def set_clock_est(self, freq, last_time, last_clock): + def set_clock_est(self, freq, conv_time, conv_clock, last_clock): self.ffi_lib.serialqueue_set_clock_est( - self.serialqueue, freq, last_time, last_clock) + self.serialqueue, freq, conv_time, conv_clock, last_clock) def disconnect(self): if self.serialqueue is not None: self.ffi_lib.serialqueue_exit(self.serialqueue) From c53e8c7d4a945fee54f6ad575381cebec2a87c6f Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 10 Feb 2021 11:09:47 -0500 Subject: [PATCH 32/51] serialqueue: Add "fast reader" support Add ability to run C code directly from the low-level socket reading thread. This enables host based low-latency handlers. Signed-off-by: Kevin O'Connor --- klippy/chelper/serialqueue.c | 67 ++++++++++++++++++++++++++++++------ klippy/chelper/serialqueue.h | 13 +++++++ 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/klippy/chelper/serialqueue.c b/klippy/chelper/serialqueue.c index 78115e09..bee39837 100644 --- a/klippy/chelper/serialqueue.c +++ b/klippy/chelper/serialqueue.c @@ -64,6 +64,9 @@ struct serialqueue { struct list_head notify_queue; // Received messages struct list_head receive_queue; + // Fastreader support + pthread_mutex_t fast_reader_dispatch_lock; + struct list_head fast_readers; // Debugging struct list_head old_sent, old_receive; // Stats @@ -195,9 +198,11 @@ update_receive_seq(struct serialqueue *sq, double eventtime, uint64_t rseq) } // Process a well formed input message -static int +static void handle_message(struct serialqueue *sq, double eventtime, int len) { + pthread_mutex_lock(&sq->lock); + // Calculate receive sequence number uint64_t rseq = ((sq->receive_seq & ~MESSAGE_SEQ_MASK) | (sq->input_buf[MESSAGE_POS_SEQ] & MESSAGE_SEQ_MASK)); @@ -205,11 +210,15 @@ handle_message(struct serialqueue *sq, double eventtime, int len) // New sequence number if (rseq < sq->receive_seq) rseq += MESSAGE_SEQ_MASK+1; - if (rseq > sq->send_seq && sq->receive_seq != 1) + if (rseq > sq->send_seq && sq->receive_seq != 1) { // An ack for a message not sent? Out of order message? - return -1; + sq->bytes_invalid += len; + pthread_mutex_unlock(&sq->lock); + return; + } update_receive_seq(sq, eventtime, rseq); } + sq->bytes_read += len; // Check for pending messages on notify_queue int must_wake = 0; @@ -247,9 +256,26 @@ handle_message(struct serialqueue *sq, double eventtime, int len) must_wake = 1; } + // Check fast readers + struct fastreader *fr; + list_for_each_entry(fr, &sq->fast_readers, node) { + if (len < fr->prefix_len + MESSAGE_MIN + || memcmp(&sq->input_buf[MESSAGE_HEADER_SIZE] + , fr->prefix, fr->prefix_len) != 0) + continue; + // Release main lock and invoke callback + pthread_mutex_lock(&sq->fast_reader_dispatch_lock); + if (must_wake) + check_wake_receive(sq); + pthread_mutex_unlock(&sq->lock); + fr->func(fr, sq->input_buf, len); + pthread_mutex_unlock(&sq->fast_reader_dispatch_lock); + return; + } + if (must_wake) check_wake_receive(sq); - return 0; + pthread_mutex_unlock(&sq->lock); } // Callback for input activity on the serial fd @@ -288,13 +314,7 @@ input_event(struct serialqueue *sq, double eventtime) return; if (len > 0) { // Received a valid message - pthread_mutex_lock(&sq->lock); - int ret = handle_message(sq, eventtime, len); - if (ret) - sq->bytes_invalid += len; - else - sq->bytes_read += len; - pthread_mutex_unlock(&sq->lock); + handle_message(sq, eventtime, len); } else { // Skip bad data at beginning of input len = -len; @@ -614,6 +634,7 @@ serialqueue_alloc(int serial_fd, char serial_fd_type, int client_id) list_init(&sq->sent_queue); list_init(&sq->receive_queue); list_init(&sq->notify_queue); + list_init(&sq->fast_readers); // Debugging list_init(&sq->old_sent); @@ -626,6 +647,9 @@ serialqueue_alloc(int serial_fd, char serial_fd_type, int client_id) if (ret) goto fail; ret = pthread_cond_init(&sq->cond, NULL); + if (ret) + goto fail; + ret = pthread_mutex_init(&sq->fast_reader_dispatch_lock, NULL); if (ret) goto fail; ret = pthread_create(&sq->tid, NULL, background_thread, sq); @@ -700,6 +724,27 @@ serialqueue_free_commandqueue(struct command_queue *cq) free(cq); } +// Add a low-latency message handler +void +serialqueue_add_fastreader(struct serialqueue *sq, struct fastreader *fr) +{ + pthread_mutex_lock(&sq->lock); + list_add_tail(&fr->node, &sq->fast_readers); + pthread_mutex_unlock(&sq->lock); +} + +// Remove a previously registered low-latency message handler +void +serialqueue_rm_fastreader(struct serialqueue *sq, struct fastreader *fr) +{ + pthread_mutex_lock(&sq->lock); + list_del(&fr->node); + pthread_mutex_unlock(&sq->lock); + + pthread_mutex_lock(&sq->fast_reader_dispatch_lock); // XXX - goofy locking + pthread_mutex_unlock(&sq->fast_reader_dispatch_lock); +} + // Add a batch of messages to the given command_queue void serialqueue_send_batch(struct serialqueue *sq, struct command_queue *cq diff --git a/klippy/chelper/serialqueue.h b/klippy/chelper/serialqueue.h index 62e70403..9e66e7f5 100644 --- a/klippy/chelper/serialqueue.h +++ b/klippy/chelper/serialqueue.h @@ -1,12 +1,23 @@ #ifndef SERIALQUEUE_H #define SERIALQUEUE_H +#include // uint8_t #include "list.h" // struct list_head #include "msgblock.h" // MESSAGE_MAX #define MAX_CLOCK 0x7fffffffffffffffLL #define BACKGROUND_PRIORITY_CLOCK 0x7fffffff00000000LL +struct fastreader; +typedef void (*fastreader_cb)(struct fastreader *fr, uint8_t *data, int len); + +struct fastreader { + struct list_node node; + fastreader_cb func; + int prefix_len; + uint8_t prefix[MESSAGE_MAX]; +}; + struct pull_queue_message { uint8_t msg[MESSAGE_MAX]; int len; @@ -21,6 +32,8 @@ void serialqueue_exit(struct serialqueue *sq); void serialqueue_free(struct serialqueue *sq); struct command_queue *serialqueue_alloc_commandqueue(void); void serialqueue_free_commandqueue(struct command_queue *cq); +void serialqueue_add_fastreader(struct serialqueue *sq, struct fastreader *fr); +void serialqueue_rm_fastreader(struct serialqueue *sq, struct fastreader *fr); void serialqueue_send_batch(struct serialqueue *sq, struct command_queue *cq , struct list_head *msgs); void serialqueue_send(struct serialqueue *sq, struct command_queue *cq From b6d8cf27d25faecfa47404924a757dc09e7a84a3 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 17 Feb 2021 19:58:47 -0500 Subject: [PATCH 33/51] serialqueue: Add serialqueue_send_one() helper function Signed-off-by: Kevin O'Connor --- klippy/chelper/serialqueue.c | 17 ++++++++++++----- klippy/chelper/serialqueue.h | 2 ++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/klippy/chelper/serialqueue.c b/klippy/chelper/serialqueue.c index bee39837..b74605ff 100644 --- a/klippy/chelper/serialqueue.c +++ b/klippy/chelper/serialqueue.c @@ -781,6 +781,17 @@ serialqueue_send_batch(struct serialqueue *sq, struct command_queue *cq kick_bg_thread(sq); } +// Helper to send a single message +void +serialqueue_send_one(struct serialqueue *sq, struct command_queue *cq + , struct queue_message *qm) +{ + struct list_head msgs; + list_init(&msgs); + list_add_tail(&qm->node, &msgs); + serialqueue_send_batch(sq, cq, &msgs); +} + // Schedule the transmission of a message on the serial port at a // given time and priority. void __visible @@ -792,11 +803,7 @@ serialqueue_send(struct serialqueue *sq, struct command_queue *cq, uint8_t *msg qm->min_clock = min_clock; qm->req_clock = req_clock; qm->notify_id = notify_id; - - struct list_head msgs; - list_init(&msgs); - list_add_tail(&qm->node, &msgs); - serialqueue_send_batch(sq, cq, &msgs); + serialqueue_send_one(sq, cq, qm); } // Return a message read from the serial port (or wait for one if none diff --git a/klippy/chelper/serialqueue.h b/klippy/chelper/serialqueue.h index 9e66e7f5..724a86a5 100644 --- a/klippy/chelper/serialqueue.h +++ b/klippy/chelper/serialqueue.h @@ -36,6 +36,8 @@ void serialqueue_add_fastreader(struct serialqueue *sq, struct fastreader *fr); void serialqueue_rm_fastreader(struct serialqueue *sq, struct fastreader *fr); void serialqueue_send_batch(struct serialqueue *sq, struct command_queue *cq , struct list_head *msgs); +void serialqueue_send_one(struct serialqueue *sq, struct command_queue *cq + , struct queue_message *qm); void serialqueue_send(struct serialqueue *sq, struct command_queue *cq , uint8_t *msg, int len, uint64_t min_clock , uint64_t req_clock, uint64_t notify_id); From e1f7748e1a9dfda3e78f8a27e9972a9eb954e946 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 11 Feb 2021 17:49:47 -0500 Subject: [PATCH 34/51] trdispatch: Support fast responses to trsync_state messages Add C code that can process trsync_state messages and dispatch responses with low-latency. Signed-off-by: Kevin O'Connor --- klippy/chelper/__init__.py | 22 +++- klippy/chelper/trdispatch.c | 226 ++++++++++++++++++++++++++++++++++++ 2 files changed, 244 insertions(+), 4 deletions(-) create mode 100644 klippy/chelper/trdispatch.c diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py index 4a8728fb..7b20963d 100644 --- a/klippy/chelper/__init__.py +++ b/klippy/chelper/__init__.py @@ -18,7 +18,7 @@ COMPILE_ARGS = ("-Wall -g -O2 -shared -fPIC" SSE_FLAGS = "-mfpmath=sse -msse2" SOURCE_FILES = [ 'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'itersolve.c', 'trapq.c', - 'pollreactor.c', 'msgblock.c', + 'pollreactor.c', 'msgblock.c', 'trdispatch.c', 'kin_cartesian.c', 'kin_corexy.c', 'kin_corexz.c', 'kin_delta.c', 'kin_polar.c', 'kin_rotary_delta.c', 'kin_winch.c', 'kin_extruder.c', 'kin_shaper.c', @@ -168,6 +168,19 @@ defs_serialqueue = """ , struct pull_queue_message *q, int max); """ +defs_trdispatch = """ + void trdispatch_start(struct trdispatch *td, uint32_t dispatch_reason); + void trdispatch_stop(struct trdispatch *td); + struct trdispatch *trdispatch_alloc(void); + struct trdispatch_mcu *trdispatch_mcu_alloc(struct trdispatch *td + , struct serialqueue *sq, struct command_queue *cq, uint32_t trsync_oid + , uint32_t set_timeout_msgtag, uint32_t trigger_msgtag + , uint32_t state_msgtag); + void trdispatch_mcu_setup(struct trdispatch_mcu *tdm + , uint64_t last_status_clock, uint64_t expire_clock + , uint64_t expire_ticks, uint64_t min_extend_ticks); +""" + defs_pyhelper = """ void set_python_logging_callback(void (*func)(const char *)); double get_monotonic(void); @@ -179,9 +192,10 @@ defs_std = """ defs_all = [ defs_pyhelper, defs_serialqueue, defs_std, defs_stepcompress, - defs_itersolve, defs_trapq, defs_kin_cartesian, defs_kin_corexy, - defs_kin_corexz, defs_kin_delta, defs_kin_polar, defs_kin_rotary_delta, - defs_kin_winch, defs_kin_extruder, defs_kin_shaper, + defs_itersolve, defs_trapq, defs_trdispatch, + defs_kin_cartesian, defs_kin_corexy, defs_kin_corexz, defs_kin_delta, + defs_kin_polar, defs_kin_rotary_delta, defs_kin_winch, defs_kin_extruder, + defs_kin_shaper, ] # Update filenames to an absolute path diff --git a/klippy/chelper/trdispatch.c b/klippy/chelper/trdispatch.c new file mode 100644 index 00000000..3cd1b642 --- /dev/null +++ b/klippy/chelper/trdispatch.c @@ -0,0 +1,226 @@ +// Trigger sync "trsync" message dispatch +// +// Copyright (C) 2021 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // pthread_mutex_lock +#include // offsetof +#include // malloc +#include // memset +#include "compiler.h" // ARRAY_SIZE +#include "list.h" // list_add_tail +#include "pollreactor.h" // PR_NEVER +#include "pyhelper.h" // report_errno +#include "serialqueue.h" // serialqueue_add_fastreader + +struct trdispatch { + struct list_head tdm_list; + + pthread_mutex_t lock; // protects variables below + uint32_t is_active, can_trigger, dispatch_reason; +}; + +struct trdispatch_mcu { + struct fastreader fr; + struct trdispatch *td; + struct list_node node; + struct serialqueue *sq; + struct command_queue *cq; + uint32_t trsync_oid, set_timeout_msgtag, trigger_msgtag; + + // Remaining fields protected by trdispatch lock + uint64_t last_status_clock, expire_clock; + uint64_t expire_ticks, min_extend_ticks; + struct clock_estimate ce; +}; + +// Send: trsync_trigger oid=%c reason=%c +static void +send_trsync_trigger(struct trdispatch_mcu *tdm) +{ + uint32_t msg[3] = { + tdm->trigger_msgtag, tdm->trsync_oid, tdm->td->dispatch_reason + }; + struct queue_message *qm = message_alloc_and_encode(msg, ARRAY_SIZE(msg)); + serialqueue_send_one(tdm->sq, tdm->cq, qm); +} + +// Send: trsync_set_timeout oid=%c clock=%u +static void +send_trsync_set_timeout(struct trdispatch_mcu *tdm) +{ + uint32_t msg[3] = { + tdm->set_timeout_msgtag, tdm->trsync_oid, tdm->expire_clock + }; + struct queue_message *qm = message_alloc_and_encode(msg, ARRAY_SIZE(msg)); + qm->req_clock = tdm->expire_clock; + serialqueue_send_one(tdm->sq, tdm->cq, qm); +} + +// Handle a trsync_state message (callback from serialqueue fastreader) +static void +handle_trsync_state(struct fastreader *fr, uint8_t *data, int len) +{ + struct trdispatch_mcu *tdm = container_of(fr, struct trdispatch_mcu, fr); + + // Parse: trsync_state oid=%c can_trigger=%c trigger_reason=%c clock=%u + uint32_t fields[5]; + int ret = msgblock_decode(fields, ARRAY_SIZE(fields), data, len); + if (ret || fields[1] != tdm->trsync_oid) + return; + uint32_t can_trigger=fields[2], clock=fields[4]; + + // Process message + struct trdispatch *td = tdm->td; + pthread_mutex_lock(&td->lock); + if (!td->can_trigger) + goto done; + + if (!can_trigger) { + // mcu reports trigger or timeout - propagate to all mcus + td->can_trigger = 0; + struct trdispatch_mcu *m; + list_for_each_entry(m, &td->tdm_list, node) { + send_trsync_trigger(m); + } + goto done; + } + + // mcu is still working okay - update last_status_clock + serialqueue_get_clock_est(tdm->sq, &tdm->ce); + tdm->last_status_clock = clock_from_clock32(&tdm->ce, clock); + + // Determine minimum acknowledged time among all mcus + double min_time = PR_NEVER, next_min_time = PR_NEVER; + struct trdispatch_mcu *m, *min_tdm = NULL; + list_for_each_entry(m, &td->tdm_list, node) { + double status_time = clock_to_time(&m->ce, m->last_status_clock); + if (status_time < next_min_time) { + next_min_time = status_time; + if (status_time < min_time) { + next_min_time = min_time; + min_time = status_time; + min_tdm = m; + } + } + } + if (next_min_time == PR_NEVER) + next_min_time = min_time; + + // Send trsync_set_timeout messages to other mcus (if needed) + list_for_each_entry(m, &td->tdm_list, node) { + double status_time = m == min_tdm ? next_min_time : min_time; + uint64_t expire=clock_from_time(&m->ce, status_time) + m->expire_ticks; + if ((int64_t)(expire - m->expire_clock) >= m->min_extend_ticks) { + m->expire_clock = expire; + send_trsync_set_timeout(m); + } + } + +done: + pthread_mutex_unlock(&td->lock); +} + +// Begin synchronization +void __visible +trdispatch_start(struct trdispatch *td, uint32_t dispatch_reason) +{ + pthread_mutex_lock(&td->lock); + if (td->is_active || list_empty(&td->tdm_list)) { + pthread_mutex_unlock(&td->lock); + return; + } + td->dispatch_reason = dispatch_reason; + td->is_active = td->can_trigger = 1; + pthread_mutex_unlock(&td->lock); + + // Register handle_trsync_state message parser for each mcu + struct trdispatch_mcu *tdm; + list_for_each_entry(tdm, &td->tdm_list, node) { + serialqueue_add_fastreader(tdm->sq, &tdm->fr); + } +} + +// Cleanup after a test completes +void __visible +trdispatch_stop(struct trdispatch *td) +{ + pthread_mutex_lock(&td->lock); + if (!td->is_active) { + pthread_mutex_unlock(&td->lock); + return; + } + td->is_active = 0; + pthread_mutex_unlock(&td->lock); + + // Unregister handle_trsync_state message parsers + struct trdispatch_mcu *tdm; + list_for_each_entry(tdm, &td->tdm_list, node) { + serialqueue_rm_fastreader(tdm->sq, &tdm->fr); + } +} + +// Create a new 'struct trdispatch' object +struct trdispatch * __visible +trdispatch_alloc(void) +{ + struct trdispatch *td = malloc(sizeof(*td)); + memset(td, 0, sizeof(*td)); + + list_init(&td->tdm_list); + + int ret = pthread_mutex_init(&td->lock, NULL); + if (ret) { + report_errno("trdispatch_alloc pthread_mutex_init", ret); + return NULL; + } + return td; +} + +// Create a new 'struct trdispatch_mcu' object +struct trdispatch_mcu * __visible +trdispatch_mcu_alloc(struct trdispatch *td, struct serialqueue *sq + , struct command_queue *cq, uint32_t trsync_oid + , uint32_t set_timeout_msgtag, uint32_t trigger_msgtag + , uint32_t state_msgtag) +{ + struct trdispatch_mcu *tdm = malloc(sizeof(*tdm)); + memset(tdm, 0, sizeof(*tdm)); + + tdm->sq = sq; + tdm->cq = cq; + tdm->trsync_oid = trsync_oid; + tdm->set_timeout_msgtag = set_timeout_msgtag; + tdm->trigger_msgtag = trigger_msgtag; + + // Setup fastreader to match trsync_state messages + uint32_t state_prefix[] = {state_msgtag, trsync_oid}; + struct queue_message *dummy = message_alloc_and_encode( + state_prefix, ARRAY_SIZE(state_prefix)); + memcpy(tdm->fr.prefix, dummy->msg, dummy->len); + tdm->fr.prefix_len = dummy->len; + free(dummy); + tdm->fr.func = handle_trsync_state; + + tdm->td = td; + list_add_tail(&tdm->node, &td->tdm_list); + + return tdm; +} + +// Setup for a trigger test +void __visible +trdispatch_mcu_setup(struct trdispatch_mcu *tdm + , uint64_t last_status_clock, uint64_t expire_clock + , uint64_t expire_ticks, uint64_t min_extend_ticks) +{ + struct trdispatch *td = tdm->td; + pthread_mutex_lock(&td->lock); + tdm->last_status_clock = last_status_clock; + tdm->expire_clock = expire_clock; + tdm->expire_ticks = expire_ticks; + tdm->min_extend_ticks = min_extend_ticks; + serialqueue_get_clock_est(tdm->sq, &tdm->ce); + pthread_mutex_unlock(&td->lock); +} From 983951443cf14fe2985585c2c2eb20efe526411d Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 15 Feb 2021 16:02:40 -0500 Subject: [PATCH 35/51] mcu: Enable trdispatch infrastructure during homing Separate out trsync handling from endstop handling in mcu.py code. Enable the low-level trdispatch C code. This is in preparation for multi-mcu homing. Signed-off-by: Kevin O'Connor --- klippy/mcu.py | 210 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 141 insertions(+), 69 deletions(-) diff --git a/klippy/mcu.py b/klippy/mcu.py index dc0011c9..bd7267ad 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -9,37 +9,143 @@ import serialhdl, msgproto, pins, chelper, clocksync class error(Exception): pass -class MCU_endstop: - RETRY_QUERY = 1.000 - def __init__(self, mcu, pin_params): +class MCU_trsync: + REASON_ENDSTOP_HIT = 1 + REASON_COMMS_TIMEOUT = 2 + REASON_HOST_REQUEST = 3 + REASON_PAST_END_TIME = 4 + def __init__(self, mcu, trdispatch): self._mcu = mcu - self._steppers = [] - self._pin = pin_params['pin'] - self._pullup = pin_params['pullup'] - self._invert = pin_params['invert'] + self._trdispatch = trdispatch self._reactor = mcu.get_printer().get_reactor() - self._oid = self._home_cmd = self._query_cmd = None - self._ts_oid = self._trsync_start_cmd = self._trsync_trigger_cmd = None - self._mcu.register_config_callback(self._build_config) - self._min_query_time = self._last_sent_time = self._end_home_time = 0. - self._trigger_completion = self._home_completion = None + self._steppers = [] + self._trdispatch_mcu = None + self._oid = mcu.create_oid() + self._cmd_queue = mcu.alloc_command_queue() + self._trsync_start_cmd = self._trsync_set_timeout_cmd = None + self._trsync_trigger_cmd = self._trsync_query_cmd = None + self._stepper_stop_cmd = None + self._trigger_completion = None + self._home_end_clock = None + mcu.register_config_callback(self._build_config) + printer = mcu.get_printer() + printer.register_event_handler("klippy:shutdown", self._shutdown) def get_mcu(self): return self._mcu + def get_oid(self): + return self._oid + def get_command_queue(self): + return self._cmd_queue def add_stepper(self, stepper): - if stepper.get_mcu() is not self._mcu: - raise pins.error("Endstop and stepper must be on the same mcu") if stepper in self._steppers: return self._steppers.append(stepper) def get_steppers(self): return list(self._steppers) def _build_config(self): + mcu = self._mcu # Setup config - self._ts_oid = self._mcu.create_oid() - self._mcu.add_config_cmd("config_trsync oid=%d" % (self._ts_oid,)) - self._mcu.add_config_cmd("trsync_trigger oid=%d reason=0" - % (self._ts_oid,), on_restart=True) + mcu.add_config_cmd("config_trsync oid=%d" % (self._oid,)) + mcu.add_config_cmd( + "trsync_start oid=%d report_clock=0 report_ticks=0 expire_reason=0" + % (self._oid,), on_restart=True) + # Lookup commands + self._trsync_start_cmd = mcu.lookup_command( + "trsync_start oid=%c report_clock=%u report_ticks=%u" + " expire_reason=%c", cq=self._cmd_queue) + self._trsync_set_timeout_cmd = mcu.lookup_command( + "trsync_set_timeout oid=%c clock=%u", cq=self._cmd_queue) + self._trsync_trigger_cmd = mcu.lookup_command( + "trsync_trigger oid=%c reason=%c", cq=self._cmd_queue) + self._trsync_query_cmd = mcu.lookup_query_command( + "trsync_trigger oid=%c reason=%c", + "trsync_state oid=%c can_trigger=%c trigger_reason=%c clock=%u", + oid=self._oid, cq=self._cmd_queue) + self._stepper_stop_cmd = mcu.lookup_command( + "stepper_stop_on_trigger oid=%c trsync_oid=%c", cq=self._cmd_queue) + # Create trdispatch_mcu object + set_timeout_tag = mcu.lookup_command_tag( + "trsync_set_timeout oid=%c clock=%u") + trigger_tag = mcu.lookup_command_tag("trsync_trigger oid=%c reason=%c") + state_tag = mcu.lookup_command_tag( + "trsync_state oid=%c can_trigger=%c trigger_reason=%c clock=%u") + ffi_main, ffi_lib = chelper.get_ffi() + self._trdispatch_mcu = ffi_main.gc(ffi_lib.trdispatch_mcu_alloc( + self._trdispatch, mcu._serial.serialqueue, # XXX + self._cmd_queue, self._oid, set_timeout_tag, trigger_tag, + state_tag), ffi_lib.free) + def _shutdown(self): + tc = self._trigger_completion + if tc is not None: + self._trigger_completion = None + tc.complete(False) + def _handle_trsync_state(self, params): + if not params['can_trigger']: + tc = self._trigger_completion + if tc is not None: + self._trigger_completion = None + self._reactor.async_complete(tc, True) + elif self._home_end_clock is not None: + clock = self._mcu.clock32_to_clock64(params['clock']) + if clock >= self._home_end_clock: + self._home_end_clock = None + self._trsync_trigger_cmd.send([self._oid, + self.REASON_PAST_END_TIME]) + def start(self, print_time, trigger_completion, expire_timeout): + self._trigger_completion = trigger_completion + self._home_end_clock = None + clock = self._mcu.print_time_to_clock(print_time) + expire_ticks = self._mcu.seconds_to_clock(expire_timeout) + expire_clock = clock + expire_ticks + report_ticks = self._mcu.seconds_to_clock(expire_timeout * .4) + min_extend_ticks = self._mcu.seconds_to_clock(expire_timeout * .4 * .8) + ffi_main, ffi_lib = chelper.get_ffi() + ffi_lib.trdispatch_mcu_setup(self._trdispatch_mcu, clock, expire_clock, + expire_ticks, min_extend_ticks) + self._mcu.register_response(self._handle_trsync_state, + "trsync_state", self._oid) + self._trsync_start_cmd.send([self._oid, clock, report_ticks, + self.REASON_COMMS_TIMEOUT]) + for s in self._steppers: + self._stepper_stop_cmd.send([s.get_oid(), self._oid]) + self._trsync_set_timeout_cmd.send([self._oid, expire_clock]) + def set_home_end_time(self, home_end_time): + self._home_end_clock = self._mcu.print_time_to_clock(home_end_time) + def stop(self): + self._mcu.register_response(None, "trsync_state", self._oid) + self._trigger_completion = None + if self._mcu.is_fileoutput(): + return self.REASON_ENDSTOP_HIT + params = self._trsync_query_cmd.send([self._oid, + self.REASON_HOST_REQUEST]) + for s in self._steppers: + s.note_homing_end(did_trigger=True) # XXX + return params['trigger_reason'] + +class MCU_endstop: + RETRY_QUERY = 1.000 + def __init__(self, mcu, pin_params): + self._mcu = mcu + self._pin = pin_params['pin'] + self._pullup = pin_params['pullup'] + self._invert = pin_params['invert'] self._oid = self._mcu.create_oid() + self._home_cmd = self._query_cmd = None + self._mcu.register_config_callback(self._build_config) + self._trigger_completion = None + ffi_main, ffi_lib = chelper.get_ffi() + self._trdispatch = ffi_main.gc(ffi_lib.trdispatch_alloc(), ffi_lib.free) + self._trsync = MCU_trsync(mcu, self._trdispatch) + def get_mcu(self): + return self._mcu + def add_stepper(self, stepper): + if stepper.get_mcu() is not self._mcu: + raise pins.error("Endstop and stepper must be on the same mcu") + self._trsync.add_stepper(stepper) + def get_steppers(self): + return self._trsync.get_steppers() + def _build_config(self): + # Setup config self._mcu.add_config_cmd("config_endstop oid=%d pin=%s pull_up=%d" % (self._oid, self._pin, self._pullup)) self._mcu.add_config_cmd( @@ -47,14 +153,7 @@ class MCU_endstop: " rest_ticks=0 pin_value=0 trsync_oid=0 trigger_reason=0" % (self._oid,), on_restart=True) # Lookup commands - cmd_queue = self._mcu.alloc_command_queue() - self._trsync_start_cmd = self._mcu.lookup_command( - "trsync_start oid=%c report_clock=%u report_ticks=%u" - " expire_reason=%c", cq=cmd_queue) - self._trsync_trigger_cmd = self._mcu.lookup_command( - "trsync_trigger oid=%c reason=%c", cq=cmd_queue) - self._stepper_stop_cmd = self._mcu.lookup_command( - "stepper_stop_on_trigger oid=%c trsync_oid=%c", cq=cmd_queue) + cmd_queue = self._trsync.get_command_queue() self._home_cmd = self._mcu.lookup_command( "endstop_home oid=%c clock=%u sample_ticks=%u sample_count=%c" " rest_ticks=%u pin_value=%c trsync_oid=%c trigger_reason=%c", @@ -67,55 +166,28 @@ class MCU_endstop: triggered=True): clock = self._mcu.print_time_to_clock(print_time) rest_ticks = self._mcu.print_time_to_clock(print_time+rest_time) - clock - self._min_query_time = self._reactor.monotonic() - self._last_sent_time = 0. - self._home_end_time = self._reactor.NEVER - self._trigger_completion = self._reactor.completion() - self._mcu.register_response(self._handle_trsync_state, - "trsync_state", self._ts_oid) - report_ticks = self._mcu.seconds_to_clock(0.100) - self._trsync_start_cmd.send([self._ts_oid, clock, report_ticks, 0], - reqclock=clock) - for s in self._steppers: - self._stepper_stop_cmd.send([s.get_oid(), self._ts_oid]) + reactor = self._mcu.get_printer().get_reactor() + self._trigger_completion = reactor.completion() + etrsync = self._trsync + etrsync.start(print_time, self._trigger_completion, .250) + ffi_main, ffi_lib = chelper.get_ffi() + ffi_lib.trdispatch_start(self._trdispatch, etrsync.REASON_HOST_REQUEST) self._home_cmd.send( [self._oid, clock, self._mcu.seconds_to_clock(sample_time), sample_count, rest_ticks, triggered ^ self._invert, - self._ts_oid, 0], reqclock=clock) - self._home_completion = self._reactor.register_callback( - self._home_retry) + etrsync.get_oid(), etrsync.REASON_ENDSTOP_HIT], reqclock=clock) return self._trigger_completion - def _handle_trsync_state(self, params): - logging.debug("trsync_state %s", params) - if params['#sent_time'] >= self._min_query_time: - if params['can_trigger']: - self._last_sent_time = params['#sent_time'] - else: - self._min_query_time = self._reactor.NEVER - self._reactor.async_complete(self._trigger_completion, True) - def _home_retry(self, eventtime): - if self._mcu.is_fileoutput(): - return True - while 1: - did_trigger = self._trigger_completion.wait(eventtime + 0.100) - if did_trigger is not None: - # Homing completed successfully - return True - # Check for timeout - last = self._mcu.estimated_print_time(self._last_sent_time) - if last > self._home_end_time or self._mcu.is_shutdown(): - return False def home_wait(self, home_end_time): - self._home_end_time = home_end_time - did_trigger = self._home_completion.wait() - self._trsync_trigger_cmd.send([self._ts_oid, 0]) - self._mcu.register_response(None, "trsync_state", self._ts_oid) + etrsync = self._trsync + etrsync.set_home_end_time(home_end_time) + if self._mcu.is_fileoutput(): + self._trigger_completion.complete(True) + self._trigger_completion.wait() self._home_cmd.send([self._oid, 0, 0, 0, 0, 0, 0, 0]) - for s in self._steppers: - s.note_homing_end(did_trigger=did_trigger) - if not self._trigger_completion.test(): - self._trigger_completion.complete(False) - return did_trigger + ffi_main, ffi_lib = chelper.get_ffi() + ffi_lib.trdispatch_stop(self._trdispatch) + res = etrsync.stop() + return res == etrsync.REASON_ENDSTOP_HIT def query_endstop(self, print_time): clock = self._mcu.print_time_to_clock(print_time) if self._mcu.is_fileoutput(): From bc29ee1c6d1fbd99a9d0c6db6b4419bbc8f8a7c3 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 2 Apr 2021 13:42:25 -0400 Subject: [PATCH 36/51] homing: Check for failures during multi-endstop homing If any endstop reports a failure, then stop homing on all endstops. Signed-off-by: Kevin O'Connor --- klippy/extras/homing.py | 9 +++++++-- klippy/mcu.py | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/klippy/extras/homing.py b/klippy/extras/homing.py index f6dead84..1d7e1948 100644 --- a/klippy/extras/homing.py +++ b/klippy/extras/homing.py @@ -13,8 +13,13 @@ ENDSTOP_SAMPLE_COUNT = 4 def multi_complete(printer, completions): if len(completions) == 1: return completions[0] - cb = (lambda e: all([c.wait() for c in completions])) - return printer.get_reactor().register_callback(cb) + # Build completion that waits for all completions + reactor = printer.get_reactor() + cp = reactor.register_callback(lambda e: [c.wait() for c in completions]) + # If any completion indicates an error, then exit main completion early + for c in completions: + reactor.register_callback(lambda e: cp.complete(1) if c.wait() else 0) + return cp # Implementation of homing/probing moves class HomingMove: diff --git a/klippy/mcu.py b/klippy/mcu.py index bd7267ad..1b77aff7 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -84,7 +84,9 @@ class MCU_trsync: tc = self._trigger_completion if tc is not None: self._trigger_completion = None - self._reactor.async_complete(tc, True) + reason = params['trigger_reason'] + is_failure = (reason == self.REASON_COMMS_TIMEOUT) + self._reactor.async_complete(tc, is_failure) elif self._home_end_clock is not None: clock = self._mcu.clock32_to_clock64(params['clock']) if clock >= self._home_end_clock: From acdad7640809fbfdfae45394f0301f08ceab3115 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Thu, 10 Jun 2021 21:30:09 -0400 Subject: [PATCH 37/51] klippy: Add a newline between initial error message and hint text Signed-off-by: Kevin O'Connor --- klippy/klippy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/klippy/klippy.py b/klippy/klippy.py index 5914c1ca..df8e110c 100644 --- a/klippy/klippy.py +++ b/klippy/klippy.py @@ -162,11 +162,11 @@ class Printer: cb() except (self.config_error, pins.error) as e: logging.exception("Config error") - self._set_state("%s%s" % (str(e), message_restart)) + self._set_state("%s\n%s" % (str(e), message_restart)) return except msgproto.error as e: logging.exception("Protocol error") - self._set_state("%s%s%s%s" % (str(e), message_protocol_error1, + self._set_state("%s\n%s%s%s" % (str(e), message_protocol_error1, self._get_versions(), message_protocol_error2)) util.dump_mcu_build() From d1665fae4f48519198fcd430d5fc18f17aee4e77 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 11 Jun 2021 23:02:05 -0400 Subject: [PATCH 38/51] docs: Update Code_Overview.md with the removal of calc_tag_position() Signed-off-by: Kevin O'Connor --- docs/Code_Overview.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/Code_Overview.md b/docs/Code_Overview.md index 167b7c18..2b35688f 100644 --- a/docs/Code_Overview.md +++ b/docs/Code_Overview.md @@ -447,17 +447,16 @@ but does not include moves on the look-ahead queue. One may use the `toolhead.flush_step_generation()` or `toolhead.wait_moves()` calls to fully flush the look-ahead and step generation code. -The "kinematic" position (`stepper.set_tag_position()` and -`kin.calc_tag_position()`) is the cartesian position of the toolhead -as derived from the "stepper" position and is relative to the -coordinate system specified in the config file. This may differ from -the requested cartesian position due to the granularity of the stepper -motors. If the robot is in motion when `stepper.set_tag_position()` is -issued then the reported value includes moves buffered on the -micro-controller, but does not include moves on the look-ahead -queue. One may use the `toolhead.flush_step_generation()` or -`toolhead.wait_moves()` calls to fully flush the look-ahead and step -generation code. +The "kinematic" position (`kin.calc_position()`) is the cartesian +position of the toolhead as derived from "stepper" positions and is +relative to the coordinate system specified in the config file. This +may differ from the requested cartesian position due to the +granularity of the stepper motors. If the robot is in motion when the +"stepper" positions are taken then the reported value includes moves +buffered on the micro-controller, but does not include moves on the +look-ahead queue. One may use the `toolhead.flush_step_generation()` +or `toolhead.wait_moves()` calls to fully flush the look-ahead and +step generation code. The "toolhead" position (`toolhead.get_position()`) is the last requested position of the toolhead in cartesian coordinates relative From 150604347730721ec5b4aefd38b05f7b8dddb608 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 11 Jun 2021 23:12:16 -0400 Subject: [PATCH 39/51] stepcompress: Add missing functions to stepcompress.h Signed-off-by: Kevin O'Connor --- klippy/chelper/stepcompress.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/klippy/chelper/stepcompress.h b/klippy/chelper/stepcompress.h index 2d1534c1..4c708541 100644 --- a/klippy/chelper/stepcompress.h +++ b/klippy/chelper/stepcompress.h @@ -16,6 +16,10 @@ int stepcompress_append(struct stepcompress *sc, int sdir , double print_time, double step_time); int stepcompress_commit(struct stepcompress *sc); int stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock); +int stepcompress_set_last_position(struct stepcompress *sc + , int64_t last_position); +int64_t stepcompress_find_past_position(struct stepcompress *sc + , uint64_t clock); int stepcompress_queue_msg(struct stepcompress *sc, uint32_t *data, int len); struct serialqueue; From 0bc076799776fd61a63a1e8ee53e7f3bdf5039d6 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 11 Jun 2021 21:38:09 -0400 Subject: [PATCH 40/51] stepper: Restore mcu_position on set_stepper_kinematics() and set_step_dist() Signed-off-by: Kevin O'Connor --- klippy/stepper.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/klippy/stepper.py b/klippy/stepper.py index 9b7263c1..9eb1a13c 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -84,8 +84,10 @@ class MCU_stepper: def get_step_dist(self): return self._step_dist def set_step_dist(self, dist): + mcu_pos = self.get_mcu_position() self._step_dist = dist self.set_stepper_kinematics(self._stepper_kinematics) + self._set_mcu_position(mcu_pos) def is_dir_inverted(self): return self._invert_dir def calc_position_from_coord(self, coord): @@ -93,21 +95,23 @@ class MCU_stepper: return ffi_lib.itersolve_calc_position_from_coord( self._stepper_kinematics, coord[0], coord[1], coord[2]) def set_position(self, coord): - opos = self.get_commanded_position() + mcu_pos = self.get_mcu_position() sk = self._stepper_kinematics ffi_main, ffi_lib = chelper.get_ffi() ffi_lib.itersolve_set_position(sk, coord[0], coord[1], coord[2]) - self._mcu_position_offset += opos - self.get_commanded_position() + self._set_mcu_position(mcu_pos) def get_commanded_position(self): - sk = self._stepper_kinematics ffi_main, ffi_lib = chelper.get_ffi() - return ffi_lib.itersolve_get_commanded_pos(sk) + return ffi_lib.itersolve_get_commanded_pos(self._stepper_kinematics) def get_mcu_position(self): mcu_pos_dist = self.get_commanded_position() + self._mcu_position_offset mcu_pos = mcu_pos_dist / self._step_dist if mcu_pos >= 0.: return int(mcu_pos + 0.5) return int(mcu_pos - 0.5) + def _set_mcu_position(self, mcu_pos): + mcu_pos_dist = mcu_pos * self._step_dist + self._mcu_position_offset = mcu_pos_dist - self.get_commanded_position() def get_past_mcu_position(self, print_time): clock = self._mcu.print_time_to_clock(print_time) ffi_main, ffi_lib = chelper.get_ffi() @@ -117,12 +121,14 @@ class MCU_stepper: return mcu_pos * self._step_dist - self._mcu_position_offset def set_stepper_kinematics(self, sk): old_sk = self._stepper_kinematics + mcu_pos = 0 + if old_sk is not None: + mcu_pos = self.get_mcu_position() self._stepper_kinematics = sk - if sk is not None: - ffi_main, ffi_lib = chelper.get_ffi() - ffi_lib.itersolve_set_stepcompress(sk, self._stepqueue, - self._step_dist) - self.set_trapq(self._trapq) + ffi_main, ffi_lib = chelper.get_ffi() + ffi_lib.itersolve_set_stepcompress(sk, self._stepqueue, self._step_dist) + self.set_trapq(self._trapq) + self._set_mcu_position(mcu_pos) return old_sk def note_homing_end(self, did_trigger=False): ffi_main, ffi_lib = chelper.get_ffi() @@ -142,8 +148,7 @@ class MCU_stepper: ret = ffi_lib.stepcompress_set_last_position(self._stepqueue, last_pos) if ret: raise error("Internal error in stepcompress") - mcu_pos_dist = last_pos * self._step_dist - self._mcu_position_offset = mcu_pos_dist - self.get_commanded_position() + self._set_mcu_position(last_pos) def set_trapq(self, tq): ffi_main, ffi_lib = chelper.get_ffi() if tq is None: @@ -157,16 +162,16 @@ class MCU_stepper: def generate_steps(self, flush_time): # Check for activity if necessary if self._active_callbacks: - ret = self._itersolve_check_active(self._stepper_kinematics, - flush_time) + sk = self._stepper_kinematics + ret = self._itersolve_check_active(sk, flush_time) if ret: cbs = self._active_callbacks self._active_callbacks = [] for cb in cbs: cb(ret) # Generate steps - ret = self._itersolve_generate_steps(self._stepper_kinematics, - flush_time) + sk = self._stepper_kinematics + ret = self._itersolve_generate_steps(sk, flush_time) if ret: raise error("Internal error in stepcompress") def is_active_axis(self, axis): From 6db40df6af51eefe87e601484a63881b17084f0a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Fri, 4 Jun 2021 16:45:22 -0400 Subject: [PATCH 41/51] docs: Deprecate pid_integral_max config option The default pid_integral_max setting should be sufficient; it's not necessary to complicate the documentation and config to support customization. Signed-off-by: Kevin O'Connor --- docs/Config_Changes.md | 4 ++++ docs/Config_Reference.md | 5 ----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index 15d94883..65a4890a 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -6,6 +6,10 @@ All dates in this document are approximate. # Changes +20210612: The `pid_integral_max` config option in heater and +temperature_fan sections is deprecated. The option will be removed in +the near future. + 20210503: The gcode_macro `default_parameter_` config option is deprecated. Use the `params` pseudo-variable to access macro parameters. Other methods for accessing macro parameters will be diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index e3b2b33f..5297edd2 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -714,9 +714,6 @@ pid_Ki: pid_Kd: # Kd is the "derivative" constant for the pid. This parameter must # be provided for PID heaters. -#pid_integral_max: -# The maximum "windup" the integral term may accumulate. The default -# is to use the same value as max_power. #max_delta: 2.0 # On 'watermark' controlled heaters this is the number of degrees in # Celsius above the target temperature before disabling the heater @@ -1989,7 +1986,6 @@ temperature. #pid_Kp: #pid_Ki: #pid_Kd: -#pid_integral_max: #pwm_cycle_time: #min_temp: #max_temp: @@ -2429,7 +2425,6 @@ additional information. #pid_Ki: #pid_Kd: #pid_deriv_time: -#pid_integral_max: #max_delta: #min_temp: #max_temp: From 07004a889d9664eefd65613d79c663d3ca705c20 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 8 Jun 2021 13:12:19 -0400 Subject: [PATCH 42/51] docs: Recommend pause of 2 seconds during sensorless homing Signed-off-by: Kevin O'Connor --- docs/TMC_Drivers.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/docs/TMC_Drivers.md b/docs/TMC_Drivers.md index cd705ae3..fee1e75a 100644 --- a/docs/TMC_Drivers.md +++ b/docs/TMC_Drivers.md @@ -258,10 +258,13 @@ After sensorless homing completes the carriage will be pressed against the end of the rail and the stepper will exert a force on the frame until the carriage is moved away. It is a good idea to create a macro to home the axis and immediately move the carriage away from the end -of the rail. It is recommended to set the speed of this subsequent -move so that it lasts at least two seconds (eg, `G1 X40 F1200`) to -ensure the stall flag in the TMC driver is cleared after the move -completes. +of the rail. + +It is a good idea for the macro to pause at least 2 seconds prior to +starting sensorless homing (or otherwise ensure that there has been no +movement on the stepper for 2 seconds). Without a delay it is possible +for the driver's internal stall flag to still be set from a previous +move. It can also be useful to have that macro set the driver current before homing and set a new current after the carriage has moved away. This @@ -279,11 +282,13 @@ gcode: {% set HOLD_CUR = driver_config.hold_current %} # Set current for sensorless homing SET_TMC_CURRENT STEPPER=stepper_x CURRENT={HOME_CUR} HOLDCURRENT={HOME_CUR} + # Pause to ensure driver stall flag is clear + G4 P2000 # Home G28 X0 # Move away G90 - G1 X40 F1200 + G1 X5 F1200 # Set current during print SET_TMC_CURRENT STEPPER=stepper_x CURRENT={RUN_CUR} HOLDCURRENT={HOLD_CUR} ``` @@ -308,16 +313,15 @@ sensitivity" for each carriage, but be aware of the following restrictions: 1. When using sensorless homing on CoreXY, make sure there is no `hold_current` in effect for either stepper during homing. -2. Make sure both the X and Y carriages are near the center of their - rails before each home attempt. +2. While tuning, make sure both the X and Y carriages are near the + center of their rails before each home attempt. 3. After tuning is complete, when homing both X and Y, use macros to ensure that one axis is homed first, then move that carriage away - from the axis limit using a move that lasts at least two seconds, - and then start the homing of the other carriage. The move away from - the axis helps ensure the stall flag is cleared from both stepper - drivers before starting the next home attempt. It also avoids - homing one axis while the other is pressed against the axis limit - (which may skew the stall detection). + from the axis limit, pause for at least 2 seconds, and then start + the homing of the other carriage. The move away from the axis + avoids homing one axis while the other is pressed against the axis + limit (which may skew the stall detection). The pause is necessary + to ensure the driver's stall flag is cleared prior to homing again. # Querying and diagnosing driver settings From a0c27571dd0ff841a1464a7df05a8cdd7a2d135d Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Sat, 12 Jun 2021 14:13:53 -0400 Subject: [PATCH 43/51] docs: Note previous firmware state issue with TMC2209/TMC2209 in TMC_Drivers.md If a high UART frequency is used to communicate with TMC2208/TMC2209 drivers then it may not be possible for Klipper to communicate with the drivers until they are reset. Note this in the troubleshooting document. Reported by @matthewlloyd. Signed-off-by: Kevin O'Connor --- docs/TMC_Drivers.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/TMC_Drivers.md b/docs/TMC_Drivers.md index fee1e75a..fd082bde 100644 --- a/docs/TMC_Drivers.md +++ b/docs/TMC_Drivers.md @@ -381,6 +381,12 @@ Make sure that the motor power is enabled, as the stepper motor driver generally needs motor power before it can communicate with the micro-controller. +If this error occurs after flashing Klipper for the first time, then +the stepper driver may have been previously programmed in a state that +is not compatible with Klipper. To reset the state, remove all power +from the printer for several seconds (physically unplug both USB and +power plugs). + Otherwise, this error is typically the result of incorrect UART pin wiring or an incorrect Klipper configuration of the UART pin settings. From a827ca87cad53f48d93ec0b808c28aa65939d3bb Mon Sep 17 00:00:00 2001 From: James Churchill Date: Mon, 17 May 2021 23:44:50 +1000 Subject: [PATCH 44/51] config: Add pin aliases to generic-creality-v4.2.7.cfg Signed-off-by: James Churchill --- config/generic-creality-v4.2.7.cfg | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/generic-creality-v4.2.7.cfg b/config/generic-creality-v4.2.7.cfg index 41226828..8f9d494e 100644 --- a/config/generic-creality-v4.2.7.cfg +++ b/config/generic-creality-v4.2.7.cfg @@ -90,3 +90,9 @@ max_velocity: 300 max_accel: 3000 max_z_velocity: 5 max_z_accel: 100 + +[board_pins] +aliases: + EXP1_1=PC6,EXP1_3=PB10,EXP1_5=PB14,EXP1_7=PB12,EXP1_9= + EXP1_2=PB2,EXP1_4=PB11,EXP1_6=PB13,EXP1_8=PB15,EXP1_10=<5V> + PROBE_IN=PB0,PROBE_OUT=PB1,FIL_RUNOUT=PC6 From 23f466bfce95062920b04a8fe1b4234bbe733994 Mon Sep 17 00:00:00 2001 From: "github@matthewlloyd.net" Date: Sun, 6 Jun 2021 15:51:35 -0400 Subject: [PATCH 45/51] config: Add configs for Prusa Buddy and Mini+ Signed-off-by: Matthew Lloyd --- config/generic-buddy.cfg | 159 ++++++++++++++++++++ config/printer-prusa-mini-plus-2020.cfg | 184 ++++++++++++++++++++++++ src/stm32/Kconfig | 3 + test/klippy/printers.test | 2 + 4 files changed, 348 insertions(+) create mode 100644 config/generic-buddy.cfg create mode 100644 config/printer-prusa-mini-plus-2020.cfg diff --git a/config/generic-buddy.cfg b/config/generic-buddy.cfg new file mode 100644 index 00000000..fcc8651a --- /dev/null +++ b/config/generic-buddy.cfg @@ -0,0 +1,159 @@ +# This file contains common configurations and pin mappings for the Prusa Buddy +# board. The LCD is not currently supported by Klipper, so the touchscreen will +# permanently display the bootloader screen after the Klipper firmware is flashed; +# use Fluidd, Mainsail, or OctoPrint etc. to control the printer. + +# To use this config, the firmware should be compiled for the STM32F407. When +# running "make menuconfig", enable "extra low-level configuration setup", +# select the "128KiB + 512 byte offset" bootloader, and USB communication. +# Connect the printer to your Raspberry Pi using the printer's micro-USB port. +# If you prefer to remove Prusa's stock bootloader entirely, select the +# "No bootloader" option. + +# When flashing for the first time, you will need to break the "appendix" +# on the Buddy board, then put the device into DFU mode by moving the jumper +# on the 3-pin header (older boards) or shorting the 2-pin header (newer boards) +# and resetting, and finally use "make flash" to install Klipper. Once Klipper is +# installed, you no longer need the jumper - just use "make flash" which will +# automatically put the device into DFU mode. + +# Note that if you were previously running Prusa firmware, you must fully +# power cycle the board after flashing. Otherwise, Klipper will be unable to +# communicate with the TMC2209s due to the abrupt change in the baud rate, +# and will show this error: "Unable to read tmc uart register IFCNT". + +# See docs/Config_Reference.md for a description of parameters. + +[stepper_x] +step_pin: PD1 +dir_pin: PD0 +enable_pin: !PD3 +microsteps: 16 +rotation_distance: 32 +endstop_pin: tmc2209_stepper_x:virtual_endstop +position_endstop: 200 +position_min: 0 +position_max: 200 +homing_speed: 50 +homing_retract_dist: 0 + +[stepper_y] +step_pin: PD13 +dir_pin: PD12 +enable_pin: !PD14 +microsteps: 16 +rotation_distance: 32 +endstop_pin: tmc2209_stepper_y:virtual_endstop +position_endstop: 0 +position_min: 0 +position_max: 200 +homing_speed: 50 +homing_retract_dist: 0 + +[stepper_z] +step_pin: PD4 +dir_pin: !PD15 +enable_pin: !PD2 +microsteps: 16 +rotation_distance: 4 +endstop_pin: probe:z_virtual_endstop +position_min: 0 +position_max: 200 + +[extruder] +step_pin: PD9 +dir_pin: !PD8 +enable_pin: !PD10 +microsteps: 16 +rotation_distance: 10 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PB1 +sensor_type: ATC Semitec 104GT-2 +sensor_pin: PC0 +control: pid +pid_Kp: 7 +pid_Ki: 0.5 +pid_Kd: 45 +min_temp: 10 +max_temp: 305 + +[tmc2209 stepper_x] +uart_pin: PD5 +uart_address: 1 +diag_pin: ^PE2 +driver_SGTHRS: 130 +run_current: 0.35 +sense_resistor: 0.22 +stealthchop_threshold: 999999 + +[tmc2209 stepper_y] +uart_pin: PD5 +uart_address: 3 +diag_pin: ^PE1 +driver_SGTHRS: 130 +run_current: 0.35 +sense_resistor: 0.22 +stealthchop_threshold: 999999 + +[tmc2209 stepper_z] +uart_pin: PD5 +uart_address: 0 +diag_pin: ^PE3 +driver_SGTHRS: 100 +run_current: 0.35 +sense_resistor: 0.22 +stealthchop_threshold: 999999 + +[tmc2209 extruder] +uart_pin: PD5 +uart_address: 2 +diag_pin: ^PA15 +driver_SGTHRS: 100 +run_current: 0.4 +sense_resistor: 0.22 + +[heater_bed] +heater_pin: PB0 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PA4 +control: pid +pid_Kp: 120 +pid_Ki: 1.5 +pid_Kd: 600 +min_temp: 10 +max_temp: 110 + +# Hotend fan. +[heater_fan hotend_fan] +pin: PE9 +tachometer_pin: PE14 + +# Part cooling fan. +[fan] +pin: PE11 +tachometer_pin: PE10 + +# The SuperPINDA has built-in temperature compensation and no thermistor output, +# so no compensation table is needed. The PINDA thermistor is otherwise on pin PA6. +[probe] +pin: PA8 +x_offset: -29 +y_offset: -3 +z_offset: 0 +speed: 6.0 + +[filament_switch_sensor filament_sensor] +switch_pin: ^PB4 +pause_on_runout: True + +[mcu] +serial: /dev/serial/by-id/usb-Klipper_stm32f407xx_3100380013504E4E53353420-if00 +restart_method: command + +[printer] +kinematics: cartesian +max_velocity: 180 +max_accel: 1250 +max_z_velocity: 12 +max_z_accel: 400 diff --git a/config/printer-prusa-mini-plus-2020.cfg b/config/printer-prusa-mini-plus-2020.cfg new file mode 100644 index 00000000..a28382ef --- /dev/null +++ b/config/printer-prusa-mini-plus-2020.cfg @@ -0,0 +1,184 @@ +# This file contains common configurations and pin mappings for the Prusa +# Mini+, which uses the Prusa Buddy board. The printer's LCD is not currently +# supported by Klipper, so the touchscreen will permanently display the +# bootloader screen after the Klipper firmware is flashed; use Fluidd, Mainsail, +# or OctoPrint etc. to control the printer. + +# To use this config, the firmware should be compiled for the STM32F407. When +# running "make menuconfig", enable "extra low-level configuration setup", +# select the "128KiB + 512 byte offset" bootloader, and USB communication. +# Connect the printer to your Raspberry Pi using the printer's micro-USB port. +# If you prefer to remove Prusa's stock bootloader entirely, select the +# "No bootloader" option. + +# When flashing for the first time, you will need to break the "appendix" +# on the Buddy board, then put the device into DFU mode by moving the jumper +# on the 3-pin header (older boards) or shorting the 2-pin header (newer boards) +# and resetting, and finally use "make flash" to install Klipper. Once Klipper is +# installed, you no longer need the jumper - just use "make flash" which will +# automatically put the device into DFU mode. + +# Note that if you were previously running Prusa firmware, you must fully +# power cycle the board after flashing. Otherwise, Klipper will be unable to +# communicate with the TMC2209s due to the abrupt change in the baud rate, +# and will show this error: "Unable to read tmc uart register IFCNT". + +# See docs/Config_Reference.md for a description of parameters. + +[stepper_x] +step_pin: PD1 +dir_pin: PD0 +enable_pin: !PD3 +microsteps: 16 +rotation_distance: 32 # 200 * 16 / 100 +endstop_pin: tmc2209_stepper_x:virtual_endstop +position_endstop: 180.4 +position_min: -2 +position_max: 180.4 +homing_speed: 50 +homing_retract_dist: 0 + +[stepper_y] +step_pin: PD13 +dir_pin: PD12 +enable_pin: !PD14 +microsteps: 16 +rotation_distance: 32 # 200 * 16 / 100 +endstop_pin: tmc2209_stepper_y:virtual_endstop +position_endstop: -3 +position_min: -3 +position_max: 180 +homing_speed: 50 +homing_retract_dist: 0 + +[stepper_z] +step_pin: PD4 +dir_pin: !PD15 +enable_pin: !PD2 +microsteps: 16 +rotation_distance: 4 +endstop_pin: probe:z_virtual_endstop +position_min: 0 +position_max: 185 + +[extruder] +step_pin: PD9 +dir_pin: !PD8 +enable_pin: !PD10 +microsteps: 16 +rotation_distance: 9.84615 # 200 * 16 / 325 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PB1 +sensor_type: ATC Semitec 104GT-2 +sensor_pin: PC0 +control: pid +# Prusa's firmware defaults. +pid_Kp: 7 +pid_Ki: 0.5 +pid_Kd: 45 +min_temp: 10 +max_temp: 305 + +[tmc2209 stepper_x] +uart_pin: PD5 +uart_address: 1 +diag_pin: ^PE2 +driver_SGTHRS: 130 +run_current: 0.35 +sense_resistor: 0.22 +stealthchop_threshold: 999999 + +[tmc2209 stepper_y] +uart_pin: PD5 +uart_address: 3 +diag_pin: ^PE1 +driver_SGTHRS: 130 +run_current: 0.35 +sense_resistor: 0.22 +stealthchop_threshold: 999999 + +[tmc2209 stepper_z] +uart_pin: PD5 +uart_address: 0 +diag_pin: ^PE3 +driver_SGTHRS: 100 +run_current: 0.35 +sense_resistor: 0.22 +stealthchop_threshold: 999999 + +[tmc2209 extruder] +uart_pin: PD5 +uart_address: 2 +diag_pin: ^PA15 +driver_SGTHRS: 100 +run_current: 0.4 +sense_resistor: 0.22 + +[heater_bed] +heater_pin: PB0 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PA4 +control: pid +# Prusa's firmware defaults. +pid_Kp: 120 +pid_Ki: 1.5 +pid_Kd: 600 +min_temp: 10 +max_temp: 110 + +# Hotend fan. +# The stock firmware uses control ranges of PWM 0-50%, RPM 1000-8000. +# Change fan_speed below to match your preference. Measured speeds: +# fan_speed 0.5: 50% PWM = 4000RPM (Prusa stock default speed) +# fan_speed 1.0: 100% PWM = 8000RPM (safe but loud) +[heater_fan hotend_fan] +pin: PE9 +tachometer_pin: PE14 +fan_speed: 0.5 + +# Part cooling fan. +# The stock firmware uses control ranges of PWM 10-50%, RPM 500-5000. +# To match stock firmware, set the Klipper fan speed to 50%. This speed +# can be safely increased to 100% for better part cooling. Measured speeds: +# 50% PWM = 2500RPM (Prusa stock default speed) +# 100% PWM = 5000RPM (better cooling, still quiet) +[fan] +pin: PE11 +tachometer_pin: PE10 + +# The SuperPINDA has built-in temperature compensation and no thermistor output, +# so no compensation table is needed here. +[probe] +pin: PA8 +x_offset: -29 +y_offset: -3 +z_offset: 0 # set this to your Live Z Offset, but negated (invert the sign) +speed: 6.0 + +[safe_z_home] +home_xy_position: 147.4,21.1 +z_hop: 4 + +[bed_mesh] +speed: 100 +horizontal_move_z: 5 +mesh_min: 10,10 +mesh_max: 141,167 +probe_count: 4,4 + +[filament_switch_sensor filament_sensor] +switch_pin: ^PB4 +pause_on_runout: True + +[mcu] +serial: /dev/serial/by-id/usb-Klipper_stm32f407xx_3100380013504E4E53353420-if00 +restart_method: command + +[printer] +kinematics: cartesian +# Prusa firmware defaults. +max_velocity: 180 +max_accel: 1250 +max_z_velocity: 12 +max_z_accel: 400 diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index 90ba6523..944d3f6f 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -142,6 +142,8 @@ choice bool "32KiB bootloader" if MACH_STM32F207 || MACH_STM32F407 || MACH_STM32F446 config STM32_FLASH_START_8800 bool "34KiB bootloader (Chitu v6 Bootloader)" if MACH_STM32F103 + config STM32_FLASH_START_20200 + bool "128KiB bootloader with 512 byte offset (Prusa Buddy)" if MACH_STM32F407 config STM32_FLASH_START_C000 bool "48KiB bootloader (MKS Robin Nano V3)" if MACH_STM32F407 config STM32_FLASH_START_10000 @@ -166,6 +168,7 @@ config FLASH_START default 0x8008800 if STM32_FLASH_START_8800 default 0x800C000 if STM32_FLASH_START_C000 default 0x8010000 if STM32_FLASH_START_10000 + default 0x8020200 if STM32_FLASH_START_20200 default 0x8000000 config ARMCM_RAM_VECTORTABLE diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 668b8687..29e9d63b 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -166,8 +166,10 @@ DICTIONARY stm32f407.dict CONFIG ../../config/generic-bigtreetech-gtr.cfg CONFIG ../../config/generic-bigtreetech-skr-pro.cfg CONFIG ../../config/generic-bigtreetech-skr-2.cfg +CONFIG ../../config/generic-buddy.cfg CONFIG ../../config/generic-flyboard.cfg CONFIG ../../config/generic-mks-robin-nano-v3.cfg +CONFIG ../../config/printer-prusa-mini-plus-2020.cfg # Printers using the stm32f446 DICTIONARY stm32f407.dict From 6dcb902d896e42c78779068adfdc67bf17c4698e Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 14 Jun 2021 12:40:36 -0400 Subject: [PATCH 46/51] config: Rename generic-buddy.cfg to generic-prusa-buddy.cfg Signed-off-by: Kevin O'Connor --- config/{generic-buddy.cfg => generic-prusa-buddy.cfg} | 2 +- test/klippy/printers.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename config/{generic-buddy.cfg => generic-prusa-buddy.cfg} (99%) diff --git a/config/generic-buddy.cfg b/config/generic-prusa-buddy.cfg similarity index 99% rename from config/generic-buddy.cfg rename to config/generic-prusa-buddy.cfg index fcc8651a..0ea65f99 100644 --- a/config/generic-buddy.cfg +++ b/config/generic-prusa-buddy.cfg @@ -65,7 +65,7 @@ step_pin: PD9 dir_pin: !PD8 enable_pin: !PD10 microsteps: 16 -rotation_distance: 10 +rotation_distance: 33.500 nozzle_diameter: 0.400 filament_diameter: 1.750 heater_pin: PB1 diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 29e9d63b..673cfd1c 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -166,9 +166,9 @@ DICTIONARY stm32f407.dict CONFIG ../../config/generic-bigtreetech-gtr.cfg CONFIG ../../config/generic-bigtreetech-skr-pro.cfg CONFIG ../../config/generic-bigtreetech-skr-2.cfg -CONFIG ../../config/generic-buddy.cfg CONFIG ../../config/generic-flyboard.cfg CONFIG ../../config/generic-mks-robin-nano-v3.cfg +CONFIG ../../config/generic-prusa-buddy.cfg CONFIG ../../config/printer-prusa-mini-plus-2020.cfg # Printers using the stm32f446 From d2c2aaf55df955e1e0f727d22362d57b41e614f5 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 8 Jun 2021 10:39:55 +0000 Subject: [PATCH 47/51] extruder: expose `can_extrude` flag based on temperature Sometimes an automated filament load is implemented, but extruder might not always extrude filament. This adds a flag to check if this operation is possible. Signed-off-by: Kamil Trzcinski --- docs/Status_Reference.md | 2 ++ klippy/kinematics/extruder.py | 1 + 2 files changed, 3 insertions(+) diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 17a759f5..81449a6a 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -144,6 +144,8 @@ The following information is available for heater objects such as the given heater. - `power`: The last setting of the PWM pin (a value between 0.0 and 1.0) associated with the heater. +- `can_extrude`: If extruder can extrude (defined by `min_extrude_temp`), + available only for [extruder](Config_Reference.md#extruder) # heaters diff --git a/klippy/kinematics/extruder.py b/klippy/kinematics/extruder.py index 6f2138ae..a232d0b7 100644 --- a/klippy/kinematics/extruder.py +++ b/klippy/kinematics/extruder.py @@ -91,6 +91,7 @@ class PrinterExtruder: self.pressure_advance_smooth_time = smooth_time def get_status(self, eventtime): return dict(self.heater.get_status(eventtime), + can_extrude=self.heater.can_extrude, pressure_advance=self.pressure_advance, smooth_time=self.pressure_advance_smooth_time) def get_name(self): From f7279a037d7760befb2f085ab0ab99057681862e Mon Sep 17 00:00:00 2001 From: Alex Zellner <37265689+zellneralex@users.noreply.github.com> Date: Mon, 14 Jun 2021 19:01:42 +0200 Subject: [PATCH 48/51] docs: add CANCEL_PRINT to docs/G-Codes.md (#4370) Signed-off-by: Alex Zellner --- docs/G-Codes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 2db1f491..0bf8099b 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -587,6 +587,7 @@ enabled: print. This is useful if one decides to cancel a print after a PAUSE. It is recommended to add this to your start gcode to make sure the paused state is fresh for each print. +- `CANCEL_PRINT`: Cancels the current print. ## Filament Sensor From 46f51b2bb0dadfff0382af32c22356571bb46d17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 14 Jun 2021 21:09:55 +0200 Subject: [PATCH 49/51] print_stats: add `cancelled` when `CANCEL_PRINT` used (#4366) Before this change, a `CANCEL_PRINT` set a `print_stats` to `paused` that would later be workaround-ed with `fluidd`/`mainsail` to re-define `CANCEL_PRINT`. This sets a proper canceled state, but additionally closes a file from a `virtual_sdcard` context for `canceled`/`error`, as this is no longer resumable from this point. Signed-off-by: Kamil Trzcinski --- klippy/extras/pause_resume.py | 9 ++++++--- klippy/extras/print_stats.py | 12 ++++++++---- klippy/extras/virtual_sdcard.py | 14 ++++++++++++-- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/klippy/extras/pause_resume.py b/klippy/extras/pause_resume.py index 229bd6f4..4d9684f2 100644 --- a/klippy/extras/pause_resume.py +++ b/klippy/extras/pause_resume.py @@ -42,12 +42,14 @@ class PauseResume: return { 'is_paused': self.is_paused } + def is_sd_active(self): + return self.v_sd is not None and self.v_sd.is_active() def send_pause_command(self): # This sends the appropriate pause command from an event. Note # the difference between pause_command_sent and is_paused, the # module isn't officially paused until the PAUSE gcode executes. if not self.pause_command_sent: - if self.v_sd is not None and self.v_sd.is_active(): + if self.is_sd_active(): # Printing from virtual sd, run pause command self.sd_paused = True self.v_sd.do_pause() @@ -88,8 +90,9 @@ class PauseResume: self.is_paused = self.pause_command_sent = False cmd_CANCEL_PRINT_help = ("Cancel the current print") def cmd_CANCEL_PRINT(self, gcmd): - self.cmd_PAUSE(gcmd) - if not self.sd_paused: + if self.is_sd_active() or self.sd_paused: + self.v_sd.do_cancel() + else: gcmd.respond_info("action:cancel") self.cmd_CLEAR_PAUSE(gcmd) diff --git a/klippy/extras/print_stats.py b/klippy/extras/print_stats.py index 7fc816d9..ab300f27 100644 --- a/klippy/extras/print_stats.py +++ b/klippy/extras/print_stats.py @@ -41,11 +41,15 @@ class PrintStats: self._update_filament_usage(curtime) if self.state != "error": self.state = "paused" - def note_error(self, message): - self.state = "error" - self.error_message = message def note_complete(self): - self.state = "complete" + self._note_finish("complete") + def note_error(self, message): + self._note_finish("error", message) + def note_cancel(self): + self._note_finish("cancelled") + def _note_finish(self, state, error_message = ""): + self.state = state + self.error_message = error_message eventtime = self.reactor.monotonic() self.total_duration = eventtime - self.print_start_time if self.filament_used < 0.0000001: diff --git a/klippy/extras/virtual_sdcard.py b/klippy/extras/virtual_sdcard.py index 66fc5ade..3cd77612 100644 --- a/klippy/extras/virtual_sdcard.py +++ b/klippy/extras/virtual_sdcard.py @@ -98,6 +98,13 @@ class VirtualSD: self.must_pause_work = False self.work_timer = self.reactor.register_timer( self.work_handler, self.reactor.NOW) + def do_cancel(self): + if self.current_file is not None: + self.do_pause() + self.current_file.close() + self.current_file = None + self.print_stats.note_cancel() + self.file_position = self.file_size = 0. # G-Code commands def cmd_error(self, gcmd): raise gcmd.error("SD write not supported") @@ -212,6 +219,7 @@ class VirtualSD: gcode_mutex = self.gcode.get_mutex() partial_input = "" lines = [] + error_message = None while not self.must_pause_work: if not lines: # Read more data @@ -245,7 +253,7 @@ class VirtualSD: try: self.gcode.run_script(line) except self.gcode.error as e: - self.print_stats.note_error(str(e)) + error_message = str(e) break except: logging.exception("virtual_sdcard dispatch") @@ -265,7 +273,9 @@ class VirtualSD: logging.info("Exiting SD card print (position %d)", self.file_position) self.work_timer = None self.cmd_from_sd = False - if self.current_file is not None: + if error_message is not None: + self.print_stats.note_error(error_message) + elif self.current_file is not None: self.print_stats.note_pause() else: self.print_stats.note_complete() From 5161c403b2dc430326a1d8bcb798b41492d234e1 Mon Sep 17 00:00:00 2001 From: KoeRt888 <65965320+KoeRt888@users.noreply.github.com> Date: Mon, 14 Jun 2021 17:40:51 -0700 Subject: [PATCH 50/51] config: Add generic-bigtreetech-skr-cr6-v1.0.cfg (#4378) Signed-off-by: Carlo Hoffmann --- config/generic-bigtreetech-skr-cr6-v1.0.cfg | 129 ++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 config/generic-bigtreetech-skr-cr6-v1.0.cfg diff --git a/config/generic-bigtreetech-skr-cr6-v1.0.cfg b/config/generic-bigtreetech-skr-cr6-v1.0.cfg new file mode 100644 index 00000000..12a42de8 --- /dev/null +++ b/config/generic-bigtreetech-skr-cr6-v1.0.cfg @@ -0,0 +1,129 @@ +# This file contains common pin mappings for the BIGTREETECH SKR CR6 +# V1.0. To use this config, the firmware should be compiled for the +# STM32F103 with a "28KiB bootloader" and USB communication. Also, +# select "Enable extra low-level configuration options" and configure +# "GPIO pins to set at micro-controller startup" to "!PA14". + +# The "make flash" command does not work on the SKR mini E3. Instead, +# after running "make", copy the generated "out/klipper.bin" file to a +# file named "firmware.bin" on an SD card and then restart the SKR +# CR6 V1.0 with that SD card. + +# See docs/Config_Reference.md for a description of parameters. + +[stepper_x] +step_pin: PB13 +dir_pin: !PB12 +enable_pin: !PB14 +microsteps: 16 +rotation_distance: 40 +endstop_pin: PC0 +position_min: -5 +position_endstop: -5 +position_max: 235 +homing_speed: 50 + +[tmc2209 stepper_x] +uart_pin: PC11 +tx_pin: PC10 +run_current: 0.550 +hold_current: 0.450 +stealthchop_threshold: 999999 +uart_address: 0 + +[stepper_y] +step_pin: PB10 +dir_pin: PB2 +enable_pin: !PB11 +microsteps: 16 +rotation_distance: 40 +endstop_pin: PC1 +position_min: -2 +position_endstop: -2 +position_max: 235 +homing_speed: 50 + +[tmc2209 stepper_y] +uart_pin: PC11 +tx_pin: PC10 +uart_address: 2 +run_current: 0.550 +hold_current: 0.450 +stealthchop_threshold: 999999 + +[stepper_z] +step_pin: PB0 +dir_pin: !PC5 +enable_pin: !PB1 +microsteps: 16 +rotation_distance: 8 +endstop_pin: probe:z_virtual_endstop +position_min: -1.0 +position_max: 250 + +[tmc2209 stepper_z] +uart_pin: PC11 +tx_pin: PC10 +uart_address: 1 +run_current: 0.550 +hold_current: 0.450 +stealthchop_threshold: 999999 + + +[extruder] +max_extrude_only_distance: 1000.0 +step_pin: PB3 +dir_pin: !PB4 +enable_pin: !PD2 +microsteps: 16 +rotation_distance: 30.4768 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PC8 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PA0 +control: pid +# tuned for stock hardware with 200 degree Celsius target +pid_Kp: 14.32 +pid_Ki: 0.81 +pid_Kd: 63.12 +min_temp: 0 +max_temp: 275 + +[tmc2209 extruder] +uart_pin: PC11 +tx_pin: PC10 +uart_address: 3 +run_current: 0.600 +hold_current: 0.400 +stealthchop_threshold: 999999 + +[heater_bed] +heater_pin: PC9 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PC3 +control: pid +# tuned for stock hardware with 50 degree Celsius target +pid_Kp: 79.49 +pid_Ki: 1.17 +pid_Kd: 1349.52 +min_temp: 0 +max_temp: 120 + +[fan] +pin: PC6 +kick_start_time: 0.5 + +[mcu] +serial: /dev/serial/by-id/usb-Klipper_stm32f103xe_30FFD8054254353925741557-if00 +restart_method: command + +[printer] +kinematics: cartesian +max_velocity: 500 +max_accel: 500 +max_z_velocity: 5 +max_z_accel: 100 + +[static_digital_output usb_pullup_enable] +pins: !PA14 From e520fb78782f7148123fd26dfc9511313d016111 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 14 Jun 2021 20:47:02 -0400 Subject: [PATCH 51/51] config: Add generic-bigtreetech-skr-cr6-v1.0.cfg and fixes Signed-off-by: Kevin O'Connor --- config/generic-bigtreetech-skr-cr6-v1.0.cfg | 13 ++++--------- test/klippy/printers.test | 1 + 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/config/generic-bigtreetech-skr-cr6-v1.0.cfg b/config/generic-bigtreetech-skr-cr6-v1.0.cfg index 12a42de8..d14deb18 100644 --- a/config/generic-bigtreetech-skr-cr6-v1.0.cfg +++ b/config/generic-bigtreetech-skr-cr6-v1.0.cfg @@ -4,7 +4,7 @@ # select "Enable extra low-level configuration options" and configure # "GPIO pins to set at micro-controller startup" to "!PA14". -# The "make flash" command does not work on the SKR mini E3. Instead, +# The "make flash" command does not work on the SKR CR6. Instead, # after running "make", copy the generated "out/klipper.bin" file to a # file named "firmware.bin" on an SD card and then restart the SKR # CR6 V1.0 with that SD card. @@ -57,7 +57,8 @@ dir_pin: !PC5 enable_pin: !PB1 microsteps: 16 rotation_distance: 8 -endstop_pin: probe:z_virtual_endstop +endstop_pin: PC2 +position_endstop: 0.0 position_min: -1.0 position_max: 250 @@ -69,9 +70,7 @@ run_current: 0.550 hold_current: 0.450 stealthchop_threshold: 999999 - [extruder] -max_extrude_only_distance: 1000.0 step_pin: PB3 dir_pin: !PB4 enable_pin: !PD2 @@ -83,7 +82,6 @@ heater_pin: PC8 sensor_type: EPCOS 100K B57560G104F sensor_pin: PA0 control: pid -# tuned for stock hardware with 200 degree Celsius target pid_Kp: 14.32 pid_Ki: 0.81 pid_Kd: 63.12 @@ -103,7 +101,6 @@ heater_pin: PC9 sensor_type: EPCOS 100K B57560G104F sensor_pin: PC3 control: pid -# tuned for stock hardware with 50 degree Celsius target pid_Kp: 79.49 pid_Ki: 1.17 pid_Kd: 1349.52 @@ -112,11 +109,9 @@ max_temp: 120 [fan] pin: PC6 -kick_start_time: 0.5 [mcu] -serial: /dev/serial/by-id/usb-Klipper_stm32f103xe_30FFD8054254353925741557-if00 -restart_method: command +serial: /dev/serial/by-id/usb-Klipper_stm32f103xe_000000000000000000000000-if00 [printer] kinematics: cartesian diff --git a/test/klippy/printers.test b/test/klippy/printers.test index 673cfd1c..1686e9d8 100644 --- a/test/klippy/printers.test +++ b/test/klippy/printers.test @@ -135,6 +135,7 @@ CONFIG ../../config/generic-bigtreetech-skr-mini-e3-v1.0.cfg CONFIG ../../config/generic-bigtreetech-skr-mini-e3-v1.2.cfg CONFIG ../../config/generic-bigtreetech-skr-mini-e3-v2.0.cfg CONFIG ../../config/generic-bigtreetech-skr-mini-mz.cfg +CONFIG ../../config/generic-bigtreetech-skr-cr6-v1.0.cfg CONFIG ../../config/generic-bigtreetech-skr-e3-dip.cfg # Printers using the stm32f103 via serial