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

Linear advance advances #24533

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
092b84a
Use the correct extruder for LA
tombrazier Jun 30, 2022
de35fbd
Removed redundant multiplications
tombrazier Jun 30, 2022
4f3c183
Generate only one extruder pulse train for linear advance
tombrazier Jul 2, 2022
0242cfb
Reduce ISR frequency to the minimum possible for LA
tombrazier Jul 2, 2022
88bb808
Use the right step rate for the phase
tombrazier Jul 2, 2022
820153b
Take ADVANCE_STEP_SMOOTHING into account in LIN_ADVANCE.
tombrazier Jul 3, 2022
aaf7d0e
First step timing improvements
tombrazier Jul 10, 2022
f7d7f07
Reduce E jitter when E steps count is small compared to block->step_e…
tombrazier Jul 6, 2022
7689153
No need to copy la_advance_rate and la_scaling from current_block, bu…
tombrazier Jul 14, 2022
c6146b8
Cleaner MIXING_EXTRUDER pulse preparation
tombrazier Jul 16, 2022
a55f315
Ensure advance steps do not accumulate over time due to rounding and …
tombrazier Jul 16, 2022
869d15e
Save 1000 bytes of flash (on AVR) for slightly longer stepper ISR
tombrazier Jul 21, 2022
0606ee0
cleanup
thinkyhead Jul 25, 2022
4ec719f
optimize calc_timer_interval
thinkyhead Jul 25, 2022
6235796
move non-inline to cpp
thinkyhead Jul 25, 2022
ded2ccd
fix some header / dependencies
thinkyhead Jul 25, 2022
06b511a
Merge remote-tracking branch 'upstream/bugfix-2.1.x' into pr/24533
thinkyhead Jul 27, 2022
d97e6cd
Resolved step frequency alignment issues between pulse_phase_isr() an…
tombrazier Jul 29, 2022
c9d7d42
Set delta_error.e in XYZEval::operator=() and set la_delta_error to t…
tombrazier Jul 29, 2022
7349c72
Merge branch 'bugfix-2.1.x' into pr/24533
thinkyhead Jul 31, 2022
5630aea
cleanup
thinkyhead Jul 31, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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