mirror of
https://github.com/andreili/klipper.git
synced 2025-08-23 11:24:06 +02:00
Merge branch 'Klipper3d:master' into master
This commit is contained in:
commit
7018075f70
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -1,6 +1,6 @@
|
||||
// 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.
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
#include <stdlib.h> // malloc
|
||||
#include <string.h> // 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; i<ss->sc_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; i<ss->sc_num; i++) {
|
||||
int ret = stepcompress_flush(ss->sc_list[i], move_clock);
|
||||
if (!sc->sk)
|
||||
return 0;
|
||||
// Generate steps
|
||||
int32_t ret = itersolve_generate_steps(sc->sk, sc, gen_steps_time);
|
||||
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;
|
||||
// Flush steps
|
||||
return stepcompress_flush(sc, flush_clock);
|
||||
}
|
||||
|
@ -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
|
||||
|
177
klippy/chelper/steppersync.c
Normal file
177
klippy/chelper/steppersync.c
Normal 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;
|
||||
}
|
18
klippy/chelper/steppersync.h
Normal file
18
klippy/chelper/steppersync.h
Normal 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
|
@ -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:
|
||||
|
@ -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):
|
||||
|
100
klippy/extras/motion_queuing.py
Normal file
100
klippy/extras/motion_queuing.py
Normal 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)
|
@ -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)))
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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):
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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.
|
||||
|
@ -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.)
|
||||
|
@ -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
|
||||
|
@ -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,16 +43,14 @@ 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',
|
||||
printer.register_event_handler('klippy:connect',
|
||||
self._query_mcu_position)
|
||||
def get_mcu(self):
|
||||
return self._mcu
|
||||
@ -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:
|
||||
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_check_active(sk, flush_time)
|
||||
if ret:
|
||||
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)
|
||||
# Generate steps
|
||||
sk = self._stepper_kinematics
|
||||
ret = self._itersolve_generate_steps(sk, flush_time)
|
||||
if ret:
|
||||
raise error("Internal error in stepcompress")
|
||||
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)
|
||||
|
@ -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)
|
||||
|
@ -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():
|
||||
|
@ -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) },
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user