diff --git a/Marlin/src/module/planner.cpp b/Marlin/src/module/planner.cpp index 8f11f4ec18a5..b4546e0235bf 100644 --- a/Marlin/src/module/planner.cpp +++ b/Marlin/src/module/planner.cpp @@ -128,7 +128,6 @@ Planner planner; block_t Planner::block_buffer[BLOCK_BUFFER_SIZE]; volatile uint8_t Planner::block_buffer_head, // Index of the next block to be pushed Planner::block_buffer_nonbusy, // Index of the first non-busy block - Planner::block_buffer_planned, // Index of the optimally planned block Planner::block_buffer_tail; // Index of the busy block, if any uint16_t Planner::cleaning_buffer_counter; // A counter to disable queuing of blocks uint8_t Planner::delay_before_delivering; // Delay block delivery so initial blocks in an empty queue may merge @@ -768,10 +767,6 @@ block_t* Planner::get_current_block() { // As this block is busy, advance the nonbusy block pointer block_buffer_nonbusy = next_block_index(block_buffer_tail); - // Push block_buffer_planned pointer, if encountered. - if (block_buffer_tail == block_buffer_planned) - block_buffer_planned = block_buffer_nonbusy; - // Return the block return block; } @@ -784,29 +779,30 @@ block_t* Planner::get_current_block() { /** * Calculate trapezoid parameters, multiplying the entry- and exit-speeds - * by the provided factors. Requires that initial_rate and final_rate are - * no less than sqrt(block->acceleration_steps_per_s2 / 2), which is ensured - * through minimum_planner_speed_sqr in _populate_block(). + * by the provided factors. If entry_factor is 0 don't change the initial_rate. + * Assumes that the implied initial_rate and final_rate are no less than + * sqrt(block->acceleration_steps_per_s2 / 2). This is ensured through + * minimum_planner_speed_sqr / min_entry_speed_sqr though note there's one + * exception in recalculate_trapezoids(). ** * ############ VERY IMPORTANT ############ * NOTE that the PRECONDITION to call this function is that the block is * NOT BUSY and it is marked as RECALCULATE. That WARRANTIES the Stepper ISR - * is not and will not use the block while we modify it, so it is safe to - * alter its values. + * is not and will not use the block while we modify it. */ -void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t entry_factor, const_float_t exit_factor) { +void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t entry_speed, const_float_t exit_speed) { - uint32_t initial_rate = LROUND(block->nominal_rate * entry_factor), - final_rate = LROUND(block->nominal_rate * exit_factor); // (steps per second) + const float spmm = block->steps_per_mm; + uint32_t initial_rate = entry_speed ? LROUND(entry_speed * spmm) : block->initial_rate, + final_rate = LROUND(exit_speed * spmm); - // Legacy check against supposed timer overflow. However Stepper::calc_timer_interval() already - // should protect against it. But removing this code produces judder in direction-switching - // moves. This is because the current discrete stepping math diverges from physical motion under - // constant acceleration when acceleration_steps_per_s2 is large compared to initial/final_rate. - NOLESS(initial_rate, uint32_t(MINIMAL_STEP_RATE)); // Enforce the minimum speed - NOLESS(final_rate, uint32_t(MINIMAL_STEP_RATE)); - NOMORE(initial_rate, block->nominal_rate); // NOTE: The nominal rate may be less than MINIMAL_STEP_RATE! - NOMORE(final_rate, block->nominal_rate); + // Removing code to constrain values produces judder in direction-switching moves because the + // current discrete stepping math diverges from physical motion under constant acceleration + // when acceleration_steps_per_s2 is large compared to initial/final_rate. + NOLESS(initial_rate, long(MINIMAL_STEP_RATE)); + NOLESS(final_rate, long(MINIMAL_STEP_RATE)); + NOMORE(initial_rate, block->nominal_rate); // NOTE: The nominal rate may be less than MINIMAL_STEP_RATE! + NOMORE(final_rate, block->nominal_rate); #if ANY(S_CURVE_ACCELERATION, LIN_ADVANCE) // If we have some plateau time, the cruise rate will be the nominal rate @@ -942,16 +938,16 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t * * 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 safe_exit_speed_sqr. - * 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. + * 1. Go over blocks sequentially in reverse order and maximize the entry junction speed: + * a. Entry speed should stay below/at the pre-computed maximum junction speed limit + * b. Aim for the maximum entry speed which is the one reverse-computed from its exit speed + * (next->entry_speed) if assuming maximum deceleration over the full block travel distance + * c. The last (newest appended) block uses safe_exit_speed exit speed (there's no 'next') + * 2. Go over blocks in chronological (forward) order and fix the exit junction speed: + * a. Exit speed (next->entry_speed) must be below/at the maximum exit speed forward-computed + * from its entry speed if assuming maximum acceleration over the full block travel distance + * b. Exit speed should stay above/at the pre-computed minimum junction speed limit + * 3. Convert entry / exit speeds (mm/s) into final/initial steps/s * * 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 @@ -959,28 +955,22 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t * 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. + * To increase computational efficiency of these guidelines: + * 1. We keep track of which blocks need calculation (block->flag.recalculate) + * 2. We stop the reverse pass on the first block whose entry_speed == max_entry_speed. As soon + * as that happens, there can be no further increases (ensured by the previous recalculate) + * 3. On the forward pass we skip through to the first block with a modified exit speed + * (next->entry_speed) + * 4. On the forward pass if we encounter a full acceleration block that limits its exit speed + * (next->entry_speed) we also update the maximum for that junction (next->max_entry_speed) + * so it's never updated again + * 5. We use speed squared (ex: entry_speed_sqr in mm^2/s^2) in acceleration limit computations + * 6. We don't recompute sqrt(entry_speed_sqr) if the block's entry speed didn't change * * 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 @@ -1003,7 +993,8 @@ void Planner::calculate_trapezoid_for_block(block_t * const block, const_float_t */ // 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, const_float_t safe_exit_speed_sqr) { +// Returns true if it could increase the current block's entry speed. +bool Planner::reverse_pass_kernel(block_t * const current, const block_t * const next, const_float_t safe_exit_speed_sqr) { // We need to recalculate only for the last block added or if next->entry_speed_sqr changed. if (!next || next->flag.recalculate) { // And only if we're not already at max entry speed. @@ -1021,196 +1012,136 @@ void Planner::reverse_pass_kernel(block_t * const current, const block_t * const // become BUSY just before being marked RECALCULATE, so check for that! if (stepper.is_block_busy(current)) { // Block became busy. Clear the RECALCULATE flag (no point in - // recalculating BUSY blocks). And don't set its speed, as it can't - // be updated at this time. + // recalculating BUSY blocks). current->flag.recalculate = false; } else { // Block is not BUSY so this is ahead of the Stepper ISR: - // Just Set the new entry speed. + current->entry_speed_sqr = new_entry_speed_sqr; + return true; } } } } + return false; } /** * recalculate() needs to go over the current plan twice. - * Once in reverse and once forward. This implements the reverse pass. + * Once in reverse and once forward. This implements the reverse pass that + * coarsely maximizes the entry speeds starting from last block. + * Requires there's at least one block with flag.recalculate in the buffer. */ void Planner::reverse_pass(const_float_t safe_exit_speed_sqr) { // Initialize block index to the last block in the planner buffer. + // This last block will have flag.recalculate set. uint8_t block_index = prev_block_index(block_buffer_head); - // Read the index of the last buffer planned block. - // The ISR may change it so get a stable local copy. - uint8_t planned_block_index = block_buffer_planned; - - // If there was a race condition and block_buffer_planned was incremented - // or was pointing at the head (queue empty) break loop now and avoid - // planning already consumed blocks - if (planned_block_index == block_buffer_head) return; + // The ISR may change block_buffer_nonbusy so get a stable local copy. + uint8_t nonbusy_block_index = block_buffer_nonbusy; - // Reverse Pass: Coarsely maximize all possible deceleration curves back-planning from the last - // block in buffer. Cease planning when the last optimal planned or tail pointer is reached. - // NOTE: Forward pass will later refine and correct the reverse pass to create an optimal plan. const block_t *next = nullptr; - while (block_index != planned_block_index) { - - // Perform the reverse pass + // Don't try to change the entry speed of the first non-busy block. + while (block_index != nonbusy_block_index) { block_t *current = &block_buffer[block_index]; // Only process movement blocks if (current->is_move()) { - reverse_pass_kernel(current, next, safe_exit_speed_sqr); + // If no entry speed increase was possible we end the reverse pass. + if (!reverse_pass_kernel(current, next, safe_exit_speed_sqr)) return; next = current; } - // Advance to the next block_index = prev_block_index(block_index); - // The ISR could advance the block_buffer_planned while we were doing the reverse pass. + // The ISR could advance block_buffer_nonbusy while we were doing the reverse pass. // We must try to avoid using an already consumed block as the last one - So follow // changes to the pointer and make sure to limit the loop to the currently busy block - while (planned_block_index != block_buffer_planned) { + while (nonbusy_block_index != block_buffer_nonbusy) { // If we reached the busy block or an already processed block, break the loop now - if (block_index == planned_block_index) return; + if (block_index == nonbusy_block_index) return; // Advance the pointer, following the busy block - planned_block_index = next_block_index(planned_block_index); + nonbusy_block_index = next_block_index(nonbusy_block_index); } } } -// The kernel called by recalculate() when scanning the plan from first to last entry. -void Planner::forward_pass_kernel(const block_t * const previous, block_t * const current, const uint8_t block_index) { - // Check against previous speed only on current->entry_speed_sqr changes (or if first time). - if (current->flag.recalculate) { - // If the previous block is accelerating check if it's too short to complete the full speed - // change then adjust the entry speed accordingly. Entry speeds have already been maximized. - if (previous->entry_speed_sqr < current->entry_speed_sqr) { - float new_entry_speed_sqr = max_allowable_speed_sqr(-previous->acceleration, previous->entry_speed_sqr, previous->millimeters); - - // If true, previous block is full-acceleration and we can move the planned pointer forward. - if (new_entry_speed_sqr < current->entry_speed_sqr) { - // Current entry speed limited by full acceleration from previous entry speed. - // Make sure entry speed not lower than minimum_planner_speed_sqr. - NOLESS(new_entry_speed_sqr, current->min_entry_speed_sqr); - current->entry_speed_sqr = new_entry_speed_sqr; - - // Set optimal plan pointer. - block_buffer_planned = block_index; - } - else { - // Previous entry speed has been maximized. - block_buffer_planned = prev_block_index(block_index); - } +// The kernel called during the forward pass. Assumes current->flag.recalculate. +void Planner::forward_pass_kernel(const block_t * const previous, block_t * const current) { + // Check if the previous block is accelerating. + if (previous->entry_speed_sqr < current->entry_speed_sqr) { + // Compute the maximum achievable speed if the previous block was fully accelerating. + float new_exit_speed_sqr = max_allowable_speed_sqr(-previous->acceleration, previous->entry_speed_sqr, previous->millimeters); + + if (new_exit_speed_sqr < current->entry_speed_sqr) { + // Current entry speed limited by full acceleration from previous entry speed. + + // Make sure entry speed not lower than minimum_planner_speed_sqr. + NOLESS(new_exit_speed_sqr, current->min_entry_speed_sqr); + current->entry_speed_sqr = new_exit_speed_sqr; + // Ensure we don't try updating entry_speed_sqr again. + current->max_entry_speed_sqr = new_exit_speed_sqr; } - - // Any block set at its maximum entry speed also creates an optimal plan up to this - // point in the buffer. When the plan is bracketed by either the beginning of the - // buffer and a maximum entry speed or two maximum entry speeds, every block in between - // cannot logically be further improved. Hence, we don't have to recompute them anymore. - if (current->entry_speed_sqr == current->max_entry_speed_sqr) - block_buffer_planned = block_index; } -} -/** - * recalculate() needs to go over the current plan twice. - * Once in reverse and once forward. This implements the forward pass. - */ -void Planner::forward_pass() { - - // Forward Pass: Forward plan the acceleration curve from the planned pointer onward. - // Also scans for optimal plan breakpoints and appropriately updates the planned pointer. - - // Begin at buffer planned pointer. Note that block_buffer_planned can be modified - // by the stepper ISR, so read it ONCE. It it guaranteed that block_buffer_planned - // will never lead head, so the loop is safe to execute. Also note that the forward - // pass will never modify the values at the tail. - uint8_t block_index = block_buffer_planned; - - block_t *block; - const block_t * previous = nullptr; - while (block_index != block_buffer_head) { - - // Perform the forward pass - block = &block_buffer[block_index]; - - // Only process movement blocks - if (block->is_move()) { - // If there's no previous block or the previous block is not - // BUSY (thus, modifiable) run the forward_pass_kernel. Otherwise, - // the previous block became BUSY, so assume the current block's - // entry speed can't be altered (since that would also require - // updating the exit speed of the previous block). - if (previous && !stepper.is_block_busy(previous)) - forward_pass_kernel(previous, block, block_index); - previous = block; - } - // Advance to the previous - block_index = next_block_index(block_index); - } + // The fully optimized entry speed is our new minimum speed. + current->min_entry_speed_sqr = current->entry_speed_sqr; } /** - * Recalculate the trapezoid speed profiles for all blocks in the plan - * according to the entry_factor for each junction. Must be called by - * recalculate() after updating the blocks. + * Do the forward pass and recalculate the trapezoid speed profiles for all blocks in the plan + * according to entry/exit speeds. */ void Planner::recalculate_trapezoids(const_float_t safe_exit_speed_sqr) { - // The tail may be changed by the ISR so get a local copy. + // Start with the block that's about to execute or is executing. uint8_t block_index = block_buffer_tail, head_block_index = block_buffer_head; - // Since there could be a sync block in the head of the queue, and the - // next loop must not recalculate the head block (as it needs to be - // specially handled), scan backwards to the first non-SYNC block. - while (head_block_index != block_index) { - - // Go back (head always point to the first free block) - const uint8_t prev_index = prev_block_index(head_block_index); - - // Get the pointer to the block - block_t *prev = &block_buffer[prev_index]; - - // It the block is a move, we're done with this loop - if (prev->is_move()) break; - // Examine the previous block. This and all following are SYNC blocks - head_block_index = prev_index; - } - - // Go from the tail (currently executed block) to the first block, without including it) block_t *block = nullptr, *next = nullptr; - float current_entry_speed = 0.0f, next_entry_speed = 0.0f; + float next_entry_speed = 0.0f; while (block_index != head_block_index) { next = &block_buffer[block_index]; - // Only process movement blocks if (next->is_move()) { - next_entry_speed = SQRT(next->entry_speed_sqr); - - if (block) { - - // If the next block is marked to RECALCULATE, also mark the previously-fetched one - if (next->flag.recalculate) block->flag.recalculate = true; + // Check if the next block's entry speed changed + if (next->flag.recalculate) { + if (!block) { + // 'next' is the first move due to either being the first added move or due to the planner + // having completely fallen behind. Revert any reverse pass change. + next->entry_speed_sqr = next->min_entry_speed_sqr; + next_entry_speed = SQRT(next->min_entry_speed_sqr); + } + else { + // Try to fix exit speed which requires trapezoid recalculation + block->flag.recalculate = true; + + // But there is an inherent race condition here, as the block may have + // become BUSY just before being marked RECALCULATE, so check for that! + if (stepper.is_block_busy(block)) { + // Block is BUSY so we can't change the exit speed. Revert any reverse pass change. + next->entry_speed_sqr = next->min_entry_speed_sqr; + if (!next->initial_rate) { + // 'next' was never calculated. Planner is falling behind so for maximum efficiency + // set next's stepping speed directly and forgo checking against min_entry_speed_sqr. + // calculate_trapezoid_for_block() can handle it, albeit sub-optimally. + next->initial_rate = block->final_rate; + } + // Note that at this point next_entry_speed is (still) 0. + } + else { + // Block is not BUSY: we won the race against the ISR or recalculate was already set - // Recalculate if current block entry or exit junction speed has changed. - if (block->flag.recalculate) { + if (next->entry_speed_sqr != next->min_entry_speed_sqr) + forward_pass_kernel(block, next); - // But there is an inherent race condition here, as the block maybe - // became BUSY, just before it was marked as RECALCULATE, so check - // if that is the case! - if (!stepper.is_block_busy(block)) { - // Block is not BUSY, we won the race against the Stepper ISR: + const float current_entry_speed = next_entry_speed; + next_entry_speed = SQRT(next->entry_speed_sqr); - // 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); + calculate_trapezoid_for_block(block, current_entry_speed, next_entry_speed); } // Reset current only to ensure next trapezoid is computed - The @@ -1220,30 +1151,17 @@ void Planner::recalculate_trapezoids(const_float_t safe_exit_speed_sqr) { } block = next; - current_entry_speed = next_entry_speed; } block_index = next_block_index(block_index); } - // Last/newest block in buffer. Always recalculated. - if (block) { + // Last/newest block in buffer. The above guarantees it's a move block. + if (block && block->flag.recalculate) { + const float current_entry_speed = next_entry_speed; next_entry_speed = SQRT(safe_exit_speed_sqr); - // Mark the next(last) block as RECALCULATE, to prevent the Stepper ISR running it. - // As the last block is always recalculated here, there is a chance the block isn't - // marked as RECALCULATE yet. That's the reason for the following line. - block->flag.recalculate = true; - - // But there is an inherent race condition here, as the block maybe - // became BUSY, just before it was marked as RECALCULATE, so check - // if that is the case! - if (!stepper.is_block_busy(block)) { - // Block is not BUSY, we won the race against the Stepper ISR: - - const float nomr = 1.0f / block->nominal_speed; - calculate_trapezoid_for_block(block, current_entry_speed * nomr, next_entry_speed * nomr); - } + calculate_trapezoid_for_block(block, current_entry_speed, next_entry_speed); // Reset block to ensure its trapezoid is computed - The stepper is free to use // the block from now on. @@ -1251,14 +1169,10 @@ void Planner::recalculate_trapezoids(const_float_t safe_exit_speed_sqr) { } } +// Requires there's at least one block with flag.recalculate in the buffer void Planner::recalculate(const_float_t safe_exit_speed_sqr) { - // Initialize block index to the last block in the planner buffer. - const uint8_t block_index = prev_block_index(block_buffer_head); - // If there is just one block, no planning can be done. Avoid it! - if (block_index != block_buffer_planned) { - reverse_pass(safe_exit_speed_sqr); - forward_pass(); - } + reverse_pass(safe_exit_speed_sqr); + // The forward pass is done as part of recalculate_trapezoids() recalculate_trapezoids(safe_exit_speed_sqr); } @@ -1667,7 +1581,7 @@ void Planner::quick_stop() { const bool was_enabled = stepper.suspend(); // Drop all queue entries - block_buffer_nonbusy = block_buffer_planned = block_buffer_head = block_buffer_tail; + block_buffer_nonbusy = block_buffer_head = block_buffer_tail; // Restart the block delay for the first movement - As the queue was // forced to empty, there's no risk the ISR will touch this. @@ -2436,6 +2350,7 @@ 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; + block->steps_per_mm = steps_per_mm; uint32_t accel; #if ENABLED(LIN_ADVANCE) bool use_advance_lead = false; @@ -2829,6 +2744,8 @@ bool Planner::_populate_block( block->entry_speed_sqr = minimum_planner_speed_sqr; // Set min entry speed. Rarely it could be higher than the previous nominal speed but that's ok. block->min_entry_speed_sqr = minimum_planner_speed_sqr; + // Zero the initial_rate to indicate that calculate_trapezoid_for_block() hasn't been called yet. + block->initial_rate = 0; block->flag.recalculate = true; diff --git a/Marlin/src/module/planner.h b/Marlin/src/module/planner.h index fd26b4340e07..1053a27688d3 100644 --- a/Marlin/src/module/planner.h +++ b/Marlin/src/module/planner.h @@ -219,6 +219,7 @@ typedef struct PlannerBlock { min_entry_speed_sqr, // Minimum allowable junction entry speed in (mm/sec)^2 max_entry_speed_sqr, // Maximum allowable junction entry speed in (mm/sec)^2 millimeters, // The total travel of this block in mm + steps_per_mm, // steps/mm acceleration; // acceleration mm/sec^2 union { @@ -442,7 +443,6 @@ class Planner { static block_t block_buffer[BLOCK_BUFFER_SIZE]; static volatile uint8_t block_buffer_head, // Index of the next block to be pushed block_buffer_nonbusy, // Index of the first non busy block - block_buffer_planned, // Index of the optimally planned block block_buffer_tail; // Index of the busy block, if any static uint16_t cleaning_buffer_counter; // A counter to disable queuing of blocks static uint8_t delay_before_delivering; // This counter delays delivery of blocks when queue becomes empty to allow the opportunity of merging blocks @@ -804,7 +804,7 @@ class Planner { FORCE_INLINE static uint8_t nonbusy_movesplanned() { return block_dec_mod(block_buffer_head, block_buffer_nonbusy); } // Remove all blocks from the buffer - FORCE_INLINE static void clear_block_buffer() { block_buffer_nonbusy = block_buffer_planned = block_buffer_head = block_buffer_tail = 0; } + FORCE_INLINE static void clear_block_buffer() { block_buffer_nonbusy = block_buffer_head = block_buffer_tail = 0; } // Check if movement queue is full FORCE_INLINE static bool is_full() { return block_buffer_tail == next_block_index(block_buffer_head); } @@ -1081,13 +1081,12 @@ class Planner { } #endif - static void calculate_trapezoid_for_block(block_t * const block, const_float_t entry_factor, const_float_t exit_factor); + static void calculate_trapezoid_for_block(block_t * const block, const_float_t entry_speed, const_float_t exit_speed); - static void reverse_pass_kernel(block_t * const current, const block_t * const next, const_float_t safe_exit_speed_sqr); - static void forward_pass_kernel(const block_t * const previous, block_t * const current, uint8_t block_index); + static bool reverse_pass_kernel(block_t * const current, const block_t * const next, const_float_t safe_exit_speed_sqr); + static void forward_pass_kernel(const block_t * const previous, block_t * const current); static void reverse_pass(const_float_t safe_exit_speed_sqr); - static void forward_pass(); static void recalculate_trapezoids(const_float_t safe_exit_speed_sqr);