From fb60aa3736245a6f50110d6b2900a0b3b3af79d7 Mon Sep 17 00:00:00 2001
From: Scott Lahteine <sourcetree@thinkyhead.com>
Date: Sat, 18 Mar 2017 10:15:54 -0500
Subject: [PATCH] UBL implementation

---
 Marlin/M100_Free_Mem_Chk.cpp   |  28 +-------
 Marlin/Marlin.h                |   3 +
 Marlin/Marlin_main.cpp         |  64 ++++++++++++++---
 Marlin/configuration_store.cpp |  84 +++++++++++++++++++++-
 Marlin/ultralcd.cpp            | 126 ++++++++++++++++++++++++++++++++-
 Marlin/ultralcd_impl_DOGM.h    |   2 +-
 Marlin/ultralcd_impl_HD44780.h |   2 +-
 7 files changed, 268 insertions(+), 41 deletions(-)

diff --git a/Marlin/M100_Free_Mem_Chk.cpp b/Marlin/M100_Free_Mem_Chk.cpp
index d82434e976..58589614d8 100644
--- a/Marlin/M100_Free_Mem_Chk.cpp
+++ b/Marlin/M100_Free_Mem_Chk.cpp
@@ -35,7 +35,7 @@
  * M100 C x Corrupts x locations within the free memory block.   This is useful to check the
  *    correctness of the M100 F and M100 D commands.
  *
- * Initial version by Roxy-3DPrintBoard
+ * Initial version by Roxy-3D
  */
 #define M100_FREE_MEMORY_DUMPER     // Comment out to remove Dump sub-command
 #define M100_FREE_MEMORY_CORRUPTOR    // Comment out to remove Corrupt sub-command
@@ -51,10 +51,9 @@ extern char __bss_end;
 // Utility functions used by M100 to get its work done.
 //
 
+#include "hex_print_routines.h"
+
 char* top_of_stack();
-void prt_hex_nibble(unsigned int);
-void prt_hex_byte(unsigned int);
-void prt_hex_word(unsigned int);
 int how_many_E5s_are_here(char*);
 
 void gcode_M100() {
@@ -211,27 +210,6 @@ char* top_of_stack() {
   return &x + 1; // x is pulled on return;
 }
 
-//
-// 3 support routines to print hex numbers.  We can print a nibble, byte and word
-//
-
-void prt_hex_nibble(unsigned int n) {
-  if (n <= 9)
-    SERIAL_ECHO(n);
-  else
-    SERIAL_ECHO((char)('A' + n - 10));
-}
-
-void prt_hex_byte(unsigned int b) {
-  prt_hex_nibble((b & 0xf0) >> 4);
-  prt_hex_nibble(b & 0x0f);
-}
-
-void prt_hex_word(unsigned int w) {
-  prt_hex_byte((w & 0xff00) >> 8);
-  prt_hex_byte(w & 0x0ff);
-}
-
 // how_many_E5s_are_here() is a utility function to easily find out how many 0xE5's are
 // at the specified location.  Having this logic as a function simplifies the search code.
 //
diff --git a/Marlin/Marlin.h b/Marlin/Marlin.h
index 1ce190b458..e77fc46158 100644
--- a/Marlin/Marlin.h
+++ b/Marlin/Marlin.h
@@ -40,6 +40,7 @@
 #include "fastio.h"
 #include "utility.h"
 
+
 #ifdef USBCON
   #include "HardwareSerial.h"
   #if ENABLED(BLUETOOTH)
@@ -82,6 +83,7 @@ extern const char errormagic[] PROGMEM;
 #define SERIAL_ECHOLNPGM(x)            SERIAL_PROTOCOLLNPGM(x)
 #define SERIAL_ECHOPAIR(name,value)    SERIAL_PROTOCOLPAIR(name, value)
 #define SERIAL_ECHOLNPAIR(name, value) SERIAL_PROTOCOLLNPAIR(name, value)
+#define SERIAL_ECHO_F(x,y)             SERIAL_PROTOCOL_F(x,y)
 
 #define SERIAL_ERROR_START            (serialprintPGM(errormagic))
 #define SERIAL_ERROR(x)                SERIAL_PROTOCOL(x)
@@ -95,6 +97,7 @@ void serial_echopair_P(const char* s_P, int v);
 void serial_echopair_P(const char* s_P, long v);
 void serial_echopair_P(const char* s_P, float v);
 void serial_echopair_P(const char* s_P, double v);
+void serial_echopair_P(const char* s_P, unsigned int v);
 void serial_echopair_P(const char* s_P, unsigned long v);
 FORCE_INLINE void serial_echopair_P(const char* s_P, uint8_t v) { serial_echopair_P(s_P, (int)v); }
 FORCE_INLINE void serial_echopair_P(const char* s_P, uint16_t v) { serial_echopair_P(s_P, (int)v); }
diff --git a/Marlin/Marlin_main.cpp b/Marlin/Marlin_main.cpp
index ef56ae8287..78afe905d0 100644
--- a/Marlin/Marlin_main.cpp
+++ b/Marlin/Marlin_main.cpp
@@ -1,6 +1,6 @@
 /**
  * Marlin 3D Printer Firmware
- * Copyright (C) 2016 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
+ * Copyright (C) 2016, 2017 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
  *
  * Based on Sprinter and grbl.
  * Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
@@ -234,6 +234,10 @@
 #include "duration_t.h"
 #include "types.h"
 
+#if ENABLED(AUTO_BED_LEVELING_UBL)
+  #include "UBL.h"
+#endif
+
 #if HAS_ABL
   #include "vector_3.h"
   #if ENABLED(AUTO_BED_LEVELING_LINEAR)
@@ -297,6 +301,10 @@
        G38_endstop_hit = false;
 #endif
 
+#if ENABLED(AUTO_BED_LEVELING_UBL)
+  bed_leveling blm;
+#endif
+
 bool Running = true;
 
 uint8_t marlin_debug_flags = DEBUG_NONE;
@@ -315,7 +323,7 @@ float current_position[XYZE] = { 0.0 };
  *   Set with 'gcode_get_destination' or 'set_destination_to_current'.
  *   'line_to_destination' sets 'current_position' to 'destination'.
  */
-static float destination[XYZE] = { 0.0 };
+float destination[XYZE] = { 0.0 };
 
 /**
  * axis_homed
@@ -1760,7 +1768,7 @@ static void clean_up_after_endstop_or_probe_move() {
 #endif //HAS_BED_PROBE
 
 #if ENABLED(Z_PROBE_ALLEN_KEY) || ENABLED(Z_PROBE_SLED) || HAS_PROBING_PROCEDURE || HOTENDS > 1 || ENABLED(NOZZLE_CLEAN_FEATURE) || ENABLED(NOZZLE_PARK_FEATURE)
-  static bool axis_unhomed_error(const bool x, const bool y, const bool z) {
+  bool axis_unhomed_error(const bool x, const bool y, const bool z) {
     const bool xx = x && !axis_homed[X_AXIS],
                yy = y && !axis_homed[Y_AXIS],
                zz = z && !axis_homed[Z_AXIS];
@@ -2009,7 +2017,7 @@ static void clean_up_after_endstop_or_probe_move() {
   #endif
 
   // returns false for ok and true for failure
-  static bool set_probe_deployed(bool deploy) {
+  bool set_probe_deployed(bool deploy) {
 
     #if ENABLED(DEBUG_LEVELING_FEATURE)
       if (DEBUGGING(LEVELING)) {
@@ -2184,7 +2192,8 @@ static void clean_up_after_endstop_or_probe_move() {
   //   - Raise to the BETWEEN height
   // - Return the probed Z position
   //
-  static float probe_pt(const float &x, const float &y, const bool stow = true, const int verbose_level = 1) {
+//float probe_pt(const float &x, const float &y, const bool stow = true, const int verbose_level = 1) {
+  float probe_pt(const float x, const float y, const bool stow, const int verbose_level) {
     #if ENABLED(DEBUG_LEVELING_FEATURE)
       if (DEBUGGING(LEVELING)) {
         SERIAL_ECHOPAIR(">>> probe_pt(", x);
@@ -3279,10 +3288,12 @@ inline void gcode_G4() {
         SERIAL_ECHOPGM("BILINEAR");
       #elif ENABLED(AUTO_BED_LEVELING_3POINT)
         SERIAL_ECHOPGM("3POINT");
+      #elif ENABLED(AUTO_BED_LEVELING_UBL)
+        SERIAL_ECHOPGM("UBL");
       #endif
       if (planner.abl_enabled) {
         SERIAL_ECHOLNPGM(" (enabled)");
-        #if ENABLED(AUTO_BED_LEVELING_LINEAR) || ENABLED(AUTO_BED_LEVELING_3POINT)
+        #if ENABLED(AUTO_BED_LEVELING_LINEAR) || ENABLED(AUTO_BED_LEVELING_3POINT) || ENABLED(AUTO_BED_LEVELING_UBL)
           float diff[XYZ] = {
             stepper.get_axis_position_mm(X_AXIS) - current_position[X_AXIS],
             stepper.get_axis_position_mm(Y_AXIS) - current_position[Y_AXIS],
@@ -3830,7 +3841,7 @@ inline void gcode_G28() {
     report_current_position();
   }
 
-#elif HAS_ABL
+#elif HAS_ABL && DISABLED(AUTO_BED_LEVELING_UBL)
 
   /**
    * G29: Detailed Z probe, probes the bed at 3 or more points.
@@ -4383,7 +4394,7 @@ inline void gcode_G28() {
       SYNC_PLAN_POSITION_KINEMATIC();
   }
 
-#endif // HAS_ABL
+#endif // HAS_ABL && DISABLED(AUTO_BED_LEVELING_UBL)
 
 #if HAS_BED_PROBE
 
@@ -6993,6 +7004,8 @@ void quickstop_stepper() {
             bed_level_virt_print();
           #endif
         }
+      #elif ENABLED(AUTO_BED_LEVELING_UBL)
+        blm.display_map(0);  // Right now, we only support one type of map
       #elif ENABLED(MESH_BED_LEVELING)
         if (mbl.has_mesh()) {
           SERIAL_ECHOLNPGM("Mesh Bed Level data:");
@@ -8303,6 +8316,12 @@ void process_next_command() {
           break;
       #endif // INCH_MODE_SUPPORT
 
+      #if ENABLED(AUTO_BED_LEVELING_UBL)
+        case 26: // G26: Mesh Validation Pattern generation
+          gcode_G26();
+          break;
+      #endif // AUTO_BED_LEVELING_UBL
+
       #if ENABLED(NOZZLE_PARK_FEATURE)
         case 27: // G27: Nozzle Park
           gcode_G27();
@@ -8314,7 +8333,8 @@ void process_next_command() {
         break;
 
       #if PLANNER_LEVELING
-        case 29: // G29 Detailed Z probe, probes the bed at 3 or more points.
+        case 29: // G29 Detailed Z probe, probes the bed at 3 or more points,
+                 // or provides access to the UBL System if enabled.
           gcode_G29();
           break;
       #endif // PLANNER_LEVELING
@@ -8421,12 +8441,24 @@ void process_next_command() {
           gcode_M43(); break;
       #endif
 
+
       #if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST)
         case 48: // M48: Z probe repeatability test
           gcode_M48();
           break;
       #endif // Z_MIN_PROBE_REPEATABILITY_TEST
 
+      #if ENABLED(AUTO_BED_LEVELING_UBL)
+        case 49: // M49: Turn on or off G26_Debug_flag for verbose output
+    if (G26_Debug_flag) {
+            SERIAL_PROTOCOLPGM("UBL Debug Flag turned off.\n");
+            G26_Debug_flag = 0; }
+    else {
+            SERIAL_PROTOCOLPGM("UBL Debug Flag turned on.\n");
+            G26_Debug_flag++; }
+          break;
+      #endif // Z_MIN_PROBE_REPEATABILITY_TEST
+
       case 75: // M75: Start print timer
         gcode_M75(); break;
       case 76: // M76: Pause print timer
@@ -9066,7 +9098,7 @@ void ok_to_send() {
       SERIAL_ECHOLNPAIR(" offset=", offset);
     }
     last_offset = offset;
-    //*/
+    */
 
     return offset;
   }
@@ -9552,6 +9584,18 @@ void set_current_from_steppers_for_axis(const AxisEnum axis) {
           return false;
         }
         else
+      #elif ENABLED(AUTO_BED_LEVELING_UBL)
+        if (blm.state.active) {
+
+//        UBL_line_to_destination(MMS_SCALED(feedrate_mm_s));
+
+          UBL_line_to_destination(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS],
+//                      (feedrate*(1.0/60.0))*(feedrate_percentage*(1.0/100.0) ), active_extruder);
+                      MMS_SCALED(feedrate_mm_s), active_extruder);
+
+          return false;
+        }
+        else
       #elif ENABLED(AUTO_BED_LEVELING_BILINEAR)
         if (planner.abl_enabled) {
           bilinear_line_to_destination(MMS_SCALED(feedrate_mm_s));
diff --git a/Marlin/configuration_store.cpp b/Marlin/configuration_store.cpp
index 0d31bee5db..ffe9fc36e5 100644
--- a/Marlin/configuration_store.cpp
+++ b/Marlin/configuration_store.cpp
@@ -164,6 +164,10 @@
   #include "stepper_indirection.h"
 #endif
 
+#if ENABLED(AUTO_BED_LEVELING_UBL)
+  #include "UBL.h"
+#endif
+
 #if ENABLED(ABL_BILINEAR_SUBDIVISION)
   extern void bed_level_virt_interpolate();
 #endif
@@ -534,6 +538,11 @@ void Config_Postprocess() {
       SERIAL_ECHOPAIR("Settings Stored (", eeprom_size - (EEPROM_OFFSET));
       SERIAL_ECHOLNPGM(" bytes)");
     }
+    #if ENABLED(AUTO_BED_LEVELING_UBL)
+      blm.store_state();
+      if (blm.state.EEPROM_storage_slot >= 0)
+        blm.store_mesh(blm.state.EEPROM_storage_slot);
+    #endif
   }
 
   /**
@@ -832,8 +841,45 @@ void Config_Postprocess() {
         SERIAL_ERRORLNPGM("EEPROM checksum mismatch");
         Config_ResetDefault();
       }
-   }
 
+      #if ENABLED(AUTO_BED_LEVELING_UBL)
+        Unified_Bed_Leveling_EEPROM_start = (eeprom_index + 32) & 0xFFF8; // Pad the end of configuration data so it
+                                                                          // can float up or down a little bit without
+                                                                          // disrupting the Unified Bed Leveling data
+        blm.load_state();
+
+        SERIAL_ECHOPGM(" UBL ");
+        if (!blm.state.active) SERIAL_ECHO("not ");
+        SERIAL_ECHOLNPGM("active!");
+
+        if (!blm.sanity_check()) {
+          int tmp_mesh;                                // We want to preserve whether the UBL System is Active
+          bool tmp_active;                             // If it is, we want to preserve the Mesh that is being used.
+          tmp_mesh = blm.state.EEPROM_storage_slot;
+          tmp_active = blm.state.active;
+          SERIAL_ECHOLNPGM("\nInitializing Bed Leveling State to current firmware settings.\n");
+          blm.state = blm.pre_initialized;             // Initialize with the pre_initialized data structure
+          blm.state.EEPROM_storage_slot = tmp_mesh;    // But then restore some data we don't want mangled
+          blm.state.active = tmp_active;
+        }
+        else {
+          SERIAL_PROTOCOLPGM("?Unable to enable Unified Bed Leveling.\n");
+          blm.state = blm.pre_initialized;
+          blm.reset();
+          blm.store_state();
+        }
+
+        if (blm.state.EEPROM_storage_slot >= 0)  {
+          blm.load_mesh(blm.state.EEPROM_storage_slot);
+          SERIAL_ECHOPAIR("Mesh ", blm.state.EEPROM_storage_slot);
+          SERIAL_ECHOLNPGM(" loaded from storage.");
+        }
+        else {
+          blm.reset();
+          SERIAL_ECHOPGM("UBL System reset() \n");
+        }
+      #endif
+    }
     #if ENABLED(EEPROM_CHITCHAT)
       Config_PrintSettings();
     #endif
@@ -1126,6 +1172,42 @@ void Config_ResetDefault() {
       SERIAL_ECHOPAIR(" Z", home_offset[Z_AXIS]);
       SERIAL_EOL;
     #endif
+  #if ENABLED(AUTO_BED_LEVELING_UBL)
+    SERIAL_ECHOLNPGM("Unified Bed Leveling:");
+    CONFIG_ECHO_START;
+
+    SERIAL_ECHOPGM("System is: ");
+    if (blm.state.active)
+       SERIAL_ECHOLNPGM("Active\n");
+    else
+       SERIAL_ECHOLNPGM("Deactive\n");
+
+    SERIAL_ECHOPAIR("Active Mesh Slot: ", blm.state.EEPROM_storage_slot);
+    SERIAL_EOL;
+
+    SERIAL_ECHOPGM("z_offset: ");
+    SERIAL_ECHO_F(blm.state.z_offset, 6);
+    SERIAL_EOL;
+
+    SERIAL_ECHOPAIR("EEPROM can hold ", (int)((E2END - sizeof(blm.state) - Unified_Bed_Leveling_EEPROM_start) / sizeof(z_values)));
+    SERIAL_ECHOLNPGM(" meshes. \n");
+
+    SERIAL_ECHOPAIR("\nUBL_MESH_NUM_X_POINTS  ", UBL_MESH_NUM_X_POINTS);
+    SERIAL_ECHOPAIR("\nUBL_MESH_NUM_Y_POINTS  ", UBL_MESH_NUM_Y_POINTS);
+
+    SERIAL_ECHOPAIR("\nUBL_MESH_MIN_X         ", UBL_MESH_MIN_X);
+    SERIAL_ECHOPAIR("\nUBL_MESH_MIN_Y         ", UBL_MESH_MIN_Y);
+
+    SERIAL_ECHOPAIR("\nUBL_MESH_MAX_X         ", UBL_MESH_MAX_X);
+    SERIAL_ECHOPAIR("\nUBL_MESH_MAX_Y         ", UBL_MESH_MAX_Y);
+
+    SERIAL_ECHOPGM("\nMESH_X_DIST        ");
+    SERIAL_ECHO_F(MESH_X_DIST, 6);
+    SERIAL_ECHOPGM("\nMESH_Y_DIST        ");
+    SERIAL_ECHO_F(MESH_Y_DIST, 6);
+    SERIAL_EOL;
+    SERIAL_EOL;
+  #endif
 
     #if HOTENDS > 1
       CONFIG_ECHO_START;
diff --git a/Marlin/ultralcd.cpp b/Marlin/ultralcd.cpp
index 2ba5419c5c..e36444582e 100755
--- a/Marlin/ultralcd.cpp
+++ b/Marlin/ultralcd.cpp
@@ -30,6 +30,8 @@
 #include "configuration_store.h"
 #include "utility.h"
 
+extern float zprobe_zoffset;
+
 #if HAS_BUZZER && DISABLED(LCD_USE_I2C_BUZZER)
   #include "buzzer.h"
 #endif
@@ -121,6 +123,11 @@ uint16_t max_display_update_time = 0;
   bool encoderRateMultiplierEnabled;
   int32_t lastEncoderMovementMillis;
 
+  #if ENABLED(AUTO_BED_LEVELING_UBL)
+  extern int UBL_has_control_of_LCD_Panel;
+  extern int G29_encoderDiff;
+  #endif
+
   #if HAS_POWER_SWITCH
     extern bool powersupply;
   #endif
@@ -801,6 +808,89 @@ void kill_screen(const char* lcd_msg) {
 
   #endif //BABYSTEPPING
 
+  #if ENABLED(AUTO_BED_LEVELING_UBL)
+
+    float Mesh_Edit_Value, Mesh_Edit_Accumulator; // We round Mesh_Edit_Value to 2.5 decimal places.  So we keep a
+                                                  // seperate value that doesn't lose precision.
+    static int loop_cnt=0, last_seen_bits;
+
+    static void _lcd_mesh_fine_tune( const char* msg) {
+      static unsigned long last_click=0;
+      int  last_digit, movement;
+      long int rounded;
+
+      defer_return_to_status = true;
+      if (encoderPosition) {                     // If moving the Encoder wheel very slowly, we just go
+        if ( (millis() - last_click) > 500L) {   // up or down by 1 position
+          if ( ((int32_t)encoderPosition) > 0 ) {
+            encoderPosition = 1;
+          }
+          else {
+            encoderPosition = (uint32_t) -1;
+          }
+        }
+        last_click = millis();
+
+        Mesh_Edit_Accumulator += ( (float) ((int32_t)encoderPosition)) * .005 / 2.0 ;
+        Mesh_Edit_Value       = Mesh_Edit_Accumulator;
+        encoderPosition       = 0;
+        lcdDrawUpdate       = LCDVIEW_REDRAW_NOW;
+
+        rounded    = (long int) (Mesh_Edit_Value * 1000.0);
+        last_digit = rounded % 5L; //10L;
+        rounded    = rounded - last_digit;
+        last_digit = rounded % 5L; //10L;
+        Mesh_Edit_Value  = ((float) rounded) / 1000.0;
+      }
+
+      if (lcdDrawUpdate) {
+        lcd_implementation_drawedit(msg, ftostr43sign( (float) Mesh_Edit_Value  ));
+      }
+
+      if ( !UBL_has_control_of_LCD_Panel && LCD_CLICKED ) {
+        UBL_has_control_of_LCD_Panel=1;   // We need to lock the normal LCD Panel System outbecause G29 (and G26) are looking for
+        lcd_return_to_status();           // long presses of the Encoder Wheel and the LCD System goes spastic when that happens.
+                                          // We will give back control from those routines when the switch is debounced.
+      }
+    }
+
+
+    void _lcd_mesh_edit() {
+      _lcd_mesh_fine_tune( PSTR("Mesh Editor: "));
+    }
+
+    float lcd_mesh_edit() {
+      lcd_goto_screen(_lcd_mesh_edit);
+      return Mesh_Edit_Value;
+    }
+
+    void lcd_mesh_edit_setup(float inital) {
+      Mesh_Edit_Value       = inital;
+      Mesh_Edit_Accumulator = inital;
+      lcd_goto_screen(_lcd_mesh_edit);
+      return ;
+    }
+
+    void _lcd_z_offset_edit() {
+      _lcd_mesh_fine_tune( PSTR("Z-Offset: "));
+    }
+
+    float lcd_z_offset_edit() {
+      lcd_goto_screen(_lcd_z_offset_edit);
+      return Mesh_Edit_Value;
+    }
+
+    void lcd_z_offset_edit_setup(float inital) {
+      Mesh_Edit_Value       = inital;
+      Mesh_Edit_Accumulator = inital;
+      lcd_goto_screen(_lcd_z_offset_edit);
+      return ;
+    }
+
+
+  #endif // AUTO_BED_LEVELING_UBL
+
+
   /**
    * Watch temperature callbacks
    */
@@ -1307,7 +1397,11 @@ KeepDrawing:
     void _lcd_level_bed_moving() {
       if (lcdDrawUpdate) {
         char msg[10];
-        sprintf_P(msg, PSTR("%i / %u"), (int)(manual_probe_index + 1), (MESH_NUM_X_POINTS) * (MESH_NUM_Y_POINTS));
+        #if ENABLED(MESH_BED_LEVELING)
+          sprintf_P(msg, PSTR("%i / %u"), (int)(manual_probe_index + 1), (MESH_NUM_X_POINTS) * (MESH_NUM_Y_POINTS));
+        #elif ENABLED(AUTO_BED_LEVELING_UBL)
+          sprintf_P(msg, PSTR("%i / %u"), (int)(manual_probe_index + 1), (UBL_MESH_NUM_X_POINTS) * (UBL_MESH_NUM_Y_POINTS));
+        #endif
         lcd_implementation_drawedit(PSTR(MSG_LEVEL_BED_NEXT_POINT), msg);
       }
 
@@ -3110,8 +3204,14 @@ void lcd_update() {
 
     lcd_buttons_update();
 
+    #if ENABLED(AUTO_BED_LEVELING_UBL)
+      const bool UBL_CONDITION = !UBL_has_control_of_LCD_Panel;
+    #else
+      constexpr bool UBL_CONDITION = true;
+    #endif
+
     // If the action button is pressed...
-    if (LCD_CLICKED) {
+    if (UBL_CONDITION && LCD_CLICKED) {
       if (!wait_for_unclick) {           // If not waiting for a debounce release:
         wait_for_unclick = true;         //  Set debounce flag to ignore continous clicks
         lcd_clicked = !wait_for_user;    //  Keep the click if not waiting for a user-click
@@ -3520,8 +3620,15 @@ void lcd_reset_alert_level() { lcd_status_message_level = 0; }
         case encrot2: ENCODER_SPIN(encrot1, encrot3); break;
         case encrot3: ENCODER_SPIN(encrot2, encrot0); break;
       }
+      #if ENABLED(AUTO_BED_LEVELING_UBL)
+        if (UBL_has_control_of_LCD_Panel) {
+          G29_encoderDiff = encoderDiff;    // Make the encoder's rotation available to G29's Mesh Editor
+          encoderDiff = 0;                  // We are going to lie to the LCD Panel and claim the encoder
+                                            // wheel has not turned.
+        }
+      #endif
+      lastEncoderBits = enc;
     }
-    lastEncoderBits = enc;
   }
 
   #if (ENABLED(LCD_I2C_TYPE_MCP23017) || ENABLED(LCD_I2C_TYPE_MCP23008)) && ENABLED(DETECT_DEVICE)
@@ -3530,6 +3637,19 @@ void lcd_reset_alert_level() { lcd_status_message_level = 0; }
     bool lcd_detected() { return true; }
   #endif
 
+  #if ENABLED(AUTO_BED_LEVELING_UBL)
+    void chirp_at_user() {
+      #if ENABLED(LCD_USE_I2C_BUZZER)
+        lcd.buzz(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ);
+      #elif PIN_EXISTS(BEEPER)
+        buzzer.tone(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ);
+      #endif
+    }
+
+    bool G29_lcd_clicked() { return LCD_CLICKED; }
+
+  #endif
+
 #endif // ULTIPANEL
 
 #endif // ULTRA_LCD
diff --git a/Marlin/ultralcd_impl_DOGM.h b/Marlin/ultralcd_impl_DOGM.h
index cbfecacfe8..5f1e4447f2 100644
--- a/Marlin/ultralcd_impl_DOGM.h
+++ b/Marlin/ultralcd_impl_DOGM.h
@@ -320,7 +320,7 @@ void lcd_kill_screen() {
   lcd_printPGM(PSTR(MSG_PLEASE_RESET));
 }
 
-static void lcd_implementation_clear() { } // Automatically cleared by Picture Loop
+void lcd_implementation_clear() { } // Automatically cleared by Picture Loop
 
 //
 // Status Screen
diff --git a/Marlin/ultralcd_impl_HD44780.h b/Marlin/ultralcd_impl_HD44780.h
index 8ccc961238..7ac7f86b85 100644
--- a/Marlin/ultralcd_impl_HD44780.h
+++ b/Marlin/ultralcd_impl_HD44780.h
@@ -378,7 +378,7 @@ static void lcd_implementation_init(
   lcd.clear();
 }
 
-static void lcd_implementation_clear() { lcd.clear(); }
+void lcd_implementation_clear() { lcd.clear(); }
 
 /* Arduino < 1.0.0 is missing a function to print PROGMEM strings, so we need to implement our own */
 void lcd_printPGM(const char *str) {