diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 83de9609..ca21bcf2 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -4944,8 +4944,8 @@ detection_length: 7.0 # a state change on the switch_pin # Default is 7 mm. extruder: -# The name of the extruder section this sensor is associated with. -# This parameter must be provided. +# The name of the extruder or extruder_stepper section this sensor +# is associated with. This parameter must be provided. switch_pin: #pause_on_runout: #runout_gcode: diff --git a/docs/FAQ.md b/docs/FAQ.md index 417fb1d4..7c8214b3 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -98,17 +98,16 @@ bootloaders. ## Can I run Klipper on something other than a Raspberry Pi 3? -The recommended hardware is a Raspberry Pi 2, Raspberry Pi 3, or -Raspberry Pi 4. +The recommended hardware is a Raspberry Pi Zero2w, Raspberry Pi 3, +Raspberry Pi 4 or Raspberry Pi 5. Klipper will also run on other SBC +devices as well as x86 hardware, as described below. -Klipper will run on a Raspberry Pi 1 and on the Raspberry Pi Zero, but -these boards don't have enough processing power to run OctoPrint +Klipper will run on a Raspberry Pi 1, 2 and on the Raspberry Pi Zero1, +but these boards don't have enough processing power to run Klipper well. It is common for print stalls to occur on these slower machines -when printing directly from OctoPrint. (The printer may move faster -than OctoPrint can send movement commands.) If you wish to run on one -one of these slower boards anyway, consider using the "virtual_sdcard" -feature when printing (see -[config reference](Config_Reference.md#virtual_sdcard) for details). +when printing (The printer may move faster than Klipper can send +movement commands.) It is not reccomended to run Kliper on these older +machines. For running on the Beaglebone, see the [Beaglebone specific installation instructions](Beaglebone.md). diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py index 6ed6ed5b..59971c1c 100644 --- a/klippy/chelper/__init__.py +++ b/klippy/chelper/__init__.py @@ -56,6 +56,8 @@ defs_stepcompress = """ , uint64_t start_clock, uint64_t end_clock); void stepcompress_set_stepper_kinematics(struct stepcompress *sc , struct stepper_kinematics *sk); + struct stepper_kinematics *stepcompress_get_stepper_kinematics( + struct stepcompress *sc); """ defs_steppersync = """ @@ -76,11 +78,14 @@ defs_itersolve = """ int32_t itersolve_is_active_axis(struct stepper_kinematics *sk, char axis); void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq , double step_dist); + struct trapq *itersolve_get_trapq(struct stepper_kinematics *sk); double itersolve_calc_position_from_coord(struct stepper_kinematics *sk , double x, double y, double z); void itersolve_set_position(struct stepper_kinematics *sk , double x, double y, double z); double itersolve_get_commanded_pos(struct stepper_kinematics *sk); + double itersolve_get_gen_steps_pre_active(struct stepper_kinematics *sk); + double itersolve_get_gen_steps_post_active(struct stepper_kinematics *sk); """ defs_trapq = """ @@ -157,8 +162,6 @@ defs_kin_extruder = """ """ defs_kin_shaper = """ - double input_shaper_get_step_generation_window( - struct stepper_kinematics *sk); int input_shaper_set_shaper_params(struct stepper_kinematics *sk, char axis , int n, double a[], double t[]); int input_shaper_set_sk(struct stepper_kinematics *sk @@ -274,6 +277,28 @@ def do_build_code(cmd): logging.error(msg) raise Exception(msg) +# Build the main c_helper.so c code library +def check_build_c_library(): + srcdir = os.path.dirname(os.path.realpath(__file__)) + srcfiles = get_abs_files(srcdir, SOURCE_FILES) + ofiles = get_abs_files(srcdir, OTHER_FILES) + destlib = get_abs_files(srcdir, [DEST_LIB])[0] + if not check_build_code(srcfiles+ofiles+[__file__], destlib): + # Code already built + return destlib + # Select command line options + if check_gcc_option(SSE_FLAGS): + cmd = "%s %s %s" % (GCC_CMD, SSE_FLAGS, COMPILE_ARGS) + else: + cmd = "%s %s" % (GCC_CMD, COMPILE_ARGS) + # Invoke compiler + logging.info("Building C code module %s", DEST_LIB) + tempdestlib = get_abs_files(srcdir, ["_temp_" + DEST_LIB])[0] + do_build_code(cmd % (tempdestlib, ' '.join(srcfiles))) + # Rename from temporary file to final file name + os.rename(tempdestlib, destlib) + return destlib + FFI_main = None FFI_lib = None pyhelper_logging_callback = None @@ -286,17 +311,9 @@ def logging_callback(msg): def get_ffi(): global FFI_main, FFI_lib, pyhelper_logging_callback if FFI_lib is None: - srcdir = os.path.dirname(os.path.realpath(__file__)) - srcfiles = get_abs_files(srcdir, SOURCE_FILES) - ofiles = get_abs_files(srcdir, OTHER_FILES) - destlib = get_abs_files(srcdir, [DEST_LIB])[0] - if check_build_code(srcfiles+ofiles+[__file__], destlib): - if check_gcc_option(SSE_FLAGS): - cmd = "%s %s %s" % (GCC_CMD, SSE_FLAGS, COMPILE_ARGS) - else: - cmd = "%s %s" % (GCC_CMD, COMPILE_ARGS) - logging.info("Building C code module %s", DEST_LIB) - do_build_code(cmd % (destlib, ' '.join(srcfiles))) + # Check if library needs to be built, and build if so + destlib = check_build_c_library() + # Open library FFI_main = cffi.FFI() for d in defs_all: FFI_main.cdef(d) diff --git a/klippy/chelper/itersolve.c b/klippy/chelper/itersolve.c index eba1deef..9b120624 100644 --- a/klippy/chelper/itersolve.c +++ b/klippy/chelper/itersolve.c @@ -248,6 +248,12 @@ itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq sk->step_dist = step_dist; } +struct trapq * __visible +itersolve_get_trapq(struct stepper_kinematics *sk) +{ + return sk->tq; +} + double __visible itersolve_calc_position_from_coord(struct stepper_kinematics *sk , double x, double y, double z) @@ -273,3 +279,15 @@ itersolve_get_commanded_pos(struct stepper_kinematics *sk) { return sk->commanded_pos; } + +double __visible +itersolve_get_gen_steps_pre_active(struct stepper_kinematics *sk) +{ + return sk->gen_steps_pre_active; +} + +double __visible +itersolve_get_gen_steps_post_active(struct stepper_kinematics *sk) +{ + return sk->gen_steps_post_active; +} diff --git a/klippy/chelper/itersolve.h b/klippy/chelper/itersolve.h index e2e46ebe..50a30f7d 100644 --- a/klippy/chelper/itersolve.h +++ b/klippy/chelper/itersolve.h @@ -31,10 +31,13 @@ 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 , double step_dist); +struct trapq *itersolve_get_trapq(struct stepper_kinematics *sk); double itersolve_calc_position_from_coord(struct stepper_kinematics *sk , double x, double y, double z); void itersolve_set_position(struct stepper_kinematics *sk , double x, double y, double z); double itersolve_get_commanded_pos(struct stepper_kinematics *sk); +double itersolve_get_gen_steps_pre_active(struct stepper_kinematics *sk); +double itersolve_get_gen_steps_post_active(struct stepper_kinematics *sk); #endif // itersolve.h diff --git a/klippy/chelper/kin_shaper.c b/klippy/chelper/kin_shaper.c index 42d572d0..d5138ff0 100644 --- a/klippy/chelper/kin_shaper.c +++ b/klippy/chelper/kin_shaper.c @@ -239,14 +239,6 @@ input_shaper_set_shaper_params(struct stepper_kinematics *sk, char axis return status; } -double __visible -input_shaper_get_step_generation_window(struct stepper_kinematics *sk) -{ - struct input_shaper *is = container_of(sk, struct input_shaper, sk); - return is->sk.gen_steps_pre_active > is->sk.gen_steps_post_active - ? is->sk.gen_steps_pre_active : is->sk.gen_steps_post_active; -} - struct stepper_kinematics * __visible input_shaper_alloc(void) { diff --git a/klippy/chelper/pyhelper.c b/klippy/chelper/pyhelper.c index 60c6de9b..8d4e4ee8 100644 --- a/klippy/chelper/pyhelper.c +++ b/klippy/chelper/pyhelper.c @@ -10,7 +10,6 @@ #include // fprintf #include // strerror #include // struct timespec -#include // PR_SET_NAME #include // prctl #include "compiler.h" // __visible #include "pyhelper.h" // get_monotonic diff --git a/klippy/chelper/stepcompress.c b/klippy/chelper/stepcompress.c index 2889570d..52dd4077 100644 --- a/klippy/chelper/stepcompress.c +++ b/klippy/chelper/stepcompress.c @@ -674,6 +674,13 @@ stepcompress_set_stepper_kinematics(struct stepcompress *sc sc->sk = sk; } +// Report current stepper_kinematics +struct stepper_kinematics * __visible +stepcompress_get_stepper_kinematics(struct stepcompress *sc) +{ + return sc->sk; +} + // Generate steps (via itersolve) and flush int32_t stepcompress_generate_steps(struct stepcompress *sc, double gen_steps_time diff --git a/klippy/chelper/stepcompress.h b/klippy/chelper/stepcompress.h index e21c4fd9..7ca0f2e4 100644 --- a/klippy/chelper/stepcompress.h +++ b/klippy/chelper/stepcompress.h @@ -41,6 +41,8 @@ int stepcompress_extract_old(struct stepcompress *sc struct stepper_kinematics; void stepcompress_set_stepper_kinematics(struct stepcompress *sc , struct stepper_kinematics *sk); +struct stepper_kinematics *stepcompress_get_stepper_kinematics( + struct stepcompress *sc); int32_t stepcompress_generate_steps(struct stepcompress *sc , double gen_steps_time , uint64_t flush_clock); diff --git a/klippy/extras/bme280.py b/klippy/extras/bme280.py index 1c26bbee..284380d2 100644 --- a/klippy/extras/bme280.py +++ b/klippy/extras/bme280.py @@ -284,7 +284,7 @@ class BME280: self.chip_type, self.i2c.i2c_address)) # Reset chip - self.write_register('RESET', [RESET_CHIP_VALUE], wait=True) + self.write_register('RESET', [RESET_CHIP_VALUE]) self.reactor.pause(self.reactor.monotonic() + .5) # Make sure non-volatile memory has been copied to registers @@ -394,7 +394,7 @@ class BME280: self.write_register('CTRL_HUM', self.os_hum) # Enter normal (periodic) mode meas = self.os_temp << 5 | self.os_pres << 2 | MODE_PERIODIC - self.write_register('CTRL_MEAS', meas, wait=True) + self.write_register('CTRL_MEAS', meas) if self.chip_type == 'BME680': self.write_register('CONFIG', self.iir_filter << 2) @@ -528,7 +528,7 @@ class BME280: # Enter forced mode meas = self.os_temp << 5 | self.os_pres << 2 | MODE - self.write_register('CTRL_MEAS', meas, wait=True) + self.write_register('CTRL_MEAS', meas) max_sample_time = self.max_sample_time if run_gas: max_sample_time += self.gas_heat_duration / 1000 @@ -776,15 +776,12 @@ class BME280: params = self.i2c.i2c_read(regs, read_len) return bytearray(params['response']) - def write_register(self, reg_name, data, wait = False): + def write_register(self, reg_name, data): if type(data) is not list: data = [data] reg = self.chip_registers[reg_name] data.insert(0, reg) - if not wait: - self.i2c.i2c_write(data) - else: - self.i2c.i2c_write_wait_ack(data) + self.i2c.i2c_write(data) def get_status(self, eventtime): data = { diff --git a/klippy/extras/bus.py b/klippy/extras/bus.py index 4121c1c8..9fb46639 100644 --- a/klippy/extras/bus.py +++ b/klippy/extras/bus.py @@ -180,6 +180,13 @@ class MCU_I2C: self.cmd_queue = self.mcu.alloc_command_queue() self.mcu.register_config_callback(self.build_config) self.i2c_write_cmd = self.i2c_read_cmd = None + printer = self.mcu.get_printer() + printer.register_event_handler("klippy:connect", self._handle_connect) + # backward support i2c_write inside the init section + self._to_write = [] + def _handle_connect(self): + for data in self._to_write: + self.i2c_write(data) def get_oid(self): return self.oid def get_mcu(self): @@ -207,16 +214,10 @@ class MCU_I2C: cq=self.cmd_queue) def i2c_write(self, data, minclock=0, reqclock=0): if self.i2c_write_cmd is None: - # Send setup message via mcu initialization - data_msg = "".join(["%02x" % (x,) for x in data]) - self.mcu.add_config_cmd("i2c_write oid=%d data=%s" % ( - self.oid, data_msg), is_init=True) + self._to_write.append(data) return - self.i2c_write_cmd.send([self.oid, data], - minclock=minclock, reqclock=reqclock) - def i2c_write_wait_ack(self, data, minclock=0, reqclock=0): self.i2c_write_cmd.send_wait_ack([self.oid, data], - minclock=minclock, reqclock=reqclock) + minclock=minclock, reqclock=reqclock) def i2c_read(self, write, read_len, retry=True): return self.i2c_read_cmd.send([self.oid, write, read_len], retry) diff --git a/klippy/extras/display/display.py b/klippy/extras/display/display.py index e9ba31d6..cc33bc15 100644 --- a/klippy/extras/display/display.py +++ b/klippy/extras/display/display.py @@ -236,6 +236,8 @@ class PrinterLCD: except: logging.exception("Error during display screen update") self.lcd_chip.flush() + if self.redraw_request_pending: + return self.redraw_time return eventtime + REDRAW_TIME def request_redraw(self): if self.redraw_request_pending: diff --git a/klippy/extras/extruder_stepper.py b/klippy/extras/extruder_stepper.py index 4ac5289f..28293a3c 100644 --- a/klippy/extras/extruder_stepper.py +++ b/klippy/extras/extruder_stepper.py @@ -15,6 +15,8 @@ class PrinterExtruderStepper: self.handle_connect) def handle_connect(self): self.extruder_stepper.sync_to_extruder(self.extruder_name) + def find_past_position(self, print_time): + return self.extruder_stepper.find_past_position(print_time) def get_status(self, eventtime): return self.extruder_stepper.get_status(eventtime) diff --git a/klippy/extras/force_move.py b/klippy/extras/force_move.py index 00f835f5..277c68e3 100644 --- a/klippy/extras/force_move.py +++ b/klippy/extras/force_move.py @@ -1,6 +1,6 @@ # Utility for manually moving a stepper for diagnostic purposes # -# Copyright (C) 2018-2019 Kevin O'Connor +# Copyright (C) 2018-2025 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging @@ -10,7 +10,6 @@ BUZZ_DISTANCE = 1. BUZZ_VELOCITY = BUZZ_DISTANCE / .250 BUZZ_RADIANS_DISTANCE = math.radians(1.) BUZZ_RADIANS_VELOCITY = BUZZ_RADIANS_DISTANCE / .250 -STALL_TIME = 0.100 # Calculate a move's accel_t, cruise_t, and cruise_v def calc_move_time(dist, speed, accel): @@ -56,24 +55,16 @@ class ForceMove: raise self.printer.config_error("Unknown stepper %s" % (name,)) return self.steppers[name] def _force_enable(self, stepper): - toolhead = self.printer.lookup_object('toolhead') - print_time = toolhead.get_last_move_time() + stepper_name = stepper.get_name() stepper_enable = self.printer.lookup_object('stepper_enable') - enable = stepper_enable.lookup_enable(stepper.get_name()) - was_enable = enable.is_motor_enabled() - if not was_enable: - enable.motor_enable(print_time) - toolhead.dwell(STALL_TIME) - return was_enable - def _restore_enable(self, stepper, was_enable): - if not was_enable: - toolhead = self.printer.lookup_object('toolhead') - toolhead.dwell(STALL_TIME) - print_time = toolhead.get_last_move_time() - stepper_enable = self.printer.lookup_object('stepper_enable') - enable = stepper_enable.lookup_enable(stepper.get_name()) - enable.motor_disable(print_time) - toolhead.dwell(STALL_TIME) + did_enable = stepper_enable.set_motors_enable([stepper_name], True) + return did_enable + def _restore_enable(self, stepper, did_enable): + if not did_enable: + return + stepper_name = stepper.get_name() + stepper_enable = self.printer.lookup_object('stepper_enable') + stepper_enable.set_motors_enable([stepper_name], False) def manual_move(self, stepper, dist, speed, accel=0.): toolhead = self.printer.lookup_object('toolhead') toolhead.flush_step_generation() @@ -85,7 +76,7 @@ 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 - toolhead.note_mcu_movequeue_activity(print_time) + self.motion_queuing.note_mcu_movequeue_activity(print_time) toolhead.dwell(accel_t + cruise_t + accel_t) toolhead.flush_step_generation() stepper.set_trapq(prev_trapq) @@ -100,7 +91,7 @@ class ForceMove: def cmd_STEPPER_BUZZ(self, gcmd): stepper = self._lookup_stepper(gcmd) logging.info("Stepper buzz %s", stepper.get_name()) - was_enable = self._force_enable(stepper) + did_enable = self._force_enable(stepper) toolhead = self.printer.lookup_object('toolhead') dist, speed = BUZZ_DISTANCE, BUZZ_VELOCITY if stepper.units_in_radians(): @@ -110,7 +101,7 @@ class ForceMove: toolhead.dwell(.050) self.manual_move(stepper, -dist, speed) toolhead.dwell(.450) - self._restore_enable(stepper, was_enable) + self._restore_enable(stepper, did_enable) cmd_FORCE_MOVE_help = "Manually move a stepper; invalidates kinematics" def cmd_FORCE_MOVE(self, gcmd): stepper = self._lookup_stepper(gcmd) diff --git a/klippy/extras/gcode_move.py b/klippy/extras/gcode_move.py index 94a0ce42..655b8108 100644 --- a/klippy/extras/gcode_move.py +++ b/klippy/extras/gcode_move.py @@ -92,7 +92,7 @@ class GCodeMove: def _get_gcode_position(self): p = [lp - bp for lp, bp in zip(self.last_position, self.base_position)] p[3] /= self.extrude_factor - return p + return p[:4] def _get_gcode_speed(self): return self.speed / self.speed_factor def _get_gcode_speed_override(self): @@ -107,7 +107,7 @@ class GCodeMove: 'absolute_extrude': self.absolute_extrude, 'homing_origin': self.Coord(*self.homing_position[:4]), 'position': self.Coord(*self.last_position[:4]), - 'gcode_position': self.Coord(*move_position[:4]), + 'gcode_position': self.Coord(*move_position), } def reset_last_position(self): if self.is_printer_ready: diff --git a/klippy/extras/heaters.py b/klippy/extras/heaters.py index fce3c49a..fcec44fc 100644 --- a/klippy/extras/heaters.py +++ b/klippy/extras/heaters.py @@ -75,7 +75,8 @@ class Heater: # No significant change in value - can suppress update return pwm_time = read_time + self.pwm_delay - self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME + self.next_pwm_time = (pwm_time + MAX_HEAT_TIME + - (3. * self.pwm_delay + 0.001)) self.last_pwm_value = value self.mcu_pwm.set_pwm(pwm_time, value) #logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])", diff --git a/klippy/extras/input_shaper.py b/klippy/extras/input_shaper.py index 67a287cd..cb9027d9 100644 --- a/klippy/extras/input_shaper.py +++ b/klippy/extras/input_shaper.py @@ -146,12 +146,8 @@ class InputShaper: is_sk = self._get_input_shaper_stepper_kinematics(s) if is_sk is None: continue - old_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk) + self.toolhead.flush_step_generation() ffi_lib.input_shaper_update_sk(is_sk) - new_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk) - if old_delay != new_delay: - self.toolhead.note_step_generation_scan_time(new_delay, - old_delay) def _update_input_shaping(self, error=None): self.toolhead.flush_step_generation() ffi_main, ffi_lib = chelper.get_ffi() @@ -163,16 +159,11 @@ class InputShaper: is_sk = self._get_input_shaper_stepper_kinematics(s) if is_sk is None: continue - old_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk) for shaper in self.shapers: if shaper in failed_shapers: continue if not shaper.set_shaper_kinematics(is_sk): failed_shapers.append(shaper) - new_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk) - if old_delay != new_delay: - self.toolhead.note_step_generation_scan_time(new_delay, - old_delay) if failed_shapers: error = error or self.printer.command_error raise error("Failed to configure shaper(s) %s with given parameters" diff --git a/klippy/extras/manual_stepper.py b/klippy/extras/manual_stepper.py index 05899f58..9c775567 100644 --- a/klippy/extras/manual_stepper.py +++ b/klippy/extras/manual_stepper.py @@ -22,6 +22,7 @@ class ManualStepper: self.velocity = config.getfloat('velocity', 5., above=0.) self.accel = self.homing_accel = config.getfloat('accel', 0., minval=0.) self.next_cmd_time = 0. + self.commanded_pos = 0. self.pos_min = config.getfloat('position_min', None) self.pos_max = config.getfloat('position_max', None) # Setup iterative solver @@ -48,21 +49,16 @@ class ManualStepper: else: self.next_cmd_time = print_time def do_enable(self, enable): - self.sync_print_time() + stepper_names = [s.get_name() for s in self.steppers] stepper_enable = self.printer.lookup_object('stepper_enable') - if enable: - for s in self.steppers: - se = stepper_enable.lookup_enable(s.get_name()) - se.motor_enable(self.next_cmd_time) - else: - for s in self.steppers: - se = stepper_enable.lookup_enable(s.get_name()) - se.motor_disable(self.next_cmd_time) - self.sync_print_time() + stepper_enable.set_motors_enable(stepper_names, enable) def do_set_position(self, setpos): - self.rail.set_position([setpos, 0., 0.]) + toolhead = self.printer.lookup_object('toolhead') + toolhead.flush_step_generation() + self.commanded_pos = setpos + self.rail.set_position([self.commanded_pos, 0., 0.]) def _submit_move(self, movetime, movepos, speed, accel): - cp = self.rail.get_commanded_position() + cp = self.commanded_pos dist = movepos - cp axis_r, accel_t, cruise_t, cruise_v = force_move.calc_move_time( dist, speed, accel) @@ -70,13 +66,13 @@ class ManualStepper: accel_t, cruise_t, accel_t, cp, 0., 0., axis_r, 0., 0., 0., cruise_v, accel) + self.commanded_pos = movepos return movetime + accel_t + cruise_t + accel_t def do_move(self, movepos, speed, accel, sync=True): self.sync_print_time() self.next_cmd_time = self._submit_move(self.next_cmd_time, movepos, speed, accel) - toolhead = self.printer.lookup_object('toolhead') - toolhead.note_mcu_movequeue_activity(self.next_cmd_time) + self.motion_queuing.note_mcu_movequeue_activity(self.next_cmd_time) if sync: self.sync_print_time() def do_homing_move(self, movepos, speed, accel, triggered, check_trigger): @@ -149,7 +145,7 @@ class ManualStepper: self.instant_corner_v = instant_corner_v self.gaxis_limit_velocity = limit_velocity self.gaxis_limit_accel = limit_accel - toolhead.add_extra_axis(self, self.get_position()[0]) + toolhead.add_extra_axis(self, self.commanded_pos) def process_move(self, print_time, move, ea_index): axis_r = move.axes_r[ea_index] start_pos = move.start_pos[ea_index] @@ -161,6 +157,7 @@ class ManualStepper: start_pos, 0., 0., 1., 0., 0., start_v, cruise_v, accel) + self.commanded_pos = move.end_pos[ea_index] def check_move(self, move, ea_index): # Check move is in bounds movepos = move.end_pos[ea_index] @@ -185,9 +182,10 @@ class ManualStepper: return self.trapq # Toolhead wrappers to support homing def flush_step_generation(self): - self.sync_print_time() + toolhead = self.printer.lookup_object('toolhead') + toolhead.flush_step_generation() def get_position(self): - return [self.rail.get_commanded_position(), 0., 0., 0.] + return [self.commanded_pos, 0., 0., 0.] def set_position(self, newpos, homing_axes=""): self.do_set_position(newpos[0]) def get_last_move_time(self): @@ -198,15 +196,16 @@ class ManualStepper: def drip_move(self, newpos, speed, drip_completion): # Submit move to trapq self.sync_print_time() - maxtime = self._submit_move(self.next_cmd_time, newpos[0], - speed, self.homing_accel) + start_time = self.next_cmd_time + end_time = self._submit_move(start_time, newpos[0], + speed, self.homing_accel) # Drip updates to motors - toolhead = self.printer.lookup_object('toolhead') - toolhead.drip_update_time(maxtime, drip_completion) + self.motion_queuing.drip_update_time(start_time, end_time, + drip_completion) # Clear trapq of any remaining parts of movement reactor = self.printer.get_reactor() self.motion_queuing.wipe_trapq(self.trapq) - self.rail.set_position([newpos[0], 0., 0.]) + self.rail.set_position([self.commanded_pos, 0., 0.]) self.sync_print_time() def get_kinematics(self): return self diff --git a/klippy/extras/motion_queuing.py b/klippy/extras/motion_queuing.py index a06b556e..9d2ef513 100644 --- a/klippy/extras/motion_queuing.py +++ b/klippy/extras/motion_queuing.py @@ -6,23 +6,53 @@ import logging import chelper +BGFLUSH_LOW_TIME = 0.200 +BGFLUSH_BATCH_TIME = 0.200 +BGFLUSH_EXTRA_TIME = 0.250 MOVE_HISTORY_EXPIRE = 30. +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 + +DRIP_SEGMENT_TIME = 0.050 +DRIP_TIME = 0.100 class PrinterMotionQueuing: def __init__(self, config): - self.printer = config.get_printer() + self.printer = printer = config.get_printer() + self.reactor = printer.get_reactor() + # Low level C allocations self.trapqs = [] self.stepcompress = [] self.steppersyncs = [] - self.flush_callbacks = [] + # Low-level C flushing calls 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 + # Flush notification callbacks + self.flush_callbacks = [] + # History expiration self.clear_history_time = 0. - is_debug = self.printer.get_start_args().get('debugoutput') is not None - self.is_debugoutput = is_debug + # Flush tracking + self.flush_timer = self.reactor.register_timer(self._flush_handler) + self.do_kick_flush_timer = True + self.last_flush_time = self.last_step_gen_time = 0. + self.need_flush_time = self.need_step_gen_time = 0. + self.check_flush_lookahead_cb = (lambda e: None) + # MCU tracking + self.all_mcus = [m for n, m in printer.lookup_objects(module='mcu')] + self.mcu = self.all_mcus[0] + self.can_pause = True + if self.mcu.is_fileoutput(): + self.can_pause = False + # Kinematic step generation scan window time tracking + self.need_calc_kin_flush_delay = True + self.kin_flush_delay = SDS_CHECK_TIME + # Register handlers + printer.register_event_handler("klippy:shutdown", self._handle_shutdown) def allocate_trapq(self): ffi_main, ffi_lib = chelper.get_ffi() trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) @@ -53,8 +83,7 @@ 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, - trapq_free_time): + 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, max_step_gen_time) @@ -72,8 +101,9 @@ class PrinterMotionQueuing: raise mcu.error("Internal error in MCU '%s' stepcompress" % (mcu.get_name(),)) # Determine maximum history to keep + trapq_free_time = max_step_gen_time - self.kin_flush_delay clear_history_time = self.clear_history_time - if self.is_debugoutput: + if not self.can_pause: 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: @@ -85,16 +115,127 @@ class PrinterMotionQueuing: self.steppersync_history_expire(ss, clock) 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.) + self.trapq_finalize_moves(trapq, self.reactor.NEVER, 0.) 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) + # Hack to globally invoke mcu check_active() + for m in self.all_mcus: + m.check_active(self.last_step_gen_time, eventtime) + # Calculate history expiration + est_print_time = self.mcu.estimated_print_time(eventtime) self.clear_history_time = est_print_time - MOVE_HISTORY_EXPIRE return False, "" + # Kinematic step generation scan window time tracking + def get_kin_flush_delay(self): + return self.kin_flush_delay + def _calc_kin_flush_delay(self): + self.need_calc_kin_flush_delay = False + ffi_main, ffi_lib = chelper.get_ffi() + kin_flush_delay = SDS_CHECK_TIME + for mcu, sc in self.stepcompress: + sk = ffi_lib.stepcompress_get_stepper_kinematics(sc) + if sk == ffi_main.NULL: + continue + trapq = ffi_lib.itersolve_get_trapq(sk) + if trapq == ffi_main.NULL: + continue + pre_active = ffi_lib.itersolve_get_gen_steps_pre_active(sk) + post_active = ffi_lib.itersolve_get_gen_steps_post_active(sk) + kin_flush_delay = max(kin_flush_delay, pre_active, post_active) + self.kin_flush_delay = kin_flush_delay + # Flush tracking + def _handle_shutdown(self): + self.can_pause = False + def setup_lookahead_flush_callback(self, check_flush_lookahead_cb): + self.check_flush_lookahead_cb = check_flush_lookahead_cb + def advance_flush_time(self, target_time, lazy_target=False): + want_flush_time = want_step_gen_time = target_time + if lazy_target: + # Account for step gen scan windows and optimize step compression + want_step_gen_time -= self.kin_flush_delay + want_flush_time = want_step_gen_time - STEPCOMPRESS_FLUSH_TIME + want_flush_time = max(want_flush_time, self.last_flush_time) + flush_time = self.last_flush_time + if want_flush_time > flush_time + 10. * MOVE_BATCH_TIME: + # Use closer startup time when coming out of idle state + curtime = self.reactor.monotonic() + est_print_time = self.mcu.estimated_print_time(curtime) + flush_time = max(flush_time, est_print_time) + while 1: + flush_time = min(flush_time + MOVE_BATCH_TIME, want_flush_time) + # Generate steps via itersolve + want_sg_wave = min(flush_time + STEPCOMPRESS_FLUSH_TIME, + want_step_gen_time) + step_gen_time = max(self.last_step_gen_time, want_sg_wave, + flush_time) + self._flush_motion_queues(flush_time, step_gen_time) + self.last_flush_time = flush_time + self.last_step_gen_time = step_gen_time + if flush_time >= want_flush_time: + break + def flush_all_steps(self): + self.need_calc_kin_flush_delay = True + self.advance_flush_time(self.need_step_gen_time) + def calc_step_gen_restart(self, est_print_time): + if self.need_calc_kin_flush_delay: + self._calc_kin_flush_delay() + kin_time = max(est_print_time + MIN_KIN_TIME, self.last_step_gen_time) + return kin_time + self.kin_flush_delay + def _flush_handler(self, eventtime): + try: + # Check if flushing is done via lookahead queue + ret = self.check_flush_lookahead_cb(eventtime) + if ret is not None: + return ret + # Flush motion queues + est_print_time = self.mcu.estimated_print_time(eventtime) + while 1: + end_flush = self.need_flush_time + BGFLUSH_EXTRA_TIME + if self.last_flush_time >= end_flush: + self.do_kick_flush_timer = True + return self.reactor.NEVER + buffer_time = self.last_flush_time - est_print_time + if buffer_time > BGFLUSH_LOW_TIME: + return eventtime + buffer_time - BGFLUSH_LOW_TIME + ftime = est_print_time + BGFLUSH_LOW_TIME + BGFLUSH_BATCH_TIME + self.advance_flush_time(min(end_flush, ftime)) + except: + logging.exception("Exception in flush_handler") + self.printer.invoke_shutdown("Exception in flush_handler") + return self.reactor.NEVER + def note_mcu_movequeue_activity(self, mq_time, is_step_gen=True): + if is_step_gen: + mq_time += self.kin_flush_delay + self.need_step_gen_time = max(self.need_step_gen_time, mq_time) + self.need_flush_time = max(self.need_flush_time, mq_time) + if self.do_kick_flush_timer: + self.do_kick_flush_timer = False + self.reactor.update_timer(self.flush_timer, self.reactor.NOW) + def drip_update_time(self, start_time, end_time, drip_completion): + # Disable background flushing from timer + self.reactor.update_timer(self.flush_timer, self.reactor.NEVER) + self.do_kick_flush_timer = False + # Flush in segments until drip_completion signal + flush_delay = DRIP_TIME + STEPCOMPRESS_FLUSH_TIME + self.kin_flush_delay + flush_time = start_time + while flush_time < end_time: + if drip_completion.test(): + break + curtime = self.reactor.monotonic() + est_print_time = self.mcu.estimated_print_time(curtime) + wait_time = flush_time - est_print_time - flush_delay + if wait_time > 0. and self.can_pause: + # Pause before sending more steps + drip_completion.wait(curtime + wait_time) + continue + flush_time = min(flush_time + DRIP_SEGMENT_TIME, end_time) + self.note_mcu_movequeue_activity(flush_time) + self.advance_flush_time(flush_time, lazy_target=True) + # Restore background flushing + self.reactor.update_timer(self.flush_timer, self.reactor.NOW) + self.advance_flush_time(self.need_step_gen_time) def load_config(config): return PrinterMotionQueuing(config) diff --git a/klippy/extras/output_pin.py b/klippy/extras/output_pin.py index 63862d97..a5129299 100644 --- a/klippy/extras/output_pin.py +++ b/klippy/extras/output_pin.py @@ -20,8 +20,8 @@ class GCodeRequestQueue: self.rqueue = [] self.next_min_flush_time = 0. self.toolhead = None - motion_queuing = printer.load_object(config, 'motion_queuing') - motion_queuing.register_flush_callback(self._flush_notification) + self.motion_queuing = printer.load_object(config, 'motion_queuing') + self.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') @@ -51,11 +51,12 @@ 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, - is_step_gen=False) + self.motion_queuing.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, is_step_gen=False) + self.motion_queuing.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 6d401c0b..d9e72c5e 100644 --- a/klippy/extras/pwm_tool.py +++ b/klippy/extras/pwm_tool.py @@ -16,8 +16,8 @@ class MCU_queued_pwm: self._max_duration = 2. 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) + self._motion_queuing = printer.load_object(config, 'motion_queuing') + self._stepqueue = self._motion_queuing.allocate_stepcompress(mcu, oid) ffi_main, ffi_lib = chelper.get_ffi() self._stepcompress_queue_mq_msg = ffi_lib.stepcompress_queue_mq_msg mcu.register_config_callback(self._build_config) @@ -62,8 +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: - motion_queuing = printer.lookup_object('motion_queuing') - motion_queuing.register_flush_callback(self._flush_notification) + self._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 @@ -116,8 +116,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, - is_step_gen=False) + self._motion_queuing.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/extras/sht3x.py b/klippy/extras/sht3x.py index c76ceb76..c4fb2296 100644 --- a/klippy/extras/sht3x.py +++ b/klippy/extras/sht3x.py @@ -80,10 +80,10 @@ class SHT3X: def _init_sht3x(self): # Device Soft Reset - self.i2c.i2c_write_wait_ack(SHT3X_CMD['OTHER']['BREAK']) + self.i2c.i2c_write(SHT3X_CMD['OTHER']['BREAK']) # Break takes ~ 1ms self.reactor.pause(self.reactor.monotonic() + .0015) - self.i2c.i2c_write_wait_ack(SHT3X_CMD['OTHER']['SOFTRESET']) + self.i2c.i2c_write(SHT3X_CMD['OTHER']['SOFTRESET']) # Wait <=1.5ms after reset self.reactor.pause(self.reactor.monotonic() + .0015) @@ -97,7 +97,7 @@ class SHT3X: logging.warning("sht3x: Reading status - checksum error!") # Enable periodic mode - self.i2c.i2c_write_wait_ack( + self.i2c.i2c_write( SHT3X_CMD['PERIODIC']['2HZ']['HIGH_REP'] ) # Wait <=15.5ms for first measurement diff --git a/klippy/extras/stepper_enable.py b/klippy/extras/stepper_enable.py index 2bad7555..926e9592 100644 --- a/klippy/extras/stepper_enable.py +++ b/klippy/extras/stepper_enable.py @@ -1,6 +1,6 @@ # Support for enable pins on stepper motor drivers # -# Copyright (C) 2019-2021 Kevin O'Connor +# Copyright (C) 2019-2025 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import logging @@ -88,30 +88,38 @@ class PrinterStepperEnable: name = mcu_stepper.get_name() enable = setup_enable_pin(self.printer, config.get('enable_pin', None)) self.enable_lines[name] = EnableTracking(mcu_stepper, enable) + def set_motors_enable(self, stepper_names, enable): + toolhead = self.printer.lookup_object('toolhead') + # Flush steps to ensure all auto enable callbacks invoked + toolhead.flush_step_generation() + print_time = None + did_change = False + for stepper_name in stepper_names: + el = self.enable_lines[stepper_name] + if el.is_motor_enabled() == enable: + continue + if print_time is None: + # Dwell for sufficient delay from any previous auto enable + if not enable: + toolhead.dwell(DISABLE_STALL_TIME) + print_time = toolhead.get_last_move_time() + if enable: + el.motor_enable(print_time) + else: + el.motor_disable(print_time) + did_change = True + # Dwell to ensure sufficient delay prior to a future auto enable + if did_change and not enable: + toolhead.dwell(DISABLE_STALL_TIME) + return did_change def motor_off(self): + self.set_motors_enable(self.get_steppers(), False) toolhead = self.printer.lookup_object('toolhead') - toolhead.dwell(DISABLE_STALL_TIME) - print_time = toolhead.get_last_move_time() - for el in self.enable_lines.values(): - el.motor_disable(print_time) toolhead.get_kinematics().clear_homing_state("xyz") - self.printer.send_event("stepper_enable:motor_off", print_time) - toolhead.dwell(DISABLE_STALL_TIME) - def motor_debug_enable(self, stepper, enable): - toolhead = self.printer.lookup_object('toolhead') - toolhead.dwell(DISABLE_STALL_TIME) - print_time = toolhead.get_last_move_time() - el = self.enable_lines[stepper] - if enable: - el.motor_enable(print_time) - logging.info("%s has been manually enabled", stepper) - else: - el.motor_disable(print_time) - logging.info("%s has been manually disabled", stepper) - toolhead.dwell(DISABLE_STALL_TIME) + self.printer.send_event("stepper_enable:motor_off") def get_status(self, eventtime): steppers = { name: et.is_motor_enabled() - for (name, et) in self.enable_lines.items() } + for (name, et) in self.enable_lines.items() } return {'steppers': steppers} def _handle_request_restart(self, print_time): self.motor_off() @@ -126,7 +134,11 @@ class PrinterStepperEnable: % (stepper_name,)) return stepper_enable = gcmd.get_int('ENABLE', 1) - self.motor_debug_enable(stepper_name, stepper_enable) + self.set_motors_enable([stepper_name], stepper_enable) + if stepper_enable: + logging.info("%s has been manually enabled", stepper_name) + else: + logging.info("%s has been manually disabled", stepper_name) def lookup_enable(self, name): if name not in self.enable_lines: raise self.printer.config_error("Unknown stepper '%s'" % (name,)) diff --git a/klippy/extras/sx1509.py b/klippy/extras/sx1509.py index 070b9133..fd36c7fe 100644 --- a/klippy/extras/sx1509.py +++ b/klippy/extras/sx1509.py @@ -29,7 +29,6 @@ class SX1509(object): self._ppins = self._printer.lookup_object("pins") self._ppins.register_chip("sx1509_" + self._name, self) self._mcu = self._i2c.get_mcu() - self._mcu.register_config_callback(self._build_config) self._oid = self._i2c.get_oid() self._last_clock = 0 # Set up registers default values @@ -37,22 +36,26 @@ class SX1509(object): REG_PULLUP : 0, REG_PULLDOWN : 0, REG_INPUT_DISABLE : 0, REG_ANALOG_DRIVER_ENABLE : 0} self.reg_i_on_dict = {reg : 0 for reg in REG_I_ON} - def _build_config(self): + config.get_printer().register_event_handler("klippy:connect", + self.handle_connect) + def handle_connect(self): # Reset the chip, Default RegClock/RegMisc 0x0 - self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % ( - self._oid, REG_RESET, 0x12)) - self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % ( - self._oid, REG_RESET, 0x34)) + self._i2c.i2c_write([REG_RESET, 0x12]) + self._i2c.i2c_write([REG_RESET, 0x34]) # Enable Oscillator - self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % ( - self._oid, REG_CLOCK, (1 << 6))) + self._i2c.i2c_write([REG_CLOCK, (1 << 6)]) # Setup Clock Divider - self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % ( - self._oid, REG_MISC, (1 << 4))) + self._i2c.i2c_write([REG_MISC, (1 << 4)]) # Transfer all regs with their initial cached state - for _reg, _data in self.reg_dict.items(): - self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%04x" % ( - self._oid, _reg, _data), is_init=True) + reactor = self._mcu.get_printer().get_reactor() + for _reg in self.reg_dict: + curtime = reactor.monotonic() + printtime = self._mcu.estimated_print_time(curtime) + self.send_register(_reg, printtime) + for reg in self.reg_i_on_dict: + curtime = reactor.monotonic() + printtime = self._mcu.estimated_print_time(curtime) + self.send_register(reg, printtime) def setup_pin(self, pin_type, pin_params): if pin_type == 'digital_out' and pin_params['pin'][0:4] == "PIN_": return SX1509_digital_out(self, pin_params) @@ -159,15 +162,6 @@ class SX1509_pwm(object): raise pins.error("SX1509_pwm must have hardware_pwm enabled") if self._max_duration: raise pins.error("SX1509 pins are not suitable for heaters") - # Send initial value - self._sx1509.set_register(self._i_on_reg, - ~int(255 * self._start_value) & 0xFF) - self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % ( - self._sx1509.get_oid(), - self._i_on_reg, - self._sx1509.reg_i_on_dict[self._i_on_reg] - ), - is_init=True) def get_mcu(self): return self._mcu def setup_max_duration(self, max_duration): @@ -181,6 +175,8 @@ class SX1509_pwm(object): shutdown_value = 1. - shutdown_value self._start_value = max(0., min(1., start_value)) self._shutdown_value = max(0., min(1., shutdown_value)) + self._sx1509.set_register(self._i_on_reg, + ~int(255 * self._start_value) & 0xFF) def set_pwm(self, print_time, value): self._sx1509.set_register(self._i_on_reg, ~int(255 * value) if not self._invert diff --git a/klippy/extras/temperature_probe.py b/klippy/extras/temperature_probe.py index c480ddae..aebb1076 100644 --- a/klippy/extras/temperature_probe.py +++ b/klippy/extras/temperature_probe.py @@ -132,7 +132,7 @@ class TemperatureProbe: self.start_pos = [] # Register GCode Commands - pname = self.name.split(maxsplit=1)[-1] + pname = self.name.split(None, 1)[-1] self.gcode.register_mux_command( "TEMPERATURE_PROBE_CALIBRATE", "PROBE", pname, self.cmd_TEMPERATURE_PROBE_CALIBRATE, @@ -357,8 +357,8 @@ class TemperatureProbe: self._check_homed() probe = self._get_probe() probe_name = probe.get_status(None)["name"] - short_name = probe_name.split(maxsplit=1)[-1] - if short_name != self.name.split(maxsplit=1)[-1]: + short_name = probe_name.split(None, 1)[-1] + if short_name != self.name.split(None, 1)[-1]: raise self.gcode.error( "[%s] not linked to registered probe [%s]." % (self.name, probe_name) @@ -588,7 +588,7 @@ class EddyDriftCompensation: temps[idx] = cur_temp probe_samples[idx].append(sample) return True - sect_name = "probe_eddy_current " + self.name.split(maxsplit=1)[-1] + sect_name = "probe_eddy_current " + self.name.split(None, 1)[-1] self.printer.lookup_object(sect_name).add_client(_on_bulk_data_recd) for i in range(DRIFT_SAMPLE_COUNT): if i == 0: diff --git a/klippy/extras/tmc2240.py b/klippy/extras/tmc2240.py index 35d2ce83..d57a93b8 100644 --- a/klippy/extras/tmc2240.py +++ b/klippy/extras/tmc2240.py @@ -262,6 +262,9 @@ FieldFormatters.update({ "adc_temp": (lambda v: "0x%04x(%.1fC)" % (v, ((v - 2038) / 7.7))), "adc_vsupply": (lambda v: "0x%04x(%.3fV)" % (v, v * 0.009732)), "adc_ain": (lambda v: "0x%04x(%.3fmV)" % (v, v * 0.3052)), + "overvoltage_vth": (lambda v: "0x%04x(%.3fV)" % (v, v * 0.009732)), + "overtempprewarning_vth": (lambda v: + "0x%04x(%.1fC)" % (v, ((v - 2038) / 7.7))), }) diff --git a/klippy/extras/z_tilt.py b/klippy/extras/z_tilt.py index 9f5ea0b9..0316ee72 100644 --- a/klippy/extras/z_tilt.py +++ b/klippy/extras/z_tilt.py @@ -79,7 +79,7 @@ class ZAdjustStatus: self.applied = False def get_status(self, eventtime): return {'applied': self.applied} - def _motor_off(self, print_time): + def _motor_off(self): self.reset() class RetryHelper: diff --git a/klippy/gcode.py b/klippy/gcode.py index 975da792..1c50695d 100644 --- a/klippy/gcode.py +++ b/klippy/gcode.py @@ -430,18 +430,17 @@ class GCodeIO: self.gcode.request_restart('exit') pending_commands.append("") # Handle case where multiple commands pending - if self.is_processing_data or len(pending_commands) > 1: - if len(pending_commands) < 20: - # Check for M112 out-of-order - for line in lines: - if self.m112_r.match(line) is not None: - self.gcode.cmd_M112(None) - if self.is_processing_data: - if len(pending_commands) >= 20: - # Stop reading input - self.reactor.unregister_fd(self.fd_handle) - self.fd_handle = None - return + if len(pending_commands) < 20: + # Check for M112 out-of-order + for line in lines: + if self.m112_r.match(line) is not None: + self.gcode.cmd_M112(None) + if self.is_processing_data: + if len(pending_commands) >= 20: + # Stop reading input + self.reactor.unregister_fd(self.fd_handle) + self.fd_handle = None + return # Process commands self.is_processing_data = True while pending_commands: diff --git a/klippy/kinematics/extruder.py b/klippy/kinematics/extruder.py index a89e3bdf..4e6f14e4 100644 --- a/klippy/kinematics/extruder.py +++ b/klippy/kinematics/extruder.py @@ -69,14 +69,16 @@ class ExtruderStepper: if not pressure_advance: new_smooth_time = 0. toolhead = self.printer.lookup_object("toolhead") - if new_smooth_time != old_smooth_time: - toolhead.note_step_generation_scan_time( - new_smooth_time * .5, old_delay=old_smooth_time * .5) ffi_main, ffi_lib = chelper.get_ffi() espa = ffi_lib.extruder_set_pressure_advance - toolhead.register_lookahead_callback( - lambda print_time: espa(self.sk_extruder, print_time, - pressure_advance, new_smooth_time)) + if new_smooth_time != old_smooth_time: + # Need full kinematic flush to change the smooth time + toolhead.flush_step_generation() + espa(self.sk_extruder, 0., pressure_advance, new_smooth_time) + else: + toolhead.register_lookahead_callback( + lambda print_time: espa(self.sk_extruder, print_time, + pressure_advance, new_smooth_time)) self.pressure_advance = pressure_advance self.pressure_advance_smooth_time = smooth_time cmd_SET_PRESSURE_ADVANCE_help = "Set pressure advance parameters" diff --git a/klippy/mcu.py b/klippy/mcu.py index db02e2a4..146b5bac 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -84,7 +84,7 @@ class CommandQueryWrapper: # Wrapper around command sending class CommandWrapper: - def __init__(self, serial, msgformat, cmd_queue=None): + def __init__(self, serial, msgformat, cmd_queue=None, debugoutput=False): self._serial = serial msgparser = serial.get_msgparser() self._cmd = msgparser.lookup_command(msgformat) @@ -92,6 +92,9 @@ class CommandWrapper: cmd_queue = serial.get_default_command_queue() self._cmd_queue = cmd_queue self._msgtag = msgparser.lookup_msgid(msgformat) & 0xffffffff + if debugoutput: + # Can't use send_wait_ack when in debugging mode + self.send_wait_ack = self.send def send(self, data=(), minclock=0, reqclock=0): cmd = self._cmd.encode(data) self._serial.raw_send(cmd, minclock, reqclock, self._cmd_queue) @@ -888,7 +891,8 @@ class MCU: def alloc_command_queue(self): return self._serial.alloc_command_queue() def lookup_command(self, msgformat, cq=None): - return CommandWrapper(self._serial, msgformat, cq) + return CommandWrapper(self._serial, msgformat, cq, + debugoutput=self.is_fileoutput()) def lookup_query_command(self, msgformat, respformat, oid=None, cq=None, is_async=False): return CommandQueryWrapper(self._serial, msgformat, respformat, oid, diff --git a/klippy/reactor.py b/klippy/reactor.py index 412d53ed..f9bedcf3 100644 --- a/klippy/reactor.py +++ b/klippy/reactor.py @@ -1,6 +1,6 @@ # File descriptor and timer event helper # -# Copyright (C) 2016-2020 Kevin O'Connor +# Copyright (C) 2016-2025 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. import os, gc, select, math, time, logging, queue @@ -14,6 +14,7 @@ class ReactorTimer: def __init__(self, callback, waketime): self.callback = callback self.waketime = waketime + self.timer_is_running = False class ReactorCompletion: class sentinel: pass @@ -118,6 +119,8 @@ class SelectReactor: return tuple(self._last_gc_times) # Timers def update_timer(self, timer_handler, waketime): + if timer_handler.timer_is_running: + return timer_handler.waketime = waketime self._next_timer = min(self._next_timer, waketime) def register_timer(self, callback, waketime=NEVER): @@ -155,7 +158,9 @@ class SelectReactor: waketime = t.waketime if eventtime >= waketime: t.waketime = self.NEVER + t.timer_is_running = True t.waketime = waketime = t.callback(eventtime) + t.timer_is_running = False if g_dispatch is not self._g_dispatch: self._next_timer = min(self._next_timer, waketime) self._end_greenlet(g_dispatch) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 21aeca3d..cf648695 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -193,25 +193,13 @@ class LookAheadQueue: BUFFER_TIME_LOW = 1.0 BUFFER_TIME_HIGH = 2.0 BUFFER_TIME_START = 0.250 -BGFLUSH_LOW_TIME = 0.200 -BGFLUSH_BATCH_TIME = 0.200 -BGFLUSH_EXTRA_TIME = 0.250 -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 - -DRIP_SEGMENT_TIME = 0.050 -DRIP_TIME = 0.100 # Main code to track events (and their timing) on the printer toolhead class ToolHead: def __init__(self, config): self.printer = config.get_printer() self.reactor = self.printer.get_reactor() - self.all_mcus = [ - m for n, m in self.printer.lookup_objects(module='mcu')] - self.mcu = self.all_mcus[0] + self.mcu = self.printer.lookup_object('mcu') self.lookahead = LookAheadQueue() self.lookahead.set_flush_time(BUFFER_TIME_HIGH) self.commanded_pos = [0., 0., 0., 0.] @@ -236,16 +224,10 @@ class ToolHead: self.print_time = 0. self.special_queuing_state = "NeedPrime" self.priming_timer = None - # Flush tracking - 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 = 0. - # Kinematic step generation scan window time tracking - self.kin_flush_delay = SDS_CHECK_TIME - self.kin_flush_times = [] # Setup for generating moves self.motion_queuing = self.printer.load_object(config, 'motion_queuing') + self.motion_queuing.setup_lookahead_flush_callback( + self._check_flush_lookahead) self.trapq = self.motion_queuing.allocate_trapq() self.trapq_append = self.motion_queuing.lookup_trapq_append() # Create kinematics class @@ -268,33 +250,15 @@ class ToolHead: # Register handlers self.printer.register_event_handler("klippy:shutdown", self._handle_shutdown) - # Print time and flush tracking - def _advance_flush_time(self, flush_time): - flush_time = max(flush_time, self.last_flush_time) - # Generate steps via itersolve - 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) - 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) - self.last_flush_time = flush_time + # Print time tracking def _advance_move_time(self, next_print_time): - pt_delay = self.kin_flush_delay + STEPCOMPRESS_FLUSH_TIME - flush_time = max(self.last_flush_time, self.print_time - pt_delay) self.print_time = max(self.print_time, next_print_time) - want_flush_time = max(flush_time, self.print_time - pt_delay) - while 1: - flush_time = min(flush_time + MOVE_BATCH_TIME, want_flush_time) - self._advance_flush_time(flush_time) - if flush_time >= want_flush_time: - break + self.motion_queuing.advance_flush_time(self.print_time, + lazy_target=True) def _calc_print_time(self): curtime = self.reactor.monotonic() est_print_time = self.mcu.estimated_print_time(curtime) - kin_time = max(est_print_time + MIN_KIN_TIME, self.min_restart_time) - kin_time += self.kin_flush_delay + kin_time = self.motion_queuing.calc_step_gen_restart(est_print_time) min_print_time = max(est_print_time + BUFFER_TIME_START, kin_time) if min_print_time > self.print_time: self.print_time = min_print_time @@ -328,10 +292,10 @@ 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) + self.motion_queuing.note_mcu_movequeue_activity(next_move_time) self._advance_move_time(next_move_time) def _flush_lookahead(self): - # Transit from "NeedPrime"/"Priming"/"Drip"/main state to "NeedPrime" + # Transit from "NeedPrime"/"Priming"/main state to "NeedPrime" self._process_lookahead() self.special_queuing_state = "NeedPrime" self.need_check_pause = -1. @@ -339,8 +303,7 @@ class ToolHead: self.check_stall_time = 0. def flush_step_generation(self): self._flush_lookahead() - self._advance_flush_time(self.step_gen_time) - self.min_restart_time = max(self.min_restart_time, self.print_time) + self.motion_queuing.flush_all_steps() def get_last_move_time(self): if self.special_queuing_state: self._flush_lookahead() @@ -391,35 +354,21 @@ class ToolHead: logging.exception("Exception in priming_handler") self.printer.invoke_shutdown("Exception in priming_handler") return self.reactor.NEVER - def _flush_handler(self, eventtime): - try: - est_print_time = self.mcu.estimated_print_time(eventtime) - if not self.special_queuing_state: - # In "main" state - flush lookahead if buffer runs low - print_time = self.print_time - buffer_time = print_time - est_print_time - if buffer_time > BUFFER_TIME_LOW: - # Running normally - reschedule check - return eventtime + buffer_time - BUFFER_TIME_LOW - # Under ran low buffer mark - flush lookahead queue - self._flush_lookahead() - if print_time != self.print_time: - self.check_stall_time = self.print_time - # In "NeedPrime"/"Priming" state - flush queues if needed - while 1: - end_flush = self.need_flush_time + BGFLUSH_EXTRA_TIME - if self.last_flush_time >= end_flush: - self.do_kick_flush_timer = True - return self.reactor.NEVER - buffer_time = self.last_flush_time - est_print_time - if buffer_time > BGFLUSH_LOW_TIME: - return eventtime + buffer_time - BGFLUSH_LOW_TIME - ftime = est_print_time + BGFLUSH_LOW_TIME + BGFLUSH_BATCH_TIME - self._advance_flush_time(min(end_flush, ftime)) - except: - logging.exception("Exception in flush_handler") - self.printer.invoke_shutdown("Exception in flush_handler") - return self.reactor.NEVER + def _check_flush_lookahead(self, eventtime): + if self.special_queuing_state: + return None + # In "main" state - flush lookahead if buffer runs low + est_print_time = self.mcu.estimated_print_time(eventtime) + print_time = self.print_time + buffer_time = print_time - est_print_time + if buffer_time > BUFFER_TIME_LOW: + # Running normally - reschedule check + return eventtime + buffer_time - BUFFER_TIME_LOW + # Under ran low buffer mark - flush lookahead queue + self._flush_lookahead() + if print_time != self.print_time: + self.check_stall_time = self.print_time + return None # Movement commands def get_position(self): return list(self.commanded_pos) @@ -491,32 +440,6 @@ 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): - # Transition from "NeedPrime"/"Priming"/main state to "Drip" state - self.special_queuing_state = "Drip" - self.need_check_pause = self.reactor.NEVER - self.reactor.update_timer(self.flush_timer, self.reactor.NEVER) - self.do_kick_flush_timer = False - self.lookahead.set_flush_time(BUFFER_TIME_HIGH) - self.check_stall_time = 0. - # Update print_time in segments until drip_completion signal - flush_delay = DRIP_TIME + STEPCOMPRESS_FLUSH_TIME + self.kin_flush_delay - while self.print_time < next_print_time: - if drip_completion.test(): - break - curtime = self.reactor.monotonic() - est_print_time = self.mcu.estimated_print_time(curtime) - wait_time = self.print_time - est_print_time - flush_delay - if wait_time > 0. and self.can_pause: - # Pause before sending more steps - 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) - self._advance_move_time(npt) - # Exit "Drip" state - self.reactor.update_timer(self.flush_timer, self.reactor.NOW) - self.flush_step_generation() def _drip_load_trapq(self, submit_move): # Queue move into trapezoid motion queue (trapq) if submit_move.move_d: @@ -524,18 +447,17 @@ class ToolHead: self.lookahead.add_move(submit_move) moves = self.lookahead.flush() self._calc_print_time() - next_move_time = self.print_time + start_time = end_time = self.print_time for move in moves: self.trapq_append( - self.trapq, next_move_time, + self.trapq, end_time, move.accel_t, move.cruise_t, move.decel_t, move.start_pos[0], move.start_pos[1], move.start_pos[2], move.axes_r[0], move.axes_r[1], move.axes_r[2], move.start_v, move.cruise_v, move.accel) - next_move_time = (next_move_time + move.accel_t - + move.cruise_t + move.decel_t) + end_time = end_time + move.accel_t + move.cruise_t + move.decel_t self.lookahead.reset() - return next_move_time + return start_time, end_time def drip_move(self, newpos, speed, drip_completion): # Create and verify move is valid newpos = newpos[:3] + self.commanded_pos[3:] @@ -543,23 +465,20 @@ class ToolHead: if move.move_d: self.kin.check_move(move) # Make sure stepper movement doesn't start before nominal start time - self.dwell(self.kin_flush_delay) + kin_flush_delay = self.motion_queuing.get_kin_flush_delay() + self.dwell(kin_flush_delay) # Transmit move in "drip" mode self._process_lookahead() - next_move_time = self._drip_load_trapq(move) - self.drip_update_time(next_move_time, drip_completion) + start_time, end_time = self._drip_load_trapq(move) + self.motion_queuing.drip_update_time(start_time, end_time, + drip_completion) # Move finished; cleanup any remnants on trapq self.motion_queuing.wipe_trapq(self.trapq) # Misc commands def stats(self, eventtime): - max_queue_time = max(self.print_time, self.last_flush_time) - for m in self.all_mcus: - m.check_active(max_queue_time, eventtime) est_print_time = self.mcu.estimated_print_time(eventtime) 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": - buffer_time = 0. return is_active, "print_time=%.3f buffer_time=%.3f print_stall=%d" % ( self.print_time, max(buffer_time, 0.), self.print_stall) def check_busy(self, eventtime): @@ -588,27 +507,12 @@ class ToolHead: return self.kin def get_trapq(self): return self.trapq - def note_step_generation_scan_time(self, delay, old_delay=0.): - self.flush_step_generation() - if old_delay: - self.kin_flush_times.pop(self.kin_flush_times.index(old_delay)) - if delay: - self.kin_flush_times.append(delay) - new_delay = max(self.kin_flush_times + [SDS_CHECK_TIME]) - self.kin_flush_delay = new_delay def register_lookahead_callback(self, callback): last_move = self.lookahead.get_last() if last_move is None: callback(self.get_last_move_time()) return last_move.timing_callbacks.append(callback) - def note_mcu_movequeue_activity(self, mq_time, is_step_gen=True): - self.need_flush_time = max(self.need_flush_time, mq_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 - self.reactor.update_timer(self.flush_timer, self.reactor.NOW) def get_max_velocity(self): return self.max_velocity, self.max_accel def _calc_junction_deviation(self): @@ -662,7 +566,7 @@ class ToolHeadCommandHelper: msg = ("max_velocity: %.6f\n" "max_accel: %.6f\n" "minimum_cruise_ratio: %.6f\n" - "square_corner_velocity: %.6f" % (mv, ma, scv, mcr)) + "square_corner_velocity: %.6f" % (mv, ma, mcr, scv)) 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): diff --git a/scripts/spi_flash/board_defs.py b/scripts/spi_flash/board_defs.py index 0e82d27b..4fdba64c 100644 --- a/scripts/spi_flash/board_defs.py +++ b/scripts/spi_flash/board_defs.py @@ -170,6 +170,14 @@ BOARD_DEFS = { "firmware_path": "ZNP_ROBIN_NANO.bin", "current_firmware_path": "ZNP_ROBIN_NANO.CUR" }, + 'qidi-x6': { + 'mcu': "stm32f401xc", + 'spi_bus': "spi2", + 'cs_pin': "PB12", + 'skip_verify': False, + 'firmware_path': 'X_4.bin', + 'current_firmware_path': 'X_4.CUR' + }, 'qidi-x7': { 'mcu': "stm32f401xc", 'spi_bus': "spi2", @@ -228,6 +236,9 @@ BOARD_ALIASES = { 'robin_v3': BOARD_DEFS['monster8'], 'btt-skrat-v1.0': BOARD_DEFS['btt-skrat'], 'chitu-v6': BOARD_DEFS['chitu-v6'], + 'qidi-x-smart3': BOARD_DEFS['qidi-x6'], + 'qidi-x-plus3': BOARD_DEFS['qidi-x6'], + 'qidi-x-max3': BOARD_DEFS['qidi-x6'], 'qidi-q1-pro': BOARD_DEFS['qidi-x7'], 'qidi-plus4': BOARD_DEFS['qidi-x7'] } diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index 8a7afc3d..1e0df93d 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -302,7 +302,7 @@ choice config STM32_FLASH_START_C000 bool "48KiB bootloader" if MACH_STM32F4x5 || MACH_STM32F401 config STM32_FLASH_START_10000 - bool "64KiB bootloader" if MACH_STM32F103 || MACH_STM32F4 + bool "64KiB bootloader" if MACH_STM32F103 || MACH_STM32F4 || MACH_N32G45x config STM32_FLASH_START_800 bool "2KiB bootloader" if MACH_STM32F103 diff --git a/src/stm32/Makefile b/src/stm32/Makefile index ad567f8e..485d2ff9 100644 --- a/src/stm32/Makefile +++ b/src/stm32/Makefile @@ -94,7 +94,7 @@ src-$(CONFIG_HAVE_GPIO_SDIO) += stm32/sdio.c target-y += $(OUT)klipper.bin $(OUT)klipper.bin: $(OUT)klipper.elf - @echo " Creating hex file $@" + @echo " Creating bin file $@" $(Q)$(OBJCOPY) -O binary $< $@ # Flash rules diff --git a/src/stm32/n32g45x_adc.c b/src/stm32/n32g45x_adc.c index d27e70c9..52cee2f5 100644 --- a/src/stm32/n32g45x_adc.c +++ b/src/stm32/n32g45x_adc.c @@ -14,6 +14,8 @@ #include "sched.h" // sched_shutdown #include "n32g45x_adc.h" // ADC +#define ADC_INVALID_PIN 0xFF + DECL_CONSTANT("ADC_MAX", 4095); #define ADC_TEMPERATURE_PIN 0xfe @@ -21,42 +23,42 @@ DECL_ENUMERATION("pin", "ADC_TEMPERATURE", ADC_TEMPERATURE_PIN); static const uint8_t adc_pins[] = { // ADC1 - 0, GPIO('A', 0), GPIO('A', 1), GPIO('A', 6), - GPIO('A', 3), GPIO('F', 4), 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - ADC_TEMPERATURE_PIN, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, + ADC_INVALID_PIN, GPIO('A', 0), GPIO('A', 1), GPIO('A', 6), + GPIO('A', 3), GPIO('F', 4), ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_TEMPERATURE_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, // ADC2 - 0, GPIO('A', 4), GPIO('A', 5), GPIO('B', 1), + ADC_INVALID_PIN, GPIO('A', 4), GPIO('A', 5), GPIO('B', 1), GPIO('A', 7), GPIO('C', 4), GPIO('C', 0), GPIO('C', 1), GPIO('C', 2), GPIO('C', 3), GPIO('F', 2), GPIO('A', 2), - GPIO('C', 5), GPIO('B', 2), 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, + GPIO('C', 5), GPIO('B', 2), ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, #if CONFIG_MACH_N32G455 // ADC3/4 for G455 only // ADC3 - 0, GPIO('B', 11), GPIO('E', 9), GPIO('E', 13), + ADC_INVALID_PIN, GPIO('B', 11), GPIO('E', 9), GPIO('E', 13), GPIO('E', 12), GPIO('B', 13), GPIO('E', 8), GPIO('D', 10), GPIO('D', 11), GPIO('D', 12), GPIO('D', 13), GPIO('D', 14), GPIO('B', 0), GPIO('E', 7), GPIO('E', 10), GPIO('E', 11), - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, // ADC4 - 0, GPIO('E', 14), GPIO('E', 15), GPIO('B', 12), - GPIO('B', 14), GPIO('B', 15), 0, 0, - 0, 0, 0, 0, - GPIO('D', 8), GPIO('D', 9), 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, - 0, 0, 0, 0, + ADC_INVALID_PIN, GPIO('E', 14), GPIO('E', 15), GPIO('B', 12), + GPIO('B', 14), GPIO('B', 15), ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, + GPIO('D', 8), GPIO('D', 9), ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, + ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, #endif }; diff --git a/test/klippy/extruders.cfg b/test/klippy/extruders.cfg index d7123d08..7384617e 100644 --- a/test/klippy/extruders.cfg +++ b/test/klippy/extruders.cfg @@ -66,3 +66,19 @@ max_velocity: 300 max_accel: 3000 max_z_velocity: 5 max_z_accel: 100 + +[filament_switch_sensor runout_switch] +switch_pin = PD4 + +[filament_motion_sensor runout_encoder] +switch_pin = PD5 +detection_length = 4 +extruder = extruder + +[filament_switch_sensor runout_switch1] +switch_pin = PL4 + +[filament_motion_sensor runout_encoder1] +switch_pin = PL6 +detection_length = 4 +extruder = extruder_stepper my_extra_stepper diff --git a/test/klippy/manual_stepper.test b/test/klippy/manual_stepper.test index d19c39c5..12726411 100644 --- a/test/klippy/manual_stepper.test +++ b/test/klippy/manual_stepper.test @@ -10,6 +10,10 @@ MANUAL_STEPPER STEPPER=basic_stepper MOVE=5 MANUAL_STEPPER STEPPER=basic_stepper MOVE=12 SPEED=12 ACCEL=9000.2 MANUAL_STEPPER STEPPER=basic_stepper ENABLE=0 +# Test long move +MANUAL_STEPPER STEPPER=basic_stepper MOVE=300 SPEED=10 ACCEL=2000 +MANUAL_STEPPER STEPPER=basic_stepper MOVE=100 SPEED=10 ACCEL=2000 + # Test homing move MANUAL_STEPPER STEPPER=homing_stepper ENABLE=1 MANUAL_STEPPER STEPPER=homing_stepper SET_POSITION=0 @@ -29,6 +33,10 @@ G28 G1 X20 Y20 Z10 G1 A10 X22 +# Verify position query commands work with extra axis +GET_POSITION +M114 + # Test unregistering MANUAL_STEPPER STEPPER=basic_stepper GCODE_AXIS= G1 X15