diff --git a/Marlin/src/Marlin.cpp b/Marlin/src/Marlin.cpp
index e612b0de93ed6020cbbdacff9e0abf3b641a2eab..5cf65cd30583f738f4c6aaee18dc593370d8b370 100644
--- a/Marlin/src/Marlin.cpp
+++ b/Marlin/src/Marlin.cpp
@@ -939,6 +939,15 @@ void setup() {
 
   queue_setup();
 
+  // UI must be initialized before EEPROM
+  // (because EEPROM code calls the UI).
+  ui.init();
+  ui.reset_status();
+
+  #if HAS_SPI_LCD && ENABLED(SHOW_BOOTSCREEN)
+    ui.show_bootscreen();
+  #endif
+
   #if ENABLED(SDIO_SUPPORT) && SD_DETECT_PIN == -1
     // Auto-mount the SD for EEPROM.dat emulation
     if (!card.isDetected()) card.initsd();
@@ -1044,13 +1053,6 @@ void setup() {
     fanmux_init();
   #endif
 
-  ui.init();
-  ui.reset_status();
-
-  #if HAS_SPI_LCD && ENABLED(SHOW_BOOTSCREEN)
-    ui.show_bootscreen();
-  #endif
-
   #if ENABLED(MIXING_EXTRUDER)
     mixer.init();
   #endif
diff --git a/Marlin/src/feature/backlash.cpp b/Marlin/src/feature/backlash.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3254fb3f3c54805897487e864c4e009581f9fad7
--- /dev/null
+++ b/Marlin/src/feature/backlash.cpp
@@ -0,0 +1,139 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2019 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 "../Marlin.h"
+
+#if ENABLED(BACKLASH_COMPENSATION)
+
+#include "backlash.h"
+#include "../module/planner.h"
+
+#if ENABLED(BACKLASH_GCODE)
+  uint8_t Backlash::correction = (BACKLASH_CORRECTION) * 0xFF;
+  #ifdef BACKLASH_DISTANCE_MM
+    float Backlash::distance_mm[XYZ] = BACKLASH_DISTANCE_MM;
+  #endif
+  #ifdef BACKLASH_SMOOTHING_MM
+    float Backlash::smoothing_mm = BACKLASH_SMOOTHING_MM;
+  #endif
+#endif
+
+#if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
+  float Backlash::measured_mm[XYZ] = { 0 };
+  uint8_t Backlash::measured_count[XYZ] = { 0 };
+#endif
+
+Backlash backlash;
+
+/**
+ * To minimize seams in the printed part, backlash correction only adds
+ * steps to the current segment (instead of creating a new segment, which
+ * causes discontinuities and print artifacts).
+ *
+ * With a non-zero BACKLASH_SMOOTHING_MM value the backlash correction is
+ * spread over multiple segments, smoothing out artifacts even more.
+ */
+
+void Backlash::add_correction_steps(const int32_t &da, const int32_t &db, const int32_t &dc, const uint8_t dm, block_t * const block) {
+  static uint8_t last_direction_bits;
+  uint8_t changed_dir = last_direction_bits ^ dm;
+  // Ignore direction change if no steps are taken in that direction
+  if (da == 0) CBI(changed_dir, X_AXIS);
+  if (db == 0) CBI(changed_dir, Y_AXIS);
+  if (dc == 0) CBI(changed_dir, Z_AXIS);
+  last_direction_bits ^= changed_dir;
+
+  if (correction == 0) return;
+
+  #ifdef BACKLASH_SMOOTHING_MM
+    // The segment proportion is a value greater than 0.0 indicating how much residual_error
+    // is corrected for in this segment. The contribution is based on segment length and the
+    // smoothing distance. Since the computation of this proportion involves a floating point
+    // division, defer computation until needed.
+    float segment_proportion = 0;
+
+    // Residual error carried forward across multiple segments, so correction can be applied
+    // to segments where there is no direction change.
+    static int32_t residual_error[XYZ] = { 0 };
+  #else
+    // No leftover residual error from segment to segment
+    int32_t residual_error[XYZ] = { 0 };
+    // No direction change, no correction.
+    if (!changed_dir) return;
+  #endif
+
+  const float f_corr = float(correction) / 255.0f;
+
+  LOOP_XYZ(axis) {
+    if (distance_mm[axis]) {
+      const bool reversing = TEST(dm,axis);
+
+      // When an axis changes direction, add axis backlash to the residual error
+      if (TEST(changed_dir, axis))
+        residual_error[axis] += (reversing ? -f_corr : f_corr) * distance_mm[axis] * planner.settings.axis_steps_per_mm[axis];
+
+      // Decide how much of the residual error to correct in this segment
+      int32_t error_correction = residual_error[axis];
+      #ifdef BACKLASH_SMOOTHING_MM
+        if (error_correction && smoothing_mm != 0) {
+          // Take up a portion of the residual_error in this segment, but only when
+          // the current segment travels in the same direction as the correction
+          if (reversing == (error_correction < 0)) {
+            if (segment_proportion == 0)
+              segment_proportion = MIN(1.0f, block->millimeters / smoothing_mm);
+            error_correction = ceil(segment_proportion * error_correction);
+          }
+          else
+            error_correction = 0; // Don't take up any backlash in this segment, as it would subtract steps
+        }
+      #endif
+      // Making a correction reduces the residual error and modifies delta_mm
+      if (error_correction) {
+        block->steps[axis] += ABS(error_correction);
+        residual_error[axis] -= error_correction;
+      }
+    }
+  }
+}
+
+#if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
+  #if USES_Z_MIN_PROBE_ENDSTOP
+    #define TEST_PROBE_PIN (READ(Z_MIN_PROBE_PIN) != Z_MIN_PROBE_ENDSTOP_INVERTING)
+  #else
+    #define TEST_PROBE_PIN (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING)
+  #endif
+
+  // Measure Z backlash by raising nozzle in increments until probe deactivates
+  void Backlash::measure_with_probe() {
+    if (measured_count[Z_AXIS] == 255) return;
+
+    float start_height = current_position[Z_AXIS];
+    while (current_position[Z_AXIS] < (start_height + BACKLASH_MEASUREMENT_LIMIT) && TEST_PROBE_PIN)
+      do_blocking_move_to_z(current_position[Z_AXIS] + BACKLASH_MEASUREMENT_RESOLUTION, MMM_TO_MMS(BACKLASH_MEASUREMENT_FEEDRATE));
+
+    // The backlash from all probe points is averaged, so count the number of measurements
+    measured_mm[Z_AXIS] += current_position[Z_AXIS] - start_height;
+    measured_count[Z_AXIS]++;
+  }
+#endif
+
+#endif // BACKLASH_COMPENSATION
diff --git a/Marlin/src/feature/backlash.h b/Marlin/src/feature/backlash.h
new file mode 100644
index 0000000000000000000000000000000000000000..c35675f1b1676a947d75598d43c0e4561d6372e3
--- /dev/null
+++ b/Marlin/src/feature/backlash.h
@@ -0,0 +1,88 @@
+/**
+ * Marlin 3D Printer Firmware
+ * Copyright (C) 2019 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/MarlinConfigPre.h"
+#include "../module/planner.h"
+
+class Backlash {
+public:
+  #if ENABLED(BACKLASH_GCODE)
+    static uint8_t correction;
+    #ifdef BACKLASH_DISTANCE_MM
+      static float distance_mm[XYZ];
+    #endif
+    #ifdef BACKLASH_SMOOTHING_MM
+      static float smoothing_mm;
+    #endif
+    static inline void set_correction(const float &v) { correction = MAX(0, MIN(1.0, v)) * all_on; }
+    static inline float get_correction() { return float(ui8_to_percent(correction)) / 100.0f; }
+  #elif ENABLED(BACKLASH_COMPENSATION)
+    static constexpr uint8_t correction = (BACKLASH_CORRECTION) * 0xFF;
+    #ifdef BACKLASH_DISTANCE_MM
+      static constexpr float distance_mm[XYZ] = BACKLASH_DISTANCE_MM;
+    #endif
+    #ifdef BACKLASH_SMOOTHING_MM
+      static constexpr float smoothing_mm = BACKLASH_SMOOTHING_MM;
+    #endif
+    static inline void set_correction(float) { }
+    static inline float get_correction() { return float(ui8_to_percent(correction)) / 100.0f; }
+  #else
+    static constexpr uint8_t correction = 0;
+    static inline void set_correction(float) { }
+    static inline float get_correction() { return 0; }
+  #endif
+
+  #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
+    private:
+      static float measured_mm[XYZ];
+      static uint8_t measured_count[XYZ];
+    public:
+      static void measure_with_probe();
+  #endif
+
+  static inline float get_measurement(const uint8_t e) {
+    // Return the measurement averaged over all readings
+    return (
+      #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
+        measured_count[e] > 0 ? measured_mm[e] / measured_count[e] :
+      #endif
+      0
+    );
+  }
+
+  static inline bool has_measurement(const uint8_t e) {
+    return (false
+      #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
+        || (measured_count[e] > 0)
+      #endif
+    );
+  }
+
+  static inline bool has_any_measurement() {
+    return has_measurement(X_AXIS) || has_measurement(Y_AXIS) || has_measurement(Z_AXIS);
+  }
+
+  void add_correction_steps(const int32_t &da, const int32_t &db, const int32_t &dc, const uint8_t dm, block_t * const block);
+};
+
+extern Backlash backlash;
diff --git a/Marlin/src/feature/runout.cpp b/Marlin/src/feature/runout.cpp
index 9395dec5b41c6ccd28d18be233283d60630fb57a..e319a7cc2fd22ffe51e78af3572a89f994e3d89a 100644
--- a/Marlin/src/feature/runout.cpp
+++ b/Marlin/src/feature/runout.cpp
@@ -51,7 +51,7 @@ void FilamentSensorBase::filament_present(const uint8_t extruder) {
   uint8_t FilamentSensorEncoder::motion_detected;
 #endif
 
-#if FILAMENT_RUNOUT_DISTANCE_MM > 0
+#ifdef FILAMENT_RUNOUT_DISTANCE_MM
   float RunoutResponseDelayed::runout_distance_mm = FILAMENT_RUNOUT_DISTANCE_MM;
   volatile float RunoutResponseDelayed::runout_mm_countdown[EXTRUDERS];
 #else
diff --git a/Marlin/src/feature/runout.h b/Marlin/src/feature/runout.h
index 3f1e5fa0187dfe83ca67fce188534c7f71dbb7a3..eccc7ab9314425c5e018d80e2812674c8719b274 100644
--- a/Marlin/src/feature/runout.h
+++ b/Marlin/src/feature/runout.h
@@ -78,6 +78,11 @@ class TFilamentMonitor : public FilamentMonitorBase {
       response.filament_present(extruder);
     }
 
+    #ifdef FILAMENT_RUNOUT_DISTANCE_MM
+      static inline float& runout_distance() { return response.runout_distance_mm; }
+      static inline void set_runout_distance(const float &mm) { response.runout_distance_mm = mm; }
+    #endif
+
     // Handle a block completion. RunoutResponseDelayed uses this to
     // add up the length of filament moved while the filament is out.
     static inline void block_completed(const block_t* const b) {
@@ -90,13 +95,13 @@ class TFilamentMonitor : public FilamentMonitorBase {
     // Give the response a chance to update its counter.
     static inline void run() {
       if (enabled && !filament_ran_out && (IS_SD_PRINTING() || print_job_timer.isRunning() || did_pause_print)) {
-        #if FILAMENT_RUNOUT_DISTANCE_MM > 0
+        #ifdef FILAMENT_RUNOUT_DISTANCE_MM
           cli(); // Prevent RunoutResponseDelayed::block_completed from accumulating here
         #endif
         response.run();
         sensor.run();
         const bool ran_out = response.has_run_out();
-        #if FILAMENT_RUNOUT_DISTANCE_MM > 0
+        #ifdef FILAMENT_RUNOUT_DISTANCE_MM
           sei();
         #endif
         if (ran_out) {
@@ -272,7 +277,7 @@ class FilamentSensorBase {
 
 /********************************* RESPONSE TYPE *********************************/
 
-#if FILAMENT_RUNOUT_DISTANCE_MM > 0
+#ifdef FILAMENT_RUNOUT_DISTANCE_MM
 
   // RunoutResponseDelayed triggers a runout event only if the length
   // of filament specified by FILAMENT_RUNOUT_DISTANCE_MM has been fed
@@ -347,11 +352,12 @@ class FilamentSensorBase {
 /********************************* TEMPLATE SPECIALIZATION *********************************/
 
 typedef TFilamentMonitor<
-  #if FILAMENT_RUNOUT_DISTANCE_MM > 0
+  #ifdef FILAMENT_RUNOUT_DISTANCE_MM
+    RunoutResponseDelayed,
     #if ENABLED(FILAMENT_MOTION_SENSOR)
-      RunoutResponseDelayed, FilamentSensorEncoder
+      FilamentSensorEncoder
     #else
-      RunoutResponseDelayed, FilamentSensorSwitch
+      FilamentSensorSwitch
     #endif
   #else
     RunoutResponseDebounced, FilamentSensorSwitch
diff --git a/Marlin/src/gcode/calibrate/G425.cpp b/Marlin/src/gcode/calibrate/G425.cpp
index 1174fc38f8c2e3448e0b170d28e1378b495b4d3a..03e4c4eb430768c392d4bc8f1ef20f800459ce91 100644
--- a/Marlin/src/gcode/calibrate/G425.cpp
+++ b/Marlin/src/gcode/calibrate/G425.cpp
@@ -31,6 +31,7 @@
 #include "../../module/tool_change.h"
 #include "../../module/endstops.h"
 #include "../../feature/bedlevel/bedlevel.h"
+#include "../../feature/backlash.h"
 
 
 /**
@@ -55,11 +56,6 @@
 #define HAS_X_CENTER BOTH(CALIBRATION_MEASURE_LEFT, CALIBRATION_MEASURE_RIGHT)
 #define HAS_Y_CENTER BOTH(CALIBRATION_MEASURE_FRONT, CALIBRATION_MEASURE_BACK)
 
-#if ENABLED(BACKLASH_GCODE)
-  extern float backlash_distance_mm[], backlash_smoothing_mm;
-  extern uint8_t backlash_correction;
-#endif
-
 enum side_t : uint8_t { TOP, RIGHT, FRONT, LEFT, BACK, NUM_SIDES };
 
 struct measurements_t {
@@ -79,13 +75,13 @@ struct measurements_t {
 #define TEMPORARY_SOFT_ENDSTOP_STATE(enable) REMEMBER(tes, soft_endstops_enabled, enable);
 
 #if ENABLED(BACKLASH_GCODE)
-  #define TEMPORARY_BACKLASH_CORRECTION(value) REMEMBER(tbst, backlash_correction, value)
+  #define TEMPORARY_BACKLASH_CORRECTION(value) REMEMBER(tbst, backlash.correction, value)
 #else
   #define TEMPORARY_BACKLASH_CORRECTION(value)
 #endif
 
 #if ENABLED(BACKLASH_GCODE) && defined(BACKLASH_SMOOTHING_MM)
-  #define TEMPORARY_BACKLASH_SMOOTHING(value) REMEMBER(tbsm, backlash_smoothing_mm, value)
+  #define TEMPORARY_BACKLASH_SMOOTHING(value) REMEMBER(tbsm, backlash.smoothing_mm, value)
 #else
   #define TEMPORARY_BACKLASH_SMOOTHING(value)
 #endif
@@ -454,22 +450,22 @@ inline void calibrate_backlash(measurements_t &m, const float uncertainty) {
 
     #if ENABLED(BACKLASH_GCODE)
       #if HAS_X_CENTER
-        backlash_distance_mm[X_AXIS] = (m.backlash[LEFT] + m.backlash[RIGHT]) / 2;
+        backlash.distance_mm[X_AXIS] = (m.backlash[LEFT] + m.backlash[RIGHT]) / 2;
       #elif ENABLED(CALIBRATION_MEASURE_LEFT)
-        backlash_distance_mm[X_AXIS] = m.backlash[LEFT];
+        backlash.distance_mm[X_AXIS] = m.backlash[LEFT];
       #elif ENABLED(CALIBRATION_MEASURE_RIGHT)
-        backlash_distance_mm[X_AXIS] = m.backlash[RIGHT];
+        backlash.distance_mm[X_AXIS] = m.backlash[RIGHT];
       #endif
 
       #if HAS_Y_CENTER
-        backlash_distance_mm[Y_AXIS] = (m.backlash[FRONT] + m.backlash[BACK]) / 2;
+        backlash.distance_mm[Y_AXIS] = (m.backlash[FRONT] + m.backlash[BACK]) / 2;
       #elif ENABLED(CALIBRATION_MEASURE_FRONT)
-        backlash_distance_mm[Y_AXIS] = m.backlash[FRONT];
+        backlash.distance_mm[Y_AXIS] = m.backlash[FRONT];
       #elif ENABLED(CALIBRATION_MEASURE_BACK)
-        backlash_distance_mm[Y_AXIS] = m.backlash[BACK];
+        backlash.distance_mm[Y_AXIS] = m.backlash[BACK];
       #endif
 
-      backlash_distance_mm[Z_AXIS] = m.backlash[TOP];
+      backlash.distance_mm[Z_AXIS] = m.backlash[TOP];
     #endif
   }
 
diff --git a/Marlin/src/gcode/calibrate/M425.cpp b/Marlin/src/gcode/calibrate/M425.cpp
index e51c9301cf0a5bc5580a5e8a530ebb552d3436ff..74b38f56cc8a7210d3f36181bfc7f1c9a26e3121 100644
--- a/Marlin/src/gcode/calibrate/M425.cpp
+++ b/Marlin/src/gcode/calibrate/M425.cpp
@@ -24,20 +24,9 @@
 
 #if ENABLED(BACKLASH_GCODE)
 
+#include "../../feature/backlash.h"
 #include "../../module/planner.h"
 
-float   backlash_distance_mm[XYZ] = BACKLASH_DISTANCE_MM;
-uint8_t backlash_correction = BACKLASH_CORRECTION * all_on;
-
-#ifdef BACKLASH_SMOOTHING_MM
-  float backlash_smoothing_mm = BACKLASH_SMOOTHING_MM;
-#endif
-
-#if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
-  float backlash_measured_mm[XYZ] = { 0 };
-  uint8_t backlash_measured_num[XYZ] = { 0 };
-#endif
-
 #include "../gcode.h"
 
 /**
@@ -60,59 +49,52 @@ void GcodeSuite::M425() {
   LOOP_XYZ(i) {
     if (parser.seen(axis_codes[i])) {
       planner.synchronize();
-      const float measured_backlash = (
-        #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
-          backlash_measured_num[i] > 0 ? backlash_measured_mm[i] / backlash_measured_num[i] : 0
-        #else
-          0
-        #endif
-      );
-      backlash_distance_mm[i] = parser.has_value() ? parser.value_linear_units() : measured_backlash;
+      backlash.distance_mm[i] = parser.has_value() ? parser.value_linear_units() : backlash.get_measurement(i);
       noArgs = false;
     }
   }
 
   if (parser.seen('F')) {
     planner.synchronize();
-    backlash_correction = MAX(0, MIN(1.0, parser.value_float())) * all_on;
+    backlash.set_correction(parser.value_float());
     noArgs = false;
   }
 
   #ifdef BACKLASH_SMOOTHING_MM
     if (parser.seen('S')) {
       planner.synchronize();
-      backlash_smoothing_mm = parser.value_linear_units();
+      backlash.smoothing_mm = parser.value_linear_units();
       noArgs = false;
     }
   #endif
 
   if (noArgs) {
-    SERIAL_ECHOPGM("Backlash correction is ");
-    if (!backlash_correction) SERIAL_ECHOPGM("in");
+    SERIAL_ECHOPGM("Backlash Correction ");
+    if (!backlash.correction) SERIAL_ECHOPGM("in");
     SERIAL_ECHOLNPGM("active:");
-    SERIAL_ECHOLNPAIR("  Correction Amount/Fade-out:     F", float(ui8_to_percent(backlash_correction)) / 100, "     (F1.0 = full, F0.0 = none)");
+    SERIAL_ECHOLNPAIR("  Correction Amount/Fade-out:     F", backlash.get_correction(), " (F1.0 = full, F0.0 = none)");
     SERIAL_ECHOPGM("  Backlash Distance (mm):        ");
     LOOP_XYZ(a) {
       SERIAL_CHAR(' ');
       SERIAL_CHAR(axis_codes[a]);
-      SERIAL_ECHO(backlash_distance_mm[a]);
+      SERIAL_ECHO(backlash.distance_mm[a]);
       SERIAL_EOL();
     }
 
     #ifdef BACKLASH_SMOOTHING_MM
-      SERIAL_ECHOLNPAIR("  Smoothing (mm):                 S", backlash_smoothing_mm);
+      SERIAL_ECHOLNPAIR("  Smoothing (mm):                 S", backlash.smoothing_mm);
     #endif
 
     #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
       SERIAL_ECHOPGM("  Average measured backlash (mm):");
-      LOOP_XYZ(a) {
-        if (backlash_measured_num[a] > 0) {
+      if (backlash.has_any_measurement()) {
+        LOOP_XYZ(a) if (backlash.has_measurement(a)) {
           SERIAL_CHAR(' ');
           SERIAL_CHAR(axis_codes[a]);
-          SERIAL_ECHO(backlash_measured_mm[a] / backlash_measured_num[a]);
+          SERIAL_ECHO(backlash.get_measurement(a));
         }
       }
-      if (!backlash_measured_num[X_AXIS] && !backlash_measured_num[Y_AXIS] && !backlash_measured_num[Z_AXIS])
+      else
         SERIAL_ECHOPGM(" (Not yet measured)");
       SERIAL_EOL();
     #endif
diff --git a/Marlin/src/gcode/feature/runout/M412.cpp b/Marlin/src/gcode/feature/runout/M412.cpp
index 749ce2a3b5924ab0d4320f145b9462d89457061b..6cf2238dbb1be93d5e6bff5a2f736593de1ce2fa 100644
--- a/Marlin/src/gcode/feature/runout/M412.cpp
+++ b/Marlin/src/gcode/feature/runout/M412.cpp
@@ -32,6 +32,9 @@
  */
 void GcodeSuite::M412() {
   if (parser.seen("HS"
+    #ifdef FILAMENT_RUNOUT_DISTANCE_MM
+      "D"
+    #endif
     #if ENABLED(HOST_ACTION_COMMANDS)
       "R"
     #endif
@@ -42,11 +45,17 @@ void GcodeSuite::M412() {
     const bool seenR = parser.seen('R'), seenS = parser.seen('S');
     if (seenR || seenS) runout.reset();
     if (seenS) runout.enabled = parser.value_bool();
+    #ifdef FILAMENT_RUNOUT_DISTANCE_MM
+      if (parser.seen('D')) runout.set_runout_distance(parser.value_linear_units());
+    #endif
   }
   else {
     SERIAL_ECHO_START();
     SERIAL_ECHOPGM("Filament runout ");
     serialprintln_onoff(runout.enabled);
+    #ifdef FILAMENT_RUNOUT_DISTANCE_MM
+      SERIAL_ECHOLNPAIR("Filament runout distance (mm): ", runout.runout_distance());
+    #endif
   }
 }
 
diff --git a/Marlin/src/inc/Conditionals_LCD.h b/Marlin/src/inc/Conditionals_LCD.h
index 45051e3a82dc4f2d316b489e5e89f90417445abb..764ccb5abcbe9ddca284572ff1e50b3537681c64 100644
--- a/Marlin/src/inc/Conditionals_LCD.h
+++ b/Marlin/src/inc/Conditionals_LCD.h
@@ -516,6 +516,9 @@
   #define GRID_MAX_POINTS ((GRID_MAX_POINTS_X) * (GRID_MAX_POINTS_Y))
 #endif
 
+#if ENABLED(MALYAN_LCD)
+  #define EXTENSIBLE_UI
+#endif
 #define HAS_SOFTWARE_ENDSTOPS EITHER(MIN_SOFTWARE_ENDSTOPS, MAX_SOFTWARE_ENDSTOPS)
 #define HAS_RESUME_CONTINUE   ANY(EXTENSIBLE_UI, NEWPANEL, EMERGENCY_PARSER)
 #define HAS_COLOR_LEDS        ANY(BLINKM, RGB_LED, RGBW_LED, PCA9632, PCA9533, NEOPIXEL_LED)
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index a268595c25db3a41f81c63c519511a82df422dac..3f80764ac4b22c5b7e75cffd5f5e11602804b4ce 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -613,6 +613,8 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
     #error "FILAMENT_RUNOUT_SENSOR with NUM_RUNOUT_SENSORS > 5 requires FIL_RUNOUT6_PIN."
   #elif DISABLED(SDSUPPORT, PRINTJOB_TIMER_AUTOSTART)
     #error "FILAMENT_RUNOUT_SENSOR requires SDSUPPORT or PRINTJOB_TIMER_AUTOSTART."
+  #elif FILAMENT_RUNOUT_DISTANCE_MM < 0
+    #error "FILAMENT_RUNOUT_DISTANCE_MM must be greater than or equal to zero."
   #elif DISABLED(ADVANCED_PAUSE_FEATURE)
     static_assert(NULL == strstr(FILAMENT_RUNOUT_SCRIPT, "M600"), "ADVANCED_PAUSE_FEATURE is required to use M600 with FILAMENT_RUNOUT_SENSOR.");
   #endif
@@ -1784,7 +1786,7 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
   + ENABLED(OLED_PANEL_TINYBOY2) \
   + ENABLED(ZONESTAR_LCD) \
   + ENABLED(ULTI_CONTROLLER) \
-  + ENABLED(EXTENSIBLE_UI)
+  + (ENABLED(EXTENSIBLE_UI) && DISABLED(MALYAN_LCD))
   #error "Please select no more than one LCD controller option."
 #endif
 
diff --git a/Marlin/src/lcd/extensible_ui/lib/example.cpp b/Marlin/src/lcd/extensible_ui/lib/example.cpp
index 2a11d5c3c44d3add2f17af641d4b9e3a31cf21bf..5a52e26787b9baf8d4fbf4d30f2aee6d4515e2bd 100644
--- a/Marlin/src/lcd/extensible_ui/lib/example.cpp
+++ b/Marlin/src/lcd/extensible_ui/lib/example.cpp
@@ -1,6 +1,6 @@
-/*************
- * dummy.cpp *
- *************/
+/***************
+ * example.cpp *
+ ***************/
 
 /****************************************************************************
  *   Written By Marcio Teixeira 2018 - Aleph Objects, Inc.                  *
@@ -21,7 +21,7 @@
 
 #include "../../../inc/MarlinConfigPre.h"
 
-#if ENABLED(EXTENSIBLE_UI)
+#if BOTH(EXTUI_EXAMPLE, EXTENSIBLE_UI)
 
 #include "../ui_api.h"
 
@@ -58,8 +58,36 @@ namespace ExtUI {
   void onUserConfirmRequired(const char * const msg) {}
   void onStatusChanged(const char * const msg) {}
   void onFactoryReset() {}
-  void onLoadSettings() {}
-  void onStoreSettings() {}
+
+  void onStoreSettings(char *buff) {
+    // This is called when saving to EEPROM (i.e. M500). If the ExtUI needs
+    // permanent data to be stored, it can write up to eeprom_data_size bytes
+    // into buff.
+
+    // Example:
+    //  static_assert(sizeof(myDataStruct) <= ExtUI::eeprom_data_size);
+    //  memcpy(buff, &myDataStruct, sizeof(myDataStruct));
+  }
+
+  void onLoadSettings(const char *buff) {
+    // This is called while loading settings from EEPROM. If the ExtUI
+    // needs to retrieve data, it should copy up to eeprom_data_size bytes
+    // from buff
+
+    // Example:
+    //  static_assert(sizeof(myDataStruct) <= ExtUI::eeprom_data_size);
+    //  memcpy(&myDataStruct, buff, sizeof(myDataStruct));
+  }
+
+  void onConfigurationStoreWritten(bool success) {
+    // This is called after the entire EEPROM has been written,
+    // whether successful or not.
+  }
+
+  void onConfigurationStoreRead(bool success) {
+    // This is called after the entire EEPROM has been read,
+    // whether successful or not.
+  }
 }
 
-#endif // EXTENSIBLE_UI
+#endif // EXTUI_EXAMPLE && EXTENSIBLE_UI
diff --git a/Marlin/src/lcd/extensible_ui/ui_api.cpp b/Marlin/src/lcd/extensible_ui/ui_api.cpp
index 8e531ec2a9b78d9a45ac2278e66df6f4f2c292b6..a140896bfad01059144f7bcaf8583c880f0b948c 100644
--- a/Marlin/src/lcd/extensible_ui/ui_api.cpp
+++ b/Marlin/src/lcd/extensible_ui/ui_api.cpp
@@ -82,11 +82,7 @@
 #include "ui_api.h"
 
 #if ENABLED(BACKLASH_GCODE)
-  extern float backlash_distance_mm[XYZ];
-  extern uint8_t backlash_correction;
-  #ifdef BACKLASH_SMOOTHING_MM
-    extern float backlash_smoothing_mm;
-  #endif
+  #include "../../feature/backlash.h"
 #endif
 
 #if HAS_LEVELING
@@ -111,7 +107,6 @@ static struct {
 } flags;
 
 namespace ExtUI {
-
   #ifdef __SAM3X8E__
     /**
      * Implement a special millis() to allow time measurement
@@ -517,13 +512,13 @@ namespace ExtUI {
     bool getFilamentRunoutEnabled()                 { return runout.enabled; }
     void setFilamentRunoutEnabled(const bool value) { runout.enabled = value; }
 
-    #if FILAMENT_RUNOUT_DISTANCE_MM > 0
+    #ifdef FILAMENT_RUNOUT_DISTANCE_MM
       float getFilamentRunoutDistance_mm() {
-        return RunoutResponseDelayed::runout_distance_mm;
+        return runout.runout_distance();
       }
 
       void setFilamentRunoutDistance_mm(const float value) {
-        RunoutResponseDelayed::runout_distance_mm = clamp(value, 0, 999);
+        runout.set_runout_distance(clamp(value, 0, 999));
       }
     #endif
   #endif
@@ -687,16 +682,16 @@ namespace ExtUI {
   #endif // HAS_HOTEND_OFFSET
 
   #if ENABLED(BACKLASH_GCODE)
-    float getAxisBacklash_mm(const axis_t axis)       { return backlash_distance_mm[axis]; }
+    float getAxisBacklash_mm(const axis_t axis)       { return backlash.distance_mm[axis]; }
     void setAxisBacklash_mm(const float value, const axis_t axis)
-                                                      { backlash_distance_mm[axis] = clamp(value,0,5); }
+                                                      { backlash.distance_mm[axis] = clamp(value,0,5); }
 
-    float getBacklashCorrection_percent()             { return ui8_to_percent(backlash_correction); }
-    void setBacklashCorrection_percent(const float value) { backlash_correction = map(clamp(value, 0, 100), 0, 100, 0, 255); }
+    float getBacklashCorrection_percent()             { return ui8_to_percent(backlash.correction); }
+    void setBacklashCorrection_percent(const float value) { backlash.correction = map(clamp(value, 0, 100), 0, 100, 0, 255); }
 
     #ifdef BACKLASH_SMOOTHING_MM
-      float getBacklashSmoothing_mm()                 { return backlash_smoothing_mm; }
-      void setBacklashSmoothing_mm(const float value) { backlash_smoothing_mm = clamp(value, 0, 999); }
+      float getBacklashSmoothing_mm()                 { return backlash.smoothing_mm; }
+      void setBacklashSmoothing_mm(const float value) { backlash.smoothing_mm = clamp(value, 0, 999); }
     #endif
   #endif
 
@@ -750,7 +745,7 @@ namespace ExtUI {
   }
 
   bool commandsInQueue() { return (planner.movesplanned() || commands_in_queue); }
-  
+
   bool isAxisPositionKnown(const axis_t axis) {
     return TEST(axis_known_position, axis);
   }
diff --git a/Marlin/src/lcd/extensible_ui/ui_api.h b/Marlin/src/lcd/extensible_ui/ui_api.h
index 2f2f99dc11ac86e3f28d640faa9651b5582ef9e3..a643ffd70daf09bf3412cfca311c7104d5d11ecb 100644
--- a/Marlin/src/lcd/extensible_ui/ui_api.h
+++ b/Marlin/src/lcd/extensible_ui/ui_api.h
@@ -46,6 +46,11 @@
 #include "../../inc/MarlinConfig.h"
 
 namespace ExtUI {
+  // The ExtUI implementation can store up to this many bytes
+  // in the EEPROM when the methods onStoreSettings and
+  // onLoadSettings are called.
+
+  static constexpr size_t eeprom_data_size = 48;
 
   enum axis_t     : uint8_t { X, Y, Z };
   enum extruder_t : uint8_t { E0, E1, E2, E3, E4, E5 };
@@ -207,7 +212,7 @@ namespace ExtUI {
     bool getFilamentRunoutEnabled();
     void setFilamentRunoutEnabled(const bool);
 
-    #if FILAMENT_RUNOUT_DISTANCE_MM > 0
+    #ifdef FILAMENT_RUNOUT_DISTANCE_MM
       float getFilamentRunoutDistance_mm();
       void setFilamentRunoutDistance_mm(const float);
     #endif
@@ -283,8 +288,10 @@ namespace ExtUI {
   void onUserConfirmRequired(const char * const msg);
   void onStatusChanged(const char * const msg);
   void onFactoryReset();
-  void onStoreSettings();
-  void onLoadSettings();
+  void onStoreSettings(char *);
+  void onLoadSettings(const char *);
+  void onConfigurationStoreWritten(bool success);
+  void onConfigurationStoreRead(bool success);
 };
 
 /**
diff --git a/Marlin/src/lcd/malyanlcd.cpp b/Marlin/src/lcd/malyanlcd.cpp
index cb0e2a00cc20aac3ef04f1193a6e097f772d36f1..8a6d611bd91229506ad8a96a91e15ec48cb6946a 100644
--- a/Marlin/src/lcd/malyanlcd.cpp
+++ b/Marlin/src/lcd/malyanlcd.cpp
@@ -41,23 +41,19 @@
  * Copyright (c) 2017 Jason Nelson (xC0000005)
  */
 
-#include "../inc/MarlinConfig.h"
+#include "../inc/MarlinConfigPre.h"
 
 #if ENABLED(MALYAN_LCD)
 
+#include "extensible_ui/ui_api.h"
+
 #include "ultralcd.h"
 #include "../module/temperature.h"
-#include "../module/planner.h"
 #include "../module/stepper.h"
 #include "../module/motion.h"
-#include "../module/probe.h"
 #include "../libs/duration_t.h"
 #include "../module/printcounter.h"
-#include "../gcode/gcode.h"
 #include "../gcode/queue.h"
-#include "../module/configuration_store.h"
-
-#include "../Marlin.h"
 
 #if ENABLED(SDSUPPORT)
   #include "../sd/cardreader.h"
@@ -412,78 +408,118 @@ void update_usb_status(const bool forceUpdate) {
   }
 }
 
-/**
- * - from printer on startup:
- * {SYS:STARTED}{VER:29}{SYS:STARTED}{R:UD}
- * The optimize attribute fixes a register Compile
- * error for amtel.
- */
-void MarlinUI::update() {
-  static char inbound_buffer[MAX_CURLY_COMMAND];
-
-  // First report USB status.
-  update_usb_status(false);
-
-  // now drain commands...
-  while (LCD_SERIAL.available()) {
-    const byte b = (byte)LCD_SERIAL.read() & 0x7F;
-    inbound_buffer[inbound_count++] = b;
-    if (b == '}' || inbound_count == sizeof(inbound_buffer) - 1) {
-      inbound_buffer[inbound_count - 1] = '\0';
-      process_lcd_command(inbound_buffer);
-      inbound_count = 0;
-      inbound_buffer[0] = 0;
-    }
+namespace ExtUI {
+  void onStartup() {
+    /**
+     * The Malyan LCD actually runs as a separate MCU on Serial 1.
+     * This code's job is to siphon the weird curly-brace commands from
+     * it and translate into gcode, which then gets injected into
+     * the command queue where possible.
+     */
+    inbound_count = 0;
+    LCD_SERIAL.begin(500000);
+
+    // Signal init
+    write_to_lcd_P(PSTR("{SYS:STARTED}\r\n"));
+
+    // send a version that says "unsupported"
+    write_to_lcd_P(PSTR("{VER:99}\r\n"));
+
+    // No idea why it does this twice.
+    write_to_lcd_P(PSTR("{SYS:STARTED}\r\n"));
+    update_usb_status(true);
   }
 
-  #if ENABLED(SDSUPPORT)
-    // The way last printing status works is simple:
-    // The UI needs to see at least one TQ which is not 100%
-    // and then when the print is complete, one which is.
-    static uint8_t last_percent_done = 100;
-
-    // If there was a print in progress, we need to emit the final
-    // print status as {TQ:100}. Reset last percent done so a new print will
-    // issue a percent of 0.
-    const uint8_t percent_done = IS_SD_PRINTING() ? card.percentDone() : last_printing_status ? 100 : 0;
-    if (percent_done != last_percent_done) {
-      char message_buffer[10];
-      sprintf_P(message_buffer, PSTR("{TQ:%03i}"), percent_done);
-      write_to_lcd(message_buffer);
-      last_percent_done = percent_done;
-      last_printing_status = IS_SD_PRINTING();
+  void onIdle() {
+    /**
+     * - from printer on startup:
+     * {SYS:STARTED}{VER:29}{SYS:STARTED}{R:UD}
+     * The optimize attribute fixes a register Compile
+     * error for amtel.
+     */
+    static char inbound_buffer[MAX_CURLY_COMMAND];
+
+    // First report USB status.
+    update_usb_status(false);
+
+    // now drain commands...
+    while (LCD_SERIAL.available()) {
+      const byte b = (byte)LCD_SERIAL.read() & 0x7F;
+      inbound_buffer[inbound_count++] = b;
+      if (b == '}' || inbound_count == sizeof(inbound_buffer) - 1) {
+        inbound_buffer[inbound_count - 1] = '\0';
+        process_lcd_command(inbound_buffer);
+        inbound_count = 0;
+        inbound_buffer[0] = 0;
+      }
     }
-  #endif
-}
 
-/**
- * The Malyan LCD actually runs as a separate MCU on Serial 1.
- * This code's job is to siphon the weird curly-brace commands from
- * it and translate into gcode, which then gets injected into
- * the command queue where possible.
- */
-void MarlinUI::init() {
-  inbound_count = 0;
-  LCD_SERIAL.begin(500000);
+    #if ENABLED(SDSUPPORT)
+      // The way last printing status works is simple:
+      // The UI needs to see at least one TQ which is not 100%
+      // and then when the print is complete, one which is.
+      static uint8_t last_percent_done = 100;
+
+      // If there was a print in progress, we need to emit the final
+      // print status as {TQ:100}. Reset last percent done so a new print will
+      // issue a percent of 0.
+      const uint8_t percent_done = IS_SD_PRINTING() ? card.percentDone() : last_printing_status ? 100 : 0;
+      if (percent_done != last_percent_done) {
+        char message_buffer[10];
+        sprintf_P(message_buffer, PSTR("{TQ:%03i}"), percent_done);
+        write_to_lcd(message_buffer);
+        last_percent_done = percent_done;
+        last_printing_status = IS_SD_PRINTING();
+      }
+    #endif
+  }
 
-  // Signal init
-  write_to_lcd_P(PSTR("{SYS:STARTED}\r\n"));
+  void onPrinterKilled(PGM_P const msg) {}
+  void onMediaInserted() {};
+  void onMediaError() {};
+  void onMediaRemoved() {};
+  void onPlayTone(const uint16_t frequency, const uint16_t duration) {}
+  void onPrintTimerStarted() {}
+  void onPrintTimerPaused() {}
+  void onPrintTimerStopped() {}
+  void onFilamentRunout() {}
+  void onUserConfirmRequired(const char * const msg) {}
+  void onStatusChanged(const char * const msg) {
+    write_to_lcd_P(PSTR("{E:"));
+    write_to_lcd(msg);
+    write_to_lcd_P("}");
+  }
+  void onFactoryReset() {}
 
-  // send a version that says "unsupported"
-  write_to_lcd_P(PSTR("{VER:99}\r\n"));
+  void onStoreSettings(char *buff) {
+    // This is called when saving to EEPROM (i.e. M500). If the ExtUI needs
+    // permanent data to be stored, it can write up to eeprom_data_size bytes
+    // into buff.
 
-  // No idea why it does this twice.
-  write_to_lcd_P(PSTR("{SYS:STARTED}\r\n"));
-  update_usb_status(true);
-}
+    // Example:
+    //  static_assert(sizeof(myDataStruct) <= ExtUI::eeprom_data_size);
+    //  memcpy(buff, &myDataStruct, sizeof(myDataStruct));
+  }
 
-/**
- * Set an alert.
- */
-void MarlinUI::set_alert_status_P(PGM_P const message) {
-  write_to_lcd_P(PSTR("{E:"));
-  write_to_lcd_P(message);
-  write_to_lcd_P("}");
+  void onLoadSettings(const char *buff) {
+    // This is called while loading settings from EEPROM. If the ExtUI
+    // needs to retrieve data, it should copy up to eeprom_data_size bytes
+    // from buff
+
+    // Example:
+    //  static_assert(sizeof(myDataStruct) <= ExtUI::eeprom_data_size);
+    //  memcpy(&myDataStruct, buff, sizeof(myDataStruct));
+  }
+
+  void onConfigurationStoreWritten(bool success) {
+    // This is called after the entire EEPROM has been written,
+    // whether successful or not.
+  }
+
+  void onConfigurationStoreRead(bool success) {
+    // This is called after the entire EEPROM has been read,
+    // whether successful or not.
+  }
 }
 
 #endif // MALYAN_LCD
diff --git a/Marlin/src/lcd/menu/menu_backlash.cpp b/Marlin/src/lcd/menu/menu_backlash.cpp
index 1b183e10632aba421a999835d0554f7207f8c2b5..9c512303b745d70438bf646bbcecdd43e7f0c6ae 100644
--- a/Marlin/src/lcd/menu/menu_backlash.cpp
+++ b/Marlin/src/lcd/menu/menu_backlash.cpp
@@ -30,26 +30,21 @@
 
 #include "menu.h"
 
-extern float backlash_distance_mm[XYZ];
-extern uint8_t backlash_correction;
-
-#ifdef BACKLASH_SMOOTHING_MM
-  extern float backlash_smoothing_mm;
-#endif
+#include "../../feature/backlash.h"
 
 void menu_backlash() {
   START_MENU();
   MENU_BACK(MSG_MAIN);
 
-  MENU_MULTIPLIER_ITEM_EDIT(percent, MSG_BACKLASH_CORRECTION, &backlash_correction, all_off, all_on);
+  MENU_MULTIPLIER_ITEM_EDIT(percent, MSG_BACKLASH_CORRECTION, &backlash.correction, all_off, all_on);
 
-  #define EDIT_BACKLASH_DISTANCE(N) MENU_MULTIPLIER_ITEM_EDIT(float43, MSG_##N, &backlash_distance_mm[_AXIS(N)], 0.0f, 9.9f);
+  #define EDIT_BACKLASH_DISTANCE(N) MENU_MULTIPLIER_ITEM_EDIT(float43, MSG_##N, &backlash.distance_mm[_AXIS(N)], 0.0f, 9.9f);
   EDIT_BACKLASH_DISTANCE(A);
   EDIT_BACKLASH_DISTANCE(B);
   EDIT_BACKLASH_DISTANCE(C);
 
   #ifdef BACKLASH_SMOOTHING_MM
-    MENU_MULTIPLIER_ITEM_EDIT(float43, MSG_BACKLASH_SMOOTHING, &backlash_smoothing_mm, 0.0f, 9.9f);
+    MENU_MULTIPLIER_ITEM_EDIT(float43, MSG_BACKLASH_SMOOTHING, &backlash.smoothing_mm, 0.0f, 9.9f);
   #endif
 
   END_MENU();
diff --git a/Marlin/src/lcd/ultralcd.cpp b/Marlin/src/lcd/ultralcd.cpp
index 3fd483a79f5ca0ff2437e4a37ff32d954c0384cd..2208b6b1bf6f3d23b6ed7344d56324ea892d109a 100644
--- a/Marlin/src/lcd/ultralcd.cpp
+++ b/Marlin/src/lcd/ultralcd.cpp
@@ -23,7 +23,7 @@
 #include "../inc/MarlinConfigPre.h"
 
 // These displays all share the MarlinUI class
-#if HAS_SPI_LCD || EITHER(MALYAN_LCD, EXTENSIBLE_UI)
+#if HAS_SPI_LCD || ENABLED(EXTENSIBLE_UI)
   #include "ultralcd.h"
   #include "fontutils.h"
   MarlinUI ui;
diff --git a/Marlin/src/module/configuration_store.cpp b/Marlin/src/module/configuration_store.cpp
index ceab71381332ba9080747213054f068437d83dbb..63d95e6b5910f5c9d348c6ca0490065d69d82516 100644
--- a/Marlin/src/module/configuration_store.cpp
+++ b/Marlin/src/module/configuration_store.cpp
@@ -37,7 +37,7 @@
  */
 
 // Change EEPROM version if the structure changes
-#define EEPROM_VERSION "V65"
+#define EEPROM_VERSION "V66"
 #define EEPROM_OFFSET 100
 
 // Check the integrity of data offsets.
@@ -90,10 +90,16 @@
 
 #include "../feature/pause.h"
 
+#if ENABLED(BACKLASH_COMPENSATION)
+  #include "../feature/backlash.h"
+#endif
+
 #if HAS_FILAMENT_SENSOR
   #include "../feature/runout.h"
 #endif
 
+#include "../lcd/extensible_ui/ui_api.h"
+
 #if ENABLED(EXTRA_LIN_ADVANCE_K)
 extern float saved_extruder_advance_K[EXTRUDERS];
 #endif
@@ -149,6 +155,7 @@ typedef struct SettingsDataStruct {
   // FILAMENT_RUNOUT_SENSOR
   //
   bool runout_sensor_enabled;                           // M412 S
+  float runout_distance_mm;                             // M412 D
 
   //
   // ENABLE_LEVELING_FADE_HEIGHT
@@ -298,6 +305,21 @@ typedef struct SettingsDataStruct {
     toolchange_settings_t toolchange_settings;          // M217 S P R
   #endif
 
+  //
+  // BACKLASH_COMPENSATION
+  //
+  float backlash_distance_mm[XYZ];                      // M425 X Y Z
+  uint8_t backlash_correction;                          // M425 F
+  float backlash_smoothing_mm;                          // M425 S
+
+  //
+  // EXTENSIBLE_UI
+  //
+  #if ENABLED(EXTENSIBLE_UI)
+    // This is a significant hardware change; don't reserve space when not present
+    uint8_t extui_data[ExtUI::eeprom_data_size];
+  #endif
+
 } SettingsData;
 
 //static_assert(sizeof(SettingsData) <= E2END + 1, "EEPROM too small to contain SettingsData!");
@@ -372,6 +394,16 @@ void MarlinSettings::postprocess() {
     report_current_position();
 }
 
+#if ENABLED(PRINTCOUNTER) && ENABLED(EEPROM_SETTINGS)
+  #include "printcounter.h"
+
+  static_assert(
+    !WITHIN(STATS_EEPROM_ADDRESS, EEPROM_OFFSET, EEPROM_OFFSET + sizeof(SettingsData)) &&
+    !WITHIN(STATS_EEPROM_ADDRESS + sizeof(printStatistics), EEPROM_OFFSET, EEPROM_OFFSET + sizeof(SettingsData)),
+    "STATS_EEPROM_ADDRESS collides with EEPROM settings storage."
+  );
+#endif
+
 #if ENABLED(SD_FIRMWARE_UPDATE)
 
   #if ENABLED(EEPROM_SETTINGS)
@@ -528,11 +560,18 @@ void MarlinSettings::postprocess() {
     //
     {
       #if HAS_FILAMENT_SENSOR
-        EEPROM_WRITE(runout.enabled);
+        const bool &runout_sensor_enabled = runout.enabled;
       #else
-        const bool runout_sensor_enabled = true;
-        EEPROM_WRITE(runout_sensor_enabled);
+        const bool runout_sensor_enabled = false;
       #endif
+      #if HAS_FILAMENT_SENSOR && defined(FILAMENT_RUNOUT_DISTANCE_MM)
+        const float &runout_distance_mm = runout.runout_distance();
+      #else
+        const float runout_distance_mm = 0;
+      #endif
+      _FIELD_TEST(runout_sensor_enabled);
+      EEPROM_WRITE(runout_sensor_enabled);
+      EEPROM_WRITE(runout_distance_mm);
     }
 
     //
@@ -1118,6 +1157,42 @@ void MarlinSettings::postprocess() {
       EEPROM_WRITE(toolchange_settings);
     #endif
 
+    //
+    // Backlash Compensation
+    //
+    {
+      #if ENABLED(BACKLASH_COMPENSATION)
+        const float   (&backlash_distance_mm)[XYZ] = backlash.distance_mm;
+        const uint8_t &backlash_correction         = backlash.correction;
+      #else
+        const float    backlash_distance_mm[XYZ]   = { 0 };
+        const uint8_t  backlash_correction         = 0;
+      #endif
+      #ifdef BACKLASH_SMOOTHING_MM
+        const float   &backlash_smoothing_mm       = backlash.smoothing_mm;
+      #else
+        const float    backlash_smoothing_mm       = 3;
+      #endif
+      _FIELD_TEST(backlash_distance_mm);
+      EEPROM_WRITE(backlash_distance_mm[X_AXIS]);
+      EEPROM_WRITE(backlash_distance_mm[Y_AXIS]);
+      EEPROM_WRITE(backlash_distance_mm[Z_AXIS]);
+      EEPROM_WRITE(backlash_correction);
+      EEPROM_WRITE(backlash_smoothing_mm);
+    }
+
+    //
+    // Extensible UI User Data
+    //
+    #if ENABLED(EXTENSIBLE_UI)
+      {
+        char extui_data[ExtUI::eeprom_data_size] = { 0 };
+        ExtUI::onStoreSettings(extui_data);
+        _FIELD_TEST(extui_data);
+        EEPROM_WRITE(extui_data);
+      }
+    #endif
+
     //
     // Validate CRC and Data Size
     //
@@ -1148,7 +1223,7 @@ void MarlinSettings::postprocess() {
     #endif
 
     #if ENABLED(EXTENSIBLE_UI)
-      if (!eeprom_error) ExtUI::onStoreSettings();
+      ExtUI::onConfigurationStoreWritten(!eeprom_error);
     #endif
 
     return !eeprom_error;
@@ -1264,12 +1339,18 @@ void MarlinSettings::postprocess() {
       // Filament Runout Sensor
       //
       {
-        _FIELD_TEST(runout_sensor_enabled);
         #if HAS_FILAMENT_SENSOR
-          EEPROM_READ(runout.enabled);
+          bool &runout_sensor_enabled = runout.enabled;
         #else
           bool runout_sensor_enabled;
-          EEPROM_READ(runout_sensor_enabled);
+        #endif
+        _FIELD_TEST(runout_sensor_enabled);
+        EEPROM_READ(runout_sensor_enabled);
+
+        float runout_distance_mm;
+        EEPROM_READ(runout_distance_mm);
+        #if HAS_FILAMENT_SENSOR && defined(FILAMENT_RUNOUT_DISTANCE_MM)
+          runout.set_runout_distance(runout_distance_mm);
         #endif
       }
 
@@ -1851,6 +1932,44 @@ void MarlinSettings::postprocess() {
         EEPROM_READ(toolchange_settings);
       #endif
 
+      //
+      // Backlash Compensation
+      //
+      {
+        #if ENABLED(BACKLASH_COMPENSATION)
+          float   (&backlash_distance_mm)[XYZ] = backlash.distance_mm;
+          uint8_t &backlash_correction         = backlash.correction;
+        #else
+          float   backlash_distance_mm[XYZ];
+          uint8_t backlash_correction;
+        #endif
+        #ifdef BACKLASH_SMOOTHING_MM
+          float &backlash_smoothing_mm = backlash.smoothing_mm;
+        #else
+          float  backlash_smoothing_mm;
+        #endif
+        _FIELD_TEST(backlash_distance_mm);
+        EEPROM_READ(backlash_distance_mm[X_AXIS]);
+        EEPROM_READ(backlash_distance_mm[Y_AXIS]);
+        EEPROM_READ(backlash_distance_mm[Z_AXIS]);
+        EEPROM_READ(backlash_correction);
+        EEPROM_READ(backlash_smoothing_mm);
+      }
+
+      //
+      // Extensible UI User Data
+      //
+      #if ENABLED(EXTENSIBLE_UI)
+        // This is a significant hardware change; don't reserve EEPROM space when not present
+        {
+          const char extui_data[ExtUI::eeprom_data_size] = { 0 };
+          _FIELD_TEST(extui_data);
+          EEPROM_READ(extui_data);
+          if(!validating)
+            ExtUI::onLoadSettings(extui_data);
+        }
+      #endif
+
       eeprom_error = size_error(eeprom_index - (EEPROM_OFFSET));
       if (eeprom_error) {
         DEBUG_ECHO_START();
@@ -1921,7 +2040,7 @@ void MarlinSettings::postprocess() {
     if (validate()) {
       const bool success = _load();
       #if ENABLED(EXTENSIBLE_UI)
-        if (success) ExtUI::onLoadSettings();
+        ExtUI::onConfigurationStoreRead(success);
       #endif
       return success;
     }
@@ -2090,6 +2209,9 @@ void MarlinSettings::reset() {
   #if HAS_FILAMENT_SENSOR
     runout.enabled = true;
     runout.reset();
+    #ifdef FILAMENT_RUNOUT_DISTANCE_MM
+      runout.set_runout_distance(FILAMENT_RUNOUT_DISTANCE_MM);
+    #endif
   #endif
 
   //
@@ -2108,6 +2230,23 @@ void MarlinSettings::reset() {
     toolchange_settings.z_raise = TOOLCHANGE_ZRAISE;
   #endif
 
+  #if ENABLED(BACKLASH_GCODE)
+    backlash.correction = (BACKLASH_CORRECTION) * 255;
+    #ifdef BACKLASH_DISTANCE_MM
+      constexpr float tmp[XYZ] = BACKLASH_DISTANCE_MM;
+      backlash.distance_mm[X_AXIS] = tmp[X_AXIS];
+      backlash.distance_mm[Y_AXIS] = tmp[Y_AXIS];
+      backlash.distance_mm[Z_AXIS] = tmp[Z_AXIS];
+    #endif
+    #ifdef BACKLASH_SMOOTHING_MM
+      backlash.smoothing_mm = BACKLASH_SMOOTHING_MM;
+    #endif
+  #endif
+
+  #if ENABLED(EXTENSIBLE_UI)
+    ExtUI::onFactoryReset();
+  #endif
+
   //
   // Magnetic Parking Extruder
   //
@@ -3200,6 +3339,31 @@ void MarlinSettings::reset() {
       CONFIG_ECHO_START();
       M217_report(true);
     #endif
+
+    #if ENABLED(BACKLASH_GCODE)
+      CONFIG_ECHO_HEADING("Backlash compensation:");
+      CONFIG_ECHO_START();
+      SERIAL_ECHOLNPAIR(
+        "  M425 F", backlash.get_correction(),
+        " X", LINEAR_UNIT(backlash.distance_mm[X_AXIS]),
+        " Y", LINEAR_UNIT(backlash.distance_mm[Y_AXIS]),
+        " Z", LINEAR_UNIT(backlash.distance_mm[Z_AXIS])
+        #ifdef BACKLASH_SMOOTHING_MM
+          , " S", LINEAR_UNIT(backlash.smoothing_mm)
+        #endif
+      );
+    #endif
+
+    #if HAS_FILAMENT_SENSOR
+      CONFIG_ECHO_HEADING("Filament runout sensor:");
+      CONFIG_ECHO_START();
+      SERIAL_ECHOLNPAIR(
+        "  M412 S", int(runout.enabled)
+        #ifdef FILAMENT_RUNOUT_DISTANCE_MM
+          , " D", LINEAR_UNIT(runout.runout_distance())
+        #endif
+      );
+    #endif
   }
 
 #endif // !DISABLE_M503
diff --git a/Marlin/src/module/planner.cpp b/Marlin/src/module/planner.cpp
index 5073d903da36e79e128d0122627f0eea45798127..9c8a6f078fc684d96a895c10c2ed511809705e21 100644
--- a/Marlin/src/module/planner.cpp
+++ b/Marlin/src/module/planner.cpp
@@ -92,6 +92,10 @@
   #include "../feature/power.h"
 #endif
 
+#if ENABLED(BACKLASH_COMPENSATION)
+  #include "../feature/backlash.h"
+#endif
+
 // Delay for delivery of first block to the stepper ISR, if the queue contains 2 or
 // fewer movements. The delay is measured in milliseconds, and must be less than 250ms
 #define BLOCK_DELAY_FOR_1ST_MOVE 100
@@ -1560,94 +1564,6 @@ void Planner::synchronize() {
   ) idle();
 }
 
-/**
- * The following implements axis backlash correction. To minimize seams
- * on the printed part, the backlash correction only adds steps to the
- * current segment (instead of creating a new segment, which causes
- * discontinuities and print artifacts).
- *
- * When BACKLASH_SMOOTHING_MM is enabled and non-zero, the backlash
- * correction is spread over multiple segments, smoothing out print
- * artifacts even more.
- */
-#if ENABLED(BACKLASH_COMPENSATION)
-  #if ENABLED(BACKLASH_GCODE)
-    extern float backlash_distance_mm[];
-    extern uint8_t backlash_correction;
-    #ifdef BACKLASH_SMOOTHING_MM
-      extern float backlash_smoothing_mm;
-    #endif
-  #else
-    constexpr float backlash_distance_mm[XYZ] = BACKLASH_DISTANCE_MM,
-    constexpr uint8_t backlash_correction = BACKLASH_CORRECTION * 255;
-    #ifdef BACKLASH_SMOOTHING_MM
-      constexpr float backlash_smoothing_mm = BACKLASH_SMOOTHING_MM;
-    #endif
-  #endif
-
-  void Planner::add_backlash_correction_steps(const int32_t da, const int32_t db, const int32_t dc, const uint8_t dm, block_t * const block) {
-    static uint8_t last_direction_bits;
-    uint8_t changed_dir = last_direction_bits ^ dm;
-    // Ignore direction change if no steps are taken in that direction
-    if (da == 0) CBI(changed_dir, X_AXIS);
-    if (db == 0) CBI(changed_dir, Y_AXIS);
-    if (dc == 0) CBI(changed_dir, Z_AXIS);
-    last_direction_bits ^= changed_dir;
-
-    if (backlash_correction == 0) return;
-
-    #ifdef BACKLASH_SMOOTHING_MM
-      // The segment proportion is a value greater than 0.0 indicating how much residual_error
-      // is corrected for in this segment. The contribution is based on segment length and the
-      // smoothing distance. Since the computation of this proportion involves a floating point
-      // division, defer computation until needed.
-      float segment_proportion = 0;
-
-      // Residual error carried forward across multiple segments, so correction can be applied
-      // to segments where there is no direction change.
-      static int32_t residual_error[XYZ] = { 0 };
-    #else
-      // No leftover residual error from segment to segment
-      int32_t residual_error[XYZ] = { 0 };
-      // No direction change, no correction.
-      if (!changed_dir) return;
-    #endif
-
-    const float f_corr = float(backlash_correction) / 255.0f;
-
-    LOOP_XYZ(axis) {
-      if (backlash_distance_mm[axis]) {
-        const bool reversing = TEST(dm,axis);
-
-        // When an axis changes direction, add axis backlash to the residual error
-        if (TEST(changed_dir, axis))
-          residual_error[axis] += (reversing ? -f_corr : f_corr) * backlash_distance_mm[axis] * planner.settings.axis_steps_per_mm[axis];
-
-        // Decide how much of the residual error to correct in this segment
-        int32_t error_correction = residual_error[axis];
-        #ifdef BACKLASH_SMOOTHING_MM
-          if (error_correction && backlash_smoothing_mm != 0) {
-            // Take up a portion of the residual_error in this segment, but only when
-            // the current segment travels in the same direction as the correction
-            if (reversing == (error_correction < 0)) {
-              if (segment_proportion == 0)
-                segment_proportion = MIN(1.0f, block->millimeters / backlash_smoothing_mm);
-              error_correction = ceil(segment_proportion * error_correction);
-            }
-            else
-              error_correction = 0; // Don't take up any backlash in this segment, as it would subtract steps
-          }
-        #endif
-        // Making a correction reduces the residual error and modifies delta_mm
-        if (error_correction) {
-          block->steps[axis] += ABS(error_correction);
-          residual_error[axis] -= error_correction;
-        }
-      }
-    }
-  }
-#endif // BACKLASH_COMPENSATION
-
 /**
  * Planner::_buffer_steps
  *
@@ -1919,7 +1835,7 @@ bool Planner::_populate_block(block_t * const block, bool split_move,
      * should *never* remove steps!
      */
     #if ENABLED(BACKLASH_COMPENSATION)
-      add_backlash_correction_steps(da, db, dc, dm, block);
+      backlash.add_correction_steps(da, db, dc, dm, block);
     #endif
   }
 
diff --git a/Marlin/src/module/planner.h b/Marlin/src/module/planner.h
index a95c3f26054a06fbf0677568c69237e90c706994..5e24ce613925a274303f64ab144df7efe20220df 100644
--- a/Marlin/src/module/planner.h
+++ b/Marlin/src/module/planner.h
@@ -338,10 +338,6 @@ class Planner {
       volatile static uint32_t block_buffer_runtime_us; //Theoretical block buffer runtime in µs
     #endif
 
-    #if ENABLED(BACKLASH_COMPENSATION)
-      static void add_backlash_correction_steps(const int32_t da, const int32_t db, const int32_t dc, const uint8_t dm, block_t * const block);
-    #endif
-
   public:
 
     /**
diff --git a/Marlin/src/module/printcounter.cpp b/Marlin/src/module/printcounter.cpp
index 4fa3e501f399d958d70ba1e36922a2eae3f3f872..51af80afcf72fb4ff03cb410897e56c0cf7daa05 100644
--- a/Marlin/src/module/printcounter.cpp
+++ b/Marlin/src/module/printcounter.cpp
@@ -29,6 +29,10 @@ Stopwatch print_job_timer;      // Global Print Job Timer instance
 
 #else // PRINTCOUNTER
 
+#if ENABLED(EXTENSIBLE_UI)
+  #include "../lcd/extensible_ui/ui_api.h"
+#endif
+
 #include "printcounter.h"
 #include "../Marlin.h"
 #include "../HAL/shared/persistent_store_api.h"
@@ -169,6 +173,10 @@ void PrintCounter::saveStats() {
   persistentStore.access_start();
   persistentStore.write_data(address + sizeof(uint8_t), (uint8_t*)&data, sizeof(printStatistics));
   persistentStore.access_finish();
+
+  #if ENABLED(EXTENSIBLE_UI)
+    ExtUI::onConfigurationStoreWritten(true);
+  #endif
 }
 
 #if HAS_SERVICE_INTERVALS
diff --git a/Marlin/src/module/probe.cpp b/Marlin/src/module/probe.cpp
index 8988b15bea4571a14f0f4712fca0b13cc4a8f140..39b6c8e8cfa325bf463e858307fa2c7a2c360059 100644
--- a/Marlin/src/module/probe.cpp
+++ b/Marlin/src/module/probe.cpp
@@ -54,6 +54,10 @@
   #include "planner.h"
 #endif
 
+#if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
+  #include "../feature/backlash.h"
+#endif
+
 float zprobe_zoffset; // Initialized by settings.load()
 
 #if ENABLED(BLTOUCH)
@@ -463,30 +467,6 @@ bool set_probe_deployed(const bool deploy) {
   }
 #endif
 
-#if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
-  #if USES_Z_MIN_PROBE_ENDSTOP
-    #define TEST_PROBE_PIN (READ(Z_MIN_PROBE_PIN) != Z_MIN_PROBE_ENDSTOP_INVERTING)
-  #else
-    #define TEST_PROBE_PIN (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING)
-  #endif
-
-  extern float backlash_measured_mm[];
-  extern uint8_t backlash_measured_num[];
-
-  /* Measure Z backlash by raising nozzle in increments until probe deactivates */
-  static void measure_backlash_with_probe() {
-    if (backlash_measured_num[Z_AXIS] == 255) return;
-
-    float start_height = current_position[Z_AXIS];
-    while (current_position[Z_AXIS] < (start_height + BACKLASH_MEASUREMENT_LIMIT) && TEST_PROBE_PIN)
-      do_blocking_move_to_z(current_position[Z_AXIS] + BACKLASH_MEASUREMENT_RESOLUTION, MMM_TO_MMS(BACKLASH_MEASUREMENT_FEEDRATE));
-
-    // The backlash from all probe points is averaged, so count the number of measurements
-    backlash_measured_mm[Z_AXIS] += current_position[Z_AXIS] - start_height;
-    backlash_measured_num[Z_AXIS]++;
-  }
-#endif
-
 /**
  * @brief Used by run_z_probe to do a single Z probe move.
  *
@@ -643,7 +623,7 @@ static float run_z_probe() {
       }
 
       #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
-        measure_backlash_with_probe();
+        backlash.measure_with_probe();
       #endif
 
   #if MULTIPLE_PROBING > 2
diff --git a/Marlin/src/module/stepper.cpp b/Marlin/src/module/stepper.cpp
index ce3141c12ff3bf48c0035952a0282350cb1dbd8c..eb4957ec2e81c7c33c8541986637053b04594056 100644
--- a/Marlin/src/module/stepper.cpp
+++ b/Marlin/src/module/stepper.cpp
@@ -113,7 +113,7 @@ Stepper stepper; // Singleton
   #include "../feature/mixing.h"
 #endif
 
-#if FILAMENT_RUNOUT_DISTANCE_MM > 0
+#ifdef FILAMENT_RUNOUT_DISTANCE_MM
   #include "../feature/runout.h"
 #endif
 
@@ -1537,7 +1537,7 @@ uint32_t Stepper::stepper_block_phase_isr() {
 
     // If current block is finished, reset pointer
     if (step_events_completed >= step_event_count) {
-      #if FILAMENT_RUNOUT_DISTANCE_MM > 0
+      #ifdef FILAMENT_RUNOUT_DISTANCE_MM
         runout.block_completed(current_block);
       #endif
       axis_did_move = 0;