diff --git a/docs/Bed_Mesh.md b/docs/Bed_Mesh.md index 5df081d1..0859913f 100644 --- a/docs/Bed_Mesh.md +++ b/docs/Bed_Mesh.md @@ -292,33 +292,6 @@ probe_count: 5, 3 z-offset. Note that this coordinate must NOT be in a location specified as 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 It is possible for some areas of a bed to report inaccurate results when diff --git a/docs/Config_Changes.md b/docs/Config_Changes.md index 4ab7d33d..838b2327 100644 --- a/docs/Config_Changes.md +++ b/docs/Config_Changes.md @@ -8,6 +8,11 @@ All dates in this document are approximate. ## 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 `scl_pin` and `sda_pin` options. Use `i2c_software_scl_pin` and `i2c_software_sda_pin` instead. diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index a9b6db6c..83de9609 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -126,8 +126,6 @@ max_accel: # decelerate to zero at each corner. The value specified here may be # changed at runtime using the SET_VELOCITY_LIMIT command. The # default is 5mm/s. -#max_accel_to_decel: -# This parameter is deprecated and should no longer be used. ``` ### [stepper] @@ -740,7 +738,6 @@ max_velocity: max_accel: #minimum_cruise_ratio: #square_corner_velocity: -#max_accel_to_decel: #max_z_velocity: #max_z_accel: diff --git a/klippy/chelper/__init__.py b/klippy/chelper/__init__.py index 622cc9a7..6ed6ed5b 100644 --- a/klippy/chelper/__init__.py +++ b/klippy/chelper/__init__.py @@ -17,16 +17,16 @@ COMPILE_ARGS = ("-Wall -g -O2 -shared -fPIC" " -o %s %s") SSE_FLAGS = "-mfpmath=sse -msse2" SOURCE_FILES = [ - 'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'itersolve.c', 'trapq.c', - 'pollreactor.c', 'msgblock.c', 'trdispatch.c', + 'pyhelper.c', 'serialqueue.c', 'stepcompress.c', 'steppersync.c', + 'itersolve.c', 'trapq.c', 'pollreactor.c', 'msgblock.c', 'trdispatch.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_extruder.c', 'kin_shaper.c', 'kin_idex.c', 'kin_generic.c' ] DEST_LIB = "c_helper.so" OTHER_FILES = [ - 'list.h', 'serialqueue.h', 'stepcompress.h', 'itersolve.h', 'pyhelper.h', - 'trapq.h', 'pollreactor.h', 'msgblock.h' + 'list.h', 'serialqueue.h', 'stepcompress.h', 'steppersync.h', + 'itersolve.h', 'pyhelper.h', 'trapq.h', 'pollreactor.h', 'msgblock.h' ] defs_stepcompress = """ @@ -54,25 +54,28 @@ defs_stepcompress = """ int stepcompress_extract_old(struct stepcompress *sc , struct pull_history_steps *p, int max , 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 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); - int steppersync_flush(struct steppersync *ss, uint64_t move_clock - , uint64_t clear_history_clock); + 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); """ defs_itersolve = """ - int32_t itersolve_generate_steps(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); - void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq); - void itersolve_set_stepcompress(struct stepper_kinematics *sk - , struct stepcompress *sc, double step_dist); + void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq + , double step_dist); double itersolve_calc_position_from_coord(struct stepper_kinematics *sk , double x, double y, double z); void itersolve_set_position(struct stepper_kinematics *sk @@ -228,7 +231,7 @@ defs_std = """ defs_all = [ 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_deltesian, defs_kin_polar, defs_kin_rotary_delta, defs_kin_winch, defs_kin_extruder, defs_kin_shaper, defs_kin_idex, diff --git a/klippy/chelper/itersolve.c b/klippy/chelper/itersolve.c index 0dbc6c51..eba1deef 100644 --- a/klippy/chelper/itersolve.c +++ b/klippy/chelper/itersolve.c @@ -26,8 +26,8 @@ struct timepos { // Generate step times for a portion of a move static int32_t -itersolve_gen_steps_range(struct stepper_kinematics *sk, struct move *m - , double abs_start, double abs_end) +itersolve_gen_steps_range(struct stepper_kinematics *sk, struct stepcompress *sc + , struct move *m, double abs_start, double abs_end) { sk_calc_callback calc_position_cb = sk->calc_position_cb; 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) end = m->move_t; 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; double target = sk->commanded_pos + (sdir ? half_step : -half_step); 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 (!is_dir_change && rel_dist >= -half_step) // 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 continue; } } // 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) return ret; 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 -int32_t __visible -itersolve_generate_steps(struct stepper_kinematics *sk, double flush_time) +int32_t +itersolve_generate_steps(struct stepper_kinematics *sk, struct stepcompress *sc + , double flush_time) { double last_flush_time = sk->last_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) pm = list_prev_entry(pm, node); do { - int32_t ret = itersolve_gen_steps_range(sk, pm, abs_start - , flush_time); + int32_t ret = itersolve_gen_steps_range( + sk, sc, pm, abs_start, flush_time); if (ret) return ret; pm = list_next_entry(pm, node); } while (pm != m); } // 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); if (ret) return ret; @@ -195,8 +196,8 @@ itersolve_generate_steps(struct stepper_kinematics *sk, double flush_time) double abs_end = force_steps_time; if (abs_end > flush_time) abs_end = flush_time; - int32_t ret = itersolve_gen_steps_range(sk, m, last_flush_time - , abs_end); + int32_t ret = itersolve_gen_steps_range( + sk, sc, m, last_flush_time, abs_end); if (ret) return ret; skip_count = 1; @@ -240,16 +241,10 @@ itersolve_is_active_axis(struct stepper_kinematics *sk, char axis) } 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; -} - -void __visible -itersolve_set_stepcompress(struct stepper_kinematics *sk - , struct stepcompress *sc, double step_dist) -{ - sk->sc = sc; sk->step_dist = step_dist; } diff --git a/klippy/chelper/itersolve.h b/klippy/chelper/itersolve.h index adb48055..e2e46ebe 100644 --- a/klippy/chelper/itersolve.h +++ b/klippy/chelper/itersolve.h @@ -26,12 +26,11 @@ struct stepper_kinematics { }; 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); 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_stepcompress(struct stepper_kinematics *sk - , struct stepcompress *sc, double step_dist); +void itersolve_set_trapq(struct stepper_kinematics *sk, struct trapq *tq + , double step_dist); double itersolve_calc_position_from_coord(struct stepper_kinematics *sk , double x, double y, double z); void itersolve_set_position(struct stepper_kinematics *sk diff --git a/klippy/chelper/stepcompress.c b/klippy/chelper/stepcompress.c index 310f2bf3..2889570d 100644 --- a/klippy/chelper/stepcompress.c +++ b/klippy/chelper/stepcompress.c @@ -1,6 +1,6 @@ // Stepper pulse schedule compression // -// Copyright (C) 2016-2021 Kevin O'Connor +// Copyright (C) 2016-2025 Kevin O'Connor // // This file may be distributed under the terms of the GNU GPLv3 license. @@ -21,6 +21,7 @@ #include // malloc #include // memset #include "compiler.h" // DIV_ROUND_UP +#include "itersolve.h" // itersolve_generate_steps #include "pyhelper.h" // errorf #include "serialqueue.h" // struct queue_message #include "stepcompress.h" // stepcompress_alloc @@ -46,6 +47,8 @@ struct stepcompress { // History tracking int64_t last_position; struct list_head history_list; + // Itersolve reference + struct stepper_kinematics *sk; }; 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 -static void -free_history(struct stepcompress *sc, uint64_t end_clock) +// Expire the stepcompress history older than the given clock +void +stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock) { while (!list_empty(&sc->history_list)) { 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 void __visible stepcompress_free(struct stepcompress *sc) @@ -305,7 +301,7 @@ stepcompress_free(struct stepcompress *sc) return; free(sc->queue); message_queue_free(&sc->msg_queue); - free_history(sc, UINT64_MAX); + stepcompress_history_expire(sc, UINT64_MAX); free(sc); } @@ -321,6 +317,12 @@ stepcompress_get_step_dir(struct stepcompress *sc) 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 static void 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 -static void +void stepcompress_set_time(struct stepcompress *sc , double time_offset, double mcu_freq) { @@ -664,164 +666,25 @@ stepcompress_extract_old(struct stepcompress *sc, struct pull_history_steps *p return res; } - -/**************************************************************** - * 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 +// Store a reference to stepper_kinematics void __visible -steppersync_free(struct steppersync *ss) +stepcompress_set_stepper_kinematics(struct stepcompress *sc + , struct stepper_kinematics *sk) { - if (!ss) - return; - free(ss->sc_list); - free(ss->move_clocks); - serialqueue_free_commandqueue(ss->cq); - free(ss); + sc->sk = sk; } -// Set the conversion rate of 'print_time' to mcu clock -void __visible -steppersync_set_time(struct steppersync *ss, double time_offset - , double mcu_freq) +// Generate steps (via itersolve) and flush +int32_t +stepcompress_generate_steps(struct stepcompress *sc, double gen_steps_time + , uint64_t flush_clock) { - int i; - for (i=0; isc_num; i++) { - struct stepcompress *sc = ss->sc_list[i]; - stepcompress_set_time(sc, time_offset, mcu_freq); - } -} - -// Expire the stepcompress history before the given clock time -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; isc_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; isc_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; + if (!sc->sk) + return 0; + // Generate steps + int32_t ret = itersolve_generate_steps(sc->sk, sc, gen_steps_time); + if (ret) + return ret; + // Flush steps + return stepcompress_flush(sc, flush_clock); } diff --git a/klippy/chelper/stepcompress.h b/klippy/chelper/stepcompress.h index c5b40383..e21c4fd9 100644 --- a/klippy/chelper/stepcompress.h +++ b/klippy/chelper/stepcompress.h @@ -17,9 +17,13 @@ void stepcompress_fill(struct stepcompress *sc, uint32_t max_error , int32_t set_next_step_dir_msgtag); void stepcompress_set_invert_sdir(struct stepcompress *sc , uint32_t invert_sdir); +void stepcompress_history_expire(struct stepcompress *sc, uint64_t end_clock); void stepcompress_free(struct stepcompress *sc); uint32_t stepcompress_get_oid(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 , double print_time, double step_time); 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 , struct pull_history_steps *p, int max , uint64_t start_clock, uint64_t end_clock); - -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); -int steppersync_flush(struct steppersync *ss, uint64_t move_clock - , uint64_t clear_history_clock); +struct stepper_kinematics; +void stepcompress_set_stepper_kinematics(struct stepcompress *sc + , struct stepper_kinematics *sk); +int32_t stepcompress_generate_steps(struct stepcompress *sc + , double gen_steps_time + , uint64_t flush_clock); #endif // stepcompress.h diff --git a/klippy/chelper/steppersync.c b/klippy/chelper/steppersync.c new file mode 100644 index 00000000..745578c7 --- /dev/null +++ b/klippy/chelper/steppersync.c @@ -0,0 +1,177 @@ +// Stepper step transmit synchronization +// +// Copyright (C) 2016-2025 Kevin O'Connor +// +// 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 // offsetof +#include // malloc +#include // 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; isc_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; isc_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; isc_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; +} diff --git a/klippy/chelper/steppersync.h b/klippy/chelper/steppersync.h new file mode 100644 index 00000000..1320bbaa --- /dev/null +++ b/klippy/chelper/steppersync.h @@ -0,0 +1,18 @@ +#ifndef STEPPERSYNC_H +#define STEPPERSYNC_H + +#include // 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 diff --git a/klippy/extras/force_move.py b/klippy/extras/force_move.py index 0be0eb9b..00f835f5 100644 --- a/klippy/extras/force_move.py +++ b/klippy/extras/force_move.py @@ -33,10 +33,10 @@ class ForceMove: self.printer = config.get_printer() self.steppers = {} # 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() - 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( ffi_lib.cartesian_stepper_alloc(b'x'), ffi_lib.free) # Register commands @@ -85,14 +85,12 @@ class ForceMove: self.trapq_append(self.trapq, print_time, accel_t, cruise_t, accel_t, 0., 0., 0., axis_r, 0., 0., 0., cruise_v, accel) print_time = print_time + accel_t + cruise_t + accel_t - 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.dwell(accel_t + cruise_t + accel_t) 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): name = gcmd.get('STEPPER') if name not in self.steppers: diff --git a/klippy/extras/manual_stepper.py b/klippy/extras/manual_stepper.py index 5a8949b9..05899f58 100644 --- a/klippy/extras/manual_stepper.py +++ b/klippy/extras/manual_stepper.py @@ -25,10 +25,9 @@ class ManualStepper: self.pos_min = config.getfloat('position_min', None) self.pos_max = config.getfloat('position_max', None) # Setup iterative solver - 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.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() self.rail.setup_itersolve('cartesian_stepper_alloc', b'x') self.rail.set_trapq(self.trapq) # Registered with toolhead as an axtra axis @@ -76,9 +75,6 @@ class ManualStepper: self.sync_print_time() self.next_cmd_time = self._submit_move(self.next_cmd_time, movepos, 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.note_mcu_movequeue_activity(self.next_cmd_time) if sync: @@ -138,7 +134,6 @@ class ManualStepper: raise gcmd.error("Must unregister axis first") # Unregister toolhead.remove_extra_axis(self) - toolhead.unregister_step_generator(self.rail.generate_steps) self.axis_gcode_id = None return 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_accel = limit_accel 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): axis_r = move.axes_r[ea_index] start_pos = move.start_pos[ea_index] @@ -208,10 +202,10 @@ class ManualStepper: speed, self.homing_accel) # Drip updates to motors 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 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.sync_print_time() def get_kinematics(self): diff --git a/klippy/extras/motion_queuing.py b/klippy/extras/motion_queuing.py new file mode 100644 index 00000000..a06b556e --- /dev/null +++ b/klippy/extras/motion_queuing.py @@ -0,0 +1,100 @@ +# Helper code for low-level motion queuing and flushing +# +# Copyright (C) 2025 Kevin O'Connor +# +# 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) diff --git a/klippy/extras/output_pin.py b/klippy/extras/output_pin.py index bde7ea69..63862d97 100644 --- a/klippy/extras/output_pin.py +++ b/klippy/extras/output_pin.py @@ -20,16 +20,17 @@ class GCodeRequestQueue: self.rqueue = [] self.next_min_flush_time = 0. 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) def _handle_connect(self): 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() rqueue = self.rqueue while rqueue: next_time = max(rqueue[0][0], self.next_min_flush_time) - if next_time > print_time: + if next_time > must_flush_time: return # Skip requests that have been overridden with a following request pos = 0 @@ -50,10 +51,11 @@ class GCodeRequestQueue: del rqueue[:pos+1] self.next_min_flush_time = next_time + max(min_wait, min_sched_time) # Ensure following queue items are flushed - self.toolhead.note_mcu_movequeue_activity(self.next_min_flush_time) + self.toolhead.note_mcu_movequeue_activity(self.next_min_flush_time, + is_step_gen=False) def _queue_request(self, print_time, value): self.rqueue.append((print_time, value)) - self.toolhead.note_mcu_movequeue_activity(print_time) + self.toolhead.note_mcu_movequeue_activity(print_time, is_step_gen=False) def queue_gcode_request(self, value): self.toolhead.register_lookahead_callback( (lambda pt: self._queue_request(pt, value))) diff --git a/klippy/extras/pwm_tool.py b/klippy/extras/pwm_tool.py index 46873203..6d401c0b 100644 --- a/klippy/extras/pwm_tool.py +++ b/klippy/extras/pwm_tool.py @@ -9,18 +9,18 @@ class error(Exception): pass class MCU_queued_pwm: - def __init__(self, pin_params): - self._mcu = pin_params['chip'] + def __init__(self, config, pin_params): + self._mcu = mcu = pin_params['chip'] self._hardware_pwm = False self._cycle_time = 0.100 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() - 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._mcu.register_config_callback(self._build_config) + mcu.register_config_callback(self._build_config) self._pin = pin_params['pin'] self._invert = pin_params['invert'] self._start_value = self._shutdown_value = float(self._invert) @@ -29,7 +29,6 @@ class MCU_queued_pwm: self._pwm_max = 0. self._set_cmd_tag = None self._toolhead = None - printer = self._mcu.get_printer() printer.register_event_handler("klippy:connect", self._handle_connect) def _handle_connect(self): 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._shutdown_value = max(0., min(1., shutdown_value)) 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: raise config_error("Pin with max duration must have start" " value equal to shutdown value") 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) self._last_clock = self._mcu.print_time_to_clock(printtime + 0.200) cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time) @@ -62,7 +62,8 @@ class MCU_queued_pwm: if self._duration_ticks >= 1<<31: raise config_error("PWM pin max duration too large") if self._duration_ticks: - 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: self._pwm_max = self._mcu.get_constant_float("PWM_MAX") self._default_value = self._shutdown_value * self._pwm_max @@ -115,14 +116,16 @@ class MCU_queued_pwm: # Continue flushing to resend time wakeclock += self._duration_ticks wake_print_time = self._mcu.clock_to_print_time(wakeclock) - self._toolhead.note_mcu_movequeue_activity(wake_print_time) + self._toolhead.note_mcu_movequeue_activity(wake_print_time, + is_step_gen=False) def set_pwm(self, print_time, value): clock = self._mcu.print_time_to_clock(print_time) if self._invert: value = 1. - value v = int(max(0., min(1., value)) * self._pwm_max + 0.5) 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: while clock >= 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') # Determine pin type 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() cycle_time = config.getfloat('cycle_time', 0.100, above=0., maxval=max_duration) diff --git a/klippy/extras/resonance_tester.py b/klippy/extras/resonance_tester.py index ff32dcac..c6171dc4 100644 --- a/klippy/extras/resonance_tester.py +++ b/klippy/extras/resonance_tester.py @@ -148,8 +148,7 @@ class ResonanceTestExecutor: last_v = last_t = last_accel = last_freq = 0. for next_t, accel, freq in test_seq: t_seg = next_t - last_t - toolhead.cmd_M204(self.gcode.create_gcode_command( - "M204", "M204", {"S": abs(accel)})) + toolhead.set_max_velocities(None, abs(accel), None, None) v = last_v + accel * t_seg abs_v = abs(v) if abs_v < 0.000001: @@ -182,8 +181,7 @@ class ResonanceTestExecutor: if last_v: d_decel = -.5 * last_v2 / old_max_accel decel_X, decel_Y = axis.get_point(d_decel) - toolhead.cmd_M204(self.gcode.create_gcode_command( - "M204", "M204", {"S": old_max_accel})) + toolhead.set_max_velocities(None, old_max_accel, None, None) toolhead.move([X + decel_X, Y + decel_Y] + tpos[2:], abs(last_v)) # Restore the original acceleration values self.gcode.run_script_from_command( diff --git a/klippy/extras/statistics.py b/klippy/extras/statistics.py index 90cd53f8..9e6188a8 100644 --- a/klippy/extras/statistics.py +++ b/klippy/extras/statistics.py @@ -65,8 +65,8 @@ class PrinterStats: def generate_stats(self, eventtime): stats = [cb(eventtime) for cb in self.stats_cb] if max([s[0] for s in stats]): - logging.info("Stats %.1f: %s", eventtime, - ' '.join([s[1] for s in stats])) + stats_str = ' '.join([s[1] for s in stats if s[1]]) + logging.info("Stats %.1f: %s", eventtime, stats_str) return eventtime + 1. def load_config(config): diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index f9030275..edd9c1a0 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -36,7 +36,6 @@ class CartKinematics: 'safe_distance', None, minval=0.)) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat('max_z_velocity', max_velocity, diff --git a/klippy/kinematics/corexy.py b/klippy/kinematics/corexy.py index d68f8854..f940c57f 100644 --- a/klippy/kinematics/corexy.py +++ b/klippy/kinematics/corexy.py @@ -20,7 +20,6 @@ class CoreXYKinematics: self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'z') for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( diff --git a/klippy/kinematics/corexz.py b/klippy/kinematics/corexz.py index 8d3e027c..d4a0eb35 100644 --- a/klippy/kinematics/corexz.py +++ b/klippy/kinematics/corexz.py @@ -20,7 +20,6 @@ class CoreXZKinematics: self.rails[2].setup_itersolve('corexz_stepper_alloc', b'-') for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( diff --git a/klippy/kinematics/delta.py b/klippy/kinematics/delta.py index aa244a8f..5ec4d54c 100644 --- a/klippy/kinematics/delta.py +++ b/klippy/kinematics/delta.py @@ -52,7 +52,6 @@ class DeltaKinematics: r.setup_itersolve('delta_stepper_alloc', a, t[0], t[1]) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks self.need_home = True self.limit_xy2 = -1. diff --git a/klippy/kinematics/deltesian.py b/klippy/kinematics/deltesian.py index 54b013a5..a62a58bd 100644 --- a/klippy/kinematics/deltesian.py +++ b/klippy/kinematics/deltesian.py @@ -40,7 +40,6 @@ class DeltesianKinematics: self.rails[2].setup_itersolve('cartesian_stepper_alloc', b'y') for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) self.limits = [(1.0, -1.0)] * 3 # X axis limits min_angle = config.getfloat('min_angle', MIN_ANGLE, diff --git a/klippy/kinematics/extruder.py b/klippy/kinematics/extruder.py index 58ccdbec..a89e3bdf 100644 --- a/klippy/kinematics/extruder.py +++ b/klippy/kinematics/extruder.py @@ -39,8 +39,6 @@ class ExtruderStepper: self.name, self.cmd_SYNC_EXTRUDER_MOTION, desc=self.cmd_SYNC_EXTRUDER_MOTION_help) 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) def get_status(self, eventtime): return {'pressure_advance': self.pressure_advance, @@ -165,10 +163,9 @@ class PrinterExtruder: self.instant_corner_v = config.getfloat( 'instantaneous_corner_velocity', 1., minval=0.) # Setup extruder trapq (trapezoidal motion queue) - 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.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() # Setup extruder stepper self.extruder_stepper = None if (config.get('step_pin', None) is not None diff --git a/klippy/kinematics/generic_cartesian.py b/klippy/kinematics/generic_cartesian.py index b8cabb77..49e16eec 100644 --- a/klippy/kinematics/generic_cartesian.py +++ b/klippy/kinematics/generic_cartesian.py @@ -108,7 +108,6 @@ class GenericCartesianKinematics: self._load_kinematics(config) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) self.dc_module = None if self.dc_carriages: pcs = [dc.get_dual_carriage() for dc in self.dc_carriages] diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py index fd2621d5..22884b93 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -39,7 +39,6 @@ class HybridCoreXYKinematics: 'safe_distance', None, minval=0.)) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py index b9699982..1cd646e2 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -39,7 +39,6 @@ class HybridCoreXZKinematics: 'safe_distance', None, minval=0.)) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( diff --git a/klippy/kinematics/polar.py b/klippy/kinematics/polar.py index ffa15d83..2e467d50 100644 --- a/klippy/kinematics/polar.py +++ b/klippy/kinematics/polar.py @@ -21,7 +21,6 @@ class PolarKinematics: for s in r.get_steppers() ] for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks max_velocity, max_accel = toolhead.get_max_velocity() self.max_z_velocity = config.getfloat( diff --git a/klippy/kinematics/rotary_delta.py b/klippy/kinematics/rotary_delta.py index 732a89a8..42b0a706 100644 --- a/klippy/kinematics/rotary_delta.py +++ b/klippy/kinematics/rotary_delta.py @@ -52,7 +52,6 @@ class RotaryDeltaKinematics: math.radians(a), ua, la) for s in self.get_steppers(): s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks self.need_home = True self.limit_xy2 = -1. diff --git a/klippy/kinematics/winch.py b/klippy/kinematics/winch.py index 47bc6855..2fa06ef2 100644 --- a/klippy/kinematics/winch.py +++ b/klippy/kinematics/winch.py @@ -21,7 +21,6 @@ class WinchKinematics: self.anchors.append(a) s.setup_itersolve('winch_stepper_alloc', *a) s.set_trapq(toolhead.get_trapq()) - toolhead.register_step_generator(s.generate_steps) # Setup boundary checks acoords = list(zip(*self.anchors)) self.axes_min = toolhead.Coord(*[min(a) for a in acoords], e=0.) diff --git a/klippy/mcu.py b/klippy/mcu.py index d3b8ffd7..db02e2a4 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -605,9 +605,7 @@ class MCU: self._max_stepper_error = config.getfloat('max_stepper_error', 0.000025, minval=0.) self._reserved_move_slots = 0 - self._stepqueues = [] self._steppersync = None - self._flush_callbacks = [] # Stats self._get_status_info = {} self._stats_sumsq_base = 0. @@ -770,13 +768,12 @@ class MCU: move_count = config_params['move_count'] if move_count < self._reserved_move_slots: raise error("Too few moves available on MCU '%s'" % (self._name,)) - ffi_main, ffi_lib = chelper.get_ffi() - self._steppersync = ffi_main.gc( - ffi_lib.steppersync_alloc(self._serial.get_serialqueue(), - self._stepqueues, len(self._stepqueues), - move_count-self._reserved_move_slots), - ffi_lib.steppersync_free) - ffi_lib.steppersync_set_time(self._steppersync, 0., self._mcu_freq) + ss_move_count = move_count - self._reserved_move_slots + motion_queuing = self._printer.lookup_object('motion_queuing') + self._steppersync = motion_queuing.allocate_steppersync( + self, self._serial.get_serialqueue(), ss_move_count) + self._ffi_lib.steppersync_set_time(self._steppersync, + 0., self._mcu_freq) # Log config information move_msg = "Configured MCU '%s' (%d moves)" % (self._name, move_count) logging.info(move_msg) @@ -971,27 +968,8 @@ class MCU: def _firmware_restart_bridge(self): self._firmware_restart(True) # Move queue tracking - def register_stepqueue(self, stepqueue): - self._stepqueues.append(stepqueue) def request_move_queue_slot(self): 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): if self._steppersync is None: return diff --git a/klippy/stepper.py b/klippy/stepper.py index 86a92358..d5b3cecd 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -19,22 +19,23 @@ MIN_OPTIMIZED_BOTH_EDGE_DURATION = 0.000000150 # Interface to low-level mcu and chelper code 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, step_pulse_duration=None, units_in_radians=False): - self._name = name + self._name = config.get_name() self._rotation_dist = rotation_dist self._steps_per_rotation = steps_per_rotation self._step_pulse_duration = step_pulse_duration self._units_in_radians = units_in_radians self._step_dist = rotation_dist / steps_per_rotation - self._mcu = step_pin_params['chip'] - self._oid = oid = self._mcu.create_oid() - self._mcu.register_config_callback(self._build_config) + self._mcu = mcu = step_pin_params['chip'] + self._oid = oid = mcu.create_oid() + mcu.register_config_callback(self._build_config) self._step_pin = step_pin_params['pin'] self._invert_step = step_pin_params['invert'] - if dir_pin_params['chip'] is not self._mcu: - raise self._mcu.get_printer().config_error( + printer = mcu.get_printer() + if dir_pin_params['chip'] is not mcu: + raise printer.config_error( "Stepper dir pin must be on same mcu as step pin") self._dir_pin = dir_pin_params['pin'] self._invert_dir = self._orig_invert_dir = dir_pin_params['invert'] @@ -42,17 +43,15 @@ class MCU_stepper: self._mcu_position_offset = 0. self._reset_cmd_tag = self._get_position_cmd = None 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() - 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) - self._mcu.register_stepqueue(self._stepqueue) self._stepper_kinematics = None - self._itersolve_generate_steps = ffi_lib.itersolve_generate_steps self._itersolve_check_active = ffi_lib.itersolve_check_active self._trapq = ffi_main.NULL - self._mcu.get_printer().register_event_handler('klippy:connect', - self._query_mcu_position) + printer.register_event_handler('klippy:connect', + self._query_mcu_position) def get_mcu(self): return self._mcu def get_name(self, short=False): @@ -135,7 +134,7 @@ class MCU_stepper: mcu_pos = self.get_mcu_position() self._rotation_dist = rotation_dist 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) def get_dir_inverted(self): return self._invert_dir, self._orig_invert_dir @@ -193,7 +192,7 @@ class MCU_stepper: mcu_pos = self.get_mcu_position() self._stepper_kinematics = sk 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_mcu_position(mcu_pos) return old_sk @@ -229,27 +228,32 @@ class MCU_stepper: ffi_main, ffi_lib = chelper.get_ffi() if tq is None: 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 self._trapq = tq return old_tq def add_active_callback(self, cb): self._active_callbacks.append(cb) - def generate_steps(self, flush_time): - # Check for activity if necessary - if self._active_callbacks: - sk = self._stepper_kinematics - ret = self._itersolve_check_active(sk, flush_time) - if ret: - cbs = self._active_callbacks - self._active_callbacks = [] - for cb in cbs: - cb(ret) - # Generate steps + if len(self._active_callbacks) == 1: + printer = self._mcu.get_printer() + motion_queuing = printer.lookup_object('motion_queuing') + motion_queuing.register_flush_callback(self._check_active) + def _check_active(self, must_flush_time, max_step_gen_time): sk = self._stepper_kinematics - ret = self._itersolve_generate_steps(sk, flush_time) - if ret: - raise error("Internal error in stepcompress") + ret = self._itersolve_check_active(sk, max_step_gen_time) + if not ret: + # 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): ffi_main, ffi_lib = chelper.get_ffi() a = axis.encode() @@ -258,7 +262,6 @@ class MCU_stepper: # Helper code to build a stepper object from a config section def PrinterStepper(config, units_in_radians=False): printer = config.get_printer() - name = config.get_name() # Stepper definition ppins = printer.lookup_object('pins') step_pin = config.get('step_pin') @@ -269,7 +272,7 @@ def PrinterStepper(config, units_in_radians=False): config, units_in_radians, True) step_pulse_duration = config.getfloat('step_pulse_duration', None, 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, step_pulse_duration, units_in_radians) # Register with helper modules @@ -442,9 +445,6 @@ class GenericPrinterRail: def setup_itersolve(self, alloc_func, *params): for stepper in self.steppers: 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): for stepper in self.steppers: stepper.set_trapq(trapq) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index f835977c..21aeca3d 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -200,7 +200,6 @@ MIN_KIN_TIME = 0.100 MOVE_BATCH_TIME = 0.500 STEPCOMPRESS_FLUSH_TIME = 0.050 SDS_CHECK_TIME = 0.001 # step+dir+step filter in stepcompress.c -MOVE_HISTORY_EXPIRE = 30. DRIP_SEGMENT_TIME = 0.050 DRIP_TIME = 0.100 @@ -219,17 +218,8 @@ class ToolHead: # Velocity and acceleration control self.max_velocity = config.getfloat('max_velocity', 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', - min_cruise_ratio, - below=1., minval=0.) + 0.5, below=1., minval=0.) self.square_corner_velocity = config.getfloat( 'square_corner_velocity', 5., minval=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.do_kick_flush_timer = True 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 self.kin_flush_delay = SDS_CHECK_TIME self.kin_flush_times = [] - # Setup iterative solver - 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 - # Motion flushing - self.step_generators = [] - self.flush_trapqs = [self.trapq] + # Setup for generating moves + 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() # Create kinematics class gcode = self.printer.lookup_object('gcode') self.Coord = gcode.Coord @@ -279,20 +265,9 @@ class ToolHead: msg = "Error loading kinematics '%s'" % (kin_name,) logging.exception(msg) raise config.error(msg) - # Register commands - 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) + # Register handlers self.printer.register_event_handler("klippy: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 def _advance_flush_time(self, 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, self.print_time - self.kin_flush_delay) sg_flush_time = max(sg_flush_want, flush_time) - for sg in self.step_generators: - sg(sg_flush_time) + trapq_free_time = sg_flush_time - self.kin_flush_delay + self.motion_queuing.flush_motion_queues(flush_time, sg_flush_time, + trapq_free_time) self.min_restart_time = max(self.min_restart_time, sg_flush_time) - # 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 def _advance_move_time(self, next_print_time): pt_delay = self.kin_flush_delay + STEPCOMPRESS_FLUSH_TIME @@ -362,8 +328,7 @@ class ToolHead: for cb in move.timing_callbacks: cb(next_move_time) # Generate steps for moves - self.note_mcu_movequeue_activity(next_move_time + self.kin_flush_delay, - set_step_gen_time=True) + self.note_mcu_movequeue_activity(next_move_time + self.kin_flush_delay) self._advance_move_time(next_move_time) def _flush_lookahead(self): # Transit from "NeedPrime"/"Priming"/"Drip"/main state to "NeedPrime" @@ -506,39 +471,27 @@ class ToolHead: eventtime = self.reactor.pause(eventtime + 0.100) def set_extruder(self, extruder, extrude_pos): # 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.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): return self.extra_axes[0] def add_extra_axis(self, ea, axis_pos): self._flush_lookahead() self.extra_axes.append(ea) 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") def remove_extra_axis(self, ea): self._flush_lookahead() if ea not in self.extra_axes: return 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.extra_axes.pop(ea_index - 3) self.printer.send_event("toolhead:update_extra_axes") def get_extra_axes(self): return [None, None, None] + self.extra_axes # Homing "drip move" handling - def drip_update_time(self, next_print_time, drip_completion, addstepper=()): + def drip_update_time(self, next_print_time, drip_completion): # Transition from "NeedPrime"/"Priming"/main state to "Drip" state self.special_queuing_state = "Drip" self.need_check_pause = self.reactor.NEVER @@ -559,10 +512,7 @@ class ToolHead: drip_completion.wait(curtime + wait_time) continue npt = min(self.print_time + DRIP_SEGMENT_TIME, next_print_time) - self.note_mcu_movequeue_activity(npt + self.kin_flush_delay, - set_step_gen_time=True) - for stepper in addstepper: - stepper.generate_steps(npt) + self.note_mcu_movequeue_activity(npt + self.kin_flush_delay) self._advance_move_time(npt) # Exit "Drip" state self.reactor.update_timer(self.flush_timer, self.reactor.NOW) @@ -599,14 +549,13 @@ class ToolHead: next_move_time = self._drip_load_trapq(move) self.drip_update_time(next_move_time, drip_completion) # 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 def stats(self, eventtime): max_queue_time = max(self.print_time, self.last_flush_time) for m in self.all_mcus: m.check_active(max_queue_time, eventtime) est_print_time = self.mcu.estimated_print_time(eventtime) - self.clear_history_time = est_print_time - MOVE_HISTORY_EXPIRE buffer_time = self.print_time - est_print_time is_active = buffer_time > -60. or not self.special_queuing_state if self.special_queuing_state == "Drip": @@ -639,11 +588,6 @@ class ToolHead: return self.kin def get_trapq(self): 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.): self.flush_step_generation() if old_delay: @@ -658,9 +602,9 @@ class ToolHead: callback(self.get_last_move_time()) return 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) - if set_step_gen_time: + if is_step_gen: self.step_gen_time = max(self.step_gen_time, mq_time) if self.do_kick_flush_timer: self.do_kick_flush_timer = False @@ -671,29 +615,8 @@ class ToolHead: scv2 = self.square_corner_velocity**2 self.junction_deviation = scv2 * (math.sqrt(2.) - 1.) / self.max_accel self.max_accel_to_decel = self.max_accel * (1. - self.min_cruise_ratio) - def cmd_G4(self, gcmd): - # Dwell - 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)) + def set_max_velocities(self, max_velocity, max_accel, + square_corner_velocity, min_cruise_ratio): if max_velocity is not None: self.max_velocity = max_velocity if max_accel is not None: @@ -703,12 +626,43 @@ class ToolHead: if min_cruise_ratio is not None: self.min_cruise_ratio = min_cruise_ratio 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" "max_accel: %.6f\n" "minimum_cruise_ratio: %.6f\n" - "square_corner_velocity: %.6f" % ( - self.max_velocity, self.max_accel, - self.min_cruise_ratio, self.square_corner_velocity)) + "square_corner_velocity: %.6f" % (mv, ma, scv, mcr)) self.printer.set_rollover_info("toolhead", "toolhead: %s" % (msg,)) if (max_velocity is None and max_accel is None and square_corner_velocity is None and min_cruise_ratio is None): @@ -725,9 +679,16 @@ class ToolHead: % (gcmd.get_commandline(),)) return accel = min(p, t) - self.max_accel = accel - self._calc_junction_deviation() + self.toolhead.set_max_velocities(None, accel, None, None) 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) + # 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) diff --git a/scripts/spi_flash/board_defs.py b/scripts/spi_flash/board_defs.py index 44eefa4f..0e82d27b 100644 --- a/scripts/spi_flash/board_defs.py +++ b/scripts/spi_flash/board_defs.py @@ -169,6 +169,14 @@ BOARD_DEFS = { "cs_pin": "PB12", "firmware_path": "ZNP_ROBIN_NANO.bin", "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'], 'robin_v3': BOARD_DEFS['monster8'], '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(): diff --git a/src/stm32/stm32f0_i2c.c b/src/stm32/stm32f0_i2c.c index 381fe8b4..61c848e4 100644 --- a/src/stm32/stm32f0_i2c.c +++ b/src/stm32/stm32f0_i2c.c @@ -44,11 +44,15 @@ struct i2c_info { DECL_CONSTANT_STR("BUS_PINS_i2c2_PB10_PB11", "PB10,PB11"); DECL_ENUMERATION("i2c_bus", "i2c2_PB13_PB14", 4); 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 - 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_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_ENUMERATION("i2c_bus", "i2c3_PA7_PA6", 8); + DECL_CONSTANT_STR("BUS_PINS_i2c3_PA7_PA6", "PA7,PA6"); #endif #elif CONFIG_MACH_STM32L4 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) }, { I2C2, GPIO('B', 10), GPIO('B', 11), 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 { I2C3, GPIO('B', 3), GPIO('B', 4), GPIO_FUNCTION(6) }, { I2C3, GPIO('C', 0), GPIO('C', 1), GPIO_FUNCTION(6) }, + { I2C3, GPIO('A', 7), GPIO('A', 6), GPIO_FUNCTION(9) }, #endif #elif CONFIG_MACH_STM32L4 { I2C1, GPIO('B', 6), GPIO('B', 7), GPIO_FUNCTION(4) }, diff --git a/test/klippy/commands.test b/test/klippy/commands.test index 33c59961..cb3b17d9 100644 --- a/test/klippy/commands.test +++ b/test/klippy/commands.test @@ -36,7 +36,7 @@ SET_GCODE_OFFSET Z=.1 M206 Z-.2 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 SET_PRESSURE_ADVANCE EXTRUDER=extruder ADVANCE=.001