Skip to content

Commit

Permalink
Fix, improve Linear Advance (MarlinFirmware#24533)
Browse files Browse the repository at this point in the history
  • Loading branch information
tombrazier authored and LCh-77 committed Aug 22, 2022
1 parent c1a3df4 commit b479a27
Show file tree
Hide file tree
Showing 4 changed files with 305 additions and 351 deletions.
219 changes: 110 additions & 109 deletions Marlin/src/module/planner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,7 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
NOLESS(initial_rate, uint32_t(MINIMAL_STEP_RATE));
NOLESS(final_rate, uint32_t(MINIMAL_STEP_RATE));

#if ENABLED(S_CURVE_ACCELERATION)
#if EITHER(S_CURVE_ACCELERATION, LIN_ADVANCE)
// If we have some plateau time, the cruise rate will be the nominal rate
uint32_t cruise_rate = block->nominal_rate;
#endif
Expand Down Expand Up @@ -820,7 +820,7 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
accelerate_steps = _MIN(uint32_t(_MAX(accelerate_steps_float, 0)), block->step_event_count);
decelerate_steps = block->step_event_count - accelerate_steps;

#if ENABLED(S_CURVE_ACCELERATION)
#if EITHER(S_CURVE_ACCELERATION, LIN_ADVANCE)
// We won't reach the cruising rate. Let's calculate the speed we will reach
cruise_rate = final_speed(initial_rate, accel, accelerate_steps);
#endif
Expand Down Expand Up @@ -849,6 +849,14 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
#endif
block->final_rate = final_rate;

#if ENABLED(LIN_ADVANCE)
if (block->la_advance_rate) {
const float comp = extruder_advance_K[block->extruder] * block->steps.e / block->step_event_count;
block->max_adv_steps = cruise_rate * comp;
block->final_adv_steps = final_rate * comp;
}
#endif

#if ENABLED(LASER_POWER_TRAP)
/**
* Laser Trapezoid Calculations
Expand Down Expand Up @@ -899,75 +907,76 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
#endif // LASER_POWER_TRAP
}

/* PLANNER SPEED DEFINITION
+--------+ <- current->nominal_speed
/ \
current->entry_speed -> + \
| + <- next->entry_speed (aka exit speed)
+-------------+
time -->
Recalculates the motion plan according to the following basic guidelines:
1. Go over every feasible block sequentially in reverse order and calculate the junction speeds
(i.e. current->entry_speed) such that:
a. No junction speed exceeds the pre-computed maximum junction speed limit or nominal speeds of
neighboring blocks.
b. A block entry speed cannot exceed one reverse-computed from its exit speed (next->entry_speed)
with a maximum allowable deceleration over the block travel distance.
c. The last (or newest appended) block is planned from a complete stop (an exit speed of zero).
2. Go over every block in chronological (forward) order and dial down junction speed values if
a. The exit speed exceeds the one forward-computed from its entry speed with the maximum allowable
acceleration over the block travel distance.
When these stages are complete, the planner will have maximized the velocity profiles throughout the all
of the planner blocks, where every block is operating at its maximum allowable acceleration limits. In
other words, for all of the blocks in the planner, the plan is optimal and no further speed improvements
are possible. If a new block is added to the buffer, the plan is recomputed according to the said
guidelines for a new optimal plan.
To increase computational efficiency of these guidelines, a set of planner block pointers have been
created to indicate stop-compute points for when the planner guidelines cannot logically make any further
changes or improvements to the plan when in normal operation and new blocks are streamed and added to the
planner buffer. For example, if a subset of sequential blocks in the planner have been planned and are
bracketed by junction velocities at their maximums (or by the first planner block as well), no new block
added to the planner buffer will alter the velocity profiles within them. So we no longer have to compute
them. Or, if a set of sequential blocks from the first block in the planner (or a optimal stop-compute
point) are all accelerating, they are all optimal and can not be altered by a new block added to the
planner buffer, as this will only further increase the plan speed to chronological blocks until a maximum
junction velocity is reached. However, if the operational conditions of the plan changes from infrequently
used feed holds or feedrate overrides, the stop-compute pointers will be reset and the entire plan is
recomputed as stated in the general guidelines.
Planner buffer index mapping:
- block_buffer_tail: Points to the beginning of the planner buffer. First to be executed or being executed.
- block_buffer_head: Points to the buffer block after the last block in the buffer. Used to indicate whether
the buffer is full or empty. As described for standard ring buffers, this block is always empty.
- block_buffer_planned: Points to the first buffer block after the last optimally planned block for normal
streaming operating conditions. Use for planning optimizations by avoiding recomputing parts of the
planner buffer that don't change with the addition of a new block, as describe above. In addition,
this block can never be less than block_buffer_tail and will always be pushed forward and maintain
this requirement when encountered by the Planner::release_current_block() routine during a cycle.
NOTE: Since the planner only computes on what's in the planner buffer, some motions with many short
segments (e.g., complex curves) may seem to move slowly. This is because there simply isn't
enough combined distance traveled in the entire buffer to accelerate up to the nominal speed and
then decelerate to a complete stop at the end of the buffer, as stated by the guidelines. If this
happens and becomes an annoyance, there are a few simple solutions:
- Maximize the machine acceleration. The planner will be able to compute higher velocity profiles
within the same combined distance.
- Maximize line motion(s) distance per block to a desired tolerance. The more combined distance the
planner has to use, the faster it can go.
- Maximize the planner buffer size. This also will increase the combined distance for the planner to
compute over. It also increases the number of computations the planner has to perform to compute an
optimal plan, so select carefully.
- Use G2/G3 arcs instead of many short segments. Arcs inform the planner of a safe exit speed at the
end of the last segment, which alleviates this problem.
*/
/**
* PLANNER SPEED DEFINITION
* +--------+ <- current->nominal_speed
* / \
* current->entry_speed -> + \
* | + <- next->entry_speed (aka exit speed)
* +-------------+
* time -->
*
* Recalculates the motion plan according to the following basic guidelines:
*
* 1. Go over every feasible block sequentially in reverse order and calculate the junction speeds
* (i.e. current->entry_speed) such that:
* a. No junction speed exceeds the pre-computed maximum junction speed limit or nominal speeds of
* neighboring blocks.
* b. A block entry speed cannot exceed one reverse-computed from its exit speed (next->entry_speed)
* with a maximum allowable deceleration over the block travel distance.
* c. The last (or newest appended) block is planned from a complete stop (an exit speed of zero).
* 2. Go over every block in chronological (forward) order and dial down junction speed values if
* a. The exit speed exceeds the one forward-computed from its entry speed with the maximum allowable
* acceleration over the block travel distance.
*
* When these stages are complete, the planner will have maximized the velocity profiles throughout the all
* of the planner blocks, where every block is operating at its maximum allowable acceleration limits. In
* other words, for all of the blocks in the planner, the plan is optimal and no further speed improvements
* are possible. If a new block is added to the buffer, the plan is recomputed according to the said
* guidelines for a new optimal plan.
*
* To increase computational efficiency of these guidelines, a set of planner block pointers have been
* created to indicate stop-compute points for when the planner guidelines cannot logically make any further
* changes or improvements to the plan when in normal operation and new blocks are streamed and added to the
* planner buffer. For example, if a subset of sequential blocks in the planner have been planned and are
* bracketed by junction velocities at their maximums (or by the first planner block as well), no new block
* added to the planner buffer will alter the velocity profiles within them. So we no longer have to compute
* them. Or, if a set of sequential blocks from the first block in the planner (or a optimal stop-compute
* point) are all accelerating, they are all optimal and can not be altered by a new block added to the
* planner buffer, as this will only further increase the plan speed to chronological blocks until a maximum
* junction velocity is reached. However, if the operational conditions of the plan changes from infrequently
* used feed holds or feedrate overrides, the stop-compute pointers will be reset and the entire plan is
* recomputed as stated in the general guidelines.
*
* Planner buffer index mapping:
* - block_buffer_tail: Points to the beginning of the planner buffer. First to be executed or being executed.
* - block_buffer_head: Points to the buffer block after the last block in the buffer. Used to indicate whether
* the buffer is full or empty. As described for standard ring buffers, this block is always empty.
* - block_buffer_planned: Points to the first buffer block after the last optimally planned block for normal
* streaming operating conditions. Use for planning optimizations by avoiding recomputing parts of the
* planner buffer that don't change with the addition of a new block, as describe above. In addition,
* this block can never be less than block_buffer_tail and will always be pushed forward and maintain
* this requirement when encountered by the Planner::release_current_block() routine during a cycle.
*
* NOTE: Since the planner only computes on what's in the planner buffer, some motions with many short
* segments (e.g., complex curves) may seem to move slowly. This is because there simply isn't
* enough combined distance traveled in the entire buffer to accelerate up to the nominal speed and
* then decelerate to a complete stop at the end of the buffer, as stated by the guidelines. If this
* happens and becomes an annoyance, there are a few simple solutions:
*
* - Maximize the machine acceleration. The planner will be able to compute higher velocity profiles
* within the same combined distance.
*
* - Maximize line motion(s) distance per block to a desired tolerance. The more combined distance the
* planner has to use, the faster it can go.
*
* - Maximize the planner buffer size. This also will increase the combined distance for the planner to
* compute over. It also increases the number of computations the planner has to perform to compute an
* optimal plan, so select carefully.
*
* - Use G2/G3 arcs instead of many short segments. Arcs inform the planner of a safe exit speed at the
* end of the last segment, which alleviates this problem.
*/

// The kernel called by recalculate() when scanning the plan from last to first entry.
void Planner::reverse_pass_kernel(block_t * const current, const block_t * const next
Expand Down Expand Up @@ -1211,13 +1220,6 @@ void Planner::recalculate_trapezoids(TERN_(HINTS_SAFE_EXIT_SPEED, const_float_t
// NOTE: Entry and exit factors always > 0 by all previous logic operations.
const float nomr = 1.0f / block->nominal_speed;
calculate_trapezoid_for_block(block, current_entry_speed * nomr, next_entry_speed * nomr);
#if ENABLED(LIN_ADVANCE)
if (block->use_advance_lead) {
const float comp = block->e_D_ratio * extruder_advance_K[active_extruder] * settings.axis_steps_per_mm[E_AXIS];
block->max_adv_steps = block->nominal_speed * comp;
block->final_adv_steps = next_entry_speed * comp;
}
#endif
}

// Reset current only to ensure next trapezoid is computed - The
Expand Down Expand Up @@ -1251,13 +1253,6 @@ void Planner::recalculate_trapezoids(TERN_(HINTS_SAFE_EXIT_SPEED, const_float_t

const float nomr = 1.0f / block->nominal_speed;
calculate_trapezoid_for_block(block, current_entry_speed * nomr, next_entry_speed * nomr);
#if ENABLED(LIN_ADVANCE)
if (block->use_advance_lead) {
const float comp = block->e_D_ratio * extruder_advance_K[active_extruder] * settings.axis_steps_per_mm[E_AXIS];
block->max_adv_steps = block->nominal_speed * comp;
block->final_adv_steps = next_entry_speed * comp;
}
#endif
}

// Reset block to ensure its trapezoid is computed - The stepper is free to use
Expand Down Expand Up @@ -2502,13 +2497,15 @@ bool Planner::_populate_block(
// Compute and limit the acceleration rate for the trapezoid generator.
const float steps_per_mm = block->step_event_count * inverse_millimeters;
uint32_t accel;
#if ENABLED(LIN_ADVANCE)
bool use_advance_lead = false;
#endif
if (NUM_AXIS_GANG(
!block->steps.a, && !block->steps.b, && !block->steps.c,
&& !block->steps.i, && !block->steps.j, && !block->steps.k,
&& !block->steps.u, && !block->steps.v, && !block->steps.w)
) { // Is this a retract / recover move?
accel = CEIL(settings.retract_acceleration * steps_per_mm); // Convert to: acceleration steps/sec^2
TERN_(LIN_ADVANCE, block->use_advance_lead = false); // No linear advance for simple retract/recover
}
else {
#define LIMIT_ACCEL_LONG(AXIS,INDX) do{ \
Expand All @@ -2535,33 +2532,29 @@ bool Planner::_populate_block(
/**
* Use LIN_ADVANCE for blocks if all these are true:
*
* esteps : This is a print move, because we checked for A, B, C steps before.
* esteps : This is a print move, because we checked for A, B, C steps before.
*
* extruder_advance_K[active_extruder] : There is an advance factor set for this extruder.
* extruder_advance_K[extruder] : There is an advance factor set for this extruder.
*
* de > 0 : Extruder is running forward (e.g., for "Wipe while retracting" (Slic3r) or "Combing" (Cura) moves)
* de > 0 : Extruder is running forward (e.g., for "Wipe while retracting" (Slic3r) or "Combing" (Cura) moves)
*/
block->use_advance_lead = esteps
&& extruder_advance_K[active_extruder]
&& de > 0;

if (block->use_advance_lead) {
block->e_D_ratio = (target_float.e - position_float.e) /
#if IS_KINEMATIC
block->millimeters
#else
use_advance_lead = esteps && extruder_advance_K[extruder] && de > 0;

if (use_advance_lead) {
float e_D_ratio = (target_float.e - position_float.e) /
TERN(IS_KINEMATIC, block->millimeters,
SQRT(sq(target_float.x - position_float.x)
+ sq(target_float.y - position_float.y)
+ sq(target_float.z - position_float.z))
#endif
;
);

// Check for unusual high e_D ratio to detect if a retract move was combined with the last print move due to min. steps per segment. Never execute this with advance!
// This assumes no one will use a retract length of 0mm < retr_length < ~0.2mm and no one will print 100mm wide lines using 3mm filament or 35mm wide lines using 1.75mm filament.
if (block->e_D_ratio > 3.0f)
block->use_advance_lead = false;
if (e_D_ratio > 3.0f)
use_advance_lead = false;
else {
const uint32_t max_accel_steps_per_s2 = MAX_E_JERK(extruder) / (extruder_advance_K[active_extruder] * block->e_D_ratio) * steps_per_mm;
// 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[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);
Expand Down Expand Up @@ -2593,13 +2586,21 @@ bool Planner::_populate_block(
block->acceleration_rate = (uint32_t)(accel * (float(1UL << 24) / (STEPPER_TIMER_RATE)));
#endif
#if ENABLED(LIN_ADVANCE)
if (block->use_advance_lead) {
block->advance_speed = (STEPPER_TIMER_RATE) / (extruder_advance_K[active_extruder] * block->e_D_ratio * block->acceleration * settings.axis_steps_per_mm[E_AXIS_N(extruder)]);
block->la_advance_rate = 0;
block->la_scaling = 0;

if (use_advance_lead) {
// the Bresenham algorithm will convert this step rate into extruder steps
block->la_advance_rate = extruder_advance_K[extruder] * block->acceleration_steps_per_s2;

// reduce LA ISR frequency by calling it only often enough to ensure that there will
// never be more than four extruder steps per call
for (uint32_t dividend = block->steps.e << 1; dividend <= (block->step_event_count >> 2); dividend <<= 1)
block->la_scaling++;

#if ENABLED(LA_DEBUG)
if (extruder_advance_K[active_extruder] * block->e_D_ratio * block->acceleration * 2 < block->nominal_speed * block->e_D_ratio)
SERIAL_ECHOLNPGM("More than 2 steps per eISR loop executed.");
if (block->advance_speed < 200)
SERIAL_ECHOLNPGM("eISR running at > 10kHz.");
if (block->la_advance_rate >> block->la_scaling > 10000)
SERIAL_ECHOLNPGM("eISR running at > 10kHz: ", block->la_advance_rate);
#endif
}
#endif
Expand Down
11 changes: 5 additions & 6 deletions Marlin/src/module/planner.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,10 @@ typedef struct PlannerBlock {

// Advance extrusion
#if ENABLED(LIN_ADVANCE)
bool use_advance_lead;
uint16_t advance_speed, // STEP timer value for extruder speed offset ISR
max_adv_steps, // max. advance steps to get cruising speed pressure (not always nominal_speed!)
final_adv_steps; // advance steps due to exit speed
float e_D_ratio;
uint32_t la_advance_rate; // The rate at which steps are added whilst accelerating
uint8_t la_scaling; // Scale ISR frequency down and step frequency up by 2 ^ la_scaling
uint16_t max_adv_steps, // Max advance steps to get cruising speed pressure
final_adv_steps; // Advance steps for exit speed pressure
#endif

uint32_t nominal_rate, // The nominal step rate for this block in step_events/sec
Expand Down Expand Up @@ -1018,7 +1017,7 @@ class Planner {
return target_velocity_sqr - 2 * accel * distance;
}

#if ENABLED(S_CURVE_ACCELERATION)
#if EITHER(S_CURVE_ACCELERATION, LIN_ADVANCE)
/**
* Calculate the speed reached given initial speed, acceleration and distance
*/
Expand Down
Loading

0 comments on commit b479a27

Please sign in to comment.