From cfc58d3ce7347002c43eaa92574e09095a039dae Mon Sep 17 00:00:00 2001 From: Ben Lye Date: Mon, 11 Aug 2025 22:49:35 +0100 Subject: [PATCH 01/23] spi_flash: Add qidi-x7 to board_defs.py (#6979) Added board definition for stm32f401xc-based Qidi X-7 board used in Qidi Q1 Pro and Plus4. Signed-off-by: Ben Lye --- scripts/spi_flash/board_defs.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/spi_flash/board_defs.py b/scripts/spi_flash/board_defs.py index 44eefa4f..0e82d27b 100644 --- a/scripts/spi_flash/board_defs.py +++ b/scripts/spi_flash/board_defs.py @@ -169,6 +169,14 @@ BOARD_DEFS = { "cs_pin": "PB12", "firmware_path": "ZNP_ROBIN_NANO.bin", "current_firmware_path": "ZNP_ROBIN_NANO.CUR" + }, + 'qidi-x7': { + 'mcu': "stm32f401xc", + 'spi_bus': "spi2", + 'cs_pin': "PB12", + 'skip_verify': False, + 'firmware_path': 'qd_mcu.bin', + 'current_firmware_path': 'qd_mcu.CUR' } } @@ -219,7 +227,9 @@ BOARD_ALIASES = { 'fysetc-s6-v2': BOARD_DEFS['fysetc-spider'], 'robin_v3': BOARD_DEFS['monster8'], 'btt-skrat-v1.0': BOARD_DEFS['btt-skrat'], - 'chitu-v6': BOARD_DEFS['chitu-v6'] + 'chitu-v6': BOARD_DEFS['chitu-v6'], + 'qidi-q1-pro': BOARD_DEFS['qidi-x7'], + 'qidi-plus4': BOARD_DEFS['qidi-x7'] } def list_boards(): From 3ef760c18ff8dfd9710ab4f0fae6cf644d3d7c34 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 4 Aug 2025 12:02:35 -0400 Subject: [PATCH 02/23] toolhead: Remove support for max_accel_to_decel This support was deprecated on 20240313. Remove the remaining compatibility code. Signed-off-by: Kevin O'Connor --- docs/Config_Changes.md | 5 +++++ docs/Config_Reference.md | 3 --- klippy/toolhead.py | 19 +------------------ test/klippy/commands.test | 2 +- 4 files changed, 7 insertions(+), 22 deletions(-) diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index 4ab7d33d..838b2327 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,11 @@ All dates in this document are approximate. ## Changes +20250811: Support for the `max_accel_to_decel` parameter in the +`[printer]` config section has been removed and support for the +`ACCEL_TO_DECEL` parameter in the `SET_VELOCITY_LIMIT` command has +been removed. These capabilities were deprecated on 20240313. + 20250721: The `[pca9632]` and `[mcp4018]` modules no longer accept the `scl_pin` and `sda_pin` options. Use `i2c_software_scl_pin` and `i2c_software_sda_pin` instead. diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index a9b6db6c..83de9609 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -126,8 +126,6 @@ max_accel: # decelerate to zero at each corner. The value specified here may be # changed at runtime using the SET_VELOCITY_LIMIT command. The # default is 5mm/s. -#max_accel_to_decel: -# This parameter is deprecated and should no longer be used. ``` ### [stepper] @@ -740,7 +738,6 @@ max_velocity: max_accel: #minimum_cruise_ratio: #square_corner_velocity: -#max_accel_to_decel: #max_z_velocity: #max_z_accel: diff --git a/klippy/toolhead.py b/klippy/toolhead.py index f835977c..f45fe839 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -219,17 +219,8 @@ class ToolHead: # Velocity and acceleration control self.max_velocity = config.getfloat('max_velocity', above=0.) self.max_accel = config.getfloat('max_accel', above=0.) - min_cruise_ratio = 0.5 - if config.getfloat('minimum_cruise_ratio', None) is None: - req_accel_to_decel = config.getfloat('max_accel_to_decel', None, - above=0.) - if req_accel_to_decel is not None: - config.deprecate('max_accel_to_decel') - min_cruise_ratio = 1. - min(1., (req_accel_to_decel - / self.max_accel)) self.min_cruise_ratio = config.getfloat('minimum_cruise_ratio', - min_cruise_ratio, - below=1., minval=0.) + 0.5, below=1., minval=0.) self.square_corner_velocity = config.getfloat( 'square_corner_velocity', 5., minval=0.) self.junction_deviation = self.max_accel_to_decel = 0. @@ -686,14 +677,6 @@ class ToolHead: 'SQUARE_CORNER_VELOCITY', None, minval=0.) min_cruise_ratio = gcmd.get_float( 'MINIMUM_CRUISE_RATIO', None, minval=0., below=1.) - if min_cruise_ratio is None: - req_accel_to_decel = gcmd.get_float('ACCEL_TO_DECEL', - None, above=0.) - if req_accel_to_decel is not None and max_accel is not None: - min_cruise_ratio = 1. - min(1., req_accel_to_decel / max_accel) - elif req_accel_to_decel is not None and max_accel is None: - min_cruise_ratio = 1. - min(1., (req_accel_to_decel - / self.max_accel)) if max_velocity is not None: self.max_velocity = max_velocity if max_accel is not None: diff --git a/test/klippy/commands.test b/test/klippy/commands.test index 33c59961..cb3b17d9 100644 --- a/test/klippy/commands.test +++ b/test/klippy/commands.test @@ -36,7 +36,7 @@ SET_GCODE_OFFSET Z=.1 M206 Z-.2 SET_GCODE_OFFSET Z_ADJUST=-.1 -SET_VELOCITY_LIMIT ACCEL=100 VELOCITY=20 SQUARE_CORNER_VELOCITY=1 ACCEL_TO_DECEL=200 +SET_VELOCITY_LIMIT ACCEL=100 VELOCITY=20 SQUARE_CORNER_VELOCITY=1 MINIMUM_CRUISE_RATIO=0 M204 S500 SET_PRESSURE_ADVANCE EXTRUDER=extruder ADVANCE=.001 From bcd4510958b4e2280266b403051894ed43fa0308 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 4 Aug 2025 13:21:55 -0400 Subject: [PATCH 03/23] toolhead: Move extra module loading out of core Toolhead() class Load these extra modules from add_printer_objects() instead. Signed-off-by: Kevin O'Connor --- klippy/toolhead.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index f45fe839..bb65c1a9 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -279,11 +279,6 @@ class ToolHead: gcode.register_command('M204', self.cmd_M204) self.printer.register_event_handler("klippy:shutdown", self._handle_shutdown) - # Load some default modules - modules = ["gcode_move", "homing", "idle_timeout", "statistics", - "manual_probe", "tuning_tower", "garbage_collection"] - for module_name in modules: - self.printer.load_object(config, module_name) # Print time and flush tracking def _advance_flush_time(self, flush_time): flush_time = max(flush_time, self.last_flush_time) @@ -712,5 +707,11 @@ class ToolHead: self._calc_junction_deviation() def add_printer_objects(config): - config.get_printer().add_object('toolhead', ToolHead(config)) + printer = config.get_printer() + printer.add_object('toolhead', ToolHead(config)) kinematics.extruder.add_printer_objects(config) + # Load some default modules + modules = ["gcode_move", "homing", "idle_timeout", "statistics", + "manual_probe", "tuning_tower", "garbage_collection"] + for module_name in modules: + printer.load_object(config, module_name) From f8da8099d5e5cdec366841728a28da23d3f9bb07 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 4 Aug 2025 13:45:59 -0400 Subject: [PATCH 04/23] toolhead: Move g-code command handlers to new ToolHeadCommandHelper() class Move the g-code command handlers to a new class. This reduces the size of the main Toolhead() class. Signed-off-by: Kevin O'Connor --- klippy/toolhead.py | 67 +++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index bb65c1a9..c78b215b 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -270,13 +270,7 @@ class ToolHead: msg = "Error loading kinematics '%s'" % (kin_name,) logging.exception(msg) raise config.error(msg) - # Register commands - gcode.register_command('G4', self.cmd_G4) - gcode.register_command('M400', self.cmd_M400) - gcode.register_command('SET_VELOCITY_LIMIT', - self.cmd_SET_VELOCITY_LIMIT, - desc=self.cmd_SET_VELOCITY_LIMIT_help) - gcode.register_command('M204', self.cmd_M204) + # Register handlers self.printer.register_event_handler("klippy:shutdown", self._handle_shutdown) # Print time and flush tracking @@ -657,21 +651,8 @@ class ToolHead: scv2 = self.square_corner_velocity**2 self.junction_deviation = scv2 * (math.sqrt(2.) - 1.) / self.max_accel self.max_accel_to_decel = self.max_accel * (1. - self.min_cruise_ratio) - def cmd_G4(self, gcmd): - # Dwell - delay = gcmd.get_float('P', 0., minval=0.) / 1000. - self.dwell(delay) - def cmd_M400(self, gcmd): - # Wait for current moves to finish - self.wait_moves() - cmd_SET_VELOCITY_LIMIT_help = "Set printer velocity limits" - def cmd_SET_VELOCITY_LIMIT(self, gcmd): - max_velocity = gcmd.get_float('VELOCITY', None, above=0.) - max_accel = gcmd.get_float('ACCEL', None, above=0.) - square_corner_velocity = gcmd.get_float( - 'SQUARE_CORNER_VELOCITY', None, minval=0.) - min_cruise_ratio = gcmd.get_float( - 'MINIMUM_CRUISE_RATIO', None, minval=0., below=1.) + def set_max_velocities(self, max_velocity, max_accel, + square_corner_velocity, min_cruise_ratio): if max_velocity is not None: self.max_velocity = max_velocity if max_accel is not None: @@ -681,12 +662,43 @@ class ToolHead: if min_cruise_ratio is not None: self.min_cruise_ratio = min_cruise_ratio self._calc_junction_deviation() + return (self.max_velocity, self.max_accel, + self.square_corner_velocity, self.min_cruise_ratio) + +# Support common G-Code commands relative to the toolhead +class ToolHeadCommandHelper: + def __init__(self, config): + self.printer = config.get_printer() + self.toolhead = self.printer.lookup_object("toolhead") + # Register commands + gcode = self.printer.lookup_object('gcode') + gcode.register_command('G4', self.cmd_G4) + gcode.register_command('M400', self.cmd_M400) + gcode.register_command('SET_VELOCITY_LIMIT', + self.cmd_SET_VELOCITY_LIMIT, + desc=self.cmd_SET_VELOCITY_LIMIT_help) + gcode.register_command('M204', self.cmd_M204) + def cmd_G4(self, gcmd): + # Dwell + delay = gcmd.get_float('P', 0., minval=0.) / 1000. + self.toolhead.dwell(delay) + def cmd_M400(self, gcmd): + # Wait for current moves to finish + self.toolhead.wait_moves() + cmd_SET_VELOCITY_LIMIT_help = "Set printer velocity limits" + def cmd_SET_VELOCITY_LIMIT(self, gcmd): + max_velocity = gcmd.get_float('VELOCITY', None, above=0.) + max_accel = gcmd.get_float('ACCEL', None, above=0.) + square_corner_velocity = gcmd.get_float( + 'SQUARE_CORNER_VELOCITY', None, minval=0.) + min_cruise_ratio = gcmd.get_float( + 'MINIMUM_CRUISE_RATIO', None, minval=0., below=1.) + mv, ma, scv, mcr = self.toolhead.set_max_velocities( + max_velocity, max_accel, square_corner_velocity, min_cruise_ratio) msg = ("max_velocity: %.6f\n" "max_accel: %.6f\n" "minimum_cruise_ratio: %.6f\n" - "square_corner_velocity: %.6f" % ( - self.max_velocity, self.max_accel, - self.min_cruise_ratio, self.square_corner_velocity)) + "square_corner_velocity: %.6f" % (mv, ma, scv, mcr)) self.printer.set_rollover_info("toolhead", "toolhead: %s" % (msg,)) if (max_velocity is None and max_accel is None and square_corner_velocity is None and min_cruise_ratio is None): @@ -703,12 +715,13 @@ class ToolHead: % (gcmd.get_commandline(),)) return accel = min(p, t) - self.max_accel = accel - self._calc_junction_deviation() + self.toolhead.set_max_velocities(None, accel, None, None) def add_printer_objects(config): printer = config.get_printer() printer.add_object('toolhead', ToolHead(config)) + ToolHeadCommandHelper(config) + # Load default extruder objects kinematics.extruder.add_printer_objects(config) # Load some default modules modules = ["gcode_move", "homing", "idle_timeout", "statistics", From 126275d1f4921b60d249c352169fe7ffd860b4ac Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 6 Aug 2025 21:20:55 -0400 Subject: [PATCH 05/23] toolhead: Default note_mcu_movequeue_activity() to set step_gen_time Change note_mcu_movequeue_activity() to default to setting the step_gen_time (instead of the previous default to not set it). Most users of the mcu "move queue" will be for stepper activity. There is also little harm in incrementing the tracking of the last possible step generation time, but accidentally generating a step without incrementing the tracking can lead to very hard to debug failures. The two cases (output_pin.py and pwm_tool.py) where note_mcu_movequeue_activity() is called and definitely not related to step generation can explicitly pass 'is_step_gen=False'. Signed-off-by: Kevin O'Connor --- klippy/extras/output_pin.py | 5 +++-- klippy/extras/pwm_tool.py | 3 ++- klippy/toolhead.py | 10 ++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/klippy/extras/output_pin.py b/klippy/extras/output_pin.py index bde7ea69..05266382 100644 --- a/klippy/extras/output_pin.py +++ b/klippy/extras/output_pin.py @@ -50,10 +50,11 @@ class GCodeRequestQueue: del rqueue[:pos+1] self.next_min_flush_time = next_time + max(min_wait, min_sched_time) # Ensure following queue items are flushed - self.toolhead.note_mcu_movequeue_activity(self.next_min_flush_time) + self.toolhead.note_mcu_movequeue_activity(self.next_min_flush_time, + is_step_gen=False) def _queue_request(self, print_time, value): self.rqueue.append((print_time, value)) - self.toolhead.note_mcu_movequeue_activity(print_time) + self.toolhead.note_mcu_movequeue_activity(print_time, is_step_gen=False) def queue_gcode_request(self, value): self.toolhead.register_lookahead_callback( (lambda pt: self._queue_request(pt, value))) diff --git a/klippy/extras/pwm_tool.py b/klippy/extras/pwm_tool.py index 46873203..53e101ae 100644 --- a/klippy/extras/pwm_tool.py +++ b/klippy/extras/pwm_tool.py @@ -115,7 +115,8 @@ class MCU_queued_pwm: # Continue flushing to resend time wakeclock += self._duration_ticks wake_print_time = self._mcu.clock_to_print_time(wakeclock) - self._toolhead.note_mcu_movequeue_activity(wake_print_time) + self._toolhead.note_mcu_movequeue_activity(wake_print_time, + is_step_gen=False) def set_pwm(self, print_time, value): clock = self._mcu.print_time_to_clock(print_time) if self._invert: diff --git a/klippy/toolhead.py b/klippy/toolhead.py index c78b215b..bddba4c7 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -342,8 +342,7 @@ class ToolHead: for cb in move.timing_callbacks: cb(next_move_time) # Generate steps for moves - self.note_mcu_movequeue_activity(next_move_time + self.kin_flush_delay, - set_step_gen_time=True) + self.note_mcu_movequeue_activity(next_move_time + self.kin_flush_delay) self._advance_move_time(next_move_time) def _flush_lookahead(self): # Transit from "NeedPrime"/"Priming"/"Drip"/main state to "NeedPrime" @@ -539,8 +538,7 @@ class ToolHead: drip_completion.wait(curtime + wait_time) continue npt = min(self.print_time + DRIP_SEGMENT_TIME, next_print_time) - self.note_mcu_movequeue_activity(npt + self.kin_flush_delay, - set_step_gen_time=True) + self.note_mcu_movequeue_activity(npt + self.kin_flush_delay) for stepper in addstepper: stepper.generate_steps(npt) self._advance_move_time(npt) @@ -638,9 +636,9 @@ class ToolHead: callback(self.get_last_move_time()) return last_move.timing_callbacks.append(callback) - def note_mcu_movequeue_activity(self, mq_time, set_step_gen_time=False): + def note_mcu_movequeue_activity(self, mq_time, is_step_gen=True): self.need_flush_time = max(self.need_flush_time, mq_time) - if set_step_gen_time: + if is_step_gen: self.step_gen_time = max(self.step_gen_time, mq_time) if self.do_kick_flush_timer: self.do_kick_flush_timer = False From 9399e738bc21e70cdd5a60bc9f8e1d51fb20cd75 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 4 Aug 2025 21:57:45 -0400 Subject: [PATCH 06/23] motion_queuing: Add new module to help with motion queues and flushing Create a new module to assist with host management of motion queues. Register all MCU_stepper objects with this module and use the module for step generation. All steppers will now automatically generate steps whenever toolhead._advance_flush_time() is invoked. It is no longer necessary for callers to individually call stepper.generate_steps(). Signed-off-by: Kevin O'Connor --- klippy/extras/force_move.py | 9 ++++----- klippy/extras/manual_stepper.py | 5 +---- klippy/extras/motion_queuing.py | 19 +++++++++++++++++++ klippy/kinematics/cartesian.py | 1 - klippy/kinematics/corexy.py | 1 - klippy/kinematics/corexz.py | 1 - klippy/kinematics/delta.py | 1 - klippy/kinematics/deltesian.py | 1 - klippy/kinematics/extruder.py | 2 -- klippy/kinematics/generic_cartesian.py | 1 - klippy/kinematics/hybrid_corexy.py | 1 - klippy/kinematics/hybrid_corexz.py | 1 - klippy/kinematics/polar.py | 1 - klippy/kinematics/rotary_delta.py | 1 - klippy/kinematics/winch.py | 1 - klippy/stepper.py | 6 ++---- klippy/toolhead.py | 14 +++----------- 17 files changed, 29 insertions(+), 37 deletions(-) create mode 100644 klippy/extras/motion_queuing.py diff --git a/klippy/extras/force_move.py b/klippy/extras/force_move.py index 0be0eb9b..54b07d59 100644 --- a/klippy/extras/force_move.py +++ b/klippy/extras/force_move.py @@ -85,14 +85,13 @@ class ForceMove: self.trapq_append(self.trapq, print_time, accel_t, cruise_t, accel_t, 0., 0., 0., axis_r, 0., 0., 0., cruise_v, accel) print_time = print_time + accel_t + cruise_t + accel_t - stepper.generate_steps(print_time) - self.trapq_finalize_moves(self.trapq, print_time + 99999.9, - print_time + 99999.9) - stepper.set_trapq(prev_trapq) - stepper.set_stepper_kinematics(prev_sk) toolhead.note_mcu_movequeue_activity(print_time) toolhead.dwell(accel_t + cruise_t + accel_t) toolhead.flush_step_generation() + stepper.set_trapq(prev_trapq) + stepper.set_stepper_kinematics(prev_sk) + self.trapq_finalize_moves(self.trapq, print_time + 99999.9, + print_time + 99999.9) def _lookup_stepper(self, gcmd): name = gcmd.get('STEPPER') if name not in self.steppers: diff --git a/klippy/extras/manual_stepper.py b/klippy/extras/manual_stepper.py index 5a8949b9..03816b5c 100644 --- a/klippy/extras/manual_stepper.py +++ b/klippy/extras/manual_stepper.py @@ -76,7 +76,6 @@ class ManualStepper: self.sync_print_time() self.next_cmd_time = self._submit_move(self.next_cmd_time, movepos, speed, accel) - self.rail.generate_steps(self.next_cmd_time) self.trapq_finalize_moves(self.trapq, self.next_cmd_time + 99999.9, self.next_cmd_time + 99999.9) toolhead = self.printer.lookup_object('toolhead') @@ -138,7 +137,6 @@ class ManualStepper: raise gcmd.error("Must unregister axis first") # Unregister toolhead.remove_extra_axis(self) - toolhead.unregister_step_generator(self.rail.generate_steps) self.axis_gcode_id = None return if (len(gcode_axis) != 1 or not gcode_axis.isupper() @@ -155,7 +153,6 @@ class ManualStepper: self.gaxis_limit_velocity = limit_velocity self.gaxis_limit_accel = limit_accel toolhead.add_extra_axis(self, self.get_position()[0]) - toolhead.register_step_generator(self.rail.generate_steps) def process_move(self, print_time, move, ea_index): axis_r = move.axes_r[ea_index] start_pos = move.start_pos[ea_index] @@ -208,7 +205,7 @@ class ManualStepper: speed, self.homing_accel) # Drip updates to motors toolhead = self.printer.lookup_object('toolhead') - toolhead.drip_update_time(maxtime, drip_completion, self.steppers) + toolhead.drip_update_time(maxtime, drip_completion) # Clear trapq of any remaining parts of movement reactor = self.printer.get_reactor() self.trapq_finalize_moves(self.trapq, reactor.NEVER, 0) diff --git a/klippy/extras/motion_queuing.py b/klippy/extras/motion_queuing.py new file mode 100644 index 00000000..4b5bd017 --- /dev/null +++ b/klippy/extras/motion_queuing.py @@ -0,0 +1,19 @@ +# Helper code for low-level motion queuing and flushing +# +# Copyright (C) 2025 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import logging + +class PrinterMotionQueuing: + def __init__(self, config): + self.printer = config.get_printer() + self.steppers = [] + def register_stepper(self, config, stepper): + self.steppers.append(stepper) + def flush_motion_queues(self, must_flush_time, max_step_gen_time): + for stepper in self.steppers: + stepper.generate_steps(max_step_gen_time) + +def load_config(config): + return PrinterMotionQueuing(config) diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index f9030275..edd9c1a0 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -36,7 +36,6 @@ class CartKinematics: 'safe_distance', None, minval=0.)) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity, diff --git a/klippy/kinematics/corexy.py b/klippy/kinematics/corexy.py index d68f8854..f940c57f 100644 --- a/klippy/kinematics/corexy.py +++ b/klippy/kinematics/corexy.py @@ -20,7 +20,6 @@ class CoreXYKinematics: self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'z') for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( diff --git a/klippy/kinematics/corexz.py b/klippy/kinematics/corexz.py index 8d3e027c..d4a0eb35 100644 --- a/klippy/kinematics/corexz.py +++ b/klippy/kinematics/corexz.py @@ -20,7 +20,6 @@ class CoreXZKinematics: self.rails[2].setup_itersolve('corexz_stepper_alloc', b'-') for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( diff --git a/klippy/kinematics/delta.py b/klippy/kinematics/delta.py index aa244a8f..5ec4d54c 100644 --- a/klippy/kinematics/delta.py +++ b/klippy/kinematics/delta.py @@ -52,7 +52,6 @@ class DeltaKinematics: r.setup_itersolve('delta_stepper_alloc', a, t[0], t[1]) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks self.need_home = True self.limit_xy2 = -1. diff --git a/klippy/kinematics/deltesian.py b/klippy/kinematics/deltesian.py index 54b013a5..a62a58bd 100644 --- a/klippy/kinematics/deltesian.py +++ b/klippy/kinematics/deltesian.py @@ -40,7 +40,6 @@ class DeltesianKinematics: self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'y') for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) self.limits = [(1.0, -1.0)] * 3 # X axis limits min_angle = config.getfloat('min_angle', MIN_ANGLE, diff --git a/klippy/kinematics/extruder.py b/klippy/kinematics/extruder.py index 58ccdbec..e3e375bc 100644 --- a/klippy/kinematics/extruder.py +++ b/klippy/kinematics/extruder.py @@ -39,8 +39,6 @@ class ExtruderStepper: self.name, self.cmd_SYNC_EXTRUDER_MOTION, desc=self.cmd_SYNC_EXTRUDER_MOTION_help) def _handle_connect(self): - toolhead = self.printer.lookup_object('toolhead') - toolhead.register_step_generator(self.stepper.generate_steps) self._set_pressure_advance(self.config_pa, self.config_smooth_time) def get_status(self, eventtime): return {'pressure_advance': self.pressure_advance, diff --git a/klippy/kinematics/generic_cartesian.py b/klippy/kinematics/generic_cartesian.py index b8cabb77..49e16eec 100644 --- a/klippy/kinematics/generic_cartesian.py +++ b/klippy/kinematics/generic_cartesian.py @@ -108,7 +108,6 @@ class GenericCartesianKinematics: self._load_kinematics(config) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) self.dc_module = None if self.dc_carriages: pcs = [dc.get_dual_carriage() for dc in self.dc_carriages] diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py index fd2621d5..22884b93 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -39,7 +39,6 @@ class HybridCoreXYKinematics: 'safe_distance', None, minval=0.)) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py index b9699982..1cd646e2 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -39,7 +39,6 @@ class HybridCoreXZKinematics: 'safe_distance', None, minval=0.)) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( diff --git a/klippy/kinematics/polar.py b/klippy/kinematics/polar.py index ffa15d83..2e467d50 100644 --- a/klippy/kinematics/polar.py +++ b/klippy/kinematics/polar.py @@ -21,7 +21,6 @@ class PolarKinematics: for s in r.get_steppers() ] for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( diff --git a/klippy/kinematics/rotary_delta.py b/klippy/kinematics/rotary_delta.py index 732a89a8..42b0a706 100644 --- a/klippy/kinematics/rotary_delta.py +++ b/klippy/kinematics/rotary_delta.py @@ -52,7 +52,6 @@ class RotaryDeltaKinematics: math.radians(a), ua, la) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks self.need_home = True self.limit_xy2 = -1. diff --git a/klippy/kinematics/winch.py b/klippy/kinematics/winch.py index 47bc6855..2fa06ef2 100644 --- a/klippy/kinematics/winch.py +++ b/klippy/kinematics/winch.py @@ -21,7 +21,6 @@ class WinchKinematics: self.anchors.append(a) s.setup_itersolve('winch_stepper_alloc', *a) s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks acoords = list(zip(*self.anchors)) self.axes_min = toolhead.Coord(*[min(a) for a in acoords], e=0.) diff --git a/klippy/stepper.py b/klippy/stepper.py index 86a92358..5ba76a5b 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -273,7 +273,8 @@ def PrinterStepper(config, units_in_radians=False): rotation_dist, steps_per_rotation, step_pulse_duration, units_in_radians) # Register with helper modules - for mname in ['stepper_enable', 'force_move', 'motion_report']: + mods = ['stepper_enable', 'force_move', 'motion_report', 'motion_queuing'] + for mname in mods: m = printer.load_object(config, mname) m.register_stepper(config, mcu_stepper) return mcu_stepper @@ -442,9 +443,6 @@ class GenericPrinterRail: def setup_itersolve(self, alloc_func, *params): for stepper in self.steppers: stepper.setup_itersolve(alloc_func, *params) - def generate_steps(self, flush_time): - for stepper in self.steppers: - stepper.generate_steps(flush_time) def set_trapq(self, trapq): for stepper in self.steppers: stepper.set_trapq(trapq) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index bddba4c7..3b9c0e8e 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -251,7 +251,7 @@ class ToolHead: self.trapq_append = ffi_lib.trapq_append self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves # Motion flushing - self.step_generators = [] + self.motion_queuing = self.printer.load_object(config, 'motion_queuing') self.flush_trapqs = [self.trapq] # Create kinematics class gcode = self.printer.lookup_object('gcode') @@ -280,8 +280,7 @@ class ToolHead: sg_flush_want = min(flush_time + STEPCOMPRESS_FLUSH_TIME, self.print_time - self.kin_flush_delay) sg_flush_time = max(sg_flush_want, flush_time) - for sg in self.step_generators: - sg(sg_flush_time) + self.motion_queuing.flush_motion_queues(flush_time, sg_flush_time) self.min_restart_time = max(self.min_restart_time, sg_flush_time) # Free trapq entries that are no longer needed clear_history_time = self.clear_history_time @@ -517,7 +516,7 @@ class ToolHead: def get_extra_axes(self): return [None, None, None] + self.extra_axes # Homing "drip move" handling - def drip_update_time(self, next_print_time, drip_completion, addstepper=()): + def drip_update_time(self, next_print_time, drip_completion): # Transition from "NeedPrime"/"Priming"/main state to "Drip" state self.special_queuing_state = "Drip" self.need_check_pause = self.reactor.NEVER @@ -539,8 +538,6 @@ class ToolHead: continue npt = min(self.print_time + DRIP_SEGMENT_TIME, next_print_time) self.note_mcu_movequeue_activity(npt + self.kin_flush_delay) - for stepper in addstepper: - stepper.generate_steps(npt) self._advance_move_time(npt) # Exit "Drip" state self.reactor.update_timer(self.flush_timer, self.reactor.NOW) @@ -617,11 +614,6 @@ class ToolHead: return self.kin def get_trapq(self): return self.trapq - def register_step_generator(self, handler): - self.step_generators.append(handler) - def unregister_step_generator(self, handler): - if handler in self.step_generators: - self.step_generators.remove(handler) def note_step_generation_scan_time(self, delay, old_delay=0.): self.flush_step_generation() if old_delay: From 5cbe7d83e8b10cc0986b12ddf49b2838655843d9 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 4 Aug 2025 22:41:15 -0400 Subject: [PATCH 07/23] motion_queuing: Track all trapqs and globally flush all trapqs Add an allocate_trapq() helper function to facilitate the creation of a low-level C trapq object. Track all trapq objects and clear history on them globally when the main motion queues are flushed. Signed-off-by: Kevin O'Connor --- klippy/extras/force_move.py | 9 ++++----- klippy/extras/manual_stepper.py | 11 ++++------- klippy/extras/motion_queuing.py | 20 ++++++++++++++++++++ klippy/kinematics/extruder.py | 7 +++---- klippy/toolhead.py | 27 +++++---------------------- 5 files changed, 36 insertions(+), 38 deletions(-) diff --git a/klippy/extras/force_move.py b/klippy/extras/force_move.py index 54b07d59..00f835f5 100644 --- a/klippy/extras/force_move.py +++ b/klippy/extras/force_move.py @@ -33,10 +33,10 @@ class ForceMove: self.printer = config.get_printer() self.steppers = {} # Setup iterative solver + self.motion_queuing = self.printer.load_object(config, 'motion_queuing') + self.trapq = self.motion_queuing.allocate_trapq() + self.trapq_append = self.motion_queuing.lookup_trapq_append() ffi_main, ffi_lib = chelper.get_ffi() - self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) - self.trapq_append = ffi_lib.trapq_append - self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves self.stepper_kinematics = ffi_main.gc( ffi_lib.cartesian_stepper_alloc(b'x'), ffi_lib.free) # Register commands @@ -90,8 +90,7 @@ class ForceMove: toolhead.flush_step_generation() stepper.set_trapq(prev_trapq) stepper.set_stepper_kinematics(prev_sk) - self.trapq_finalize_moves(self.trapq, print_time + 99999.9, - print_time + 99999.9) + self.motion_queuing.wipe_trapq(self.trapq) def _lookup_stepper(self, gcmd): name = gcmd.get('STEPPER') if name not in self.steppers: diff --git a/klippy/extras/manual_stepper.py b/klippy/extras/manual_stepper.py index 03816b5c..05899f58 100644 --- a/klippy/extras/manual_stepper.py +++ b/klippy/extras/manual_stepper.py @@ -25,10 +25,9 @@ class ManualStepper: self.pos_min = config.getfloat('position_min', None) self.pos_max = config.getfloat('position_max', None) # Setup iterative solver - ffi_main, ffi_lib = chelper.get_ffi() - self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) - self.trapq_append = ffi_lib.trapq_append - self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves + self.motion_queuing = self.printer.load_object(config, 'motion_queuing') + self.trapq = self.motion_queuing.allocate_trapq() + self.trapq_append = self.motion_queuing.lookup_trapq_append() self.rail.setup_itersolve('cartesian_stepper_alloc', b'x') self.rail.set_trapq(self.trapq) # Registered with toolhead as an axtra axis @@ -76,8 +75,6 @@ class ManualStepper: self.sync_print_time() self.next_cmd_time = self._submit_move(self.next_cmd_time, movepos, speed, accel) - self.trapq_finalize_moves(self.trapq, self.next_cmd_time + 99999.9, - self.next_cmd_time + 99999.9) toolhead = self.printer.lookup_object('toolhead') toolhead.note_mcu_movequeue_activity(self.next_cmd_time) if sync: @@ -208,7 +205,7 @@ class ManualStepper: toolhead.drip_update_time(maxtime, drip_completion) # Clear trapq of any remaining parts of movement reactor = self.printer.get_reactor() - self.trapq_finalize_moves(self.trapq, reactor.NEVER, 0) + self.motion_queuing.wipe_trapq(self.trapq) self.rail.set_position([newpos[0], 0., 0.]) self.sync_print_time() def get_kinematics(self): diff --git a/klippy/extras/motion_queuing.py b/klippy/extras/motion_queuing.py index 4b5bd017..d8db880a 100644 --- a/klippy/extras/motion_queuing.py +++ b/klippy/extras/motion_queuing.py @@ -4,16 +4,36 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging +import chelper class PrinterMotionQueuing: def __init__(self, config): self.printer = config.get_printer() self.steppers = [] + self.trapqs = [] + ffi_main, ffi_lib = chelper.get_ffi() + self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves + def allocate_trapq(self): + ffi_main, ffi_lib = chelper.get_ffi() + trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) + self.trapqs.append(trapq) + return trapq def register_stepper(self, config, stepper): self.steppers.append(stepper) def flush_motion_queues(self, must_flush_time, max_step_gen_time): for stepper in self.steppers: stepper.generate_steps(max_step_gen_time) + def clean_motion_queues(self, trapq_free_time, clear_history_time): + for trapq in self.trapqs: + self.trapq_finalize_moves(trapq, trapq_free_time, + clear_history_time) + def wipe_trapq(self, trapq): + # Expire any remaining movement in the trapq (force to history list) + NEVER = 9999999999999999. + self.trapq_finalize_moves(trapq, NEVER, 0.) + def lookup_trapq_append(self): + ffi_main, ffi_lib = chelper.get_ffi() + return ffi_lib.trapq_append def load_config(config): return PrinterMotionQueuing(config) diff --git a/klippy/kinematics/extruder.py b/klippy/kinematics/extruder.py index e3e375bc..a89e3bdf 100644 --- a/klippy/kinematics/extruder.py +++ b/klippy/kinematics/extruder.py @@ -163,10 +163,9 @@ class PrinterExtruder: self.instant_corner_v = config.getfloat( 'instantaneous_corner_velocity', 1., minval=0.) # Setup extruder trapq (trapezoidal motion queue) - ffi_main, ffi_lib = chelper.get_ffi() - self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) - self.trapq_append = ffi_lib.trapq_append - self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves + self.motion_queuing = self.printer.load_object(config, 'motion_queuing') + self.trapq = self.motion_queuing.allocate_trapq() + self.trapq_append = self.motion_queuing.lookup_trapq_append() # Setup extruder stepper self.extruder_stepper = None if (config.get('step_pin', None) is not None diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 3b9c0e8e..bc8c254d 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -245,14 +245,10 @@ class ToolHead: # Kinematic step generation scan window time tracking self.kin_flush_delay = SDS_CHECK_TIME self.kin_flush_times = [] - # Setup iterative solver - ffi_main, ffi_lib = chelper.get_ffi() - self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) - self.trapq_append = ffi_lib.trapq_append - self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves - # Motion flushing + # Setup for generating moves self.motion_queuing = self.printer.load_object(config, 'motion_queuing') - self.flush_trapqs = [self.trapq] + self.trapq = self.motion_queuing.allocate_trapq() + self.trapq_append = self.motion_queuing.lookup_trapq_append() # Create kinematics class gcode = self.printer.lookup_object('gcode') self.Coord = gcode.Coord @@ -287,8 +283,7 @@ class ToolHead: if not self.can_pause: clear_history_time = flush_time - MOVE_HISTORY_EXPIRE free_time = sg_flush_time - self.kin_flush_delay - for trapq in self.flush_trapqs: - self.trapq_finalize_moves(trapq, free_time, clear_history_time) + self.motion_queuing.clean_motion_queues(free_time, clear_history_time) # Flush stepcompress and mcu steppersync for m in self.all_mcus: m.flush_moves(flush_time, clear_history_time) @@ -484,32 +479,20 @@ class ToolHead: eventtime = self.reactor.pause(eventtime + 0.100) def set_extruder(self, extruder, extrude_pos): # XXX - should use add_extra_axis - prev_ea_trapq = self.extra_axes[0].get_trapq() - if prev_ea_trapq in self.flush_trapqs: - self.flush_trapqs.remove(prev_ea_trapq) self.extra_axes[0] = extruder self.commanded_pos[3] = extrude_pos - ea_trapq = extruder.get_trapq() - if ea_trapq is not None: - self.flush_trapqs.append(ea_trapq) def get_extruder(self): return self.extra_axes[0] def add_extra_axis(self, ea, axis_pos): self._flush_lookahead() self.extra_axes.append(ea) self.commanded_pos.append(axis_pos) - ea_trapq = ea.get_trapq() - if ea_trapq is not None: - self.flush_trapqs.append(ea_trapq) self.printer.send_event("toolhead:update_extra_axes") def remove_extra_axis(self, ea): self._flush_lookahead() if ea not in self.extra_axes: return ea_index = self.extra_axes.index(ea) + 3 - ea_trapq = ea.get_trapq() - if ea_trapq in self.flush_trapqs: - self.flush_trapqs.remove(ea_trapq) self.commanded_pos.pop(ea_index) self.extra_axes.pop(ea_index - 3) self.printer.send_event("toolhead:update_extra_axes") @@ -574,7 +557,7 @@ class ToolHead: next_move_time = self._drip_load_trapq(move) self.drip_update_time(next_move_time, drip_completion) # Move finished; cleanup any remnants on trapq - self.trapq_finalize_moves(self.trapq, self.reactor.NEVER, 0) + self.motion_queuing.wipe_trapq(self.trapq) # Misc commands def stats(self, eventtime): max_queue_time = max(self.print_time, self.last_flush_time) From 128226fe8af1834ed424e509bde185202a61c4dc Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 4 Aug 2025 23:26:30 -0400 Subject: [PATCH 08/23] motion_queuing: Add allocate_steppersync() call Allocate the low-level C steppersync object from the motion_queuing module. Signed-off-by: Kevin O'Connor --- klippy/extras/motion_queuing.py | 9 +++++++++ klippy/mcu.py | 14 +++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/klippy/extras/motion_queuing.py b/klippy/extras/motion_queuing.py index d8db880a..21d5465e 100644 --- a/klippy/extras/motion_queuing.py +++ b/klippy/extras/motion_queuing.py @@ -11,6 +11,7 @@ class PrinterMotionQueuing: self.printer = config.get_printer() self.steppers = [] self.trapqs = [] + self.steppersyncs = [] ffi_main, ffi_lib = chelper.get_ffi() self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves def allocate_trapq(self): @@ -18,6 +19,14 @@ class PrinterMotionQueuing: trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) self.trapqs.append(trapq) return trapq + def allocate_steppersync(self, mcu, serialqueue, stepqueues, move_count): + ffi_main, ffi_lib = chelper.get_ffi() + ss = ffi_main.gc( + ffi_lib.steppersync_alloc(serialqueue, stepqueues, len(stepqueues), + move_count), + ffi_lib.steppersync_free) + self.steppersyncs.append((mcu, ss)) + return ss def register_stepper(self, config, stepper): self.steppers.append(stepper) def flush_motion_queues(self, must_flush_time, max_step_gen_time): diff --git a/klippy/mcu.py b/klippy/mcu.py index d3b8ffd7..48eee4e5 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -770,13 +770,13 @@ class MCU: move_count = config_params['move_count'] if move_count < self._reserved_move_slots: raise error("Too few moves available on MCU '%s'" % (self._name,)) - ffi_main, ffi_lib = chelper.get_ffi() - self._steppersync = ffi_main.gc( - ffi_lib.steppersync_alloc(self._serial.get_serialqueue(), - self._stepqueues, len(self._stepqueues), - move_count-self._reserved_move_slots), - ffi_lib.steppersync_free) - ffi_lib.steppersync_set_time(self._steppersync, 0., self._mcu_freq) + ss_move_count = move_count - self._reserved_move_slots + motion_queuing = self._printer.lookup_object('motion_queuing') + self._steppersync = motion_queuing.allocate_steppersync( + self, self._serial.get_serialqueue(), + self._stepqueues, ss_move_count) + self._ffi_lib.steppersync_set_time(self._steppersync, + 0., self._mcu_freq) # Log config information move_msg = "Configured MCU '%s' (%d moves)" % (self._name, move_count) logging.info(move_msg) From 6f685e9e01ba1581473fd340a3c3d91765626870 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 4 Aug 2025 23:52:59 -0400 Subject: [PATCH 09/23] motion_queuing: Add allocate_stepcompress() call Allocate the low-level C stepcompress object in the motion_queuing module. This simplifies the mcu.py code as it no longer needs to track the stepqueues for the steppersync object. Signed-off-by: Kevin O'Connor --- klippy/extras/motion_queuing.py | 13 ++++++++++++- klippy/extras/pwm_tool.py | 17 ++++++++--------- klippy/mcu.py | 6 +----- klippy/stepper.py | 27 +++++++++++++-------------- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/klippy/extras/motion_queuing.py b/klippy/extras/motion_queuing.py index 21d5465e..91060019 100644 --- a/klippy/extras/motion_queuing.py +++ b/klippy/extras/motion_queuing.py @@ -11,6 +11,7 @@ class PrinterMotionQueuing: self.printer = config.get_printer() self.steppers = [] self.trapqs = [] + self.stepcompress = [] self.steppersyncs = [] ffi_main, ffi_lib = chelper.get_ffi() self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves @@ -19,7 +20,17 @@ class PrinterMotionQueuing: trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) self.trapqs.append(trapq) return trapq - def allocate_steppersync(self, mcu, serialqueue, stepqueues, move_count): + def allocate_stepcompress(self, mcu, oid): + ffi_main, ffi_lib = chelper.get_ffi() + sc = ffi_main.gc(ffi_lib.stepcompress_alloc(oid), + ffi_lib.stepcompress_free) + self.stepcompress.append((mcu, sc)) + return sc + def allocate_steppersync(self, mcu, serialqueue, move_count): + stepqueues = [] + for sc_mcu, sc in self.stepcompress: + if sc_mcu is mcu: + stepqueues.append(sc) ffi_main, ffi_lib = chelper.get_ffi() ss = ffi_main.gc( ffi_lib.steppersync_alloc(serialqueue, stepqueues, len(stepqueues), diff --git a/klippy/extras/pwm_tool.py b/klippy/extras/pwm_tool.py index 53e101ae..a0739b5b 100644 --- a/klippy/extras/pwm_tool.py +++ b/klippy/extras/pwm_tool.py @@ -9,18 +9,18 @@ class error(Exception): pass class MCU_queued_pwm: - def __init__(self, pin_params): - self._mcu = pin_params['chip'] + def __init__(self, config, pin_params): + self._mcu = mcu = pin_params['chip'] self._hardware_pwm = False self._cycle_time = 0.100 self._max_duration = 2. - self._oid = self._mcu.create_oid() + self._oid = oid = mcu.create_oid() + printer = mcu.get_printer() + motion_queuing = printer.load_object(config, 'motion_queuing') + self._stepqueue = motion_queuing.allocate_stepcompress(mcu, oid) ffi_main, ffi_lib = chelper.get_ffi() - self._stepqueue = ffi_main.gc(ffi_lib.stepcompress_alloc(self._oid), - ffi_lib.stepcompress_free) - self._mcu.register_stepqueue(self._stepqueue) self._stepcompress_queue_mq_msg = ffi_lib.stepcompress_queue_mq_msg - self._mcu.register_config_callback(self._build_config) + mcu.register_config_callback(self._build_config) self._pin = pin_params['pin'] self._invert = pin_params['invert'] self._start_value = self._shutdown_value = float(self._invert) @@ -29,7 +29,6 @@ class MCU_queued_pwm: self._pwm_max = 0. self._set_cmd_tag = None self._toolhead = None - printer = self._mcu.get_printer() printer.register_event_handler("klippy:connect", self._handle_connect) def _handle_connect(self): self._toolhead = self._mcu.get_printer().lookup_object("toolhead") @@ -135,7 +134,7 @@ class PrinterOutputPin: ppins = self.printer.lookup_object('pins') # Determine pin type pin_params = ppins.lookup_pin(config.get('pin'), can_invert=True) - self.mcu_pin = MCU_queued_pwm(pin_params) + self.mcu_pin = MCU_queued_pwm(config, pin_params) max_duration = self.mcu_pin.get_mcu().max_nominal_duration() cycle_time = config.getfloat('cycle_time', 0.100, above=0., maxval=max_duration) diff --git a/klippy/mcu.py b/klippy/mcu.py index 48eee4e5..d7665036 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -605,7 +605,6 @@ class MCU: self._max_stepper_error = config.getfloat('max_stepper_error', 0.000025, minval=0.) self._reserved_move_slots = 0 - self._stepqueues = [] self._steppersync = None self._flush_callbacks = [] # Stats @@ -773,8 +772,7 @@ class MCU: ss_move_count = move_count - self._reserved_move_slots motion_queuing = self._printer.lookup_object('motion_queuing') self._steppersync = motion_queuing.allocate_steppersync( - self, self._serial.get_serialqueue(), - self._stepqueues, ss_move_count) + self, self._serial.get_serialqueue(), ss_move_count) self._ffi_lib.steppersync_set_time(self._steppersync, 0., self._mcu_freq) # Log config information @@ -971,8 +969,6 @@ class MCU: def _firmware_restart_bridge(self): self._firmware_restart(True) # Move queue tracking - def register_stepqueue(self, stepqueue): - self._stepqueues.append(stepqueue) def request_move_queue_slot(self): self._reserved_move_slots += 1 def register_flush_callback(self, callback): diff --git a/klippy/stepper.py b/klippy/stepper.py index 5ba76a5b..6ce9130f 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -19,22 +19,23 @@ MIN_OPTIMIZED_BOTH_EDGE_DURATION = 0.000000150 # Interface to low-level mcu and chelper code class MCU_stepper: - def __init__(self, name, step_pin_params, dir_pin_params, + def __init__(self, config, step_pin_params, dir_pin_params, rotation_dist, steps_per_rotation, step_pulse_duration=None, units_in_radians=False): - self._name = name + self._name = config.get_name() self._rotation_dist = rotation_dist self._steps_per_rotation = steps_per_rotation self._step_pulse_duration = step_pulse_duration self._units_in_radians = units_in_radians self._step_dist = rotation_dist / steps_per_rotation - self._mcu = step_pin_params['chip'] - self._oid = oid = self._mcu.create_oid() - self._mcu.register_config_callback(self._build_config) + self._mcu = mcu = step_pin_params['chip'] + self._oid = oid = mcu.create_oid() + mcu.register_config_callback(self._build_config) self._step_pin = step_pin_params['pin'] self._invert_step = step_pin_params['invert'] - if dir_pin_params['chip'] is not self._mcu: - raise self._mcu.get_printer().config_error( + printer = mcu.get_printer() + if dir_pin_params['chip'] is not mcu: + raise printer.config_error( "Stepper dir pin must be on same mcu as step pin") self._dir_pin = dir_pin_params['pin'] self._invert_dir = self._orig_invert_dir = dir_pin_params['invert'] @@ -42,17 +43,16 @@ class MCU_stepper: self._mcu_position_offset = 0. self._reset_cmd_tag = self._get_position_cmd = None self._active_callbacks = [] + motion_queuing = printer.load_object(config, 'motion_queuing') + self._stepqueue = motion_queuing.allocate_stepcompress(mcu, oid) ffi_main, ffi_lib = chelper.get_ffi() - self._stepqueue = ffi_main.gc(ffi_lib.stepcompress_alloc(oid), - ffi_lib.stepcompress_free) ffi_lib.stepcompress_set_invert_sdir(self._stepqueue, self._invert_dir) - self._mcu.register_stepqueue(self._stepqueue) self._stepper_kinematics = None self._itersolve_generate_steps = ffi_lib.itersolve_generate_steps self._itersolve_check_active = ffi_lib.itersolve_check_active self._trapq = ffi_main.NULL - self._mcu.get_printer().register_event_handler('klippy:connect', - self._query_mcu_position) + printer.register_event_handler('klippy:connect', + self._query_mcu_position) def get_mcu(self): return self._mcu def get_name(self, short=False): @@ -258,7 +258,6 @@ class MCU_stepper: # Helper code to build a stepper object from a config section def PrinterStepper(config, units_in_radians=False): printer = config.get_printer() - name = config.get_name() # Stepper definition ppins = printer.lookup_object('pins') step_pin = config.get('step_pin') @@ -269,7 +268,7 @@ def PrinterStepper(config, units_in_radians=False): config, units_in_radians, True) step_pulse_duration = config.getfloat('step_pulse_duration', None, minval=0., maxval=.001) - mcu_stepper = MCU_stepper(name, step_pin_params, dir_pin_params, + mcu_stepper = MCU_stepper(config, step_pin_params, dir_pin_params, rotation_dist, steps_per_rotation, step_pulse_duration, units_in_radians) # Register with helper modules From c09ca4cf5a547f82c00e974e35a577ebcc5b2a4f Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 5 Aug 2025 00:06:32 -0400 Subject: [PATCH 10/23] motion_queuing: Add register_flush_callback() Move register_flush_callback() from mcu.py code to motion_queuing module. Signed-off-by: Kevin O'Connor --- klippy/extras/motion_queuing.py | 5 +++++ klippy/extras/output_pin.py | 5 +++-- klippy/extras/pwm_tool.py | 11 +++++++---- klippy/mcu.py | 5 ----- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/klippy/extras/motion_queuing.py b/klippy/extras/motion_queuing.py index 91060019..55a7b929 100644 --- a/klippy/extras/motion_queuing.py +++ b/klippy/extras/motion_queuing.py @@ -13,6 +13,7 @@ class PrinterMotionQueuing: self.trapqs = [] self.stepcompress = [] self.steppersyncs = [] + self.flush_callbacks = [] ffi_main, ffi_lib = chelper.get_ffi() self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves def allocate_trapq(self): @@ -40,7 +41,11 @@ class PrinterMotionQueuing: return ss def register_stepper(self, config, stepper): self.steppers.append(stepper) + def register_flush_callback(self, callback): + self.flush_callbacks.append(callback) def flush_motion_queues(self, must_flush_time, max_step_gen_time): + for cb in self.flush_callbacks: + cb(must_flush_time) for stepper in self.steppers: stepper.generate_steps(max_step_gen_time) def clean_motion_queues(self, trapq_free_time, clear_history_time): diff --git a/klippy/extras/output_pin.py b/klippy/extras/output_pin.py index 05266382..9eb8ea8b 100644 --- a/klippy/extras/output_pin.py +++ b/klippy/extras/output_pin.py @@ -20,11 +20,12 @@ class GCodeRequestQueue: self.rqueue = [] self.next_min_flush_time = 0. self.toolhead = None - mcu.register_flush_callback(self._flush_notification) + motion_queuing = printer.load_object(config, 'motion_queuing') + motion_queuing.register_flush_callback(self._flush_notification) printer.register_event_handler("klippy:connect", self._handle_connect) def _handle_connect(self): self.toolhead = self.printer.lookup_object('toolhead') - def _flush_notification(self, print_time, clock): + def _flush_notification(self, print_time): min_sched_time = self.mcu.min_schedule_time() rqueue = self.rqueue while rqueue: diff --git a/klippy/extras/pwm_tool.py b/klippy/extras/pwm_tool.py index a0739b5b..69fc1a46 100644 --- a/klippy/extras/pwm_tool.py +++ b/klippy/extras/pwm_tool.py @@ -46,12 +46,13 @@ class MCU_queued_pwm: self._start_value = max(0., min(1., start_value)) self._shutdown_value = max(0., min(1., shutdown_value)) def _build_config(self): - config_error = self._mcu.get_printer().config_error + printer = self._mcu.get_printer() + config_error = printer.config_error if self._max_duration and self._start_value != self._shutdown_value: raise config_error("Pin with max duration must have start" " value equal to shutdown value") cmd_queue = self._mcu.alloc_command_queue() - curtime = self._mcu.get_printer().get_reactor().monotonic() + curtime = printer.get_reactor().monotonic() printtime = self._mcu.estimated_print_time(curtime) self._last_clock = self._mcu.print_time_to_clock(printtime + 0.200) cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time) @@ -61,7 +62,8 @@ class MCU_queued_pwm: if self._duration_ticks >= 1<<31: raise config_error("PWM pin max duration too large") if self._duration_ticks: - self._mcu.register_flush_callback(self._flush_notification) + motion_queuing = printer.lookup_object('motion_queuing') + motion_queuing.register_flush_callback(self._flush_notification) if self._hardware_pwm: self._pwm_max = self._mcu.get_constant_float("PWM_MAX") self._default_value = self._shutdown_value * self._pwm_max @@ -122,7 +124,8 @@ class MCU_queued_pwm: value = 1. - value v = int(max(0., min(1., value)) * self._pwm_max + 0.5) self._send_update(clock, v) - def _flush_notification(self, print_time, clock): + def _flush_notification(self, print_time): + clock = self._mcu.print_time_to_clock(print_time) if self._last_value != self._default_value: while clock >= self._last_clock + self._duration_ticks: self._send_update(self._last_clock + self._duration_ticks, diff --git a/klippy/mcu.py b/klippy/mcu.py index d7665036..c8bc9c9b 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -606,7 +606,6 @@ class MCU: minval=0.) self._reserved_move_slots = 0 self._steppersync = None - self._flush_callbacks = [] # Stats self._get_status_info = {} self._stats_sumsq_base = 0. @@ -971,16 +970,12 @@ class MCU: # Move queue tracking def request_move_queue_slot(self): self._reserved_move_slots += 1 - def register_flush_callback(self, callback): - self._flush_callbacks.append(callback) def flush_moves(self, print_time, clear_history_time): if self._steppersync is None: return clock = self.print_time_to_clock(print_time) if clock < 0: return - for cb in self._flush_callbacks: - cb(print_time, clock) clear_history_clock = \ max(0, self.print_time_to_clock(clear_history_time)) ret = self._ffi_lib.steppersync_flush(self._steppersync, clock, From 864c78f24a0dbfe120313ab996c6e4257f644344 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 5 Aug 2025 00:16:13 -0400 Subject: [PATCH 11/23] motion_queueing: Add flush_steppersync() Move the mcu.flush_moves() code to motion_queuing.flush_steppersync(). Signed-off-by: Kevin O'Connor --- klippy/extras/motion_queuing.py | 12 ++++++++++++ klippy/mcu.py | 13 ------------- klippy/toolhead.py | 3 +-- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/klippy/extras/motion_queuing.py b/klippy/extras/motion_queuing.py index 55a7b929..fdbb5df2 100644 --- a/klippy/extras/motion_queuing.py +++ b/klippy/extras/motion_queuing.py @@ -16,6 +16,7 @@ class PrinterMotionQueuing: self.flush_callbacks = [] ffi_main, ffi_lib = chelper.get_ffi() self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves + self.steppersync_flush = ffi_lib.steppersync_flush def allocate_trapq(self): ffi_main, ffi_lib = chelper.get_ffi() trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) @@ -52,6 +53,17 @@ class PrinterMotionQueuing: for trapq in self.trapqs: self.trapq_finalize_moves(trapq, trapq_free_time, clear_history_time) + def flush_steppersync(self, print_time, clear_history_time): + for mcu, ss in self.steppersyncs: + clock = mcu.print_time_to_clock(print_time) + if clock < 0: + continue + clear_history_clock = \ + max(0, mcu.print_time_to_clock(clear_history_time)) + ret = self.steppersync_flush(ss, clock, clear_history_clock) + if ret: + raise mcu.error("Internal error in MCU '%s' stepcompress" + % (mcu.get_name(),)) def wipe_trapq(self, trapq): # Expire any remaining movement in the trapq (force to history list) NEVER = 9999999999999999. diff --git a/klippy/mcu.py b/klippy/mcu.py index c8bc9c9b..db02e2a4 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -970,19 +970,6 @@ class MCU: # Move queue tracking def request_move_queue_slot(self): self._reserved_move_slots += 1 - def flush_moves(self, print_time, clear_history_time): - if self._steppersync is None: - return - clock = self.print_time_to_clock(print_time) - if clock < 0: - return - clear_history_clock = \ - max(0, self.print_time_to_clock(clear_history_time)) - ret = self._ffi_lib.steppersync_flush(self._steppersync, clock, - clear_history_clock) - if ret: - raise error("Internal error in MCU '%s' stepcompress" - % (self._name,)) def check_active(self, print_time, eventtime): if self._steppersync is None: return diff --git a/klippy/toolhead.py b/klippy/toolhead.py index bc8c254d..6fd5f2de 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -285,8 +285,7 @@ class ToolHead: free_time = sg_flush_time - self.kin_flush_delay self.motion_queuing.clean_motion_queues(free_time, clear_history_time) # Flush stepcompress and mcu steppersync - for m in self.all_mcus: - m.flush_moves(flush_time, clear_history_time) + self.motion_queuing.flush_steppersync(flush_time, clear_history_time) self.last_flush_time = flush_time def _advance_move_time(self, next_print_time): pt_delay = self.kin_flush_delay + STEPCOMPRESS_FLUSH_TIME From 7b25d1c06f1b31fd78972383d4c1ee402cb4c737 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 5 Aug 2025 14:07:59 -0400 Subject: [PATCH 12/23] stepcompress: Export steppersync_history_expire() Don't implement history expiration from the main steppersync_flush() code. Instead, have callers directly invoke steppersync_history_expire(). Signed-off-by: Kevin O'Connor --- klippy/chelper/__init__.py | 4 ++-- klippy/chelper/stepcompress.c | 22 ++++++---------------- klippy/chelper/stepcompress.h | 4 ++-- klippy/extras/motion_queuing.py | 8 +++++--- 4 files changed, 15 insertions(+), 23 deletions(-) diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py index 622cc9a7..d56b720c 100644 --- a/klippy/chelper/__init__.py +++ b/klippy/chelper/__init__.py @@ -60,8 +60,8 @@ defs_stepcompress = """ void steppersync_free(struct steppersync *ss); void steppersync_set_time(struct steppersync *ss , double time_offset, double mcu_freq); - int steppersync_flush(struct steppersync *ss, uint64_t move_clock - , uint64_t clear_history_clock); + void steppersync_history_expire(struct steppersync *ss, uint64_t end_clock); + int steppersync_flush(struct steppersync *ss, uint64_t move_clock); """ defs_itersolve = """ diff --git a/klippy/chelper/stepcompress.c b/klippy/chelper/stepcompress.c index 310f2bf3..939ac2c1 100644 --- a/klippy/chelper/stepcompress.c +++ b/klippy/chelper/stepcompress.c @@ -276,9 +276,9 @@ stepcompress_set_invert_sdir(struct stepcompress *sc, uint32_t invert_sdir) } } -// Helper to free items from the history_list +// Expire the stepcompress history older than the given clock static void -free_history(struct stepcompress *sc, uint64_t end_clock) +stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock) { while (!list_empty(&sc->history_list)) { struct history_steps *hs = list_last_entry( @@ -290,13 +290,6 @@ free_history(struct stepcompress *sc, uint64_t end_clock) } } -// Expire the stepcompress history older than the given clock -static void -stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock) -{ - free_history(sc, end_clock); -} - // Free memory associated with a 'stepcompress' object void __visible stepcompress_free(struct stepcompress *sc) @@ -305,7 +298,7 @@ stepcompress_free(struct stepcompress *sc) return; free(sc->queue); message_queue_free(&sc->msg_queue); - free_history(sc, UINT64_MAX); + stepcompress_history_expire(sc, UINT64_MAX); free(sc); } @@ -734,12 +727,11 @@ steppersync_set_time(struct steppersync *ss, double time_offset } // Expire the stepcompress history before the given clock time -static void +void __visible steppersync_history_expire(struct steppersync *ss, uint64_t end_clock) { int i; - for (i = 0; i < ss->sc_num; i++) - { + for (i = 0; i < ss->sc_num; i++) { struct stepcompress *sc = ss->sc_list[i]; stepcompress_history_expire(sc, end_clock); } @@ -772,8 +764,7 @@ heap_replace(struct steppersync *ss, uint64_t req_clock) // Find and transmit any scheduled steps prior to the given 'move_clock' int __visible -steppersync_flush(struct steppersync *ss, uint64_t move_clock - , uint64_t clear_history_clock) +steppersync_flush(struct steppersync *ss, uint64_t move_clock) { // Flush each stepcompress to the specified move_clock int i; @@ -822,6 +813,5 @@ steppersync_flush(struct steppersync *ss, uint64_t move_clock if (!list_empty(&msgs)) serialqueue_send_batch(ss->sq, ss->cq, &msgs); - steppersync_history_expire(ss, clear_history_clock); return 0; } diff --git a/klippy/chelper/stepcompress.h b/klippy/chelper/stepcompress.h index c5b40383..06bc7a49 100644 --- a/klippy/chelper/stepcompress.h +++ b/klippy/chelper/stepcompress.h @@ -42,7 +42,7 @@ struct steppersync *steppersync_alloc( void steppersync_free(struct steppersync *ss); void steppersync_set_time(struct steppersync *ss, double time_offset , double mcu_freq); -int steppersync_flush(struct steppersync *ss, uint64_t move_clock - , uint64_t clear_history_clock); +void steppersync_history_expire(struct steppersync *ss, uint64_t end_clock); +int steppersync_flush(struct steppersync *ss, uint64_t move_clock); #endif // stepcompress.h diff --git a/klippy/extras/motion_queuing.py b/klippy/extras/motion_queuing.py index fdbb5df2..3a7bba46 100644 --- a/klippy/extras/motion_queuing.py +++ b/klippy/extras/motion_queuing.py @@ -17,6 +17,7 @@ class PrinterMotionQueuing: ffi_main, ffi_lib = chelper.get_ffi() self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves self.steppersync_flush = ffi_lib.steppersync_flush + self.steppersync_history_expire = ffi_lib.steppersync_history_expire def allocate_trapq(self): ffi_main, ffi_lib = chelper.get_ffi() trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) @@ -58,12 +59,13 @@ class PrinterMotionQueuing: clock = mcu.print_time_to_clock(print_time) if clock < 0: continue - clear_history_clock = \ - max(0, mcu.print_time_to_clock(clear_history_time)) - ret = self.steppersync_flush(ss, clock, clear_history_clock) + ret = self.steppersync_flush(ss, clock) if ret: raise mcu.error("Internal error in MCU '%s' stepcompress" % (mcu.get_name(),)) + clear_history_clock = \ + max(0, mcu.print_time_to_clock(clear_history_time)) + self.steppersync_history_expire(ss, clear_history_clock) def wipe_trapq(self, trapq): # Expire any remaining movement in the trapq (force to history list) NEVER = 9999999999999999. From 1d569a6631781ed8b3d39eadaf203272d93769e1 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 5 Aug 2025 14:27:45 -0400 Subject: [PATCH 13/23] motion_queuing: Remove flush_steppersync() Move code from flush_steppersync() to existing flush_motion_queues() and clean_motion_queues() functions. Signed-off-by: Kevin O'Connor --- klippy/extras/motion_queuing.py | 24 +++++++++++++----------- klippy/toolhead.py | 2 -- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/klippy/extras/motion_queuing.py b/klippy/extras/motion_queuing.py index 3a7bba46..c0de615d 100644 --- a/klippy/extras/motion_queuing.py +++ b/klippy/extras/motion_queuing.py @@ -46,26 +46,28 @@ class PrinterMotionQueuing: def register_flush_callback(self, callback): self.flush_callbacks.append(callback) def flush_motion_queues(self, must_flush_time, max_step_gen_time): + # Invoke flush callbacks (if any) for cb in self.flush_callbacks: cb(must_flush_time) + # Generate itersolve steps for stepper in self.steppers: stepper.generate_steps(max_step_gen_time) - def clean_motion_queues(self, trapq_free_time, clear_history_time): - for trapq in self.trapqs: - self.trapq_finalize_moves(trapq, trapq_free_time, - clear_history_time) - def flush_steppersync(self, print_time, clear_history_time): + # Flush steps from stepcompress and steppersync for mcu, ss in self.steppersyncs: - clock = mcu.print_time_to_clock(print_time) - if clock < 0: - continue + clock = max(0, mcu.print_time_to_clock(must_flush_time)) ret = self.steppersync_flush(ss, clock) if ret: raise mcu.error("Internal error in MCU '%s' stepcompress" % (mcu.get_name(),)) - clear_history_clock = \ - max(0, mcu.print_time_to_clock(clear_history_time)) - self.steppersync_history_expire(ss, clear_history_clock) + def clean_motion_queues(self, trapq_free_time, clear_history_time): + # Move processed trapq moves to history list, and expire old history + for trapq in self.trapqs: + self.trapq_finalize_moves(trapq, trapq_free_time, + clear_history_time) + # Clean up old history entries in stepcompress objects + for mcu, ss in self.steppersyncs: + clock = max(0, mcu.print_time_to_clock(clear_history_time)) + self.steppersync_history_expire(ss, clock) def wipe_trapq(self, trapq): # Expire any remaining movement in the trapq (force to history list) NEVER = 9999999999999999. diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 6fd5f2de..9a6c2345 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -284,8 +284,6 @@ class ToolHead: clear_history_time = flush_time - MOVE_HISTORY_EXPIRE free_time = sg_flush_time - self.kin_flush_delay self.motion_queuing.clean_motion_queues(free_time, clear_history_time) - # Flush stepcompress and mcu steppersync - self.motion_queuing.flush_steppersync(flush_time, clear_history_time) self.last_flush_time = flush_time def _advance_move_time(self, next_print_time): pt_delay = self.kin_flush_delay + STEPCOMPRESS_FLUSH_TIME From 6d592794381af0e93338b272dce3f1262499db31 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 5 Aug 2025 13:56:17 -0400 Subject: [PATCH 14/23] statistics: Avoid adding extra blank spaces on empty stats reports Signed-off-by: Kevin O'Connor --- klippy/extras/statistics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/klippy/extras/statistics.py b/klippy/extras/statistics.py index 90cd53f8..9e6188a8 100644 --- a/klippy/extras/statistics.py +++ b/klippy/extras/statistics.py @@ -65,8 +65,8 @@ class PrinterStats: def generate_stats(self, eventtime): stats = [cb(eventtime) for cb in self.stats_cb] if max([s[0] for s in stats]): - logging.info("Stats %.1f: %s", eventtime, - ' '.join([s[1] for s in stats])) + stats_str = ' '.join([s[1] for s in stats if s[1]]) + logging.info("Stats %.1f: %s", eventtime, stats_str) return eventtime + 1. def load_config(config): From b5e573957cb8b18871ae81fe873402bcc39fac44 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 5 Aug 2025 14:39:04 -0400 Subject: [PATCH 15/23] motion_queuing: Move clear_history_time from toolhead to motion_queuing Implement the 30 second clear_history_time offset checking directly in the motion_queuing module. Signed-off-by: Kevin O'Connor --- klippy/extras/motion_queuing.py | 15 ++++++++++++++- klippy/toolhead.py | 9 ++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/klippy/extras/motion_queuing.py b/klippy/extras/motion_queuing.py index c0de615d..6fadbef1 100644 --- a/klippy/extras/motion_queuing.py +++ b/klippy/extras/motion_queuing.py @@ -6,6 +6,8 @@ import logging import chelper +MOVE_HISTORY_EXPIRE = 30. + class PrinterMotionQueuing: def __init__(self, config): self.printer = config.get_printer() @@ -18,6 +20,9 @@ class PrinterMotionQueuing: self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves self.steppersync_flush = ffi_lib.steppersync_flush self.steppersync_history_expire = ffi_lib.steppersync_history_expire + self.clear_history_time = 0. + is_debug = self.printer.get_start_args().get('debugoutput') is not None + self.is_debugoutput = is_debug def allocate_trapq(self): ffi_main, ffi_lib = chelper.get_ffi() trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) @@ -59,7 +64,10 @@ class PrinterMotionQueuing: if ret: raise mcu.error("Internal error in MCU '%s' stepcompress" % (mcu.get_name(),)) - def clean_motion_queues(self, trapq_free_time, clear_history_time): + def clean_motion_queues(self, trapq_free_time): + clear_history_time = self.clear_history_time + if self.is_debugoutput: + clear_history_time = trapq_free_time - MOVE_HISTORY_EXPIRE # Move processed trapq moves to history list, and expire old history for trapq in self.trapqs: self.trapq_finalize_moves(trapq, trapq_free_time, @@ -75,6 +83,11 @@ class PrinterMotionQueuing: def lookup_trapq_append(self): ffi_main, ffi_lib = chelper.get_ffi() return ffi_lib.trapq_append + def stats(self, eventtime): + mcu = self.printer.lookup_object('mcu') + est_print_time = mcu.estimated_print_time(eventtime) + self.clear_history_time = est_print_time - MOVE_HISTORY_EXPIRE + return False, "" def load_config(config): return PrinterMotionQueuing(config) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 9a6c2345..7f53ef44 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -200,7 +200,6 @@ MIN_KIN_TIME = 0.100 MOVE_BATCH_TIME = 0.500 STEPCOMPRESS_FLUSH_TIME = 0.050 SDS_CHECK_TIME = 0.001 # step+dir+step filter in stepcompress.c -MOVE_HISTORY_EXPIRE = 30. DRIP_SEGMENT_TIME = 0.050 DRIP_TIME = 0.100 @@ -241,7 +240,7 @@ class ToolHead: self.flush_timer = self.reactor.register_timer(self._flush_handler) self.do_kick_flush_timer = True self.last_flush_time = self.min_restart_time = 0. - self.need_flush_time = self.step_gen_time = self.clear_history_time = 0. + self.need_flush_time = self.step_gen_time = 0. # Kinematic step generation scan window time tracking self.kin_flush_delay = SDS_CHECK_TIME self.kin_flush_times = [] @@ -279,11 +278,8 @@ class ToolHead: self.motion_queuing.flush_motion_queues(flush_time, sg_flush_time) self.min_restart_time = max(self.min_restart_time, sg_flush_time) # Free trapq entries that are no longer needed - clear_history_time = self.clear_history_time - if not self.can_pause: - clear_history_time = flush_time - MOVE_HISTORY_EXPIRE free_time = sg_flush_time - self.kin_flush_delay - self.motion_queuing.clean_motion_queues(free_time, clear_history_time) + self.motion_queuing.clean_motion_queues(free_time) self.last_flush_time = flush_time def _advance_move_time(self, next_print_time): pt_delay = self.kin_flush_delay + STEPCOMPRESS_FLUSH_TIME @@ -561,7 +557,6 @@ class ToolHead: for m in self.all_mcus: m.check_active(max_queue_time, eventtime) est_print_time = self.mcu.estimated_print_time(eventtime) - self.clear_history_time = est_print_time - MOVE_HISTORY_EXPIRE buffer_time = self.print_time - est_print_time is_active = buffer_time > -60. or not self.special_queuing_state if self.special_queuing_state == "Drip": From c454e88d9aec86b149e1e3fa9daa75944872e74a Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 5 Aug 2025 14:50:36 -0400 Subject: [PATCH 16/23] stepper: Implement active callbacks via motion_queuing.register_flush_callback() Use the existing register_flush_callback() system to implement motor activity checking. This simplifies the generate_steps() code. Signed-off-by: Kevin O'Connor --- klippy/extras/motion_queuing.py | 7 ++++++- klippy/extras/output_pin.py | 4 ++-- klippy/extras/pwm_tool.py | 4 ++-- klippy/stepper.py | 28 +++++++++++++++++++--------- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/klippy/extras/motion_queuing.py b/klippy/extras/motion_queuing.py index 6fadbef1..06fa5a9b 100644 --- a/klippy/extras/motion_queuing.py +++ b/klippy/extras/motion_queuing.py @@ -50,10 +50,15 @@ class PrinterMotionQueuing: self.steppers.append(stepper) def register_flush_callback(self, callback): self.flush_callbacks.append(callback) + def unregister_flush_callback(self, callback): + if callback in self.flush_callbacks: + fcbs = list(self.flush_callbacks) + fcbs.remove(callback) + self.flush_callbacks = fcbs def flush_motion_queues(self, must_flush_time, max_step_gen_time): # Invoke flush callbacks (if any) for cb in self.flush_callbacks: - cb(must_flush_time) + cb(must_flush_time, max_step_gen_time) # Generate itersolve steps for stepper in self.steppers: stepper.generate_steps(max_step_gen_time) diff --git a/klippy/extras/output_pin.py b/klippy/extras/output_pin.py index 9eb8ea8b..63862d97 100644 --- a/klippy/extras/output_pin.py +++ b/klippy/extras/output_pin.py @@ -25,12 +25,12 @@ class GCodeRequestQueue: printer.register_event_handler("klippy:connect", self._handle_connect) def _handle_connect(self): self.toolhead = self.printer.lookup_object('toolhead') - def _flush_notification(self, print_time): + def _flush_notification(self, must_flush_time, max_step_gen_time): min_sched_time = self.mcu.min_schedule_time() rqueue = self.rqueue while rqueue: next_time = max(rqueue[0][0], self.next_min_flush_time) - if next_time > print_time: + if next_time > must_flush_time: return # Skip requests that have been overridden with a following request pos = 0 diff --git a/klippy/extras/pwm_tool.py b/klippy/extras/pwm_tool.py index 69fc1a46..6d401c0b 100644 --- a/klippy/extras/pwm_tool.py +++ b/klippy/extras/pwm_tool.py @@ -124,8 +124,8 @@ class MCU_queued_pwm: value = 1. - value v = int(max(0., min(1., value)) * self._pwm_max + 0.5) self._send_update(clock, v) - def _flush_notification(self, print_time): - clock = self._mcu.print_time_to_clock(print_time) + def _flush_notification(self, must_flush_time, max_step_gen_time): + clock = self._mcu.print_time_to_clock(must_flush_time) if self._last_value != self._default_value: while clock >= self._last_clock + self._duration_ticks: self._send_update(self._last_clock + self._duration_ticks, diff --git a/klippy/stepper.py b/klippy/stepper.py index 6ce9130f..8e6a883d 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -235,16 +235,26 @@ class MCU_stepper: return old_tq def add_active_callback(self, cb): self._active_callbacks.append(cb) + if len(self._active_callbacks) == 1: + printer = self._mcu.get_printer() + motion_queuing = printer.lookup_object('motion_queuing') + motion_queuing.register_flush_callback(self._check_active) + def _check_active(self, must_flush_time, max_step_gen_time): + sk = self._stepper_kinematics + ret = self._itersolve_check_active(sk, max_step_gen_time) + if not ret: + # Stepper motor still not active + return + # Motor is active, disable future checking + printer = self._mcu.get_printer() + motion_queuing = printer.lookup_object('motion_queuing') + motion_queuing.unregister_flush_callback(self._check_active) + cbs = self._active_callbacks + self._active_callbacks = [] + # Invoke callbacks + for cb in cbs: + cb(ret) def generate_steps(self, flush_time): - # Check for activity if necessary - if self._active_callbacks: - 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 sk = self._stepper_kinematics ret = self._itersolve_generate_steps(sk, flush_time) From c520bf981dc37bb3dccdad6dcd78012ad13abd1f Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 5 Aug 2025 16:32:27 -0400 Subject: [PATCH 17/23] steppersync: Split steppersync code from stepcompress.c to new file Signed-off-by: Kevin O'Connor --- klippy/chelper/__init__.py | 12 ++- klippy/chelper/stepcompress.c | 173 ++-------------------------------- klippy/chelper/stepcompress.h | 15 +-- klippy/chelper/steppersync.c | 168 +++++++++++++++++++++++++++++++++ klippy/chelper/steppersync.h | 16 ++++ 5 files changed, 206 insertions(+), 178 deletions(-) create mode 100644 klippy/chelper/steppersync.c create mode 100644 klippy/chelper/steppersync.h diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py index d56b720c..3800834b 100644 --- a/klippy/chelper/__init__.py +++ b/klippy/chelper/__init__.py @@ -17,16 +17,16 @@ COMPILE_ARGS = ("-Wall -g -O2 -shared -fPIC" " -o %s %s") SSE_FLAGS = "-mfpmath=sse -msse2" SOURCE_FILES = [ - 'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'itersolve.c', 'trapq.c', - 'pollreactor.c', 'msgblock.c', 'trdispatch.c', + 'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'steppersync.c', + 'itersolve.c', 'trapq.c', 'pollreactor.c', 'msgblock.c', 'trdispatch.c', 'kin_cartesian.c', 'kin_corexy.c', 'kin_corexz.c', 'kin_delta.c', 'kin_deltesian.c', 'kin_polar.c', 'kin_rotary_delta.c', 'kin_winch.c', 'kin_extruder.c', 'kin_shaper.c', 'kin_idex.c', 'kin_generic.c' ] DEST_LIB = "c_helper.so" OTHER_FILES = [ - 'list.h', 'serialqueue.h', 'stepcompress.h', 'itersolve.h', 'pyhelper.h', - 'trapq.h', 'pollreactor.h', 'msgblock.h' + 'list.h', 'serialqueue.h', 'stepcompress.h', 'steppersync.h', + 'itersolve.h', 'pyhelper.h', 'trapq.h', 'pollreactor.h', 'msgblock.h' ] defs_stepcompress = """ @@ -54,7 +54,9 @@ defs_stepcompress = """ int stepcompress_extract_old(struct stepcompress *sc , struct pull_history_steps *p, int max , uint64_t start_clock, uint64_t end_clock); +""" +defs_steppersync = """ struct steppersync *steppersync_alloc(struct serialqueue *sq , struct stepcompress **sc_list, int sc_num, int move_num); void steppersync_free(struct steppersync *ss); @@ -228,7 +230,7 @@ defs_std = """ defs_all = [ defs_pyhelper, defs_serialqueue, defs_std, defs_stepcompress, - defs_itersolve, defs_trapq, defs_trdispatch, + defs_steppersync, defs_itersolve, defs_trapq, defs_trdispatch, defs_kin_cartesian, defs_kin_corexy, defs_kin_corexz, defs_kin_delta, defs_kin_deltesian, defs_kin_polar, defs_kin_rotary_delta, defs_kin_winch, defs_kin_extruder, defs_kin_shaper, defs_kin_idex, diff --git a/klippy/chelper/stepcompress.c b/klippy/chelper/stepcompress.c index 939ac2c1..470feb71 100644 --- a/klippy/chelper/stepcompress.c +++ b/klippy/chelper/stepcompress.c @@ -1,6 +1,6 @@ // Stepper pulse schedule compression // -// Copyright (C) 2016-2021 Kevin O'Connor +// Copyright (C) 2016-2025 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -277,7 +277,7 @@ stepcompress_set_invert_sdir(struct stepcompress *sc, uint32_t invert_sdir) } // Expire the stepcompress history older than the given clock -static void +void stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock) { while (!list_empty(&sc->history_list)) { @@ -314,6 +314,12 @@ stepcompress_get_step_dir(struct stepcompress *sc) return sc->next_step_dir; } +struct list_head * +stepcompress_get_msg_queue(struct stepcompress *sc) +{ + return &sc->msg_queue; +} + // Determine the "print time" of the last_step_clock static void calc_last_step_print_time(struct stepcompress *sc) @@ -323,7 +329,7 @@ calc_last_step_print_time(struct stepcompress *sc) } // Set the conversion rate of 'print_time' to mcu clock -static void +void stepcompress_set_time(struct stepcompress *sc , double time_offset, double mcu_freq) { @@ -532,7 +538,7 @@ stepcompress_commit(struct stepcompress *sc) } // Flush pending steps -static int +int stepcompress_flush(struct stepcompress *sc, uint64_t move_clock) { if (sc->next_step_clock && move_clock >= sc->next_step_clock) { @@ -656,162 +662,3 @@ stepcompress_extract_old(struct stepcompress *sc, struct pull_history_steps *p } return res; } - - -/**************************************************************** - * Step compress synchronization - ****************************************************************/ - -// The steppersync object is used to synchronize the output of mcu -// step commands. The mcu can only queue a limited number of step -// commands - this code tracks when items on the mcu step queue become -// free so that new commands can be transmitted. It also ensures the -// mcu step queue is ordered between steppers so that no stepper -// starves the other steppers of space in the mcu step queue. - -struct steppersync { - // Serial port - struct serialqueue *sq; - struct command_queue *cq; - // Storage for associated stepcompress objects - struct stepcompress **sc_list; - int sc_num; - // Storage for list of pending move clocks - uint64_t *move_clocks; - int num_move_clocks; -}; - -// Allocate a new 'steppersync' object -struct steppersync * __visible -steppersync_alloc(struct serialqueue *sq, struct stepcompress **sc_list - , int sc_num, int move_num) -{ - struct steppersync *ss = malloc(sizeof(*ss)); - memset(ss, 0, sizeof(*ss)); - ss->sq = sq; - ss->cq = serialqueue_alloc_commandqueue(); - - ss->sc_list = malloc(sizeof(*sc_list)*sc_num); - memcpy(ss->sc_list, sc_list, sizeof(*sc_list)*sc_num); - ss->sc_num = sc_num; - - ss->move_clocks = malloc(sizeof(*ss->move_clocks)*move_num); - memset(ss->move_clocks, 0, sizeof(*ss->move_clocks)*move_num); - ss->num_move_clocks = move_num; - - return ss; -} - -// Free memory associated with a 'steppersync' object -void __visible -steppersync_free(struct steppersync *ss) -{ - if (!ss) - return; - free(ss->sc_list); - free(ss->move_clocks); - serialqueue_free_commandqueue(ss->cq); - free(ss); -} - -// Set the conversion rate of 'print_time' to mcu clock -void __visible -steppersync_set_time(struct steppersync *ss, double time_offset - , double mcu_freq) -{ - int i; - for (i=0; isc_num; i++) { - struct stepcompress *sc = ss->sc_list[i]; - stepcompress_set_time(sc, time_offset, mcu_freq); - } -} - -// Expire the stepcompress history before the given clock time -void __visible -steppersync_history_expire(struct steppersync *ss, uint64_t end_clock) -{ - int i; - for (i = 0; i < ss->sc_num; i++) { - struct stepcompress *sc = ss->sc_list[i]; - stepcompress_history_expire(sc, end_clock); - } -} - -// Implement a binary heap algorithm to track when the next available -// 'struct move' in the mcu will be available -static void -heap_replace(struct steppersync *ss, uint64_t req_clock) -{ - uint64_t *mc = ss->move_clocks; - int nmc = ss->num_move_clocks, pos = 0; - for (;;) { - int child1_pos = 2*pos+1, child2_pos = 2*pos+2; - uint64_t child2_clock = child2_pos < nmc ? mc[child2_pos] : UINT64_MAX; - uint64_t child1_clock = child1_pos < nmc ? mc[child1_pos] : UINT64_MAX; - if (req_clock <= child1_clock && req_clock <= child2_clock) { - mc[pos] = req_clock; - break; - } - if (child1_clock < child2_clock) { - mc[pos] = child1_clock; - pos = child1_pos; - } else { - mc[pos] = child2_clock; - pos = child2_pos; - } - } -} - -// Find and transmit any scheduled steps prior to the given 'move_clock' -int __visible -steppersync_flush(struct steppersync *ss, uint64_t move_clock) -{ - // Flush each stepcompress to the specified move_clock - int i; - for (i=0; isc_num; i++) { - int ret = stepcompress_flush(ss->sc_list[i], move_clock); - if (ret) - return ret; - } - - // Order commands by the reqclock of each pending command - struct list_head msgs; - list_init(&msgs); - for (;;) { - // Find message with lowest reqclock - uint64_t req_clock = MAX_CLOCK; - struct queue_message *qm = NULL; - for (i=0; isc_num; i++) { - struct stepcompress *sc = ss->sc_list[i]; - if (!list_empty(&sc->msg_queue)) { - struct queue_message *m = list_first_entry( - &sc->msg_queue, struct queue_message, node); - if (m->req_clock < req_clock) { - qm = m; - req_clock = m->req_clock; - } - } - } - if (!qm || (qm->min_clock && req_clock > move_clock)) - break; - - uint64_t next_avail = ss->move_clocks[0]; - if (qm->min_clock) - // The qm->min_clock field is overloaded to indicate that - // the command uses the 'move queue' and to store the time - // that move queue item becomes available. - heap_replace(ss, qm->min_clock); - // Reset the min_clock to its normal meaning (minimum transmit time) - qm->min_clock = next_avail; - - // Batch this command - list_del(&qm->node); - list_add_tail(&qm->node, &msgs); - } - - // Transmit commands - if (!list_empty(&msgs)) - serialqueue_send_batch(ss->sq, ss->cq, &msgs); - - return 0; -} diff --git a/klippy/chelper/stepcompress.h b/klippy/chelper/stepcompress.h index 06bc7a49..1850436e 100644 --- a/klippy/chelper/stepcompress.h +++ b/klippy/chelper/stepcompress.h @@ -17,12 +17,17 @@ void stepcompress_fill(struct stepcompress *sc, uint32_t max_error , int32_t set_next_step_dir_msgtag); void stepcompress_set_invert_sdir(struct stepcompress *sc , uint32_t invert_sdir); +void stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock); void stepcompress_free(struct stepcompress *sc); uint32_t stepcompress_get_oid(struct stepcompress *sc); int stepcompress_get_step_dir(struct stepcompress *sc); +struct list_head *stepcompress_get_msg_queue(struct stepcompress *sc); +void stepcompress_set_time(struct stepcompress *sc + , double time_offset, double mcu_freq); int stepcompress_append(struct stepcompress *sc, int sdir , double print_time, double step_time); int stepcompress_commit(struct stepcompress *sc); +int stepcompress_flush(struct stepcompress *sc, uint64_t move_clock); int stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock); int stepcompress_set_last_position(struct stepcompress *sc, uint64_t clock , int64_t last_position); @@ -35,14 +40,4 @@ int stepcompress_extract_old(struct stepcompress *sc , struct pull_history_steps *p, int max , uint64_t start_clock, uint64_t end_clock); -struct serialqueue; -struct steppersync *steppersync_alloc( - struct serialqueue *sq, struct stepcompress **sc_list, int sc_num - , int move_num); -void steppersync_free(struct steppersync *ss); -void steppersync_set_time(struct steppersync *ss, double time_offset - , double mcu_freq); -void steppersync_history_expire(struct steppersync *ss, uint64_t end_clock); -int steppersync_flush(struct steppersync *ss, uint64_t move_clock); - #endif // stepcompress.h diff --git a/klippy/chelper/steppersync.c b/klippy/chelper/steppersync.c new file mode 100644 index 00000000..0542ee4f --- /dev/null +++ b/klippy/chelper/steppersync.c @@ -0,0 +1,168 @@ +// Stepper step transmit synchronization +// +// Copyright (C) 2016-2025 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +// The steppersync object is used to synchronize the output of mcu +// step commands. The mcu can only queue a limited number of step +// commands - this code tracks when items on the mcu step queue become +// free so that new commands can be transmitted. It also ensures the +// mcu step queue is ordered between steppers so that no stepper +// starves the other steppers of space in the mcu step queue. + +#include // offsetof +#include // malloc +#include // memset +#include "compiler.h" // __visible +#include "serialqueue.h" // struct queue_message +#include "stepcompress.h" // stepcompress_flush +#include "steppersync.h" // steppersync_alloc + +struct steppersync { + // Serial port + struct serialqueue *sq; + struct command_queue *cq; + // Storage for associated stepcompress objects + struct stepcompress **sc_list; + int sc_num; + // Storage for list of pending move clocks + uint64_t *move_clocks; + int num_move_clocks; +}; + +// Allocate a new 'steppersync' object +struct steppersync * __visible +steppersync_alloc(struct serialqueue *sq, struct stepcompress **sc_list + , int sc_num, int move_num) +{ + struct steppersync *ss = malloc(sizeof(*ss)); + memset(ss, 0, sizeof(*ss)); + ss->sq = sq; + ss->cq = serialqueue_alloc_commandqueue(); + + ss->sc_list = malloc(sizeof(*sc_list)*sc_num); + memcpy(ss->sc_list, sc_list, sizeof(*sc_list)*sc_num); + ss->sc_num = sc_num; + + ss->move_clocks = malloc(sizeof(*ss->move_clocks)*move_num); + memset(ss->move_clocks, 0, sizeof(*ss->move_clocks)*move_num); + ss->num_move_clocks = move_num; + + return ss; +} + +// Free memory associated with a 'steppersync' object +void __visible +steppersync_free(struct steppersync *ss) +{ + if (!ss) + return; + free(ss->sc_list); + free(ss->move_clocks); + serialqueue_free_commandqueue(ss->cq); + free(ss); +} + +// Set the conversion rate of 'print_time' to mcu clock +void __visible +steppersync_set_time(struct steppersync *ss, double time_offset + , double mcu_freq) +{ + int i; + for (i=0; isc_num; i++) { + struct stepcompress *sc = ss->sc_list[i]; + stepcompress_set_time(sc, time_offset, mcu_freq); + } +} + +// Expire the stepcompress history before the given clock time +void __visible +steppersync_history_expire(struct steppersync *ss, uint64_t end_clock) +{ + int i; + for (i = 0; i < ss->sc_num; i++) { + struct stepcompress *sc = ss->sc_list[i]; + stepcompress_history_expire(sc, end_clock); + } +} + +// Implement a binary heap algorithm to track when the next available +// 'struct move' in the mcu will be available +static void +heap_replace(struct steppersync *ss, uint64_t req_clock) +{ + uint64_t *mc = ss->move_clocks; + int nmc = ss->num_move_clocks, pos = 0; + for (;;) { + int child1_pos = 2*pos+1, child2_pos = 2*pos+2; + uint64_t child2_clock = child2_pos < nmc ? mc[child2_pos] : UINT64_MAX; + uint64_t child1_clock = child1_pos < nmc ? mc[child1_pos] : UINT64_MAX; + if (req_clock <= child1_clock && req_clock <= child2_clock) { + mc[pos] = req_clock; + break; + } + if (child1_clock < child2_clock) { + mc[pos] = child1_clock; + pos = child1_pos; + } else { + mc[pos] = child2_clock; + pos = child2_pos; + } + } +} + +// Find and transmit any scheduled steps prior to the given 'move_clock' +int __visible +steppersync_flush(struct steppersync *ss, uint64_t move_clock) +{ + // Flush each stepcompress to the specified move_clock + int i; + for (i=0; isc_num; i++) { + int ret = stepcompress_flush(ss->sc_list[i], move_clock); + if (ret) + return ret; + } + + // Order commands by the reqclock of each pending command + struct list_head msgs; + list_init(&msgs); + for (;;) { + // Find message with lowest reqclock + uint64_t req_clock = MAX_CLOCK; + struct queue_message *qm = NULL; + for (i=0; isc_num; i++) { + struct stepcompress *sc = ss->sc_list[i]; + struct list_head *sc_mq = stepcompress_get_msg_queue(sc); + if (!list_empty(sc_mq)) { + struct queue_message *m = list_first_entry( + sc_mq, struct queue_message, node); + if (m->req_clock < req_clock) { + qm = m; + req_clock = m->req_clock; + } + } + } + if (!qm || (qm->min_clock && req_clock > move_clock)) + break; + + uint64_t next_avail = ss->move_clocks[0]; + if (qm->min_clock) + // The qm->min_clock field is overloaded to indicate that + // the command uses the 'move queue' and to store the time + // that move queue item becomes available. + heap_replace(ss, qm->min_clock); + // Reset the min_clock to its normal meaning (minimum transmit time) + qm->min_clock = next_avail; + + // Batch this command + list_del(&qm->node); + list_add_tail(&qm->node, &msgs); + } + + // Transmit commands + if (!list_empty(&msgs)) + serialqueue_send_batch(ss->sq, ss->cq, &msgs); + + return 0; +} diff --git a/klippy/chelper/steppersync.h b/klippy/chelper/steppersync.h new file mode 100644 index 00000000..ffc8d569 --- /dev/null +++ b/klippy/chelper/steppersync.h @@ -0,0 +1,16 @@ +#ifndef STEPPERSYNC_H +#define STEPPERSYNC_H + +#include // uint64_t + +struct serialqueue; +struct steppersync *steppersync_alloc( + struct serialqueue *sq, struct stepcompress **sc_list, int sc_num + , int move_num); +void steppersync_free(struct steppersync *ss); +void steppersync_set_time(struct steppersync *ss, double time_offset + , double mcu_freq); +void steppersync_history_expire(struct steppersync *ss, uint64_t end_clock); +int steppersync_flush(struct steppersync *ss, uint64_t move_clock); + +#endif // steppersync.h From dd4cc8eb4cba7ef6c510d52148589824d24043a8 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 5 Aug 2025 17:07:19 -0400 Subject: [PATCH 18/23] itersolve: Do not store a reference to 'struct stepcompress' Pass in the 'struct stepcompress' reference to each call of itersolve_generate_steps(). Signed-off-by: Kevin O'Connor --- klippy/chelper/__init__.py | 7 +++---- klippy/chelper/itersolve.c | 33 ++++++++++++++------------------- klippy/chelper/itersolve.h | 7 +++---- klippy/stepper.py | 9 ++++----- 4 files changed, 24 insertions(+), 32 deletions(-) diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py index 3800834b..175b10be 100644 --- a/klippy/chelper/__init__.py +++ b/klippy/chelper/__init__.py @@ -68,13 +68,12 @@ defs_steppersync = """ defs_itersolve = """ int32_t itersolve_generate_steps(struct stepper_kinematics *sk - , double flush_time); + , struct stepcompress *sc, double flush_time); double itersolve_check_active(struct stepper_kinematics *sk , double flush_time); int32_t itersolve_is_active_axis(struct stepper_kinematics *sk, char axis); - void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq); - void itersolve_set_stepcompress(struct stepper_kinematics *sk - , struct stepcompress *sc, double step_dist); + void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq + , double step_dist); double itersolve_calc_position_from_coord(struct stepper_kinematics *sk , double x, double y, double z); void itersolve_set_position(struct stepper_kinematics *sk diff --git a/klippy/chelper/itersolve.c b/klippy/chelper/itersolve.c index 0dbc6c51..968c8cfb 100644 --- a/klippy/chelper/itersolve.c +++ b/klippy/chelper/itersolve.c @@ -26,8 +26,8 @@ struct timepos { // Generate step times for a portion of a move static int32_t -itersolve_gen_steps_range(struct stepper_kinematics *sk, struct move *m - , double abs_start, double abs_end) +itersolve_gen_steps_range(struct stepper_kinematics *sk, struct stepcompress *sc + , struct move *m, double abs_start, double abs_end) { sk_calc_callback calc_position_cb = sk->calc_position_cb; double half_step = .5 * sk->step_dist; @@ -37,7 +37,7 @@ itersolve_gen_steps_range(struct stepper_kinematics *sk, struct move *m if (end > m->move_t) end = m->move_t; struct timepos old_guess = {start, sk->commanded_pos}, guess = old_guess; - int sdir = stepcompress_get_step_dir(sk->sc); + int sdir = stepcompress_get_step_dir(sc); int is_dir_change = 0, have_bracket = 0, check_oscillate = 0; double target = sk->commanded_pos + (sdir ? half_step : -half_step); double last_time=start, low_time=start, high_time=start + SEEK_TIME_RESET; @@ -99,13 +99,13 @@ itersolve_gen_steps_range(struct stepper_kinematics *sk, struct move *m if (!have_bracket || high_time - low_time > .000000001) { if (!is_dir_change && rel_dist >= -half_step) // Avoid rollback if stepper fully reaches step position - stepcompress_commit(sk->sc); + stepcompress_commit(sc); // Guess is not close enough - guess again with new time continue; } } // Found next step - submit it - int ret = stepcompress_append(sk->sc, sdir, m->print_time, guess.time); + int ret = stepcompress_append(sc, sdir, m->print_time, guess.time); if (ret) return ret; target = sdir ? target+half_step+half_step : target-half_step-half_step; @@ -144,7 +144,8 @@ check_active(struct stepper_kinematics *sk, struct move *m) // Generate step times for a range of moves on the trapq int32_t __visible -itersolve_generate_steps(struct stepper_kinematics *sk, double flush_time) +itersolve_generate_steps(struct stepper_kinematics *sk, struct stepcompress *sc + , double flush_time) { double last_flush_time = sk->last_flush_time; sk->last_flush_time = flush_time; @@ -170,15 +171,15 @@ itersolve_generate_steps(struct stepper_kinematics *sk, double flush_time) while (--skip_count && pm->print_time > abs_start) pm = list_prev_entry(pm, node); do { - int32_t ret = itersolve_gen_steps_range(sk, pm, abs_start - , flush_time); + int32_t ret = itersolve_gen_steps_range( + sk, sc, pm, abs_start, flush_time); if (ret) return ret; pm = list_next_entry(pm, node); } while (pm != m); } // Generate steps for this move - int32_t ret = itersolve_gen_steps_range(sk, m, last_flush_time + int32_t ret = itersolve_gen_steps_range(sk, sc, m, last_flush_time , flush_time); if (ret) return ret; @@ -195,8 +196,8 @@ itersolve_generate_steps(struct stepper_kinematics *sk, double flush_time) double abs_end = force_steps_time; if (abs_end > flush_time) abs_end = flush_time; - int32_t ret = itersolve_gen_steps_range(sk, m, last_flush_time - , abs_end); + int32_t ret = itersolve_gen_steps_range( + sk, sc, m, last_flush_time, abs_end); if (ret) return ret; skip_count = 1; @@ -240,16 +241,10 @@ itersolve_is_active_axis(struct stepper_kinematics *sk, char axis) } void __visible -itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq) +itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq + , double step_dist) { sk->tq = tq; -} - -void __visible -itersolve_set_stepcompress(struct stepper_kinematics *sk - , struct stepcompress *sc, double step_dist) -{ - sk->sc = sc; sk->step_dist = step_dist; } diff --git a/klippy/chelper/itersolve.h b/klippy/chelper/itersolve.h index adb48055..e2e46ebe 100644 --- a/klippy/chelper/itersolve.h +++ b/klippy/chelper/itersolve.h @@ -26,12 +26,11 @@ struct stepper_kinematics { }; int32_t itersolve_generate_steps(struct stepper_kinematics *sk - , double flush_time); + , struct stepcompress *sc, double flush_time); double itersolve_check_active(struct stepper_kinematics *sk, double flush_time); int32_t itersolve_is_active_axis(struct stepper_kinematics *sk, char axis); -void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq); -void itersolve_set_stepcompress(struct stepper_kinematics *sk - , struct stepcompress *sc, double step_dist); +void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq + , double step_dist); double itersolve_calc_position_from_coord(struct stepper_kinematics *sk , double x, double y, double z); void itersolve_set_position(struct stepper_kinematics *sk diff --git a/klippy/stepper.py b/klippy/stepper.py index 8e6a883d..8e87c538 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -135,7 +135,7 @@ class MCU_stepper: mcu_pos = self.get_mcu_position() self._rotation_dist = rotation_dist self._step_dist = rotation_dist / self._steps_per_rotation - self.set_stepper_kinematics(self._stepper_kinematics) + self.set_trapq(self._trapq) self._set_mcu_position(mcu_pos) def get_dir_inverted(self): return self._invert_dir, self._orig_invert_dir @@ -192,8 +192,6 @@ class MCU_stepper: if old_sk is not None: mcu_pos = self.get_mcu_position() self._stepper_kinematics = sk - 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 @@ -229,7 +227,8 @@ class MCU_stepper: ffi_main, ffi_lib = chelper.get_ffi() if tq is None: tq = ffi_main.NULL - ffi_lib.itersolve_set_trapq(self._stepper_kinematics, tq) + ffi_lib.itersolve_set_trapq(self._stepper_kinematics, + tq, self._step_dist) old_tq = self._trapq self._trapq = tq return old_tq @@ -257,7 +256,7 @@ class MCU_stepper: def generate_steps(self, flush_time): # Generate steps sk = self._stepper_kinematics - ret = self._itersolve_generate_steps(sk, flush_time) + ret = self._itersolve_generate_steps(sk, self._stepqueue, flush_time) if ret: raise error("Internal error in stepcompress") def is_active_axis(self, axis): From 2919f373438cc4e6268842113f2a0c40d9f8cde0 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Tue, 5 Aug 2025 17:51:59 -0400 Subject: [PATCH 19/23] stepcompress: Store a reference to 'struct stepper_kinematics' Support storing a reference to 'struct stepper_kinematics' in 'struct stepcompress' and support globally generating steps via the steppersync mechanism. Signed-off-by: Kevin O'Connor --- klippy/chelper/__init__.py | 6 ++++-- klippy/chelper/itersolve.c | 2 +- klippy/chelper/stepcompress.c | 28 +++++++++++++++++++++++++++- klippy/chelper/stepcompress.h | 7 ++++++- klippy/chelper/steppersync.c | 25 +++++++++++++++++-------- klippy/chelper/steppersync.h | 2 ++ klippy/extras/motion_queuing.py | 14 +++++++------- klippy/stepper.py | 12 +++--------- 8 files changed, 67 insertions(+), 29 deletions(-) diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py index 175b10be..6ed6ed5b 100644 --- a/klippy/chelper/__init__.py +++ b/klippy/chelper/__init__.py @@ -54,6 +54,8 @@ defs_stepcompress = """ int stepcompress_extract_old(struct stepcompress *sc , struct pull_history_steps *p, int max , uint64_t start_clock, uint64_t end_clock); + void stepcompress_set_stepper_kinematics(struct stepcompress *sc + , struct stepper_kinematics *sk); """ defs_steppersync = """ @@ -62,13 +64,13 @@ defs_steppersync = """ void steppersync_free(struct steppersync *ss); void steppersync_set_time(struct steppersync *ss , double time_offset, double mcu_freq); + int32_t steppersync_generate_steps(struct steppersync *ss + , double gen_steps_time, uint64_t flush_clock); void steppersync_history_expire(struct steppersync *ss, uint64_t end_clock); int steppersync_flush(struct steppersync *ss, uint64_t move_clock); """ defs_itersolve = """ - int32_t itersolve_generate_steps(struct stepper_kinematics *sk - , struct stepcompress *sc, double flush_time); double itersolve_check_active(struct stepper_kinematics *sk , double flush_time); int32_t itersolve_is_active_axis(struct stepper_kinematics *sk, char axis); diff --git a/klippy/chelper/itersolve.c b/klippy/chelper/itersolve.c index 968c8cfb..eba1deef 100644 --- a/klippy/chelper/itersolve.c +++ b/klippy/chelper/itersolve.c @@ -143,7 +143,7 @@ check_active(struct stepper_kinematics *sk, struct move *m) } // Generate step times for a range of moves on the trapq -int32_t __visible +int32_t itersolve_generate_steps(struct stepper_kinematics *sk, struct stepcompress *sc , double flush_time) { diff --git a/klippy/chelper/stepcompress.c b/klippy/chelper/stepcompress.c index 470feb71..2889570d 100644 --- a/klippy/chelper/stepcompress.c +++ b/klippy/chelper/stepcompress.c @@ -21,6 +21,7 @@ #include // malloc #include // memset #include "compiler.h" // DIV_ROUND_UP +#include "itersolve.h" // itersolve_generate_steps #include "pyhelper.h" // errorf #include "serialqueue.h" // struct queue_message #include "stepcompress.h" // stepcompress_alloc @@ -46,6 +47,8 @@ struct stepcompress { // History tracking int64_t last_position; struct list_head history_list; + // Itersolve reference + struct stepper_kinematics *sk; }; struct step_move { @@ -538,7 +541,7 @@ stepcompress_commit(struct stepcompress *sc) } // Flush pending steps -int +static int stepcompress_flush(struct stepcompress *sc, uint64_t move_clock) { if (sc->next_step_clock && move_clock >= sc->next_step_clock) { @@ -662,3 +665,26 @@ stepcompress_extract_old(struct stepcompress *sc, struct pull_history_steps *p } return res; } + +// Store a reference to stepper_kinematics +void __visible +stepcompress_set_stepper_kinematics(struct stepcompress *sc + , struct stepper_kinematics *sk) +{ + sc->sk = sk; +} + +// Generate steps (via itersolve) and flush +int32_t +stepcompress_generate_steps(struct stepcompress *sc, double gen_steps_time + , uint64_t flush_clock) +{ + if (!sc->sk) + return 0; + // Generate steps + int32_t ret = itersolve_generate_steps(sc->sk, sc, gen_steps_time); + if (ret) + return ret; + // Flush steps + return stepcompress_flush(sc, flush_clock); +} diff --git a/klippy/chelper/stepcompress.h b/klippy/chelper/stepcompress.h index 1850436e..e21c4fd9 100644 --- a/klippy/chelper/stepcompress.h +++ b/klippy/chelper/stepcompress.h @@ -27,7 +27,6 @@ void stepcompress_set_time(struct stepcompress *sc int stepcompress_append(struct stepcompress *sc, int sdir , double print_time, double step_time); int stepcompress_commit(struct stepcompress *sc); -int stepcompress_flush(struct stepcompress *sc, uint64_t move_clock); int stepcompress_reset(struct stepcompress *sc, uint64_t last_step_clock); int stepcompress_set_last_position(struct stepcompress *sc, uint64_t clock , int64_t last_position); @@ -39,5 +38,11 @@ int stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock int stepcompress_extract_old(struct stepcompress *sc , struct pull_history_steps *p, int max , uint64_t start_clock, uint64_t end_clock); +struct stepper_kinematics; +void stepcompress_set_stepper_kinematics(struct stepcompress *sc + , struct stepper_kinematics *sk); +int32_t stepcompress_generate_steps(struct stepcompress *sc + , double gen_steps_time + , uint64_t flush_clock); #endif // stepcompress.h diff --git a/klippy/chelper/steppersync.c b/klippy/chelper/steppersync.c index 0542ee4f..745578c7 100644 --- a/klippy/chelper/steppersync.c +++ b/klippy/chelper/steppersync.c @@ -76,6 +76,22 @@ steppersync_set_time(struct steppersync *ss, double time_offset } } +// Generate steps and flush stepcompress objects +int32_t __visible +steppersync_generate_steps(struct steppersync *ss, double gen_steps_time + , uint64_t flush_clock) +{ + int i; + for (i=0; isc_num; i++) { + struct stepcompress *sc = ss->sc_list[i]; + int32_t ret = stepcompress_generate_steps(sc, gen_steps_time + , flush_clock); + if (ret) + return ret; + } + return 0; +} + // Expire the stepcompress history before the given clock time void __visible steppersync_history_expire(struct steppersync *ss, uint64_t end_clock) @@ -116,14 +132,6 @@ heap_replace(struct steppersync *ss, uint64_t req_clock) int __visible steppersync_flush(struct steppersync *ss, uint64_t move_clock) { - // Flush each stepcompress to the specified move_clock - int i; - for (i=0; isc_num; i++) { - int ret = stepcompress_flush(ss->sc_list[i], move_clock); - if (ret) - return ret; - } - // Order commands by the reqclock of each pending command struct list_head msgs; list_init(&msgs); @@ -131,6 +139,7 @@ steppersync_flush(struct steppersync *ss, uint64_t move_clock) // Find message with lowest reqclock uint64_t req_clock = MAX_CLOCK; struct queue_message *qm = NULL; + int i; for (i=0; isc_num; i++) { struct stepcompress *sc = ss->sc_list[i]; struct list_head *sc_mq = stepcompress_get_msg_queue(sc); diff --git a/klippy/chelper/steppersync.h b/klippy/chelper/steppersync.h index ffc8d569..1320bbaa 100644 --- a/klippy/chelper/steppersync.h +++ b/klippy/chelper/steppersync.h @@ -10,6 +10,8 @@ struct steppersync *steppersync_alloc( void steppersync_free(struct steppersync *ss); void steppersync_set_time(struct steppersync *ss, double time_offset , double mcu_freq); +int32_t steppersync_generate_steps(struct steppersync *ss, double gen_steps_time + , uint64_t flush_clock); void steppersync_history_expire(struct steppersync *ss, uint64_t end_clock); int steppersync_flush(struct steppersync *ss, uint64_t move_clock); diff --git a/klippy/extras/motion_queuing.py b/klippy/extras/motion_queuing.py index 06fa5a9b..c13b4970 100644 --- a/klippy/extras/motion_queuing.py +++ b/klippy/extras/motion_queuing.py @@ -11,13 +11,13 @@ MOVE_HISTORY_EXPIRE = 30. class PrinterMotionQueuing: def __init__(self, config): self.printer = config.get_printer() - self.steppers = [] self.trapqs = [] self.stepcompress = [] self.steppersyncs = [] self.flush_callbacks = [] ffi_main, ffi_lib = chelper.get_ffi() self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves + self.steppersync_generate_steps = ffi_lib.steppersync_generate_steps self.steppersync_flush = ffi_lib.steppersync_flush self.steppersync_history_expire = ffi_lib.steppersync_history_expire self.clear_history_time = 0. @@ -46,8 +46,6 @@ class PrinterMotionQueuing: ffi_lib.steppersync_free) self.steppersyncs.append((mcu, ss)) return ss - def register_stepper(self, config, stepper): - self.steppers.append(stepper) def register_flush_callback(self, callback): self.flush_callbacks.append(callback) def unregister_flush_callback(self, callback): @@ -59,12 +57,14 @@ class PrinterMotionQueuing: # Invoke flush callbacks (if any) for cb in self.flush_callbacks: cb(must_flush_time, max_step_gen_time) - # Generate itersolve steps - for stepper in self.steppers: - stepper.generate_steps(max_step_gen_time) - # Flush steps from stepcompress and steppersync for mcu, ss in self.steppersyncs: clock = max(0, mcu.print_time_to_clock(must_flush_time)) + # Generate steps + ret = self.steppersync_generate_steps(ss, max_step_gen_time, clock) + if ret: + raise mcu.error("Internal error in MCU '%s' stepcompress" + % (mcu.get_name(),)) + # Flush steps from steppersync ret = self.steppersync_flush(ss, clock) if ret: raise mcu.error("Internal error in MCU '%s' stepcompress" diff --git a/klippy/stepper.py b/klippy/stepper.py index 8e87c538..d5b3cecd 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -48,7 +48,6 @@ class MCU_stepper: ffi_main, ffi_lib = chelper.get_ffi() ffi_lib.stepcompress_set_invert_sdir(self._stepqueue, self._invert_dir) self._stepper_kinematics = None - self._itersolve_generate_steps = ffi_lib.itersolve_generate_steps self._itersolve_check_active = ffi_lib.itersolve_check_active self._trapq = ffi_main.NULL printer.register_event_handler('klippy:connect', @@ -192,6 +191,8 @@ class MCU_stepper: if old_sk is not None: mcu_pos = self.get_mcu_position() self._stepper_kinematics = sk + ffi_main, ffi_lib = chelper.get_ffi() + ffi_lib.stepcompress_set_stepper_kinematics(self._stepqueue, sk); self.set_trapq(self._trapq) self._set_mcu_position(mcu_pos) return old_sk @@ -253,12 +254,6 @@ class MCU_stepper: # Invoke callbacks for cb in cbs: cb(ret) - def generate_steps(self, flush_time): - # Generate steps - sk = self._stepper_kinematics - ret = self._itersolve_generate_steps(sk, self._stepqueue, flush_time) - if ret: - raise error("Internal error in stepcompress") def is_active_axis(self, axis): ffi_main, ffi_lib = chelper.get_ffi() a = axis.encode() @@ -281,8 +276,7 @@ def PrinterStepper(config, units_in_radians=False): rotation_dist, steps_per_rotation, step_pulse_duration, units_in_radians) # Register with helper modules - mods = ['stepper_enable', 'force_move', 'motion_report', 'motion_queuing'] - for mname in mods: + for mname in ['stepper_enable', 'force_move', 'motion_report']: m = printer.load_object(config, mname) m.register_stepper(config, mcu_stepper) return mcu_stepper From d6d85872896dc6f938cfd8d9c19af6d308e8e7b0 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Wed, 6 Aug 2025 13:27:24 -0400 Subject: [PATCH 20/23] motion_queuing: Remove clean_motion_queues() Merge the clean_motion_queues() code into the existing flush_motion_queues() code. Signed-off-by: Kevin O'Connor --- klippy/extras/motion_queuing.py | 6 ++++-- klippy/toolhead.py | 7 +++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/klippy/extras/motion_queuing.py b/klippy/extras/motion_queuing.py index c13b4970..a06b556e 100644 --- a/klippy/extras/motion_queuing.py +++ b/klippy/extras/motion_queuing.py @@ -53,10 +53,12 @@ class PrinterMotionQueuing: fcbs = list(self.flush_callbacks) fcbs.remove(callback) self.flush_callbacks = fcbs - def flush_motion_queues(self, must_flush_time, max_step_gen_time): + def flush_motion_queues(self, must_flush_time, max_step_gen_time, + trapq_free_time): # Invoke flush callbacks (if any) for cb in self.flush_callbacks: cb(must_flush_time, max_step_gen_time) + # Generate stepper movement and transmit for mcu, ss in self.steppersyncs: clock = max(0, mcu.print_time_to_clock(must_flush_time)) # Generate steps @@ -69,7 +71,7 @@ class PrinterMotionQueuing: if ret: raise mcu.error("Internal error in MCU '%s' stepcompress" % (mcu.get_name(),)) - def clean_motion_queues(self, trapq_free_time): + # Determine maximum history to keep clear_history_time = self.clear_history_time if self.is_debugoutput: clear_history_time = trapq_free_time - MOVE_HISTORY_EXPIRE diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 7f53ef44..21aeca3d 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -275,11 +275,10 @@ class ToolHead: sg_flush_want = min(flush_time + STEPCOMPRESS_FLUSH_TIME, self.print_time - self.kin_flush_delay) sg_flush_time = max(sg_flush_want, flush_time) - self.motion_queuing.flush_motion_queues(flush_time, sg_flush_time) + trapq_free_time = sg_flush_time - self.kin_flush_delay + self.motion_queuing.flush_motion_queues(flush_time, sg_flush_time, + trapq_free_time) self.min_restart_time = max(self.min_restart_time, sg_flush_time) - # Free trapq entries that are no longer needed - free_time = sg_flush_time - self.kin_flush_delay - self.motion_queuing.clean_motion_queues(free_time) self.last_flush_time = flush_time def _advance_move_time(self, next_print_time): pt_delay = self.kin_flush_delay + STEPCOMPRESS_FLUSH_TIME From edbfc6f8567b40b7c884f543a0b7cbe49ca05f24 Mon Sep 17 00:00:00 2001 From: Timofey Titovets Date: Thu, 14 Aug 2025 20:45:40 +0200 Subject: [PATCH 21/23] resonance_tester: replace missing M204 call Signed-off-by: Timofey Titovets --- klippy/extras/resonance_tester.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/klippy/extras/resonance_tester.py b/klippy/extras/resonance_tester.py index ff32dcac..c6171dc4 100644 --- a/klippy/extras/resonance_tester.py +++ b/klippy/extras/resonance_tester.py @@ -148,8 +148,7 @@ class ResonanceTestExecutor: last_v = last_t = last_accel = last_freq = 0. for next_t, accel, freq in test_seq: t_seg = next_t - last_t - toolhead.cmd_M204(self.gcode.create_gcode_command( - "M204", "M204", {"S": abs(accel)})) + toolhead.set_max_velocities(None, abs(accel), None, None) v = last_v + accel * t_seg abs_v = abs(v) if abs_v < 0.000001: @@ -182,8 +181,7 @@ class ResonanceTestExecutor: if last_v: d_decel = -.5 * last_v2 / old_max_accel decel_X, decel_Y = axis.get_point(d_decel) - toolhead.cmd_M204(self.gcode.create_gcode_command( - "M204", "M204", {"S": old_max_accel})) + toolhead.set_max_velocities(None, old_max_accel, None, None) toolhead.move([X + decel_X, Y + decel_Y] + tpos[2:], abs(last_v)) # Restore the original acceleration values self.gcode.run_script_from_command( From 78462cff4c1656893b61d97e30bfc20e0e9398c7 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 4 Aug 2025 11:35:33 -0400 Subject: [PATCH 22/23] docs: Remove "relative_reference_index" documentation from Bed_Mesh.md The "relative_reference_index" was deprecated on 20230619 and removed on 20240215. So, remove the last references from the documentation. Signed-off-by: Kevin O'Connor --- docs/Bed_Mesh.md | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/docs/Bed_Mesh.md b/docs/Bed_Mesh.md index 5df081d1..0859913f 100644 --- a/docs/Bed_Mesh.md +++ b/docs/Bed_Mesh.md @@ -292,33 +292,6 @@ probe_count: 5, 3 z-offset. Note that this coordinate must NOT be in a location specified as a `faulty_region` if a probe is necessary. -#### The deprecated relative_reference_index - -Existing configurations using the `relative_reference_index` option must be -updated to use the `zero_reference_position`. The response to the -[BED_MESH_OUTPUT PGP=1](#output) gcode command will include the (X, Y) -coordinate associated with the index; this position may be used as the value for -the `zero_reference_position`. The output will look similar to the following: - -``` -// bed_mesh: generated points -// Index | Tool Adjusted | Probe -// 0 | (1.0, 1.0) | (24.0, 6.0) -// 1 | (36.7, 1.0) | (59.7, 6.0) -// 2 | (72.3, 1.0) | (95.3, 6.0) -// 3 | (108.0, 1.0) | (131.0, 6.0) -... (additional generated points) -// bed_mesh: relative_reference_index 24 is (131.5, 108.0) -``` - -_Note: The above output is also printed in `klippy.log` during initialization._ - -Using the example above we see that the `relative_reference_index` is -printed along with its coordinate. Thus the `zero_reference_position` -is `131.5, 108`. - - - ### Faulty Regions It is possible for some areas of a bed to report inaccurate results when From d34d3b05b89b3bb3d56b61b0029a42af9704b2f2 Mon Sep 17 00:00:00 2001 From: BIGTREETECH <38851044+bigtreetech@users.noreply.github.com> Date: Fri, 15 Aug 2025 19:43:43 +0200 Subject: [PATCH 23/23] stm32: Add i2c2_PA7_PA6 and i2c3_PA7_PA6 for stm32g0 (#7007) Signed-off-by: Alan.Ma from BigTreeTech --- src/stm32/stm32f0_i2c.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/stm32/stm32f0_i2c.c b/src/stm32/stm32f0_i2c.c index 381fe8b4..61c848e4 100644 --- a/src/stm32/stm32f0_i2c.c +++ b/src/stm32/stm32f0_i2c.c @@ -44,11 +44,15 @@ struct i2c_info { DECL_CONSTANT_STR("BUS_PINS_i2c2_PB10_PB11", "PB10,PB11"); DECL_ENUMERATION("i2c_bus", "i2c2_PB13_PB14", 4); DECL_CONSTANT_STR("BUS_PINS_i2c2_PB13_PB14", "PB13,PB14"); + DECL_ENUMERATION("i2c_bus", "i2c2_PA7_PA6", 5); + DECL_CONSTANT_STR("BUS_PINS_i2c2_PA7_PA6", "PA7,PA6"); #ifdef I2C3 - DECL_ENUMERATION("i2c_bus", "i2c3_PB3_PB4", 5); + DECL_ENUMERATION("i2c_bus", "i2c3_PB3_PB4", 6); DECL_CONSTANT_STR("BUS_PINS_i2c3_PB3_PB4", "PB3,PB4"); - DECL_ENUMERATION("i2c_bus", "i2c3_PC0_PC1", 6); + DECL_ENUMERATION("i2c_bus", "i2c3_PC0_PC1", 7); DECL_CONSTANT_STR("BUS_PINS_i2c3_PC0_PC1", "PC0,PC1"); + DECL_ENUMERATION("i2c_bus", "i2c3_PA7_PA6", 8); + DECL_CONSTANT_STR("BUS_PINS_i2c3_PA7_PA6", "PA7,PA6"); #endif #elif CONFIG_MACH_STM32L4 DECL_ENUMERATION("i2c_bus", "i2c1_PB6_PB7", 0); @@ -105,9 +109,11 @@ static const struct i2c_info i2c_bus[] = { { I2C1, GPIO('A', 9), GPIO('A', 10), GPIO_FUNCTION(6) }, { I2C2, GPIO('B', 10), GPIO('B', 11), GPIO_FUNCTION(6) }, { I2C2, GPIO('B', 13), GPIO('B', 14), GPIO_FUNCTION(6) }, + { I2C2, GPIO('A', 7), GPIO('A', 6), GPIO_FUNCTION(8) }, #ifdef I2C3 { I2C3, GPIO('B', 3), GPIO('B', 4), GPIO_FUNCTION(6) }, { I2C3, GPIO('C', 0), GPIO('C', 1), GPIO_FUNCTION(6) }, + { I2C3, GPIO('A', 7), GPIO('A', 6), GPIO_FUNCTION(9) }, #endif #elif CONFIG_MACH_STM32L4 { I2C1, GPIO('B', 6), GPIO('B', 7), GPIO_FUNCTION(4) },