Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix CLASSIC_JERK to consistently respect jerk limits. #26529

Merged
merged 15 commits into from
Dec 14, 2023
5 changes: 1 addition & 4 deletions Marlin/src/gcode/config/M200-M205.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,14 +297,11 @@ void GcodeSuite::M205() {
if (parser.seenval('S')) planner.settings.min_feedrate_mm_s = parser.value_linear_units();
if (parser.seenval('T')) planner.settings.min_travel_feedrate_mm_s = parser.value_linear_units();
#if HAS_JUNCTION_DEVIATION
#if ENABLED(CLASSIC_JERK) && AXIS_COLLISION('J')
#error "Can't set_max_jerk for 'J' axis because 'J' is used for Junction Deviation."
#endif
if (parser.seenval('J')) {
const float junc_dev = parser.value_linear_units();
if (WITHIN(junc_dev, 0.01f, 0.3f)) {
planner.junction_deviation_mm = junc_dev;
TERN_(LIN_ADVANCE, planner.recalculate_max_e_jerk());
TERN_(HAS_LINEAR_E_JERK, planner.recalculate_max_e_jerk());
}
else
SERIAL_ERROR_MSG("?J out of range (0.01 to 0.3)");
Expand Down
7 changes: 4 additions & 3 deletions Marlin/src/gcode/motion/G4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ void GcodeSuite::G4() {
SERIAL_ECHOLNPGM(STR_Z_MOVE_COMP);
#endif

if (!ui.has_status()) LCD_MESSAGE(MSG_DWELL);

dwell(dwell_ms);
if (dwell_ms) {
if (!ui.has_status()) LCD_MESSAGE(MSG_DWELL);
dwell(dwell_ms);
}
}
192 changes: 73 additions & 119 deletions Marlin/src/module/planner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,7 @@ float Planner::mm_per_step[DISTINCT_AXES]; // (mm) Millimeters per step
#if HAS_LINEAR_E_JERK
float Planner::max_e_jerk[DISTINCT_E]; // Calculated from junction_deviation_mm
#endif
#endif

#if ENABLED(CLASSIC_JERK)
#else // CLASSIC_JERK
TERN(HAS_LINEAR_E_JERK, xyz_pos_t, xyze_pos_t) Planner::max_jerk;
#endif

Expand Down Expand Up @@ -2374,42 +2372,42 @@ bool Planner::_populate_block(

// Limit speed on extruders, if any
#if HAS_EXTRUDERS
{
current_speed.e = dist_mm.e * inverse_secs;
#if HAS_MIXER_SYNC_CHANNEL
// Move all mixing extruders at the specified rate
if (mixer.get_current_vtool() == MIXER_AUTORETRACT_TOOL)
current_speed.e *= MIXING_STEPPERS;
#endif

const feedRate_t cs = ABS(current_speed.e),
max_fr = settings.max_feedrate_mm_s[E_AXIS_N(extruder)]
* TERN(HAS_MIXER_SYNC_CHANNEL, MIXING_STEPPERS, 1);

if (cs > max_fr) NOMORE(speed_factor, max_fr / cs); //respect max feedrate on any movement (doesn't matter if E axes only or not)

#if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT)
const feedRate_t max_vfr = volumetric_extruder_feedrate_limit[extruder]
* TERN(HAS_MIXER_SYNC_CHANNEL, MIXING_STEPPERS, 1);
{
current_speed.e = dist_mm.e * inverse_secs;
#if HAS_MIXER_SYNC_CHANNEL
// Move all mixing extruders at the specified rate
if (mixer.get_current_vtool() == MIXER_AUTORETRACT_TOOL)
current_speed.e *= MIXING_STEPPERS;
#endif

// TODO: Doesn't work properly for joined segments. Set MIN_STEPS_PER_SEGMENT 1 as workaround.
const feedRate_t cs = ABS(current_speed.e),
max_fr = settings.max_feedrate_mm_s[E_AXIS_N(extruder)]
* TERN(HAS_MIXER_SYNC_CHANNEL, MIXING_STEPPERS, 1);

if (block->steps.a || block->steps.b || block->steps.c) {
if (cs > max_fr) NOMORE(speed_factor, max_fr / cs); //respect max feedrate on any movement (doesn't matter if E axes only or not)

if (max_vfr > 0 && cs > max_vfr) {
NOMORE(speed_factor, max_vfr / cs); // respect volumetric extruder limit (if any)
/* <-- add a slash to enable
SERIAL_ECHOPGM("volumetric extruder limit enforced: ", (cs * CIRCLE_AREA(filament_size[extruder] * 0.5f)));
SERIAL_ECHOPGM(" mm^3/s (", cs);
SERIAL_ECHOPGM(" mm/s) limited to ", (max_vfr * CIRCLE_AREA(filament_size[extruder] * 0.5f)));
SERIAL_ECHOPGM(" mm^3/s (", max_vfr);
SERIAL_ECHOLNPGM(" mm/s)");
//*/
}
#if ENABLED(VOLUMETRIC_EXTRUDER_LIMIT)
const feedRate_t max_vfr = volumetric_extruder_feedrate_limit[extruder]
* TERN(HAS_MIXER_SYNC_CHANNEL, MIXING_STEPPERS, 1);

// TODO: Doesn't work properly for joined segments. Set MIN_STEPS_PER_SEGMENT 1 as workaround.

if (block->steps.a || block->steps.b || block->steps.c) {

if (max_vfr > 0 && cs > max_vfr) {
NOMORE(speed_factor, max_vfr / cs); // respect volumetric extruder limit (if any)
/* <-- add a slash to enable
SERIAL_ECHOPGM("volumetric extruder limit enforced: ", (cs * CIRCLE_AREA(filament_size[extruder] * 0.5f)));
SERIAL_ECHOPGM(" mm^3/s (", cs);
SERIAL_ECHOPGM(" mm/s) limited to ", (max_vfr * CIRCLE_AREA(filament_size[extruder] * 0.5f)));
SERIAL_ECHOPGM(" mm^3/s (", max_vfr);
SERIAL_ECHOLNPGM(" mm/s)");
//*/
}
#endif
}
#endif
}
#endif
}
#endif // HAS_EXTRUDERS

#ifdef XY_FREQUENCY_LIMIT

Expand Down Expand Up @@ -2492,7 +2490,7 @@ bool Planner::_populate_block(
*
* extruder_advance_K[extruder] : There is an advance factor set for this extruder.
*
* dist.e > 0 : Extruder is running forward (e.g., for "Wipe while retracting" (Slic3r) or "Combing" (Cura) moves)
* dist.e > 0 : Extruder is running forward (e.g., for "Wipe while retracting" (Slic3r) or "Combing" (Cura) moves)
*/
use_advance_lead = esteps && extruder_advance_K[E_INDEX_N(extruder)] && dist.e > 0;

Expand All @@ -2511,9 +2509,10 @@ bool Planner::_populate_block(
else {
// Scale E acceleration so that it will be possible to jump to the advance speed.
const uint32_t max_accel_steps_per_s2 = MAX_E_JERK(extruder) / (extruder_advance_K[E_INDEX_N(extruder)] * e_D_ratio) * steps_per_mm;
if (TERN0(LA_DEBUG, accel > max_accel_steps_per_s2))
SERIAL_ECHOLNPGM("Acceleration limited.");
NOMORE(accel, max_accel_steps_per_s2);
if (accel > max_accel_steps_per_s2) {
accel = max_accel_steps_per_s2;
if (ENABLED(LA_DEBUG)) SERIAL_ECHOLNPGM("Acceleration limited.");
}
}
}
#endif
Expand Down Expand Up @@ -2764,104 +2763,59 @@ bool Planner::_populate_block(

prev_unit_vec = unit_vec;

#endif

#if ENABLED(CLASSIC_JERK)
#else // CLASSIC_JERK

/**
* Adapted from Průša MKS firmware
* Heavily modified. Originally adapted from Průša firmware.
* https://github.com/prusa3d/Prusa-Firmware
*/
// Exit speed limited by a jerk to full halt of a previous last segment
static float previous_safe_speed;

// Start with a safe speed (from which the machine may halt to stop immediately).
float safe_speed = block->nominal_speed;

#ifndef TRAVEL_EXTRA_XYJERK
#define TRAVEL_EXTRA_XYJERK 0
#define TRAVEL_EXTRA_XYJERK 0.0f
#endif
const float extra_xyjerk = TERN0(HAS_EXTRUDERS, dist.e <= 0) ? TRAVEL_EXTRA_XYJERK : 0;

uint8_t limited = 0;
TERN(HAS_LINEAR_E_JERK, LOOP_NUM_AXES, LOOP_LOGICAL_AXES)(i) {
const float jerk = ABS(current_speed[i]), // cs : Starting from zero, change in speed for this axis
maxj = (max_jerk[i] + (i == X_AXIS || i == Y_AXIS ? extra_xyjerk : 0.0f)); // mj : The max jerk setting for this axis
if (jerk > maxj) { // cs > mj : New current speed too fast?
if (limited) { // limited already?
const float mjerk = block->nominal_speed * maxj; // ns*mj
if (jerk * safe_speed > mjerk) safe_speed = mjerk / jerk; // ns*mj/cs
}
else {
safe_speed *= maxj / jerk; // Initial limit: ns*mj/cs
++limited; // Initially limited
}
}
}
const float extra_xyjerk = TERN0(HAS_EXTRUDERS, dist.e <= 0) ? TRAVEL_EXTRA_XYJERK : 0.0f;

float vmax_junction;
if (moves_queued && !UNEAR_ZERO(previous_nominal_speed)) {
// Estimate a maximum velocity allowed at a joint of two successive segments.
// If this maximum velocity allowed is lower than the minimum of the entry / exit safe velocities,
// then the machine is not coasting anymore and the safe entry / exit velocities shall be used.
if (!moves_queued || UNEAR_ZERO(previous_nominal_speed)) {
// Compute "safe" speed, limited by a jerk to/from full halt.

// Factor to multiply the previous / current nominal velocities to get componentwise limited velocities.
float v_factor = 1;
limited = 0;
float v_factor = 1.0f;
LOOP_LOGICAL_AXES(i) {
const float jerk = ABS(current_speed[i]), // Starting from zero, change in speed for this axis
maxj = max_jerk[i] + (i == X_AXIS || i == Y_AXIS ? extra_xyjerk : 0.0f); // The max jerk setting for this axis
if (jerk * v_factor > maxj) v_factor = maxj / jerk;
}
vmax_junction_sqr = sq(block->nominal_speed * v_factor);
NOLESS(minimum_planner_speed_sqr, vmax_junction_sqr);
}
else {
// Compute the maximum velocity allowed at a joint of two successive segments.

// The junction velocity will be shared between successive segments. Limit the junction velocity to their minimum.
// Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting.
float smaller_speed_factor = 1.0f;
float vmax_junction, previous_speed_factor, current_speed_factor;
if (block->nominal_speed < previous_nominal_speed) {
vmax_junction = block->nominal_speed;
smaller_speed_factor = vmax_junction / previous_nominal_speed;
previous_speed_factor = vmax_junction / previous_nominal_speed;
current_speed_factor = 1.0f;
}
else
else {
vmax_junction = previous_nominal_speed;
previous_speed_factor = 1.0f;
current_speed_factor = vmax_junction / block->nominal_speed;
}

// Now limit the jerk in all axes.
TERN(HAS_LINEAR_E_JERK, LOOP_NUM_AXES, LOOP_LOGICAL_AXES)(axis) {
// Limit an axis. We have to differentiate: coasting, reversal of an axis, full stop.
float v_exit = previous_speed[axis] * smaller_speed_factor,
v_entry = current_speed[axis];
if (limited) {
v_exit *= v_factor;
v_entry *= v_factor;
}

// Calculate jerk depending on whether the axis is coasting in the same direction or reversing.
const float jerk = (v_exit > v_entry)
? // coasting axis reversal
( (v_entry > 0 || v_exit < 0) ? (v_exit - v_entry) : _MAX(v_exit, -v_entry) )
: // v_exit <= v_entry coasting axis reversal
( (v_entry < 0 || v_exit > 0) ? (v_entry - v_exit) : _MAX(-v_exit, v_entry) );

const float maxj = (max_jerk[axis] + (axis == X_AXIS || axis == Y_AXIS ? extra_xyjerk : 0.0f));

if (jerk > maxj) {
v_factor *= maxj / jerk;
++limited;
}
float v_factor = 1.0f;
LOOP_LOGICAL_AXES(i) {
// Scale per-axis velocities for the same vmax_junction.
const float v_exit = previous_speed[i] * previous_speed_factor,
v_entry = current_speed[i] * current_speed_factor;

// Jerk is the per-axis velocity difference.
const float jerk = ABS(v_exit - v_entry),
maxj = max_jerk[i] + (i == X_AXIS || i == Y_AXIS ? extra_xyjerk : 0.0f);
if (jerk * v_factor > maxj) v_factor = maxj / jerk;
}
if (limited) vmax_junction *= v_factor;
// Now the transition velocity is known, which maximizes the shared exit / entry velocity while
// respecting the jerk factors, it may be possible, that applying separate safe exit / entry velocities will achieve faster prints.
const float vmax_junction_threshold = vmax_junction * 0.99f;
if (previous_safe_speed > vmax_junction_threshold && safe_speed > vmax_junction_threshold)
vmax_junction = safe_speed;
vmax_junction_sqr = sq(vmax_junction * v_factor);
}
else
vmax_junction = safe_speed;

previous_safe_speed = safe_speed;

NOLESS(minimum_planner_speed_sqr, sq(safe_speed));

#if HAS_JUNCTION_DEVIATION
NOMORE(vmax_junction_sqr, sq(vmax_junction)); // Throttle down to max speed
#else
vmax_junction_sqr = sq(vmax_junction); // Go up or down to the new speed
#endif

#endif // CLASSIC_JERK

Expand Down
4 changes: 1 addition & 3 deletions Marlin/src/module/planner.h
Original file line number Diff line number Diff line change
Expand Up @@ -475,9 +475,7 @@ class Planner {
#if HAS_LINEAR_E_JERK
static float max_e_jerk[DISTINCT_E]; // Calculated from junction_deviation_mm
#endif
#endif

#if ENABLED(CLASSIC_JERK)
#else // CLASSIC_JERK
// (mm/s^2) M205 XYZ(E) - The largest speed change requiring no acceleration.
static TERN(HAS_LINEAR_E_JERK, xyz_pos_t, xyze_pos_t) max_jerk;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HAS_LINEAR_E_JERK is always false for CLASSIC_JERK:

#if ALL(HAS_JUNCTION_DEVIATION, LIN_ADVANCE)
  #define HAS_LINEAR_E_JERK 1
#endif

#endif
Expand Down
Loading