From 6fed66dd428f46d073a3c28f6ff39b13c86cc65e Mon Sep 17 00:00:00 2001
From: Mihai <299015+mh-dm@users.noreply.github.com>
Date: Mon, 1 Jul 2024 21:56:06 +0300
Subject: [PATCH] =?UTF-8?q?=F0=9F=A9=B9=E2=9A=A1=EF=B8=8F=20Fix=20judder,?=
 =?UTF-8?q?=20optimize=20planner=20(#27035)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 Marlin/src/module/planner.cpp | 323 +++++++++++++---------------------
 Marlin/src/module/planner.h   |  11 +-
 2 files changed, 125 insertions(+), 209 deletions(-)

diff --git a/Marlin/src/module/planner.cpp b/Marlin/src/module/planner.cpp
index 8f11f4ec18a..b4546e0235b 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;
+  // The ISR may change block_buffer_nonbusy so get a stable local copy.
+  uint8_t nonbusy_block_index = block_buffer_nonbusy;
 
-  // 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;
-
-  // 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);
+// 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 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;
+    if (new_exit_speed_sqr < current->entry_speed_sqr) {
+      // Current entry speed limited by full acceleration from previous entry speed.
 
-        // Set optimal plan pointer.
-        block_buffer_planned = block_index;
-      }
-      else {
-        // Previous entry speed has been maximized.
-        block_buffer_planned = prev_block_index(block_index);
-      }
+      // 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;
   }
+
+  // The fully optimized entry speed is our new minimum speed.
+  current->min_entry_speed_sqr = current->entry_speed_sqr;
 }
 
 /**
- * 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);
-  }
-}
-
-/**
- * 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);
+      // 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;
 
-      if (block) {
+          // 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
 
-        // If the next block is marked to RECALCULATE, also mark the previously-fetched one
-        if (next->flag.recalculate) block->flag.recalculate = true;
+            if (next->entry_speed_sqr != next->min_entry_speed_sqr)
+              forward_pass_kernel(block, next);
 
-        // Recalculate if current block entry or exit junction speed has changed.
-        if (block->flag.recalculate) {
+            const float current_entry_speed = next_entry_speed;
+            next_entry_speed = SQRT(next->entry_speed_sqr);
 
-          // 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:
-
-            // 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 fd26b4340e0..1053a27688d 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);