Skip to content

Commit a3b1f03

Browse files
tombrazierLCh-77
authored andcommitted
⚡️ Optimize Planner calculations (MarlinFirmware#24484)
1 parent 0c69d7f commit a3b1f03

File tree

2 files changed

+80
-110
lines changed

2 files changed

+80
-110
lines changed

Marlin/src/module/planner.cpp

+78-86
Original file line numberDiff line numberDiff line change
@@ -28,38 +28,41 @@
2828
* Derived from Grbl
2929
* Copyright (c) 2009-2011 Simen Svale Skogsrud
3030
*
31-
* The ring buffer implementation gleaned from the wiring_serial library by David A. Mellis.
31+
* Ring buffer gleaned from wiring_serial library by David A. Mellis.
3232
*
33+
* Fast inverse function needed for Bézier interpolation for AVR
34+
* was designed, written and tested by Eduardo José Tagle, April 2018.
3335
*
34-
* Reasoning behind the mathematics in this module (in the key of 'Mathematica'):
36+
* Planner mathematics (Mathematica-style):
3537
*
36-
* s == speed, a == acceleration, t == time, d == distance
38+
* Where: s == speed, a == acceleration, t == time, d == distance
3739
*
3840
* Basic definitions:
3941
* Speed[s_, a_, t_] := s + (a*t)
4042
* Travel[s_, a_, t_] := Integrate[Speed[s, a, t], t]
4143
*
4244
* Distance to reach a specific speed with a constant acceleration:
4345
* Solve[{Speed[s, a, t] == m, Travel[s, a, t] == d}, d, t]
44-
* d -> (m^2 - s^2)/(2 a) --> estimate_acceleration_distance()
46+
* d -> (m^2 - s^2) / (2 a)
4547
*
4648
* Speed after a given distance of travel with constant acceleration:
4749
* Solve[{Speed[s, a, t] == m, Travel[s, a, t] == d}, m, t]
4850
* m -> Sqrt[2 a d + s^2]
4951
*
5052
* DestinationSpeed[s_, a_, d_] := Sqrt[2 a d + s^2]
5153
*
52-
* When to start braking (di) to reach a specified destination speed (s2) after accelerating
53-
* from initial speed s1 without ever stopping at a plateau:
54+
* When to start braking (di) to reach a specified destination speed (s2) after
55+
* acceleration from initial speed s1 without ever reaching a plateau:
5456
* Solve[{DestinationSpeed[s1, a, di] == DestinationSpeed[s2, a, d - di]}, di]
55-
* di -> (2 a d - s1^2 + s2^2)/(4 a) --> intersection_distance()
57+
* di -> (2 a d - s1^2 + s2^2)/(4 a)
5658
*
57-
* IntersectionDistance[s1_, s2_, a_, d_] := (2 a d - s1^2 + s2^2)/(4 a)
59+
* We note, as an optimization, that if we have already calculated an
60+
* acceleration distance d1 from s1 to m and a deceration distance d2
61+
* from m to s2 then
5862
*
59-
* --
60-
*
61-
* The fast inverse function needed for Bézier interpolation for AVR
62-
* was designed, written and tested by Eduardo José Tagle on April/2018
63+
* d1 -> (m^2 - s1^2) / (2 a)
64+
* d2 -> (m^2 - s2^2) / (2 a)
65+
* di -> (d + d1 - d2) / 2
6366
*/
6467

6568
#include "planner.h"
@@ -211,7 +214,7 @@ xyze_long_t Planner::position{0};
211214
uint32_t Planner::acceleration_long_cutoff;
212215

213216
xyze_float_t Planner::previous_speed;
214-
float Planner::previous_nominal_speed_sqr;
217+
float Planner::previous_nominal_speed;
215218

216219
#if ENABLED(DISABLE_INACTIVE_EXTRUDER)
217220
last_move_t Planner::g_uc_extruder_last_move[E_STEPPERS] = { 0 };
@@ -220,7 +223,7 @@ float Planner::previous_nominal_speed_sqr;
220223
#ifdef XY_FREQUENCY_LIMIT
221224
int8_t Planner::xy_freq_limit_hz = XY_FREQUENCY_LIMIT;
222225
float Planner::xy_freq_min_speed_factor = (XY_FREQUENCY_MIN_PERCENT) * 0.01f;
223-
int32_t Planner::xy_freq_min_interval_us = LROUND(1000000.0 / (XY_FREQUENCY_LIMIT));
226+
int32_t Planner::xy_freq_min_interval_us = LROUND(1000000.0f / (XY_FREQUENCY_LIMIT));
224227
#endif
225228

226229
#if ENABLED(LIN_ADVANCE)
@@ -250,7 +253,7 @@ void Planner::init() {
250253
TERN_(HAS_POSITION_FLOAT, position_float.reset());
251254
TERN_(IS_KINEMATIC, position_cart.reset());
252255
previous_speed.reset();
253-
previous_nominal_speed_sqr = 0;
256+
previous_nominal_speed = 0;
254257
TERN_(ABL_PLANAR, bed_level_matrix.set_to_identity());
255258
clear_block_buffer();
256259
delay_before_delivering = 0;
@@ -786,41 +789,48 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t
786789
NOLESS(final_rate, uint32_t(MINIMAL_STEP_RATE));
787790

788791
#if ENABLED(S_CURVE_ACCELERATION)
789-
uint32_t cruise_rate = initial_rate;
792+
// If we have some plateau time, the cruise rate will be the nominal rate
793+
uint32_t cruise_rate = block->nominal_rate;
790794
#endif
791795

792796
const int32_t accel = block->acceleration_steps_per_s2;
793797

794-
// Steps required for acceleration, deceleration to/from nominal rate
795-
uint32_t accelerate_steps = CEIL(estimate_acceleration_distance(initial_rate, block->nominal_rate, accel)),
796-
decelerate_steps = FLOOR(estimate_acceleration_distance(block->nominal_rate, final_rate, -accel));
797-
// Steps between acceleration and deceleration, if any
798-
int32_t plateau_steps = block->step_event_count - accelerate_steps - decelerate_steps;
799-
800-
// Does accelerate_steps + decelerate_steps exceed step_event_count?
801-
// Then we can't possibly reach the nominal rate, there will be no cruising.
802-
// Use intersection_distance() to calculate accel / braking time in order to
803-
// reach the final_rate exactly at the end of this block.
804-
if (plateau_steps < 0) {
805-
const float accelerate_steps_float = CEIL(intersection_distance(initial_rate, final_rate, accel, block->step_event_count));
806-
accelerate_steps = _MIN(uint32_t(_MAX(accelerate_steps_float, 0)), block->step_event_count);
807-
decelerate_steps = block->step_event_count - accelerate_steps;
808-
plateau_steps = 0;
809-
810-
#if ENABLED(S_CURVE_ACCELERATION)
811-
// We won't reach the cruising rate. Let's calculate the speed we will reach
812-
cruise_rate = final_speed(initial_rate, accel, accelerate_steps);
813-
#endif
798+
// Steps for acceleration, plateau and deceleration
799+
int32_t plateau_steps = block->step_event_count;
800+
uint32_t accelerate_steps = 0,
801+
decelerate_steps = 0;
802+
803+
if (accel != 0) {
804+
// Steps required for acceleration, deceleration to/from nominal rate
805+
const float nominal_rate_sq = sq(float(block->nominal_rate));
806+
float accelerate_steps_float = (nominal_rate_sq - sq(float(initial_rate))) * (0.5f / accel);
807+
accelerate_steps = CEIL(accelerate_steps_float);
808+
const float decelerate_steps_float = (nominal_rate_sq - sq(float(final_rate))) * (0.5f / accel);
809+
decelerate_steps = decelerate_steps_float;
810+
811+
// Steps between acceleration and deceleration, if any
812+
plateau_steps -= accelerate_steps + decelerate_steps;
813+
814+
// Does accelerate_steps + decelerate_steps exceed step_event_count?
815+
// Then we can't possibly reach the nominal rate, there will be no cruising.
816+
// Calculate accel / braking time in order to reach the final_rate exactly
817+
// at the end of this block.
818+
if (plateau_steps < 0) {
819+
accelerate_steps_float = CEIL((block->step_event_count + accelerate_steps_float - decelerate_steps_float) * 0.5f);
820+
accelerate_steps = _MIN(uint32_t(_MAX(accelerate_steps_float, 0)), block->step_event_count);
821+
decelerate_steps = block->step_event_count - accelerate_steps;
822+
823+
#if ENABLED(S_CURVE_ACCELERATION)
824+
// We won't reach the cruising rate. Let's calculate the speed we will reach
825+
cruise_rate = final_speed(initial_rate, accel, accelerate_steps);
826+
#endif
827+
}
814828
}
815-
#if ENABLED(S_CURVE_ACCELERATION)
816-
else // We have some plateau time, so the cruise rate will be the nominal rate
817-
cruise_rate = block->nominal_rate;
818-
#endif
819829

820830
#if ENABLED(S_CURVE_ACCELERATION)
821831
// Jerk controlled speed requires to express speed versus time, NOT steps
822-
uint32_t acceleration_time = ((float)(cruise_rate - initial_rate) / accel) * (STEPPER_TIMER_RATE),
823-
deceleration_time = ((float)(cruise_rate - final_rate) / accel) * (STEPPER_TIMER_RATE),
832+
uint32_t acceleration_time = (float(cruise_rate - initial_rate) / accel) * (STEPPER_TIMER_RATE),
833+
deceleration_time = (float(cruise_rate - final_rate) / accel) * (STEPPER_TIMER_RATE),
824834
// And to offload calculations from the ISR, we also calculate the inverse of those times here
825835
acceleration_time_inverse = get_period_inverse(acceleration_time),
826836
deceleration_time_inverse = get_period_inverse(deceleration_time);
@@ -1175,7 +1185,7 @@ void Planner::recalculate_trapezoids(TERN_(HINTS_SAFE_EXIT_SPEED, const_float_t
11751185

11761186
// Go from the tail (currently executed block) to the first block, without including it)
11771187
block_t *block = nullptr, *next = nullptr;
1178-
float current_entry_speed = 0.0, next_entry_speed = 0.0;
1188+
float current_entry_speed = 0.0f, next_entry_speed = 0.0f;
11791189
while (block_index != head_block_index) {
11801190

11811191
next = &block_buffer[block_index];
@@ -1199,13 +1209,12 @@ void Planner::recalculate_trapezoids(TERN_(HINTS_SAFE_EXIT_SPEED, const_float_t
11991209
// Block is not BUSY, we won the race against the Stepper ISR:
12001210

12011211
// NOTE: Entry and exit factors always > 0 by all previous logic operations.
1202-
const float current_nominal_speed = SQRT(block->nominal_speed_sqr),
1203-
nomr = 1.0f / current_nominal_speed;
1212+
const float nomr = 1.0f / block->nominal_speed;
12041213
calculate_trapezoid_for_block(block, current_entry_speed * nomr, next_entry_speed * nomr);
12051214
#if ENABLED(LIN_ADVANCE)
12061215
if (block->use_advance_lead) {
12071216
const float comp = block->e_D_ratio * extruder_advance_K[active_extruder] * settings.axis_steps_per_mm[E_AXIS];
1208-
block->max_adv_steps = current_nominal_speed * comp;
1217+
block->max_adv_steps = block->nominal_speed * comp;
12091218
block->final_adv_steps = next_entry_speed * comp;
12101219
}
12111220
#endif
@@ -1240,13 +1249,12 @@ void Planner::recalculate_trapezoids(TERN_(HINTS_SAFE_EXIT_SPEED, const_float_t
12401249
if (!stepper.is_block_busy(block)) {
12411250
// Block is not BUSY, we won the race against the Stepper ISR:
12421251

1243-
const float current_nominal_speed = SQRT(block->nominal_speed_sqr),
1244-
nomr = 1.0f / current_nominal_speed;
1252+
const float nomr = 1.0f / block->nominal_speed;
12451253
calculate_trapezoid_for_block(block, current_entry_speed * nomr, next_entry_speed * nomr);
12461254
#if ENABLED(LIN_ADVANCE)
12471255
if (block->use_advance_lead) {
12481256
const float comp = block->e_D_ratio * extruder_advance_K[active_extruder] * settings.axis_steps_per_mm[E_AXIS];
1249-
block->max_adv_steps = current_nominal_speed * comp;
1257+
block->max_adv_steps = block->nominal_speed * comp;
12501258
block->final_adv_steps = next_entry_speed * comp;
12511259
}
12521260
#endif
@@ -1290,14 +1298,10 @@ void Planner::recalculate(TERN_(HINTS_SAFE_EXIT_SPEED, const_float_t safe_exit_s
12901298
#define FAN_SET(F) do{ kickstart_fan(fan_speed, ms, F); _FAN_SET(F); }while(0)
12911299

12921300
const millis_t ms = millis();
1293-
TERN_(HAS_FAN0, FAN_SET(0));
1294-
TERN_(HAS_FAN1, FAN_SET(1));
1295-
TERN_(HAS_FAN2, FAN_SET(2));
1296-
TERN_(HAS_FAN3, FAN_SET(3));
1297-
TERN_(HAS_FAN4, FAN_SET(4));
1298-
TERN_(HAS_FAN5, FAN_SET(5));
1299-
TERN_(HAS_FAN6, FAN_SET(6));
1300-
TERN_(HAS_FAN7, FAN_SET(7));
1301+
TERN_(HAS_FAN0, FAN_SET(0)); TERN_(HAS_FAN1, FAN_SET(1));
1302+
TERN_(HAS_FAN2, FAN_SET(2)); TERN_(HAS_FAN3, FAN_SET(3));
1303+
TERN_(HAS_FAN4, FAN_SET(4)); TERN_(HAS_FAN5, FAN_SET(5));
1304+
TERN_(HAS_FAN6, FAN_SET(6)); TERN_(HAS_FAN7, FAN_SET(7));
13011305
}
13021306

13031307
#if FAN_KICKSTART_TIME
@@ -1485,7 +1489,7 @@ void Planner::check_axes_activity() {
14851489
for (uint8_t b = block_buffer_tail; b != block_buffer_head; b = next_block_index(b)) {
14861490
const block_t * const block = &block_buffer[b];
14871491
if (NUM_AXIS_GANG(block->steps.x, || block->steps.y, || block->steps.z, || block->steps.i, || block->steps.j, || block->steps.k, || block->steps.u, || block->steps.v, || block->steps.w)) {
1488-
const float se = (float)block->steps.e / block->step_event_count * SQRT(block->nominal_speed_sqr); // mm/sec;
1492+
const float se = float(block->steps.e) / block->step_event_count * block->nominal_speed; // mm/sec
14891493
NOLESS(high, se);
14901494
}
14911495
}
@@ -1936,7 +1940,7 @@ bool Planner::_populate_block(
19361940
#if ENABLED(MIXING_EXTRUDER)
19371941
bool ignore_e = false;
19381942
float collector[MIXING_STEPPERS];
1939-
mixer.refresh_collector(1.0, mixer.get_current_vtool(), collector);
1943+
mixer.refresh_collector(1.0f, mixer.get_current_vtool(), collector);
19401944
MIXER_STEPPER_LOOP(e)
19411945
if (e_steps * collector[e] > max_e_steps) { ignore_e = true; break; }
19421946
#else
@@ -2193,7 +2197,7 @@ bool Planner::_populate_block(
21932197
#if SECONDARY_LINEAR_AXES >= 1 && NONE(FOAMCUTTER_XYUV, ARTICULATED_ROBOT_ARM)
21942198
if (NEAR_ZERO(distance_sqr)) {
21952199
// Move does not involve any primary linear axes (xyz) but might involve secondary linear axes
2196-
distance_sqr = (0.0
2200+
distance_sqr = (0.0f
21972201
SECONDARY_AXIS_GANG(
21982202
IF_DISABLED(AXIS4_ROTATES, + sq(steps_dist_mm.i)),
21992203
IF_DISABLED(AXIS5_ROTATES, + sq(steps_dist_mm.j)),
@@ -2396,7 +2400,7 @@ bool Planner::_populate_block(
23962400
if (was_enabled) stepper.wake_up();
23972401
#endif
23982402

2399-
block->nominal_speed_sqr = sq(block->millimeters * inverse_secs); // (mm/sec)^2 Always > 0
2403+
block->nominal_speed = block->millimeters * inverse_secs; // (mm/sec) Always > 0
24002404
block->nominal_rate = CEIL(block->step_event_count * inverse_secs); // (step/sec) Always > 0
24012405

24022406
#if ENABLED(FILAMENT_WIDTH_SENSOR)
@@ -2492,7 +2496,7 @@ bool Planner::_populate_block(
24922496
if (speed_factor < 1.0f) {
24932497
current_speed *= speed_factor;
24942498
block->nominal_rate *= speed_factor;
2495-
block->nominal_speed_sqr = block->nominal_speed_sqr * sq(speed_factor);
2499+
block->nominal_speed *= speed_factor;
24962500
}
24972501

24982502
// Compute and limit the acceleration rate for the trapezoid generator.
@@ -2592,7 +2596,7 @@ bool Planner::_populate_block(
25922596
if (block->use_advance_lead) {
25932597
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)]);
25942598
#if ENABLED(LA_DEBUG)
2595-
if (extruder_advance_K[active_extruder] * block->e_D_ratio * block->acceleration * 2 < SQRT(block->nominal_speed_sqr) * block->e_D_ratio)
2599+
if (extruder_advance_K[active_extruder] * block->e_D_ratio * block->acceleration * 2 < block->nominal_speed * block->e_D_ratio)
25962600
SERIAL_ECHOLNPGM("More than 2 steps per eISR loop executed.");
25972601
if (block->advance_speed < 200)
25982602
SERIAL_ECHOLNPGM("eISR running at > 10kHz.");
@@ -2663,7 +2667,7 @@ bool Planner::_populate_block(
26632667
unit_vec *= inverse_millimeters; // Use pre-calculated (1 / SQRT(x^2 + y^2 + z^2))
26642668

26652669
// Skip first block or when previous_nominal_speed is used as a flag for homing and offset cycles.
2666-
if (moves_queued && !UNEAR_ZERO(previous_nominal_speed_sqr)) {
2670+
if (moves_queued && !UNEAR_ZERO(previous_nominal_speed)) {
26672671
// Compute cosine of angle between previous and current path. (prev_unit_vec is negative)
26682672
// NOTE: Max junction velocity is computed without sin() or acos() by trig half angle identity.
26692673
float junction_cos_theta = LOGICAL_AXIS_GANG(
@@ -2792,7 +2796,7 @@ bool Planner::_populate_block(
27922796
}
27932797

27942798
// Get the lowest speed
2795-
vmax_junction_sqr = _MIN(vmax_junction_sqr, block->nominal_speed_sqr, previous_nominal_speed_sqr);
2799+
vmax_junction_sqr = _MIN(vmax_junction_sqr, sq(block->nominal_speed), sq(previous_nominal_speed));
27962800
}
27972801
else // Init entry speed to zero. Assume it starts from rest. Planner will correct this later.
27982802
vmax_junction_sqr = 0;
@@ -2801,27 +2805,17 @@ bool Planner::_populate_block(
28012805

28022806
#endif
28032807

2804-
#ifdef USE_CACHED_SQRT
2805-
#define CACHED_SQRT(N, V) \
2806-
static float saved_V, N; \
2807-
if (V != saved_V) { N = SQRT(V); saved_V = V; }
2808-
#else
2809-
#define CACHED_SQRT(N, V) const float N = SQRT(V)
2810-
#endif
2811-
28122808
#if HAS_CLASSIC_JERK
28132809

28142810
/**
28152811
* Adapted from Průša MKS firmware
28162812
* https://github.com/prusa3d/Prusa-Firmware
28172813
*/
2818-
CACHED_SQRT(nominal_speed, block->nominal_speed_sqr);
2819-
28202814
// Exit speed limited by a jerk to full halt of a previous last segment
28212815
static float previous_safe_speed;
28222816

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

28262820
#ifndef TRAVEL_EXTRA_XYJERK
28272821
#define TRAVEL_EXTRA_XYJERK 0
@@ -2834,7 +2828,7 @@ bool Planner::_populate_block(
28342828
maxj = (max_jerk[i] + (i == X_AXIS || i == Y_AXIS ? extra_xyjerk : 0.0f)); // mj : The max jerk setting for this axis
28352829
if (jerk > maxj) { // cs > mj : New current speed too fast?
28362830
if (limited) { // limited already?
2837-
const float mjerk = nominal_speed * maxj; // ns*mj
2831+
const float mjerk = block->nominal_speed * maxj; // ns*mj
28382832
if (jerk * safe_speed > mjerk) safe_speed = mjerk / jerk; // ns*mj/cs
28392833
}
28402834
else {
@@ -2845,7 +2839,7 @@ bool Planner::_populate_block(
28452839
}
28462840

28472841
float vmax_junction;
2848-
if (moves_queued && !UNEAR_ZERO(previous_nominal_speed_sqr)) {
2842+
if (moves_queued && !UNEAR_ZERO(previous_nominal_speed)) {
28492843
// Estimate a maximum velocity allowed at a joint of two successive segments.
28502844
// If this maximum velocity allowed is lower than the minimum of the entry / exit safe velocities,
28512845
// then the machine is not coasting anymore and the safe entry / exit velocities shall be used.
@@ -2856,11 +2850,9 @@ bool Planner::_populate_block(
28562850

28572851
// The junction velocity will be shared between successive segments. Limit the junction velocity to their minimum.
28582852
// Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting.
2859-
CACHED_SQRT(previous_nominal_speed, previous_nominal_speed_sqr);
2860-
28612853
float smaller_speed_factor = 1.0f;
2862-
if (nominal_speed < previous_nominal_speed) {
2863-
vmax_junction = nominal_speed;
2854+
if (block->nominal_speed < previous_nominal_speed) {
2855+
vmax_junction = block->nominal_speed;
28642856
smaller_speed_factor = vmax_junction / previous_nominal_speed;
28652857
}
28662858
else
@@ -2927,11 +2919,11 @@ bool Planner::_populate_block(
29272919
// block nominal speed limits both the current and next maximum junction speeds. Hence, in both
29282920
// the reverse and forward planners, the corresponding block junction speed will always be at the
29292921
// the maximum junction speed and may always be ignored for any speed reduction checks.
2930-
block->flag.set_nominal(block->nominal_speed_sqr <= v_allowable_sqr);
2922+
block->flag.set_nominal(sq(block->nominal_speed) <= v_allowable_sqr);
29312923

29322924
// Update previous path unit_vector and nominal speed
29332925
previous_speed = current_speed;
2934-
previous_nominal_speed_sqr = block->nominal_speed_sqr;
2926+
previous_nominal_speed = block->nominal_speed;
29352927

29362928
position = target; // Update the position
29372929

@@ -3268,7 +3260,7 @@ void Planner::set_machine_position_mm(const abce_pos_t &abce) {
32683260
);
32693261

32703262
if (has_blocks_queued()) {
3271-
//previous_nominal_speed_sqr = 0.0; // Reset planner junction speeds. Assume start from rest.
3263+
//previous_nominal_speed = 0.0f; // Reset planner junction speeds. Assume start from rest.
32723264
//previous_speed.reset();
32733265
buffer_sync_block(BLOCK_BIT_SYNC_POSITION);
32743266
}
@@ -3344,7 +3336,7 @@ void Planner::refresh_positioning() {
33443336
inline void limit_and_warn(float &val, const AxisEnum axis, PGM_P const setting_name, const xyze_float_t &max_limit) {
33453337
const uint8_t lim_axis = TERN_(HAS_EXTRUDERS, axis > E_AXIS ? E_AXIS :) axis;
33463338
const float before = val;
3347-
LIMIT(val, 0.1, max_limit[lim_axis]);
3339+
LIMIT(val, 0.1f, max_limit[lim_axis]);
33483340
if (before != val) {
33493341
SERIAL_CHAR(AXIS_CHAR(lim_axis));
33503342
SERIAL_ECHOPGM(" Max ");

0 commit comments

Comments
 (0)