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
# 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:

View File

@ -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).

View File

@ -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)

View File

@ -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;
}

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);
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

View File

@ -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)
{

View File

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

View File

@ -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

View File

@ -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);

View File

@ -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)
def get_status(self, eventtime):
data = {

View File

@ -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,14 +214,8 @@ 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)
def i2c_read(self, write, read_len, retry=True):

View File

@ -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:

View File

@ -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)

View File

@ -1,6 +1,6 @@
# 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.
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()
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')
enable = stepper_enable.lookup_enable(stepper.get_name())
enable.motor_disable(print_time)
toolhead.dwell(STALL_TIME)
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)

View File

@ -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:

View File

@ -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])",

View File

@ -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"

View File

@ -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],
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

View File

@ -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)

View File

@ -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)))

View File

@ -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,7 +116,7 @@ class MCU_queued_pwm:
# Continue flushing to resend time
wakeclock += self._duration_ticks
wake_print_time = self._mcu.clock_to_print_time(wakeclock)
self._toolhead.note_mcu_movequeue_activity(wake_print_time,
self._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)

View File

@ -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

View File

@ -1,6 +1,6 @@
# 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.
import logging
@ -88,27 +88,35 @@ 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 motor_off(self):
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()
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)
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.get_kinematics().clear_homing_state("xyz")
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() }
@ -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,))

View File

@ -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

View File

@ -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:

View File

@ -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))),
})

View File

@ -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:

View File

@ -430,7 +430,6 @@ 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:

View File

@ -69,11 +69,13 @@ 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
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))

View File

@ -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,

View File

@ -1,6 +1,6 @@
# 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.
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)

View File

@ -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,11 +354,11 @@ 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:
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:
@ -405,21 +368,7 @@ class ToolHead:
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
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):

View File

@ -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']
}

View File

@ -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

View File

@ -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

View File

@ -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
};

View File

@ -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

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 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