diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index 8123582b80f9d6b62c5cc1d01dfa859939645575..0acb279fb62aae63d9973ba11067e9b13db3a575 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -1663,6 +1663,16 @@
 // Support for G5 with XYZE destination and IJPQ offsets. Requires ~2666 bytes.
 //#define BEZIER_CURVE_SUPPORT
 
+/**
+ * Direct Stepping
+ *
+ * Comparable to the method used by Klipper, G6 direct stepping significantly
+ * reduces motion calculations, increases top printing speeds, and results in
+ * less step aliasing by calculating all motions in advance.
+ * Preparing your G-code: https://github.com/colinrgodsey/step-daemon
+ */
+//#define DIRECT_STEPPING
+
 /**
  * G38 Probe Target
  *
@@ -1731,14 +1741,16 @@
 //================================= Buffers =================================
 //===========================================================================
 
-// @section hidden
+// @section motion
 
-// The number of linear motions that can be in the plan at any give time.
-// THE BLOCK_BUFFER_SIZE NEEDS TO BE A POWER OF 2 (e.g. 8, 16, 32) because shifts and ors are used to do the ring-buffering.
-#if ENABLED(SDSUPPORT)
-  #define BLOCK_BUFFER_SIZE 16 // SD,LCD,Buttons take more memory, block buffer needs to be smaller
+// The number of lineear moves that can be in the planner at once.
+// The value of BLOCK_BUFFER_SIZE must be a power of 2 (e.g. 8, 16, 32)
+#if BOTH(SDSUPPORT, DIRECT_STEPPING)
+  #define BLOCK_BUFFER_SIZE  8
+#elif ENABLED(SDSUPPORT)
+  #define BLOCK_BUFFER_SIZE 16
 #else
-  #define BLOCK_BUFFER_SIZE 16 // maximize block buffer
+  #define BLOCK_BUFFER_SIZE 16
 #endif
 
 // @section serial
diff --git a/Marlin/src/HAL/AVR/MarlinSerial.cpp b/Marlin/src/HAL/AVR/MarlinSerial.cpp
index 350d0f302d19244121f3e11c54e37d8e1ee6f194..c544b905f36ce90b164c7cb531f37b2504652083 100644
--- a/Marlin/src/HAL/AVR/MarlinSerial.cpp
+++ b/Marlin/src/HAL/AVR/MarlinSerial.cpp
@@ -43,6 +43,10 @@
   #include "MarlinSerial.h"
   #include "../../MarlinCore.h"
 
+  #if ENABLED(DIRECT_STEPPING)
+    #include "../../feature/direct_stepping.h"
+  #endif
+
   template<typename Cfg> typename MarlinSerial<Cfg>::ring_buffer_r MarlinSerial<Cfg>::rx_buffer = { 0, 0, { 0 } };
   template<typename Cfg> typename MarlinSerial<Cfg>::ring_buffer_t MarlinSerial<Cfg>::tx_buffer = { 0 };
   template<typename Cfg> bool     MarlinSerial<Cfg>::_written = false;
@@ -131,6 +135,18 @@
 
     static EmergencyParser::State emergency_state; // = EP_RESET
 
+    // This must read the R_UCSRA register before reading the received byte to detect error causes
+    if (Cfg::DROPPED_RX && B_DOR && !++rx_dropped_bytes) --rx_dropped_bytes;
+    if (Cfg::RX_OVERRUNS && B_DOR && !++rx_buffer_overruns) --rx_buffer_overruns;
+    if (Cfg::RX_FRAMING_ERRORS && B_FE && !++rx_framing_errors) --rx_framing_errors;
+
+    // Read the character from the USART
+    uint8_t c = R_UDR;
+
+    #if ENABLED(DIRECT_STEPPING)
+      if (page_manager.maybe_store_rxd_char(c)) return;
+    #endif
+
     // Get the tail - Nothing can alter its value while this ISR is executing, but there's
     // a chance that this ISR interrupted the main process while it was updating the index.
     // The backup mechanism ensures the correct value is always returned.
@@ -142,14 +158,6 @@
     // Get the next element
     ring_buffer_pos_t i = (ring_buffer_pos_t)(h + 1) & (ring_buffer_pos_t)(Cfg::RX_SIZE - 1);
 
-    // This must read the R_UCSRA register before reading the received byte to detect error causes
-    if (Cfg::DROPPED_RX && B_DOR && !++rx_dropped_bytes) --rx_dropped_bytes;
-    if (Cfg::RX_OVERRUNS && B_DOR && !++rx_buffer_overruns) --rx_buffer_overruns;
-    if (Cfg::RX_FRAMING_ERRORS && B_FE && !++rx_framing_errors) --rx_framing_errors;
-
-    // Read the character from the USART
-    uint8_t c = R_UDR;
-
     if (Cfg::EMERGENCYPARSER) emergency_parser.update(emergency_state, c);
 
     // If the character is to be stored at the index just before the tail
diff --git a/Marlin/src/MarlinCore.cpp b/Marlin/src/MarlinCore.cpp
index 73f936bc05ecc7f21c5bc2adb37b030d59f9f293..2434df0ad493a4cb2176a1a361f87c0b079d63f6 100644
--- a/Marlin/src/MarlinCore.cpp
+++ b/Marlin/src/MarlinCore.cpp
@@ -59,6 +59,10 @@
 #include "gcode/parser.h"
 #include "gcode/queue.h"
 
+#if ENABLED(DIRECT_STEPPING)
+  #include "feature/direct_stepping.h"
+#endif
+
 #if ENABLED(TOUCH_BUTTONS)
   #include "feature/touch/xpt2046.h"
 #endif
@@ -713,6 +717,9 @@ void idle(TERN_(ADVANCED_PAUSE_FEATURE, bool no_stepper_sleep/*=false*/)) {
 
   // Handle Joystick jogging
   TERN_(POLL_JOG, joystick.inject_jog_moves());
+
+  // Direct Stepping
+  TERN_(DIRECT_STEPPING, page_manager.write_responses());
 }
 
 /**
@@ -1124,6 +1131,10 @@ void setup() {
     SETUP_RUN(max7219.init());
   #endif
 
+  #if ENABLED(DIRECT_STEPPING)
+    SETUP_RUN(page_manager.init());
+  #endif
+
   marlin_state = MF_RUNNING;
 
   SETUP_LOG("setup() completed.");
diff --git a/Marlin/src/feature/direct_stepping.cpp b/Marlin/src/feature/direct_stepping.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7bed075b876274cb4e2b47df59b15cdc832943e3
--- /dev/null
+++ b/Marlin/src/feature/direct_stepping.cpp
@@ -0,0 +1,273 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include "../inc/MarlinConfigPre.h"
+
+#if ENABLED(DIRECT_STEPPING)
+
+#include "direct_stepping.h"
+
+#include "../MarlinCore.h"
+
+#define CHECK_PAGE(I, R) do{                                \
+  if (I >= sizeof(page_states) / sizeof(page_states[0])) {  \
+    fatal_error = true;                                     \
+    return R;                                               \
+  }                                                         \
+}while(0)
+
+#define CHECK_PAGE_STATE(I, R, S) do { \
+  CHECK_PAGE(I, R);                    \
+  if (page_states[I] != S) {           \
+    fatal_error = true;                \
+    return R;                          \
+  }                                    \
+}while(0)
+
+namespace DirectStepping {
+
+  template<typename Cfg>
+  State SerialPageManager<Cfg>::state;
+
+  template<typename Cfg>
+  volatile bool SerialPageManager<Cfg>::fatal_error;
+
+  template<typename Cfg>
+  volatile PageState SerialPageManager<Cfg>::page_states[Cfg::NUM_PAGES];
+
+  template<typename Cfg>
+  volatile bool SerialPageManager<Cfg>::page_states_dirty;
+
+  template<typename Cfg>
+  millis_t SerialPageManager<Cfg>::next_response;
+
+  template<typename Cfg>
+  uint8_t SerialPageManager<Cfg>::pages[Cfg::NUM_PAGES][Cfg::PAGE_SIZE];
+          
+  template<typename Cfg>
+  uint8_t SerialPageManager<Cfg>::checksum;
+
+  template<typename Cfg>
+  typename Cfg::write_byte_idx_t SerialPageManager<Cfg>::write_byte_idx;
+
+  template<typename Cfg>
+  typename Cfg::page_idx_t SerialPageManager<Cfg>::write_page_idx;
+
+  template<typename Cfg>
+  typename Cfg::write_byte_idx_t SerialPageManager<Cfg>::write_page_size;
+
+  template <typename Cfg>
+  void SerialPageManager<Cfg>::init() {
+    for (int i = 0 ; i < Cfg::NUM_PAGES ; i++)
+      page_states[i] = PageState::FREE;
+
+    fatal_error = false;
+    next_response = 0;
+    state = State::NEWLINE;
+
+    page_states_dirty = false;
+
+    SERIAL_ECHOLNPGM("pages_ready");
+  }
+
+  template<typename Cfg>
+  FORCE_INLINE bool SerialPageManager<Cfg>::maybe_store_rxd_char(uint8_t c) {
+    switch (state) {
+      default:
+      case State::MONITOR:
+        switch (c) {
+          case '\n':
+          case '\r':
+            state = State::NEWLINE;
+          default:
+            return false;
+        }
+      case State::NEWLINE:
+        switch (c) {
+          case Cfg::CONTROL_CHAR:
+            state = State::ADDRESS;
+            return true;
+          case '\n':
+          case '\r':
+            state = State::NEWLINE;
+            return false;
+          default:
+            state = State::MONITOR;
+            return false;
+        }
+      case State::ADDRESS:
+        //TODO: 16 bit address, State::ADDRESS2
+        write_page_idx = c;
+        write_byte_idx = 0;
+        checksum = 0;
+
+        CHECK_PAGE(write_page_idx, true);
+
+        if (page_states[write_page_idx] == PageState::FAIL) {
+          // Special case for fail
+          state = State::UNFAIL;
+          return true;
+        }
+
+        set_page_state(write_page_idx, PageState::WRITING);
+
+        state = Cfg::DIRECTIONAL ? State::COLLECT : State::SIZE;
+
+        return true;
+      case State::SIZE:
+        // Zero means full page size
+        write_page_size = c;
+        state = State::COLLECT;
+        return true;
+      case State::COLLECT:
+        pages[write_page_idx][write_byte_idx++] = c;
+        checksum ^= c;
+
+        // check if still collecting
+        if (Cfg::PAGE_SIZE == 256) {
+          // special case for 8-bit, check if rolled back to 0
+          if (Cfg::DIRECTIONAL || !write_page_size) { // full 256 bytes
+            if (write_byte_idx) return true;
+          } else {
+            if (write_byte_idx < write_page_size) return true;
+          }
+        } else if (Cfg::DIRECTIONAL) {
+          if (write_byte_idx != Cfg::PAGE_SIZE) return true;
+        } else {
+          if (write_byte_idx < write_page_size) return true;
+        }
+
+        state = State::CHECKSUM;
+        return true;
+      case State::CHECKSUM: {
+        const PageState page_state = (checksum == c) ? PageState::OK : PageState::FAIL;
+        set_page_state(write_page_idx, page_state);
+        state = State::MONITOR;
+        return true;
+      }
+      case State::UNFAIL:
+        if (c == 0) {
+          set_page_state(write_page_idx, PageState::FREE);
+        } else {
+          fatal_error = true;
+        }
+        state = State::MONITOR;
+        return true;
+    }
+  }
+
+  template <typename Cfg>
+  void SerialPageManager<Cfg>::write_responses() {
+    if (fatal_error) {
+      kill(GET_TEXT(MSG_BAD_PAGE));
+      return;
+    }
+
+    // Runs on a set interval also, as responses may get lost.
+    if (next_response && next_response < millis()) {
+      page_states_dirty = true;
+    }
+
+    if (!page_states_dirty) return;
+
+    page_states_dirty = false;
+    next_response = millis() + Cfg::RESPONSE_INTERVAL_MS;
+
+    SERIAL_ECHO(Cfg::CONTROL_CHAR);
+    constexpr int state_bits = 2;
+    constexpr int n_bytes = Cfg::NUM_PAGES >> state_bits;
+    volatile uint8_t bits_b[n_bytes] = { 0 };
+
+    for (page_idx_t i = 0 ; i < Cfg::NUM_PAGES ; i++) {
+      bits_b[i >> state_bits] |= page_states[i] << ((i * state_bits) & 0x7);
+    }
+
+    uint8_t crc = 0;
+    for (uint8_t i = 0 ; i < n_bytes ; i++) {
+      crc ^= bits_b[i];
+      SERIAL_ECHO(bits_b[i]);
+    }
+
+    SERIAL_ECHO(crc);
+    SERIAL_EOL();
+  }
+
+  template <typename Cfg>
+  FORCE_INLINE void SerialPageManager<Cfg>::set_page_state(const page_idx_t page_idx, const PageState page_state) {
+    CHECK_PAGE(page_idx,);
+
+    page_states[page_idx] = page_state;
+    page_states_dirty = true;
+  }
+
+  template <>
+  FORCE_INLINE uint8_t *PageManager::get_page(const page_idx_t page_idx) {
+    CHECK_PAGE(page_idx, nullptr);
+
+    return pages[page_idx];
+  }
+
+  template <>
+  FORCE_INLINE void PageManager::free_page(const page_idx_t page_idx) {
+    set_page_state(page_idx, PageState::FREE);
+  }
+
+};
+
+DirectStepping::PageManager page_manager;
+
+const uint8_t segment_table[DirectStepping::Config::NUM_SEGMENTS][DirectStepping::Config::SEGMENT_STEPS] PROGMEM = {
+
+  #if STEPPER_PAGE_FORMAT == SP_4x4D_128
+
+    { 1, 1, 1, 1, 1, 1, 1, 0 }, //  0 = -7
+    { 1, 1, 1, 0, 1, 1, 1, 0 }, //  1 = -6
+    { 0, 1, 1, 0, 1, 0, 1, 1 }, //  2 = -5
+    { 0, 1, 0, 1, 0, 1, 0, 1 }, //  3 = -4
+    { 0, 1, 0, 0, 1, 0, 0, 1 }, //  4 = -3
+    { 0, 0, 1, 0, 0, 0, 1, 0 }, //  5 = -2
+    { 0, 0, 0, 0, 1, 0, 0, 0 }, //  6 = -1
+    { 0, 0, 0, 0, 0, 0, 0, 0 }, //  7 =  0
+    { 0, 0, 0, 0, 1, 0, 0, 0 }, //  8 =  1
+    { 0, 0, 1, 0, 0, 0, 1, 0 }, //  9 =  2
+    { 0, 1, 0, 0, 1, 0, 0, 1 }, // 10 =  3
+    { 0, 1, 0, 1, 0, 1, 0, 1 }, // 11 =  4
+    { 0, 1, 1, 0, 1, 0, 1, 1 }, // 12 =  5
+    { 1, 1, 1, 0, 1, 1, 1, 0 }, // 13 =  6
+    { 1, 1, 1, 1, 1, 1, 1, 0 }, // 14 =  7
+    { 0 }
+
+  #elif STEPPER_PAGE_FORMAT == SP_4x2_256
+
+    { 0, 0, 0, 0 }, // 0
+    { 0, 1, 0, 0 }, // 1
+    { 1, 0, 1, 0 }, // 2
+    { 1, 1, 1, 0 }, // 3
+
+  #elif STEPPER_PAGE_FORMAT == SP_4x1_512
+
+    {0} // Uncompressed format, table not used
+
+  #endif
+
+};
+
+#endif // DIRECT_STEPPING
diff --git a/Marlin/src/feature/direct_stepping.h b/Marlin/src/feature/direct_stepping.h
new file mode 100644
index 0000000000000000000000000000000000000000..4d12f83db766e3928c1894fd101c8fc2cfa6fc39
--- /dev/null
+++ b/Marlin/src/feature/direct_stepping.h
@@ -0,0 +1,137 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include "../inc/MarlinConfig.h"
+
+namespace DirectStepping {
+
+  enum State : char {
+    MONITOR, NEWLINE, ADDRESS, SIZE, COLLECT, CHECKSUM, UNFAIL
+  };
+
+  enum PageState : uint8_t {
+    FREE, WRITING, OK, FAIL
+  };
+
+  // Static state used for stepping through direct stepping pages
+  struct page_step_state_t {
+    // Current page
+    uint8_t *page;
+    // Current segment
+    uint16_t segment_idx;
+    // Current steps within segment
+    uint8_t segment_steps;
+    // Segment delta
+    xyze_uint8_t sd;
+    // Block delta
+    xyze_int_t bd;
+  };
+
+  template<typename Cfg>
+  class SerialPageManager {
+  public:
+
+    typedef typename Cfg::page_idx_t page_idx_t;
+
+    static bool maybe_store_rxd_char(uint8_t c);
+    static void write_responses();
+
+    // common methods for page managers
+    static void init();
+    static uint8_t *get_page(const page_idx_t page_idx);
+    static void free_page(const page_idx_t page_idx);
+
+  protected:
+
+    typedef typename Cfg::write_byte_idx_t write_byte_idx_t;
+
+    static State state;
+    static volatile bool fatal_error;
+
+    static volatile PageState page_states[Cfg::NUM_PAGES];
+    static volatile bool page_states_dirty;
+    static millis_t next_response;
+
+    static uint8_t pages[Cfg::NUM_PAGES][Cfg::PAGE_SIZE];
+    static uint8_t checksum;
+    static write_byte_idx_t write_byte_idx;
+    static page_idx_t write_page_idx;
+    static write_byte_idx_t write_page_size;
+
+    static void set_page_state(const page_idx_t page_idx, const PageState page_state);
+  };
+
+  template<bool b, typename T, typename F> struct TypeSelector { typedef T type;} ;
+  template<typename T, typename F> struct TypeSelector<false, T, F> { typedef F type; };
+
+  template <int num_pages, int num_axes, int bits_segment, bool dir, int segments>
+  struct config_t {
+    static constexpr char CONTROL_CHAR  = '!';
+
+    static constexpr int NUM_PAGES      = num_pages;
+    static constexpr int NUM_AXES       = num_axes;
+    static constexpr int BITS_SEGMENT   = bits_segment;
+    static constexpr int DIRECTIONAL    = dir ? 1 : 0;
+    static constexpr int SEGMENTS       = segments;
+
+    static constexpr int RAW            = (BITS_SEGMENT == 1) ? 1 : 0;
+    static constexpr int NUM_SEGMENTS   = 1 << BITS_SEGMENT;
+    static constexpr int SEGMENT_STEPS  = 1 << (BITS_SEGMENT - DIRECTIONAL - RAW);
+    static constexpr int TOTAL_STEPS    = SEGMENT_STEPS * SEGMENTS;
+    static constexpr int PAGE_SIZE      = (NUM_AXES * BITS_SEGMENT * SEGMENTS) / 8;
+
+    static constexpr millis_t RESPONSE_INTERVAL_MS = 50;
+
+    typedef typename TypeSelector<(PAGE_SIZE>256), uint16_t, uint8_t>::type write_byte_idx_t;
+    typedef typename TypeSelector<(NUM_PAGES>256), uint16_t, uint8_t>::type page_idx_t;
+  };
+
+  template <uint8_t num_pages>
+  using SP_4x4D_128 = config_t<num_pages, 4, 4, true,  128>;
+
+  template <uint8_t num_pages>
+  using SP_4x2_256  = config_t<num_pages, 4, 2, false, 256>;
+
+  template <uint8_t num_pages>
+  using SP_4x1_512  = config_t<num_pages, 4, 1, false, 512>;
+
+  // configured types
+  typedef STEPPER_PAGE_FORMAT<STEPPER_PAGES> Config;
+
+  template class PAGE_MANAGER<Config>;
+  typedef PAGE_MANAGER<Config> PageManager;
+};
+
+#define SP_4x4D_128 1
+//#define SP_4x4_128 2
+//#define SP_4x2D_256 3
+#define SP_4x2_256 4
+#define SP_4x1_512 5
+
+typedef typename DirectStepping::Config::page_idx_t page_idx_t;
+
+// TODO: use config
+typedef DirectStepping::page_step_state_t page_step_state_t;
+
+extern const uint8_t segment_table[DirectStepping::Config::NUM_SEGMENTS][DirectStepping::Config::SEGMENT_STEPS];
+extern DirectStepping::PageManager page_manager;
diff --git a/Marlin/src/gcode/gcode.cpp b/Marlin/src/gcode/gcode.cpp
index ac5a60ed9364b5a17753630c729c2dff1dd772ba..14f2eea7d9967daa155a47005acaf9d2fdeef9a0 100644
--- a/Marlin/src/gcode/gcode.cpp
+++ b/Marlin/src/gcode/gcode.cpp
@@ -261,6 +261,10 @@ void GcodeSuite::process_parsed_command(const bool no_ok/*=false*/) {
         case 5: G5(); break;                                      // G5: Cubic B_spline
       #endif
 
+      #if ENABLED(DIRECT_STEPPING)
+        case 6: G6(); break;                                      // G6: Direct Stepper Move
+      #endif
+
       #if ENABLED(FWRETRACT)
         case 10: G10(); break;                                    // G10: Retract / Swap Retract
         case 11: G11(); break;                                    // G11: Recover / Swap Recover
diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h
index 92292f609ba38e4e757d086250d5c1ace2a8d6d5..573ef0f6250f8e823d64237068de1709034444e4 100644
--- a/Marlin/src/gcode/gcode.h
+++ b/Marlin/src/gcode/gcode.h
@@ -402,6 +402,8 @@ private:
 
   TERN_(BEZIER_CURVE_SUPPORT, static void G5());
 
+  TERN_(DIRECT_STEPPING, static void G6());
+
   #if ENABLED(FWRETRACT)
     static void G10();
     static void G11();
diff --git a/Marlin/src/gcode/geometry/G92.cpp b/Marlin/src/gcode/geometry/G92.cpp
index 91a746dd76103a302408aaedfc8fe36d889f5be1..16717de14de62a1324a56a6a0e50009cb3d8c24c 100644
--- a/Marlin/src/gcode/geometry/G92.cpp
+++ b/Marlin/src/gcode/geometry/G92.cpp
@@ -99,5 +99,7 @@ void GcodeSuite::G92() {
   if    (sync_XYZ) sync_plan_position();
   else if (sync_E) sync_plan_position_e();
 
-  report_current_position();
+  #if DISABLED(DIRECT_STEPPING)
+    report_current_position();
+  #endif
 }
diff --git a/Marlin/src/gcode/motion/G6.cpp b/Marlin/src/gcode/motion/G6.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..4405ff6b9cc1bfc24a6779e3996eb3e542823cb8
--- /dev/null
+++ b/Marlin/src/gcode/motion/G6.cpp
@@ -0,0 +1,61 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ *
+ * Based on Sprinter and grbl.
+ * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#include "../../inc/MarlinConfig.h"
+
+#if ENABLED(DIRECT_STEPPING)
+
+#include "../../feature/direct_stepping.h"
+
+#include "../gcode.h"
+#include "../../module/planner.h"
+
+/**
+ * G6: Direct Stepper Move
+ */
+void GcodeSuite::G6() {
+  // TODO: feedrate support?
+  if (parser.seen('R'))
+    planner.last_page_step_rate = parser.value_ulong();
+
+  if (!DirectStepping::Config::DIRECTIONAL) {
+    if (parser.seen('X')) planner.last_page_dir.x = !!parser.value_byte();
+    if (parser.seen('Y')) planner.last_page_dir.y = !!parser.value_byte();
+    if (parser.seen('Z')) planner.last_page_dir.z = !!parser.value_byte();
+    if (parser.seen('E')) planner.last_page_dir.e = !!parser.value_byte();
+  }
+
+  // No index means we just set the state
+  if (!parser.seen('I')) return;
+
+  // No speed is set, can't schedule the move
+  if (!planner.last_page_step_rate) return;
+
+  const page_idx_t page_idx = (page_idx_t) parser.value_ulong();
+
+  uint16_t num_steps = DirectStepping::Config::TOTAL_STEPS;
+  if (parser.seen('S')) num_steps = parser.value_ushort();
+
+  planner.buffer_page(page_idx, 0, num_steps);
+  reset_stepper_timeout();
+}
+
+#endif // DIRECT_STEPPING
diff --git a/Marlin/src/inc/Conditionals_adv.h b/Marlin/src/inc/Conditionals_adv.h
index 2009e7c9659bec78a704c47340bc5e173bf10e12..70a13330384dcc11df19cca11824351156998288 100644
--- a/Marlin/src/inc/Conditionals_adv.h
+++ b/Marlin/src/inc/Conditionals_adv.h
@@ -323,6 +323,18 @@
   #endif
 #endif
 
+#if ENABLED(DIRECT_STEPPING)
+  #ifndef STEPPER_PAGES
+    #define STEPPER_PAGES 16
+  #endif
+  #ifndef STEPPER_PAGE_FORMAT
+    #define STEPPER_PAGE_FORMAT SP_4x2_256
+  #endif
+  #ifndef PAGE_MANAGER
+    #define PAGE_MANAGER SerialPageManager
+  #endif
+#endif
+
 //
 // SD Card connection methods
 // Defined here so pins and sanity checks can use them
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index 11cc7060d4ff604723f6e5f25e6e217d532b16ce..c8f8907e88b2416b86f17729bc3ef80ecede5105 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -2923,3 +2923,12 @@ static_assert(   _ARR_TEST(3,0) && _ARR_TEST(3,1) && _ARR_TEST(3,2)
 #if SAVED_POSITIONS > 256
   #error "SAVED_POSITIONS must be an integer from 0 to 256."
 #endif
+
+/**
+ * Sanity checks for stepper chunk support
+ */
+#if ENABLED(DIRECT_STEPPING)
+  #if ENABLED(LIN_ADVANCE)
+    #error "DIRECT_STEPPING is incompatible with LIN_ADVANCE. Enable in external planner if possible."
+  #endif
+#endif
diff --git a/Marlin/src/lcd/language/language_en.h b/Marlin/src/lcd/language/language_en.h
index df27de57a569b06be99f889615c51af8932cb639..3ee5bea6e4105a074ad2315c4e0473b8a7392b20 100644
--- a/Marlin/src/lcd/language/language_en.h
+++ b/Marlin/src/lcd/language/language_en.h
@@ -577,6 +577,9 @@ namespace Language_en {
   PROGMEM Language_Str MSG_SNAKE                           = _UxGT("Sn4k3");
   PROGMEM Language_Str MSG_MAZE                            = _UxGT("Maze");
 
+  PROGMEM Language_Str MSG_BAD_PAGE                        = _UxGT("Bad page index");
+  PROGMEM Language_Str MSG_BAD_PAGE_SPEED                  = _UxGT("Bad page speed");
+
   //
   // Filament Change screens show up to 3 lines on a 4-line display
   //                        ...or up to 2 lines on a 3-line display
diff --git a/Marlin/src/module/planner.cpp b/Marlin/src/module/planner.cpp
index 7d23789df1f742e8f0100b24139353ee701cbc06..dea51ac67faaf5e64bcbc3e025c3a391968164a0 100644
--- a/Marlin/src/module/planner.cpp
+++ b/Marlin/src/module/planner.cpp
@@ -151,6 +151,11 @@ float Planner::steps_to_mm[XYZE_N];             // (mm) Millimeters per step
   uint8_t Planner::last_extruder = 0;     // Respond to extruder change
 #endif
 
+#if ENABLED(DIRECT_STEPPING)
+  uint32_t Planner::last_page_step_rate = 0;
+  xyze_bool_t Planner::last_page_dir{0};
+#endif
+
 #if EXTRUDERS
   int16_t Planner::flow_percentage[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(100); // Extrusion factor for each extruder
   float Planner::e_factor[EXTRUDERS] = ARRAY_BY_EXTRUDERS1(1.0f); // The flow percentage and volumetric multiplier combine to scale E movement
@@ -235,6 +240,10 @@ void Planner::init() {
   TERN_(ABL_PLANAR, bed_level_matrix.set_to_identity());
   clear_block_buffer();
   delay_before_delivering = 0;
+  #if ENABLED(DIRECT_STEPPING)
+    last_page_step_rate = 0;
+    last_page_dir.reset();
+  #endif
 }
 
 #if ENABLED(S_CURVE_ACCELERATION)
@@ -906,7 +915,7 @@ void Planner::calculate_trapezoid_for_block(block_t* const block, const float &e
       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::discard_current_block() routine during a cycle.
+      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 lots of short
   line segments, like G2/3 arcs or complex curves, may seem to move slow. This is because there simply isn't
@@ -994,8 +1003,8 @@ void Planner::reverse_pass() {
     // Perform the reverse pass
     block_t *current = &block_buffer[block_index];
 
-    // Only consider non sync blocks
-    if (!TEST(current->flag, BLOCK_BIT_SYNC_POSITION)) {
+    // Only consider non sync and page blocks
+    if (!TEST(current->flag, BLOCK_BIT_SYNC_POSITION) && !IS_PAGE(current)) {
       reverse_pass_kernel(current, next);
       next = current;
     }
@@ -1089,8 +1098,8 @@ void Planner::forward_pass() {
     // Perform the forward pass
     block = &block_buffer[block_index];
 
-    // Skip SYNC blocks
-    if (!TEST(block->flag, BLOCK_BIT_SYNC_POSITION)) {
+    // Skip SYNC and page blocks
+    if (!TEST(block->flag, BLOCK_BIT_SYNC_POSITION) && !IS_PAGE(block)) {
       // 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
@@ -1139,8 +1148,8 @@ void Planner::recalculate_trapezoids() {
 
     next = &block_buffer[block_index];
 
-    // Skip sync blocks
-    if (!TEST(next->flag, BLOCK_BIT_SYNC_POSITION)) {
+    // Skip sync and page blocks
+    if (!TEST(next->flag, BLOCK_BIT_SYNC_POSITION) && !IS_PAGE(next)) {
       next_entry_speed = SQRT(next->entry_speed_sqr);
 
       if (block) {
@@ -2717,6 +2726,69 @@ bool Planner::buffer_line(const float &rx, const float &ry, const float &rz, con
   #endif
 } // buffer_line()
 
+#if ENABLED(DIRECT_STEPPING)
+
+  void Planner::buffer_page(const page_idx_t page_idx, const uint8_t extruder, const uint16_t num_steps) {
+    if (!last_page_step_rate) {
+      kill(GET_TEXT(MSG_BAD_PAGE_SPEED));
+      return;
+    }
+
+    uint8_t next_buffer_head;
+    block_t * const block = get_next_free_block(next_buffer_head);
+
+    block->flag = BLOCK_FLAG_IS_PAGE;
+
+    #if FAN_COUNT > 0
+      FANS_LOOP(i) block->fan_speed[i] = thermalManager.fan_speed[i];
+    #endif
+
+    #if EXTRUDERS > 1
+      block->extruder = extruder;
+    #endif
+
+    block->page_idx = page_idx;
+
+    block->step_event_count = num_steps;
+    block->initial_rate =
+      block->final_rate =
+      block->nominal_rate = last_page_step_rate; // steps/s
+
+    block->accelerate_until = 0;
+    block->decelerate_after = block->step_event_count;
+
+    // Will be set to last direction later if directional format.
+    block->direction_bits = 0;
+
+    #define PAGE_UPDATE_DIR(AXIS) \
+      if (!last_page_dir[_AXIS(AXIS)]) SBI(block->direction_bits, _AXIS(AXIS));
+
+    if (!DirectStepping::Config::DIRECTIONAL) {
+      PAGE_UPDATE_DIR(X);
+      PAGE_UPDATE_DIR(Y);
+      PAGE_UPDATE_DIR(Z);
+      PAGE_UPDATE_DIR(E);
+    }
+
+    // If this is the first added movement, reload the delay, otherwise, cancel it.
+    if (block_buffer_head == block_buffer_tail) {
+      // If it was the first queued block, restart the 1st block delivery delay, to
+      // give the planner an opportunity to queue more movements and plan them
+      // As there are no queued movements, the Stepper ISR will not touch this
+      // variable, so there is no risk setting this here (but it MUST be done
+      // before the following line!!)
+      delay_before_delivering = BLOCK_DELAY_FOR_1ST_MOVE;
+    }
+
+    // Move buffer head
+    block_buffer_head = next_buffer_head;
+
+    enable_all_steppers();
+    stepper.wake_up();
+  }
+
+#endif // DIRECT_STEPPING
+
 /**
  * Directly set the planner ABC position (and stepper positions)
  * converting mm (or angles for SCARA) into steps.
diff --git a/Marlin/src/module/planner.h b/Marlin/src/module/planner.h
index 60dbb612c7ecfc53fb339b14e813909cca65faf4..0314758a1d2d75472754b2afdd75f4ad51798741 100644
--- a/Marlin/src/module/planner.h
+++ b/Marlin/src/module/planner.h
@@ -66,6 +66,13 @@
   #include "../feature/spindle_laser_types.h"
 #endif
 
+#if ENABLED(DIRECT_STEPPING)
+  #include "../feature/direct_stepping.h"
+  #define IS_PAGE(B) TEST(B->flag, BLOCK_BIT_IS_PAGE)
+#else
+  #define IS_PAGE(B) false
+#endif
+
 // Feedrate for manual moves
 #ifdef MANUAL_FEEDRATE
   constexpr xyze_feedrate_t _mf = MANUAL_FEEDRATE,
@@ -90,13 +97,21 @@ enum BlockFlagBit : char {
 
   // Sync the stepper counts from the block
   BLOCK_BIT_SYNC_POSITION
+
+  // Direct stepping page
+  #if ENABLED(DIRECT_STEPPING)
+    , BLOCK_BIT_IS_PAGE
+  #endif
 };
 
 enum BlockFlag : char {
-  BLOCK_FLAG_RECALCULATE          = _BV(BLOCK_BIT_RECALCULATE),
-  BLOCK_FLAG_NOMINAL_LENGTH       = _BV(BLOCK_BIT_NOMINAL_LENGTH),
-  BLOCK_FLAG_CONTINUED            = _BV(BLOCK_BIT_CONTINUED),
-  BLOCK_FLAG_SYNC_POSITION        = _BV(BLOCK_BIT_SYNC_POSITION)
+    BLOCK_FLAG_RECALCULATE          = _BV(BLOCK_BIT_RECALCULATE)
+  , BLOCK_FLAG_NOMINAL_LENGTH       = _BV(BLOCK_BIT_NOMINAL_LENGTH)
+  , BLOCK_FLAG_CONTINUED            = _BV(BLOCK_BIT_CONTINUED)
+  , BLOCK_FLAG_SYNC_POSITION        = _BV(BLOCK_BIT_SYNC_POSITION)
+  #if ENABLED(DIRECT_STEPPING)
+    , BLOCK_FLAG_IS_PAGE            = _BV(BLOCK_BIT_IS_PAGE)
+  #endif
 };
 
 #if ENABLED(LASER_POWER_INLINE)
@@ -180,6 +195,10 @@ typedef struct block_t {
            final_rate,                      // The minimal rate at exit
            acceleration_steps_per_s2;       // acceleration steps/sec^2
 
+  #if ENABLED(DIRECT_STEPPING)
+    page_idx_t page_idx;                    // Page index used for direct stepping
+  #endif
+
   #if HAS_CUTTER
     cutter_power_t cutter_power;            // Power level for Spindle, Laser, etc.
   #endif
@@ -296,6 +315,11 @@ class Planner {
       static uint8_t last_extruder;                 // Respond to extruder change
     #endif
 
+    #if ENABLED(DIRECT_STEPPING)
+      static uint32_t last_page_step_rate;          // Last page step rate given
+      static xyze_bool_t last_page_dir;             // Last page direction given
+    #endif
+
     #if EXTRUDERS
       static int16_t flow_percentage[EXTRUDERS];    // Extrusion factor for each extruder
       static float e_factor[EXTRUDERS];             // The flow percentage and volumetric multiplier combine to scale E movement
@@ -726,6 +750,10 @@ class Planner {
       );
     }
 
+    #if ENABLED(DIRECT_STEPPING)
+      static void buffer_page(const page_idx_t page_idx, const uint8_t extruder, const uint16_t num_steps);
+    #endif
+
     /**
      * Set the planner.position and individual stepper positions.
      * Used by G92, G28, G29, and other procedures.
@@ -811,10 +839,10 @@ class Planner {
     static block_t* get_current_block();
 
     /**
-     * "Discard" the block and "release" the memory.
+     * "Release" the current block so its slot can be reused.
      * Called when the current block is no longer needed.
      */
-    FORCE_INLINE static void discard_current_block() {
+    FORCE_INLINE static void release_current_block() {
       if (has_blocks_queued())
         block_buffer_tail = next_block_index(block_buffer_tail);
     }
diff --git a/Marlin/src/module/stepper.cpp b/Marlin/src/module/stepper.cpp
index 92ee753392c8a11523e2bd72db9a693e2c571990..68e75e36e356e96406844e7cc960425e7da57c96 100644
--- a/Marlin/src/module/stepper.cpp
+++ b/Marlin/src/module/stepper.cpp
@@ -230,6 +230,10 @@ uint32_t Stepper::advance_divisor = 0,
   uint32_t Stepper::nextBabystepISR = BABYSTEP_NEVER;
 #endif
 
+#if ENABLED(DIRECT_STEPPING)
+  page_step_state_t Stepper::page_step_state;
+#endif
+
 int32_t Stepper::ticks_nominal = -1;
 #if DISABLED(S_CURVE_ACCELERATION)
   uint32_t Stepper::acc_step_rate; // needed for deceleration start point
@@ -1520,11 +1524,7 @@ void Stepper::pulse_phase_isr() {
   // If we must abort the current block, do so!
   if (abort_current_block) {
     abort_current_block = false;
-    if (current_block) {
-      axis_did_move = 0;
-      current_block = nullptr;
-      planner.discard_current_block();
-    }
+    if (current_block) discard_current_block();
   }
 
   // If there is no current block, do nothing
@@ -1558,46 +1558,160 @@ void Stepper::pulse_phase_isr() {
       } \
     }while(0)
 
-    // Start an active pulse, if Bresenham says so, and update position
+    // Start an active pulse if needed
     #define PULSE_START(AXIS) do{ \
       if (step_needed[_AXIS(AXIS)]) { \
         _APPLY_STEP(AXIS, !_INVERT_STEP_PIN(AXIS), 0); \
       } \
     }while(0)
 
-    // Stop an active pulse, if any, and adjust error term
+    // Stop an active pulse if needed
     #define PULSE_STOP(AXIS) do { \
       if (step_needed[_AXIS(AXIS)]) { \
         _APPLY_STEP(AXIS, _INVERT_STEP_PIN(AXIS), 0); \
       } \
     }while(0)
 
-    // Determine if pulses are needed
-    #if HAS_X_STEP
-      PULSE_PREP(X);
-    #endif
-    #if HAS_Y_STEP
-      PULSE_PREP(Y);
-    #endif
-    #if HAS_Z_STEP
-      PULSE_PREP(Z);
-    #endif
+    // Direct Stepping page?
+    const bool is_page = IS_PAGE(current_block);
+
+    #if ENABLED(DIRECT_STEPPING)
+
+      if (is_page) {
+
+        #if STEPPER_PAGE_FORMAT == SP_4x4D_128
+
+          #define PAGE_SEGMENT_UPDATE(AXIS, VALUE, MID) do{ \
+                 if ((VALUE) == MID) {}                     \
+            else if ((VALUE) <  MID) SBI(dm, _AXIS(AXIS));  \
+            else                     CBI(dm, _AXIS(AXIS));  \
+            page_step_state.sd[_AXIS(AXIS)] = VALUE;        \
+            page_step_state.bd[_AXIS(AXIS)] += VALUE;       \
+          }while(0)
+
+          #define PAGE_PULSE_PREP(AXIS) do{ \
+            step_needed[_AXIS(AXIS)] =      \
+              pgm_read_byte(&segment_table[page_step_state.sd[_AXIS(AXIS)]][page_step_state.segment_steps & 0x7]); \
+          }while(0)
+
+          switch (page_step_state.segment_steps) {
+            case 8:
+              page_step_state.segment_idx += 2;
+              page_step_state.segment_steps = 0;
+              // fallthru
+            case 0: {
+              const uint8_t low = page_step_state.page[page_step_state.segment_idx],
+                           high = page_step_state.page[page_step_state.segment_idx + 1];
+              uint8_t dm = last_direction_bits;
+
+              PAGE_SEGMENT_UPDATE(X, low >> 4, 7);
+              PAGE_SEGMENT_UPDATE(Y, low & 0xF, 7);
+              PAGE_SEGMENT_UPDATE(Z, high >> 4, 7);
+              PAGE_SEGMENT_UPDATE(E, high & 0xF, 7);
+
+              if (dm != last_direction_bits) {
+                last_direction_bits = dm;
+                set_directions();
+              }
+            } break;
+
+            default: break;
+          }
+
+          PAGE_PULSE_PREP(X),
+          PAGE_PULSE_PREP(Y),
+          PAGE_PULSE_PREP(Z),
+          PAGE_PULSE_PREP(E);
+
+          page_step_state.segment_steps++;
+
+        #elif STEPPER_PAGE_FORMAT == SP_4x2_256
+
+          #define PAGE_SEGMENT_UPDATE(AXIS, VALUE) \
+            page_step_state.sd[_AXIS(AXIS)] = VALUE; \
+            page_step_state.bd[_AXIS(AXIS)] += VALUE;
+
+          #define PAGE_PULSE_PREP(AXIS) do{ \
+            step_needed[_AXIS(AXIS)] =      \
+              pgm_read_byte(&segment_table[page_step_state.sd[_AXIS(AXIS)]][page_step_state.segment_steps & 0x3]); \
+          }while(0)
+
+          switch (page_step_state.segment_steps) {
+            case 4:
+              page_step_state.segment_idx++;
+              page_step_state.segment_steps = 0;
+              // fallthru
+            case 0: {
+              const uint8_t b = page_step_state.page[page_step_state.segment_idx];
+              PAGE_SEGMENT_UPDATE(X, (b >> 6) & 0x3);
+              PAGE_SEGMENT_UPDATE(Y, (b >> 4) & 0x3);
+              PAGE_SEGMENT_UPDATE(Z, (b >> 2) & 0x3);
+              PAGE_SEGMENT_UPDATE(E, (b >> 0) & 0x3);
+            } break;
+            default: break;
+          }
+
+          PAGE_PULSE_PREP(X);
+          PAGE_PULSE_PREP(Y);
+          PAGE_PULSE_PREP(Z);
+          PAGE_PULSE_PREP(E);
+
+          page_step_state.segment_steps++;
+
+        #elif STEPPER_PAGE_FORMAT == SP_4x1_512
+
+          #define PAGE_PULSE_PREP(AXIS, BITS) do{             \
+            step_needed[_AXIS(AXIS)] = (steps >> BITS) & 0x1; \
+            if (step_needed[_AXIS(AXIS)])                     \
+              page_step_state.bd[_AXIS(AXIS)]++;              \
+          }while(0)
+
+          uint8_t steps = page_step_state.page[page_step_state.segment_idx >> 1];
+
+          if (page_step_state.segment_idx & 0x1) steps >>= 4;
+
+          PAGE_PULSE_PREP(X, 3);
+          PAGE_PULSE_PREP(Y, 2);
+          PAGE_PULSE_PREP(Z, 1);
+          PAGE_PULSE_PREP(E, 0);
+
+          page_step_state.segment_idx++;
 
-    #if EITHER(LIN_ADVANCE, MIXING_EXTRUDER)
-      delta_error.e += advance_dividend.e;
-      if (delta_error.e >= 0) {
-        count_position.e += count_direction.e;
-        #if ENABLED(LIN_ADVANCE)
-          delta_error.e -= advance_divisor;
-          // Don't step E here - But remember the number of steps to perform
-          motor_direction(E_AXIS) ? --LA_steps : ++LA_steps;
         #else
-          step_needed.e = true;
+          #error "Unknown direct stepping page format!"
         #endif
       }
-    #elif HAS_E0_STEP
-      PULSE_PREP(E);
-    #endif
+
+    #endif // DIRECT_STEPPING
+
+    if (!is_page) {
+      // Determine if pulses are needed
+      #if HAS_X_STEP
+        PULSE_PREP(X);
+      #endif
+      #if HAS_Y_STEP
+        PULSE_PREP(Y);
+      #endif
+      #if HAS_Z_STEP
+        PULSE_PREP(Z);
+      #endif
+
+      #if EITHER(LIN_ADVANCE, MIXING_EXTRUDER)
+        delta_error.e += advance_dividend.e;
+        if (delta_error.e >= 0) {
+          count_position.e += count_direction.e;
+          #if ENABLED(LIN_ADVANCE)
+            delta_error.e -= advance_divisor;
+            // Don't step E here - But remember the number of steps to perform
+            motor_direction(E_AXIS) ? --LA_steps : ++LA_steps;
+          #else
+            step_needed.e = true;
+          #endif
+        }
+      #elif HAS_E0_STEP
+        PULSE_PREP(E);
+      #endif
+    }
 
     #if ISR_MULTI_STEPS
       if (firstStep)
@@ -1676,14 +1790,28 @@ uint32_t Stepper::block_phase_isr() {
   // If there is a current block
   if (current_block) {
 
-    // If current block is finished, reset pointer
+    // If current block is finished, reset pointer and finalize state
     if (step_events_completed >= step_event_count) {
+      #if ENABLED(DIRECT_STEPPING)
+        #if STEPPER_PAGE_FORMAT == SP_4x4D_128
+          #define PAGE_SEGMENT_UPDATE_POS(AXIS) \
+            count_position[_AXIS(AXIS)] += page_step_state.bd[_AXIS(AXIS)] - 128 * 7;
+        #elif STEPPER_PAGE_FORMAT == SP_4x1_512 || STEPPER_PAGE_FORMAT == SP_4x2_256
+          #define PAGE_SEGMENT_UPDATE_POS(AXIS) \
+            count_position[_AXIS(AXIS)] += page_step_state.bd[_AXIS(AXIS)] * count_direction[_AXIS(AXIS)];
+        #endif
+
+        if (IS_PAGE(current_block)) {
+          PAGE_SEGMENT_UPDATE_POS(X);
+          PAGE_SEGMENT_UPDATE_POS(Y);
+          PAGE_SEGMENT_UPDATE_POS(Z);
+          PAGE_SEGMENT_UPDATE_POS(E);
+        }
+      #endif
       #ifdef FILAMENT_RUNOUT_DISTANCE_MM
         runout.block_completed(current_block);
       #endif
-      axis_did_move = 0;
-      current_block = nullptr;
-      planner.discard_current_block();
+      discard_current_block();
     }
     else {
       // Step events not completed yet...
@@ -1867,7 +1995,7 @@ uint32_t Stepper::block_phase_isr() {
       // Sync block? Sync the stepper counts and return
       while (TEST(current_block->flag, BLOCK_BIT_SYNC_POSITION)) {
         _set_position(current_block->position);
-        planner.discard_current_block();
+        discard_current_block();
 
         // Try to get a new block
         if (!(current_block = planner.get_current_block()))
@@ -1878,6 +2006,23 @@ uint32_t Stepper::block_phase_isr() {
 
       TERN_(POWER_LOSS_RECOVERY, recovery.info.sdpos = current_block->sdpos);
 
+      #if ENABLED(DIRECT_STEPPING)
+        if (IS_PAGE(current_block)) {
+          page_step_state.segment_steps = 0;
+          page_step_state.segment_idx = 0;
+          page_step_state.page = page_manager.get_page(current_block->page_idx);
+          page_step_state.bd.reset();
+
+          if (DirectStepping::Config::DIRECTIONAL)
+            current_block->direction_bits = last_direction_bits;
+
+          if (!page_step_state.page) {
+            discard_current_block();
+            return interval;
+          }
+        }
+      #endif
+
       // Flag all moving axes for proper endstop handling
 
       #if IS_CORE
diff --git a/Marlin/src/module/stepper.h b/Marlin/src/module/stepper.h
index 793dc8745bdf273bb83daeaa1847e136805eb66d..a89d36e98f15fd8d6a86b8ea281b9e167d48e193 100644
--- a/Marlin/src/module/stepper.h
+++ b/Marlin/src/module/stepper.h
@@ -334,6 +334,10 @@ class Stepper {
       static uint32_t nextBabystepISR;
     #endif
 
+    #if ENABLED(DIRECT_STEPPING)
+      static page_step_state_t page_step_state;
+    #endif
+
     static int32_t ticks_nominal;
     #if DISABLED(S_CURVE_ACCELERATION)
       static uint32_t acc_step_rate; // needed for deceleration start point
@@ -426,6 +430,17 @@ class Stepper {
     static void report_a_position(const xyz_long_t &pos);
     static void report_positions();
 
+    // Discard current block and free any resources
+    FORCE_INLINE static void discard_current_block() {
+      #if ENABLED(DIRECT_STEPPING)
+        if (IS_PAGE(current_block))
+          page_manager.free_page(current_block->page_idx);
+      #endif
+      current_block = nullptr;
+      axis_did_move = 0;
+      planner.release_current_block();
+    }
+
     // Quickly stop all steppers
     FORCE_INLINE static void quick_stop() { abort_current_block = true; }
 
diff --git a/buildroot/share/tests/mega2560-tests b/buildroot/share/tests/mega2560-tests
index b17d60ba895e4ac2778e3cd1f79564c79317f37f..ed5713eff8a169ca22ac7d8d72e1300ce8a4fd65 100755
--- a/buildroot/share/tests/mega2560-tests
+++ b/buildroot/share/tests/mega2560-tests
@@ -71,8 +71,9 @@ opt_set NUM_SERVOS 1
 opt_enable ZONESTAR_LCD Z_PROBE_SERVO_NR Z_SERVO_ANGLES DEACTIVATE_SERVOS_AFTER_MOVE BOOT_MARLIN_LOGO_ANIMATED \
            AUTO_BED_LEVELING_3POINT DEBUG_LEVELING_FEATURE EEPROM_SETTINGS EEPROM_CHITCHAT M114_DETAIL \
            NO_VOLUMETRICS EXTENDED_CAPABILITIES_REPORT AUTO_REPORT_TEMPERATURES AUTOTEMP G38_PROBE_TARGET JOYSTICK \
-           PRUSA_MMU2 MMU2_MENUS PRUSA_MMU2_S_MODE FILAMENT_RUNOUT_SENSOR NOZZLE_PARK_FEATURE ADVANCED_PAUSE_FEATURE Z_SAFE_HOMING
-exec_test $1 $2 "RAMPS | ZONESTAR_LCD | MMU2 | Servo Probe | ABL 3-Pt | Debug Leveling | EEPROM | G38 ..."
+           PRUSA_MMU2 MMU2_MENUS PRUSA_MMU2_S_MODE DIRECT_STEPPING \
+           FILAMENT_RUNOUT_SENSOR NOZZLE_PARK_FEATURE ADVANCED_PAUSE_FEATURE Z_SAFE_HOMING
+exec_test $1 $2 "RAMPS | ZONESTAR + Chinese | MMU2 | Servo | 3-Point + Debug | G38 ..."
 
 #
 # Test MINIRAMBO with PWM_MOTOR_CURRENT and many features