Merge branch 'Klipper3d:master' into master

This commit is contained in:
Andrei 2025-09-07 12:43:19 +02:00 committed by GitHub
commit b0617c7266
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 485 additions and 360 deletions

View File

@ -4944,8 +4944,8 @@ detection_length: 7.0
# a state change on the switch_pin # a state change on the switch_pin
# Default is 7 mm. # Default is 7 mm.
extruder: extruder:
# The name of the extruder section this sensor is associated with. # The name of the extruder or extruder_stepper section this sensor
# This parameter must be provided. # is associated with. This parameter must be provided.
switch_pin: switch_pin:
#pause_on_runout: #pause_on_runout:
#runout_gcode: #runout_gcode:

View File

@ -98,17 +98,16 @@ bootloaders.
## Can I run Klipper on something other than a Raspberry Pi 3? ## Can I run Klipper on something other than a Raspberry Pi 3?
The recommended hardware is a Raspberry Pi 2, Raspberry Pi 3, or The recommended hardware is a Raspberry Pi Zero2w, Raspberry Pi 3,
Raspberry Pi 4. 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 Klipper will run on a Raspberry Pi 1, 2 and on the Raspberry Pi Zero1,
these boards don't have enough processing power to run OctoPrint 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 well. It is common for print stalls to occur on these slower machines
when printing directly from OctoPrint. (The printer may move faster when printing (The printer may move faster than Klipper can send
than OctoPrint can send movement commands.) If you wish to run on one movement commands.) It is not reccomended to run Kliper on these older
one of these slower boards anyway, consider using the "virtual_sdcard" machines.
feature when printing (see
[config reference](Config_Reference.md#virtual_sdcard) for details).
For running on the Beaglebone, see the For running on the Beaglebone, see the
[Beaglebone specific installation instructions](Beaglebone.md). [Beaglebone specific installation instructions](Beaglebone.md).

View File

@ -56,6 +56,8 @@ defs_stepcompress = """
, uint64_t start_clock, uint64_t end_clock); , uint64_t start_clock, uint64_t end_clock);
void stepcompress_set_stepper_kinematics(struct stepcompress *sc void stepcompress_set_stepper_kinematics(struct stepcompress *sc
, struct stepper_kinematics *sk); , struct stepper_kinematics *sk);
struct stepper_kinematics *stepcompress_get_stepper_kinematics(
struct stepcompress *sc);
""" """
defs_steppersync = """ defs_steppersync = """
@ -76,11 +78,14 @@ defs_itersolve = """
int32_t itersolve_is_active_axis(struct stepper_kinematics *sk, char axis); 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_trapq(struct stepper_kinematics *sk, struct trapq *tq
, double step_dist); , double step_dist);
struct trapq *itersolve_get_trapq(struct stepper_kinematics *sk);
double itersolve_calc_position_from_coord(struct stepper_kinematics *sk double itersolve_calc_position_from_coord(struct stepper_kinematics *sk
, double x, double y, double z); , double x, double y, double z);
void itersolve_set_position(struct stepper_kinematics *sk void itersolve_set_position(struct stepper_kinematics *sk
, double x, double y, double z); , double x, double y, double z);
double itersolve_get_commanded_pos(struct stepper_kinematics *sk); 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 = """ defs_trapq = """
@ -157,8 +162,6 @@ defs_kin_extruder = """
""" """
defs_kin_shaper = """ 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 input_shaper_set_shaper_params(struct stepper_kinematics *sk, char axis
, int n, double a[], double t[]); , int n, double a[], double t[]);
int input_shaper_set_sk(struct stepper_kinematics *sk int input_shaper_set_sk(struct stepper_kinematics *sk
@ -274,6 +277,28 @@ def do_build_code(cmd):
logging.error(msg) logging.error(msg)
raise Exception(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_main = None
FFI_lib = None FFI_lib = None
pyhelper_logging_callback = None pyhelper_logging_callback = None
@ -286,17 +311,9 @@ def logging_callback(msg):
def get_ffi(): def get_ffi():
global FFI_main, FFI_lib, pyhelper_logging_callback global FFI_main, FFI_lib, pyhelper_logging_callback
if FFI_lib is None: if FFI_lib is None:
srcdir = os.path.dirname(os.path.realpath(__file__)) # Check if library needs to be built, and build if so
srcfiles = get_abs_files(srcdir, SOURCE_FILES) destlib = check_build_c_library()
ofiles = get_abs_files(srcdir, OTHER_FILES) # Open library
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)))
FFI_main = cffi.FFI() FFI_main = cffi.FFI()
for d in defs_all: for d in defs_all:
FFI_main.cdef(d) FFI_main.cdef(d)

View File

@ -248,6 +248,12 @@ itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq
sk->step_dist = step_dist; sk->step_dist = step_dist;
} }
struct trapq * __visible
itersolve_get_trapq(struct stepper_kinematics *sk)
{
return sk->tq;
}
double __visible double __visible
itersolve_calc_position_from_coord(struct stepper_kinematics *sk itersolve_calc_position_from_coord(struct stepper_kinematics *sk
, double x, double y, double z) , double x, double y, double z)
@ -273,3 +279,15 @@ itersolve_get_commanded_pos(struct stepper_kinematics *sk)
{ {
return sk->commanded_pos; 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;
}

View File

@ -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); 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_trapq(struct stepper_kinematics *sk, struct trapq *tq
, double step_dist); , double step_dist);
struct trapq *itersolve_get_trapq(struct stepper_kinematics *sk);
double itersolve_calc_position_from_coord(struct stepper_kinematics *sk double itersolve_calc_position_from_coord(struct stepper_kinematics *sk
, double x, double y, double z); , double x, double y, double z);
void itersolve_set_position(struct stepper_kinematics *sk void itersolve_set_position(struct stepper_kinematics *sk
, double x, double y, double z); , double x, double y, double z);
double itersolve_get_commanded_pos(struct stepper_kinematics *sk); 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 #endif // itersolve.h

View File

@ -239,14 +239,6 @@ input_shaper_set_shaper_params(struct stepper_kinematics *sk, char axis
return status; 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 struct stepper_kinematics * __visible
input_shaper_alloc(void) input_shaper_alloc(void)
{ {

View File

@ -10,7 +10,6 @@
#include <stdio.h> // fprintf #include <stdio.h> // fprintf
#include <string.h> // strerror #include <string.h> // strerror
#include <time.h> // struct timespec #include <time.h> // struct timespec
#include <linux/prctl.h> // PR_SET_NAME
#include <sys/prctl.h> // prctl #include <sys/prctl.h> // prctl
#include "compiler.h" // __visible #include "compiler.h" // __visible
#include "pyhelper.h" // get_monotonic #include "pyhelper.h" // get_monotonic

View File

@ -674,6 +674,13 @@ stepcompress_set_stepper_kinematics(struct stepcompress *sc
sc->sk = sk; 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 // Generate steps (via itersolve) and flush
int32_t int32_t
stepcompress_generate_steps(struct stepcompress *sc, double gen_steps_time stepcompress_generate_steps(struct stepcompress *sc, double gen_steps_time

View File

@ -41,6 +41,8 @@ int stepcompress_extract_old(struct stepcompress *sc
struct stepper_kinematics; struct stepper_kinematics;
void stepcompress_set_stepper_kinematics(struct stepcompress *sc void stepcompress_set_stepper_kinematics(struct stepcompress *sc
, struct stepper_kinematics *sk); , struct stepper_kinematics *sk);
struct stepper_kinematics *stepcompress_get_stepper_kinematics(
struct stepcompress *sc);
int32_t stepcompress_generate_steps(struct stepcompress *sc int32_t stepcompress_generate_steps(struct stepcompress *sc
, double gen_steps_time , double gen_steps_time
, uint64_t flush_clock); , uint64_t flush_clock);

View File

@ -284,7 +284,7 @@ class BME280:
self.chip_type, self.i2c.i2c_address)) self.chip_type, self.i2c.i2c_address))
# Reset chip # 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) self.reactor.pause(self.reactor.monotonic() + .5)
# Make sure non-volatile memory has been copied to registers # Make sure non-volatile memory has been copied to registers
@ -394,7 +394,7 @@ class BME280:
self.write_register('CTRL_HUM', self.os_hum) self.write_register('CTRL_HUM', self.os_hum)
# Enter normal (periodic) mode # Enter normal (periodic) mode
meas = self.os_temp << 5 | self.os_pres << 2 | MODE_PERIODIC 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': if self.chip_type == 'BME680':
self.write_register('CONFIG', self.iir_filter << 2) self.write_register('CONFIG', self.iir_filter << 2)
@ -528,7 +528,7 @@ class BME280:
# Enter forced mode # Enter forced mode
meas = self.os_temp << 5 | self.os_pres << 2 | 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 max_sample_time = self.max_sample_time
if run_gas: if run_gas:
max_sample_time += self.gas_heat_duration / 1000 max_sample_time += self.gas_heat_duration / 1000
@ -776,15 +776,12 @@ class BME280:
params = self.i2c.i2c_read(regs, read_len) params = self.i2c.i2c_read(regs, read_len)
return bytearray(params['response']) 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: if type(data) is not list:
data = [data] data = [data]
reg = self.chip_registers[reg_name] reg = self.chip_registers[reg_name]
data.insert(0, reg) data.insert(0, reg)
if not wait:
self.i2c.i2c_write(data) self.i2c.i2c_write(data)
else:
self.i2c.i2c_write_wait_ack(data)
def get_status(self, eventtime): def get_status(self, eventtime):
data = { data = {

View File

@ -180,6 +180,13 @@ class MCU_I2C:
self.cmd_queue = self.mcu.alloc_command_queue() self.cmd_queue = self.mcu.alloc_command_queue()
self.mcu.register_config_callback(self.build_config) self.mcu.register_config_callback(self.build_config)
self.i2c_write_cmd = self.i2c_read_cmd = None 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): def get_oid(self):
return self.oid return self.oid
def get_mcu(self): def get_mcu(self):
@ -207,14 +214,8 @@ class MCU_I2C:
cq=self.cmd_queue) cq=self.cmd_queue)
def i2c_write(self, data, minclock=0, reqclock=0): def i2c_write(self, data, minclock=0, reqclock=0):
if self.i2c_write_cmd is None: if self.i2c_write_cmd is None:
# Send setup message via mcu initialization self._to_write.append(data)
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)
return 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], 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): def i2c_read(self, write, read_len, retry=True):

View File

@ -236,6 +236,8 @@ class PrinterLCD:
except: except:
logging.exception("Error during display screen update") logging.exception("Error during display screen update")
self.lcd_chip.flush() self.lcd_chip.flush()
if self.redraw_request_pending:
return self.redraw_time
return eventtime + REDRAW_TIME return eventtime + REDRAW_TIME
def request_redraw(self): def request_redraw(self):
if self.redraw_request_pending: if self.redraw_request_pending:

View File

@ -15,6 +15,8 @@ class PrinterExtruderStepper:
self.handle_connect) self.handle_connect)
def handle_connect(self): def handle_connect(self):
self.extruder_stepper.sync_to_extruder(self.extruder_name) 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): def get_status(self, eventtime):
return self.extruder_stepper.get_status(eventtime) return self.extruder_stepper.get_status(eventtime)

View File

@ -1,6 +1,6 @@
# Utility for manually moving a stepper for diagnostic purposes # Utility for manually moving a stepper for diagnostic purposes
# #
# Copyright (C) 2018-2019 Kevin O'Connor <kevin@koconnor.net> # Copyright (C) 2018-2025 Kevin O'Connor <kevin@koconnor.net>
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging import math, logging
@ -10,7 +10,6 @@ BUZZ_DISTANCE = 1.
BUZZ_VELOCITY = BUZZ_DISTANCE / .250 BUZZ_VELOCITY = BUZZ_DISTANCE / .250
BUZZ_RADIANS_DISTANCE = math.radians(1.) BUZZ_RADIANS_DISTANCE = math.radians(1.)
BUZZ_RADIANS_VELOCITY = BUZZ_RADIANS_DISTANCE / .250 BUZZ_RADIANS_VELOCITY = BUZZ_RADIANS_DISTANCE / .250
STALL_TIME = 0.100
# Calculate a move's accel_t, cruise_t, and cruise_v # Calculate a move's accel_t, cruise_t, and cruise_v
def calc_move_time(dist, speed, accel): def calc_move_time(dist, speed, accel):
@ -56,24 +55,16 @@ class ForceMove:
raise self.printer.config_error("Unknown stepper %s" % (name,)) raise self.printer.config_error("Unknown stepper %s" % (name,))
return self.steppers[name] return self.steppers[name]
def _force_enable(self, stepper): def _force_enable(self, stepper):
toolhead = self.printer.lookup_object('toolhead') stepper_name = stepper.get_name()
print_time = toolhead.get_last_move_time()
stepper_enable = self.printer.lookup_object('stepper_enable') stepper_enable = self.printer.lookup_object('stepper_enable')
enable = stepper_enable.lookup_enable(stepper.get_name()) did_enable = stepper_enable.set_motors_enable([stepper_name], True)
was_enable = enable.is_motor_enabled() return did_enable
if not was_enable: def _restore_enable(self, stepper, did_enable):
enable.motor_enable(print_time) if not did_enable:
toolhead.dwell(STALL_TIME) return
return was_enable stepper_name = stepper.get_name()
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') stepper_enable = self.printer.lookup_object('stepper_enable')
enable = stepper_enable.lookup_enable(stepper.get_name()) stepper_enable.set_motors_enable([stepper_name], False)
enable.motor_disable(print_time)
toolhead.dwell(STALL_TIME)
def manual_move(self, stepper, dist, speed, accel=0.): def manual_move(self, stepper, dist, speed, accel=0.):
toolhead = self.printer.lookup_object('toolhead') toolhead = self.printer.lookup_object('toolhead')
toolhead.flush_step_generation() toolhead.flush_step_generation()
@ -85,7 +76,7 @@ class ForceMove:
self.trapq_append(self.trapq, print_time, accel_t, cruise_t, accel_t, self.trapq_append(self.trapq, print_time, accel_t, cruise_t, accel_t,
0., 0., 0., axis_r, 0., 0., 0., cruise_v, accel) 0., 0., 0., axis_r, 0., 0., 0., cruise_v, accel)
print_time = print_time + accel_t + cruise_t + accel_t 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.dwell(accel_t + cruise_t + accel_t)
toolhead.flush_step_generation() toolhead.flush_step_generation()
stepper.set_trapq(prev_trapq) stepper.set_trapq(prev_trapq)
@ -100,7 +91,7 @@ class ForceMove:
def cmd_STEPPER_BUZZ(self, gcmd): def cmd_STEPPER_BUZZ(self, gcmd):
stepper = self._lookup_stepper(gcmd) stepper = self._lookup_stepper(gcmd)
logging.info("Stepper buzz %s", stepper.get_name()) 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') toolhead = self.printer.lookup_object('toolhead')
dist, speed = BUZZ_DISTANCE, BUZZ_VELOCITY dist, speed = BUZZ_DISTANCE, BUZZ_VELOCITY
if stepper.units_in_radians(): if stepper.units_in_radians():
@ -110,7 +101,7 @@ class ForceMove:
toolhead.dwell(.050) toolhead.dwell(.050)
self.manual_move(stepper, -dist, speed) self.manual_move(stepper, -dist, speed)
toolhead.dwell(.450) 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" cmd_FORCE_MOVE_help = "Manually move a stepper; invalidates kinematics"
def cmd_FORCE_MOVE(self, gcmd): def cmd_FORCE_MOVE(self, gcmd):
stepper = self._lookup_stepper(gcmd) stepper = self._lookup_stepper(gcmd)

View File

@ -92,7 +92,7 @@ class GCodeMove:
def _get_gcode_position(self): def _get_gcode_position(self):
p = [lp - bp for lp, bp in zip(self.last_position, self.base_position)] p = [lp - bp for lp, bp in zip(self.last_position, self.base_position)]
p[3] /= self.extrude_factor p[3] /= self.extrude_factor
return p return p[:4]
def _get_gcode_speed(self): def _get_gcode_speed(self):
return self.speed / self.speed_factor return self.speed / self.speed_factor
def _get_gcode_speed_override(self): def _get_gcode_speed_override(self):
@ -107,7 +107,7 @@ class GCodeMove:
'absolute_extrude': self.absolute_extrude, 'absolute_extrude': self.absolute_extrude,
'homing_origin': self.Coord(*self.homing_position[:4]), 'homing_origin': self.Coord(*self.homing_position[:4]),
'position': self.Coord(*self.last_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): def reset_last_position(self):
if self.is_printer_ready: if self.is_printer_ready:

View File

@ -75,7 +75,8 @@ class Heater:
# No significant change in value - can suppress update # No significant change in value - can suppress update
return return
pwm_time = read_time + self.pwm_delay 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.last_pwm_value = value
self.mcu_pwm.set_pwm(pwm_time, value) self.mcu_pwm.set_pwm(pwm_time, value)
#logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])", #logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])",

View File

@ -146,12 +146,8 @@ class InputShaper:
is_sk = self._get_input_shaper_stepper_kinematics(s) is_sk = self._get_input_shaper_stepper_kinematics(s)
if is_sk is None: if is_sk is None:
continue 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) 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): def _update_input_shaping(self, error=None):
self.toolhead.flush_step_generation() self.toolhead.flush_step_generation()
ffi_main, ffi_lib = chelper.get_ffi() ffi_main, ffi_lib = chelper.get_ffi()
@ -163,16 +159,11 @@ class InputShaper:
is_sk = self._get_input_shaper_stepper_kinematics(s) is_sk = self._get_input_shaper_stepper_kinematics(s)
if is_sk is None: if is_sk is None:
continue continue
old_delay = ffi_lib.input_shaper_get_step_generation_window(is_sk)
for shaper in self.shapers: for shaper in self.shapers:
if shaper in failed_shapers: if shaper in failed_shapers:
continue continue
if not shaper.set_shaper_kinematics(is_sk): if not shaper.set_shaper_kinematics(is_sk):
failed_shapers.append(shaper) 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: if failed_shapers:
error = error or self.printer.command_error error = error or self.printer.command_error
raise error("Failed to configure shaper(s) %s with given parameters" raise error("Failed to configure shaper(s) %s with given parameters"

View File

@ -22,6 +22,7 @@ class ManualStepper:
self.velocity = config.getfloat('velocity', 5., above=0.) self.velocity = config.getfloat('velocity', 5., above=0.)
self.accel = self.homing_accel = config.getfloat('accel', 0., minval=0.) self.accel = self.homing_accel = config.getfloat('accel', 0., minval=0.)
self.next_cmd_time = 0. self.next_cmd_time = 0.
self.commanded_pos = 0.
self.pos_min = config.getfloat('position_min', None) self.pos_min = config.getfloat('position_min', None)
self.pos_max = config.getfloat('position_max', None) self.pos_max = config.getfloat('position_max', None)
# Setup iterative solver # Setup iterative solver
@ -48,21 +49,16 @@ class ManualStepper:
else: else:
self.next_cmd_time = print_time self.next_cmd_time = print_time
def do_enable(self, enable): 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') stepper_enable = self.printer.lookup_object('stepper_enable')
if enable: stepper_enable.set_motors_enable(stepper_names, 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()
def do_set_position(self, setpos): 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): def _submit_move(self, movetime, movepos, speed, accel):
cp = self.rail.get_commanded_position() cp = self.commanded_pos
dist = movepos - cp dist = movepos - cp
axis_r, accel_t, cruise_t, cruise_v = force_move.calc_move_time( axis_r, accel_t, cruise_t, cruise_v = force_move.calc_move_time(
dist, speed, accel) dist, speed, accel)
@ -70,13 +66,13 @@ class ManualStepper:
accel_t, cruise_t, accel_t, accel_t, cruise_t, accel_t,
cp, 0., 0., axis_r, 0., 0., cp, 0., 0., axis_r, 0., 0.,
0., cruise_v, accel) 0., cruise_v, accel)
self.commanded_pos = movepos
return movetime + accel_t + cruise_t + accel_t return movetime + accel_t + cruise_t + accel_t
def do_move(self, movepos, speed, accel, sync=True): def do_move(self, movepos, speed, accel, sync=True):
self.sync_print_time() self.sync_print_time()
self.next_cmd_time = self._submit_move(self.next_cmd_time, movepos, self.next_cmd_time = self._submit_move(self.next_cmd_time, movepos,
speed, accel) speed, accel)
toolhead = self.printer.lookup_object('toolhead') self.motion_queuing.note_mcu_movequeue_activity(self.next_cmd_time)
toolhead.note_mcu_movequeue_activity(self.next_cmd_time)
if sync: if sync:
self.sync_print_time() self.sync_print_time()
def do_homing_move(self, movepos, speed, accel, triggered, check_trigger): 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.instant_corner_v = instant_corner_v
self.gaxis_limit_velocity = limit_velocity self.gaxis_limit_velocity = limit_velocity
self.gaxis_limit_accel = limit_accel 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): def process_move(self, print_time, move, ea_index):
axis_r = move.axes_r[ea_index] axis_r = move.axes_r[ea_index]
start_pos = move.start_pos[ea_index] start_pos = move.start_pos[ea_index]
@ -161,6 +157,7 @@ class ManualStepper:
start_pos, 0., 0., start_pos, 0., 0.,
1., 0., 0., 1., 0., 0.,
start_v, cruise_v, accel) start_v, cruise_v, accel)
self.commanded_pos = move.end_pos[ea_index]
def check_move(self, move, ea_index): def check_move(self, move, ea_index):
# Check move is in bounds # Check move is in bounds
movepos = move.end_pos[ea_index] movepos = move.end_pos[ea_index]
@ -185,9 +182,10 @@ class ManualStepper:
return self.trapq return self.trapq
# Toolhead wrappers to support homing # Toolhead wrappers to support homing
def flush_step_generation(self): def flush_step_generation(self):
self.sync_print_time() toolhead = self.printer.lookup_object('toolhead')
toolhead.flush_step_generation()
def get_position(self): 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=""): def set_position(self, newpos, homing_axes=""):
self.do_set_position(newpos[0]) self.do_set_position(newpos[0])
def get_last_move_time(self): def get_last_move_time(self):
@ -198,15 +196,16 @@ class ManualStepper:
def drip_move(self, newpos, speed, drip_completion): def drip_move(self, newpos, speed, drip_completion):
# Submit move to trapq # Submit move to trapq
self.sync_print_time() self.sync_print_time()
maxtime = self._submit_move(self.next_cmd_time, newpos[0], start_time = self.next_cmd_time
end_time = self._submit_move(start_time, newpos[0],
speed, self.homing_accel) speed, self.homing_accel)
# Drip updates to motors # Drip updates to motors
toolhead = self.printer.lookup_object('toolhead') self.motion_queuing.drip_update_time(start_time, end_time,
toolhead.drip_update_time(maxtime, drip_completion) drip_completion)
# Clear trapq of any remaining parts of movement # Clear trapq of any remaining parts of movement
reactor = self.printer.get_reactor() reactor = self.printer.get_reactor()
self.motion_queuing.wipe_trapq(self.trapq) 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() self.sync_print_time()
def get_kinematics(self): def get_kinematics(self):
return self return self

View File

@ -6,23 +6,53 @@
import logging import logging
import chelper import chelper
BGFLUSH_LOW_TIME = 0.200
BGFLUSH_BATCH_TIME = 0.200
BGFLUSH_EXTRA_TIME = 0.250
MOVE_HISTORY_EXPIRE = 30. 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: class PrinterMotionQueuing:
def __init__(self, config): 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.trapqs = []
self.stepcompress = [] self.stepcompress = []
self.steppersyncs = [] self.steppersyncs = []
self.flush_callbacks = [] # Low-level C flushing calls
ffi_main, ffi_lib = chelper.get_ffi() ffi_main, ffi_lib = chelper.get_ffi()
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
self.steppersync_generate_steps = ffi_lib.steppersync_generate_steps self.steppersync_generate_steps = ffi_lib.steppersync_generate_steps
self.steppersync_flush = ffi_lib.steppersync_flush self.steppersync_flush = ffi_lib.steppersync_flush
self.steppersync_history_expire = ffi_lib.steppersync_history_expire self.steppersync_history_expire = ffi_lib.steppersync_history_expire
# Flush notification callbacks
self.flush_callbacks = []
# History expiration
self.clear_history_time = 0. self.clear_history_time = 0.
is_debug = self.printer.get_start_args().get('debugoutput') is not None # Flush tracking
self.is_debugoutput = is_debug 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): def allocate_trapq(self):
ffi_main, ffi_lib = chelper.get_ffi() ffi_main, ffi_lib = chelper.get_ffi()
trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free)
@ -53,8 +83,7 @@ class PrinterMotionQueuing:
fcbs = list(self.flush_callbacks) fcbs = list(self.flush_callbacks)
fcbs.remove(callback) fcbs.remove(callback)
self.flush_callbacks = fcbs 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) # Invoke flush callbacks (if any)
for cb in self.flush_callbacks: for cb in self.flush_callbacks:
cb(must_flush_time, max_step_gen_time) cb(must_flush_time, max_step_gen_time)
@ -72,8 +101,9 @@ class PrinterMotionQueuing:
raise mcu.error("Internal error in MCU '%s' stepcompress" raise mcu.error("Internal error in MCU '%s' stepcompress"
% (mcu.get_name(),)) % (mcu.get_name(),))
# Determine maximum history to keep # Determine maximum history to keep
trapq_free_time = max_step_gen_time - self.kin_flush_delay
clear_history_time = self.clear_history_time 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 clear_history_time = trapq_free_time - MOVE_HISTORY_EXPIRE
# Move processed trapq moves to history list, and expire old history # Move processed trapq moves to history list, and expire old history
for trapq in self.trapqs: for trapq in self.trapqs:
@ -85,16 +115,127 @@ class PrinterMotionQueuing:
self.steppersync_history_expire(ss, clock) self.steppersync_history_expire(ss, clock)
def wipe_trapq(self, trapq): def wipe_trapq(self, trapq):
# Expire any remaining movement in the trapq (force to history list) # Expire any remaining movement in the trapq (force to history list)
NEVER = 9999999999999999. self.trapq_finalize_moves(trapq, self.reactor.NEVER, 0.)
self.trapq_finalize_moves(trapq, NEVER, 0.)
def lookup_trapq_append(self): def lookup_trapq_append(self):
ffi_main, ffi_lib = chelper.get_ffi() ffi_main, ffi_lib = chelper.get_ffi()
return ffi_lib.trapq_append return ffi_lib.trapq_append
def stats(self, eventtime): def stats(self, eventtime):
mcu = self.printer.lookup_object('mcu') # Hack to globally invoke mcu check_active()
est_print_time = mcu.estimated_print_time(eventtime) 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 self.clear_history_time = est_print_time - MOVE_HISTORY_EXPIRE
return False, "" 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): def load_config(config):
return PrinterMotionQueuing(config) return PrinterMotionQueuing(config)

View File

@ -20,8 +20,8 @@ class GCodeRequestQueue:
self.rqueue = [] self.rqueue = []
self.next_min_flush_time = 0. self.next_min_flush_time = 0.
self.toolhead = None self.toolhead = None
motion_queuing = printer.load_object(config, 'motion_queuing') self.motion_queuing = printer.load_object(config, 'motion_queuing')
motion_queuing.register_flush_callback(self._flush_notification) self.motion_queuing.register_flush_callback(self._flush_notification)
printer.register_event_handler("klippy:connect", self._handle_connect) printer.register_event_handler("klippy:connect", self._handle_connect)
def _handle_connect(self): def _handle_connect(self):
self.toolhead = self.printer.lookup_object('toolhead') self.toolhead = self.printer.lookup_object('toolhead')
@ -51,11 +51,12 @@ class GCodeRequestQueue:
del rqueue[:pos+1] del rqueue[:pos+1]
self.next_min_flush_time = next_time + max(min_wait, min_sched_time) self.next_min_flush_time = next_time + max(min_wait, min_sched_time)
# Ensure following queue items are flushed # Ensure following queue items are flushed
self.toolhead.note_mcu_movequeue_activity(self.next_min_flush_time, self.motion_queuing.note_mcu_movequeue_activity(
is_step_gen=False) self.next_min_flush_time, is_step_gen=False)
def _queue_request(self, print_time, value): def _queue_request(self, print_time, value):
self.rqueue.append((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): def queue_gcode_request(self, value):
self.toolhead.register_lookahead_callback( self.toolhead.register_lookahead_callback(
(lambda pt: self._queue_request(pt, value))) (lambda pt: self._queue_request(pt, value)))

View File

@ -16,8 +16,8 @@ class MCU_queued_pwm:
self._max_duration = 2. self._max_duration = 2.
self._oid = oid = mcu.create_oid() self._oid = oid = mcu.create_oid()
printer = mcu.get_printer() printer = mcu.get_printer()
motion_queuing = printer.load_object(config, 'motion_queuing') self._motion_queuing = printer.load_object(config, 'motion_queuing')
self._stepqueue = motion_queuing.allocate_stepcompress(mcu, oid) self._stepqueue = self._motion_queuing.allocate_stepcompress(mcu, oid)
ffi_main, ffi_lib = chelper.get_ffi() ffi_main, ffi_lib = chelper.get_ffi()
self._stepcompress_queue_mq_msg = ffi_lib.stepcompress_queue_mq_msg self._stepcompress_queue_mq_msg = ffi_lib.stepcompress_queue_mq_msg
mcu.register_config_callback(self._build_config) mcu.register_config_callback(self._build_config)
@ -62,8 +62,8 @@ class MCU_queued_pwm:
if self._duration_ticks >= 1<<31: if self._duration_ticks >= 1<<31:
raise config_error("PWM pin max duration too large") raise config_error("PWM pin max duration too large")
if self._duration_ticks: if self._duration_ticks:
motion_queuing = printer.lookup_object('motion_queuing') self._motion_queuing.register_flush_callback(
motion_queuing.register_flush_callback(self._flush_notification) self._flush_notification)
if self._hardware_pwm: if self._hardware_pwm:
self._pwm_max = self._mcu.get_constant_float("PWM_MAX") self._pwm_max = self._mcu.get_constant_float("PWM_MAX")
self._default_value = self._shutdown_value * self._pwm_max self._default_value = self._shutdown_value * self._pwm_max
@ -116,7 +116,7 @@ class MCU_queued_pwm:
# Continue flushing to resend time # Continue flushing to resend time
wakeclock += self._duration_ticks wakeclock += self._duration_ticks
wake_print_time = self._mcu.clock_to_print_time(wakeclock) wake_print_time = self._mcu.clock_to_print_time(wakeclock)
self._toolhead.note_mcu_movequeue_activity(wake_print_time, self._motion_queuing.note_mcu_movequeue_activity(wake_print_time,
is_step_gen=False) is_step_gen=False)
def set_pwm(self, print_time, value): def set_pwm(self, print_time, value):
clock = self._mcu.print_time_to_clock(print_time) clock = self._mcu.print_time_to_clock(print_time)

View File

@ -80,10 +80,10 @@ class SHT3X:
def _init_sht3x(self): def _init_sht3x(self):
# Device Soft Reset # Device Soft Reset
self.i2c.i2c_write_wait_ack(SHT3X_CMD['OTHER']['BREAK']) self.i2c.i2c_write(SHT3X_CMD['OTHER']['BREAK'])
# Break takes ~ 1ms # Break takes ~ 1ms
self.reactor.pause(self.reactor.monotonic() + .0015) 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 # Wait <=1.5ms after reset
self.reactor.pause(self.reactor.monotonic() + .0015) self.reactor.pause(self.reactor.monotonic() + .0015)
@ -97,7 +97,7 @@ class SHT3X:
logging.warning("sht3x: Reading status - checksum error!") logging.warning("sht3x: Reading status - checksum error!")
# Enable periodic mode # Enable periodic mode
self.i2c.i2c_write_wait_ack( self.i2c.i2c_write(
SHT3X_CMD['PERIODIC']['2HZ']['HIGH_REP'] SHT3X_CMD['PERIODIC']['2HZ']['HIGH_REP']
) )
# Wait <=15.5ms for first measurement # Wait <=15.5ms for first measurement

View File

@ -1,6 +1,6 @@
# Support for enable pins on stepper motor drivers # Support for enable pins on stepper motor drivers
# #
# Copyright (C) 2019-2021 Kevin O'Connor <kevin@koconnor.net> # Copyright (C) 2019-2025 Kevin O'Connor <kevin@koconnor.net>
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import logging import logging
@ -88,27 +88,35 @@ class PrinterStepperEnable:
name = mcu_stepper.get_name() name = mcu_stepper.get_name()
enable = setup_enable_pin(self.printer, config.get('enable_pin', None)) enable = setup_enable_pin(self.printer, config.get('enable_pin', None))
self.enable_lines[name] = EnableTracking(mcu_stepper, enable) self.enable_lines[name] = EnableTracking(mcu_stepper, enable)
def motor_off(self): def set_motors_enable(self, stepper_names, enable):
toolhead = self.printer.lookup_object('toolhead') 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) toolhead.dwell(DISABLE_STALL_TIME)
print_time = toolhead.get_last_move_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: if enable:
el.motor_enable(print_time) el.motor_enable(print_time)
logging.info("%s has been manually enabled", stepper)
else: else:
el.motor_disable(print_time) el.motor_disable(print_time)
logging.info("%s has been manually disabled", stepper) 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) 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.get_kinematics().clear_homing_state("xyz")
self.printer.send_event("stepper_enable:motor_off")
def get_status(self, eventtime): def get_status(self, eventtime):
steppers = { name: et.is_motor_enabled() steppers = { name: et.is_motor_enabled()
for (name, et) in self.enable_lines.items() } for (name, et) in self.enable_lines.items() }
@ -126,7 +134,11 @@ class PrinterStepperEnable:
% (stepper_name,)) % (stepper_name,))
return return
stepper_enable = gcmd.get_int('ENABLE', 1) 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): def lookup_enable(self, name):
if name not in self.enable_lines: if name not in self.enable_lines:
raise self.printer.config_error("Unknown stepper '%s'" % (name,)) raise self.printer.config_error("Unknown stepper '%s'" % (name,))

View File

@ -29,7 +29,6 @@ class SX1509(object):
self._ppins = self._printer.lookup_object("pins") self._ppins = self._printer.lookup_object("pins")
self._ppins.register_chip("sx1509_" + self._name, self) self._ppins.register_chip("sx1509_" + self._name, self)
self._mcu = self._i2c.get_mcu() self._mcu = self._i2c.get_mcu()
self._mcu.register_config_callback(self._build_config)
self._oid = self._i2c.get_oid() self._oid = self._i2c.get_oid()
self._last_clock = 0 self._last_clock = 0
# Set up registers default values # Set up registers default values
@ -37,22 +36,26 @@ class SX1509(object):
REG_PULLUP : 0, REG_PULLDOWN : 0, REG_PULLUP : 0, REG_PULLDOWN : 0,
REG_INPUT_DISABLE : 0, REG_ANALOG_DRIVER_ENABLE : 0} REG_INPUT_DISABLE : 0, REG_ANALOG_DRIVER_ENABLE : 0}
self.reg_i_on_dict = {reg : 0 for reg in REG_I_ON} 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 # Reset the chip, Default RegClock/RegMisc 0x0
self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % ( self._i2c.i2c_write([REG_RESET, 0x12])
self._oid, REG_RESET, 0x12)) self._i2c.i2c_write([REG_RESET, 0x34])
self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % (
self._oid, REG_RESET, 0x34))
# Enable Oscillator # Enable Oscillator
self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % ( self._i2c.i2c_write([REG_CLOCK, (1 << 6)])
self._oid, REG_CLOCK, (1 << 6)))
# Setup Clock Divider # Setup Clock Divider
self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%02x" % ( self._i2c.i2c_write([REG_MISC, (1 << 4)])
self._oid, REG_MISC, (1 << 4)))
# Transfer all regs with their initial cached state # Transfer all regs with their initial cached state
for _reg, _data in self.reg_dict.items(): reactor = self._mcu.get_printer().get_reactor()
self._mcu.add_config_cmd("i2c_write oid=%d data=%02x%04x" % ( for _reg in self.reg_dict:
self._oid, _reg, _data), is_init=True) 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): def setup_pin(self, pin_type, pin_params):
if pin_type == 'digital_out' and pin_params['pin'][0:4] == "PIN_": if pin_type == 'digital_out' and pin_params['pin'][0:4] == "PIN_":
return SX1509_digital_out(self, pin_params) 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") raise pins.error("SX1509_pwm must have hardware_pwm enabled")
if self._max_duration: if self._max_duration:
raise pins.error("SX1509 pins are not suitable for heaters") 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): def get_mcu(self):
return self._mcu return self._mcu
def setup_max_duration(self, max_duration): def setup_max_duration(self, max_duration):
@ -181,6 +175,8 @@ class SX1509_pwm(object):
shutdown_value = 1. - shutdown_value shutdown_value = 1. - shutdown_value
self._start_value = max(0., min(1., start_value)) self._start_value = max(0., min(1., start_value))
self._shutdown_value = max(0., min(1., shutdown_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): def set_pwm(self, print_time, value):
self._sx1509.set_register(self._i_on_reg, ~int(255 * value) self._sx1509.set_register(self._i_on_reg, ~int(255 * value)
if not self._invert if not self._invert

View File

@ -132,7 +132,7 @@ class TemperatureProbe:
self.start_pos = [] self.start_pos = []
# Register GCode Commands # Register GCode Commands
pname = self.name.split(maxsplit=1)[-1] pname = self.name.split(None, 1)[-1]
self.gcode.register_mux_command( self.gcode.register_mux_command(
"TEMPERATURE_PROBE_CALIBRATE", "PROBE", pname, "TEMPERATURE_PROBE_CALIBRATE", "PROBE", pname,
self.cmd_TEMPERATURE_PROBE_CALIBRATE, self.cmd_TEMPERATURE_PROBE_CALIBRATE,
@ -357,8 +357,8 @@ class TemperatureProbe:
self._check_homed() self._check_homed()
probe = self._get_probe() probe = self._get_probe()
probe_name = probe.get_status(None)["name"] probe_name = probe.get_status(None)["name"]
short_name = probe_name.split(maxsplit=1)[-1] short_name = probe_name.split(None, 1)[-1]
if short_name != self.name.split(maxsplit=1)[-1]: if short_name != self.name.split(None, 1)[-1]:
raise self.gcode.error( raise self.gcode.error(
"[%s] not linked to registered probe [%s]." "[%s] not linked to registered probe [%s]."
% (self.name, probe_name) % (self.name, probe_name)
@ -588,7 +588,7 @@ class EddyDriftCompensation:
temps[idx] = cur_temp temps[idx] = cur_temp
probe_samples[idx].append(sample) probe_samples[idx].append(sample)
return True 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) self.printer.lookup_object(sect_name).add_client(_on_bulk_data_recd)
for i in range(DRIFT_SAMPLE_COUNT): for i in range(DRIFT_SAMPLE_COUNT):
if i == 0: if i == 0:

View File

@ -262,6 +262,9 @@ FieldFormatters.update({
"adc_temp": (lambda v: "0x%04x(%.1fC)" % (v, ((v - 2038) / 7.7))), "adc_temp": (lambda v: "0x%04x(%.1fC)" % (v, ((v - 2038) / 7.7))),
"adc_vsupply": (lambda v: "0x%04x(%.3fV)" % (v, v * 0.009732)), "adc_vsupply": (lambda v: "0x%04x(%.3fV)" % (v, v * 0.009732)),
"adc_ain": (lambda v: "0x%04x(%.3fmV)" % (v, v * 0.3052)), "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))),
}) })

View File

@ -79,7 +79,7 @@ class ZAdjustStatus:
self.applied = False self.applied = False
def get_status(self, eventtime): def get_status(self, eventtime):
return {'applied': self.applied} return {'applied': self.applied}
def _motor_off(self, print_time): def _motor_off(self):
self.reset() self.reset()
class RetryHelper: class RetryHelper:

View File

@ -430,7 +430,6 @@ class GCodeIO:
self.gcode.request_restart('exit') self.gcode.request_restart('exit')
pending_commands.append("") pending_commands.append("")
# Handle case where multiple commands pending # Handle case where multiple commands pending
if self.is_processing_data or len(pending_commands) > 1:
if len(pending_commands) < 20: if len(pending_commands) < 20:
# Check for M112 out-of-order # Check for M112 out-of-order
for line in lines: for line in lines:

View File

@ -69,11 +69,13 @@ class ExtruderStepper:
if not pressure_advance: if not pressure_advance:
new_smooth_time = 0. new_smooth_time = 0.
toolhead = self.printer.lookup_object("toolhead") 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() ffi_main, ffi_lib = chelper.get_ffi()
espa = ffi_lib.extruder_set_pressure_advance espa = ffi_lib.extruder_set_pressure_advance
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( toolhead.register_lookahead_callback(
lambda print_time: espa(self.sk_extruder, print_time, lambda print_time: espa(self.sk_extruder, print_time,
pressure_advance, new_smooth_time)) pressure_advance, new_smooth_time))

View File

@ -84,7 +84,7 @@ class CommandQueryWrapper:
# Wrapper around command sending # Wrapper around command sending
class CommandWrapper: class CommandWrapper:
def __init__(self, serial, msgformat, cmd_queue=None): def __init__(self, serial, msgformat, cmd_queue=None, debugoutput=False):
self._serial = serial self._serial = serial
msgparser = serial.get_msgparser() msgparser = serial.get_msgparser()
self._cmd = msgparser.lookup_command(msgformat) self._cmd = msgparser.lookup_command(msgformat)
@ -92,6 +92,9 @@ class CommandWrapper:
cmd_queue = serial.get_default_command_queue() cmd_queue = serial.get_default_command_queue()
self._cmd_queue = cmd_queue self._cmd_queue = cmd_queue
self._msgtag = msgparser.lookup_msgid(msgformat) & 0xffffffff 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): def send(self, data=(), minclock=0, reqclock=0):
cmd = self._cmd.encode(data) cmd = self._cmd.encode(data)
self._serial.raw_send(cmd, minclock, reqclock, self._cmd_queue) self._serial.raw_send(cmd, minclock, reqclock, self._cmd_queue)
@ -888,7 +891,8 @@ class MCU:
def alloc_command_queue(self): def alloc_command_queue(self):
return self._serial.alloc_command_queue() return self._serial.alloc_command_queue()
def lookup_command(self, msgformat, cq=None): 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, def lookup_query_command(self, msgformat, respformat, oid=None,
cq=None, is_async=False): cq=None, is_async=False):
return CommandQueryWrapper(self._serial, msgformat, respformat, oid, return CommandQueryWrapper(self._serial, msgformat, respformat, oid,

View File

@ -1,6 +1,6 @@
# File descriptor and timer event helper # File descriptor and timer event helper
# #
# Copyright (C) 2016-2020 Kevin O'Connor <kevin@koconnor.net> # Copyright (C) 2016-2025 Kevin O'Connor <kevin@koconnor.net>
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import os, gc, select, math, time, logging, queue import os, gc, select, math, time, logging, queue
@ -14,6 +14,7 @@ class ReactorTimer:
def __init__(self, callback, waketime): def __init__(self, callback, waketime):
self.callback = callback self.callback = callback
self.waketime = waketime self.waketime = waketime
self.timer_is_running = False
class ReactorCompletion: class ReactorCompletion:
class sentinel: pass class sentinel: pass
@ -118,6 +119,8 @@ class SelectReactor:
return tuple(self._last_gc_times) return tuple(self._last_gc_times)
# Timers # Timers
def update_timer(self, timer_handler, waketime): def update_timer(self, timer_handler, waketime):
if timer_handler.timer_is_running:
return
timer_handler.waketime = waketime timer_handler.waketime = waketime
self._next_timer = min(self._next_timer, waketime) self._next_timer = min(self._next_timer, waketime)
def register_timer(self, callback, waketime=NEVER): def register_timer(self, callback, waketime=NEVER):
@ -155,7 +158,9 @@ class SelectReactor:
waketime = t.waketime waketime = t.waketime
if eventtime >= waketime: if eventtime >= waketime:
t.waketime = self.NEVER t.waketime = self.NEVER
t.timer_is_running = True
t.waketime = waketime = t.callback(eventtime) t.waketime = waketime = t.callback(eventtime)
t.timer_is_running = False
if g_dispatch is not self._g_dispatch: if g_dispatch is not self._g_dispatch:
self._next_timer = min(self._next_timer, waketime) self._next_timer = min(self._next_timer, waketime)
self._end_greenlet(g_dispatch) self._end_greenlet(g_dispatch)

View File

@ -193,25 +193,13 @@ class LookAheadQueue:
BUFFER_TIME_LOW = 1.0 BUFFER_TIME_LOW = 1.0
BUFFER_TIME_HIGH = 2.0 BUFFER_TIME_HIGH = 2.0
BUFFER_TIME_START = 0.250 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 # Main code to track events (and their timing) on the printer toolhead
class ToolHead: class ToolHead:
def __init__(self, config): def __init__(self, config):
self.printer = config.get_printer() self.printer = config.get_printer()
self.reactor = self.printer.get_reactor() self.reactor = self.printer.get_reactor()
self.all_mcus = [ self.mcu = self.printer.lookup_object('mcu')
m for n, m in self.printer.lookup_objects(module='mcu')]
self.mcu = self.all_mcus[0]
self.lookahead = LookAheadQueue() self.lookahead = LookAheadQueue()
self.lookahead.set_flush_time(BUFFER_TIME_HIGH) self.lookahead.set_flush_time(BUFFER_TIME_HIGH)
self.commanded_pos = [0., 0., 0., 0.] self.commanded_pos = [0., 0., 0., 0.]
@ -236,16 +224,10 @@ class ToolHead:
self.print_time = 0. self.print_time = 0.
self.special_queuing_state = "NeedPrime" self.special_queuing_state = "NeedPrime"
self.priming_timer = None 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 # Setup for generating moves
self.motion_queuing = self.printer.load_object(config, 'motion_queuing') 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 = self.motion_queuing.allocate_trapq()
self.trapq_append = self.motion_queuing.lookup_trapq_append() self.trapq_append = self.motion_queuing.lookup_trapq_append()
# Create kinematics class # Create kinematics class
@ -268,33 +250,15 @@ class ToolHead:
# Register handlers # Register handlers
self.printer.register_event_handler("klippy:shutdown", self.printer.register_event_handler("klippy:shutdown",
self._handle_shutdown) self._handle_shutdown)
# Print time and flush tracking # Print time 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
def _advance_move_time(self, next_print_time): 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) self.print_time = max(self.print_time, next_print_time)
want_flush_time = max(flush_time, self.print_time - pt_delay) self.motion_queuing.advance_flush_time(self.print_time,
while 1: lazy_target=True)
flush_time = min(flush_time + MOVE_BATCH_TIME, want_flush_time)
self._advance_flush_time(flush_time)
if flush_time >= want_flush_time:
break
def _calc_print_time(self): def _calc_print_time(self):
curtime = self.reactor.monotonic() curtime = self.reactor.monotonic()
est_print_time = self.mcu.estimated_print_time(curtime) 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.motion_queuing.calc_step_gen_restart(est_print_time)
kin_time += self.kin_flush_delay
min_print_time = max(est_print_time + BUFFER_TIME_START, kin_time) min_print_time = max(est_print_time + BUFFER_TIME_START, kin_time)
if min_print_time > self.print_time: if min_print_time > self.print_time:
self.print_time = min_print_time self.print_time = min_print_time
@ -328,10 +292,10 @@ class ToolHead:
for cb in move.timing_callbacks: for cb in move.timing_callbacks:
cb(next_move_time) cb(next_move_time)
# Generate steps for moves # 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) self._advance_move_time(next_move_time)
def _flush_lookahead(self): 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._process_lookahead()
self.special_queuing_state = "NeedPrime" self.special_queuing_state = "NeedPrime"
self.need_check_pause = -1. self.need_check_pause = -1.
@ -339,8 +303,7 @@ class ToolHead:
self.check_stall_time = 0. self.check_stall_time = 0.
def flush_step_generation(self): def flush_step_generation(self):
self._flush_lookahead() self._flush_lookahead()
self._advance_flush_time(self.step_gen_time) self.motion_queuing.flush_all_steps()
self.min_restart_time = max(self.min_restart_time, self.print_time)
def get_last_move_time(self): def get_last_move_time(self):
if self.special_queuing_state: if self.special_queuing_state:
self._flush_lookahead() self._flush_lookahead()
@ -391,11 +354,11 @@ class ToolHead:
logging.exception("Exception in priming_handler") logging.exception("Exception in priming_handler")
self.printer.invoke_shutdown("Exception in priming_handler") self.printer.invoke_shutdown("Exception in priming_handler")
return self.reactor.NEVER return self.reactor.NEVER
def _flush_handler(self, eventtime): def _check_flush_lookahead(self, eventtime):
try: if self.special_queuing_state:
est_print_time = self.mcu.estimated_print_time(eventtime) return None
if not self.special_queuing_state:
# In "main" state - flush lookahead if buffer runs low # In "main" state - flush lookahead if buffer runs low
est_print_time = self.mcu.estimated_print_time(eventtime)
print_time = self.print_time print_time = self.print_time
buffer_time = print_time - est_print_time buffer_time = print_time - est_print_time
if buffer_time > BUFFER_TIME_LOW: if buffer_time > BUFFER_TIME_LOW:
@ -405,21 +368,7 @@ class ToolHead:
self._flush_lookahead() self._flush_lookahead()
if print_time != self.print_time: if print_time != self.print_time:
self.check_stall_time = self.print_time self.check_stall_time = self.print_time
# In "NeedPrime"/"Priming" state - flush queues if needed return None
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
# Movement commands # Movement commands
def get_position(self): def get_position(self):
return list(self.commanded_pos) return list(self.commanded_pos)
@ -491,32 +440,6 @@ class ToolHead:
def get_extra_axes(self): def get_extra_axes(self):
return [None, None, None] + self.extra_axes return [None, None, None] + self.extra_axes
# Homing "drip move" handling # 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): def _drip_load_trapq(self, submit_move):
# Queue move into trapezoid motion queue (trapq) # Queue move into trapezoid motion queue (trapq)
if submit_move.move_d: if submit_move.move_d:
@ -524,18 +447,17 @@ class ToolHead:
self.lookahead.add_move(submit_move) self.lookahead.add_move(submit_move)
moves = self.lookahead.flush() moves = self.lookahead.flush()
self._calc_print_time() self._calc_print_time()
next_move_time = self.print_time start_time = end_time = self.print_time
for move in moves: for move in moves:
self.trapq_append( self.trapq_append(
self.trapq, next_move_time, self.trapq, end_time,
move.accel_t, move.cruise_t, move.decel_t, move.accel_t, move.cruise_t, move.decel_t,
move.start_pos[0], move.start_pos[1], move.start_pos[2], 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.axes_r[0], move.axes_r[1], move.axes_r[2],
move.start_v, move.cruise_v, move.accel) move.start_v, move.cruise_v, move.accel)
next_move_time = (next_move_time + move.accel_t end_time = end_time + move.accel_t + move.cruise_t + move.decel_t
+ move.cruise_t + move.decel_t)
self.lookahead.reset() self.lookahead.reset()
return next_move_time return start_time, end_time
def drip_move(self, newpos, speed, drip_completion): def drip_move(self, newpos, speed, drip_completion):
# Create and verify move is valid # Create and verify move is valid
newpos = newpos[:3] + self.commanded_pos[3:] newpos = newpos[:3] + self.commanded_pos[3:]
@ -543,23 +465,20 @@ class ToolHead:
if move.move_d: if move.move_d:
self.kin.check_move(move) self.kin.check_move(move)
# Make sure stepper movement doesn't start before nominal start time # 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 # Transmit move in "drip" mode
self._process_lookahead() self._process_lookahead()
next_move_time = self._drip_load_trapq(move) start_time, end_time = self._drip_load_trapq(move)
self.drip_update_time(next_move_time, drip_completion) self.motion_queuing.drip_update_time(start_time, end_time,
drip_completion)
# Move finished; cleanup any remnants on trapq # Move finished; cleanup any remnants on trapq
self.motion_queuing.wipe_trapq(self.trapq) self.motion_queuing.wipe_trapq(self.trapq)
# Misc commands # Misc commands
def stats(self, eventtime): 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) est_print_time = self.mcu.estimated_print_time(eventtime)
buffer_time = self.print_time - est_print_time buffer_time = self.print_time - est_print_time
is_active = buffer_time > -60. or not self.special_queuing_state 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" % ( return is_active, "print_time=%.3f buffer_time=%.3f print_stall=%d" % (
self.print_time, max(buffer_time, 0.), self.print_stall) self.print_time, max(buffer_time, 0.), self.print_stall)
def check_busy(self, eventtime): def check_busy(self, eventtime):
@ -588,27 +507,12 @@ class ToolHead:
return self.kin return self.kin
def get_trapq(self): def get_trapq(self):
return self.trapq 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): def register_lookahead_callback(self, callback):
last_move = self.lookahead.get_last() last_move = self.lookahead.get_last()
if last_move is None: if last_move is None:
callback(self.get_last_move_time()) callback(self.get_last_move_time())
return return
last_move.timing_callbacks.append(callback) 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): def get_max_velocity(self):
return self.max_velocity, self.max_accel return self.max_velocity, self.max_accel
def _calc_junction_deviation(self): def _calc_junction_deviation(self):
@ -662,7 +566,7 @@ class ToolHeadCommandHelper:
msg = ("max_velocity: %.6f\n" msg = ("max_velocity: %.6f\n"
"max_accel: %.6f\n" "max_accel: %.6f\n"
"minimum_cruise_ratio: %.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,)) self.printer.set_rollover_info("toolhead", "toolhead: %s" % (msg,))
if (max_velocity is None and max_accel is None if (max_velocity is None and max_accel is None
and square_corner_velocity is None and min_cruise_ratio is None): and square_corner_velocity is None and min_cruise_ratio is None):

View File

@ -170,6 +170,14 @@ BOARD_DEFS = {
"firmware_path": "ZNP_ROBIN_NANO.bin", "firmware_path": "ZNP_ROBIN_NANO.bin",
"current_firmware_path": "ZNP_ROBIN_NANO.CUR" "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': { 'qidi-x7': {
'mcu': "stm32f401xc", 'mcu': "stm32f401xc",
'spi_bus': "spi2", 'spi_bus': "spi2",
@ -228,6 +236,9 @@ BOARD_ALIASES = {
'robin_v3': BOARD_DEFS['monster8'], 'robin_v3': BOARD_DEFS['monster8'],
'btt-skrat-v1.0': BOARD_DEFS['btt-skrat'], 'btt-skrat-v1.0': BOARD_DEFS['btt-skrat'],
'chitu-v6': BOARD_DEFS['chitu-v6'], '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-q1-pro': BOARD_DEFS['qidi-x7'],
'qidi-plus4': BOARD_DEFS['qidi-x7'] 'qidi-plus4': BOARD_DEFS['qidi-x7']
} }

View File

@ -302,7 +302,7 @@ choice
config STM32_FLASH_START_C000 config STM32_FLASH_START_C000
bool "48KiB bootloader" if MACH_STM32F4x5 || MACH_STM32F401 bool "48KiB bootloader" if MACH_STM32F4x5 || MACH_STM32F401
config STM32_FLASH_START_10000 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 config STM32_FLASH_START_800
bool "2KiB bootloader" if MACH_STM32F103 bool "2KiB bootloader" if MACH_STM32F103

View File

@ -94,7 +94,7 @@ src-$(CONFIG_HAVE_GPIO_SDIO) += stm32/sdio.c
target-y += $(OUT)klipper.bin target-y += $(OUT)klipper.bin
$(OUT)klipper.bin: $(OUT)klipper.elf $(OUT)klipper.bin: $(OUT)klipper.elf
@echo " Creating hex file $@" @echo " Creating bin file $@"
$(Q)$(OBJCOPY) -O binary $< $@ $(Q)$(OBJCOPY) -O binary $< $@
# Flash rules # Flash rules

View File

@ -14,6 +14,8 @@
#include "sched.h" // sched_shutdown #include "sched.h" // sched_shutdown
#include "n32g45x_adc.h" // ADC #include "n32g45x_adc.h" // ADC
#define ADC_INVALID_PIN 0xFF
DECL_CONSTANT("ADC_MAX", 4095); DECL_CONSTANT("ADC_MAX", 4095);
#define ADC_TEMPERATURE_PIN 0xfe #define ADC_TEMPERATURE_PIN 0xfe
@ -21,42 +23,42 @@ DECL_ENUMERATION("pin", "ADC_TEMPERATURE", ADC_TEMPERATURE_PIN);
static const uint8_t adc_pins[] = { static const uint8_t adc_pins[] = {
// ADC1 // ADC1
0, GPIO('A', 0), GPIO('A', 1), GPIO('A', 6), ADC_INVALID_PIN, GPIO('A', 0), GPIO('A', 1), GPIO('A', 6),
GPIO('A', 3), GPIO('F', 4), 0, 0, GPIO('A', 3), GPIO('F', 4), ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
ADC_TEMPERATURE_PIN, 0, 0, 0, ADC_TEMPERATURE_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
// ADC2 // 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('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', 2), GPIO('C', 3), GPIO('F', 2), GPIO('A', 2),
GPIO('C', 5), GPIO('B', 2), 0, 0, GPIO('C', 5), GPIO('B', 2), ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
#if CONFIG_MACH_N32G455 // ADC3/4 for G455 only #if CONFIG_MACH_N32G455 // ADC3/4 for G455 only
// ADC3 // 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('E', 12), GPIO('B', 13), GPIO('E', 8), GPIO('D', 10),
GPIO('D', 11), GPIO('D', 12), GPIO('D', 13), GPIO('D', 14), GPIO('D', 11), GPIO('D', 12), GPIO('D', 13), GPIO('D', 14),
GPIO('B', 0), GPIO('E', 7), GPIO('E', 10), GPIO('E', 11), GPIO('B', 0), GPIO('E', 7), GPIO('E', 10), GPIO('E', 11),
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
// ADC4 // ADC4
0, GPIO('E', 14), GPIO('E', 15), GPIO('B', 12), ADC_INVALID_PIN, GPIO('E', 14), GPIO('E', 15), GPIO('B', 12),
GPIO('B', 14), GPIO('B', 15), 0, 0, GPIO('B', 14), GPIO('B', 15), ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
GPIO('D', 8), GPIO('D', 9), 0, 0, GPIO('D', 8), GPIO('D', 9), ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
0, 0, 0, 0, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN, ADC_INVALID_PIN,
#endif #endif
}; };

View File

@ -66,3 +66,19 @@ max_velocity: 300
max_accel: 3000 max_accel: 3000
max_z_velocity: 5 max_z_velocity: 5
max_z_accel: 100 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

View File

@ -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 MOVE=12 SPEED=12 ACCEL=9000.2
MANUAL_STEPPER STEPPER=basic_stepper ENABLE=0 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 # Test homing move
MANUAL_STEPPER STEPPER=homing_stepper ENABLE=1 MANUAL_STEPPER STEPPER=homing_stepper ENABLE=1
MANUAL_STEPPER STEPPER=homing_stepper SET_POSITION=0 MANUAL_STEPPER STEPPER=homing_stepper SET_POSITION=0
@ -29,6 +33,10 @@ G28
G1 X20 Y20 Z10 G1 X20 Y20 Z10
G1 A10 X22 G1 A10 X22
# Verify position query commands work with extra axis
GET_POSITION
M114
# Test unregistering # Test unregistering
MANUAL_STEPPER STEPPER=basic_stepper GCODE_AXIS= MANUAL_STEPPER STEPPER=basic_stepper GCODE_AXIS=
G1 X15 G1 X15