Compare commits

...

24 Commits

Author SHA1 Message Date
Andrei
7018075f70
Merge branch 'Klipper3d:master' into master 2025-08-17 06:13:48 +02:00
BIGTREETECH
d34d3b05b8
stm32: Add i2c2_PA7_PA6 and i2c3_PA7_PA6 for stm32g0 (#7007)
Signed-off-by: Alan.Ma from BigTreeTech <tech@biqu3d.com>
2025-08-15 13:43:43 -04:00
Kevin O'Connor
78462cff4c docs: Remove "relative_reference_index" documentation from Bed_Mesh.md
The "relative_reference_index" was deprecated on 20230619 and removed
on 20240215.  So, remove the last references from the documentation.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-15 12:57:19 -04:00
Timofey Titovets
edbfc6f856 resonance_tester: replace missing M204 call
Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
2025-08-14 15:10:13 -04:00
Kevin O'Connor
d6d8587289 motion_queuing: Remove clean_motion_queues()
Merge the clean_motion_queues() code into the existing
flush_motion_queues() code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
2919f37343 stepcompress: Store a reference to 'struct stepper_kinematics'
Support storing a reference to 'struct stepper_kinematics' in 'struct
stepcompress' and support globally generating steps via the
steppersync mechanism.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
dd4cc8eb4c itersolve: Do not store a reference to 'struct stepcompress'
Pass in the 'struct stepcompress' reference to each call of
itersolve_generate_steps().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
c520bf981d steppersync: Split steppersync code from stepcompress.c to new file
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
c454e88d9a stepper: Implement active callbacks via motion_queuing.register_flush_callback()
Use the existing register_flush_callback() system to implement motor
activity checking.  This simplifies the generate_steps() code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
b5e573957c motion_queuing: Move clear_history_time from toolhead to motion_queuing
Implement the 30 second clear_history_time offset checking directly in
the motion_queuing module.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
6d59279438 statistics: Avoid adding extra blank spaces on empty stats reports
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
1d569a6631 motion_queuing: Remove flush_steppersync()
Move code from flush_steppersync() to existing flush_motion_queues()
and clean_motion_queues() functions.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
7b25d1c06f stepcompress: Export steppersync_history_expire()
Don't implement history expiration from the main steppersync_flush()
code.  Instead, have callers directly invoke
steppersync_history_expire().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
864c78f24a motion_queueing: Add flush_steppersync()
Move the mcu.flush_moves() code to motion_queuing.flush_steppersync().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:35 -04:00
Kevin O'Connor
c09ca4cf5a motion_queuing: Add register_flush_callback()
Move register_flush_callback() from mcu.py code to motion_queuing
module.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
6f685e9e01 motion_queuing: Add allocate_stepcompress() call
Allocate the low-level C stepcompress object in the motion_queuing
module.  This simplifies the mcu.py code as it no longer needs to
track the stepqueues for the steppersync object.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
128226fe8a motion_queuing: Add allocate_steppersync() call
Allocate the low-level C steppersync object from the motion_queuing
module.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
5cbe7d83e8 motion_queuing: Track all trapqs and globally flush all trapqs
Add an allocate_trapq() helper function to facilitate the creation of
a low-level C trapq object.  Track all trapq objects and clear history
on them globally when the main motion queues are flushed.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
9399e738bc motion_queuing: Add new module to help with motion queues and flushing
Create a new module to assist with host management of motion queues.
Register all MCU_stepper objects with this module and use the module
for step generation.

All steppers will now automatically generate steps whenever
toolhead._advance_flush_time() is invoked.  It is no longer necessary
for callers to individually call stepper.generate_steps().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
126275d1f4 toolhead: Default note_mcu_movequeue_activity() to set step_gen_time
Change note_mcu_movequeue_activity() to default to setting the
step_gen_time (instead of the previous default to not set it).

Most users of the mcu "move queue" will be for stepper activity.
There is also little harm in incrementing the tracking of the last
possible step generation time, but accidentally generating a step
without incrementing the tracking can lead to very hard to debug
failures.

The two cases (output_pin.py and pwm_tool.py) where
note_mcu_movequeue_activity() is called and definitely not related to
step generation can explicitly pass 'is_step_gen=False'.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
f8da8099d5 toolhead: Move g-code command handlers to new ToolHeadCommandHelper() class
Move the g-code command handlers to a new class.  This reduces the
size of the main Toolhead() class.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
bcd4510958 toolhead: Move extra module loading out of core Toolhead() class
Load these extra modules from add_printer_objects() instead.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:43:34 -04:00
Kevin O'Connor
3ef760c18f toolhead: Remove support for max_accel_to_decel
This support was deprecated on 20240313.  Remove the remaining
compatibility code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2025-08-11 19:14:37 -04:00
Ben Lye
cfc58d3ce7
spi_flash: Add qidi-x7 to board_defs.py (#6979)
Added board definition for stm32f401xc-based Qidi X-7 board used in Qidi Q1 Pro and Plus4.

Signed-off-by: Ben Lye <ben@lye.co.nz>
2025-08-11 17:49:35 -04:00
35 changed files with 539 additions and 473 deletions

View File

@ -292,33 +292,6 @@ probe_count: 5, 3
z-offset. Note that this coordinate must NOT be in a location specified as z-offset. Note that this coordinate must NOT be in a location specified as
a `faulty_region` if a probe is necessary. a `faulty_region` if a probe is necessary.
#### The deprecated relative_reference_index
Existing configurations using the `relative_reference_index` option must be
updated to use the `zero_reference_position`. The response to the
[BED_MESH_OUTPUT PGP=1](#output) gcode command will include the (X, Y)
coordinate associated with the index; this position may be used as the value for
the `zero_reference_position`. The output will look similar to the following:
```
// bed_mesh: generated points
// Index | Tool Adjusted | Probe
// 0 | (1.0, 1.0) | (24.0, 6.0)
// 1 | (36.7, 1.0) | (59.7, 6.0)
// 2 | (72.3, 1.0) | (95.3, 6.0)
// 3 | (108.0, 1.0) | (131.0, 6.0)
... (additional generated points)
// bed_mesh: relative_reference_index 24 is (131.5, 108.0)
```
_Note: The above output is also printed in `klippy.log` during initialization._
Using the example above we see that the `relative_reference_index` is
printed along with its coordinate. Thus the `zero_reference_position`
is `131.5, 108`.
### Faulty Regions ### Faulty Regions
It is possible for some areas of a bed to report inaccurate results when It is possible for some areas of a bed to report inaccurate results when

View File

@ -8,6 +8,11 @@ All dates in this document are approximate.
## Changes ## Changes
20250811: Support for the `max_accel_to_decel` parameter in the
`[printer]` config section has been removed and support for the
`ACCEL_TO_DECEL` parameter in the `SET_VELOCITY_LIMIT` command has
been removed. These capabilities were deprecated on 20240313.
20250721: The `[pca9632]` and `[mcp4018]` modules no longer accept the 20250721: The `[pca9632]` and `[mcp4018]` modules no longer accept the
`scl_pin` and `sda_pin` options. Use `i2c_software_scl_pin` and `scl_pin` and `sda_pin` options. Use `i2c_software_scl_pin` and
`i2c_software_sda_pin` instead. `i2c_software_sda_pin` instead.

View File

@ -126,8 +126,6 @@ max_accel:
# decelerate to zero at each corner. The value specified here may be # decelerate to zero at each corner. The value specified here may be
# changed at runtime using the SET_VELOCITY_LIMIT command. The # changed at runtime using the SET_VELOCITY_LIMIT command. The
# default is 5mm/s. # default is 5mm/s.
#max_accel_to_decel:
# This parameter is deprecated and should no longer be used.
``` ```
### [stepper] ### [stepper]
@ -740,7 +738,6 @@ max_velocity:
max_accel: max_accel:
#minimum_cruise_ratio: #minimum_cruise_ratio:
#square_corner_velocity: #square_corner_velocity:
#max_accel_to_decel:
#max_z_velocity: #max_z_velocity:
#max_z_accel: #max_z_accel:

View File

@ -17,16 +17,16 @@ COMPILE_ARGS = ("-Wall -g -O2 -shared -fPIC"
" -o %s %s") " -o %s %s")
SSE_FLAGS = "-mfpmath=sse -msse2" SSE_FLAGS = "-mfpmath=sse -msse2"
SOURCE_FILES = [ SOURCE_FILES = [
'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'itersolve.c', 'trapq.c', 'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'steppersync.c',
'pollreactor.c', 'msgblock.c', 'trdispatch.c', 'itersolve.c', 'trapq.c', 'pollreactor.c', 'msgblock.c', 'trdispatch.c',
'kin_cartesian.c', 'kin_corexy.c', 'kin_corexz.c', 'kin_delta.c', 'kin_cartesian.c', 'kin_corexy.c', 'kin_corexz.c', 'kin_delta.c',
'kin_deltesian.c', 'kin_polar.c', 'kin_rotary_delta.c', 'kin_winch.c', 'kin_deltesian.c', 'kin_polar.c', 'kin_rotary_delta.c', 'kin_winch.c',
'kin_extruder.c', 'kin_shaper.c', 'kin_idex.c', 'kin_generic.c' 'kin_extruder.c', 'kin_shaper.c', 'kin_idex.c', 'kin_generic.c'
] ]
DEST_LIB = "c_helper.so" DEST_LIB = "c_helper.so"
OTHER_FILES = [ OTHER_FILES = [
'list.h', 'serialqueue.h', 'stepcompress.h', 'itersolve.h', 'pyhelper.h', 'list.h', 'serialqueue.h', 'stepcompress.h', 'steppersync.h',
'trapq.h', 'pollreactor.h', 'msgblock.h' 'itersolve.h', 'pyhelper.h', 'trapq.h', 'pollreactor.h', 'msgblock.h'
] ]
defs_stepcompress = """ defs_stepcompress = """
@ -54,25 +54,28 @@ defs_stepcompress = """
int stepcompress_extract_old(struct stepcompress *sc int stepcompress_extract_old(struct stepcompress *sc
, struct pull_history_steps *p, int max , struct pull_history_steps *p, int max
, uint64_t start_clock, uint64_t end_clock); , uint64_t start_clock, uint64_t end_clock);
void stepcompress_set_stepper_kinematics(struct stepcompress *sc
, struct stepper_kinematics *sk);
"""
defs_steppersync = """
struct steppersync *steppersync_alloc(struct serialqueue *sq struct steppersync *steppersync_alloc(struct serialqueue *sq
, struct stepcompress **sc_list, int sc_num, int move_num); , struct stepcompress **sc_list, int sc_num, int move_num);
void steppersync_free(struct steppersync *ss); void steppersync_free(struct steppersync *ss);
void steppersync_set_time(struct steppersync *ss void steppersync_set_time(struct steppersync *ss
, double time_offset, double mcu_freq); , double time_offset, double mcu_freq);
int steppersync_flush(struct steppersync *ss, uint64_t move_clock int32_t steppersync_generate_steps(struct steppersync *ss
, uint64_t clear_history_clock); , double gen_steps_time, uint64_t flush_clock);
void steppersync_history_expire(struct steppersync *ss, uint64_t end_clock);
int steppersync_flush(struct steppersync *ss, uint64_t move_clock);
""" """
defs_itersolve = """ defs_itersolve = """
int32_t itersolve_generate_steps(struct stepper_kinematics *sk
, double flush_time);
double itersolve_check_active(struct stepper_kinematics *sk double itersolve_check_active(struct stepper_kinematics *sk
, double flush_time); , 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
void itersolve_set_stepcompress(struct stepper_kinematics *sk , double step_dist);
, struct stepcompress *sc, double step_dist);
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
@ -228,7 +231,7 @@ defs_std = """
defs_all = [ defs_all = [
defs_pyhelper, defs_serialqueue, defs_std, defs_stepcompress, defs_pyhelper, defs_serialqueue, defs_std, defs_stepcompress,
defs_itersolve, defs_trapq, defs_trdispatch, defs_steppersync, defs_itersolve, defs_trapq, defs_trdispatch,
defs_kin_cartesian, defs_kin_corexy, defs_kin_corexz, defs_kin_delta, defs_kin_cartesian, defs_kin_corexy, defs_kin_corexz, defs_kin_delta,
defs_kin_deltesian, defs_kin_polar, defs_kin_rotary_delta, defs_kin_winch, defs_kin_deltesian, defs_kin_polar, defs_kin_rotary_delta, defs_kin_winch,
defs_kin_extruder, defs_kin_shaper, defs_kin_idex, defs_kin_extruder, defs_kin_shaper, defs_kin_idex,

View File

@ -26,8 +26,8 @@ struct timepos {
// Generate step times for a portion of a move // Generate step times for a portion of a move
static int32_t static int32_t
itersolve_gen_steps_range(struct stepper_kinematics *sk, struct move *m itersolve_gen_steps_range(struct stepper_kinematics *sk, struct stepcompress *sc
, double abs_start, double abs_end) , struct move *m, double abs_start, double abs_end)
{ {
sk_calc_callback calc_position_cb = sk->calc_position_cb; sk_calc_callback calc_position_cb = sk->calc_position_cb;
double half_step = .5 * sk->step_dist; double half_step = .5 * sk->step_dist;
@ -37,7 +37,7 @@ itersolve_gen_steps_range(struct stepper_kinematics *sk, struct move *m
if (end > m->move_t) if (end > m->move_t)
end = m->move_t; end = m->move_t;
struct timepos old_guess = {start, sk->commanded_pos}, guess = old_guess; struct timepos old_guess = {start, sk->commanded_pos}, guess = old_guess;
int sdir = stepcompress_get_step_dir(sk->sc); int sdir = stepcompress_get_step_dir(sc);
int is_dir_change = 0, have_bracket = 0, check_oscillate = 0; int is_dir_change = 0, have_bracket = 0, check_oscillate = 0;
double target = sk->commanded_pos + (sdir ? half_step : -half_step); double target = sk->commanded_pos + (sdir ? half_step : -half_step);
double last_time=start, low_time=start, high_time=start + SEEK_TIME_RESET; double last_time=start, low_time=start, high_time=start + SEEK_TIME_RESET;
@ -99,13 +99,13 @@ itersolve_gen_steps_range(struct stepper_kinematics *sk, struct move *m
if (!have_bracket || high_time - low_time > .000000001) { if (!have_bracket || high_time - low_time > .000000001) {
if (!is_dir_change && rel_dist >= -half_step) if (!is_dir_change && rel_dist >= -half_step)
// Avoid rollback if stepper fully reaches step position // Avoid rollback if stepper fully reaches step position
stepcompress_commit(sk->sc); stepcompress_commit(sc);
// Guess is not close enough - guess again with new time // Guess is not close enough - guess again with new time
continue; continue;
} }
} }
// Found next step - submit it // Found next step - submit it
int ret = stepcompress_append(sk->sc, sdir, m->print_time, guess.time); int ret = stepcompress_append(sc, sdir, m->print_time, guess.time);
if (ret) if (ret)
return ret; return ret;
target = sdir ? target+half_step+half_step : target-half_step-half_step; target = sdir ? target+half_step+half_step : target-half_step-half_step;
@ -143,8 +143,9 @@ check_active(struct stepper_kinematics *sk, struct move *m)
} }
// Generate step times for a range of moves on the trapq // Generate step times for a range of moves on the trapq
int32_t __visible int32_t
itersolve_generate_steps(struct stepper_kinematics *sk, double flush_time) itersolve_generate_steps(struct stepper_kinematics *sk, struct stepcompress *sc
, double flush_time)
{ {
double last_flush_time = sk->last_flush_time; double last_flush_time = sk->last_flush_time;
sk->last_flush_time = flush_time; sk->last_flush_time = flush_time;
@ -170,15 +171,15 @@ itersolve_generate_steps(struct stepper_kinematics *sk, double flush_time)
while (--skip_count && pm->print_time > abs_start) while (--skip_count && pm->print_time > abs_start)
pm = list_prev_entry(pm, node); pm = list_prev_entry(pm, node);
do { do {
int32_t ret = itersolve_gen_steps_range(sk, pm, abs_start int32_t ret = itersolve_gen_steps_range(
, flush_time); sk, sc, pm, abs_start, flush_time);
if (ret) if (ret)
return ret; return ret;
pm = list_next_entry(pm, node); pm = list_next_entry(pm, node);
} while (pm != m); } while (pm != m);
} }
// Generate steps for this move // Generate steps for this move
int32_t ret = itersolve_gen_steps_range(sk, m, last_flush_time int32_t ret = itersolve_gen_steps_range(sk, sc, m, last_flush_time
, flush_time); , flush_time);
if (ret) if (ret)
return ret; return ret;
@ -195,8 +196,8 @@ itersolve_generate_steps(struct stepper_kinematics *sk, double flush_time)
double abs_end = force_steps_time; double abs_end = force_steps_time;
if (abs_end > flush_time) if (abs_end > flush_time)
abs_end = flush_time; abs_end = flush_time;
int32_t ret = itersolve_gen_steps_range(sk, m, last_flush_time int32_t ret = itersolve_gen_steps_range(
, abs_end); sk, sc, m, last_flush_time, abs_end);
if (ret) if (ret)
return ret; return ret;
skip_count = 1; skip_count = 1;
@ -240,16 +241,10 @@ itersolve_is_active_axis(struct stepper_kinematics *sk, char axis)
} }
void __visible void __visible
itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq) itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq
, double step_dist)
{ {
sk->tq = tq; sk->tq = tq;
}
void __visible
itersolve_set_stepcompress(struct stepper_kinematics *sk
, struct stepcompress *sc, double step_dist)
{
sk->sc = sc;
sk->step_dist = step_dist; sk->step_dist = step_dist;
} }

View File

@ -26,12 +26,11 @@ struct stepper_kinematics {
}; };
int32_t itersolve_generate_steps(struct stepper_kinematics *sk int32_t itersolve_generate_steps(struct stepper_kinematics *sk
, double flush_time); , struct stepcompress *sc, double flush_time);
double itersolve_check_active(struct stepper_kinematics *sk, double flush_time); 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
void itersolve_set_stepcompress(struct stepper_kinematics *sk , double step_dist);
, struct stepcompress *sc, double step_dist);
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

View File

@ -1,6 +1,6 @@
// Stepper pulse schedule compression // Stepper pulse schedule compression
// //
// Copyright (C) 2016-2021 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.
@ -21,6 +21,7 @@
#include <stdlib.h> // malloc #include <stdlib.h> // malloc
#include <string.h> // memset #include <string.h> // memset
#include "compiler.h" // DIV_ROUND_UP #include "compiler.h" // DIV_ROUND_UP
#include "itersolve.h" // itersolve_generate_steps
#include "pyhelper.h" // errorf #include "pyhelper.h" // errorf
#include "serialqueue.h" // struct queue_message #include "serialqueue.h" // struct queue_message
#include "stepcompress.h" // stepcompress_alloc #include "stepcompress.h" // stepcompress_alloc
@ -46,6 +47,8 @@ struct stepcompress {
// History tracking // History tracking
int64_t last_position; int64_t last_position;
struct list_head history_list; struct list_head history_list;
// Itersolve reference
struct stepper_kinematics *sk;
}; };
struct step_move { struct step_move {
@ -276,9 +279,9 @@ stepcompress_set_invert_sdir(struct stepcompress *sc, uint32_t invert_sdir)
} }
} }
// Helper to free items from the history_list // Expire the stepcompress history older than the given clock
static void void
free_history(struct stepcompress *sc, uint64_t end_clock) stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock)
{ {
while (!list_empty(&sc->history_list)) { while (!list_empty(&sc->history_list)) {
struct history_steps *hs = list_last_entry( struct history_steps *hs = list_last_entry(
@ -290,13 +293,6 @@ free_history(struct stepcompress *sc, uint64_t end_clock)
} }
} }
// Expire the stepcompress history older than the given clock
static void
stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock)
{
free_history(sc, end_clock);
}
// Free memory associated with a 'stepcompress' object // Free memory associated with a 'stepcompress' object
void __visible void __visible
stepcompress_free(struct stepcompress *sc) stepcompress_free(struct stepcompress *sc)
@ -305,7 +301,7 @@ stepcompress_free(struct stepcompress *sc)
return; return;
free(sc->queue); free(sc->queue);
message_queue_free(&sc->msg_queue); message_queue_free(&sc->msg_queue);
free_history(sc, UINT64_MAX); stepcompress_history_expire(sc, UINT64_MAX);
free(sc); free(sc);
} }
@ -321,6 +317,12 @@ stepcompress_get_step_dir(struct stepcompress *sc)
return sc->next_step_dir; return sc->next_step_dir;
} }
struct list_head *
stepcompress_get_msg_queue(struct stepcompress *sc)
{
return &sc->msg_queue;
}
// Determine the "print time" of the last_step_clock // Determine the "print time" of the last_step_clock
static void static void
calc_last_step_print_time(struct stepcompress *sc) calc_last_step_print_time(struct stepcompress *sc)
@ -330,7 +332,7 @@ calc_last_step_print_time(struct stepcompress *sc)
} }
// Set the conversion rate of 'print_time' to mcu clock // Set the conversion rate of 'print_time' to mcu clock
static void void
stepcompress_set_time(struct stepcompress *sc stepcompress_set_time(struct stepcompress *sc
, double time_offset, double mcu_freq) , double time_offset, double mcu_freq)
{ {
@ -664,164 +666,25 @@ stepcompress_extract_old(struct stepcompress *sc, struct pull_history_steps *p
return res; return res;
} }
// Store a reference to stepper_kinematics
/****************************************************************
* Step compress synchronization
****************************************************************/
// The steppersync object is used to synchronize the output of mcu
// step commands. The mcu can only queue a limited number of step
// commands - this code tracks when items on the mcu step queue become
// free so that new commands can be transmitted. It also ensures the
// mcu step queue is ordered between steppers so that no stepper
// starves the other steppers of space in the mcu step queue.
struct steppersync {
// Serial port
struct serialqueue *sq;
struct command_queue *cq;
// Storage for associated stepcompress objects
struct stepcompress **sc_list;
int sc_num;
// Storage for list of pending move clocks
uint64_t *move_clocks;
int num_move_clocks;
};
// Allocate a new 'steppersync' object
struct steppersync * __visible
steppersync_alloc(struct serialqueue *sq, struct stepcompress **sc_list
, int sc_num, int move_num)
{
struct steppersync *ss = malloc(sizeof(*ss));
memset(ss, 0, sizeof(*ss));
ss->sq = sq;
ss->cq = serialqueue_alloc_commandqueue();
ss->sc_list = malloc(sizeof(*sc_list)*sc_num);
memcpy(ss->sc_list, sc_list, sizeof(*sc_list)*sc_num);
ss->sc_num = sc_num;
ss->move_clocks = malloc(sizeof(*ss->move_clocks)*move_num);
memset(ss->move_clocks, 0, sizeof(*ss->move_clocks)*move_num);
ss->num_move_clocks = move_num;
return ss;
}
// Free memory associated with a 'steppersync' object
void __visible void __visible
steppersync_free(struct steppersync *ss) stepcompress_set_stepper_kinematics(struct stepcompress *sc
, struct stepper_kinematics *sk)
{ {
if (!ss) sc->sk = sk;
return;
free(ss->sc_list);
free(ss->move_clocks);
serialqueue_free_commandqueue(ss->cq);
free(ss);
} }
// Set the conversion rate of 'print_time' to mcu clock // Generate steps (via itersolve) and flush
void __visible int32_t
steppersync_set_time(struct steppersync *ss, double time_offset stepcompress_generate_steps(struct stepcompress *sc, double gen_steps_time
, double mcu_freq) , uint64_t flush_clock)
{ {
int i; if (!sc->sk)
for (i=0; i<ss->sc_num; i++) { return 0;
struct stepcompress *sc = ss->sc_list[i]; // Generate steps
stepcompress_set_time(sc, time_offset, mcu_freq); int32_t ret = itersolve_generate_steps(sc->sk, sc, gen_steps_time);
} if (ret)
} return ret;
// Flush steps
// Expire the stepcompress history before the given clock time return stepcompress_flush(sc, flush_clock);
static void
steppersync_history_expire(struct steppersync *ss, uint64_t end_clock)
{
int i;
for (i = 0; i < ss->sc_num; i++)
{
struct stepcompress *sc = ss->sc_list[i];
stepcompress_history_expire(sc, end_clock);
}
}
// Implement a binary heap algorithm to track when the next available
// 'struct move' in the mcu will be available
static void
heap_replace(struct steppersync *ss, uint64_t req_clock)
{
uint64_t *mc = ss->move_clocks;
int nmc = ss->num_move_clocks, pos = 0;
for (;;) {
int child1_pos = 2*pos+1, child2_pos = 2*pos+2;
uint64_t child2_clock = child2_pos < nmc ? mc[child2_pos] : UINT64_MAX;
uint64_t child1_clock = child1_pos < nmc ? mc[child1_pos] : UINT64_MAX;
if (req_clock <= child1_clock && req_clock <= child2_clock) {
mc[pos] = req_clock;
break;
}
if (child1_clock < child2_clock) {
mc[pos] = child1_clock;
pos = child1_pos;
} else {
mc[pos] = child2_clock;
pos = child2_pos;
}
}
}
// Find and transmit any scheduled steps prior to the given 'move_clock'
int __visible
steppersync_flush(struct steppersync *ss, uint64_t move_clock
, uint64_t clear_history_clock)
{
// Flush each stepcompress to the specified move_clock
int i;
for (i=0; i<ss->sc_num; i++) {
int ret = stepcompress_flush(ss->sc_list[i], move_clock);
if (ret)
return ret;
}
// Order commands by the reqclock of each pending command
struct list_head msgs;
list_init(&msgs);
for (;;) {
// Find message with lowest reqclock
uint64_t req_clock = MAX_CLOCK;
struct queue_message *qm = NULL;
for (i=0; i<ss->sc_num; i++) {
struct stepcompress *sc = ss->sc_list[i];
if (!list_empty(&sc->msg_queue)) {
struct queue_message *m = list_first_entry(
&sc->msg_queue, struct queue_message, node);
if (m->req_clock < req_clock) {
qm = m;
req_clock = m->req_clock;
}
}
}
if (!qm || (qm->min_clock && req_clock > move_clock))
break;
uint64_t next_avail = ss->move_clocks[0];
if (qm->min_clock)
// The qm->min_clock field is overloaded to indicate that
// the command uses the 'move queue' and to store the time
// that move queue item becomes available.
heap_replace(ss, qm->min_clock);
// Reset the min_clock to its normal meaning (minimum transmit time)
qm->min_clock = next_avail;
// Batch this command
list_del(&qm->node);
list_add_tail(&qm->node, &msgs);
}
// Transmit commands
if (!list_empty(&msgs))
serialqueue_send_batch(ss->sq, ss->cq, &msgs);
steppersync_history_expire(ss, clear_history_clock);
return 0;
} }

View File

@ -17,9 +17,13 @@ void stepcompress_fill(struct stepcompress *sc, uint32_t max_error
, int32_t set_next_step_dir_msgtag); , int32_t set_next_step_dir_msgtag);
void stepcompress_set_invert_sdir(struct stepcompress *sc void stepcompress_set_invert_sdir(struct stepcompress *sc
, uint32_t invert_sdir); , uint32_t invert_sdir);
void stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock);
void stepcompress_free(struct stepcompress *sc); void stepcompress_free(struct stepcompress *sc);
uint32_t stepcompress_get_oid(struct stepcompress *sc); uint32_t stepcompress_get_oid(struct stepcompress *sc);
int stepcompress_get_step_dir(struct stepcompress *sc); int stepcompress_get_step_dir(struct stepcompress *sc);
struct list_head *stepcompress_get_msg_queue(struct stepcompress *sc);
void stepcompress_set_time(struct stepcompress *sc
, double time_offset, double mcu_freq);
int stepcompress_append(struct stepcompress *sc, int sdir int stepcompress_append(struct stepcompress *sc, int sdir
, double print_time, double step_time); , double print_time, double step_time);
int stepcompress_commit(struct stepcompress *sc); int stepcompress_commit(struct stepcompress *sc);
@ -34,15 +38,11 @@ int stepcompress_queue_mq_msg(struct stepcompress *sc, uint64_t req_clock
int stepcompress_extract_old(struct stepcompress *sc int stepcompress_extract_old(struct stepcompress *sc
, struct pull_history_steps *p, int max , struct pull_history_steps *p, int max
, uint64_t start_clock, uint64_t end_clock); , uint64_t start_clock, uint64_t end_clock);
struct stepper_kinematics;
struct serialqueue; void stepcompress_set_stepper_kinematics(struct stepcompress *sc
struct steppersync *steppersync_alloc( , struct stepper_kinematics *sk);
struct serialqueue *sq, struct stepcompress **sc_list, int sc_num int32_t stepcompress_generate_steps(struct stepcompress *sc
, int move_num); , double gen_steps_time
void steppersync_free(struct steppersync *ss); , uint64_t flush_clock);
void steppersync_set_time(struct steppersync *ss, double time_offset
, double mcu_freq);
int steppersync_flush(struct steppersync *ss, uint64_t move_clock
, uint64_t clear_history_clock);
#endif // stepcompress.h #endif // stepcompress.h

View File

@ -0,0 +1,177 @@
// Stepper step transmit synchronization
//
// Copyright (C) 2016-2025 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
// The steppersync object is used to synchronize the output of mcu
// step commands. The mcu can only queue a limited number of step
// commands - this code tracks when items on the mcu step queue become
// free so that new commands can be transmitted. It also ensures the
// mcu step queue is ordered between steppers so that no stepper
// starves the other steppers of space in the mcu step queue.
#include <stddef.h> // offsetof
#include <stdlib.h> // malloc
#include <string.h> // memset
#include "compiler.h" // __visible
#include "serialqueue.h" // struct queue_message
#include "stepcompress.h" // stepcompress_flush
#include "steppersync.h" // steppersync_alloc
struct steppersync {
// Serial port
struct serialqueue *sq;
struct command_queue *cq;
// Storage for associated stepcompress objects
struct stepcompress **sc_list;
int sc_num;
// Storage for list of pending move clocks
uint64_t *move_clocks;
int num_move_clocks;
};
// Allocate a new 'steppersync' object
struct steppersync * __visible
steppersync_alloc(struct serialqueue *sq, struct stepcompress **sc_list
, int sc_num, int move_num)
{
struct steppersync *ss = malloc(sizeof(*ss));
memset(ss, 0, sizeof(*ss));
ss->sq = sq;
ss->cq = serialqueue_alloc_commandqueue();
ss->sc_list = malloc(sizeof(*sc_list)*sc_num);
memcpy(ss->sc_list, sc_list, sizeof(*sc_list)*sc_num);
ss->sc_num = sc_num;
ss->move_clocks = malloc(sizeof(*ss->move_clocks)*move_num);
memset(ss->move_clocks, 0, sizeof(*ss->move_clocks)*move_num);
ss->num_move_clocks = move_num;
return ss;
}
// Free memory associated with a 'steppersync' object
void __visible
steppersync_free(struct steppersync *ss)
{
if (!ss)
return;
free(ss->sc_list);
free(ss->move_clocks);
serialqueue_free_commandqueue(ss->cq);
free(ss);
}
// Set the conversion rate of 'print_time' to mcu clock
void __visible
steppersync_set_time(struct steppersync *ss, double time_offset
, double mcu_freq)
{
int i;
for (i=0; i<ss->sc_num; i++) {
struct stepcompress *sc = ss->sc_list[i];
stepcompress_set_time(sc, time_offset, mcu_freq);
}
}
// Generate steps and flush stepcompress objects
int32_t __visible
steppersync_generate_steps(struct steppersync *ss, double gen_steps_time
, uint64_t flush_clock)
{
int i;
for (i=0; i<ss->sc_num; i++) {
struct stepcompress *sc = ss->sc_list[i];
int32_t ret = stepcompress_generate_steps(sc, gen_steps_time
, flush_clock);
if (ret)
return ret;
}
return 0;
}
// Expire the stepcompress history before the given clock time
void __visible
steppersync_history_expire(struct steppersync *ss, uint64_t end_clock)
{
int i;
for (i = 0; i < ss->sc_num; i++) {
struct stepcompress *sc = ss->sc_list[i];
stepcompress_history_expire(sc, end_clock);
}
}
// Implement a binary heap algorithm to track when the next available
// 'struct move' in the mcu will be available
static void
heap_replace(struct steppersync *ss, uint64_t req_clock)
{
uint64_t *mc = ss->move_clocks;
int nmc = ss->num_move_clocks, pos = 0;
for (;;) {
int child1_pos = 2*pos+1, child2_pos = 2*pos+2;
uint64_t child2_clock = child2_pos < nmc ? mc[child2_pos] : UINT64_MAX;
uint64_t child1_clock = child1_pos < nmc ? mc[child1_pos] : UINT64_MAX;
if (req_clock <= child1_clock && req_clock <= child2_clock) {
mc[pos] = req_clock;
break;
}
if (child1_clock < child2_clock) {
mc[pos] = child1_clock;
pos = child1_pos;
} else {
mc[pos] = child2_clock;
pos = child2_pos;
}
}
}
// Find and transmit any scheduled steps prior to the given 'move_clock'
int __visible
steppersync_flush(struct steppersync *ss, uint64_t move_clock)
{
// Order commands by the reqclock of each pending command
struct list_head msgs;
list_init(&msgs);
for (;;) {
// Find message with lowest reqclock
uint64_t req_clock = MAX_CLOCK;
struct queue_message *qm = NULL;
int i;
for (i=0; i<ss->sc_num; i++) {
struct stepcompress *sc = ss->sc_list[i];
struct list_head *sc_mq = stepcompress_get_msg_queue(sc);
if (!list_empty(sc_mq)) {
struct queue_message *m = list_first_entry(
sc_mq, struct queue_message, node);
if (m->req_clock < req_clock) {
qm = m;
req_clock = m->req_clock;
}
}
}
if (!qm || (qm->min_clock && req_clock > move_clock))
break;
uint64_t next_avail = ss->move_clocks[0];
if (qm->min_clock)
// The qm->min_clock field is overloaded to indicate that
// the command uses the 'move queue' and to store the time
// that move queue item becomes available.
heap_replace(ss, qm->min_clock);
// Reset the min_clock to its normal meaning (minimum transmit time)
qm->min_clock = next_avail;
// Batch this command
list_del(&qm->node);
list_add_tail(&qm->node, &msgs);
}
// Transmit commands
if (!list_empty(&msgs))
serialqueue_send_batch(ss->sq, ss->cq, &msgs);
return 0;
}

View File

@ -0,0 +1,18 @@
#ifndef STEPPERSYNC_H
#define STEPPERSYNC_H
#include <stdint.h> // uint64_t
struct serialqueue;
struct steppersync *steppersync_alloc(
struct serialqueue *sq, struct stepcompress **sc_list, int sc_num
, int move_num);
void steppersync_free(struct steppersync *ss);
void steppersync_set_time(struct steppersync *ss, double time_offset
, double mcu_freq);
int32_t steppersync_generate_steps(struct steppersync *ss, double gen_steps_time
, uint64_t flush_clock);
void steppersync_history_expire(struct steppersync *ss, uint64_t end_clock);
int steppersync_flush(struct steppersync *ss, uint64_t move_clock);
#endif // steppersync.h

View File

@ -33,10 +33,10 @@ class ForceMove:
self.printer = config.get_printer() self.printer = config.get_printer()
self.steppers = {} self.steppers = {}
# Setup iterative solver # Setup iterative solver
self.motion_queuing = self.printer.load_object(config, 'motion_queuing')
self.trapq = self.motion_queuing.allocate_trapq()
self.trapq_append = self.motion_queuing.lookup_trapq_append()
ffi_main, ffi_lib = chelper.get_ffi() ffi_main, ffi_lib = chelper.get_ffi()
self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free)
self.trapq_append = ffi_lib.trapq_append
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
self.stepper_kinematics = ffi_main.gc( self.stepper_kinematics = ffi_main.gc(
ffi_lib.cartesian_stepper_alloc(b'x'), ffi_lib.free) ffi_lib.cartesian_stepper_alloc(b'x'), ffi_lib.free)
# Register commands # Register commands
@ -85,14 +85,12 @@ 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
stepper.generate_steps(print_time)
self.trapq_finalize_moves(self.trapq, print_time + 99999.9,
print_time + 99999.9)
stepper.set_trapq(prev_trapq)
stepper.set_stepper_kinematics(prev_sk)
toolhead.note_mcu_movequeue_activity(print_time) toolhead.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_stepper_kinematics(prev_sk)
self.motion_queuing.wipe_trapq(self.trapq)
def _lookup_stepper(self, gcmd): def _lookup_stepper(self, gcmd):
name = gcmd.get('STEPPER') name = gcmd.get('STEPPER')
if name not in self.steppers: if name not in self.steppers:

View File

@ -25,10 +25,9 @@ class ManualStepper:
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
ffi_main, ffi_lib = chelper.get_ffi() self.motion_queuing = self.printer.load_object(config, 'motion_queuing')
self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) self.trapq = self.motion_queuing.allocate_trapq()
self.trapq_append = ffi_lib.trapq_append self.trapq_append = self.motion_queuing.lookup_trapq_append()
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
self.rail.setup_itersolve('cartesian_stepper_alloc', b'x') self.rail.setup_itersolve('cartesian_stepper_alloc', b'x')
self.rail.set_trapq(self.trapq) self.rail.set_trapq(self.trapq)
# Registered with toolhead as an axtra axis # Registered with toolhead as an axtra axis
@ -76,9 +75,6 @@ class ManualStepper:
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)
self.rail.generate_steps(self.next_cmd_time)
self.trapq_finalize_moves(self.trapq, self.next_cmd_time + 99999.9,
self.next_cmd_time + 99999.9)
toolhead = self.printer.lookup_object('toolhead') toolhead = self.printer.lookup_object('toolhead')
toolhead.note_mcu_movequeue_activity(self.next_cmd_time) toolhead.note_mcu_movequeue_activity(self.next_cmd_time)
if sync: if sync:
@ -138,7 +134,6 @@ class ManualStepper:
raise gcmd.error("Must unregister axis first") raise gcmd.error("Must unregister axis first")
# Unregister # Unregister
toolhead.remove_extra_axis(self) toolhead.remove_extra_axis(self)
toolhead.unregister_step_generator(self.rail.generate_steps)
self.axis_gcode_id = None self.axis_gcode_id = None
return return
if (len(gcode_axis) != 1 or not gcode_axis.isupper() if (len(gcode_axis) != 1 or not gcode_axis.isupper()
@ -155,7 +150,6 @@ class ManualStepper:
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.get_position()[0])
toolhead.register_step_generator(self.rail.generate_steps)
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]
@ -208,10 +202,10 @@ class ManualStepper:
speed, self.homing_accel) speed, self.homing_accel)
# Drip updates to motors # Drip updates to motors
toolhead = self.printer.lookup_object('toolhead') toolhead = self.printer.lookup_object('toolhead')
toolhead.drip_update_time(maxtime, drip_completion, self.steppers) toolhead.drip_update_time(maxtime, drip_completion)
# Clear trapq of any remaining parts of movement # Clear trapq of any remaining parts of movement
reactor = self.printer.get_reactor() reactor = self.printer.get_reactor()
self.trapq_finalize_moves(self.trapq, reactor.NEVER, 0) self.motion_queuing.wipe_trapq(self.trapq)
self.rail.set_position([newpos[0], 0., 0.]) self.rail.set_position([newpos[0], 0., 0.])
self.sync_print_time() self.sync_print_time()
def get_kinematics(self): def get_kinematics(self):

View File

@ -0,0 +1,100 @@
# Helper code for low-level motion queuing and flushing
#
# Copyright (C) 2025 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
import chelper
MOVE_HISTORY_EXPIRE = 30.
class PrinterMotionQueuing:
def __init__(self, config):
self.printer = config.get_printer()
self.trapqs = []
self.stepcompress = []
self.steppersyncs = []
self.flush_callbacks = []
ffi_main, ffi_lib = chelper.get_ffi()
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
self.steppersync_generate_steps = ffi_lib.steppersync_generate_steps
self.steppersync_flush = ffi_lib.steppersync_flush
self.steppersync_history_expire = ffi_lib.steppersync_history_expire
self.clear_history_time = 0.
is_debug = self.printer.get_start_args().get('debugoutput') is not None
self.is_debugoutput = is_debug
def allocate_trapq(self):
ffi_main, ffi_lib = chelper.get_ffi()
trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free)
self.trapqs.append(trapq)
return trapq
def allocate_stepcompress(self, mcu, oid):
ffi_main, ffi_lib = chelper.get_ffi()
sc = ffi_main.gc(ffi_lib.stepcompress_alloc(oid),
ffi_lib.stepcompress_free)
self.stepcompress.append((mcu, sc))
return sc
def allocate_steppersync(self, mcu, serialqueue, move_count):
stepqueues = []
for sc_mcu, sc in self.stepcompress:
if sc_mcu is mcu:
stepqueues.append(sc)
ffi_main, ffi_lib = chelper.get_ffi()
ss = ffi_main.gc(
ffi_lib.steppersync_alloc(serialqueue, stepqueues, len(stepqueues),
move_count),
ffi_lib.steppersync_free)
self.steppersyncs.append((mcu, ss))
return ss
def register_flush_callback(self, callback):
self.flush_callbacks.append(callback)
def unregister_flush_callback(self, callback):
if callback in self.flush_callbacks:
fcbs = list(self.flush_callbacks)
fcbs.remove(callback)
self.flush_callbacks = fcbs
def flush_motion_queues(self, must_flush_time, max_step_gen_time,
trapq_free_time):
# Invoke flush callbacks (if any)
for cb in self.flush_callbacks:
cb(must_flush_time, max_step_gen_time)
# Generate stepper movement and transmit
for mcu, ss in self.steppersyncs:
clock = max(0, mcu.print_time_to_clock(must_flush_time))
# Generate steps
ret = self.steppersync_generate_steps(ss, max_step_gen_time, clock)
if ret:
raise mcu.error("Internal error in MCU '%s' stepcompress"
% (mcu.get_name(),))
# Flush steps from steppersync
ret = self.steppersync_flush(ss, clock)
if ret:
raise mcu.error("Internal error in MCU '%s' stepcompress"
% (mcu.get_name(),))
# Determine maximum history to keep
clear_history_time = self.clear_history_time
if self.is_debugoutput:
clear_history_time = trapq_free_time - MOVE_HISTORY_EXPIRE
# Move processed trapq moves to history list, and expire old history
for trapq in self.trapqs:
self.trapq_finalize_moves(trapq, trapq_free_time,
clear_history_time)
# Clean up old history entries in stepcompress objects
for mcu, ss in self.steppersyncs:
clock = max(0, mcu.print_time_to_clock(clear_history_time))
self.steppersync_history_expire(ss, clock)
def wipe_trapq(self, trapq):
# Expire any remaining movement in the trapq (force to history list)
NEVER = 9999999999999999.
self.trapq_finalize_moves(trapq, NEVER, 0.)
def lookup_trapq_append(self):
ffi_main, ffi_lib = chelper.get_ffi()
return ffi_lib.trapq_append
def stats(self, eventtime):
mcu = self.printer.lookup_object('mcu')
est_print_time = mcu.estimated_print_time(eventtime)
self.clear_history_time = est_print_time - MOVE_HISTORY_EXPIRE
return False, ""
def load_config(config):
return PrinterMotionQueuing(config)

View File

@ -20,16 +20,17 @@ class GCodeRequestQueue:
self.rqueue = [] self.rqueue = []
self.next_min_flush_time = 0. self.next_min_flush_time = 0.
self.toolhead = None self.toolhead = None
mcu.register_flush_callback(self._flush_notification) motion_queuing = printer.load_object(config, 'motion_queuing')
motion_queuing.register_flush_callback(self._flush_notification)
printer.register_event_handler("klippy:connect", self._handle_connect) 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')
def _flush_notification(self, print_time, clock): def _flush_notification(self, must_flush_time, max_step_gen_time):
min_sched_time = self.mcu.min_schedule_time() min_sched_time = self.mcu.min_schedule_time()
rqueue = self.rqueue rqueue = self.rqueue
while rqueue: while rqueue:
next_time = max(rqueue[0][0], self.next_min_flush_time) next_time = max(rqueue[0][0], self.next_min_flush_time)
if next_time > print_time: if next_time > must_flush_time:
return return
# Skip requests that have been overridden with a following request # Skip requests that have been overridden with a following request
pos = 0 pos = 0
@ -50,10 +51,11 @@ 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.toolhead.note_mcu_movequeue_activity(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) self.toolhead.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

@ -9,18 +9,18 @@ class error(Exception):
pass pass
class MCU_queued_pwm: class MCU_queued_pwm:
def __init__(self, pin_params): def __init__(self, config, pin_params):
self._mcu = pin_params['chip'] self._mcu = mcu = pin_params['chip']
self._hardware_pwm = False self._hardware_pwm = False
self._cycle_time = 0.100 self._cycle_time = 0.100
self._max_duration = 2. self._max_duration = 2.
self._oid = self._mcu.create_oid() self._oid = oid = mcu.create_oid()
printer = mcu.get_printer()
motion_queuing = printer.load_object(config, 'motion_queuing')
self._stepqueue = motion_queuing.allocate_stepcompress(mcu, oid)
ffi_main, ffi_lib = chelper.get_ffi() ffi_main, ffi_lib = chelper.get_ffi()
self._stepqueue = ffi_main.gc(ffi_lib.stepcompress_alloc(self._oid),
ffi_lib.stepcompress_free)
self._mcu.register_stepqueue(self._stepqueue)
self._stepcompress_queue_mq_msg = ffi_lib.stepcompress_queue_mq_msg self._stepcompress_queue_mq_msg = ffi_lib.stepcompress_queue_mq_msg
self._mcu.register_config_callback(self._build_config) mcu.register_config_callback(self._build_config)
self._pin = pin_params['pin'] self._pin = pin_params['pin']
self._invert = pin_params['invert'] self._invert = pin_params['invert']
self._start_value = self._shutdown_value = float(self._invert) self._start_value = self._shutdown_value = float(self._invert)
@ -29,7 +29,6 @@ class MCU_queued_pwm:
self._pwm_max = 0. self._pwm_max = 0.
self._set_cmd_tag = None self._set_cmd_tag = None
self._toolhead = None self._toolhead = None
printer = self._mcu.get_printer()
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._mcu.get_printer().lookup_object("toolhead") self._toolhead = self._mcu.get_printer().lookup_object("toolhead")
@ -47,12 +46,13 @@ class MCU_queued_pwm:
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))
def _build_config(self): def _build_config(self):
config_error = self._mcu.get_printer().config_error printer = self._mcu.get_printer()
config_error = printer.config_error
if self._max_duration and self._start_value != self._shutdown_value: if self._max_duration and self._start_value != self._shutdown_value:
raise config_error("Pin with max duration must have start" raise config_error("Pin with max duration must have start"
" value equal to shutdown value") " value equal to shutdown value")
cmd_queue = self._mcu.alloc_command_queue() cmd_queue = self._mcu.alloc_command_queue()
curtime = self._mcu.get_printer().get_reactor().monotonic() curtime = printer.get_reactor().monotonic()
printtime = self._mcu.estimated_print_time(curtime) printtime = self._mcu.estimated_print_time(curtime)
self._last_clock = self._mcu.print_time_to_clock(printtime + 0.200) self._last_clock = self._mcu.print_time_to_clock(printtime + 0.200)
cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time) cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time)
@ -62,7 +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:
self._mcu.register_flush_callback(self._flush_notification) motion_queuing = printer.lookup_object('motion_queuing')
motion_queuing.register_flush_callback(self._flush_notification)
if self._hardware_pwm: 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
@ -115,14 +116,16 @@ 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._toolhead.note_mcu_movequeue_activity(wake_print_time,
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)
if self._invert: if self._invert:
value = 1. - value value = 1. - value
v = int(max(0., min(1., value)) * self._pwm_max + 0.5) v = int(max(0., min(1., value)) * self._pwm_max + 0.5)
self._send_update(clock, v) self._send_update(clock, v)
def _flush_notification(self, print_time, clock): def _flush_notification(self, must_flush_time, max_step_gen_time):
clock = self._mcu.print_time_to_clock(must_flush_time)
if self._last_value != self._default_value: if self._last_value != self._default_value:
while clock >= self._last_clock + self._duration_ticks: while clock >= self._last_clock + self._duration_ticks:
self._send_update(self._last_clock + self._duration_ticks, self._send_update(self._last_clock + self._duration_ticks,
@ -134,7 +137,7 @@ class PrinterOutputPin:
ppins = self.printer.lookup_object('pins') ppins = self.printer.lookup_object('pins')
# Determine pin type # Determine pin type
pin_params = ppins.lookup_pin(config.get('pin'), can_invert=True) pin_params = ppins.lookup_pin(config.get('pin'), can_invert=True)
self.mcu_pin = MCU_queued_pwm(pin_params) self.mcu_pin = MCU_queued_pwm(config, pin_params)
max_duration = self.mcu_pin.get_mcu().max_nominal_duration() max_duration = self.mcu_pin.get_mcu().max_nominal_duration()
cycle_time = config.getfloat('cycle_time', 0.100, above=0., cycle_time = config.getfloat('cycle_time', 0.100, above=0.,
maxval=max_duration) maxval=max_duration)

View File

@ -148,8 +148,7 @@ class ResonanceTestExecutor:
last_v = last_t = last_accel = last_freq = 0. last_v = last_t = last_accel = last_freq = 0.
for next_t, accel, freq in test_seq: for next_t, accel, freq in test_seq:
t_seg = next_t - last_t t_seg = next_t - last_t
toolhead.cmd_M204(self.gcode.create_gcode_command( toolhead.set_max_velocities(None, abs(accel), None, None)
"M204", "M204", {"S": abs(accel)}))
v = last_v + accel * t_seg v = last_v + accel * t_seg
abs_v = abs(v) abs_v = abs(v)
if abs_v < 0.000001: if abs_v < 0.000001:
@ -182,8 +181,7 @@ class ResonanceTestExecutor:
if last_v: if last_v:
d_decel = -.5 * last_v2 / old_max_accel d_decel = -.5 * last_v2 / old_max_accel
decel_X, decel_Y = axis.get_point(d_decel) decel_X, decel_Y = axis.get_point(d_decel)
toolhead.cmd_M204(self.gcode.create_gcode_command( toolhead.set_max_velocities(None, old_max_accel, None, None)
"M204", "M204", {"S": old_max_accel}))
toolhead.move([X + decel_X, Y + decel_Y] + tpos[2:], abs(last_v)) toolhead.move([X + decel_X, Y + decel_Y] + tpos[2:], abs(last_v))
# Restore the original acceleration values # Restore the original acceleration values
self.gcode.run_script_from_command( self.gcode.run_script_from_command(

View File

@ -65,8 +65,8 @@ class PrinterStats:
def generate_stats(self, eventtime): def generate_stats(self, eventtime):
stats = [cb(eventtime) for cb in self.stats_cb] stats = [cb(eventtime) for cb in self.stats_cb]
if max([s[0] for s in stats]): if max([s[0] for s in stats]):
logging.info("Stats %.1f: %s", eventtime, stats_str = ' '.join([s[1] for s in stats if s[1]])
' '.join([s[1] for s in stats])) logging.info("Stats %.1f: %s", eventtime, stats_str)
return eventtime + 1. return eventtime + 1.
def load_config(config): def load_config(config):

View File

@ -36,7 +36,6 @@ class CartKinematics:
'safe_distance', None, minval=0.)) 'safe_distance', None, minval=0.))
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity() max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity, self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity,

View File

@ -20,7 +20,6 @@ class CoreXYKinematics:
self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'z') self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'z')
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity() max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat( self.max_z_velocity = config.getfloat(

View File

@ -20,7 +20,6 @@ class CoreXZKinematics:
self.rails[2].setup_itersolve('corexz_stepper_alloc', b'-') self.rails[2].setup_itersolve('corexz_stepper_alloc', b'-')
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity() max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat( self.max_z_velocity = config.getfloat(

View File

@ -52,7 +52,6 @@ class DeltaKinematics:
r.setup_itersolve('delta_stepper_alloc', a, t[0], t[1]) r.setup_itersolve('delta_stepper_alloc', a, t[0], t[1])
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
self.need_home = True self.need_home = True
self.limit_xy2 = -1. self.limit_xy2 = -1.

View File

@ -40,7 +40,6 @@ class DeltesianKinematics:
self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'y') self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'y')
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
self.limits = [(1.0, -1.0)] * 3 self.limits = [(1.0, -1.0)] * 3
# X axis limits # X axis limits
min_angle = config.getfloat('min_angle', MIN_ANGLE, min_angle = config.getfloat('min_angle', MIN_ANGLE,

View File

@ -39,8 +39,6 @@ class ExtruderStepper:
self.name, self.cmd_SYNC_EXTRUDER_MOTION, self.name, self.cmd_SYNC_EXTRUDER_MOTION,
desc=self.cmd_SYNC_EXTRUDER_MOTION_help) desc=self.cmd_SYNC_EXTRUDER_MOTION_help)
def _handle_connect(self): def _handle_connect(self):
toolhead = self.printer.lookup_object('toolhead')
toolhead.register_step_generator(self.stepper.generate_steps)
self._set_pressure_advance(self.config_pa, self.config_smooth_time) self._set_pressure_advance(self.config_pa, self.config_smooth_time)
def get_status(self, eventtime): def get_status(self, eventtime):
return {'pressure_advance': self.pressure_advance, return {'pressure_advance': self.pressure_advance,
@ -165,10 +163,9 @@ class PrinterExtruder:
self.instant_corner_v = config.getfloat( self.instant_corner_v = config.getfloat(
'instantaneous_corner_velocity', 1., minval=0.) 'instantaneous_corner_velocity', 1., minval=0.)
# Setup extruder trapq (trapezoidal motion queue) # Setup extruder trapq (trapezoidal motion queue)
ffi_main, ffi_lib = chelper.get_ffi() self.motion_queuing = self.printer.load_object(config, 'motion_queuing')
self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) self.trapq = self.motion_queuing.allocate_trapq()
self.trapq_append = ffi_lib.trapq_append self.trapq_append = self.motion_queuing.lookup_trapq_append()
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
# Setup extruder stepper # Setup extruder stepper
self.extruder_stepper = None self.extruder_stepper = None
if (config.get('step_pin', None) is not None if (config.get('step_pin', None) is not None

View File

@ -108,7 +108,6 @@ class GenericCartesianKinematics:
self._load_kinematics(config) self._load_kinematics(config)
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
self.dc_module = None self.dc_module = None
if self.dc_carriages: if self.dc_carriages:
pcs = [dc.get_dual_carriage() for dc in self.dc_carriages] pcs = [dc.get_dual_carriage() for dc in self.dc_carriages]

View File

@ -39,7 +39,6 @@ class HybridCoreXYKinematics:
'safe_distance', None, minval=0.)) 'safe_distance', None, minval=0.))
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity() max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat( self.max_z_velocity = config.getfloat(

View File

@ -39,7 +39,6 @@ class HybridCoreXZKinematics:
'safe_distance', None, minval=0.)) 'safe_distance', None, minval=0.))
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity() max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat( self.max_z_velocity = config.getfloat(

View File

@ -21,7 +21,6 @@ class PolarKinematics:
for s in r.get_steppers() ] for s in r.get_steppers() ]
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
max_velocity, max_accel = toolhead.get_max_velocity() max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat( self.max_z_velocity = config.getfloat(

View File

@ -52,7 +52,6 @@ class RotaryDeltaKinematics:
math.radians(a), ua, la) math.radians(a), ua, la)
for s in self.get_steppers(): for s in self.get_steppers():
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
self.need_home = True self.need_home = True
self.limit_xy2 = -1. self.limit_xy2 = -1.

View File

@ -21,7 +21,6 @@ class WinchKinematics:
self.anchors.append(a) self.anchors.append(a)
s.setup_itersolve('winch_stepper_alloc', *a) s.setup_itersolve('winch_stepper_alloc', *a)
s.set_trapq(toolhead.get_trapq()) s.set_trapq(toolhead.get_trapq())
toolhead.register_step_generator(s.generate_steps)
# Setup boundary checks # Setup boundary checks
acoords = list(zip(*self.anchors)) acoords = list(zip(*self.anchors))
self.axes_min = toolhead.Coord(*[min(a) for a in acoords], e=0.) self.axes_min = toolhead.Coord(*[min(a) for a in acoords], e=0.)

View File

@ -605,9 +605,7 @@ class MCU:
self._max_stepper_error = config.getfloat('max_stepper_error', 0.000025, self._max_stepper_error = config.getfloat('max_stepper_error', 0.000025,
minval=0.) minval=0.)
self._reserved_move_slots = 0 self._reserved_move_slots = 0
self._stepqueues = []
self._steppersync = None self._steppersync = None
self._flush_callbacks = []
# Stats # Stats
self._get_status_info = {} self._get_status_info = {}
self._stats_sumsq_base = 0. self._stats_sumsq_base = 0.
@ -770,13 +768,12 @@ class MCU:
move_count = config_params['move_count'] move_count = config_params['move_count']
if move_count < self._reserved_move_slots: if move_count < self._reserved_move_slots:
raise error("Too few moves available on MCU '%s'" % (self._name,)) raise error("Too few moves available on MCU '%s'" % (self._name,))
ffi_main, ffi_lib = chelper.get_ffi() ss_move_count = move_count - self._reserved_move_slots
self._steppersync = ffi_main.gc( motion_queuing = self._printer.lookup_object('motion_queuing')
ffi_lib.steppersync_alloc(self._serial.get_serialqueue(), self._steppersync = motion_queuing.allocate_steppersync(
self._stepqueues, len(self._stepqueues), self, self._serial.get_serialqueue(), ss_move_count)
move_count-self._reserved_move_slots), self._ffi_lib.steppersync_set_time(self._steppersync,
ffi_lib.steppersync_free) 0., self._mcu_freq)
ffi_lib.steppersync_set_time(self._steppersync, 0., self._mcu_freq)
# Log config information # Log config information
move_msg = "Configured MCU '%s' (%d moves)" % (self._name, move_count) move_msg = "Configured MCU '%s' (%d moves)" % (self._name, move_count)
logging.info(move_msg) logging.info(move_msg)
@ -971,27 +968,8 @@ class MCU:
def _firmware_restart_bridge(self): def _firmware_restart_bridge(self):
self._firmware_restart(True) self._firmware_restart(True)
# Move queue tracking # Move queue tracking
def register_stepqueue(self, stepqueue):
self._stepqueues.append(stepqueue)
def request_move_queue_slot(self): def request_move_queue_slot(self):
self._reserved_move_slots += 1 self._reserved_move_slots += 1
def register_flush_callback(self, callback):
self._flush_callbacks.append(callback)
def flush_moves(self, print_time, clear_history_time):
if self._steppersync is None:
return
clock = self.print_time_to_clock(print_time)
if clock < 0:
return
for cb in self._flush_callbacks:
cb(print_time, clock)
clear_history_clock = \
max(0, self.print_time_to_clock(clear_history_time))
ret = self._ffi_lib.steppersync_flush(self._steppersync, clock,
clear_history_clock)
if ret:
raise error("Internal error in MCU '%s' stepcompress"
% (self._name,))
def check_active(self, print_time, eventtime): def check_active(self, print_time, eventtime):
if self._steppersync is None: if self._steppersync is None:
return return

View File

@ -19,22 +19,23 @@ MIN_OPTIMIZED_BOTH_EDGE_DURATION = 0.000000150
# Interface to low-level mcu and chelper code # Interface to low-level mcu and chelper code
class MCU_stepper: class MCU_stepper:
def __init__(self, name, step_pin_params, dir_pin_params, def __init__(self, config, step_pin_params, dir_pin_params,
rotation_dist, steps_per_rotation, rotation_dist, steps_per_rotation,
step_pulse_duration=None, units_in_radians=False): step_pulse_duration=None, units_in_radians=False):
self._name = name self._name = config.get_name()
self._rotation_dist = rotation_dist self._rotation_dist = rotation_dist
self._steps_per_rotation = steps_per_rotation self._steps_per_rotation = steps_per_rotation
self._step_pulse_duration = step_pulse_duration self._step_pulse_duration = step_pulse_duration
self._units_in_radians = units_in_radians self._units_in_radians = units_in_radians
self._step_dist = rotation_dist / steps_per_rotation self._step_dist = rotation_dist / steps_per_rotation
self._mcu = step_pin_params['chip'] self._mcu = mcu = step_pin_params['chip']
self._oid = oid = self._mcu.create_oid() self._oid = oid = mcu.create_oid()
self._mcu.register_config_callback(self._build_config) mcu.register_config_callback(self._build_config)
self._step_pin = step_pin_params['pin'] self._step_pin = step_pin_params['pin']
self._invert_step = step_pin_params['invert'] self._invert_step = step_pin_params['invert']
if dir_pin_params['chip'] is not self._mcu: printer = mcu.get_printer()
raise self._mcu.get_printer().config_error( if dir_pin_params['chip'] is not mcu:
raise printer.config_error(
"Stepper dir pin must be on same mcu as step pin") "Stepper dir pin must be on same mcu as step pin")
self._dir_pin = dir_pin_params['pin'] self._dir_pin = dir_pin_params['pin']
self._invert_dir = self._orig_invert_dir = dir_pin_params['invert'] self._invert_dir = self._orig_invert_dir = dir_pin_params['invert']
@ -42,17 +43,15 @@ class MCU_stepper:
self._mcu_position_offset = 0. self._mcu_position_offset = 0.
self._reset_cmd_tag = self._get_position_cmd = None self._reset_cmd_tag = self._get_position_cmd = None
self._active_callbacks = [] self._active_callbacks = []
motion_queuing = printer.load_object(config, 'motion_queuing')
self._stepqueue = motion_queuing.allocate_stepcompress(mcu, oid)
ffi_main, ffi_lib = chelper.get_ffi() ffi_main, ffi_lib = chelper.get_ffi()
self._stepqueue = ffi_main.gc(ffi_lib.stepcompress_alloc(oid),
ffi_lib.stepcompress_free)
ffi_lib.stepcompress_set_invert_sdir(self._stepqueue, self._invert_dir) ffi_lib.stepcompress_set_invert_sdir(self._stepqueue, self._invert_dir)
self._mcu.register_stepqueue(self._stepqueue)
self._stepper_kinematics = None self._stepper_kinematics = None
self._itersolve_generate_steps = ffi_lib.itersolve_generate_steps
self._itersolve_check_active = ffi_lib.itersolve_check_active self._itersolve_check_active = ffi_lib.itersolve_check_active
self._trapq = ffi_main.NULL self._trapq = ffi_main.NULL
self._mcu.get_printer().register_event_handler('klippy:connect', printer.register_event_handler('klippy:connect',
self._query_mcu_position) self._query_mcu_position)
def get_mcu(self): def get_mcu(self):
return self._mcu return self._mcu
def get_name(self, short=False): def get_name(self, short=False):
@ -135,7 +134,7 @@ class MCU_stepper:
mcu_pos = self.get_mcu_position() mcu_pos = self.get_mcu_position()
self._rotation_dist = rotation_dist self._rotation_dist = rotation_dist
self._step_dist = rotation_dist / self._steps_per_rotation self._step_dist = rotation_dist / self._steps_per_rotation
self.set_stepper_kinematics(self._stepper_kinematics) self.set_trapq(self._trapq)
self._set_mcu_position(mcu_pos) self._set_mcu_position(mcu_pos)
def get_dir_inverted(self): def get_dir_inverted(self):
return self._invert_dir, self._orig_invert_dir return self._invert_dir, self._orig_invert_dir
@ -193,7 +192,7 @@ class MCU_stepper:
mcu_pos = self.get_mcu_position() mcu_pos = self.get_mcu_position()
self._stepper_kinematics = sk self._stepper_kinematics = sk
ffi_main, ffi_lib = chelper.get_ffi() ffi_main, ffi_lib = chelper.get_ffi()
ffi_lib.itersolve_set_stepcompress(sk, self._stepqueue, self._step_dist) ffi_lib.stepcompress_set_stepper_kinematics(self._stepqueue, sk);
self.set_trapq(self._trapq) self.set_trapq(self._trapq)
self._set_mcu_position(mcu_pos) self._set_mcu_position(mcu_pos)
return old_sk return old_sk
@ -229,27 +228,32 @@ class MCU_stepper:
ffi_main, ffi_lib = chelper.get_ffi() ffi_main, ffi_lib = chelper.get_ffi()
if tq is None: if tq is None:
tq = ffi_main.NULL tq = ffi_main.NULL
ffi_lib.itersolve_set_trapq(self._stepper_kinematics, tq) ffi_lib.itersolve_set_trapq(self._stepper_kinematics,
tq, self._step_dist)
old_tq = self._trapq old_tq = self._trapq
self._trapq = tq self._trapq = tq
return old_tq return old_tq
def add_active_callback(self, cb): def add_active_callback(self, cb):
self._active_callbacks.append(cb) self._active_callbacks.append(cb)
def generate_steps(self, flush_time): if len(self._active_callbacks) == 1:
# Check for activity if necessary printer = self._mcu.get_printer()
if self._active_callbacks: motion_queuing = printer.lookup_object('motion_queuing')
sk = self._stepper_kinematics motion_queuing.register_flush_callback(self._check_active)
ret = self._itersolve_check_active(sk, flush_time) def _check_active(self, must_flush_time, max_step_gen_time):
if ret:
cbs = self._active_callbacks
self._active_callbacks = []
for cb in cbs:
cb(ret)
# Generate steps
sk = self._stepper_kinematics sk = self._stepper_kinematics
ret = self._itersolve_generate_steps(sk, flush_time) ret = self._itersolve_check_active(sk, max_step_gen_time)
if ret: if not ret:
raise error("Internal error in stepcompress") # Stepper motor still not active
return
# Motor is active, disable future checking
printer = self._mcu.get_printer()
motion_queuing = printer.lookup_object('motion_queuing')
motion_queuing.unregister_flush_callback(self._check_active)
cbs = self._active_callbacks
self._active_callbacks = []
# Invoke callbacks
for cb in cbs:
cb(ret)
def is_active_axis(self, axis): def is_active_axis(self, axis):
ffi_main, ffi_lib = chelper.get_ffi() ffi_main, ffi_lib = chelper.get_ffi()
a = axis.encode() a = axis.encode()
@ -258,7 +262,6 @@ class MCU_stepper:
# Helper code to build a stepper object from a config section # Helper code to build a stepper object from a config section
def PrinterStepper(config, units_in_radians=False): def PrinterStepper(config, units_in_radians=False):
printer = config.get_printer() printer = config.get_printer()
name = config.get_name()
# Stepper definition # Stepper definition
ppins = printer.lookup_object('pins') ppins = printer.lookup_object('pins')
step_pin = config.get('step_pin') step_pin = config.get('step_pin')
@ -269,7 +272,7 @@ def PrinterStepper(config, units_in_radians=False):
config, units_in_radians, True) config, units_in_radians, True)
step_pulse_duration = config.getfloat('step_pulse_duration', None, step_pulse_duration = config.getfloat('step_pulse_duration', None,
minval=0., maxval=.001) minval=0., maxval=.001)
mcu_stepper = MCU_stepper(name, step_pin_params, dir_pin_params, mcu_stepper = MCU_stepper(config, step_pin_params, dir_pin_params,
rotation_dist, steps_per_rotation, rotation_dist, steps_per_rotation,
step_pulse_duration, units_in_radians) step_pulse_duration, units_in_radians)
# Register with helper modules # Register with helper modules
@ -442,9 +445,6 @@ class GenericPrinterRail:
def setup_itersolve(self, alloc_func, *params): def setup_itersolve(self, alloc_func, *params):
for stepper in self.steppers: for stepper in self.steppers:
stepper.setup_itersolve(alloc_func, *params) stepper.setup_itersolve(alloc_func, *params)
def generate_steps(self, flush_time):
for stepper in self.steppers:
stepper.generate_steps(flush_time)
def set_trapq(self, trapq): def set_trapq(self, trapq):
for stepper in self.steppers: for stepper in self.steppers:
stepper.set_trapq(trapq) stepper.set_trapq(trapq)

View File

@ -200,7 +200,6 @@ MIN_KIN_TIME = 0.100
MOVE_BATCH_TIME = 0.500 MOVE_BATCH_TIME = 0.500
STEPCOMPRESS_FLUSH_TIME = 0.050 STEPCOMPRESS_FLUSH_TIME = 0.050
SDS_CHECK_TIME = 0.001 # step+dir+step filter in stepcompress.c SDS_CHECK_TIME = 0.001 # step+dir+step filter in stepcompress.c
MOVE_HISTORY_EXPIRE = 30.
DRIP_SEGMENT_TIME = 0.050 DRIP_SEGMENT_TIME = 0.050
DRIP_TIME = 0.100 DRIP_TIME = 0.100
@ -219,17 +218,8 @@ class ToolHead:
# Velocity and acceleration control # Velocity and acceleration control
self.max_velocity = config.getfloat('max_velocity', above=0.) self.max_velocity = config.getfloat('max_velocity', above=0.)
self.max_accel = config.getfloat('max_accel', above=0.) self.max_accel = config.getfloat('max_accel', above=0.)
min_cruise_ratio = 0.5
if config.getfloat('minimum_cruise_ratio', None) is None:
req_accel_to_decel = config.getfloat('max_accel_to_decel', None,
above=0.)
if req_accel_to_decel is not None:
config.deprecate('max_accel_to_decel')
min_cruise_ratio = 1. - min(1., (req_accel_to_decel
/ self.max_accel))
self.min_cruise_ratio = config.getfloat('minimum_cruise_ratio', self.min_cruise_ratio = config.getfloat('minimum_cruise_ratio',
min_cruise_ratio, 0.5, below=1., minval=0.)
below=1., minval=0.)
self.square_corner_velocity = config.getfloat( self.square_corner_velocity = config.getfloat(
'square_corner_velocity', 5., minval=0.) 'square_corner_velocity', 5., minval=0.)
self.junction_deviation = self.max_accel_to_decel = 0. self.junction_deviation = self.max_accel_to_decel = 0.
@ -250,18 +240,14 @@ class ToolHead:
self.flush_timer = self.reactor.register_timer(self._flush_handler) self.flush_timer = self.reactor.register_timer(self._flush_handler)
self.do_kick_flush_timer = True self.do_kick_flush_timer = True
self.last_flush_time = self.min_restart_time = 0. self.last_flush_time = self.min_restart_time = 0.
self.need_flush_time = self.step_gen_time = self.clear_history_time = 0. self.need_flush_time = self.step_gen_time = 0.
# Kinematic step generation scan window time tracking # Kinematic step generation scan window time tracking
self.kin_flush_delay = SDS_CHECK_TIME self.kin_flush_delay = SDS_CHECK_TIME
self.kin_flush_times = [] self.kin_flush_times = []
# Setup iterative solver # Setup for generating moves
ffi_main, ffi_lib = chelper.get_ffi() self.motion_queuing = self.printer.load_object(config, 'motion_queuing')
self.trapq = ffi_main.gc(ffi_lib.trapq_alloc(), ffi_lib.trapq_free) self.trapq = self.motion_queuing.allocate_trapq()
self.trapq_append = ffi_lib.trapq_append self.trapq_append = self.motion_queuing.lookup_trapq_append()
self.trapq_finalize_moves = ffi_lib.trapq_finalize_moves
# Motion flushing
self.step_generators = []
self.flush_trapqs = [self.trapq]
# Create kinematics class # Create kinematics class
gcode = self.printer.lookup_object('gcode') gcode = self.printer.lookup_object('gcode')
self.Coord = gcode.Coord self.Coord = gcode.Coord
@ -279,20 +265,9 @@ class ToolHead:
msg = "Error loading kinematics '%s'" % (kin_name,) msg = "Error loading kinematics '%s'" % (kin_name,)
logging.exception(msg) logging.exception(msg)
raise config.error(msg) raise config.error(msg)
# Register commands # Register handlers
gcode.register_command('G4', self.cmd_G4)
gcode.register_command('M400', self.cmd_M400)
gcode.register_command('SET_VELOCITY_LIMIT',
self.cmd_SET_VELOCITY_LIMIT,
desc=self.cmd_SET_VELOCITY_LIMIT_help)
gcode.register_command('M204', self.cmd_M204)
self.printer.register_event_handler("klippy:shutdown", self.printer.register_event_handler("klippy:shutdown",
self._handle_shutdown) self._handle_shutdown)
# Load some default modules
modules = ["gcode_move", "homing", "idle_timeout", "statistics",
"manual_probe", "tuning_tower", "garbage_collection"]
for module_name in modules:
self.printer.load_object(config, module_name)
# Print time and flush tracking # Print time and flush tracking
def _advance_flush_time(self, flush_time): def _advance_flush_time(self, flush_time):
flush_time = max(flush_time, self.last_flush_time) flush_time = max(flush_time, self.last_flush_time)
@ -300,19 +275,10 @@ class ToolHead:
sg_flush_want = min(flush_time + STEPCOMPRESS_FLUSH_TIME, sg_flush_want = min(flush_time + STEPCOMPRESS_FLUSH_TIME,
self.print_time - self.kin_flush_delay) self.print_time - self.kin_flush_delay)
sg_flush_time = max(sg_flush_want, flush_time) sg_flush_time = max(sg_flush_want, flush_time)
for sg in self.step_generators: trapq_free_time = sg_flush_time - self.kin_flush_delay
sg(sg_flush_time) 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.min_restart_time = max(self.min_restart_time, sg_flush_time)
# Free trapq entries that are no longer needed
clear_history_time = self.clear_history_time
if not self.can_pause:
clear_history_time = flush_time - MOVE_HISTORY_EXPIRE
free_time = sg_flush_time - self.kin_flush_delay
for trapq in self.flush_trapqs:
self.trapq_finalize_moves(trapq, free_time, clear_history_time)
# Flush stepcompress and mcu steppersync
for m in self.all_mcus:
m.flush_moves(flush_time, clear_history_time)
self.last_flush_time = 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 pt_delay = self.kin_flush_delay + STEPCOMPRESS_FLUSH_TIME
@ -362,8 +328,7 @@ 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.note_mcu_movequeue_activity(next_move_time + self.kin_flush_delay)
set_step_gen_time=True)
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"/"Drip"/main state to "NeedPrime"
@ -506,39 +471,27 @@ class ToolHead:
eventtime = self.reactor.pause(eventtime + 0.100) eventtime = self.reactor.pause(eventtime + 0.100)
def set_extruder(self, extruder, extrude_pos): def set_extruder(self, extruder, extrude_pos):
# XXX - should use add_extra_axis # XXX - should use add_extra_axis
prev_ea_trapq = self.extra_axes[0].get_trapq()
if prev_ea_trapq in self.flush_trapqs:
self.flush_trapqs.remove(prev_ea_trapq)
self.extra_axes[0] = extruder self.extra_axes[0] = extruder
self.commanded_pos[3] = extrude_pos self.commanded_pos[3] = extrude_pos
ea_trapq = extruder.get_trapq()
if ea_trapq is not None:
self.flush_trapqs.append(ea_trapq)
def get_extruder(self): def get_extruder(self):
return self.extra_axes[0] return self.extra_axes[0]
def add_extra_axis(self, ea, axis_pos): def add_extra_axis(self, ea, axis_pos):
self._flush_lookahead() self._flush_lookahead()
self.extra_axes.append(ea) self.extra_axes.append(ea)
self.commanded_pos.append(axis_pos) self.commanded_pos.append(axis_pos)
ea_trapq = ea.get_trapq()
if ea_trapq is not None:
self.flush_trapqs.append(ea_trapq)
self.printer.send_event("toolhead:update_extra_axes") self.printer.send_event("toolhead:update_extra_axes")
def remove_extra_axis(self, ea): def remove_extra_axis(self, ea):
self._flush_lookahead() self._flush_lookahead()
if ea not in self.extra_axes: if ea not in self.extra_axes:
return return
ea_index = self.extra_axes.index(ea) + 3 ea_index = self.extra_axes.index(ea) + 3
ea_trapq = ea.get_trapq()
if ea_trapq in self.flush_trapqs:
self.flush_trapqs.remove(ea_trapq)
self.commanded_pos.pop(ea_index) self.commanded_pos.pop(ea_index)
self.extra_axes.pop(ea_index - 3) self.extra_axes.pop(ea_index - 3)
self.printer.send_event("toolhead:update_extra_axes") self.printer.send_event("toolhead:update_extra_axes")
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, addstepper=()): def drip_update_time(self, next_print_time, drip_completion):
# Transition from "NeedPrime"/"Priming"/main state to "Drip" state # Transition from "NeedPrime"/"Priming"/main state to "Drip" state
self.special_queuing_state = "Drip" self.special_queuing_state = "Drip"
self.need_check_pause = self.reactor.NEVER self.need_check_pause = self.reactor.NEVER
@ -559,10 +512,7 @@ class ToolHead:
drip_completion.wait(curtime + wait_time) drip_completion.wait(curtime + wait_time)
continue continue
npt = min(self.print_time + DRIP_SEGMENT_TIME, next_print_time) npt = min(self.print_time + DRIP_SEGMENT_TIME, next_print_time)
self.note_mcu_movequeue_activity(npt + self.kin_flush_delay, self.note_mcu_movequeue_activity(npt + self.kin_flush_delay)
set_step_gen_time=True)
for stepper in addstepper:
stepper.generate_steps(npt)
self._advance_move_time(npt) self._advance_move_time(npt)
# Exit "Drip" state # Exit "Drip" state
self.reactor.update_timer(self.flush_timer, self.reactor.NOW) self.reactor.update_timer(self.flush_timer, self.reactor.NOW)
@ -599,14 +549,13 @@ class ToolHead:
next_move_time = self._drip_load_trapq(move) next_move_time = self._drip_load_trapq(move)
self.drip_update_time(next_move_time, drip_completion) self.drip_update_time(next_move_time, drip_completion)
# Move finished; cleanup any remnants on trapq # Move finished; cleanup any remnants on trapq
self.trapq_finalize_moves(self.trapq, self.reactor.NEVER, 0) self.motion_queuing.wipe_trapq(self.trapq)
# Misc commands # Misc commands
def stats(self, eventtime): def stats(self, eventtime):
max_queue_time = max(self.print_time, self.last_flush_time) max_queue_time = max(self.print_time, self.last_flush_time)
for m in self.all_mcus: for m in self.all_mcus:
m.check_active(max_queue_time, eventtime) 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)
self.clear_history_time = est_print_time - MOVE_HISTORY_EXPIRE
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": if self.special_queuing_state == "Drip":
@ -639,11 +588,6 @@ class ToolHead:
return self.kin return self.kin
def get_trapq(self): def get_trapq(self):
return self.trapq return self.trapq
def register_step_generator(self, handler):
self.step_generators.append(handler)
def unregister_step_generator(self, handler):
if handler in self.step_generators:
self.step_generators.remove(handler)
def note_step_generation_scan_time(self, delay, old_delay=0.): def note_step_generation_scan_time(self, delay, old_delay=0.):
self.flush_step_generation() self.flush_step_generation()
if old_delay: if old_delay:
@ -658,9 +602,9 @@ class ToolHead:
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, set_step_gen_time=False): def note_mcu_movequeue_activity(self, mq_time, is_step_gen=True):
self.need_flush_time = max(self.need_flush_time, mq_time) self.need_flush_time = max(self.need_flush_time, mq_time)
if set_step_gen_time: if is_step_gen:
self.step_gen_time = max(self.step_gen_time, mq_time) self.step_gen_time = max(self.step_gen_time, mq_time)
if self.do_kick_flush_timer: if self.do_kick_flush_timer:
self.do_kick_flush_timer = False self.do_kick_flush_timer = False
@ -671,29 +615,8 @@ class ToolHead:
scv2 = self.square_corner_velocity**2 scv2 = self.square_corner_velocity**2
self.junction_deviation = scv2 * (math.sqrt(2.) - 1.) / self.max_accel self.junction_deviation = scv2 * (math.sqrt(2.) - 1.) / self.max_accel
self.max_accel_to_decel = self.max_accel * (1. - self.min_cruise_ratio) self.max_accel_to_decel = self.max_accel * (1. - self.min_cruise_ratio)
def cmd_G4(self, gcmd): def set_max_velocities(self, max_velocity, max_accel,
# Dwell square_corner_velocity, min_cruise_ratio):
delay = gcmd.get_float('P', 0., minval=0.) / 1000.
self.dwell(delay)
def cmd_M400(self, gcmd):
# Wait for current moves to finish
self.wait_moves()
cmd_SET_VELOCITY_LIMIT_help = "Set printer velocity limits"
def cmd_SET_VELOCITY_LIMIT(self, gcmd):
max_velocity = gcmd.get_float('VELOCITY', None, above=0.)
max_accel = gcmd.get_float('ACCEL', None, above=0.)
square_corner_velocity = gcmd.get_float(
'SQUARE_CORNER_VELOCITY', None, minval=0.)
min_cruise_ratio = gcmd.get_float(
'MINIMUM_CRUISE_RATIO', None, minval=0., below=1.)
if min_cruise_ratio is None:
req_accel_to_decel = gcmd.get_float('ACCEL_TO_DECEL',
None, above=0.)
if req_accel_to_decel is not None and max_accel is not None:
min_cruise_ratio = 1. - min(1., req_accel_to_decel / max_accel)
elif req_accel_to_decel is not None and max_accel is None:
min_cruise_ratio = 1. - min(1., (req_accel_to_decel
/ self.max_accel))
if max_velocity is not None: if max_velocity is not None:
self.max_velocity = max_velocity self.max_velocity = max_velocity
if max_accel is not None: if max_accel is not None:
@ -703,12 +626,43 @@ class ToolHead:
if min_cruise_ratio is not None: if min_cruise_ratio is not None:
self.min_cruise_ratio = min_cruise_ratio self.min_cruise_ratio = min_cruise_ratio
self._calc_junction_deviation() self._calc_junction_deviation()
return (self.max_velocity, self.max_accel,
self.square_corner_velocity, self.min_cruise_ratio)
# Support common G-Code commands relative to the toolhead
class ToolHeadCommandHelper:
def __init__(self, config):
self.printer = config.get_printer()
self.toolhead = self.printer.lookup_object("toolhead")
# Register commands
gcode = self.printer.lookup_object('gcode')
gcode.register_command('G4', self.cmd_G4)
gcode.register_command('M400', self.cmd_M400)
gcode.register_command('SET_VELOCITY_LIMIT',
self.cmd_SET_VELOCITY_LIMIT,
desc=self.cmd_SET_VELOCITY_LIMIT_help)
gcode.register_command('M204', self.cmd_M204)
def cmd_G4(self, gcmd):
# Dwell
delay = gcmd.get_float('P', 0., minval=0.) / 1000.
self.toolhead.dwell(delay)
def cmd_M400(self, gcmd):
# Wait for current moves to finish
self.toolhead.wait_moves()
cmd_SET_VELOCITY_LIMIT_help = "Set printer velocity limits"
def cmd_SET_VELOCITY_LIMIT(self, gcmd):
max_velocity = gcmd.get_float('VELOCITY', None, above=0.)
max_accel = gcmd.get_float('ACCEL', None, above=0.)
square_corner_velocity = gcmd.get_float(
'SQUARE_CORNER_VELOCITY', None, minval=0.)
min_cruise_ratio = gcmd.get_float(
'MINIMUM_CRUISE_RATIO', None, minval=0., below=1.)
mv, ma, scv, mcr = self.toolhead.set_max_velocities(
max_velocity, max_accel, square_corner_velocity, min_cruise_ratio)
msg = ("max_velocity: %.6f\n" 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" % ( "square_corner_velocity: %.6f" % (mv, ma, scv, mcr))
self.max_velocity, self.max_accel,
self.min_cruise_ratio, self.square_corner_velocity))
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):
@ -725,9 +679,16 @@ class ToolHead:
% (gcmd.get_commandline(),)) % (gcmd.get_commandline(),))
return return
accel = min(p, t) accel = min(p, t)
self.max_accel = accel self.toolhead.set_max_velocities(None, accel, None, None)
self._calc_junction_deviation()
def add_printer_objects(config): def add_printer_objects(config):
config.get_printer().add_object('toolhead', ToolHead(config)) printer = config.get_printer()
printer.add_object('toolhead', ToolHead(config))
ToolHeadCommandHelper(config)
# Load default extruder objects
kinematics.extruder.add_printer_objects(config) kinematics.extruder.add_printer_objects(config)
# Load some default modules
modules = ["gcode_move", "homing", "idle_timeout", "statistics",
"manual_probe", "tuning_tower", "garbage_collection"]
for module_name in modules:
printer.load_object(config, module_name)

View File

@ -169,6 +169,14 @@ BOARD_DEFS = {
"cs_pin": "PB12", "cs_pin": "PB12",
"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-x7': {
'mcu': "stm32f401xc",
'spi_bus': "spi2",
'cs_pin': "PB12",
'skip_verify': False,
'firmware_path': 'qd_mcu.bin',
'current_firmware_path': 'qd_mcu.CUR'
} }
} }
@ -219,7 +227,9 @@ BOARD_ALIASES = {
'fysetc-s6-v2': BOARD_DEFS['fysetc-spider'], 'fysetc-s6-v2': BOARD_DEFS['fysetc-spider'],
'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-q1-pro': BOARD_DEFS['qidi-x7'],
'qidi-plus4': BOARD_DEFS['qidi-x7']
} }
def list_boards(): def list_boards():

View File

@ -44,11 +44,15 @@ struct i2c_info {
DECL_CONSTANT_STR("BUS_PINS_i2c2_PB10_PB11", "PB10,PB11"); DECL_CONSTANT_STR("BUS_PINS_i2c2_PB10_PB11", "PB10,PB11");
DECL_ENUMERATION("i2c_bus", "i2c2_PB13_PB14", 4); DECL_ENUMERATION("i2c_bus", "i2c2_PB13_PB14", 4);
DECL_CONSTANT_STR("BUS_PINS_i2c2_PB13_PB14", "PB13,PB14"); DECL_CONSTANT_STR("BUS_PINS_i2c2_PB13_PB14", "PB13,PB14");
DECL_ENUMERATION("i2c_bus", "i2c2_PA7_PA6", 5);
DECL_CONSTANT_STR("BUS_PINS_i2c2_PA7_PA6", "PA7,PA6");
#ifdef I2C3 #ifdef I2C3
DECL_ENUMERATION("i2c_bus", "i2c3_PB3_PB4", 5); DECL_ENUMERATION("i2c_bus", "i2c3_PB3_PB4", 6);
DECL_CONSTANT_STR("BUS_PINS_i2c3_PB3_PB4", "PB3,PB4"); DECL_CONSTANT_STR("BUS_PINS_i2c3_PB3_PB4", "PB3,PB4");
DECL_ENUMERATION("i2c_bus", "i2c3_PC0_PC1", 6); DECL_ENUMERATION("i2c_bus", "i2c3_PC0_PC1", 7);
DECL_CONSTANT_STR("BUS_PINS_i2c3_PC0_PC1", "PC0,PC1"); DECL_CONSTANT_STR("BUS_PINS_i2c3_PC0_PC1", "PC0,PC1");
DECL_ENUMERATION("i2c_bus", "i2c3_PA7_PA6", 8);
DECL_CONSTANT_STR("BUS_PINS_i2c3_PA7_PA6", "PA7,PA6");
#endif #endif
#elif CONFIG_MACH_STM32L4 #elif CONFIG_MACH_STM32L4
DECL_ENUMERATION("i2c_bus", "i2c1_PB6_PB7", 0); DECL_ENUMERATION("i2c_bus", "i2c1_PB6_PB7", 0);
@ -105,9 +109,11 @@ static const struct i2c_info i2c_bus[] = {
{ I2C1, GPIO('A', 9), GPIO('A', 10), GPIO_FUNCTION(6) }, { I2C1, GPIO('A', 9), GPIO('A', 10), GPIO_FUNCTION(6) },
{ I2C2, GPIO('B', 10), GPIO('B', 11), GPIO_FUNCTION(6) }, { I2C2, GPIO('B', 10), GPIO('B', 11), GPIO_FUNCTION(6) },
{ I2C2, GPIO('B', 13), GPIO('B', 14), GPIO_FUNCTION(6) }, { I2C2, GPIO('B', 13), GPIO('B', 14), GPIO_FUNCTION(6) },
{ I2C2, GPIO('A', 7), GPIO('A', 6), GPIO_FUNCTION(8) },
#ifdef I2C3 #ifdef I2C3
{ I2C3, GPIO('B', 3), GPIO('B', 4), GPIO_FUNCTION(6) }, { I2C3, GPIO('B', 3), GPIO('B', 4), GPIO_FUNCTION(6) },
{ I2C3, GPIO('C', 0), GPIO('C', 1), GPIO_FUNCTION(6) }, { I2C3, GPIO('C', 0), GPIO('C', 1), GPIO_FUNCTION(6) },
{ I2C3, GPIO('A', 7), GPIO('A', 6), GPIO_FUNCTION(9) },
#endif #endif
#elif CONFIG_MACH_STM32L4 #elif CONFIG_MACH_STM32L4
{ I2C1, GPIO('B', 6), GPIO('B', 7), GPIO_FUNCTION(4) }, { I2C1, GPIO('B', 6), GPIO('B', 7), GPIO_FUNCTION(4) },

View File

@ -36,7 +36,7 @@ SET_GCODE_OFFSET Z=.1
M206 Z-.2 M206 Z-.2
SET_GCODE_OFFSET Z_ADJUST=-.1 SET_GCODE_OFFSET Z_ADJUST=-.1
SET_VELOCITY_LIMIT ACCEL=100 VELOCITY=20 SQUARE_CORNER_VELOCITY=1 ACCEL_TO_DECEL=200 SET_VELOCITY_LIMIT ACCEL=100 VELOCITY=20 SQUARE_CORNER_VELOCITY=1 MINIMUM_CRUISE_RATIO=0
M204 S500 M204 S500
SET_PRESSURE_ADVANCE EXTRUDER=extruder ADVANCE=.001 SET_PRESSURE_ADVANCE EXTRUDER=extruder ADVANCE=.001