From 41a51e952792653059019e9e6064f6cd7a0beadb Mon Sep 17 00:00:00 2001
From: tombrazier <68918209+tombrazier@users.noreply.github.com>
Date: Tue, 1 Mar 2022 22:14:52 +0000
Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix=20backlash=20applied=20steps?=
 =?UTF-8?q?=20when=20config=20changes=20(#23826)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Followup to #23814
---
 Marlin/src/feature/backlash.cpp       | 76 ++++++++++++++++-----------
 Marlin/src/feature/backlash.h         | 46 ++++++++++------
 Marlin/src/gcode/calibrate/G425.cpp   | 60 +++++++++++++--------
 Marlin/src/gcode/calibrate/M425.cpp   | 24 ++++-----
 Marlin/src/lcd/extui/ui_api.cpp       | 12 ++---
 Marlin/src/lcd/menu/menu_backlash.cpp | 13 +++--
 Marlin/src/module/planner.cpp         | 16 +++---
 Marlin/src/module/settings.cpp        | 37 ++++++-------
 8 files changed, 167 insertions(+), 117 deletions(-)

diff --git a/Marlin/src/feature/backlash.cpp b/Marlin/src/feature/backlash.cpp
index 23458cf86cb..84382cf8568 100644
--- a/Marlin/src/feature/backlash.cpp
+++ b/Marlin/src/feature/backlash.cpp
@@ -30,9 +30,7 @@
 #include "../module/planner.h"
 
 axis_bits_t Backlash::last_direction_bits;
-#ifdef BACKLASH_SMOOTHING_MM
-  xyz_long_t Backlash::residual_error{0};
-#endif
+xyz_long_t Backlash::residual_error{0};
 
 #ifdef BACKLASH_DISTANCE_MM
   #if ENABLED(BACKLASH_GCODE)
@@ -43,7 +41,7 @@ axis_bits_t Backlash::last_direction_bits;
 #endif
 
 #if ENABLED(BACKLASH_GCODE)
-  uint8_t Backlash::correction = (BACKLASH_CORRECTION) * 0xFF;
+  uint8_t Backlash::correction = (BACKLASH_CORRECTION) * all_on;
   #ifdef BACKLASH_SMOOTHING_MM
     float Backlash::smoothing_mm = BACKLASH_SMOOTHING_MM;
   #endif
@@ -87,7 +85,7 @@ void Backlash::add_correction_steps(const int32_t &da, const int32_t &db, const
   #endif
   last_direction_bits ^= changed_dir;
 
-  if (correction == 0) return;
+  if (!correction && !residual_error) return;
 
   #ifdef BACKLASH_SMOOTHING_MM
     // The segment proportion is a value greater than 0.0 indicating how much residual_error
@@ -95,35 +93,28 @@ void Backlash::add_correction_steps(const int32_t &da, const int32_t &db, const
     // smoothing distance. Since the computation of this proportion involves a floating point
     // division, defer computation until needed.
     float segment_proportion = 0;
-  #else
-    // No direction change, no correction.
-    if (!changed_dir) return;
-    // No leftover residual error from segment to segment
-    xyz_long_t residual_error{0};
   #endif
 
-  const float f_corr = float(correction) / 255.0f;
+  const float f_corr = float(correction) / all_on;
 
   LOOP_LINEAR_AXES(axis) {
     if (distance_mm[axis]) {
-      const bool reversing = TEST(dm,axis);
+      const bool reverse = 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];
+        residual_error[axis] += (reverse ? -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];
+      if (reverse != (error_correction < 0))
+        error_correction = 0; // Don't take up any backlash in this segment, as it would subtract steps
+
       #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
+          // Take up a portion of the residual_error in this segment
+          if (segment_proportion == 0) segment_proportion = _MIN(1.0f, block->millimeters / smoothing_mm);
+          error_correction = CEIL(segment_proportion * error_correction);
         }
       #endif
 
@@ -153,27 +144,52 @@ void Backlash::add_correction_steps(const int32_t &da, const int32_t &db, const
   }
 }
 
-int32_t Backlash::applied_steps(const AxisEnum axis) {
+int32_t Backlash::get_applied_steps(const AxisEnum axis) {
   if (axis >= LINEAR_AXES) return 0;
 
-  const bool reversing = TEST(last_direction_bits, axis);
+  const bool reverse = TEST(last_direction_bits, axis);
 
-  #ifdef BACKLASH_SMOOTHING_MM
-    const int32_t residual_error_axis = residual_error[axis];
-  #else
-    constexpr int32_t residual_error_axis = 0;
-  #endif
+  const int32_t residual_error_axis = residual_error[axis];
 
   // At startup it is assumed the last move was forwards. So the applied
   // steps will always be a non-positive number.
 
-  if (!reversing) return -residual_error_axis;
+  if (!reverse) return -residual_error_axis;
 
-  const float f_corr = float(correction) / 255.0f;
+  const float f_corr = float(correction) / all_on;
   const int32_t full_error_axis = -f_corr * distance_mm[axis] * planner.settings.axis_steps_per_mm[axis];
   return full_error_axis - residual_error_axis;
 }
 
+class Backlash::StepAdjuster {
+  xyz_long_t applied_steps;
+public:
+  StepAdjuster() {
+    LOOP_LINEAR_AXES(axis) applied_steps[axis] = backlash.get_applied_steps((AxisEnum)axis);
+  }
+  ~StepAdjuster() {
+    // after backlash compensation parameter changes, ensure applied step count does not change
+    LOOP_LINEAR_AXES(axis) residual_error[axis] += backlash.get_applied_steps((AxisEnum)axis) - applied_steps[axis];
+  }
+};
+
+void Backlash::set_correction_uint8(const uint8_t v) {
+  StepAdjuster adjuster;
+  correction = v;
+}
+
+void Backlash::set_distance_mm(const AxisEnum axis, const float v) {
+  StepAdjuster adjuster;
+  distance_mm[axis] = v;
+}
+
+#ifdef BACKLASH_SMOOTHING_MM
+  void Backlash::set_smoothing_mm(const float v) {
+    StepAdjuster adjuster;
+    smoothing_mm = v;
+  }
+#endif
+
 #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
 
   #include "../module/probe.h"
diff --git a/Marlin/src/feature/backlash.h b/Marlin/src/feature/backlash.h
index ef29012cb14..0bace526e53 100644
--- a/Marlin/src/feature/backlash.h
+++ b/Marlin/src/feature/backlash.h
@@ -24,27 +24,22 @@
 #include "../inc/MarlinConfigPre.h"
 #include "../module/planner.h"
 
-constexpr uint8_t all_on = 0xFF, all_off = 0x00;
-
 class Backlash {
+public:
+  static constexpr uint8_t all_on = 0xFF, all_off = 0x00;
+
 private:
   static axis_bits_t last_direction_bits;
-  #ifdef BACKLASH_SMOOTHING_MM
-    static xyz_long_t residual_error;
-  #endif
+  static xyz_long_t residual_error;
 
-public:
   #if ENABLED(BACKLASH_GCODE)
-    static xyz_float_t distance_mm;
     static uint8_t correction;
+    static xyz_float_t distance_mm;
     #ifdef BACKLASH_SMOOTHING_MM
       static float smoothing_mm;
     #endif
-
-    static void set_correction(const_float_t v) { correction = _MAX(0, _MIN(1.0, v)) * all_on; }
-    static float get_correction() { return float(ui8_to_percent(correction)) / 100.0f; }
   #else
-    static constexpr uint8_t correction = (BACKLASH_CORRECTION) * 0xFF;
+    static constexpr uint8_t correction = (BACKLASH_CORRECTION) * all_on;
     static const xyz_float_t distance_mm;
     #ifdef BACKLASH_SMOOTHING_MM
       static constexpr float smoothing_mm = BACKLASH_SMOOTHING_MM;
@@ -52,13 +47,13 @@ public:
   #endif
 
   #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
-    private:
-      static xyz_float_t measured_mm;
-      static xyz_uint8_t measured_count;
-    public:
-      static void measure_with_probe();
+    static xyz_float_t measured_mm;
+    static xyz_uint8_t measured_count;
   #endif
 
+  class StepAdjuster;
+
+public:
   static float get_measurement(const AxisEnum a) {
     UNUSED(a);
     // Return the measurement averaged over all readings
@@ -78,7 +73,24 @@ public:
   }
 
   static void add_correction_steps(const int32_t &da, const int32_t &db, const int32_t &dc, const axis_bits_t dm, block_t * const block);
-  static int32_t applied_steps(const AxisEnum axis);
+  static int32_t get_applied_steps(const AxisEnum axis);
+
+  #if ENABLED(BACKLASH_GCODE)
+    static void set_correction_uint8(const uint8_t v);
+    static uint8_t get_correction_uint8() { return correction; }
+    static void set_correction(const float v) { set_correction_uint8(_MAX(0, _MIN(1.0, v)) * all_on + 0.5f); }
+    static float get_correction() { return float(get_correction_uint8()) / all_on; }
+    static void set_distance_mm(const AxisEnum axis, const float v);
+    static float get_distance_mm(const AxisEnum axis) {return distance_mm[axis];}
+    #ifdef BACKLASH_SMOOTHING_MM
+      static void set_smoothing_mm(const float v);
+      static float get_smoothing_mm() {return smoothing_mm;}
+    #endif
+  #endif
+
+  #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
+    static void measure_with_probe();
+  #endif
 };
 
 extern Backlash backlash;
diff --git a/Marlin/src/gcode/calibrate/G425.cpp b/Marlin/src/gcode/calibrate/G425.cpp
index 906f8cc4194..a2dec64bc3a 100644
--- a/Marlin/src/gcode/calibrate/G425.cpp
+++ b/Marlin/src/gcode/calibrate/G425.cpp
@@ -105,13 +105,27 @@ struct measurements_t {
 };
 
 #if ENABLED(BACKLASH_GCODE)
-  #define TEMPORARY_BACKLASH_CORRECTION(value) REMEMBER(tbst, backlash.correction, value)
+  class restorer_correction {
+    const uint8_t val_;
+  public:
+    restorer_correction(const uint8_t temp_val) : val_(backlash.get_correction_uint8()) { backlash.set_correction_uint8(temp_val); }
+    ~restorer_correction() { backlash.set_correction_uint8(val_); }
+  };
+
+  #define TEMPORARY_BACKLASH_CORRECTION(value) restorer_correction restorer_tbst(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)
+  class restorer_smoothing {
+    const float val_;
+  public:
+    restorer_smoothing(const float temp_val) : val_(backlash.get_smoothing_mm()) { backlash.set_smoothing_mm(temp_val); }
+    ~restorer_smoothing() { backlash.set_smoothing_mm(val_); }
+  };
+
+  #define TEMPORARY_BACKLASH_SMOOTHING(value) restorer_smoothing restorer_tbsm(value)
 #else
   #define TEMPORARY_BACKLASH_SMOOTHING(value)
 #endif
@@ -524,7 +538,7 @@ inline void calibrate_backlash(measurements_t &m, const float uncertainty) {
 
   {
     // New scope for TEMPORARY_BACKLASH_CORRECTION
-    TEMPORARY_BACKLASH_CORRECTION(all_off);
+    TEMPORARY_BACKLASH_CORRECTION(backlash.all_off);
     TEMPORARY_BACKLASH_SMOOTHING(0.0f);
 
     probe_sides(m, uncertainty);
@@ -532,45 +546,45 @@ inline void calibrate_backlash(measurements_t &m, const float uncertainty) {
     #if ENABLED(BACKLASH_GCODE)
 
       #if HAS_X_CENTER
-        backlash.distance_mm.x = (m.backlash[LEFT] + m.backlash[RIGHT]) / 2;
+        backlash.set_distance_mm(X_AXIS, (m.backlash[LEFT] + m.backlash[RIGHT]) / 2);
       #elif ENABLED(CALIBRATION_MEASURE_LEFT)
-        backlash.distance_mm.x = m.backlash[LEFT];
+        backlash.set_distance_mm(X_AXIS, m.backlash[LEFT]);
       #elif ENABLED(CALIBRATION_MEASURE_RIGHT)
-        backlash.distance_mm.x = m.backlash[RIGHT];
+        backlash.set_distance_mm(X_AXIS, m.backlash[RIGHT]);
       #endif
 
       #if HAS_Y_CENTER
-        backlash.distance_mm.y = (m.backlash[FRONT] + m.backlash[BACK]) / 2;
+        backlash.set_distance_mm(Y_AXIS, (m.backlash[FRONT] + m.backlash[BACK]) / 2);
       #elif ENABLED(CALIBRATION_MEASURE_FRONT)
-        backlash.distance_mm.y = m.backlash[FRONT];
+        backlash.set_distance_mm(Y_AXIS, m.backlash[FRONT]);
       #elif ENABLED(CALIBRATION_MEASURE_BACK)
-        backlash.distance_mm.y = m.backlash[BACK];
+        backlash.set_distance_mm(Y_AXIS, m.backlash[BACK]);
       #endif
 
-      TERN_(HAS_Z_AXIS, if (AXIS_CAN_CALIBRATE(Z)) backlash.distance_mm.z = m.backlash[TOP]);
+      TERN_(HAS_Z_AXIS, if (AXIS_CAN_CALIBRATE(Z)) backlash.set_distance_mm(Z_AXIS, m.backlash[TOP]));
 
       #if HAS_I_CENTER
-        backlash.distance_mm.i = (m.backlash[IMINIMUM] + m.backlash[IMAXIMUM]) / 2;
+        backlash.set_distance_mm(I_AXIS, (m.backlash[IMINIMUM] + m.backlash[IMAXIMUM]) / 2);
       #elif ENABLED(CALIBRATION_MEASURE_IMIN)
-        backlash.distance_mm.i = m.backlash[IMINIMUM];
+        backlash.set_distance_mm(I_AXIS, m.backlash[IMINIMUM]);
       #elif ENABLED(CALIBRATION_MEASURE_IMAX)
-        backlash.distance_mm.i = m.backlash[IMAXIMUM];
+        backlash.set_distance_mm(I_AXIS, m.backlash[IMAXIMUM]);
       #endif
 
       #if HAS_J_CENTER
-        backlash.distance_mm.j = (m.backlash[JMINIMUM] + m.backlash[JMAXIMUM]) / 2;
+        backlash.set_distance_mm(J_AXIS, (m.backlash[JMINIMUM] + m.backlash[JMAXIMUM]) / 2);
       #elif ENABLED(CALIBRATION_MEASURE_JMIN)
-        backlash.distance_mm.j = m.backlash[JMINIMUM];
+        backlash.set_distance_mm(J_AXIS, m.backlash[JMINIMUM]);
       #elif ENABLED(CALIBRATION_MEASURE_JMAX)
-        backlash.distance_mm.j = m.backlash[JMAXIMUM];
+        backlash.set_distance_mm(J_AXIS, m.backlash[JMAXIMUM]);
       #endif
 
       #if HAS_K_CENTER
-        backlash.distance_mm.k = (m.backlash[KMINIMUM] + m.backlash[KMAXIMUM]) / 2;
+        backlash.set_distance_mm(K_AXIS, (m.backlash[KMINIMUM] + m.backlash[KMAXIMUM]) / 2);
       #elif ENABLED(CALIBRATION_MEASURE_KMIN)
-        backlash.distance_mm.k = m.backlash[KMINIMUM];
+        backlash.set_distance_mm(K_AXIS, m.backlash[KMINIMUM]);
       #elif ENABLED(CALIBRATION_MEASURE_KMAX)
-        backlash.distance_mm.k = m.backlash[KMAXIMUM];
+        backlash.set_distance_mm(K_AXIS, m.backlash[KMAXIMUM]);
       #endif
 
     #endif // BACKLASH_GCODE
@@ -581,7 +595,7 @@ inline void calibrate_backlash(measurements_t &m, const float uncertainty) {
     // allowed directions to take up any backlash
     {
       // New scope for TEMPORARY_BACKLASH_CORRECTION
-      TEMPORARY_BACKLASH_CORRECTION(all_on);
+      TEMPORARY_BACKLASH_CORRECTION(backlash.all_on);
       TEMPORARY_BACKLASH_SMOOTHING(0.0f);
       const xyz_float_t move = LINEAR_AXIS_ARRAY(
         AXIS_CAN_CALIBRATE(X) * 3, AXIS_CAN_CALIBRATE(Y) * 3, AXIS_CAN_CALIBRATE(Z) * 3,
@@ -611,7 +625,7 @@ inline void update_measurements(measurements_t &m, const AxisEnum axis) {
  *    - Call calibrate_backlash() beforehand for best accuracy
  */
 inline void calibrate_toolhead(measurements_t &m, const float uncertainty, const uint8_t extruder) {
-  TEMPORARY_BACKLASH_CORRECTION(all_on);
+  TEMPORARY_BACKLASH_CORRECTION(backlash.all_on);
   TEMPORARY_BACKLASH_SMOOTHING(0.0f);
 
   TERN(HAS_MULTI_HOTEND, set_nozzle(m, extruder), UNUSED(extruder));
@@ -648,7 +662,7 @@ inline void calibrate_toolhead(measurements_t &m, const float uncertainty, const
  *   uncertainty    in     - How far away from the object to begin probing
  */
 inline void calibrate_all_toolheads(measurements_t &m, const float uncertainty) {
-  TEMPORARY_BACKLASH_CORRECTION(all_on);
+  TEMPORARY_BACKLASH_CORRECTION(backlash.all_on);
   TEMPORARY_BACKLASH_SMOOTHING(0.0f);
 
   HOTEND_LOOP() calibrate_toolhead(m, uncertainty, e);
@@ -674,7 +688,7 @@ inline void calibrate_all() {
 
   TERN_(HAS_HOTEND_OFFSET, reset_hotend_offsets());
 
-  TEMPORARY_BACKLASH_CORRECTION(all_on);
+  TEMPORARY_BACKLASH_CORRECTION(backlash.all_on);
   TEMPORARY_BACKLASH_SMOOTHING(0.0f);
 
   // Do a fast and rough calibration of the toolheads
diff --git a/Marlin/src/gcode/calibrate/M425.cpp b/Marlin/src/gcode/calibrate/M425.cpp
index 2d36e0d410d..6b1f56fcf65 100644
--- a/Marlin/src/gcode/calibrate/M425.cpp
+++ b/Marlin/src/gcode/calibrate/M425.cpp
@@ -63,7 +63,7 @@ void GcodeSuite::M425() {
   LOOP_LINEAR_AXES(a) {
     if (axis_can_calibrate(a) && parser.seen(AXIS_CHAR(a))) {
       planner.synchronize();
-      backlash.distance_mm[a] = parser.has_value() ? parser.value_linear_units() : backlash.get_measurement(AxisEnum(a));
+      backlash.set_distance_mm(AxisEnum(a), parser.has_value() ? parser.value_linear_units() : backlash.get_measurement(AxisEnum(a)));
       noArgs = false;
     }
   }
@@ -77,25 +77,25 @@ void GcodeSuite::M425() {
   #ifdef BACKLASH_SMOOTHING_MM
     if (parser.seen('S')) {
       planner.synchronize();
-      backlash.smoothing_mm = parser.value_linear_units();
+      backlash.set_smoothing_mm(parser.value_linear_units());
       noArgs = false;
     }
   #endif
 
   if (noArgs) {
     SERIAL_ECHOPGM("Backlash Correction ");
-    if (!backlash.correction) SERIAL_ECHOPGM("in");
+    if (!backlash.get_correction_uint8()) SERIAL_ECHOPGM("in");
     SERIAL_ECHOLNPGM("active:");
     SERIAL_ECHOLNPGM("  Correction Amount/Fade-out:     F", backlash.get_correction(), " (F1.0 = full, F0.0 = none)");
     SERIAL_ECHOPGM("  Backlash Distance (mm):        ");
     LOOP_LINEAR_AXES(a) if (axis_can_calibrate(a)) {
       SERIAL_CHAR(' ', AXIS_CHAR(a));
-      SERIAL_ECHO(backlash.distance_mm[a]);
+      SERIAL_ECHO(backlash.get_distance_mm(AxisEnum(a)));
       SERIAL_EOL();
     }
 
     #ifdef BACKLASH_SMOOTHING_MM
-      SERIAL_ECHOLNPGM("  Smoothing (mm):                 S", backlash.smoothing_mm);
+      SERIAL_ECHOLNPGM("  Smoothing (mm):                 S", backlash.get_smoothing_mm());
     #endif
 
     #if ENABLED(MEASURE_BACKLASH_WHEN_PROBING)
@@ -118,15 +118,15 @@ void GcodeSuite::M425_report(const bool forReplay/*=true*/) {
   SERIAL_ECHOLNPGM_P(
     PSTR("  M425 F"), backlash.get_correction()
     #ifdef BACKLASH_SMOOTHING_MM
-      , PSTR(" S"), LINEAR_UNIT(backlash.smoothing_mm)
+      , PSTR(" S"), LINEAR_UNIT(backlash.get_smoothing_mm())
     #endif
     , LIST_N(DOUBLE(LINEAR_AXES),
-        SP_X_STR, LINEAR_UNIT(backlash.distance_mm.x),
-        SP_Y_STR, LINEAR_UNIT(backlash.distance_mm.y),
-        SP_Z_STR, LINEAR_UNIT(backlash.distance_mm.z),
-        SP_I_STR, LINEAR_UNIT(backlash.distance_mm.i),
-        SP_J_STR, LINEAR_UNIT(backlash.distance_mm.j),
-        SP_K_STR, LINEAR_UNIT(backlash.distance_mm.k)
+        SP_X_STR, LINEAR_UNIT(backlash.get_distance_mm(X_AXIS)),
+        SP_Y_STR, LINEAR_UNIT(backlash.get_distance_mm(Y_AXIS)),
+        SP_Z_STR, LINEAR_UNIT(backlash.get_distance_mm(Z_AXIS)),
+        SP_I_STR, LINEAR_UNIT(backlash.get_distance_mm(I_AXIS)),
+        SP_J_STR, LINEAR_UNIT(backlash.get_distance_mm(J_AXIS)),
+        SP_K_STR, LINEAR_UNIT(backlash.get_distance_mm(K_AXIS))
       )
   );
 }
diff --git a/Marlin/src/lcd/extui/ui_api.cpp b/Marlin/src/lcd/extui/ui_api.cpp
index f44e8bf7203..bfcbc39d7bf 100644
--- a/Marlin/src/lcd/extui/ui_api.cpp
+++ b/Marlin/src/lcd/extui/ui_api.cpp
@@ -861,16 +861,16 @@ namespace ExtUI {
   #endif
 
   #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.get_distance_mm((AxisEnum)axis); }
     void setAxisBacklash_mm(const_float_t value, const axis_t axis)
-                                                      { backlash.distance_mm[axis] = constrain(value,0,5); }
+                                                      { backlash.set_distance_mm((AxisEnum)axis, constrain(value,0,5)); }
 
-    float getBacklashCorrection_percent()             { return ui8_to_percent(backlash.correction); }
-    void setBacklashCorrection_percent(const_float_t value) { backlash.correction = map(constrain(value, 0, 100), 0, 100, 0, 255); }
+    float getBacklashCorrection_percent()             { return backlash.get_correction() * 100.0f; }
+    void setBacklashCorrection_percent(const_float_t value) { backlash.set_correction(constrain(value, 0, 100) / 100.0f); }
 
     #ifdef BACKLASH_SMOOTHING_MM
-      float getBacklashSmoothing_mm()                 { return backlash.smoothing_mm; }
-      void setBacklashSmoothing_mm(const_float_t value) { backlash.smoothing_mm = constrain(value, 0, 999); }
+      float getBacklashSmoothing_mm()                 { return backlash.get_smoothing_mm(); }
+      void setBacklashSmoothing_mm(const_float_t value) { backlash.set_smoothing_mm(constrain(value, 0, 999)); }
     #endif
   #endif
 
diff --git a/Marlin/src/lcd/menu/menu_backlash.cpp b/Marlin/src/lcd/menu/menu_backlash.cpp
index 5776234f72d..faed8cf7776 100644
--- a/Marlin/src/lcd/menu/menu_backlash.cpp
+++ b/Marlin/src/lcd/menu/menu_backlash.cpp
@@ -36,14 +36,20 @@ void menu_backlash() {
   START_MENU();
   BACK_ITEM(MSG_MAIN);
 
-  EDIT_ITEM_FAST(percent, MSG_BACKLASH_CORRECTION, &backlash.correction, all_off, all_on);
+  editable.uint8 = backlash.get_correction_uint8();
+  EDIT_ITEM_FAST(percent, MSG_BACKLASH_CORRECTION, &editable.uint8, backlash.all_off, backlash.all_on, []{ backlash.set_correction_uint8(editable.uint8); });
 
   #if DISABLED(CORE_BACKLASH) || EITHER(MARKFORGED_XY, MARKFORGED_YX)
     #define _CAN_CALI AXIS_CAN_CALIBRATE
   #else
     #define _CAN_CALI(A) true
   #endif
-  #define EDIT_BACKLASH_DISTANCE(N) EDIT_ITEM_FAST(float43, MSG_BACKLASH_##N, &backlash.distance_mm[_AXIS(N)], 0.0f, 9.9f);
+
+  #define EDIT_BACKLASH_DISTANCE(N) do { \
+    editable.decimal = backlash.get_distance_mm(_AXIS(N)); \
+    EDIT_ITEM_FAST(float43, MSG_BACKLASH_##N, &editable.decimal, 0.0f, 9.9f, []{ backlash.set_distance_mm(_AXIS(N), editable.decimal); }); \
+  } while (0);
+
   if (_CAN_CALI(A)) EDIT_BACKLASH_DISTANCE(A);
   #if HAS_Y_AXIS && _CAN_CALI(B)
     EDIT_BACKLASH_DISTANCE(B);
@@ -62,7 +68,8 @@ void menu_backlash() {
   #endif
 
   #ifdef BACKLASH_SMOOTHING_MM
-    EDIT_ITEM_FAST(float43, MSG_BACKLASH_SMOOTHING, &backlash.smoothing_mm, 0.0f, 9.9f);
+    editable.decimal = backlash.get_smoothing_mm();
+    EDIT_ITEM_FAST(float43, MSG_BACKLASH_SMOOTHING, &editable.decimal, 0.0f, 9.9f, []{ backlash.set_smoothing_mm(editable.decimal); });
   #endif
 
   END_MENU();
diff --git a/Marlin/src/module/planner.cpp b/Marlin/src/module/planner.cpp
index 37b264a3c55..51440aac262 100644
--- a/Marlin/src/module/planner.cpp
+++ b/Marlin/src/module/planner.cpp
@@ -1706,7 +1706,7 @@ void Planner::endstop_triggered(const AxisEnum axis) {
 }
 
 float Planner::triggered_position_mm(const AxisEnum axis) {
-  const float result = DIFF_TERN(BACKLASH_COMPENSATION, stepper.triggered_position(axis), backlash.applied_steps(axis));
+  const float result = DIFF_TERN(BACKLASH_COMPENSATION, stepper.triggered_position(axis), backlash.get_applied_steps(axis));
   return result * mm_per_step[axis];
 }
 
@@ -1729,8 +1729,8 @@ float Planner::get_axis_position_mm(const AxisEnum axis) {
       // Protect the access to the position.
       const bool was_enabled = stepper.suspend();
 
-      const int32_t p1 = DIFF_TERN(BACKLASH_COMPENSATION, stepper.position(CORE_AXIS_1), backlash.applied_steps(CORE_AXIS_1)),
-                    p2 = DIFF_TERN(BACKLASH_COMPENSATION, stepper.position(CORE_AXIS_2), backlash.applied_steps(CORE_AXIS_2));
+      const int32_t p1 = DIFF_TERN(BACKLASH_COMPENSATION, stepper.position(CORE_AXIS_1), backlash.get_applied_steps(CORE_AXIS_1)),
+                    p2 = DIFF_TERN(BACKLASH_COMPENSATION, stepper.position(CORE_AXIS_2), backlash.get_applied_steps(CORE_AXIS_2));
 
       if (was_enabled) stepper.wake_up();
 
@@ -1739,7 +1739,7 @@ float Planner::get_axis_position_mm(const AxisEnum axis) {
       axis_steps = (axis == CORE_AXIS_2 ? CORESIGN(p1 - p2) : p1 + p2) * 0.5f;
     }
     else
-      axis_steps = DIFF_TERN(BACKLASH_COMPENSATION, stepper.position(axis), backlash.applied_steps(axis));
+      axis_steps = DIFF_TERN(BACKLASH_COMPENSATION, stepper.position(axis), backlash.get_applied_steps(axis));
 
   #elif EITHER(MARKFORGED_XY, MARKFORGED_YX)
 
@@ -1756,12 +1756,12 @@ float Planner::get_axis_position_mm(const AxisEnum axis) {
       axis_steps = ((axis == CORE_AXIS_1) ? p1 - p2 : p2);
     }
     else
-      axis_steps = DIFF_TERN(BACKLASH_COMPENSATION, stepper.position(axis), backlash.applied_steps(axis));
+      axis_steps = DIFF_TERN(BACKLASH_COMPENSATION, stepper.position(axis), backlash.get_applied_steps(axis));
 
   #else
 
     axis_steps = stepper.position(axis);
-    TERN_(BACKLASH_COMPENSATION, axis_steps -= backlash.applied_steps(axis));
+    TERN_(BACKLASH_COMPENSATION, axis_steps -= backlash.get_applied_steps(axis));
 
   #endif
 
@@ -2844,7 +2844,7 @@ void Planner::buffer_sync_block(TERN_(LASER_SYNCHRONOUS_M106_M107, uint8_t sync_
 
   block->position = position;
   #if ENABLED(BACKLASH_COMPENSATION)
-    LOOP_LINEAR_AXES(axis) block->position[axis] += backlash.applied_steps((AxisEnum)axis);
+    LOOP_LINEAR_AXES(axis) block->position[axis] += backlash.get_applied_steps((AxisEnum)axis);
   #endif
 
   #if BOTH(HAS_FAN, LASER_SYNCHRONOUS_M106_M107)
@@ -3122,7 +3122,7 @@ void Planner::set_machine_position_mm(const abce_pos_t &abce) {
   else {
     #if ENABLED(BACKLASH_COMPENSATION)
       abce_long_t stepper_pos = position;
-      LOOP_LINEAR_AXES(axis) stepper_pos[axis] += backlash.applied_steps((AxisEnum)axis);
+      LOOP_LINEAR_AXES(axis) stepper_pos[axis] += backlash.get_applied_steps((AxisEnum)axis);
       stepper.set_position(stepper_pos);
     #else
       stepper.set_position(position);
diff --git a/Marlin/src/module/settings.cpp b/Marlin/src/module/settings.cpp
index 66316edbbb3..f512d87b997 100644
--- a/Marlin/src/module/settings.cpp
+++ b/Marlin/src/module/settings.cpp
@@ -1426,14 +1426,15 @@ void MarlinSettings::postprocess() {
     //
     {
       #if ENABLED(BACKLASH_GCODE)
-        const xyz_float_t &backlash_distance_mm = backlash.distance_mm;
-        const uint8_t &backlash_correction = backlash.correction;
+        xyz_float_t backlash_distance_mm;
+        LOOP_LINEAR_AXES(axis) backlash_distance_mm[axis] = backlash.get_distance_mm((AxisEnum)axis);
+        const uint8_t backlash_correction = backlash.get_correction_uint8();
       #else
         const xyz_float_t backlash_distance_mm{0};
         const uint8_t backlash_correction = 0;
       #endif
       #if ENABLED(BACKLASH_GCODE) && defined(BACKLASH_SMOOTHING_MM)
-        const float &backlash_smoothing_mm = backlash.smoothing_mm;
+        const float backlash_smoothing_mm = backlash.get_smoothing_mm();
       #else
         const float backlash_smoothing_mm = 3;
       #endif
@@ -2364,22 +2365,22 @@ void MarlinSettings::postprocess() {
       // Backlash Compensation
       //
       {
-        #if ENABLED(BACKLASH_GCODE)
-          const xyz_float_t &backlash_distance_mm = backlash.distance_mm;
-          const uint8_t &backlash_correction = backlash.correction;
-        #else
-          xyz_float_t backlash_distance_mm;
-          uint8_t backlash_correction;
-        #endif
-        #if ENABLED(BACKLASH_GCODE) && defined(BACKLASH_SMOOTHING_MM)
-          const float &backlash_smoothing_mm = backlash.smoothing_mm;
-        #else
-          float backlash_smoothing_mm;
-        #endif
+        xyz_float_t backlash_distance_mm;
+        uint8_t backlash_correction;
+        float backlash_smoothing_mm;
+
         _FIELD_TEST(backlash_distance_mm);
         EEPROM_READ(backlash_distance_mm);
         EEPROM_READ(backlash_correction);
         EEPROM_READ(backlash_smoothing_mm);
+
+        #if ENABLED(BACKLASH_GCODE)
+          LOOP_LINEAR_AXES(axis) backlash.set_distance_mm((AxisEnum)axis, backlash_distance_mm[axis]);
+          backlash.set_correction_uint8(backlash_correction);
+          #ifdef BACKLASH_SMOOTHING_MM
+            backlash.set_smoothing_mm(backlash_smoothing_mm);
+          #endif
+        #endif
       }
 
       //
@@ -2811,11 +2812,11 @@ void MarlinSettings::reset() {
   #endif
 
   #if ENABLED(BACKLASH_GCODE)
-    backlash.correction = (BACKLASH_CORRECTION) * 255;
+    backlash.set_correction(BACKLASH_CORRECTION);
     constexpr xyz_float_t tmp = BACKLASH_DISTANCE_MM;
-    backlash.distance_mm = tmp;
+    LOOP_LINEAR_AXES(axis) backlash.set_distance_mm((AxisEnum)axis, tmp[axis]);
     #ifdef BACKLASH_SMOOTHING_MM
-      backlash.smoothing_mm = BACKLASH_SMOOTHING_MM;
+      backlash.set_smoothing_mm(BACKLASH_SMOOTHING_MM);
     #endif
   #endif