Merge branch 'Klipper3d:master' into master

This commit is contained in:
Andrei 2025-08-17 06:13:48 +02:00 committed by GitHub
commit 7018075f70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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