From fb60aa3736245a6f50110d6b2900a0b3b3af79d7 Mon Sep 17 00:00:00 2001 From: Scott Lahteine 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 d82434e97..58589614d 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 1ce190b45..e77fc4615 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 ef56ae828..78afe905d 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 0d31bee5d..ffe9fc36e 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 2ba5419c5..e36444582 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 cbfecacfe..5f1e4447f 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 8ccc96123..7ac7f86b8 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) {