From 7f3440896ef851c20f3dc9af3963e88732c54c54 Mon Sep 17 00:00:00 2001 From: paul Date: Wed, 26 Feb 2025 22:02:52 +0000 Subject: [PATCH 1/8] Add compiler directive to config.h --- Marlin/Configuration.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index 14d1cfb168..27ff2a8889 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -2107,6 +2107,34 @@ //#define AUTO_BED_LEVELING_UBL //#define MESH_BED_LEVELING +/* When a level probe is fitted but not used for homing, enabling USE_PROBE_FOR_MESH_REF will avoid a level mesh + * being created with an unwanted offset between the fixed Z endstop/Homing Switch and probe trigger height. The + * probe Z offset menu option is removed from the level sub menu, as entering a Z offset there would shift the mesh + * and the Homeing position giving confusing status values for Z. It is recomended not to enter a Z offset value in + * the probe offset section of config.h for the same reason. + * + * If no Z offsets are entered elsewhere the status screen will always show the actual Z possition. e.g when homing + * the Z status will show 0 at the Home position, unless the height of the fixed Z stop/Homeing switch above the bed + * has been set by the HOME_SWITCH_TO_BED_OFFSET_MENU option while creating a level mesh. In which case the Home + * position will show as that height. The printer will move down to the print height when printing starts and Z status + * will show the first layer height. The Nozzle will follow the mesh and baby stepping can be used to temporarily lower + * or raise the nozzle to adjust first layer squish. + * + * If your Z stop/Homeing switch has a fine adjustment capability, the HOME_SWITCH_TO_BED_OFFSET_MENU menu option can be + * left at zero or disabled. Using the paper pinch test and setting the Z stop/Homeing switch position works just as well. + */ +#ifndef USE_PROBE_FOR_Z_HOMING + #if defined AUTO_BED_LEVELING_BILINEAR || defined AUTO_BED_LEVELING_UBL || defined AUTO_BED_LEVELING_LINEAR || defined AUTO_BED_LEVELING_3POINT + //#define USE_PROBE_FOR_MESH_REF + #ifdef USE_PROBE_FOR_MESH_REF + /*optional HOME_SWITCH_TO_BED_OFFSET_MENU enables a menu option called "Home Offset Z" to adjust the Z offset between + * the Fixed Z endstop/Homing switch and the bed. This offset is only used to adjust the level mesh down + * to the Zero plane (Bed) as the mesh is created, so a new level mesh must be created when if it is changed to apply it.*/ + #define HOME_SWITCH_TO_BED_OFFSET_MENU + #endif + #endif +#endif + /** * Commands to execute at the start of G29 probing, * after switching to the PROBING_TOOL. From 1445c99af1dbf1dde1eccf3c9206da468786b351 Mon Sep 17 00:00:00 2001 From: paul Date: Wed, 26 Feb 2025 22:27:07 +0000 Subject: [PATCH 2/8] Declare Variable in gcode.h and changes to /abl/G29.cpp --- Marlin/src/gcode/bedlevel/abl/G29.cpp | 44 +++++++++++++++++++++++---- Marlin/src/gcode/gcode.h | 4 +++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp index b25fe5ebe3..d2ba9a27fe 100644 --- a/Marlin/src/gcode/bedlevel/abl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp @@ -77,6 +77,10 @@ #endif #endif +#ifdef USE_PROBE_FOR_MESH_REF + float mesh_zero_ref_offset = 0; // declared in gcode.h as external so it can be set in menu_probe_level.cpp +#endif + /** * @brief Do some things before returning from G29. * @param retry : true if the G29 can and should be retried. false if the failure is too serious. @@ -263,9 +267,24 @@ G29_TYPE GcodeSuite::G29() { G29_RETURN(false, false); } - // Send 'N' to force homing before G29 (internal only) - if (parser.seen_test('N')) - process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); + #ifdef USE_PROBE_FOR_MESH_REF + // Send 'N' to force homing before G29 (internal only) + if (parser.seen_test('N')){ + process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); + } + else { + process_subcommands_now(F("G28L0 X Y")); // Home X and Y only + } + // Set the probe trigger height as Z home before leveling + probe.probe_at_point(current_position, PROBE_PT_NONE,0 ,false ,true, Z_PROBE_LOW_POINT, Z_TWEEN_SAFE_CLEARANCE, false); + set_axis_is_at_home(Z_AXIS); + sync_plan_position(); + #else + // Send 'N' to force homing before G29 (internal only) + if (parser.seen_test('N')){ + process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); + } + #endif // Don't allow auto-leveling without homing first if (homing_needed_error()) G29_RETURN(false, false); @@ -592,11 +611,19 @@ G29_TYPE GcodeSuite::G29() { #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - const float newz = abl.measured_z + abl.Z_offset; + #ifdef USE_PROBE_FOR_MESH_REF + const float newz = abl.measured_z + mesh_zero_ref_offset; + #else + const float newz = abl.measured_z + abl.Z_offset; + #endif abl.z_values[abl.meshCount.x][abl.meshCount.y] = newz; TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(abl.meshCount, newz)); - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM_P(PSTR("Save X"), abl.meshCount.x, SP_Y_STR, abl.meshCount.y, SP_Z_STR, abl.measured_z + abl.Z_offset); + #ifdef USE_PROBE_FOR_MESH_REF + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM_P(PSTR("Save X"), abl.meshCount.x, SP_Y_STR, abl.meshCount.y, SP_Z_STR, abl.measured_z + mesh_zero_ref_offset); + #else + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM_P(PSTR("Save X"), abl.meshCount.x, SP_Y_STR, abl.meshCount.y, SP_Z_STR, abl.measured_z + abl.Z_offset); + #endif #endif } @@ -798,7 +825,12 @@ G29_TYPE GcodeSuite::G29() { #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - const float z = abl.measured_z + abl.Z_offset; + #ifdef USE_PROBE_FOR_MESH_REF + const float z = abl.measured_z + mesh_zero_ref_offset; + #else + const float z = abl.measured_z + abl.Z_offset; + #endif + abl.z_values[abl.meshCount.x][abl.meshCount.y] = z; TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(abl.meshCount, z)); diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h index 589cd2bc48..c4c81b2647 100644 --- a/Marlin/src/gcode/gcode.h +++ b/Marlin/src/gcode/gcode.h @@ -375,6 +375,10 @@ typedef bits_t(NUM_REL_MODES) relative_t; extern const char G28_STR[]; +#ifdef USE_PROBE_FOR_MESH_REF + extern float mesh_zero_ref_offset; +#endif + class GcodeSuite { public: From 4cfc2d571db804c15a13e661e0693c438b26f29a Mon Sep 17 00:00:00 2001 From: paul Date: Wed, 26 Feb 2025 22:37:04 +0000 Subject: [PATCH 3/8] change Z offset menu item --- Marlin/src/lcd/menu/menu_probe_level.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Marlin/src/lcd/menu/menu_probe_level.cpp b/Marlin/src/lcd/menu/menu_probe_level.cpp index ba806d665f..51b7c08952 100644 --- a/Marlin/src/lcd/menu/menu_probe_level.cpp +++ b/Marlin/src/lcd/menu/menu_probe_level.cpp @@ -26,6 +26,10 @@ #include "../../inc/MarlinConfigPre.h" +#ifdef USE_PROBE_FOR_MESH_REF + #include "../../gcode/gcode.h" +#endif + #if HAS_MARLINUI_MENU && ANY(HAS_LEVELING, HAS_BED_PROBE, ASSISTED_TRAMMING_WIZARD, LCD_BED_TRAMMING) #include "menu_item.h" @@ -350,7 +354,15 @@ void menu_probe_level() { } else { #if HAS_BED_PROBE - EDIT_ITEM_N(LCD_Z_OFFSET_TYPE, Z_AXIS, MSG_ZPROBE_OFFSET_N, &probe.offset.z, PROBE_OFFSET_ZMIN, PROBE_OFFSET_ZMAX); + #ifdef USE_PROBE_FOR_MESH_REF + #ifdef HOME_SWITCH_TO_BED_OFFSET_MENU + // Change the name of the menu option as the offsett relates to the fixed Z stop/Homing switch but + // reduce the range to -1/+1 and use the same offset variable so function stays the same + EDIT_ITEM_N(LCD_Z_OFFSET_TYPE, Z_AXIS, MSG_HOME_OFFSET_Z, &mesh_zero_ref_offset, -1, 0); + #endif + #else + EDIT_ITEM_N(LCD_Z_OFFSET_TYPE, Z_AXIS, MSG_ZPROBE_OFFSET_N, &probe.offset.z, PROBE_OFFSET_ZMIN, PROBE_OFFSET_ZMAX); + #endif #endif } From 3a0cc43b79f1d6dbecbeade4efad1574063d05e1 Mon Sep 17 00:00:00 2001 From: paul Date: Wed, 26 Feb 2025 22:53:55 +0000 Subject: [PATCH 4/8] changes to /feature/bedlevel/ubl/ubl_G29.cpp --- Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp | 42 ++++++++++++++++----- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp index e6f93a001b..733aaa8abe 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp +++ b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp @@ -74,6 +74,10 @@ #define SIZE_OF_LITTLE_RAISE 1 #define BIG_RAISE_NOT_NEEDED 0 +#ifdef USE_PROBE_FOR_MESH_REF + float mesh_zero_ref_offset = 0; // declared in gcode.h as external so it can be set in menu_probe_level.cpp +#endif + /** * G29: Unified Bed Leveling by Roxy * @@ -320,14 +324,30 @@ void unified_bed_leveling::G29() { // Check for commands that require the printer to be homed if (may_move) { planner.synchronize(); - #if ALL(DWIN_LCD_PROUI, ZHOME_BEFORE_LEVELING) - save_ubl_active_state_and_disable(); - gcode.process_subcommands_now(F("G28Z")); - restore_ubl_active_state(false); // ...without telling ExtUI "done" - #else - // Send 'N' to force homing before G29 (internal only) - if (axes_should_home() || parser.seen_test('N')) gcode.home_all_axes(); - #endif + + #ifdef USE_PROBE_FOR_MESH_REF + // Send 'N' to force homing before G29 (internal only) + if (axes_should_home() || parser.seen_test('N')){ + gcode.home_all_axes(); + } + else { + gcode.process_subcommands_now(F("G28L0 X Y")); // Home X and Y only + } + // Set the probe trigger height as Z home before leveling + probe.probe_at_point(current_position, PROBE_PT_NONE,0 ,false ,true, Z_PROBE_LOW_POINT, Z_TWEEN_SAFE_CLEARANCE, false); + set_axis_is_at_home(Z_AXIS); + sync_plan_position(); + #else + #if ALL(DWIN_LCD_PROUI, ZHOME_BEFORE_LEVELING) + save_ubl_active_state_and_disable(); + gcode.process_subcommands_now(F("G28Z")); + restore_ubl_active_state(false); // ...without telling ExtUI "done" + #else + // Send 'N' to force homing before G29 (internal only) + if (axes_should_home() || parser.seen_test('N')) gcode.home_all_axes(); + #endif + #endif + probe.use_probing_tool(); #ifdef EVENT_GCODE_BEFORE_G29 @@ -807,7 +827,11 @@ void unified_bed_leveling::shift_mesh_height() { if (best.pos.x >= 0) { // mesh point found and is reachable by probe TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::G29_POINT_START)); - const float measured_z = probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity); + #ifdef USE_PROBE_FOR_MESH_REF // adjust the mesh point value + const float measured_z = probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity) - mesh_zero_ref_offset; + #else + const float measured_z = probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity); + #endif z_values[best.pos.x][best.pos.y] = isnan(measured_z) ? HUGE_VALF : measured_z; // Mark invalid point already probed with HUGE_VALF to omit it in the next loop #if ENABLED(EXTENSIBLE_UI) ExtUI::onMeshUpdate(best.pos, ExtUI::G29_POINT_FINISH); From b45143dd3cead52bd6a8a93beb17ff9349bad0e7 Mon Sep 17 00:00:00 2001 From: paul Date: Wed, 26 Feb 2025 22:53:55 +0000 Subject: [PATCH 5/8] changes to /feature/bedlevel/ubl/ubl_G29.cpp --- Marlin/Modified files/Configuration.h | 3721 +++++++++++++++++++ Marlin/Modified files/G29.cpp | 1069 ++++++ Marlin/Modified files/Modified files.zip | Bin 0 -> 93207 bytes Marlin/Modified files/gcode.h | 1347 +++++++ Marlin/Modified files/menu_probe_level.cpp | 423 +++ Marlin/Modified files/ubl_G29.cpp | 1898 ++++++++++ Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp | 42 +- 7 files changed, 8491 insertions(+), 9 deletions(-) create mode 100644 Marlin/Modified files/Configuration.h create mode 100644 Marlin/Modified files/G29.cpp create mode 100644 Marlin/Modified files/Modified files.zip create mode 100644 Marlin/Modified files/gcode.h create mode 100644 Marlin/Modified files/menu_probe_level.cpp create mode 100644 Marlin/Modified files/ubl_G29.cpp diff --git a/Marlin/Modified files/Configuration.h b/Marlin/Modified files/Configuration.h new file mode 100644 index 0000000000..27ff2a8889 --- /dev/null +++ b/Marlin/Modified files/Configuration.h @@ -0,0 +1,3721 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#pragma once + +/** + * Configuration.h + * + * Basic settings such as: + * + * - Type of electronics + * - Type of temperature sensor + * - Printer geometry + * - Endstop configuration + * - LCD controller + * - Extra features + * + * Advanced settings can be found in Configuration_adv.h + */ +#define CONFIGURATION_H_VERSION 02010300 + +//=========================================================================== +//============================= Getting Started ============================= +//=========================================================================== + +/** + * Here are some useful links to help get your machine configured and calibrated: + * + * Example Configs: https://github.com/MarlinFirmware/Configurations/branches/all + * + * Průša Calculator: https://blog.prusa3d.com/calculator_3416/ + * + * Calibration Guides: https://reprap.org/wiki/Calibration + * https://reprap.org/wiki/Triffid_Hunter%27s_Calibration_Guide + * https://web.archive.org/web/20220907014303/sites.google.com/site/repraplogphase/calibration-of-your-reprap + * https://youtu.be/wAL9d7FgInk + * https://teachingtechyt.github.io/calibration.html + * + * Calibration Objects: https://www.thingiverse.com/thing:5573 + * https://www.thingiverse.com/thing:1278865 + */ + +// @section info + +// Author info of this build printed to the host during boot and M115 +#define STRING_CONFIG_H_AUTHOR "(none, default config)" // Original author or contributor. +//#define CUSTOM_VERSION_FILE Version.h // Path from the root directory (no quotes) + +// @section machine + +// Choose the name from boards.h that matches your setup +#ifndef MOTHERBOARD + #define MOTHERBOARD BOARD_RAMPS_14_EFB +#endif + +// @section serial + +/** + * Select the serial port on the board to use for communication with the host. + * This allows the connection of wireless adapters (for instance) to non-default port pins. + * Serial port -1 is the USB emulated serial port, if available. + * Note: The first serial port (-1 or 0) will always be used by the Arduino bootloader. + * + * :[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + */ +#define SERIAL_PORT 0 + +/** + * Serial Port Baud Rate + * This is the default communication speed for all serial ports. + * Set the baud rate defaults for additional serial ports below. + * + * 250000 works in most cases, but you might try a lower speed if + * you commonly experience drop-outs during host printing. + * You may try up to 1000000 to speed up SD file transfer. + * + * :[2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000] + */ +#define BAUDRATE 250000 + +//#define BAUD_RATE_GCODE // Enable G-code M575 to set the baud rate + +/** + * Select a secondary serial port on the board to use for communication with the host. + * Currently Ethernet (-2) is only supported on Teensy 4.1 boards. + * :[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + */ +//#define SERIAL_PORT_2 -1 +//#define BAUDRATE_2 250000 // :[2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000] Enable to override BAUDRATE + +/** + * Select a third serial port on the board to use for communication with the host. + * Currently supported for AVR, DUE, SAMD51, LPC1768/9, STM32/STM32F1/HC32, and Teensy 4.x + * :[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + */ +//#define SERIAL_PORT_3 1 +//#define BAUDRATE_3 250000 // :[2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000] Enable to override BAUDRATE + +/** + * Select a serial port to communicate with RS485 protocol + * :[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + */ +//#define RS485_SERIAL_PORT 1 +#ifdef RS485_SERIAL_PORT + //#define M485_PROTOCOL 1 // Check your host for protocol compatibility + //#define RS485_BUS_BUFFER_SIZE 128 +#endif + +// Enable the Bluetooth serial interface on AT90USB devices +//#define BLUETOOTH + +// Name displayed in the LCD "Ready" message and Info menu +//#define CUSTOM_MACHINE_NAME "3D Printer" + +// Printer's unique ID, used by some programs to differentiate between machines. +// Choose your own or use a service like https://www.uuidgenerator.net/version4 +//#define MACHINE_UUID "00000000-0000-0000-0000-000000000000" + +// @section stepper drivers + +/** + * Stepper Drivers + * + * These settings allow Marlin to tune stepper driver timing and enable advanced options for + * stepper drivers that support them. You may also override timing options in Configuration_adv.h. + * + * Use TMC2208/TMC2208_STANDALONE for TMC2225 drivers and TMC2209/TMC2209_STANDALONE for TMC2226 drivers. + * + * Options: A4988, A5984, DRV8825, LV8729, TB6560, TB6600, TMC2100, + * TMC2130, TMC2130_STANDALONE, TMC2160, TMC2160_STANDALONE, + * TMC2208, TMC2208_STANDALONE, TMC2209, TMC2209_STANDALONE, + * TMC2660, TMC2660_STANDALONE, TMC5130, TMC5130_STANDALONE, + * TMC5160, TMC5160_STANDALONE + * :['A4988', 'A5984', 'DRV8825', 'LV8729', 'TB6560', 'TB6600', 'TMC2100', 'TMC2130', 'TMC2130_STANDALONE', 'TMC2160', 'TMC2160_STANDALONE', 'TMC2208', 'TMC2208_STANDALONE', 'TMC2209', 'TMC2209_STANDALONE', 'TMC2660', 'TMC2660_STANDALONE', 'TMC5130', 'TMC5130_STANDALONE', 'TMC5160', 'TMC5160_STANDALONE'] + */ +#define X_DRIVER_TYPE A4988 +#define Y_DRIVER_TYPE A4988 +#define Z_DRIVER_TYPE A4988 +//#define X2_DRIVER_TYPE A4988 +//#define Y2_DRIVER_TYPE A4988 +//#define Z2_DRIVER_TYPE A4988 +//#define Z3_DRIVER_TYPE A4988 +//#define Z4_DRIVER_TYPE A4988 +//#define I_DRIVER_TYPE A4988 +//#define J_DRIVER_TYPE A4988 +//#define K_DRIVER_TYPE A4988 +//#define U_DRIVER_TYPE A4988 +//#define V_DRIVER_TYPE A4988 +//#define W_DRIVER_TYPE A4988 +#define E0_DRIVER_TYPE A4988 +//#define E1_DRIVER_TYPE A4988 +//#define E2_DRIVER_TYPE A4988 +//#define E3_DRIVER_TYPE A4988 +//#define E4_DRIVER_TYPE A4988 +//#define E5_DRIVER_TYPE A4988 +//#define E6_DRIVER_TYPE A4988 +//#define E7_DRIVER_TYPE A4988 + +/** + * Additional Axis Settings + * + * Define AXISn_ROTATES for all axes that rotate or pivot. + * Rotational axis coordinates are expressed in degrees. + * + * AXISn_NAME defines the letter used to refer to the axis in (most) G-code commands. + * By convention the names and roles are typically: + * 'A' : Rotational axis parallel to X + * 'B' : Rotational axis parallel to Y + * 'C' : Rotational axis parallel to Z + * 'U' : Secondary linear axis parallel to X + * 'V' : Secondary linear axis parallel to Y + * 'W' : Secondary linear axis parallel to Z + * + * Regardless of these settings the axes are internally named I, J, K, U, V, W. + */ +#ifdef I_DRIVER_TYPE + #define AXIS4_NAME 'A' // :['A', 'B', 'C', 'U', 'V', 'W'] + #define AXIS4_ROTATES +#endif +#ifdef J_DRIVER_TYPE + #define AXIS5_NAME 'B' // :['B', 'C', 'U', 'V', 'W'] + #define AXIS5_ROTATES +#endif +#ifdef K_DRIVER_TYPE + #define AXIS6_NAME 'C' // :['C', 'U', 'V', 'W'] + #define AXIS6_ROTATES +#endif +#ifdef U_DRIVER_TYPE + #define AXIS7_NAME 'U' // :['U', 'V', 'W'] + //#define AXIS7_ROTATES +#endif +#ifdef V_DRIVER_TYPE + #define AXIS8_NAME 'V' // :['V', 'W'] + //#define AXIS8_ROTATES +#endif +#ifdef W_DRIVER_TYPE + #define AXIS9_NAME 'W' // :['W'] + //#define AXIS9_ROTATES +#endif + +// @section extruder + +// This defines the number of extruders +// :[0, 1, 2, 3, 4, 5, 6, 7, 8] +#define EXTRUDERS 1 + +// Generally expected filament diameter (1.75, 2.85, 3.0, ...). Used for Volumetric, Filament Width Sensor, etc. +#define DEFAULT_NOMINAL_FILAMENT_DIA 1.75 + +// For Cyclops or any "multi-extruder" that shares a single nozzle. +//#define SINGLENOZZLE + +// Save and restore temperature and fan speed on tool-change. +// Set standby for the unselected tool with M104/106/109 T... +#if ENABLED(SINGLENOZZLE) + //#define SINGLENOZZLE_STANDBY_TEMP + //#define SINGLENOZZLE_STANDBY_FAN +#endif + +// A dual extruder that uses a single stepper motor +//#define SWITCHING_EXTRUDER +#if ENABLED(SWITCHING_EXTRUDER) + #define SWITCHING_EXTRUDER_SERVO_NR 0 + #define SWITCHING_EXTRUDER_SERVO_ANGLES { 0, 90 } // Angles for E0, E1[, E2, E3] + #if EXTRUDERS > 3 + #define SWITCHING_EXTRUDER_E23_SERVO_NR 1 + #endif +#endif + +// Switch extruders by bumping the toolhead. Requires EVENT_GCODE_TOOLCHANGE_#. +//#define MECHANICAL_SWITCHING_EXTRUDER + +/** + * A dual-nozzle that uses a servomotor to raise/lower one (or both) of the nozzles. + * Can be combined with SWITCHING_EXTRUDER. + */ +//#define SWITCHING_NOZZLE +#if ENABLED(SWITCHING_NOZZLE) + #define SWITCHING_NOZZLE_SERVO_NR 0 + //#define SWITCHING_NOZZLE_E1_SERVO_NR 1 // If two servos are used, the index of the second + #define SWITCHING_NOZZLE_SERVO_ANGLES { 0, 90 } // A pair of angles for { E0, E1 }. + // For Dual Servo use two pairs: { { lower, raise }, { lower, raise } } + #define SWITCHING_NOZZLE_SERVO_DWELL 2500 // Dwell time to wait for servo to make physical move +#endif + +// Switch nozzles by bumping the toolhead. Requires EVENT_GCODE_TOOLCHANGE_#. +//#define MECHANICAL_SWITCHING_NOZZLE + +/** + * Two separate X-carriages with extruders that connect to a moving part + * via a solenoid docking mechanism. Requires SOL1_PIN and SOL2_PIN. + */ +//#define PARKING_EXTRUDER + +/** + * Two separate X-carriages with extruders that connect to a moving part + * via a magnetic docking mechanism using movements and no solenoid + * + * project : https://www.thingiverse.com/thing:3080893 + * movements : https://youtu.be/0xCEiG9VS3k + * https://youtu.be/Bqbcs0CU2FE + */ +//#define MAGNETIC_PARKING_EXTRUDER + +#if ANY(PARKING_EXTRUDER, MAGNETIC_PARKING_EXTRUDER) + + #define PARKING_EXTRUDER_PARKING_X { -78, 184 } // X positions for parking the extruders + #define PARKING_EXTRUDER_GRAB_DISTANCE 1 // (mm) Distance to move beyond the parking point to grab the extruder + + #if ENABLED(PARKING_EXTRUDER) + + #define PARKING_EXTRUDER_SOLENOIDS_INVERT // If enabled, the solenoid is NOT magnetized with applied voltage + #define PARKING_EXTRUDER_SOLENOIDS_PINS_ACTIVE LOW // LOW or HIGH pin signal energizes the coil + #define PARKING_EXTRUDER_SOLENOIDS_DELAY 250 // (ms) Delay for magnetic field. No delay if 0 or not defined. + //#define MANUAL_SOLENOID_CONTROL // Manual control of docking solenoids with M380 S / M381 + + #elif ENABLED(MAGNETIC_PARKING_EXTRUDER) + + #define MPE_FAST_SPEED 9000 // (mm/min) Speed for travel before last distance point + #define MPE_SLOW_SPEED 4500 // (mm/min) Speed for last distance travel to park and couple + #define MPE_TRAVEL_DISTANCE 10 // (mm) Last distance point + #define MPE_COMPENSATION 0 // Offset Compensation -1 , 0 , 1 (multiplier) only for coupling + + #endif + +#endif + +/** + * Switching Toolhead + * + * Support for swappable and dockable toolheads, such as + * the E3D Tool Changer. Toolheads are locked with a servo. + */ +//#define SWITCHING_TOOLHEAD + +/** + * Magnetic Switching Toolhead + * + * Support swappable and dockable toolheads with a magnetic + * docking mechanism using movement and no servo. + */ +//#define MAGNETIC_SWITCHING_TOOLHEAD + +/** + * Electromagnetic Switching Toolhead + * + * Parking for CoreXY / HBot kinematics. + * Toolheads are parked at one edge and held with an electromagnet. + * Supports more than 2 Toolheads. See https://youtu.be/JolbsAKTKf4 + */ +//#define ELECTROMAGNETIC_SWITCHING_TOOLHEAD + +#if ANY(SWITCHING_TOOLHEAD, MAGNETIC_SWITCHING_TOOLHEAD, ELECTROMAGNETIC_SWITCHING_TOOLHEAD) + #define SWITCHING_TOOLHEAD_Y_POS 235 // (mm) Y position of the toolhead dock + #define SWITCHING_TOOLHEAD_Y_SECURITY 10 // (mm) Security distance Y axis + #define SWITCHING_TOOLHEAD_Y_CLEAR 60 // (mm) Minimum distance from dock for unobstructed X axis + #define SWITCHING_TOOLHEAD_X_POS { 215, 0 } // (mm) X positions for parking the extruders + #if ENABLED(SWITCHING_TOOLHEAD) + #define SWITCHING_TOOLHEAD_SERVO_NR 2 // Index of the servo connector + #define SWITCHING_TOOLHEAD_SERVO_ANGLES { 0, 180 } // (degrees) Angles for Lock, Unlock + #elif ENABLED(MAGNETIC_SWITCHING_TOOLHEAD) + #define SWITCHING_TOOLHEAD_Y_RELEASE 5 // (mm) Security distance Y axis + #define SWITCHING_TOOLHEAD_X_SECURITY { 90, 150 } // (mm) Security distance X axis (T0,T1) + //#define PRIME_BEFORE_REMOVE // Prime the nozzle before release from the dock + #if ENABLED(PRIME_BEFORE_REMOVE) + #define SWITCHING_TOOLHEAD_PRIME_MM 20 // (mm) Extruder prime length + #define SWITCHING_TOOLHEAD_RETRACT_MM 10 // (mm) Retract after priming length + #define SWITCHING_TOOLHEAD_PRIME_FEEDRATE 300 // (mm/min) Extruder prime feedrate + #define SWITCHING_TOOLHEAD_RETRACT_FEEDRATE 2400 // (mm/min) Extruder retract feedrate + #endif + #elif ENABLED(ELECTROMAGNETIC_SWITCHING_TOOLHEAD) + #define SWITCHING_TOOLHEAD_Z_HOP 2 // (mm) Z raise for switching + #endif +#endif + +/** + * "Mixing Extruder" + * - Adds G-codes M163 and M164 to set and "commit" the current mix factors. + * - Extends the stepping routines to move multiple steppers in proportion to the mix. + * - Optional support for Repetier Firmware's 'M164 S' supporting virtual tools. + * - This implementation supports up to two mixing extruders. + * - Enable DIRECT_MIXING_IN_G1 for M165 and mixing in G1 (from Pia Taubert's reference implementation). + */ +//#define MIXING_EXTRUDER +#if ENABLED(MIXING_EXTRUDER) + #define MIXING_STEPPERS 2 // Number of steppers in your mixing extruder + #define MIXING_VIRTUAL_TOOLS 16 // Use the Virtual Tool method with M163 and M164 + //#define DIRECT_MIXING_IN_G1 // Allow ABCDHI mix factors in G1 movement commands + //#define GRADIENT_MIX // Support for gradient mixing with M166 and LCD + //#define MIXING_PRESETS // Assign 8 default V-tool presets for 2 or 3 MIXING_STEPPERS + #if ENABLED(GRADIENT_MIX) + //#define GRADIENT_VTOOL // Add M166 T to use a V-tool index as a Gradient alias + #endif +#endif + +// Offset of the extruders (uncomment if using more than one and relying on firmware to position when changing). +// The offset has to be X=0, Y=0 for the extruder 0 hotend (default extruder). +// For the other hotends it is their distance from the extruder 0 hotend. +//#define HOTEND_OFFSET_X { 0.0, 20.00 } // (mm) relative X-offset for each nozzle +//#define HOTEND_OFFSET_Y { 0.0, 5.00 } // (mm) relative Y-offset for each nozzle +//#define HOTEND_OFFSET_Z { 0.0, 0.00 } // (mm) relative Z-offset for each nozzle + +// @section multi-material + +/** + * Multi-Material Unit + * Set to one of these predefined models: + * + * PRUSA_MMU1 : Průša MMU1 (The "multiplexer" version) + * PRUSA_MMU2 : Průša MMU2 + * PRUSA_MMU2S : Průša MMU2S (Requires MK3S extruder with motion sensor, EXTRUDERS = 5) + * PRUSA_MMU3 : Průša MMU3 (Requires MK3S extruder with motion sensor and MMU firmware version 3.x.x, EXTRUDERS = 5) + * EXTENDABLE_EMU_MMU2 : MMU with configurable number of filaments (ERCF, SMuFF or similar with Průša MMU2 compatible firmware) + * EXTENDABLE_EMU_MMU2S : MMUS with configurable number of filaments (ERCF, SMuFF or similar with Průša MMU2 compatible firmware) + * + * Requires NOZZLE_PARK_FEATURE to park print head in case MMU unit fails. + * See additional options in Configuration_adv.h. + * :["PRUSA_MMU1", "PRUSA_MMU2", "PRUSA_MMU2S", "PRUSA_MMU3", "EXTENDABLE_EMU_MMU2", "EXTENDABLE_EMU_MMU2S"] + */ +//#define MMU_MODEL PRUSA_MMU3 + +// @section psu control + +/** + * Power Supply Control + * + * Enable and connect the power supply to the PS_ON_PIN. + * Specify whether the power supply is active HIGH or active LOW. + */ +//#define PSU_CONTROL +//#define PSU_NAME "Power Supply" + +#if ENABLED(PSU_CONTROL) + //#define MKS_PWC // Using the MKS PWC add-on + //#define PS_OFF_CONFIRM // Confirm dialog when power off + //#define PS_OFF_SOUND // Beep 1s when power off + #define PSU_ACTIVE_STATE LOW // Set 'LOW' for ATX, 'HIGH' for X-Box + + //#define PSU_DEFAULT_OFF // Keep power off until enabled directly with M80 + //#define PSU_POWERUP_DELAY 250 // (ms) Delay for the PSU to warm up to full power + //#define LED_POWEROFF_TIMEOUT 10000 // (ms) Turn off LEDs after power-off, with this amount of delay + + //#define PSU_OFF_REDUNDANT // Second pin for redundant power control + //#define PSU_OFF_REDUNDANT_INVERTED // Redundant pin state is the inverse of PSU_ACTIVE_STATE + + //#define PS_ON1_PIN 6 // Redundant pin required to enable power in combination with PS_ON_PIN + + //#define PS_ON_EDM_PIN 8 // External Device Monitoring pins for external power control relay feedback. Fault on mismatch. + //#define PS_ON1_EDM_PIN 9 + #define PS_EDM_RESPONSE 250 // (ms) Time to allow for relay action + + //#define POWER_OFF_TIMER // Enable M81 D to power off after a delay + //#define POWER_OFF_WAIT_FOR_COOLDOWN // Enable M81 S to power off only after cooldown + + //#define PSU_POWERUP_GCODE "M355 S1" // G-code to run after power-on (e.g., case light on) + //#define PSU_POWEROFF_GCODE "M355 S0" // G-code to run before power-off (e.g., case light off) + + //#define AUTO_POWER_CONTROL // Enable automatic control of the PS_ON pin + #if ENABLED(AUTO_POWER_CONTROL) + #define AUTO_POWER_FANS // Turn on PSU for fans + #define AUTO_POWER_E_FANS // Turn on PSU for E Fans + #define AUTO_POWER_CONTROLLERFAN // Turn on PSU for Controller Fan + #define AUTO_POWER_CHAMBER_FAN // Turn on PSU for Chamber Fan + #define AUTO_POWER_COOLER_FAN // Turn on PSU for Cooler Fan + #define AUTO_POWER_SPINDLE_LASER // Turn on PSU for Spindle/Laser + #define POWER_TIMEOUT 30 // (s) Turn off power if the machine is idle for this duration + //#define POWER_OFF_DELAY 60 // (s) Delay of poweroff after M81 command. Useful to let fans run for extra time. + #endif + #if ANY(AUTO_POWER_CONTROL, POWER_OFF_WAIT_FOR_COOLDOWN) + //#define AUTO_POWER_E_TEMP 50 // (°C) PSU on if any extruder is over this temperature + //#define AUTO_POWER_CHAMBER_TEMP 30 // (°C) PSU on if the chamber is over this temperature + //#define AUTO_POWER_COOLER_TEMP 26 // (°C) PSU on if the cooler is over this temperature + #endif +#endif + +//=========================================================================== +//============================= Thermal Settings ============================ +//=========================================================================== +// @section temperature + +/** + * Temperature Sensors: + * + * NORMAL IS 4.7kΩ PULLUP! Hotend sensors can use 1kΩ pullup with correct resistor and table. + * + * ================================================================ + * Analog Thermistors - 4.7kΩ pullup - Normal + * ================================================================ + * 1 : 100kΩ EPCOS - Best choice for EPCOS thermistors + * 331 : 100kΩ Same as #1, but 3.3V scaled for MEGA + * 332 : 100kΩ Same as #1, but 3.3V scaled for DUE + * 2 : 200kΩ ATC Semitec 204GT-2 + * 202 : 200kΩ Copymaster 3D + * 3 : ???Ω Mendel-parts thermistor + * 4 : 10kΩ Generic Thermistor !! DO NOT use for a hotend - it gives bad resolution at high temp. !! + * 5 : 100kΩ ATC Semitec 104GT-2/104NT-4-R025H42G - Used in ParCan, J-Head, and E3D, SliceEngineering 300°C + * 501 : 100kΩ Zonestar - Tronxy X3A + * 502 : 100kΩ Zonestar - used by hot bed in Zonestar Průša P802M + * 503 : 100kΩ Zonestar (Z8XM2) Heated Bed thermistor + * 504 : 100kΩ Zonestar P802QR2 (Part# QWG-104F-B3950) Hotend Thermistor + * 505 : 100kΩ Zonestar P802QR2 (Part# QWG-104F-3950) Bed Thermistor + * 512 : 100kΩ RPW-Ultra hotend + * 6 : 100kΩ EPCOS - Not as accurate as table #1 (created using a fluke thermocouple) + * 7 : 100kΩ Honeywell 135-104LAG-J01 + * 71 : 100kΩ Honeywell 135-104LAF-J01 + * 8 : 100kΩ Vishay 0603 SMD NTCS0603E3104FXT + * 9 : 100kΩ GE Sensing AL03006-58.2K-97-G1 + * 10 : 100kΩ RS PRO 198-961 + * 11 : 100kΩ Keenovo AC silicone mats, most Wanhao i3 machines - beta 3950, 1% + * 12 : 100kΩ Vishay 0603 SMD NTCS0603E3104FXT (#8) - calibrated for Makibox hot bed + * 13 : 100kΩ Hisens up to 300°C - for "Simple ONE" & "All In ONE" hotend - beta 3950, 1% + * 14 : 100kΩ (R25), 4092K (beta25), 4.7kΩ pull-up, bed thermistor as used in Ender-5 S1 + * 15 : 100kΩ Calibrated for JGAurora A5 hotend + * 17 : 100kΩ Dagoma NTC white thermistor + * 18 : 200kΩ ATC Semitec 204GT-2 Dagoma.Fr - MKS_Base_DKU001327 + * 22 : 100kΩ GTM32 Pro vB - hotend - 4.7kΩ pullup to 3.3V and 220Ω to analog input + * 23 : 100kΩ GTM32 Pro vB - bed - 4.7kΩ pullup to 3.3v and 220Ω to analog input + * 30 : 100kΩ Kis3d Silicone heating mat 200W/300W with 6mm precision cast plate (EN AW 5083) NTC100K - beta 3950 + * 60 : 100kΩ Maker's Tool Works Kapton Bed Thermistor - beta 3950 + * 61 : 100kΩ Formbot/Vivedino 350°C Thermistor - beta 3950 + * 66 : 4.7MΩ Dyze Design / Trianglelab T-D500 500°C High Temperature Thermistor + * 67 : 500kΩ SliceEngineering 450°C Thermistor + * 68 : PT100 Smplifier board from Dyze Design + * 70 : 100kΩ bq Hephestos 2 + * 75 : 100kΩ Generic Silicon Heat Pad with NTC100K MGB18-104F39050L32 + * 666 : 200kΩ Einstart S custom thermistor with 10k pullup. + * 2000 : 100kΩ Ultimachine Rambo TDK NTCG104LH104KT1 NTC100K motherboard Thermistor + * + * ================================================================ + * Analog Thermistors - 1kΩ pullup + * Atypical, and requires changing out the 4.7kΩ pullup for 1kΩ. + * (but gives greater accuracy and more stable PID) + * ================================================================ + * 51 : 100kΩ EPCOS (1kΩ pullup) + * 52 : 200kΩ ATC Semitec 204GT-2 (1kΩ pullup) + * 55 : 100kΩ ATC Semitec 104GT-2 - Used in ParCan & J-Head (1kΩ pullup) + * + * ================================================================ + * Analog Thermistors - 10kΩ pullup - Atypical + * ================================================================ + * 99 : 100kΩ Found on some Wanhao i3 machines with a 10kΩ pull-up resistor + * + * ================================================================ + * Analog RTDs (Pt100/Pt1000) + * ================================================================ + * 110 : Pt100 with 1kΩ pullup (atypical) + * 147 : Pt100 with 4.7kΩ pullup + * 1010 : Pt1000 with 1kΩ pullup (atypical) + * 1022 : Pt1000 with 2.2kΩ pullup + * 1047 : Pt1000 with 4.7kΩ pullup (E3D) + * 20 : Pt100 with circuit in the Ultimainboard V2.x with mainboard ADC reference voltage = INA826 amplifier-board supply voltage. + * NOTE: (1) Must use an ADC input with no pullup. (2) Some INA826 amplifiers are unreliable at 3.3V so consider using sensor 147, 110, or 21. + * 21 : Pt100 with circuit in the Ultimainboard V2.x with 3.3v ADC reference voltage (STM32, LPC176x....) and 5V INA826 amplifier board supply. + * NOTE: ADC pins are not 5V tolerant. Not recommended because it's possible to damage the CPU by going over 500°C. + * 201 : Pt100 with circuit in Overlord, similar to Ultimainboard V2.x + * + * ================================================================ + * SPI RTD/Thermocouple Boards + * ================================================================ + * -5 : MAX31865 with Pt100/Pt1000, 2, 3, or 4-wire (only for sensors 0-2 and bed) + * NOTE: You must uncomment/set the MAX31865_*_OHMS_n defines below. + * -3 : MAX31855 with Thermocouple, -200°C to +700°C (only for sensors 0-2 and bed) + * -2 : MAX6675 with Thermocouple, 0°C to +700°C (only for sensors 0-2 and bed) + * + * NOTE: Ensure TEMP_n_CS_PIN is set in your pins file for each TEMP_SENSOR_n using an SPI Thermocouple. By default, + * Hardware SPI on the default serial bus is used. If you have also set TEMP_n_SCK_PIN and TEMP_n_MISO_PIN, + * Software SPI will be used on those ports instead. You can force Hardware SPI on the default bus in the + * Configuration_adv.h file. At this time, separate Hardware SPI buses for sensors are not supported. + * + * ================================================================ + * Analog Thermocouple Boards + * ================================================================ + * -4 : AD8495 with Thermocouple + * -1 : AD595 with Thermocouple + * + * ================================================================ + * SoC internal sensor + * ================================================================ + * 100 : SoC internal sensor + * + * ================================================================ + * Custom/Dummy/Other Thermal Sensors + * ================================================================ + * 0 : not used + * 1000 : Custom - Specify parameters in Configuration_adv.h + * + * !!! Use these for Testing or Development purposes. NEVER for production machine. !!! + * 998 : Dummy Table that ALWAYS reads 25°C or the temperature defined below. + * 999 : Dummy Table that ALWAYS reads 100°C or the temperature defined below. + */ +#define TEMP_SENSOR_0 1 +#define TEMP_SENSOR_1 0 +#define TEMP_SENSOR_2 0 +#define TEMP_SENSOR_3 0 +#define TEMP_SENSOR_4 0 +#define TEMP_SENSOR_5 0 +#define TEMP_SENSOR_6 0 +#define TEMP_SENSOR_7 0 +#define TEMP_SENSOR_BED 1 +#define TEMP_SENSOR_PROBE 0 +#define TEMP_SENSOR_CHAMBER 0 +#define TEMP_SENSOR_COOLER 0 +#define TEMP_SENSOR_BOARD 0 +#define TEMP_SENSOR_SOC 0 +#define TEMP_SENSOR_REDUNDANT 0 + +// Dummy thermistor constant temperature readings, for use with 998 and 999 +#define DUMMY_THERMISTOR_998_VALUE 25 +#define DUMMY_THERMISTOR_999_VALUE 100 + +// Resistor values when using MAX31865 sensors (-5) on TEMP_SENSOR_0 / 1 +#if TEMP_SENSOR_IS_MAX_TC(0) + #define MAX31865_SENSOR_OHMS_0 100 // (Ω) Typically 100 or 1000 (PT100 or PT1000) + #define MAX31865_CALIBRATION_OHMS_0 430 // (Ω) Typically 430 for Adafruit PT100; 4300 for Adafruit PT1000 +#endif +#if TEMP_SENSOR_IS_MAX_TC(1) + #define MAX31865_SENSOR_OHMS_1 100 + #define MAX31865_CALIBRATION_OHMS_1 430 +#endif +#if TEMP_SENSOR_IS_MAX_TC(2) + #define MAX31865_SENSOR_OHMS_2 100 + #define MAX31865_CALIBRATION_OHMS_2 430 +#endif +#if TEMP_SENSOR_IS_MAX_TC(BED) + #define MAX31865_SENSOR_OHMS_BED 100 + #define MAX31865_CALIBRATION_OHMS_BED 430 +#endif + +#if HAS_E_TEMP_SENSOR + #define TEMP_RESIDENCY_TIME 10 // (seconds) Time to wait for hotend to "settle" in M109 + #define TEMP_WINDOW 1 // (°C) Temperature proximity for the "temperature reached" timer + #define TEMP_HYSTERESIS 3 // (°C) Temperature proximity considered "close enough" to the target +#endif + +#if TEMP_SENSOR_BED + #define TEMP_BED_RESIDENCY_TIME 10 // (seconds) Time to wait for bed to "settle" in M190 + #define TEMP_BED_WINDOW 1 // (°C) Temperature proximity for the "temperature reached" timer + #define TEMP_BED_HYSTERESIS 3 // (°C) Temperature proximity considered "close enough" to the target +#endif + +#if TEMP_SENSOR_CHAMBER + #define TEMP_CHAMBER_RESIDENCY_TIME 10 // (seconds) Time to wait for chamber to "settle" in M191 + #define TEMP_CHAMBER_WINDOW 1 // (°C) Temperature proximity for the "temperature reached" timer + #define TEMP_CHAMBER_HYSTERESIS 3 // (°C) Temperature proximity considered "close enough" to the target +#endif + +/** + * Redundant Temperature Sensor (TEMP_SENSOR_REDUNDANT) + * + * Use a temp sensor as a redundant sensor for another reading. Select an unused temperature sensor, and another + * sensor you'd like it to be redundant for. If the two thermistors differ by TEMP_SENSOR_REDUNDANT_MAX_DIFF (°C), + * the print will be aborted. Whichever sensor is selected will have its normal functions disabled; i.e. selecting + * the Bed sensor (-1) will disable bed heating/monitoring. + * + * For selecting source/target use: COOLER, PROBE, BOARD, CHAMBER, BED, E0, E1, E2, E3, E4, E5, E6, E7 + */ +#if TEMP_SENSOR_REDUNDANT + #define TEMP_SENSOR_REDUNDANT_SOURCE E1 // The sensor that will provide the redundant reading. + #define TEMP_SENSOR_REDUNDANT_TARGET E0 // The sensor that we are providing a redundant reading for. + #define TEMP_SENSOR_REDUNDANT_MAX_DIFF 10 // (°C) Temperature difference that will trigger a print abort. +#endif + +// Below this temperature the heater will be switched off +// because it probably indicates a broken thermistor wire. +#define HEATER_0_MINTEMP 5 +#define HEATER_1_MINTEMP 5 +#define HEATER_2_MINTEMP 5 +#define HEATER_3_MINTEMP 5 +#define HEATER_4_MINTEMP 5 +#define HEATER_5_MINTEMP 5 +#define HEATER_6_MINTEMP 5 +#define HEATER_7_MINTEMP 5 +#define BED_MINTEMP 5 +#define CHAMBER_MINTEMP 5 + +// Above this temperature the heater will be switched off. +// This can protect components from overheating, but NOT from shorts and failures. +// (Use MINTEMP for thermistor short/failure protection.) +#define HEATER_0_MAXTEMP 275 +#define HEATER_1_MAXTEMP 275 +#define HEATER_2_MAXTEMP 275 +#define HEATER_3_MAXTEMP 275 +#define HEATER_4_MAXTEMP 275 +#define HEATER_5_MAXTEMP 275 +#define HEATER_6_MAXTEMP 275 +#define HEATER_7_MAXTEMP 275 +#define BED_MAXTEMP 150 +#define CHAMBER_MAXTEMP 60 + +/** + * Thermal Overshoot + * During heatup (and printing) the temperature can often "overshoot" the target by many degrees + * (especially before PID tuning). Setting the target temperature too close to MAXTEMP guarantees + * a MAXTEMP shutdown! Use these values to forbid temperatures being set too close to MAXTEMP. + */ +#define HOTEND_OVERSHOOT 15 // (°C) Forbid temperatures over MAXTEMP - OVERSHOOT +#define BED_OVERSHOOT 10 // (°C) Forbid temperatures over MAXTEMP - OVERSHOOT +#define COOLER_OVERSHOOT 2 // (°C) Forbid temperatures closer than OVERSHOOT + +//=========================================================================== +//============================= PID Settings ================================ +//=========================================================================== + +// @section hotend temp + +/** + * Temperature Control + * + * (NONE) : Bang-bang heating + * PIDTEMP : PID temperature control (~4.1K) + * MPCTEMP : Predictive Model temperature control. (~1.8K without auto-tune) + */ +#define PIDTEMP // See the PID Tuning Guide at https://reprap.org/wiki/PID_Tuning +//#define MPCTEMP // See https://marlinfw.org/docs/features/model_predictive_control.html + +#define PID_MAX 255 // Limit hotend current while PID is active (see PID_FUNCTIONAL_RANGE below); 255=full current +#define PID_K1 0.95 // Smoothing factor within any PID loop + +#if ENABLED(PIDTEMP) + //#define PID_DEBUG // Print PID debug data to the serial port. Use 'M303 D' to toggle activation. + //#define PID_PARAMS_PER_HOTEND // Use separate PID parameters for each extruder (useful for mismatched extruders) + // Set/get with G-code: M301 E[extruder number, 0-2] + + #if ENABLED(PID_PARAMS_PER_HOTEND) + // Specify up to one value per hotend here, according to your setup. + // If there are fewer values, the last one applies to the remaining hotends. + #define DEFAULT_Kp_LIST { 22.20, 22.20 } + #define DEFAULT_Ki_LIST { 1.08, 1.08 } + #define DEFAULT_Kd_LIST { 114.00, 114.00 } + #else + #define DEFAULT_Kp 22.20 + #define DEFAULT_Ki 1.08 + #define DEFAULT_Kd 114.00 + #endif +#else + #define BANG_MAX 255 // Limit hotend current while in bang-bang mode; 255=full current +#endif + +/** + * Model Predictive Control for hotend + * + * Use a physical model of the hotend to control temperature. When configured correctly this gives + * better responsiveness and stability than PID and removes the need for PID_EXTRUSION_SCALING + * and PID_FAN_SCALING. Enable MPC_AUTOTUNE and use M306 T to autotune the model. + * @section mpc temp + */ +#if ENABLED(MPCTEMP) + #define MPC_AUTOTUNE // Include a method to do MPC auto-tuning (~6.3K bytes of flash) + #if ENABLED(MPC_AUTOTUNE) + //#define MPC_AUTOTUNE_DEBUG // Enable MPC debug logging (~870 bytes of flash) + #endif + //#define MPC_EDIT_MENU // Add MPC editing to the "Advanced Settings" menu. (~1.3K bytes of flash) + //#define MPC_AUTOTUNE_MENU // Add MPC auto-tuning to the "Advanced Settings" menu. (~350 bytes of flash) + + #define MPC_MAX 255 // (0..255) Current to nozzle while MPC is active. + #define MPC_HEATER_POWER { 40.0f } // (W) Heat cartridge powers. + + #define MPC_INCLUDE_FAN // Model the fan speed? + + // Measured physical constants from M306 + #define MPC_BLOCK_HEAT_CAPACITY { 16.7f } // (J/K) Heat block heat capacities. + #define MPC_SENSOR_RESPONSIVENESS { 0.22f } // (K/s per ∆K) Rate of change of sensor temperature from heat block. + #define MPC_AMBIENT_XFER_COEFF { 0.068f } // (W/K) Heat transfer coefficients from heat block to room air with fan off. + #if ENABLED(MPC_INCLUDE_FAN) + #define MPC_AMBIENT_XFER_COEFF_FAN255 { 0.097f } // (W/K) Heat transfer coefficients from heat block to room air with fan on full. + #endif + + // For one fan and multiple hotends MPC needs to know how to apply the fan cooling effect. + #if ENABLED(MPC_INCLUDE_FAN) + //#define MPC_FAN_0_ALL_HOTENDS + //#define MPC_FAN_0_ACTIVE_HOTEND + #endif + + // Filament Heat Capacity (joules/kelvin/mm) + // Set at runtime with M306 H + #define FILAMENT_HEAT_CAPACITY_PERMM { 5.6e-3f } // 0.0056 J/K/mm for 1.75mm PLA (0.0149 J/K/mm for 2.85mm PLA). + // 0.0036 J/K/mm for 1.75mm PETG (0.0094 J/K/mm for 2.85mm PETG). + // 0.00515 J/K/mm for 1.75mm ABS (0.0137 J/K/mm for 2.85mm ABS). + // 0.00522 J/K/mm for 1.75mm Nylon (0.0138 J/K/mm for 2.85mm Nylon). + + // Advanced options + #define MPC_SMOOTHING_FACTOR 0.5f // (0.0...1.0) Noisy temperature sensors may need a lower value for stabilization. + #define MPC_MIN_AMBIENT_CHANGE 1.0f // (K/s) Modeled ambient temperature rate of change, when correcting model inaccuracies. + #define MPC_STEADYSTATE 0.5f // (K/s) Temperature change rate for steady state logic to be enforced. + + #define MPC_TUNING_POS { X_CENTER, Y_CENTER, 1.0f } // (mm) M306 Autotuning position, ideally bed center at first layer height. + #define MPC_TUNING_END_Z 10.0f // (mm) M306 Autotuning final Z position. +#endif + +//=========================================================================== +//====================== PID > Bed Temperature Control ====================== +//=========================================================================== + +// @section bed temp + +/** + * Max Bed Power + * Applies to all forms of bed control (PID, bang-bang, and bang-bang with hysteresis). + * When set to any value below 255, enables a form of PWM to the bed that acts like a divider + * so don't use it unless you are OK with PWM on your bed. (See the comment on enabling PIDTEMPBED) + */ +#define MAX_BED_POWER 255 // limits duty cycle to bed; 255=full current + +/** + * PID Bed Heating + * + * The PID frequency will be the same as the extruder PWM. + * If PID_dT is the default, and correct for the hardware/configuration, that means 7.689Hz, + * which is fine for driving a square wave into a resistive load and does not significantly + * impact FET heating. This also works fine on a Fotek SSR-10DA Solid State Relay into a 250W + * heater. If your configuration is significantly different than this and you don't understand + * the issues involved, don't use bed PID until someone else verifies that your hardware works. + * + * With this option disabled, bang-bang will be used. BED_LIMIT_SWITCHING enables hysteresis. + */ +//#define PIDTEMPBED + +#if ENABLED(PIDTEMPBED) + //#define MIN_BED_POWER 0 + //#define PID_BED_DEBUG // Print Bed PID debug data to the serial port. + + // 120V 250W silicone heater into 4mm borosilicate (MendelMax 1.5+) + // from FOPDT model - kp=.39 Tp=405 Tdead=66, Tc set to 79.2, aggressive factor of .15 (vs .1, 1, 10) + #define DEFAULT_bedKp 10.00 + #define DEFAULT_bedKi .023 + #define DEFAULT_bedKd 305.4 + + // FIND YOUR OWN: "M303 E-1 C8 S90" to run autotune on the bed at 90 degreesC for 8 cycles. +#else + //#define BED_LIMIT_SWITCHING // Keep the bed temperature within BED_HYSTERESIS of the target +#endif + +/** + * Peltier Bed - Heating and Cooling + * + * A Peltier device transfers heat from one side to the other in proportion to the amount of + * current flowing through the device and the direction of current flow. So the same device + * can both heat and cool. + * + * When "cooling" in addition to rejecting the heat transferred from the hot side to the cold + * side, the dissipated power (voltage * current) must also be rejected. Be sure to set up a + * fan that can be powered in sync with the Peltier unit. + * + * This feature is only set up to run in bang-bang mode because Peltiers don't handle PWM + * well without filter circuitry. + * + * Since existing 3D printers are made to handle relatively high current for the heated bed, + * we can use the heated bed power pins to control the Peltier power using the same G-codes + * as the heated bed (M140, M190, etc.). + * + * A second GPIO pin is required to control current direction. + * Two configurations are possible: Relay and H-Bridge + * + * (At this time only relay is supported. H-bridge requires 4 MOS switches configured in H-Bridge.) + * + * Power is handled by the bang-bang control loop: 0 or 255. + * Cooling applications are more common than heating, so the pin states are commonly: + * LOW = Heating = Relay Energized + * HIGH = Cooling = Relay in "Normal" state + */ +//#define PELTIER_BED +#if ENABLED(PELTIER_BED) + #define PELTIER_DIR_PIN -1 // Relay control pin for Peltier + #define PELTIER_DIR_HEAT_STATE LOW // The relay pin state that causes the Peltier to heat +#endif + +// Add 'M190 R T' for more gradual M190 R bed cooling. +//#define BED_ANNEALING_GCODE + +//=========================================================================== +//==================== PID > Chamber Temperature Control ==================== +//=========================================================================== + +/** + * PID Chamber Heating + * + * If this option is enabled set PID constants below. + * If this option is disabled, bang-bang will be used and CHAMBER_LIMIT_SWITCHING will enable + * hysteresis. + * + * The PID frequency will be the same as the extruder PWM. + * If PID_dT is the default, and correct for the hardware/configuration, that means 7.689Hz, + * which is fine for driving a square wave into a resistive load and does not significantly + * impact FET heating. This also works fine on a Fotek SSR-10DA Solid State Relay into a 200W + * heater. If your configuration is significantly different than this and you don't understand + * the issues involved, don't use chamber PID until someone else verifies that your hardware works. + * @section chamber temp + */ +//#define PIDTEMPCHAMBER +//#define CHAMBER_LIMIT_SWITCHING + +/** + * Max Chamber Power + * Applies to all forms of chamber control (PID, bang-bang, and bang-bang with hysteresis). + * When set to any value below 255, enables a form of PWM to the chamber heater that acts like a divider + * so don't use it unless you are OK with PWM on your heater. (See the comment on enabling PIDTEMPCHAMBER) + */ +#define MAX_CHAMBER_POWER 255 // limits duty cycle to chamber heater; 255=full current + +#if ENABLED(PIDTEMPCHAMBER) + #define MIN_CHAMBER_POWER 0 + //#define PID_CHAMBER_DEBUG // Print Chamber PID debug data to the serial port. + + // Lasko "MyHeat Personal Heater" (200w) modified with a Fotek SSR-10DA to control only the heating element + // and placed inside the small Creality printer enclosure tent. + // + #define DEFAULT_chamberKp 37.04 + #define DEFAULT_chamberKi 1.40 + #define DEFAULT_chamberKd 655.17 + // M309 P37.04 I1.04 D655.17 + + // FIND YOUR OWN: "M303 E-2 C8 S50" to run autotune on the chamber at 50 degreesC for 8 cycles. +#endif // PIDTEMPCHAMBER + +// @section pid temp + +#if ANY(PIDTEMP, PIDTEMPBED, PIDTEMPCHAMBER) + //#define PID_OPENLOOP // Puts PID in open loop. M104/M140 sets the output power from 0 to PID_MAX + //#define SLOW_PWM_HEATERS // PWM with very low frequency (roughly 0.125Hz=8s) and minimum state time of approximately 1s useful for heaters driven by a relay + #define PID_FUNCTIONAL_RANGE 10 // If the temperature difference between the target temperature and the actual temperature + // is more than PID_FUNCTIONAL_RANGE then the PID will be shut off and the heater will be set to min/max. + + //#define PID_EDIT_MENU // Add PID editing to the "Advanced Settings" menu. (~700 bytes of flash) + //#define PID_AUTOTUNE_MENU // Add PID auto-tuning to the "Advanced Settings" menu. (~250 bytes of flash) +#endif + +// @section safety + +/** + * Prevent extrusion if the temperature is below EXTRUDE_MINTEMP. + * Add M302 to set the minimum extrusion temperature and/or turn + * cold extrusion prevention on and off. + * + * *** IT IS HIGHLY RECOMMENDED TO LEAVE THIS OPTION ENABLED! *** + */ +#define PREVENT_COLD_EXTRUSION +#define EXTRUDE_MINTEMP 170 + +/** + * Prevent a single extrusion longer than EXTRUDE_MAXLENGTH. + * Note: For Bowden Extruders make this large enough to allow load/unload. + */ +#define PREVENT_LENGTHY_EXTRUDE +#define EXTRUDE_MAXLENGTH 200 + +//=========================================================================== +//======================== Thermal Runaway Protection ======================= +//=========================================================================== + +/** + * Thermal Protection provides additional protection to your printer from damage + * and fire. Marlin always includes safe min and max temperature ranges which + * protect against a broken or disconnected thermistor wire. + * + * The issue: If a thermistor falls out, it will report the much lower + * temperature of the air in the room, and the the firmware will keep + * the heater on. + * + * If you get "Thermal Runaway" or "Heating failed" errors the + * details can be tuned in Configuration_adv.h + */ + +#define THERMAL_PROTECTION_HOTENDS // Enable thermal protection for all extruders +#define THERMAL_PROTECTION_BED // Enable thermal protection for the heated bed +#define THERMAL_PROTECTION_CHAMBER // Enable thermal protection for the heated chamber +#define THERMAL_PROTECTION_COOLER // Enable thermal protection for the laser cooling + +//=========================================================================== +//============================= Mechanical Settings ========================= +//=========================================================================== + +// @section kinematics + +// Enable one of the options below for CoreXY, CoreXZ, or CoreYZ kinematics, +// either in the usual order or reversed +//#define COREXY +//#define COREXZ +//#define COREYZ +//#define COREYX +//#define COREZX +//#define COREZY + +// +// MarkForged Kinematics +// See https://reprap.org/forum/read.php?152,504042 +// +//#define MARKFORGED_XY +//#define MARKFORGED_YX +#if ANY(MARKFORGED_XY, MARKFORGED_YX) + //#define MARKFORGED_INVERSE // Enable for an inverted Markforged kinematics belt path +#endif + +// Enable for a belt style printer with endless "Z" motion +//#define BELTPRINTER + +// Articulated robot (arm). Joints are directly mapped to axes with no kinematics. +//#define ARTICULATED_ROBOT_ARM + +// For a hot wire cutter with parallel horizontal axes (X, I) where the heights of the two wire +// ends are controlled by parallel axes (Y, J). Joints are directly mapped to axes (no kinematics). +//#define FOAMCUTTER_XYUV + +// @section polargraph + +// Enable for Polargraph Kinematics +//#define POLARGRAPH +#if ENABLED(POLARGRAPH) + #define POLARGRAPH_MAX_BELT_LEN 1035.0 // (mm) Belt length at full extension. Override with M665 H. + #define DEFAULT_SEGMENTS_PER_SECOND 5 // Move segmentation based on duration + #define PEN_UP_DOWN_MENU // Add "Pen Up" and "Pen Down" to the MarlinUI menu +#endif + +// @section delta + +// Enable for DELTA kinematics and configure below +//#define DELTA +#if ENABLED(DELTA) + + // Make delta curves from many straight lines (linear interpolation). + // This is a trade-off between visible corners (not enough segments) + // and processor overload (too many expensive sqrt calls). + #define DEFAULT_SEGMENTS_PER_SECOND 200 + + // After homing move down to a height where XY movement is unconstrained + //#define DELTA_HOME_TO_SAFE_ZONE + + // Delta calibration menu + // Add three-point calibration to the MarlinUI menu. + // See http://minow.blogspot.com/index.html#4918805519571907051 + //#define DELTA_CALIBRATION_MENU + + // G33 Delta Auto-Calibration. Enable EEPROM_SETTINGS to store results. + //#define DELTA_AUTO_CALIBRATION + + #if ENABLED(DELTA_AUTO_CALIBRATION) + // Default number of probe points : n*n (1 -> 7) + #define DELTA_CALIBRATION_DEFAULT_POINTS 4 + #endif + + #if ANY(DELTA_AUTO_CALIBRATION, DELTA_CALIBRATION_MENU) + // Step size for paper-test probing + #define PROBE_MANUALLY_STEP 0.05 // (mm) + #endif + + // Print surface diameter/2 minus unreachable space (avoid collisions with vertical towers). + #define PRINTABLE_RADIUS 140.0 // (mm) + + // Maximum reachable area + #define DELTA_MAX_RADIUS 140.0 // (mm) + + // Center-to-center distance of the holes in the diagonal push rods. + #define DELTA_DIAGONAL_ROD 250.0 // (mm) + + // Distance between bed and nozzle Z home position + #define DELTA_HEIGHT 250.00 // (mm) Get this value from G33 auto calibrate + + #define DELTA_ENDSTOP_ADJ { 0.0, 0.0, 0.0 } // (mm) Get these values from G33 auto calibrate + + // Horizontal distance bridged by diagonal push rods when effector is centered. + #define DELTA_RADIUS 124.0 // (mm) Get this value from G33 auto calibrate + + // Trim adjustments for individual towers + // tower angle corrections for X and Y tower / rotate XYZ so Z tower angle = 0 + // measured in degrees anticlockwise looking from above the printer + #define DELTA_TOWER_ANGLE_TRIM { 0.0, 0.0, 0.0 } // (mm) Get these values from G33 auto calibrate + + // Delta radius and diagonal rod adjustments + //#define DELTA_RADIUS_TRIM_TOWER { 0.0, 0.0, 0.0 } // (mm) + //#define DELTA_DIAGONAL_ROD_TRIM_TOWER { 0.0, 0.0, 0.0 } // (mm) +#endif + +// @section scara + +/** + * MORGAN_SCARA was developed by QHARLEY in South Africa in 2012-2013. + * Implemented and slightly reworked by JCERNY in June, 2014. + * + * Mostly Printed SCARA is an open source design by Tyler Williams. See: + * https://www.thingiverse.com/thing:2487048 + * https://www.thingiverse.com/thing:1241491 + */ +//#define MORGAN_SCARA +//#define MP_SCARA +#if ANY(MORGAN_SCARA, MP_SCARA) + // If movement is choppy try lowering this value + #define DEFAULT_SEGMENTS_PER_SECOND 200 + + // Length of inner and outer support arms. Measure arm lengths precisely. + #define SCARA_LINKAGE_1 150 // (mm) + #define SCARA_LINKAGE_2 150 // (mm) + + // SCARA tower offset (position of Tower relative to bed zero position) + // This needs to be reasonably accurate as it defines the printbed position in the SCARA space. + #define SCARA_OFFSET_X 100 // (mm) + #define SCARA_OFFSET_Y -56 // (mm) + + #if ENABLED(MORGAN_SCARA) + + //#define DEBUG_SCARA_KINEMATICS + #define FEEDRATE_SCALING // Convert XY feedrate from mm/s to degrees/s on the fly + + // Radius around the center where the arm cannot reach + #define MIDDLE_DEAD_ZONE_R 0 // (mm) + + #elif ENABLED(MP_SCARA) + + #define SCARA_OFFSET_THETA1 12 // degrees + #define SCARA_OFFSET_THETA2 131 // degrees + + #endif + +#endif + +// @section tpara + +// Enable for TPARA kinematics and configure below +//#define AXEL_TPARA +#if ENABLED(AXEL_TPARA) + #define DEBUG_TPARA_KINEMATICS + #define DEFAULT_SEGMENTS_PER_SECOND 200 + + // Length of inner and outer support arms. Measure arm lengths precisely. + #define TPARA_LINKAGE_1 120 // (mm) + #define TPARA_LINKAGE_2 120 // (mm) + + // TPARA tower offset (position of Tower relative to bed zero position) + // This needs to be reasonably accurate as it defines the printbed position in the TPARA space. + #define TPARA_OFFSET_X 0 // (mm) + #define TPARA_OFFSET_Y 0 // (mm) + #define TPARA_OFFSET_Z 0 // (mm) + + #define FEEDRATE_SCALING // Convert XY feedrate from mm/s to degrees/s on the fly + + // Radius around the center where the arm cannot reach + #define MIDDLE_DEAD_ZONE_R 0 // (mm) +#endif + +// @section polar + +/** + * POLAR Kinematics + * developed by Kadir ilkimen for PolarBear CNC and babyBear + * https://github.com/kadirilkimen/Polar-Bear-Cnc-Machine + * https://github.com/kadirilkimen/babyBear-3D-printer + * + * A polar machine can have different configurations. + * This kinematics is only compatible with the following configuration: + * X : Independent linear + * Y or B : Polar + * Z : Independent linear + * + * For example, PolarBear has CoreXZ plus Polar Y or B. + * + * Motion problem for Polar axis near center / origin: + * + * 3D printing: + * Movements very close to the center of the polar axis take more time than others. + * This brief delay results in more material deposition due to the pressure in the nozzle. + * + * Current Kinematics and feedrate scaling deals with this by making the movement as fast + * as possible. It works for slow movements but doesn't work well with fast ones. A more + * complicated extrusion compensation must be implemented. + * + * Ideally, it should estimate that a long rotation near the center is ahead and will cause + * unwanted deposition. Therefore it can compensate the extrusion beforehand. + * + * Laser cutting: + * Same thing would be a problem for laser engraving too. As it spends time rotating at the + * center point, more likely it will burn more material than it should. Therefore similar + * compensation would be implemented for laser-cutting operations. + * + * Milling: + * This shouldn't be a problem for cutting/milling operations. + */ +//#define POLAR +#if ENABLED(POLAR) + #define DEFAULT_SEGMENTS_PER_SECOND 180 // If movement is choppy try lowering this value + #define PRINTABLE_RADIUS 82.0f // (mm) Maximum travel of X axis + + // Movements fall inside POLAR_FAST_RADIUS are assigned the highest possible feedrate + // to compensate unwanted deposition related to the near-origin motion problem. + #define POLAR_FAST_RADIUS 3.0f // (mm) + + // Radius which is unreachable by the tool. + // Needed if the tool is not perfectly aligned to the center of the polar axis. + #define POLAR_CENTER_OFFSET 0.0f // (mm) + + #define FEEDRATE_SCALING // Convert XY feedrate from mm/s to degrees/s on the fly +#endif + +//=========================================================================== +//============================== Endstop Settings =========================== +//=========================================================================== + +// @section endstops + +// Enable pullup for all endstops to prevent a floating state +#define ENDSTOPPULLUPS +#if DISABLED(ENDSTOPPULLUPS) + // Disable ENDSTOPPULLUPS to set pullups individually + //#define ENDSTOPPULLUP_XMIN + //#define ENDSTOPPULLUP_YMIN + //#define ENDSTOPPULLUP_ZMIN + //#define ENDSTOPPULLUP_IMIN + //#define ENDSTOPPULLUP_JMIN + //#define ENDSTOPPULLUP_KMIN + //#define ENDSTOPPULLUP_UMIN + //#define ENDSTOPPULLUP_VMIN + //#define ENDSTOPPULLUP_WMIN + //#define ENDSTOPPULLUP_XMAX + //#define ENDSTOPPULLUP_YMAX + //#define ENDSTOPPULLUP_ZMAX + //#define ENDSTOPPULLUP_IMAX + //#define ENDSTOPPULLUP_JMAX + //#define ENDSTOPPULLUP_KMAX + //#define ENDSTOPPULLUP_UMAX + //#define ENDSTOPPULLUP_VMAX + //#define ENDSTOPPULLUP_WMAX + //#define ENDSTOPPULLUP_ZMIN_PROBE +#endif + +// Enable pulldown for all endstops to prevent a floating state +//#define ENDSTOPPULLDOWNS +#if DISABLED(ENDSTOPPULLDOWNS) + // Disable ENDSTOPPULLDOWNS to set pulldowns individually + //#define ENDSTOPPULLDOWN_XMIN + //#define ENDSTOPPULLDOWN_YMIN + //#define ENDSTOPPULLDOWN_ZMIN + //#define ENDSTOPPULLDOWN_IMIN + //#define ENDSTOPPULLDOWN_JMIN + //#define ENDSTOPPULLDOWN_KMIN + //#define ENDSTOPPULLDOWN_UMIN + //#define ENDSTOPPULLDOWN_VMIN + //#define ENDSTOPPULLDOWN_WMIN + //#define ENDSTOPPULLDOWN_XMAX + //#define ENDSTOPPULLDOWN_YMAX + //#define ENDSTOPPULLDOWN_ZMAX + //#define ENDSTOPPULLDOWN_IMAX + //#define ENDSTOPPULLDOWN_JMAX + //#define ENDSTOPPULLDOWN_KMAX + //#define ENDSTOPPULLDOWN_UMAX + //#define ENDSTOPPULLDOWN_VMAX + //#define ENDSTOPPULLDOWN_WMAX + //#define ENDSTOPPULLDOWN_ZMIN_PROBE +#endif + +/** + * Endstop "Hit" State + * Set to the state (HIGH or LOW) that applies to each endstop. + */ +#define X_MIN_ENDSTOP_HIT_STATE HIGH +#define X_MAX_ENDSTOP_HIT_STATE HIGH +#define Y_MIN_ENDSTOP_HIT_STATE HIGH +#define Y_MAX_ENDSTOP_HIT_STATE HIGH +#define Z_MIN_ENDSTOP_HIT_STATE HIGH +#define Z_MAX_ENDSTOP_HIT_STATE HIGH +#define I_MIN_ENDSTOP_HIT_STATE HIGH +#define I_MAX_ENDSTOP_HIT_STATE HIGH +#define J_MIN_ENDSTOP_HIT_STATE HIGH +#define J_MAX_ENDSTOP_HIT_STATE HIGH +#define K_MIN_ENDSTOP_HIT_STATE HIGH +#define K_MAX_ENDSTOP_HIT_STATE HIGH +#define U_MIN_ENDSTOP_HIT_STATE HIGH +#define U_MAX_ENDSTOP_HIT_STATE HIGH +#define V_MIN_ENDSTOP_HIT_STATE HIGH +#define V_MAX_ENDSTOP_HIT_STATE HIGH +#define W_MIN_ENDSTOP_HIT_STATE HIGH +#define W_MAX_ENDSTOP_HIT_STATE HIGH +#define Z_MIN_PROBE_ENDSTOP_HIT_STATE HIGH + +// Enable this feature if all enabled endstop pins are interrupt-capable. +// This will remove the need to poll the interrupt pins, saving many CPU cycles. +//#define ENDSTOP_INTERRUPTS_FEATURE + +/** + * Endstop Noise Threshold + * + * Enable if your probe or endstops falsely trigger due to noise. + * + * - Higher values may affect repeatability or accuracy of some bed probes. + * - To fix noise install a 100nF ceramic capacitor in parallel with the switch. + * - This feature is not required for common micro-switches mounted on PCBs + * based on the Makerbot design, which already have the 100nF capacitor. + * + * :[2,3,4,5,6,7] + */ +//#define ENDSTOP_NOISE_THRESHOLD 2 + +// Check for stuck or disconnected endstops during homing moves. +//#define DETECT_BROKEN_ENDSTOP + +//============================================================================= +//============================== Movement Settings ============================ +//============================================================================= +// @section motion + +/** + * Default Settings + * + * These settings can be reset by M502 + * + * Note that if EEPROM is enabled, saved values will override these. + */ + +/** + * With this option each E stepper can have its own factors for the + * following movement settings. If fewer factors are given than the + * total number of extruders, the last value applies to the rest. + */ +//#define DISTINCT_E_FACTORS + +/** + * Default Axis Steps Per Unit (linear=steps/mm, rotational=steps/°) + * Override with M92 (when enabled below) + * X, Y, Z [, I [, J [, K...]]], E0 [, E1[, E2...]] + */ +#define DEFAULT_AXIS_STEPS_PER_UNIT { 80, 80, 400, 500 } + +/** + * Enable support for M92. Disable to save at least ~530 bytes of flash. + */ +#define EDITABLE_STEPS_PER_UNIT + +/** + * Default Max Feed Rate (linear=mm/s, rotational=°/s) + * Override with M203 + * X, Y, Z [, I [, J [, K...]]], E0 [, E1[, E2...]] + */ +#define DEFAULT_MAX_FEEDRATE { 300, 300, 5, 25 } + +//#define LIMITED_MAX_FR_EDITING // Limit edit via M203 or LCD to DEFAULT_MAX_FEEDRATE * 2 +#if ENABLED(LIMITED_MAX_FR_EDITING) + #define MAX_FEEDRATE_EDIT_VALUES { 600, 600, 10, 50 } // ...or, set your own edit limits +#endif + +/** + * Default Max Acceleration (speed change with time) (linear=mm/(s^2), rotational=°/(s^2)) + * (Maximum start speed for accelerated moves) + * Override with M201 + * X, Y, Z [, I [, J [, K...]]], E0 [, E1[, E2...]] + */ +#define DEFAULT_MAX_ACCELERATION { 3000, 3000, 100, 10000 } + +//#define LIMITED_MAX_ACCEL_EDITING // Limit edit via M201 or LCD to DEFAULT_MAX_ACCELERATION * 2 +#if ENABLED(LIMITED_MAX_ACCEL_EDITING) + #define MAX_ACCEL_EDIT_VALUES { 6000, 6000, 200, 20000 } // ...or, set your own edit limits +#endif + +/** + * Default Acceleration (speed change with time) (linear=mm/(s^2), rotational=°/(s^2)) + * Override with M204 + * + * M204 P Acceleration + * M204 R Retract Acceleration + * M204 T Travel Acceleration + */ +#define DEFAULT_ACCELERATION 3000 // X, Y, Z and E acceleration for printing moves +#define DEFAULT_RETRACT_ACCELERATION 3000 // E acceleration for retracts +#define DEFAULT_TRAVEL_ACCELERATION 3000 // X, Y, Z acceleration for travel (non printing) moves + +/** + * Default Jerk limits (mm/s) + * Override with M205 X Y Z . . . E + * + * "Jerk" specifies the minimum speed change that requires acceleration. + * When changing speed and direction, if the difference is less than the + * value set here, it may happen instantaneously. + */ +//#define CLASSIC_JERK +#if ENABLED(CLASSIC_JERK) + #define DEFAULT_XJERK 10.0 + #define DEFAULT_YJERK 10.0 + #define DEFAULT_ZJERK 0.3 + #define DEFAULT_EJERK 5.0 + //#define DEFAULT_IJERK 0.3 + //#define DEFAULT_JJERK 0.3 + //#define DEFAULT_KJERK 0.3 + //#define DEFAULT_UJERK 0.3 + //#define DEFAULT_VJERK 0.3 + //#define DEFAULT_WJERK 0.3 + + //#define TRAVEL_EXTRA_XYJERK 0.0 // Additional jerk allowance for all travel moves + + //#define LIMITED_JERK_EDITING // Limit edit via M205 or LCD to DEFAULT_aJERK * 2 + #if ENABLED(LIMITED_JERK_EDITING) + #define MAX_JERK_EDIT_VALUES { 20, 20, 0.6, 10 } // ...or, set your own edit limits + #endif +#endif + +/** + * Junction Deviation Factor + * + * See: + * https://reprap.org/forum/read.php?1,739819 + * https://blog.kyneticcnc.com/2018/10/computing-junction-deviation-for-marlin.html + */ +#if DISABLED(CLASSIC_JERK) + #define JUNCTION_DEVIATION_MM 0.013 // (mm) Distance from real junction edge + #define JD_HANDLE_SMALL_SEGMENTS // Use curvature estimation instead of just the junction angle + // for small segments (< 1mm) with large junction angles (> 135°). +#endif + +/** + * S-Curve Acceleration + * + * This option eliminates vibration during printing by fitting a Bézier + * curve to move acceleration, producing much smoother direction changes. + * + * See https://github.com/synthetos/TinyG/wiki/Jerk-Controlled-Motion-Explained + */ +//#define S_CURVE_ACCELERATION + +//=========================================================================== +//============================= Z Probe Options ============================= +//=========================================================================== +// @section probes + +// +// See https://marlinfw.org/docs/configuration/probes.html +// + +/** + * Enable this option for a probe connected to the Z-MIN pin. + * The probe replaces the Z-MIN endstop and is used for Z homing. + * (Automatically enables USE_PROBE_FOR_Z_HOMING.) + */ +#define Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN + +// Force the use of the probe for Z-axis homing +//#define USE_PROBE_FOR_Z_HOMING + +/** + * Z_MIN_PROBE_PIN + * + * Override this pin only if the probe cannot be connected to + * the default Z_MIN_PROBE_PIN for the selected MOTHERBOARD. + * + * - The simplest option is to use a free endstop connector. + * - Use 5V for powered (usually inductive) sensors. + * + * - For simple switches... + * - Normally-closed (NC) also connect to GND. + * - Normally-open (NO) also connect to 5V. + */ +//#define Z_MIN_PROBE_PIN -1 + +/** + * Probe Type + * + * Allen Key Probes, Servo Probes, Z-Sled Probes, FIX_MOUNTED_PROBE, etc. + * Activate one of these to use Auto Bed Leveling below. + */ + +/** + * The "Manual Probe" provides a means to do "Auto" Bed Leveling without a probe. + * Use G29 repeatedly, adjusting the Z height at each point with movement commands + * or (with LCD_BED_LEVELING) the LCD controller. + */ +//#define PROBE_MANUALLY + +/** + * A Fix-Mounted Probe either doesn't deploy or needs manual deployment. + * (e.g., an inductive probe or a nozzle-based probe-switch.) + */ +//#define FIX_MOUNTED_PROBE + +/** + * Use the nozzle as the probe, as with a conductive + * nozzle system or a piezo-electric smart effector. + */ +//#define NOZZLE_AS_PROBE + +/** + * Z Servo Probe, such as an endstop switch on a rotating arm. + */ +//#define Z_PROBE_SERVO_NR 0 +#ifdef Z_PROBE_SERVO_NR + //#define Z_SERVO_ANGLES { 70, 0 } // Z Servo Deploy and Stow angles + //#define Z_SERVO_MEASURE_ANGLE 45 // Use if the servo must move to a "free" position for measuring after deploy + //#define Z_SERVO_INTERMEDIATE_STOW // Stow the probe between points + //#define Z_SERVO_DEACTIVATE_AFTER_STOW // Deactivate the servo when probe is stowed +#endif + +/** + * The BLTouch probe uses a Hall effect sensor and emulates a servo. + */ +//#define BLTOUCH + +/** + * MagLev V4 probe by MDD + * + * This probe is deployed and activated by powering a built-in electromagnet. + */ +//#define MAGLEV4 +#if ENABLED(MAGLEV4) + //#define MAGLEV_TRIGGER_PIN 11 // Set to the connected digital output + #define MAGLEV_TRIGGER_DELAY 15 // Changing this risks overheating the coil +#endif + +/** + * Touch-MI Probe by hotends.fr + * + * This probe is deployed and activated by moving the X-axis to a magnet at the edge of the bed. + * By default, the magnet is assumed to be on the left and activated by a home. If the magnet is + * on the right, enable and set TOUCH_MI_DEPLOY_XPOS to the deploy position. + * + * Also requires: BABYSTEPPING, BABYSTEP_ZPROBE_OFFSET, Z_SAFE_HOMING, + * and a minimum Z_CLEARANCE_FOR_HOMING of 10. + */ +//#define TOUCH_MI_PROBE +#if ENABLED(TOUCH_MI_PROBE) + #define TOUCH_MI_RETRACT_Z 0.5 // Height at which the probe retracts + //#define TOUCH_MI_DEPLOY_XPOS (X_MAX_BED + 2) // For a magnet on the right side of the bed + //#define TOUCH_MI_MANUAL_DEPLOY // For manual deploy (LCD menu) +#endif + +/** + * Bed Distance Sensor + * + * Measures the distance from bed to nozzle with accuracy of 0.01mm. + * For information about this sensor https://github.com/markniu/Bed_Distance_sensor + * Uses I2C port, so it requires I2C library markyue/Panda_SoftMasterI2C. + */ +//#define BD_SENSOR +#if ENABLED(BD_SENSOR) + //#define BD_SENSOR_PROBE_NO_STOP // Probe bed without stopping at each probe point +#endif + +/** + * BIQU MicroProbe + * + * A lightweight, solenoid-driven probe. + * For information about this sensor https://github.com/bigtreetech/MicroProbe + * + * Also requires PROBE_ENABLE_DISABLE + */ +//#define BIQU_MICROPROBE_V1 // Triggers HIGH +//#define BIQU_MICROPROBE_V2 // Triggers LOW + +// A probe that is deployed and stowed with a solenoid pin (SOL1_PIN) +//#define SOLENOID_PROBE + +// A sled-mounted probe like those designed by Charles Bell. +//#define Z_PROBE_SLED +//#define SLED_DOCKING_OFFSET 5 // The extra distance the X axis must travel to pickup the sled. 0 should be fine but you can push it further if you'd like. + +// A probe deployed by moving the x-axis, such as the Wilson II's rack-and-pinion probe designed by Marty Rice. +//#define RACK_AND_PINION_PROBE +#if ENABLED(RACK_AND_PINION_PROBE) + #define Z_PROBE_DEPLOY_X X_MIN_POS + #define Z_PROBE_RETRACT_X X_MAX_POS +#endif + +/** + * Magnetically Mounted Probe + * For probes such as Euclid, Klicky, Klackender, etc. + */ +//#define MAG_MOUNTED_PROBE +#if ENABLED(MAG_MOUNTED_PROBE) + #define PROBE_DEPLOY_FEEDRATE (133*60) // (mm/min) Probe deploy speed + #define PROBE_STOW_FEEDRATE (133*60) // (mm/min) Probe stow speed + + #define MAG_MOUNTED_DEPLOY_1 { PROBE_DEPLOY_FEEDRATE, { 245, 114, 30 } } // Move to side Dock & Attach probe + #define MAG_MOUNTED_DEPLOY_2 { PROBE_DEPLOY_FEEDRATE, { 210, 114, 30 } } // Move probe off dock + #define MAG_MOUNTED_DEPLOY_3 { PROBE_DEPLOY_FEEDRATE, { 0, 0, 0 } } // Extra move if needed + #define MAG_MOUNTED_DEPLOY_4 { PROBE_DEPLOY_FEEDRATE, { 0, 0, 0 } } // Extra move if needed + #define MAG_MOUNTED_DEPLOY_5 { PROBE_DEPLOY_FEEDRATE, { 0, 0, 0 } } // Extra move if needed + #define MAG_MOUNTED_STOW_1 { PROBE_STOW_FEEDRATE, { 245, 114, 20 } } // Move to dock + #define MAG_MOUNTED_STOW_2 { PROBE_STOW_FEEDRATE, { 245, 114, 0 } } // Place probe beside remover + #define MAG_MOUNTED_STOW_3 { PROBE_STOW_FEEDRATE, { 230, 114, 0 } } // Side move to remove probe + #define MAG_MOUNTED_STOW_4 { PROBE_STOW_FEEDRATE, { 210, 114, 20 } } // Side move to remove probe + #define MAG_MOUNTED_STOW_5 { PROBE_STOW_FEEDRATE, { 0, 0, 0 } } // Extra move if needed +#endif + +// Duet Smart Effector (for delta printers) - https://docs.duet3d.com/en/Duet3D_hardware/Accessories/Smart_Effector +// When the pin is defined you can use M672 to set/reset the probe sensitivity. +//#define DUET_SMART_EFFECTOR +#if ENABLED(DUET_SMART_EFFECTOR) + #define SMART_EFFECTOR_MOD_PIN -1 // Connect a GPIO pin to the Smart Effector MOD pin +#endif + +/** + * Use StallGuard2 to probe the bed with the nozzle. + * Requires stallGuard-capable Trinamic stepper drivers. + * CAUTION: This can damage machines with Z lead screws. + * Take extreme care when setting up this feature. + */ +//#define SENSORLESS_PROBING + +/** + * Allen key retractable z-probe as seen on many Kossel delta printers - https://reprap.org/wiki/Kossel#Autolevel_probe + * Deploys by touching z-axis belt. Retracts by pushing the probe down. + */ +//#define Z_PROBE_ALLEN_KEY +#if ENABLED(Z_PROBE_ALLEN_KEY) + // 2 or 3 sets of coordinates for deploying and retracting the spring loaded touch probe on G29, + // if servo actuated touch probe is not defined. Uncomment as appropriate for your printer/probe. + + #define Z_PROBE_ALLEN_KEY_DEPLOY_1 { 30.0, PRINTABLE_RADIUS, 100.0 } + #define Z_PROBE_ALLEN_KEY_DEPLOY_1_FEEDRATE XY_PROBE_FEEDRATE + + #define Z_PROBE_ALLEN_KEY_DEPLOY_2 { 0.0, PRINTABLE_RADIUS, 100.0 } + #define Z_PROBE_ALLEN_KEY_DEPLOY_2_FEEDRATE (XY_PROBE_FEEDRATE)/10 + + #define Z_PROBE_ALLEN_KEY_DEPLOY_3 { 0.0, (PRINTABLE_RADIUS) * 0.75, 100.0 } + #define Z_PROBE_ALLEN_KEY_DEPLOY_3_FEEDRATE XY_PROBE_FEEDRATE + + #define Z_PROBE_ALLEN_KEY_STOW_1 { -64.0, 56.0, 23.0 } // Move the probe into position + #define Z_PROBE_ALLEN_KEY_STOW_1_FEEDRATE XY_PROBE_FEEDRATE + + #define Z_PROBE_ALLEN_KEY_STOW_2 { -64.0, 56.0, 3.0 } // Push it down + #define Z_PROBE_ALLEN_KEY_STOW_2_FEEDRATE (XY_PROBE_FEEDRATE)/10 + + #define Z_PROBE_ALLEN_KEY_STOW_3 { -64.0, 56.0, 50.0 } // Move it up to clear + #define Z_PROBE_ALLEN_KEY_STOW_3_FEEDRATE XY_PROBE_FEEDRATE + + #define Z_PROBE_ALLEN_KEY_STOW_4 { 0.0, 0.0, 50.0 } + #define Z_PROBE_ALLEN_KEY_STOW_4_FEEDRATE XY_PROBE_FEEDRATE + +#endif // Z_PROBE_ALLEN_KEY + +/** + * Nozzle-to-Probe offsets { X, Y, Z } + * + * X and Y offset + * Use a caliper or ruler to measure the distance from the tip of + * the Nozzle to the center-point of the Probe in the X and Y axes. + * + * Z offset + * - For the Z offset use your best known value and adjust at runtime. + * - Common probes trigger below the nozzle and have negative values for Z offset. + * - Probes triggering above the nozzle height are uncommon but do exist. When using + * probes such as this, carefully set Z_CLEARANCE_DEPLOY_PROBE and Z_CLEARANCE_BETWEEN_PROBES + * to avoid collisions during probing. + * + * Tune and Adjust + * - Probe Offsets can be tuned at runtime with 'M851', LCD menus, babystepping, etc. + * - PROBE_OFFSET_WIZARD (Configuration_adv.h) can be used for setting the Z offset. + * + * Assuming the typical work area orientation: + * - Probe to RIGHT of the Nozzle has a Positive X offset + * - Probe to LEFT of the Nozzle has a Negative X offset + * - Probe in BACK of the Nozzle has a Positive Y offset + * - Probe in FRONT of the Nozzle has a Negative Y offset + * + * Some examples: + * #define NOZZLE_TO_PROBE_OFFSET { 10, 10, -1 } // Example "1" + * #define NOZZLE_TO_PROBE_OFFSET {-10, 5, -1 } // Example "2" + * #define NOZZLE_TO_PROBE_OFFSET { 5, -5, -1 } // Example "3" + * #define NOZZLE_TO_PROBE_OFFSET {-15,-10, -1 } // Example "4" + * + * +-- BACK ---+ + * | [+] | + * L | 1 | R <-- Example "1" (right+, back+) + * E | 2 | I <-- Example "2" ( left-, back+) + * F |[-] N [+]| G <-- Nozzle + * T | 3 | H <-- Example "3" (right+, front-) + * | 4 | T <-- Example "4" ( left-, front-) + * | [-] | + * O-- FRONT --+ + */ +#define NOZZLE_TO_PROBE_OFFSET { 10, 10, 0 } + +// Enable and set to use a specific tool for probing. Disable to allow any tool. +#define PROBING_TOOL 0 +#ifdef PROBING_TOOL + //#define PROBE_TOOLCHANGE_NO_MOVE // Suppress motion on probe tool-change +#endif + +// Most probes should stay away from the edges of the bed, but +// with NOZZLE_AS_PROBE this can be negative for a wider probing area. +#define PROBING_MARGIN 10 + +// X and Y axis travel speed between probes. +// Leave undefined to use the average of the current XY homing feedrate. +#define XY_PROBE_FEEDRATE (133*60) // (mm/min) + +// Feedrate for the first approach when double-probing (MULTIPLE_PROBING == 2) +#define Z_PROBE_FEEDRATE_FAST (4*60) // (mm/min) + +// Feedrate for the "accurate" probe of each point +#define Z_PROBE_FEEDRATE_SLOW (Z_PROBE_FEEDRATE_FAST / 2) // (mm/min) + +/** + * Probe Activation Switch + * A switch indicating proper deployment, or an optical + * switch triggered when the carriage is near the bed. + */ +//#define PROBE_ACTIVATION_SWITCH +#if ENABLED(PROBE_ACTIVATION_SWITCH) + #define PROBE_ACTIVATION_SWITCH_STATE LOW // State indicating probe is active + //#define PROBE_ACTIVATION_SWITCH_PIN PC6 // Override default pin +#endif + +/** + * Tare Probe (determine zero-point) prior to each probe. + * Useful for a strain gauge or piezo sensor that needs to factor out + * elements such as cables pulling on the carriage. + */ +//#define PROBE_TARE +#if ENABLED(PROBE_TARE) + #define PROBE_TARE_TIME 200 // (ms) Time to hold tare pin + #define PROBE_TARE_DELAY 200 // (ms) Delay after tare before + #define PROBE_TARE_STATE HIGH // State to write pin for tare + //#define PROBE_TARE_PIN PA5 // Override default pin + //#define PROBE_TARE_MENU // Display a menu item to tare the probe + #if ENABLED(PROBE_ACTIVATION_SWITCH) + //#define PROBE_TARE_ONLY_WHILE_INACTIVE // Fail to tare/probe if PROBE_ACTIVATION_SWITCH is active + #endif +#endif + +/** + * Probe Enable / Disable + * The probe only provides a triggered signal when enabled. + */ +//#define PROBE_ENABLE_DISABLE +#if ENABLED(PROBE_ENABLE_DISABLE) + //#define PROBE_ENABLE_PIN -1 // Override the default pin here +#endif + +/** + * Multiple Probing + * + * You may get improved results by probing 2 or more times. + * With EXTRA_PROBING the more atypical reading(s) will be disregarded. + * + * A total of 2 does fast/slow probes with a weighted average. + * A total of 3 or more adds more slow probes, taking the average. + */ +//#define MULTIPLE_PROBING 2 +//#define EXTRA_PROBING 1 + +/** + * Z probes require clearance when deploying, stowing, and moving between + * probe points to avoid hitting the bed and other hardware. + * Servo-mounted probes require extra space for the arm to rotate. + * Inductive probes need space to keep from triggering early. + * + * Use these settings to specify the distance (mm) to raise the probe (or + * lower the bed). The values set here apply over and above any (negative) + * probe Z Offset set with NOZZLE_TO_PROBE_OFFSET, M851, or the LCD. + * Only integer values >= 1 are valid here. + * + * Example: 'M851 Z-5' with a CLEARANCE of 4 => 9mm from bed to nozzle. + * But: 'M851 Z+1' with a CLEARANCE of 2 => 2mm from bed to nozzle. + */ +#define Z_CLEARANCE_DEPLOY_PROBE 10 // (mm) Z Clearance for Deploy/Stow +#define Z_CLEARANCE_BETWEEN_PROBES 5 // (mm) Z Clearance between probe points +#define Z_CLEARANCE_MULTI_PROBE 5 // (mm) Z Clearance between multiple probes +#define Z_PROBE_ERROR_TOLERANCE 3 // (mm) Tolerance for early trigger (<= -probe.offset.z + ZPET) +//#define Z_AFTER_PROBING 5 // (mm) Z position after probing is done + +#define Z_PROBE_LOW_POINT -2 // (mm) Farthest distance below the trigger-point to go before stopping + +// For M851 provide ranges for adjusting the X, Y, and Z probe offsets +//#define PROBE_OFFSET_XMIN -50 // (mm) +//#define PROBE_OFFSET_XMAX 50 // (mm) +//#define PROBE_OFFSET_YMIN -50 // (mm) +//#define PROBE_OFFSET_YMAX 50 // (mm) +//#define PROBE_OFFSET_ZMIN -20 // (mm) +//#define PROBE_OFFSET_ZMAX 20 // (mm) + +// Enable the M48 repeatability test to test probe accuracy +//#define Z_MIN_PROBE_REPEATABILITY_TEST + +// Before deploy/stow pause for user confirmation +//#define PAUSE_BEFORE_DEPLOY_STOW +#if ENABLED(PAUSE_BEFORE_DEPLOY_STOW) + //#define PAUSE_PROBE_DEPLOY_WHEN_TRIGGERED // For Manual Deploy Allenkey Probe +#endif + +/** + * Enable one or more of the following if probing seems unreliable. + * Heaters and/or fans can be disabled during probing to minimize electrical + * noise. A delay can also be added to allow noise and vibration to settle. + * These options are most useful for the BLTouch probe, but may also improve + * readings with inductive probes and piezo sensors. + */ +//#define PROBING_HEATERS_OFF // Turn heaters off when probing +#if ENABLED(PROBING_HEATERS_OFF) + //#define WAIT_FOR_BED_HEATER // Wait for bed to heat back up between probes (to improve accuracy) + //#define WAIT_FOR_HOTEND // Wait for hotend to heat back up between probes (to improve accuracy & prevent cold extrude) +#endif +//#define PROBING_FANS_OFF // Turn fans off when probing +//#define PROBING_ESTEPPERS_OFF // Turn all extruder steppers off when probing +//#define PROBING_STEPPERS_OFF // Turn all steppers off (unless needed to hold position) when probing (including extruders) +//#define DELAY_BEFORE_PROBING 200 // (ms) To prevent vibrations from triggering piezo sensors + +// Require minimum nozzle and/or bed temperature for probing +//#define PREHEAT_BEFORE_PROBING +#if ENABLED(PREHEAT_BEFORE_PROBING) + #define PROBING_NOZZLE_TEMP 120 // (°C) Only applies to E0 at this time + #define PROBING_BED_TEMP 50 +#endif + +// @section stepper drivers + +// For Inverting Stepper Enable Pins (Active Low) use 0, Non Inverting (Active High) use 1 +// :['LOW', 'HIGH'] +#define X_ENABLE_ON LOW +#define Y_ENABLE_ON LOW +#define Z_ENABLE_ON LOW +#define E_ENABLE_ON LOW // For all extruders +//#define I_ENABLE_ON LOW +//#define J_ENABLE_ON LOW +//#define K_ENABLE_ON LOW +//#define U_ENABLE_ON LOW +//#define V_ENABLE_ON LOW +//#define W_ENABLE_ON LOW + +// Disable axis steppers immediately when they're not being stepped. +// WARNING: When motors turn off there is a chance of losing position accuracy! +//#define DISABLE_X +//#define DISABLE_Y +//#define DISABLE_Z +//#define DISABLE_I +//#define DISABLE_J +//#define DISABLE_K +//#define DISABLE_U +//#define DISABLE_V +//#define DISABLE_W + +// Turn off the display blinking that warns about possible accuracy reduction +//#define DISABLE_REDUCED_ACCURACY_WARNING + +// @section extruder + +//#define DISABLE_E // Disable the extruder when not stepping +#define DISABLE_OTHER_EXTRUDERS // Keep only the active extruder enabled + +// @section motion + +// Invert the stepper direction. Change (or reverse the motor connector) if an axis goes the wrong way. +#define INVERT_X_DIR false +#define INVERT_Y_DIR true +#define INVERT_Z_DIR false +//#define INVERT_I_DIR false +//#define INVERT_J_DIR false +//#define INVERT_K_DIR false +//#define INVERT_U_DIR false +//#define INVERT_V_DIR false +//#define INVERT_W_DIR false + +// @section extruder + +// For direct drive extruder v9 set to true, for geared extruder set to false. +#define INVERT_E0_DIR false +#define INVERT_E1_DIR false +#define INVERT_E2_DIR false +#define INVERT_E3_DIR false +#define INVERT_E4_DIR false +#define INVERT_E5_DIR false +#define INVERT_E6_DIR false +#define INVERT_E7_DIR false + +// @section homing + +//#define NO_MOTION_BEFORE_HOMING // Inhibit movement until all axes have been homed. Also enable HOME_AFTER_DEACTIVATE for extra safety. +//#define HOME_AFTER_DEACTIVATE // Require rehoming after steppers are deactivated. Also enable NO_MOTION_BEFORE_HOMING for extra safety. + +/** + * Set Z_IDLE_HEIGHT if the Z-Axis moves on its own when steppers are disabled. + * - Use a low value (i.e., Z_MIN_POS) if the nozzle falls down to the bed. + * - Use a large value (i.e., Z_MAX_POS) if the bed falls down, away from the nozzle. + */ +//#define Z_IDLE_HEIGHT Z_HOME_POS + +//#define Z_CLEARANCE_FOR_HOMING 4 // (mm) Minimal Z height before homing (G28) for Z clearance above the bed, clamps, ... + // You'll need this much clearance above Z_MAX_POS to avoid grinding. + +//#define Z_AFTER_HOMING 10 // (mm) Height to move to after homing (if Z was homed) +//#define XY_AFTER_HOMING { 10, 10 } // (mm) Move to an XY position after homing (and raising Z) + +//#define EVENT_GCODE_AFTER_HOMING "M300 P440 S200" // Commands to run after G28 (and move to XY_AFTER_HOMING) + +// Direction of endstops when homing; 1=MAX, -1=MIN +// :[-1,1] +#define X_HOME_DIR -1 +#define Y_HOME_DIR -1 +#define Z_HOME_DIR -1 +//#define I_HOME_DIR -1 +//#define J_HOME_DIR -1 +//#define K_HOME_DIR -1 +//#define U_HOME_DIR -1 +//#define V_HOME_DIR -1 +//#define W_HOME_DIR -1 + +/** + * Safety Stops + * If an axis has endstops on both ends the one specified above is used for + * homing, while the other can be used for things like SD_ABORT_ON_ENDSTOP_HIT. + */ +//#define X_SAFETY_STOP +//#define Y_SAFETY_STOP +//#define Z_SAFETY_STOP +//#define I_SAFETY_STOP +//#define J_SAFETY_STOP +//#define K_SAFETY_STOP +//#define U_SAFETY_STOP +//#define V_SAFETY_STOP +//#define W_SAFETY_STOP + +// @section geometry + +// The size of the printable area +#define X_BED_SIZE 200 +#define Y_BED_SIZE 200 + +// Travel limits (linear=mm, rotational=°) after homing, corresponding to endstop positions. +#define X_MIN_POS 0 +#define Y_MIN_POS 0 +#define Z_MIN_POS 0 +#define X_MAX_POS X_BED_SIZE +#define Y_MAX_POS Y_BED_SIZE +#define Z_MAX_POS 200 +//#define I_MIN_POS 0 +//#define I_MAX_POS 50 +//#define J_MIN_POS 0 +//#define J_MAX_POS 50 +//#define K_MIN_POS 0 +//#define K_MAX_POS 50 +//#define U_MIN_POS 0 +//#define U_MAX_POS 50 +//#define V_MIN_POS 0 +//#define V_MAX_POS 50 +//#define W_MIN_POS 0 +//#define W_MAX_POS 50 + +/** + * Software Endstops + * + * - Prevent moves outside the set machine bounds. + * - Individual axes can be disabled, if desired. + * - X and Y only apply to Cartesian robots. + * - Use 'M211' to set software endstops on/off or report current state + */ + +// Min software endstops constrain movement within minimum coordinate bounds +#define MIN_SOFTWARE_ENDSTOPS +#if ENABLED(MIN_SOFTWARE_ENDSTOPS) + #define MIN_SOFTWARE_ENDSTOP_X + #define MIN_SOFTWARE_ENDSTOP_Y + #define MIN_SOFTWARE_ENDSTOP_Z + #define MIN_SOFTWARE_ENDSTOP_I + #define MIN_SOFTWARE_ENDSTOP_J + #define MIN_SOFTWARE_ENDSTOP_K + #define MIN_SOFTWARE_ENDSTOP_U + #define MIN_SOFTWARE_ENDSTOP_V + #define MIN_SOFTWARE_ENDSTOP_W +#endif + +// Max software endstops constrain movement within maximum coordinate bounds +#define MAX_SOFTWARE_ENDSTOPS +#if ENABLED(MAX_SOFTWARE_ENDSTOPS) + #define MAX_SOFTWARE_ENDSTOP_X + #define MAX_SOFTWARE_ENDSTOP_Y + #define MAX_SOFTWARE_ENDSTOP_Z + #define MAX_SOFTWARE_ENDSTOP_I + #define MAX_SOFTWARE_ENDSTOP_J + #define MAX_SOFTWARE_ENDSTOP_K + #define MAX_SOFTWARE_ENDSTOP_U + #define MAX_SOFTWARE_ENDSTOP_V + #define MAX_SOFTWARE_ENDSTOP_W +#endif + +#if ANY(MIN_SOFTWARE_ENDSTOPS, MAX_SOFTWARE_ENDSTOPS) + //#define SOFT_ENDSTOPS_MENU_ITEM // Enable/Disable software endstops from the LCD +#endif + +/** + * @section filament runout sensors + * + * Filament Runout Sensors + * Mechanical or opto endstops are used to check for the presence of filament. + * + * IMPORTANT: Runout will only trigger if Marlin is aware that a print job is running. + * Marlin knows a print job is running when: + * 1. Running a print job from media started with M24. + * 2. The Print Job Timer has been started with M75. + * 3. The heaters were turned on and PRINTJOB_TIMER_AUTOSTART is enabled. + * + * RAMPS-based boards use SERVO3_PIN for the first runout sensor. + * For other boards you may need to define FIL_RUNOUT_PIN, FIL_RUNOUT2_PIN, etc. + */ +//#define FILAMENT_RUNOUT_SENSOR +#if ENABLED(FILAMENT_RUNOUT_SENSOR) + #define FIL_RUNOUT_ENABLED_DEFAULT true // Enable the sensor on startup. Override with M412 followed by M500. + #define NUM_RUNOUT_SENSORS 1 // Number of sensors, up to one per extruder. Define a FIL_RUNOUT#_PIN for each. + + #define FIL_RUNOUT_STATE LOW // Pin state indicating that filament is NOT present. + #define FIL_RUNOUT_PULLUP // Use internal pullup for filament runout pins. + //#define FIL_RUNOUT_PULLDOWN // Use internal pulldown for filament runout pins. + //#define WATCH_ALL_RUNOUT_SENSORS // Execute runout script on any triggering sensor, not only for the active extruder. + // This is automatically enabled for MIXING_EXTRUDERs. + + // Override individually if the runout sensors vary + //#define FIL_RUNOUT1_STATE LOW + //#define FIL_RUNOUT1_PULLUP + //#define FIL_RUNOUT1_PULLDOWN + + //#define FIL_RUNOUT2_STATE LOW + //#define FIL_RUNOUT2_PULLUP + //#define FIL_RUNOUT2_PULLDOWN + + //#define FIL_RUNOUT3_STATE LOW + //#define FIL_RUNOUT3_PULLUP + //#define FIL_RUNOUT3_PULLDOWN + + //#define FIL_RUNOUT4_STATE LOW + //#define FIL_RUNOUT4_PULLUP + //#define FIL_RUNOUT4_PULLDOWN + + //#define FIL_RUNOUT5_STATE LOW + //#define FIL_RUNOUT5_PULLUP + //#define FIL_RUNOUT5_PULLDOWN + + //#define FIL_RUNOUT6_STATE LOW + //#define FIL_RUNOUT6_PULLUP + //#define FIL_RUNOUT6_PULLDOWN + + //#define FIL_RUNOUT7_STATE LOW + //#define FIL_RUNOUT7_PULLUP + //#define FIL_RUNOUT7_PULLDOWN + + //#define FIL_RUNOUT8_STATE LOW + //#define FIL_RUNOUT8_PULLUP + //#define FIL_RUNOUT8_PULLDOWN + + // Commands to execute on filament runout. + // With multiple runout sensors use the %c placeholder for the current tool in commands (e.g., "M600 T%c") + // NOTE: After 'M412 H1' the host handles filament runout and this script does not apply. + #define FILAMENT_RUNOUT_SCRIPT "M600" + + // After a runout is detected, continue printing this length of filament + // before executing the runout script. Useful for a sensor at the end of + // a feed tube. Requires 4 bytes SRAM per sensor, plus 4 bytes overhead. + //#define FILAMENT_RUNOUT_DISTANCE_MM 25 + + #ifdef FILAMENT_RUNOUT_DISTANCE_MM + // Enable this option to use an encoder disc that toggles the runout pin + // as the filament moves. (Be sure to set FILAMENT_RUNOUT_DISTANCE_MM + // large enough to avoid false positives.) + //#define FILAMENT_MOTION_SENSOR + + #if ENABLED(FILAMENT_MOTION_SENSOR) + //#define FILAMENT_SWITCH_AND_MOTION + #if ENABLED(FILAMENT_SWITCH_AND_MOTION) + #define NUM_MOTION_SENSORS 1 // Number of sensors, up to one per extruder. Define a FIL_MOTION#_PIN for each. + //#define FIL_MOTION1_PIN -1 + + // Override individually if the motion sensors vary + //#define FIL_MOTION1_STATE LOW + //#define FIL_MOTION1_PULLUP + //#define FIL_MOTION1_PULLDOWN + + //#define FIL_MOTION2_STATE LOW + //#define FIL_MOTION2_PULLUP + //#define FIL_MOTION2_PULLDOWN + + //#define FIL_MOTION3_STATE LOW + //#define FIL_MOTION3_PULLUP + //#define FIL_MOTION3_PULLDOWN + + //#define FIL_MOTION4_STATE LOW + //#define FIL_MOTION4_PULLUP + //#define FIL_MOTION4_PULLDOWN + + //#define FIL_MOTION5_STATE LOW + //#define FIL_MOTION5_PULLUP + //#define FIL_MOTION5_PULLDOWN + + //#define FIL_MOTION6_STATE LOW + //#define FIL_MOTION6_PULLUP + //#define FIL_MOTION6_PULLDOWN + + //#define FIL_MOTION7_STATE LOW + //#define FIL_MOTION7_PULLUP + //#define FIL_MOTION7_PULLDOWN + + //#define FIL_MOTION8_STATE LOW + //#define FIL_MOTION8_PULLUP + //#define FIL_MOTION8_PULLDOWN + #endif + #endif // FILAMENT_MOTION_SENSOR + #endif // FILAMENT_RUNOUT_DISTANCE_MM +#endif // FILAMENT_RUNOUT_SENSOR + +//=========================================================================== +//=============================== Bed Leveling ============================== +//=========================================================================== +// @section calibrate + +/** + * Choose one of the options below to enable G29 Bed Leveling. The parameters + * and behavior of G29 will change depending on your selection. + * + * If using a Probe for Z Homing, enable Z_SAFE_HOMING also! + * + * - AUTO_BED_LEVELING_3POINT + * Probe 3 arbitrary points on the bed (that aren't collinear) + * You specify the XY coordinates of all 3 points. + * The result is a single tilted plane. Best for a flat bed. + * + * - AUTO_BED_LEVELING_LINEAR + * Probe several points in a grid. + * You specify the rectangle and the density of sample points. + * The result is a single tilted plane. Best for a flat bed. + * + * - AUTO_BED_LEVELING_BILINEAR + * Probe several points in a grid. + * You specify the rectangle and the density of sample points. + * The result is a mesh, best for large or uneven beds. + * + * - AUTO_BED_LEVELING_UBL (Unified Bed Leveling) + * A comprehensive bed leveling system combining the features and benefits + * of other systems. UBL also includes integrated Mesh Generation, Mesh + * Validation and Mesh Editing systems. + * + * - MESH_BED_LEVELING + * Probe a grid manually + * The result is a mesh, suitable for large or uneven beds. (See BILINEAR.) + * For machines without a probe, Mesh Bed Leveling provides a method to perform + * leveling in steps so you can manually adjust the Z height at each grid-point. + * With an LCD controller the process is guided step-by-step. + */ +//#define AUTO_BED_LEVELING_3POINT +//#define AUTO_BED_LEVELING_LINEAR +//#define AUTO_BED_LEVELING_BILINEAR +//#define AUTO_BED_LEVELING_UBL +//#define MESH_BED_LEVELING + +/* When a level probe is fitted but not used for homing, enabling USE_PROBE_FOR_MESH_REF will avoid a level mesh + * being created with an unwanted offset between the fixed Z endstop/Homing Switch and probe trigger height. The + * probe Z offset menu option is removed from the level sub menu, as entering a Z offset there would shift the mesh + * and the Homeing position giving confusing status values for Z. It is recomended not to enter a Z offset value in + * the probe offset section of config.h for the same reason. + * + * If no Z offsets are entered elsewhere the status screen will always show the actual Z possition. e.g when homing + * the Z status will show 0 at the Home position, unless the height of the fixed Z stop/Homeing switch above the bed + * has been set by the HOME_SWITCH_TO_BED_OFFSET_MENU option while creating a level mesh. In which case the Home + * position will show as that height. The printer will move down to the print height when printing starts and Z status + * will show the first layer height. The Nozzle will follow the mesh and baby stepping can be used to temporarily lower + * or raise the nozzle to adjust first layer squish. + * + * If your Z stop/Homeing switch has a fine adjustment capability, the HOME_SWITCH_TO_BED_OFFSET_MENU menu option can be + * left at zero or disabled. Using the paper pinch test and setting the Z stop/Homeing switch position works just as well. + */ +#ifndef USE_PROBE_FOR_Z_HOMING + #if defined AUTO_BED_LEVELING_BILINEAR || defined AUTO_BED_LEVELING_UBL || defined AUTO_BED_LEVELING_LINEAR || defined AUTO_BED_LEVELING_3POINT + //#define USE_PROBE_FOR_MESH_REF + #ifdef USE_PROBE_FOR_MESH_REF + /*optional HOME_SWITCH_TO_BED_OFFSET_MENU enables a menu option called "Home Offset Z" to adjust the Z offset between + * the Fixed Z endstop/Homing switch and the bed. This offset is only used to adjust the level mesh down + * to the Zero plane (Bed) as the mesh is created, so a new level mesh must be created when if it is changed to apply it.*/ + #define HOME_SWITCH_TO_BED_OFFSET_MENU + #endif + #endif +#endif + +/** + * Commands to execute at the start of G29 probing, + * after switching to the PROBING_TOOL. + */ +//#define EVENT_GCODE_BEFORE_G29 "M300 P440 S200" + +/** + * Commands to execute at the end of G29 probing. + * Useful to retract or move the Z probe out of the way. + */ +//#define EVENT_GCODE_AFTER_G29 "G1 Z10 F12000\nG1 X15 Y330\nG1 Z0.5\nG1 Z10" + +/** + * Normally G28 leaves leveling disabled on completion. Enable one of + * these options to restore the prior leveling state or to always enable + * leveling immediately after G28. + */ +//#define RESTORE_LEVELING_AFTER_G28 +//#define ENABLE_LEVELING_AFTER_G28 + +/** + * Auto-leveling needs preheating + */ +//#define PREHEAT_BEFORE_LEVELING +#if ENABLED(PREHEAT_BEFORE_LEVELING) + #define LEVELING_NOZZLE_TEMP 120 // (°C) Only applies to E0 at this time + #define LEVELING_BED_TEMP 50 +#endif + +/** + * Enable detailed logging of G28, G29, M48, etc. + * Turn on with the command 'M111 S32'. + * NOTE: Requires a lot of flash! + */ +//#define DEBUG_LEVELING_FEATURE + +#if ANY(MESH_BED_LEVELING, AUTO_BED_LEVELING_UBL, PROBE_MANUALLY) + // Set a height for the start of manual adjustment + #define MANUAL_PROBE_START_Z 0.2 // (mm) Comment out to use the last-measured height +#endif + +#if ANY(MESH_BED_LEVELING, AUTO_BED_LEVELING_BILINEAR, AUTO_BED_LEVELING_UBL) + /** + * Gradually reduce leveling correction until a set height is reached, + * at which point movement will be level to the machine's XY plane. + * The height can be set with M420 Z + */ + #define ENABLE_LEVELING_FADE_HEIGHT + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + #define DEFAULT_LEVELING_FADE_HEIGHT 10.0 // (mm) Default fade height. + #endif + + /** + * For Cartesian machines, instead of dividing moves on mesh boundaries, + * split up moves into short segments like a Delta. This follows the + * contours of the bed more closely than edge-to-edge straight moves. + */ + #define SEGMENT_LEVELED_MOVES + #define LEVELED_SEGMENT_LENGTH 5.0 // (mm) Length of all segments (except the last one) + + /** + * Enable the G26 Mesh Validation Pattern tool. + */ + //#define G26_MESH_VALIDATION + #if ENABLED(G26_MESH_VALIDATION) + #define MESH_TEST_NOZZLE_SIZE 0.4 // (mm) Diameter of primary nozzle. + #define MESH_TEST_LAYER_HEIGHT 0.2 // (mm) Default layer height for G26. + #define MESH_TEST_HOTEND_TEMP 205 // (°C) Default nozzle temperature for G26. + #define MESH_TEST_BED_TEMP 60 // (°C) Default bed temperature for G26. + #define G26_XY_FEEDRATE 20 // (mm/s) Feedrate for G26 XY moves. + #define G26_XY_FEEDRATE_TRAVEL 100 // (mm/s) Feedrate for G26 XY travel moves. + #define G26_RETRACT_MULTIPLIER 1.0 // G26 Q (retraction) used by default between mesh test elements. + #endif + +#endif + +#if ANY(AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_BILINEAR) + + // Set the number of grid points per dimension. + #define GRID_MAX_POINTS_X 3 + #define GRID_MAX_POINTS_Y GRID_MAX_POINTS_X + + // Probe along the Y axis, advancing X after each column + //#define PROBE_Y_FIRST + + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + + // Beyond the probed grid, continue the implied tilt? + // Default is to maintain the height of the nearest edge. + //#define EXTRAPOLATE_BEYOND_GRID + + // + // Subdivision of the grid by Catmull-Rom method. + // Synthesizes intermediate points to produce a more detailed mesh. + // + //#define ABL_BILINEAR_SUBDIVISION + #if ENABLED(ABL_BILINEAR_SUBDIVISION) + // Number of subdivisions between probe points + #define BILINEAR_SUBDIVISIONS 3 + #endif + + #endif + +#elif ENABLED(AUTO_BED_LEVELING_UBL) + + //=========================================================================== + //========================= Unified Bed Leveling ============================ + //=========================================================================== + + //#define MESH_EDIT_GFX_OVERLAY // Display a graphics overlay while editing the mesh + + #define MESH_INSET 1 // Set Mesh bounds as an inset region of the bed + #define GRID_MAX_POINTS_X 10 // Don't use more than 15 points per axis, implementation limited. + #define GRID_MAX_POINTS_Y GRID_MAX_POINTS_X + + //#define UBL_HILBERT_CURVE // Use Hilbert distribution for less travel when probing multiple points + + //#define UBL_TILT_ON_MESH_POINTS // Use nearest mesh points with G29 J for better Z reference + //#define UBL_TILT_ON_MESH_POINTS_3POINT // Use nearest mesh points with G29 J0 (3-point) + + #define UBL_MESH_EDIT_MOVES_Z // Sophisticated users prefer no movement of nozzle + #define UBL_SAVE_ACTIVE_ON_M500 // Save the currently active mesh in the current slot on M500 + + //#define UBL_Z_RAISE_WHEN_OFF_MESH 2.5 // When the nozzle is off the mesh, this value is used + // as the Z-Height correction value. + + //#define UBL_MESH_WIZARD // Run several commands in a row to get a complete mesh + + /** + * Probing not allowed within the position of an obstacle. + */ + //#define AVOID_OBSTACLES + #if ENABLED(AVOID_OBSTACLES) + #define CLIP_W 23 // Bed clip width, should be padded a few mm over its physical size + #define CLIP_H 14 // Bed clip height, should be padded a few mm over its physical size + + // Obstacle Rectangles defined as { X1, Y1, X2, Y2 } + #define OBSTACLE1 { (X_BED_SIZE) / 4 - (CLIP_W) / 2, 0, (X_BED_SIZE) / 4 + (CLIP_W) / 2, CLIP_H } + #define OBSTACLE2 { (X_BED_SIZE) * 3 / 4 - (CLIP_W) / 2, 0, (X_BED_SIZE) * 3 / 4 + (CLIP_W) / 2, CLIP_H } + #define OBSTACLE3 { (X_BED_SIZE) / 4 - (CLIP_W) / 2, (Y_BED_SIZE) - (CLIP_H), (X_BED_SIZE) / 4 + (CLIP_W) / 2, Y_BED_SIZE } + #define OBSTACLE4 { (X_BED_SIZE) * 3 / 4 - (CLIP_W) / 2, (Y_BED_SIZE) - (CLIP_H), (X_BED_SIZE) * 3 / 4 + (CLIP_W) / 2, Y_BED_SIZE } + + // The probed grid must be inset for G29 J. This is okay, since it is + // only used to compute a linear transformation for the mesh itself. + #define G29J_MESH_TILT_MARGIN ((CLIP_H) + 1) + #endif + +#elif ENABLED(MESH_BED_LEVELING) + + //=========================================================================== + //=================================== Mesh ================================== + //=========================================================================== + + #define MESH_INSET 10 // Set Mesh bounds as an inset region of the bed + #define GRID_MAX_POINTS_X 3 // Don't use more than 7 points per axis, implementation limited. + #define GRID_MAX_POINTS_Y GRID_MAX_POINTS_X + + //#define MESH_G28_REST_ORIGIN // After homing all axes ('G28' or 'G28 XYZ') rest Z at Z_MIN_POS + +#endif // BED_LEVELING + +/** + * Add a bed leveling sub-menu for ABL or MBL. + * Include a guided procedure if manual probing is enabled. + */ +//#define LCD_BED_LEVELING + +#if ENABLED(LCD_BED_LEVELING) + #define MESH_EDIT_Z_STEP 0.025 // (mm) Step size while manually probing Z axis. + #define LCD_PROBE_Z_RANGE 4 // (mm) Z Range centered on Z_MIN_POS for LCD Z adjustment + //#define MESH_EDIT_MENU // Add a menu to edit mesh points +#endif + +// Add a menu item to move between bed corners for manual bed adjustment +//#define LCD_BED_TRAMMING + +#if ENABLED(LCD_BED_TRAMMING) + #define BED_TRAMMING_INSET_LFRB { 30, 30, 30, 30 } // (mm) Left, Front, Right, Back insets + #define BED_TRAMMING_HEIGHT 0.0 // (mm) Z height of nozzle at tramming points + #define BED_TRAMMING_Z_HOP 4.0 // (mm) Z raise between tramming points + //#define BED_TRAMMING_INCLUDE_CENTER // Move to the center after the last corner + //#define BED_TRAMMING_USE_PROBE + #if ENABLED(BED_TRAMMING_USE_PROBE) + #define BED_TRAMMING_PROBE_TOLERANCE 0.1 // (mm) + #define BED_TRAMMING_VERIFY_RAISED // After adjustment triggers the probe, re-probe to verify + //#define BED_TRAMMING_AUDIO_FEEDBACK + #endif + + /** + * Corner Leveling Order + * + * Set 2 or 4 points. When 2 points are given, the 3rd is the center of the opposite edge. + * + * LF Left-Front RF Right-Front + * LB Left-Back RB Right-Back + * + * Examples: + * + * Default {LF,RB,LB,RF} {LF,RF} {LB,LF} + * LB --------- RB LB --------- RB LB --------- RB LB --------- RB + * | 4 3 | | 3 2 | | <3> | | 1 | + * | | | | | | | <3>| + * | 1 2 | | 1 4 | | 1 2 | | 2 | + * LF --------- RF LF --------- RF LF --------- RF LF --------- RF + */ + #define BED_TRAMMING_LEVELING_ORDER { LF, RF, RB, LB } +#endif + +// @section homing + +// The center of the bed is at (X=0, Y=0) +//#define BED_CENTER_AT_0_0 + +// Manually set the home position. Leave these undefined for automatic settings. +// For DELTA this is the top-center of the Cartesian print volume. +//#define MANUAL_X_HOME_POS 0 +//#define MANUAL_Y_HOME_POS 0 +//#define MANUAL_Z_HOME_POS 0 +//#define MANUAL_I_HOME_POS 0 +//#define MANUAL_J_HOME_POS 0 +//#define MANUAL_K_HOME_POS 0 +//#define MANUAL_U_HOME_POS 0 +//#define MANUAL_V_HOME_POS 0 +//#define MANUAL_W_HOME_POS 0 + +/** + * Use "Z Safe Homing" to avoid homing with a Z probe outside the bed area. + * + * - Moves the Z probe (or nozzle) to a defined XY point before Z homing. + * - Allows Z homing only when XY positions are known and trusted. + * - If stepper drivers sleep, XY homing may be required again before Z homing. + */ +//#define Z_SAFE_HOMING + +#if ENABLED(Z_SAFE_HOMING) + #define Z_SAFE_HOMING_X_POINT X_CENTER // (mm) X point for Z homing + #define Z_SAFE_HOMING_Y_POINT Y_CENTER // (mm) Y point for Z homing + //#define Z_SAFE_HOMING_POINT_ABSOLUTE // Ignore home offsets (M206) for Z homing position +#endif + +// Homing speeds (linear=mm/min, rotational=°/min) +#define HOMING_FEEDRATE_MM_M { (50*60), (50*60), (4*60) } + +// Edit homing feedrates with M210 and MarlinUI menu items +//#define EDITABLE_HOMING_FEEDRATE + +// Validate that endstops are triggered on homing moves +#define VALIDATE_HOMING_ENDSTOPS + +// @section calibrate + +/** + * Bed Skew Compensation + * + * This feature corrects for misalignment in the XYZ axes. + * + * Take the following steps to get the bed skew in the XY plane: + * 1. Print a test square (e.g., https://www.thingiverse.com/thing:2563185) + * 2. For XY_DIAG_AC measure the diagonal A to C + * 3. For XY_DIAG_BD measure the diagonal B to D + * 4. For XY_SIDE_AD measure the edge A to D + * + * Marlin automatically computes skew factors from these measurements. + * Skew factors may also be computed and set manually: + * + * - Compute AB : SQRT(2*AC*AC+2*BD*BD-4*AD*AD)/2 + * - XY_SKEW_FACTOR : TAN(PI/2-ACOS((AC*AC-AB*AB-AD*AD)/(2*AB*AD))) + * + * If desired, follow the same procedure for XZ and YZ. + * Use these diagrams for reference: + * + * Y Z Z + * ^ B-------C ^ B-------C ^ B-------C + * | / / | / / | / / + * | / / | / / | / / + * | A-------D | A-------D | A-------D + * +-------------->X +-------------->X +-------------->Y + * XY_SKEW_FACTOR XZ_SKEW_FACTOR YZ_SKEW_FACTOR + */ +//#define SKEW_CORRECTION + +#if ENABLED(SKEW_CORRECTION) + // Input all length measurements here: + #define XY_DIAG_AC 282.8427124746 + #define XY_DIAG_BD 282.8427124746 + #define XY_SIDE_AD 200 + + // Or, set the XY skew factor directly: + //#define XY_SKEW_FACTOR 0.0 + + //#define SKEW_CORRECTION_FOR_Z + #if ENABLED(SKEW_CORRECTION_FOR_Z) + #define XZ_DIAG_AC 282.8427124746 + #define XZ_DIAG_BD 282.8427124746 + #define YZ_DIAG_AC 282.8427124746 + #define YZ_DIAG_BD 282.8427124746 + #define YZ_SIDE_AD 200 + + // Or, set the Z skew factors directly: + //#define XZ_SKEW_FACTOR 0.0 + //#define YZ_SKEW_FACTOR 0.0 + #endif + + // Enable this option for M852 to set skew at runtime + //#define SKEW_CORRECTION_GCODE +#endif + +//============================================================================= +//============================= Additional Features =========================== +//============================================================================= + +// @section eeprom + +/** + * EEPROM + * + * Persistent storage to preserve configurable settings across reboots. + * + * M500 - Store settings to EEPROM. + * M501 - Read settings from EEPROM. (i.e., Throw away unsaved changes) + * M502 - Revert settings to "factory" defaults. (Follow with M500 to init the EEPROM.) + */ +//#define EEPROM_SETTINGS // Persistent storage with M500 and M501 +//#define DISABLE_M503 // Saves ~2700 bytes of flash. Disable for release! +#define EEPROM_CHITCHAT // Give feedback on EEPROM commands. Disable to save flash. +#define EEPROM_BOOT_SILENT // Keep M503 quiet and only give errors during first load +#if ENABLED(EEPROM_SETTINGS) + //#define EEPROM_AUTO_INIT // Init EEPROM automatically on any errors. + //#define EEPROM_INIT_NOW // Init EEPROM on first boot after a new build. +#endif + +// @section host + +// +// Host Keepalive +// +// When enabled Marlin will send a busy status message to the host +// every couple of seconds when it can't accept commands. +// +#define HOST_KEEPALIVE_FEATURE // Disable this if your host doesn't like keepalive messages +#define DEFAULT_KEEPALIVE_INTERVAL 2 // Number of seconds between "busy" messages. Set with M113. +#define BUSY_WHILE_HEATING // Some hosts require "busy" messages even during heating + +// @section units + +// +// G20/G21 Inch mode support +// +//#define INCH_MODE_SUPPORT + +// +// M149 Set temperature units support +// +//#define TEMPERATURE_UNITS_SUPPORT + +// @section temperature + +// +// Preheat Constants - Up to 10 are supported without changes +// +#define PREHEAT_1_LABEL "PLA" +#define PREHEAT_1_TEMP_HOTEND 180 +#define PREHEAT_1_TEMP_BED 70 +#define PREHEAT_1_TEMP_CHAMBER 35 +#define PREHEAT_1_FAN_SPEED 0 // Value from 0 to 255 + +#define PREHEAT_2_LABEL "ABS" +#define PREHEAT_2_TEMP_HOTEND 240 +#define PREHEAT_2_TEMP_BED 110 +#define PREHEAT_2_TEMP_CHAMBER 35 +#define PREHEAT_2_FAN_SPEED 0 // Value from 0 to 255 + +/** + * @section nozzle park + * + * Nozzle Park + * + * Park the nozzle at the given XYZ position on idle or G27. + * + * The "P" parameter controls the action applied to the Z axis: + * + * P0 (Default) If Z is below park Z raise the nozzle. + * P1 Raise the nozzle always to Z-park height. + * P2 Raise the nozzle by Z-park amount, limited to Z_MAX_POS. + */ +//#define NOZZLE_PARK_FEATURE + +#if ENABLED(NOZZLE_PARK_FEATURE) + // Specify a park position as { X, Y, Z_raise } + #define NOZZLE_PARK_POINT { (X_MIN_POS + 10), (Y_MAX_POS - 10), 20 } + #define NOZZLE_PARK_MOVE 0 // Park motion: 0 = XY Move, 1 = X Only, 2 = Y Only, 3 = X before Y, 4 = Y before X + #define NOZZLE_PARK_Z_RAISE_MIN 2 // (mm) Always raise Z by at least this distance + #define NOZZLE_PARK_XY_FEEDRATE 100 // (mm/s) X and Y axes feedrate (also used for delta Z axis) + #define NOZZLE_PARK_Z_FEEDRATE 5 // (mm/s) Z axis feedrate (not used for delta printers) +#endif + +/** + * @section nozzle clean + * + * Clean Nozzle Feature + * + * Adds the G12 command to perform a nozzle cleaning process. + * + * Parameters: + * P Pattern + * S Strokes / Repetitions + * T Triangles (P1 only) + * + * Patterns: + * P0 Straight line (default). This process requires a sponge type material + * at a fixed bed location. "S" specifies strokes (i.e. back-forth motions) + * between the start / end points. + * + * P1 Zig-zag pattern between (X0, Y0) and (X1, Y1), "T" specifies the + * number of zig-zag triangles to do. "S" defines the number of strokes. + * Zig-zags are done in whichever is the narrower dimension. + * For example, "G12 P1 S1 T3" will execute: + * + * -- + * | (X0, Y1) | /\ /\ /\ | (X1, Y1) + * | | / \ / \ / \ | + * A | | / \ / \ / \ | + * | | / \ / \ / \ | + * | (X0, Y0) | / \/ \/ \ | (X1, Y0) + * -- +--------------------------------+ + * |________|_________|_________| + * T1 T2 T3 + * + * P2 Circular pattern with middle at NOZZLE_CLEAN_CIRCLE_MIDDLE. + * "R" specifies the radius. "S" specifies the stroke count. + * Before starting, the nozzle moves to NOZZLE_CLEAN_START_POINT. + * + * Caveats: The ending Z should be the same as starting Z. + */ +//#define NOZZLE_CLEAN_FEATURE + +#if ENABLED(NOZZLE_CLEAN_FEATURE) + #define NOZZLE_CLEAN_PATTERN_LINE // Provide 'G12 P0' - a simple linear cleaning pattern + #define NOZZLE_CLEAN_PATTERN_ZIGZAG // Provide 'G12 P1' - a zigzag cleaning pattern + #define NOZZLE_CLEAN_PATTERN_CIRCLE // Provide 'G12 P2' - a circular cleaning pattern + + // Default pattern to use when 'P' is not provided to G12. One of the enabled options above. + #define NOZZLE_CLEAN_DEFAULT_PATTERN 0 + + #define NOZZLE_CLEAN_STROKES 12 // Default number of pattern repetitions + + #if ENABLED(NOZZLE_CLEAN_PATTERN_ZIGZAG) + #define NOZZLE_CLEAN_TRIANGLES 3 // Default number of triangles + #endif + + // Specify positions for each tool as { { X, Y, Z }, { X, Y, Z } } + // Dual hotend system may use { { -20, (Y_BED_SIZE / 2), (Z_MIN_POS + 1) }, { 420, (Y_BED_SIZE / 2), (Z_MIN_POS + 1) }} + #define NOZZLE_CLEAN_START_POINT { { 30, 30, (Z_MIN_POS + 1) } } + #define NOZZLE_CLEAN_END_POINT { { 100, 60, (Z_MIN_POS + 1) } } + + #if ENABLED(NOZZLE_CLEAN_PATTERN_CIRCLE) + #define NOZZLE_CLEAN_CIRCLE_RADIUS 6.5 // (mm) Circular pattern radius + #define NOZZLE_CLEAN_CIRCLE_FN 10 // Circular pattern circle number of segments + #define NOZZLE_CLEAN_CIRCLE_MIDDLE NOZZLE_CLEAN_START_POINT // Middle point of circle + #endif + + // Move the nozzle to the initial position after cleaning + #define NOZZLE_CLEAN_GOBACK + + // For a purge/clean station that's always at the gantry height (thus no Z move) + //#define NOZZLE_CLEAN_NO_Z + + // For a purge/clean station mounted on the X axis + //#define NOZZLE_CLEAN_NO_Y + + // Require a minimum hotend temperature for cleaning + #define NOZZLE_CLEAN_MIN_TEMP 170 + //#define NOZZLE_CLEAN_HEATUP // Heat up the nozzle instead of skipping wipe + + // Explicit wipe G-code script applies to a G12 with no arguments. + //#define WIPE_SEQUENCE_COMMANDS "G1 X-17 Y25 Z10 F4000\nG1 Z1\nM114\nG1 X-17 Y25\nG1 X-17 Y95\nG1 X-17 Y25\nG1 X-17 Y95\nG1 X-17 Y25\nG1 X-17 Y95\nG1 X-17 Y25\nG1 X-17 Y95\nG1 X-17 Y25\nG1 X-17 Y95\nG1 X-17 Y25\nG1 X-17 Y95\nG1 Z15\nM400\nG0 X-10.0 Y-9.0" + +#endif + +// @section host + +/** + * Print Job Timer + * + * Automatically start and stop the print job timer on M104/M109/M140/M190/M141/M191. + * The print job timer will only be stopped if the bed/chamber target temp is + * below BED_MINTEMP/CHAMBER_MINTEMP. + * + * M104 (hotend, no wait) - high temp = none, low temp = stop timer + * M109 (hotend, wait) - high temp = start timer, low temp = stop timer + * M140 (bed, no wait) - high temp = none, low temp = stop timer + * M190 (bed, wait) - high temp = start timer, low temp = none + * M141 (chamber, no wait) - high temp = none, low temp = stop timer + * M191 (chamber, wait) - high temp = start timer, low temp = none + * + * For M104/M109, high temp is anything over EXTRUDE_MINTEMP / 2. + * For M140/M190, high temp is anything over BED_MINTEMP. + * For M141/M191, high temp is anything over CHAMBER_MINTEMP. + * + * The timer can also be controlled with the following commands: + * + * M75 - Start the print job timer + * M76 - Pause the print job timer + * M77 - Stop the print job timer + */ +#define PRINTJOB_TIMER_AUTOSTART + +// @section stats + +/** + * Print Counter + * + * Track statistical data such as: + * + * - Total print jobs + * - Total successful print jobs + * - Total failed print jobs + * - Total time printing + * + * View the current statistics with M78. + */ +//#define PRINTCOUNTER +#if ENABLED(PRINTCOUNTER) + #define PRINTCOUNTER_SAVE_INTERVAL 60 // (minutes) EEPROM save interval during print. A value of 0 will save stats at end of print. +#endif + +// @section security + +/** + * Password + * + * Set a numerical password for the printer which can be requested: + * + * - When the printer boots up + * - Upon opening the 'Print from Media' Menu + * - When SD printing is completed or aborted + * + * The following G-codes can be used: + * + * M510 - Lock Printer. Blocks all commands except M511. + * M511 - Unlock Printer. + * M512 - Set, Change and Remove Password. + * + * If you forget the password and get locked out you'll need to re-flash + * the firmware with the feature disabled, reset EEPROM, and (optionally) + * re-flash the firmware again with this feature enabled. + */ +//#define PASSWORD_FEATURE +#if ENABLED(PASSWORD_FEATURE) + #define PASSWORD_LENGTH 4 // (#) Number of digits (1-9). 3 or 4 is recommended + #define PASSWORD_ON_STARTUP + #define PASSWORD_UNLOCK_GCODE // Unlock with the M511 P command. Disable to prevent brute-force attack. + #define PASSWORD_CHANGE_GCODE // Change the password with M512 P S. + //#define PASSWORD_ON_SD_PRINT_MENU // This does not prevent G-codes from running + //#define PASSWORD_AFTER_SD_PRINT_END + //#define PASSWORD_AFTER_SD_PRINT_ABORT + //#include "Configuration_Secure.h" // External file with PASSWORD_DEFAULT_VALUE +#endif + +// @section media + +/** + * SD CARD + * + * SD Card support is disabled by default. If your controller has an SD slot, + * you must uncomment the following option or it won't work. + */ +//#define SDSUPPORT + +/** + * SD CARD: ENABLE CRC + * + * Use CRC checks and retries on the SD communication. + */ +#if ENABLED(SDSUPPORT) + //#define SD_CHECK_AND_RETRY +#endif + +// @section interface + +/** + * LCD LANGUAGE + * + * Select the language to display on the LCD. These languages are available: + * + * en, an, bg, ca, cz, da, de, el, el_CY, es, eu, fi, fr, gl, hr, hu, it, + * jp_kana, ko_KR, nl, pl, pt, pt_br, ro, ru, sk, sv, tr, uk, vi, zh_CN, zh_TW + * + * :{ 'en':'English', 'an':'Aragonese', 'bg':'Bulgarian', 'ca':'Catalan', 'cz':'Czech', 'da':'Danish', 'de':'German', 'el':'Greek (Greece)', 'el_CY':'Greek (Cyprus)', 'es':'Spanish', 'eu':'Basque-Euskera', 'fi':'Finnish', 'fr':'French', 'gl':'Galician', 'hr':'Croatian', 'hu':'Hungarian', 'it':'Italian', 'jp_kana':'Japanese', 'ko_KR':'Korean (South Korea)', 'nl':'Dutch', 'pl':'Polish', 'pt':'Portuguese', 'pt_br':'Portuguese (Brazilian)', 'ro':'Romanian', 'ru':'Russian', 'sk':'Slovak', 'sv':'Swedish', 'tr':'Turkish', 'uk':'Ukrainian', 'vi':'Vietnamese', 'zh_CN':'Chinese (Simplified)', 'zh_TW':'Chinese (Traditional)' } + */ +#define LCD_LANGUAGE en + +/** + * LCD Character Set + * + * Note: This option is NOT applicable to Graphical Displays. + * + * All character-based LCDs provide ASCII plus one of these + * language extensions: + * + * - JAPANESE ... the most common + * - WESTERN ... with more accented characters + * - CYRILLIC ... for the Russian language + * + * To determine the language extension installed on your controller: + * + * - Compile and upload with LCD_LANGUAGE set to 'test' + * - Click the controller to view the LCD menu + * - The LCD will display Japanese, Western, or Cyrillic text + * + * See https://marlinfw.org/docs/development/lcd_language.html + * + * :['JAPANESE', 'WESTERN', 'CYRILLIC'] + */ +#define DISPLAY_CHARSET_HD44780 JAPANESE + +/** + * Info Screen Style (0:Classic, 1:Průša, 2:CNC) + * + * :[0:'Classic', 1:'Průša', 2:'CNC'] + */ +#define LCD_INFO_SCREEN_STYLE 0 + +/** + * LCD Menu Items + * + * Disable all menus and only display the Status Screen, or + * just remove some extraneous menu items to recover space. + */ +//#define NO_LCD_MENUS +//#define SLIM_LCD_MENUS + +// +// ENCODER SETTINGS +// +// This option overrides the default number of encoder pulses needed to +// produce one step. Should be increased for high-resolution encoders. +// +//#define ENCODER_PULSES_PER_STEP 4 + +// +// Use this option to override the number of step signals required to +// move between next/prev menu items. +// +//#define ENCODER_STEPS_PER_MENU_ITEM 1 + +/** + * Encoder Direction Options + * + * Test your encoder's behavior first with both options disabled. + * + * Reversed Value Edit and Menu Nav? Enable REVERSE_ENCODER_DIRECTION. + * Reversed Menu Navigation only? Enable REVERSE_MENU_DIRECTION. + * Reversed Value Editing only? Enable BOTH options. + */ + +// +// This option reverses the encoder direction everywhere. +// +// Set this option if CLOCKWISE causes values to DECREASE +// +//#define REVERSE_ENCODER_DIRECTION + +// +// This option reverses the encoder direction for navigating LCD menus. +// +// If CLOCKWISE normally moves DOWN this makes it go UP. +// If CLOCKWISE normally moves UP this makes it go DOWN. +// +//#define REVERSE_MENU_DIRECTION + +// +// This option reverses the encoder direction for Select Screen. +// +// If CLOCKWISE normally moves LEFT this makes it go RIGHT. +// If CLOCKWISE normally moves RIGHT this makes it go LEFT. +// +//#define REVERSE_SELECT_DIRECTION + +// +// Encoder EMI Noise Filter +// +// This option increases encoder samples to filter out phantom encoder clicks caused by EMI noise. +// +//#define ENCODER_NOISE_FILTER +#if ENABLED(ENCODER_NOISE_FILTER) + #define ENCODER_SAMPLES 10 +#endif + +// +// Individual Axis Homing +// +// Add individual axis homing items (Home X, Home Y, and Home Z) to the LCD menu. +// +//#define INDIVIDUAL_AXIS_HOMING_MENU +//#define INDIVIDUAL_AXIS_HOMING_SUBMENU + +// +// SPEAKER/BUZZER +// +// If you have a speaker that can produce tones, enable it here. +// By default Marlin assumes you have a buzzer with a fixed frequency. +// +//#define SPEAKER + +// +// The duration and frequency for the UI feedback sound. +// Set these to 0 to disable audio feedback in the LCD menus. +// +// Note: Test audio output with the G-Code: +// M300 S P +// +//#define LCD_FEEDBACK_FREQUENCY_DURATION_MS 2 +//#define LCD_FEEDBACK_FREQUENCY_HZ 5000 + +// +// Tone queue size, used to keep beeps from blocking execution. +// Default is 4, or override here. Costs 4 bytes of SRAM per entry. +// +//#define TONE_QUEUE_LENGTH 4 + +// +// A sequence of tones to play at startup, in pairs of tone (Hz), duration (ms). +// Silence in-between tones. +// +//#define STARTUP_TUNE { 698, 300, 0, 50, 523, 50, 0, 25, 494, 50, 0, 25, 523, 100, 0, 50, 554, 300, 0, 100, 523, 300 } + +//============================================================================= +//======================== LCD / Controller Selection ========================= +//======================== (Character-based LCDs) ========================= +//============================================================================= +// @section lcd + +// +// RepRapDiscount Smart Controller. +// https://reprap.org/wiki/RepRapDiscount_Smart_Controller +// +// Note: Usually sold with a white PCB. +// +//#define REPRAP_DISCOUNT_SMART_CONTROLLER + +// +// GT2560 (YHCB2004) LCD Display +// +// Requires Testato, Koepel softwarewire library and +// Andriy Golovnya's LiquidCrystal_AIP31068 library. +// +//#define YHCB2004 + +// +// Original RADDS LCD Display+Encoder+SDCardReader +// https://web.archive.org/web/20200719145306/doku.radds.org/dokumentation/lcd-display/ +// +//#define RADDS_DISPLAY + +// +// ULTIMAKER Controller. +// +//#define ULTIMAKERCONTROLLER + +// +// ULTIPANEL as seen on Thingiverse. +// +//#define ULTIPANEL + +// +// PanelOne from T3P3 (via RAMPS 1.4 AUX2/AUX3) +// https://reprap.org/wiki/PanelOne +// +//#define PANEL_ONE + +// +// GADGETS3D G3D LCD/SD Controller +// https://reprap.org/wiki/RAMPS_1.3/1.4_GADGETS3D_Shield_with_Panel +// +// Note: Usually sold with a blue PCB. +// +//#define G3D_PANEL + +// +// RigidBot Panel V1.0 +// +//#define RIGIDBOT_PANEL + +// +// Makeboard 3D Printer Parts 3D Printer Mini Display 1602 Mini Controller +// https://www.aliexpress.com/item/32765887917.html +// +//#define MAKEBOARD_MINI_2_LINE_DISPLAY_1602 + +// +// ANET and Tronxy 20x4 Controller +// +//#define ZONESTAR_LCD // Requires ADC_KEYPAD_PIN to be assigned to an analog pin. + // This LCD is known to be susceptible to electrical interference + // which scrambles the display. Pressing any button clears it up. + // This is a LCD2004 display with 5 analog buttons. + +// +// Generic 16x2, 16x4, 20x2, or 20x4 character-based LCD. +// +//#define ULTRA_LCD + +//============================================================================= +//======================== LCD / Controller Selection ========================= +//===================== (I2C and Shift-Register LCDs) ===================== +//============================================================================= + +// +// CONTROLLER TYPE: I2C +// +// Note: These controllers require the installation of Arduino's LiquidCrystal_I2C +// library. For more info: https://github.com/kiyoshigawa/LiquidCrystal_I2C +// + +// +// Elefu RA Board Control Panel +// https://web.archive.org/web/20140823033947/www.elefu.com/index.php?route=product/product&product_id=53 +// +//#define RA_CONTROL_PANEL + +// +// Sainsmart (YwRobot) LCD Displays +// +// These require LiquidCrystal_I2C library: +// https://github.com/MarlinFirmware/New-LiquidCrystal +// https://github.com/fmalpartida/New-LiquidCrystal/wiki +// +//#define LCD_SAINSMART_I2C_1602 +//#define LCD_SAINSMART_I2C_2004 + +// +// Generic LCM1602 LCD adapter +// +//#define LCM1602 + +// +// PANELOLU2 LCD with status LEDs, +// separate encoder and click inputs. +// +// Note: This controller requires Arduino's LiquidTWI2 library v1.2.3 or later. +// For more info: https://github.com/lincomatic/LiquidTWI2 +// +// Note: The PANELOLU2 encoder click input can either be directly connected to +// a pin (if BTN_ENC defined to != -1) or read through I2C (when BTN_ENC == -1). +// +//#define LCD_I2C_PANELOLU2 + +// +// Panucatt VIKI LCD with status LEDs, +// integrated click & L/R/U/D buttons, separate encoder inputs. +// +//#define LCD_I2C_VIKI + +// +// CONTROLLER TYPE: Shift register panels +// + +// +// 2-wire Non-latching LCD SR from https://github.com/fmalpartida/New-LiquidCrystal/wiki/schematics#user-content-ShiftRegister_connection +// LCD configuration: https://reprap.org/wiki/SAV_3D_LCD +// +//#define SAV_3DLCD + +// +// 3-wire SR LCD with strobe using 74HC4094 +// https://github.com/mikeshub/SailfishLCD +// Uses the code directly from Sailfish +// +//#define FF_INTERFACEBOARD + +// +// TFT GLCD Panel with Marlin UI +// Panel connected to main board by SPI or I2C interface. +// See https://github.com/Serhiy-K/TFTGLCDAdapter +// +//#define TFTGLCD_PANEL_SPI +//#define TFTGLCD_PANEL_I2C + +//============================================================================= +//======================= LCD / Controller Selection ======================= +//========================= (Graphical LCDs) ======================== +//============================================================================= + +// +// CONTROLLER TYPE: Graphical 128x64 (DOGM) +// +// IMPORTANT: The U8glib library is required for Graphical Display! +// https://github.com/olikraus/U8glib_Arduino +// +// NOTE: If the LCD is unresponsive you may need to reverse the plugs. +// + +// +// RepRapDiscount FULL GRAPHIC Smart Controller +// https://reprap.org/wiki/RepRapDiscount_Full_Graphic_Smart_Controller +// +//#define REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER + +// +// K.3D Full Graphic Smart Controller +// +//#define K3D_FULL_GRAPHIC_SMART_CONTROLLER + +// +// ReprapWorld Graphical LCD +// https://reprapworld.com/electronics/3d-printer-modules/autonomous-printing/graphical-lcd-screen-v1-0/ +// +//#define REPRAPWORLD_GRAPHICAL_LCD + +// +// Activate one of these if you have a Panucatt Devices +// Viki 2.0 or mini Viki with Graphic LCD +// https://www.panucatt.com +// +//#define VIKI2 +//#define miniVIKI + +// +// Alfawise Ex8 printer LCD marked as WYH L12864 COG +// +//#define WYH_L12864 + +// +// MakerLab Mini Panel with graphic +// controller and SD support - https://reprap.org/wiki/Mini_panel +// +//#define MINIPANEL + +// +// MaKr3d Makr-Panel with graphic controller and SD support. +// https://reprap.org/wiki/MaKrPanel +// +//#define MAKRPANEL + +// +// Adafruit ST7565 Full Graphic Controller. +// https://github.com/eboston/Adafruit-ST7565-Full-Graphic-Controller/ +// +//#define ELB_FULL_GRAPHIC_CONTROLLER + +// +// BQ LCD Smart Controller shipped by +// default with the BQ Hephestos 2 and Witbox 2. +// +//#define BQ_LCD_SMART_CONTROLLER + +// +// Cartesio UI +// https://web.archive.org/web/20180605050442/mauk.cc/webshop/cartesio-shop/electronics/user-interface +// +//#define CARTESIO_UI + +// +// LCD for Melzi Card with Graphical LCD +// +//#define LCD_FOR_MELZI + +// +// Original Ulticontroller from Ultimaker 2 printer with SSD1309 I2C display and encoder +// https://github.com/Ultimaker/Ultimaker2/tree/master/1249_Ulticontroller_Board_(x1) +// +//#define ULTI_CONTROLLER + +// +// MKS MINI12864 with graphic controller and SD support +// https://reprap.org/wiki/MKS_MINI_12864 +// +//#define MKS_MINI_12864 + +// +// MKS MINI12864 V3 is an alias for FYSETC_MINI_12864_2_1. Type A/B. NeoPixel RGB Backlight. +// +//#define MKS_MINI_12864_V3 + +// +// MKS LCD12864A/B with graphic controller and SD support. Follows MKS_MINI_12864 pinout. +// https://www.aliexpress.com/item/33018110072.html +// +//#define MKS_LCD12864A +//#define MKS_LCD12864B + +// +// FYSETC variant of the MINI12864 graphic controller with SD support +// https://wiki.fysetc.com/Mini12864_Panel/ +// +//#define FYSETC_MINI_12864_X_X // Type C/D/E/F. No tunable RGB Backlight by default +//#define FYSETC_MINI_12864_1_2 // Type C/D/E/F. Simple RGB Backlight (always on) +//#define FYSETC_MINI_12864_2_0 // Type A/B. Discreet RGB Backlight +//#define FYSETC_MINI_12864_2_1 // Type A/B. NeoPixel RGB Backlight +//#define FYSETC_GENERIC_12864_1_1 // Larger display with basic ON/OFF backlight. + +// +// BigTreeTech Mini 12864 V1.0 / V2.0 is an alias for FYSETC_MINI_12864_2_1. Type A/B. NeoPixel RGB Backlight. +// https://github.com/bigtreetech/MINI-12864 +// +//#define BTT_MINI_12864 + +// +// BEEZ MINI 12864 is an alias for FYSETC_MINI_12864_2_1. Type A/B. NeoPixel RGB Backlight. +// +//#define BEEZ_MINI_12864 + +// +// Factory display for Creality CR-10 / CR-7 / Ender-3 +// https://marlinfw.org/docs/hardware/controllers.html#cr10_stockdisplay +// +// Connect to EXP1 on RAMPS and compatible boards. +// +//#define CR10_STOCKDISPLAY + +// +// Ender-2 OEM display, a variant of the MKS_MINI_12864 +// +//#define ENDER2_STOCKDISPLAY + +// +// ANET and Tronxy 128×64 Full Graphics Controller as used on Anet A6 +// +//#define ANET_FULL_GRAPHICS_LCD + +// +// GUCOCO CTC 128×64 Full Graphics Controller as used on GUCOCO CTC A10S +// +//#define CTC_A10S_A13 + +// +// AZSMZ 12864 LCD with SD +// https://www.aliexpress.com/item/32837222770.html +// +//#define AZSMZ_12864 + +// +// Silvergate GLCD controller +// https://github.com/android444/Silvergate +// +//#define SILVER_GATE_GLCD_CONTROLLER + +// +// eMotion Tech LCD with SD +// https://www.reprap-france.com/produit/1234568748-ecran-graphique-128-x-64-points-2-1 +// +//#define EMOTION_TECH_LCD + +//============================================================================= +//============================== OLED Displays ============================== +//============================================================================= + +// +// SSD1306 OLED full graphics generic display +// +//#define U8GLIB_SSD1306 + +// +// SAV OLEd LCD module support using either SSD1306 or SH1106 based LCD modules +// +//#define SAV_3DGLCD +#if ENABLED(SAV_3DGLCD) + #define U8GLIB_SSD1306 + //#define U8GLIB_SH1106 +#endif + +// +// TinyBoy2 128x64 OLED / Encoder Panel +// +//#define OLED_PANEL_TINYBOY2 + +// +// MKS OLED 1.3" 128×64 Full Graphics Controller +// https://reprap.org/wiki/MKS_12864OLED +// +// Tiny, but very sharp OLED display +// +//#define MKS_12864OLED // Uses the SH1106 controller +//#define MKS_12864OLED_SSD1306 // Uses the SSD1306 controller + +// +// Zonestar OLED 128×64 Full Graphics Controller +// +//#define ZONESTAR_12864LCD // Graphical (DOGM) with ST7920 controller +//#define ZONESTAR_12864OLED // 1.3" OLED with SH1106 controller +//#define ZONESTAR_12864OLED_SSD1306 // 0.96" OLED with SSD1306 controller + +// +// Einstart S OLED SSD1306 +// +//#define U8GLIB_SH1106_EINSTART + +// +// Overlord OLED display/controller with i2c buzzer and LEDs +// +//#define OVERLORD_OLED + +// +// FYSETC OLED 2.42" 128×64 Full Graphics Controller with WS2812 RGB +// Where to find : https://www.aliexpress.com/item/4000345255731.html +//#define FYSETC_242_OLED_12864 // Uses the SSD1309 controller + +// +// K.3D SSD1309 OLED 2.42" 128×64 Full Graphics Controller +// +//#define K3D_242_OLED_CONTROLLER // Software SPI + +//============================================================================= +//========================== Extensible UI Displays =========================== +//============================================================================= + +/** + * DGUS Touch Display with DWIN OS. (Choose one.) + * + * ORIGIN (Marlin DWIN_SET) + * - Download https://github.com/coldtobi/Marlin_DGUS_Resources + * - Copy the downloaded DWIN_SET folder to the SD card. + * - Product: https://www.aliexpress.com/item/32993409517.html + * + * FYSETC (Supplier default) + * - Download https://github.com/FYSETC/FYSTLCD-2.0 + * - Copy the downloaded SCREEN folder to the SD card. + * - Product: https://www.aliexpress.com/item/32961471929.html + * + * HIPRECY (Supplier default) + * - Download https://github.com/HiPrecy/Touch-Lcd-LEO + * - Copy the downloaded DWIN_SET folder to the SD card. + * + * MKS (MKS-H43) (Supplier default) + * - Download https://github.com/makerbase-mks/MKS-H43 + * - Copy the downloaded DWIN_SET folder to the SD card. + * - Product: https://www.aliexpress.com/item/1005002008179262.html + * + * RELOADED (T5UID1) + * - Download https://github.com/Neo2003/DGUS-reloaded/releases + * - Copy the downloaded DWIN_SET folder to the SD card. + * + * IA_CREALITY (T5UID1) + * - Download https://github.com/InsanityAutomation/Marlin/raw/CrealityDwin_2.0/TM3D_Combined480272_Landscape_V7.7z + * - Copy the downloaded DWIN_SET folder to the SD card. + * + * E3S1PRO (T5L) + * - Download https://github.com/CrealityOfficial/Ender-3S1/archive/3S1_Plus_Screen.zip + * - Copy the downloaded DWIN_SET folder to the SD card. + * + * Flash display with DGUS Displays for Marlin: + * - Format the SD card to FAT32 with an allocation size of 4kb. + * - Download files as specified for your type of display. + * - Plug the microSD card into the back of the display. + * - Boot the display and wait for the update to complete. + * + * :[ 'ORIGIN', 'FYSETC', 'HYPRECY', 'MKS', 'RELOADED', 'IA_CREALITY', 'E3S1PRO' ] + */ +//#define DGUS_LCD_UI ORIGIN +#if DGUS_UI_IS(MKS) + #define USE_MKS_GREEN_UI +#elif DGUS_UI_IS(IA_CREALITY) + //#define LCD_SCREEN_ROTATE 90 // Portrait Mode or 800x480 displays + //#define IA_CREALITY_BOOT_DELAY 1500 // (ms) +#endif + +// +// Touch-screen LCD for Malyan M200/M300 printers +// +//#define MALYAN_LCD + +// +// Touch UI for FTDI EVE (FT800/FT810) displays +// See Configuration_adv.h for all configuration options. +// +//#define TOUCH_UI_FTDI_EVE + +// +// Touch-screen LCD for Anycubic Chiron +// +//#define ANYCUBIC_LCD_CHIRON + +// +// Touch-screen LCD for Anycubic i3 Mega +// +//#define ANYCUBIC_LCD_I3MEGA +#if ENABLED(ANYCUBIC_LCD_I3MEGA) + //#define ANYCUBIC_LCD_GCODE_EXT // Add ".gcode" to menu entries for DGUS clone compatibility +#endif + +// +// Touch-screen LCD for Anycubic Vyper +// +//#define ANYCUBIC_LCD_VYPER + +// +// Sovol SV-06 Resistive Touch Screen +// +//#define SOVOL_SV06_RTS + +// +// 320x240 Nextion 2.8" serial TFT Resistive Touch Screen NX3224T028 +// +//#define NEXTION_TFT + +// +// Third-party or vendor-customized controller interfaces. +// Sources should be installed in 'src/lcd/extui'. +// +//#define EXTENSIBLE_UI + +#if ENABLED(EXTENSIBLE_UI) + //#define EXTUI_LOCAL_BEEPER // Enables use of local Beeper pin with external display +#endif + +//============================================================================= +//=============================== Graphical TFTs ============================== +//============================================================================= + +/** + * Specific TFT Model Presets. Enable one of the following options + * or enable TFT_GENERIC and set sub-options. + */ + +// +// 480x320, 3.5", SPI Display with Rotary Encoder from MKS +// Usually paired with MKS Robin Nano V2 & V3 +// https://github.com/makerbase-mks/MKS-TFT-Hardware/tree/master/MKS%20TS35 +// +//#define MKS_TS35_V2_0 + +// +// 320x240, 2.4", FSMC Display From MKS +// Usually paired with MKS Robin Nano V1.2 +// +//#define MKS_ROBIN_TFT24 + +// +// 320x240, 2.8", FSMC Display From MKS +// Usually paired with MKS Robin Nano V1.2 +// +//#define MKS_ROBIN_TFT28 + +// +// 320x240, 3.2", FSMC Display From MKS +// Usually paired with MKS Robin Nano V1.2 +// +//#define MKS_ROBIN_TFT32 + +// +// 480x320, 3.5", FSMC Display From MKS +// Usually paired with MKS Robin Nano V1.2 +// +//#define MKS_ROBIN_TFT35 + +// +// 480x272, 4.3", FSMC Display From MKS +// +//#define MKS_ROBIN_TFT43 + +// +// 320x240, 3.2", FSMC Display From MKS +// Usually paired with MKS Robin +// +//#define MKS_ROBIN_TFT_V1_1R + +// +// 480x320, 3.5", FSMC Stock Display from Tronxy +// +//#define TFT_TRONXY_X5SA + +// +// 480x320, 3.5", FSMC Stock Display from AnyCubic +// +//#define ANYCUBIC_TFT35 + +// +// 320x240, 2.8", FSMC Stock Display from Longer/Alfawise +// +//#define LONGER_LK_TFT28 + +// +// 320x240, 2.8", FSMC Stock Display from ET4 +// +//#define ANET_ET4_TFT28 + +// +// 480x320, 3.5", FSMC Stock Display from ET5 +// +//#define ANET_ET5_TFT35 + +// +// 1024x600, 7", RGB Stock Display with Rotary Encoder from BIQU BX +// https://github.com/bigtreetech/BIQU-BX/tree/master/Hardware +// +//#define BIQU_BX_TFT70 + +// +// 480x320, 3.5", SPI Stock Display with Rotary Encoder from BIQU B1 SE Series +// https://github.com/bigtreetech/TFT35-SPI/tree/master/v1 +// +//#define BTT_TFT35_SPI_V1_0 + +// +// Generic TFT with detailed options +// +//#define TFT_GENERIC +#if ENABLED(TFT_GENERIC) + // :[ 'AUTO', 'ST7735', 'ST7789', 'ST7796', 'R61505', 'ILI9328', 'ILI9341', 'ILI9488' ] + #define TFT_DRIVER AUTO + + // Interface. Enable one of the following options: + //#define TFT_INTERFACE_FSMC + //#define TFT_INTERFACE_SPI + + // TFT Resolution. Enable one of the following options: + //#define TFT_RES_320x240 + //#define TFT_RES_480x272 + //#define TFT_RES_480x320 + //#define TFT_RES_1024x600 +#endif + +/** + * TFT UI - User Interface Selection. Enable one of the following options: + * + * TFT_CLASSIC_UI - Emulated DOGM - 128x64 Upscaled + * TFT_COLOR_UI - Marlin Default Menus, Touch Friendly, using full TFT capabilities + * TFT_LVGL_UI - A Modern UI using LVGL + * + * For LVGL_UI also copy the 'assets' folder from the build directory to the + * root of your SD card, together with the compiled firmware. + */ +//#define TFT_CLASSIC_UI +//#define TFT_COLOR_UI +//#define TFT_LVGL_UI + +#if ENABLED(TFT_COLOR_UI) + /** + * TFT Font for Color UI. Choose one of the following: + * + * NOTOSANS - Default font with anti-aliasing. Supports Latin Extended and non-Latin characters. + * UNIFONT - Lightweight font, no anti-aliasing. Supports Latin Extended and non-Latin characters. + * HELVETICA - Lightweight font, no anti-aliasing. Supports Basic Latin (0x0020-0x007F) and Latin-1 Supplement (0x0080-0x00FF) characters only. + */ + #define TFT_FONT NOTOSANS + + /** + * TFT Theme for Color UI. Choose one of the following or add a new one to 'Marlin/src/lcd/tft/themes' directory + * + * BLUE_MARLIN - Default theme with 'midnight blue' background + * BLACK_MARLIN - Theme with 'black' background + * ANET_BLACK - Theme used for Anet ET4/5 + */ + #define TFT_THEME BLACK_MARLIN + + //#define TFT_SHARED_IO // I/O is shared between TFT display and other devices. Disable async data transfer. + + #define COMPACT_MARLIN_BOOT_LOGO // Use compressed data to save Flash space +#endif + +#if ENABLED(TFT_LVGL_UI) + //#define MKS_WIFI_MODULE // MKS WiFi module +#endif + +/** + * TFT Rotation. Set to one of the following values: + * + * TFT_ROTATE_90, TFT_ROTATE_90_MIRROR_X, TFT_ROTATE_90_MIRROR_Y, + * TFT_ROTATE_180, TFT_ROTATE_180_MIRROR_X, TFT_ROTATE_180_MIRROR_Y, + * TFT_ROTATE_270, TFT_ROTATE_270_MIRROR_X, TFT_ROTATE_270_MIRROR_Y, + * TFT_MIRROR_X, TFT_MIRROR_Y, TFT_NO_ROTATION + * + * :{ 'TFT_NO_ROTATION':'None', 'TFT_ROTATE_90':'90°', 'TFT_ROTATE_90_MIRROR_X':'90° (Mirror X)', 'TFT_ROTATE_90_MIRROR_Y':'90° (Mirror Y)', 'TFT_ROTATE_180':'180°', 'TFT_ROTATE_180_MIRROR_X':'180° (Mirror X)', 'TFT_ROTATE_180_MIRROR_Y':'180° (Mirror Y)', 'TFT_ROTATE_270':'270°', 'TFT_ROTATE_270_MIRROR_X':'270° (Mirror X)', 'TFT_ROTATE_270_MIRROR_Y':'270° (Mirror Y)', 'TFT_MIRROR_X':'Mirror X', 'TFT_MIRROR_Y':'Mirror Y' } + */ +//#define TFT_ROTATION TFT_NO_ROTATION + +//============================================================================= +//============================ Other Controllers ============================ +//============================================================================= + +// +// Ender-3 v2 OEM display. A DWIN display with Rotary Encoder. +// +//#define DWIN_CREALITY_LCD // Creality UI +//#define DWIN_LCD_PROUI // Pro UI by MRiscoC +//#define DWIN_CREALITY_LCD_JYERSUI // Jyers UI by Jacob Myers +//#define DWIN_MARLINUI_PORTRAIT // MarlinUI (portrait orientation) +//#define DWIN_MARLINUI_LANDSCAPE // MarlinUI (landscape orientation) + +// +// Touch Screen Settings +// +//#define TOUCH_SCREEN +#if ENABLED(TOUCH_SCREEN) + #define BUTTON_DELAY_EDIT 50 // (ms) Button repeat delay for edit screens + #define BUTTON_DELAY_MENU 250 // (ms) Button repeat delay for menus + + #if ANY(TFT_CLASSIC_UI, TFT_COLOR_UI) + //#define NO_BACK_MENU_ITEM // Don't display a top menu item to go back to the parent menu + #endif + + #define TOUCH_SCREEN_CALIBRATION + + //#define TOUCH_CALIBRATION_X 12316 + //#define TOUCH_CALIBRATION_Y -8981 + //#define TOUCH_OFFSET_X -43 + //#define TOUCH_OFFSET_Y 257 + //#define TOUCH_ORIENTATION TOUCH_LANDSCAPE + + #if ALL(TOUCH_SCREEN_CALIBRATION, EEPROM_SETTINGS) + #define TOUCH_CALIBRATION_AUTO_SAVE // Auto save successful calibration values to EEPROM + #endif + + #if ENABLED(TFT_COLOR_UI) + //#define SINGLE_TOUCH_NAVIGATION + #endif +#endif + +// +// RepRapWorld REPRAPWORLD_KEYPAD v1.1 +// https://reprapworld.com/products/electronics/ramps/keypad_v1_0_fully_assembled/ +// +//#define REPRAPWORLD_KEYPAD +#if ENABLED(REPRAPWORLD_KEYPAD) + //#define REPRAPWORLD_KEYPAD_MOVE_STEP 10.0 // (mm) Distance to move per key-press +#endif + +// +// EasyThreeD ET-4000+ with button input and status LED +// +//#define EASYTHREED_UI + +//============================================================================= +//=============================== Extra Features ============================== +//============================================================================= + +// @section fans + +// Set number of user-controlled fans. Disable to use all board-defined fans. +// :[1,2,3,4,5,6,7,8] +//#define NUM_M106_FANS 1 + +// Use software PWM to drive the fan, as for the heaters. This uses a very low frequency +// which is not as annoying as with the hardware PWM. On the other hand, if this frequency +// is too low, you should also increment SOFT_PWM_SCALE. +//#define FAN_SOFT_PWM + +// Incrementing this by 1 will double the software PWM frequency, +// affecting heaters, and the fan if FAN_SOFT_PWM is enabled. +// However, control resolution will be halved for each increment; +// at zero value, there are 128 effective control positions. +// :[0,1,2,3,4,5,6,7] +#define SOFT_PWM_SCALE 0 + +// If SOFT_PWM_SCALE is set to a value higher than 0, dithering can +// be used to mitigate the associated resolution loss. If enabled, +// some of the PWM cycles are stretched so on average the desired +// duty cycle is attained. +//#define SOFT_PWM_DITHER + +// @section extras + +// Support for the BariCUDA Paste Extruder +//#define BARICUDA + +// @section lights + +// Temperature status LEDs that display the hotend and bed temperature. +// If all hotends, bed temperature, and target temperature are under 54C +// then the BLUE led is on. Otherwise the RED led is on. (1C hysteresis) +//#define TEMP_STAT_LEDS + +// Support for BlinkM/CyzRgb +//#define BLINKM + +// Support for PCA9632 PWM LED driver +//#define PCA9632 + +// Support for PCA9533 PWM LED driver +//#define PCA9533 + +/** + * RGB LED / LED Strip Control + * + * Enable support for an RGB LED connected to 5V digital pins, or + * an RGB Strip connected to MOSFETs controlled by digital pins. + * + * Adds the M150 command to set the LED (or LED strip) color. + * If pins are PWM capable (e.g., 4, 5, 6, 11) then a range of + * luminance values can be set from 0 to 255. + * For NeoPixel LED an overall brightness parameter is also available. + * + * === CAUTION === + * LED Strips require a MOSFET Chip between PWM lines and LEDs, + * as the Arduino cannot handle the current the LEDs will require. + * Failure to follow this precaution can destroy your Arduino! + * + * NOTE: A separate 5V power supply is required! The NeoPixel LED needs + * more current than the Arduino 5V linear regulator can produce. + * + * Requires PWM frequency between 50 <> 100Hz (Check HAL or variant) + * Use FAST_PWM_FAN, if possible, to reduce fan noise. + */ + +// LED Type. Enable only one of the following two options: +//#define RGB_LED +//#define RGBW_LED + +#if ANY(RGB_LED, RGBW_LED) + //#define RGB_LED_R_PIN 34 + //#define RGB_LED_G_PIN 43 + //#define RGB_LED_B_PIN 35 + //#define RGB_LED_W_PIN -1 +#endif + +#if ANY(RGB_LED, RGBW_LED, PCA9632) + //#define RGB_STARTUP_TEST // For PWM pins, fade between all colors + #if ENABLED(RGB_STARTUP_TEST) + #define RGB_STARTUP_TEST_INNER_MS 10 // (ms) Reduce or increase fading speed + #endif +#endif + +// Support for Adafruit NeoPixel LED driver +//#define NEOPIXEL_LED +#if ENABLED(NEOPIXEL_LED) + #define NEOPIXEL_TYPE NEO_GRBW // NEO_GRBW, NEO_RGBW, NEO_GRB, NEO_RBG, etc. + // See https://github.com/adafruit/Adafruit_NeoPixel/blob/master/Adafruit_NeoPixel.h + //#define NEOPIXEL_PIN 4 // LED driving pin + //#define NEOPIXEL2_TYPE NEOPIXEL_TYPE + //#define NEOPIXEL2_PIN 5 + #define NEOPIXEL_PIXELS 30 // Number of LEDs in the strip. (Longest strip when NEOPIXEL2_SEPARATE is disabled.) + #define NEOPIXEL_IS_SEQUENTIAL // Sequential display for temperature change - LED by LED. Disable to change all LEDs at once. + #define NEOPIXEL_BRIGHTNESS 127 // Initial brightness (0-255) + //#define NEOPIXEL_STARTUP_TEST // Cycle through colors at startup + + // Support for second Adafruit NeoPixel LED driver controlled with M150 S1 ... + //#define NEOPIXEL2_SEPARATE + #if ENABLED(NEOPIXEL2_SEPARATE) + #define NEOPIXEL2_PIXELS 15 // Number of LEDs in the second strip + #define NEOPIXEL2_BRIGHTNESS 127 // Initial brightness (0-255) + #define NEOPIXEL2_STARTUP_TEST // Cycle through colors at startup + #define NEOPIXEL_M150_DEFAULT -1 // Default strip for M150 without 'S'. Use -1 to set all by default. + #else + //#define NEOPIXEL2_INSERIES // Default behavior is NeoPixel 2 in parallel + #endif + + // Use some of the NeoPixel LEDs for static (background) lighting + //#define NEOPIXEL_BKGD_INDEX_FIRST 0 // Index of the first background LED + //#define NEOPIXEL_BKGD_INDEX_LAST 5 // Index of the last background LED + //#define NEOPIXEL_BKGD_COLOR { 255, 255, 255, 0 } // R, G, B, W + //#define NEOPIXEL_BKGD_TIMEOUT_COLOR { 25, 25, 25, 0 } // R, G, B, W + //#define NEOPIXEL_BKGD_ALWAYS_ON // Keep the backlight on when other NeoPixels are off +#endif + +/** + * Printer Event LEDs + * + * During printing, the LEDs will reflect the printer status: + * + * - Gradually change from blue to violet as the heated bed gets to target temp + * - Gradually change from violet to red as the hotend gets to temperature + * - Change to white to illuminate work surface + * - Change to green once print has finished + * - Turn off after the print has finished and the user has pushed a button + */ +#if ANY(BLINKM, RGB_LED, RGBW_LED, PCA9632, PCA9533, NEOPIXEL_LED) + #define PRINTER_EVENT_LEDS +#endif + +// @section servos + +/** + * Number of servos + * + * For some servo-related options NUM_SERVOS will be set automatically. + * Set this manually if there are extra servos needing manual control. + * Set to 0 to turn off servo support. + */ +//#define NUM_SERVOS 3 // Note: Servo index starts with 0 for M280-M282 commands + +// (ms) Delay before the next move will start, to give the servo time to reach its target angle. +// 300ms is a good value but you can try less delay. +// If the servo can't reach the requested position, increase it. +#define SERVO_DELAY { 300 } + +// Only power servos during movement, otherwise leave off to prevent jitter +//#define DEACTIVATE_SERVOS_AFTER_MOVE + +// Edit servo angles with M281 and save to EEPROM with M500 +//#define EDITABLE_SERVO_ANGLES + +// Disable servo with M282 to reduce power consumption, noise, and heat when not in use +//#define SERVO_DETACH_GCODE diff --git a/Marlin/Modified files/G29.cpp b/Marlin/Modified files/G29.cpp new file mode 100644 index 0000000000..d2ba9a27fe --- /dev/null +++ b/Marlin/Modified files/G29.cpp @@ -0,0 +1,1069 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +/** + * G29.cpp - Auto Bed Leveling + */ + +#include "../../../inc/MarlinConfig.h" + +#if HAS_ABL_NOT_UBL + +#include "../../gcode.h" +#include "../../../feature/bedlevel/bedlevel.h" +#include "../../../module/motion.h" +#include "../../../module/planner.h" +#include "../../../module/probe.h" +#include "../../../module/temperature.h" +#include "../../queue.h" + +#if ENABLED(AUTO_BED_LEVELING_LINEAR) + #include "../../../libs/least_squares_fit.h" +#endif + +#if ABL_PLANAR + #include "../../../libs/vector_3.h" +#endif +#if ENABLED(BD_SENSOR_PROBE_NO_STOP) + #include "../../../feature/bedlevel/bdl/bdl.h" +#endif + +#include "../../../lcd/marlinui.h" +#if ENABLED(EXTENSIBLE_UI) + #include "../../../lcd/extui/ui_api.h" +#elif ENABLED(DWIN_CREALITY_LCD) + #include "../../../lcd/e3v2/creality/dwin.h" +#elif ENABLED(SOVOL_SV06_RTS) + #include "../../../lcd/sovol_rts/sovol_rts.h" +#endif + +#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) +#include "../../../core/debug_out.h" + +#if DISABLED(PROBE_MANUALLY) && FT_MOTION_DISABLE_FOR_PROBING + #include "../../../module/ft_motion.h" +#endif + +#if ABL_USES_GRID + #if ENABLED(PROBE_Y_FIRST) + #define PR_OUTER_VAR abl.meshCount.x + #define PR_OUTER_SIZE abl.grid_points.x + #define PR_INNER_VAR abl.meshCount.y + #define PR_INNER_SIZE abl.grid_points.y + #else + #define PR_OUTER_VAR abl.meshCount.y + #define PR_OUTER_SIZE abl.grid_points.y + #define PR_INNER_VAR abl.meshCount.x + #define PR_INNER_SIZE abl.grid_points.x + #endif +#endif + +#ifdef USE_PROBE_FOR_MESH_REF + float mesh_zero_ref_offset = 0; // declared in gcode.h as external so it can be set in menu_probe_level.cpp +#endif + +/** + * @brief Do some things before returning from G29. + * @param retry : true if the G29 can and should be retried. false if the failure is too serious. + * @param did : true if the leveling procedure completed successfully. + */ +static void pre_g29_return(const bool retry, const bool did) { + if (!retry) { + TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_IDLE, false)); + } + #if DISABLED(G29_RETRY_AND_RECOVER) + if (!retry || did) { + TERN_(DWIN_CREALITY_LCD, dwinLevelingDone()); + TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone()); + } + #endif +} + +#define G29_RETURN(retry, did) do{ \ + pre_g29_return(TERN0(G29_RETRY_AND_RECOVER, retry), did); \ + return TERN_(G29_RETRY_AND_RECOVER, retry); \ +}while(0) + +// For manual probing values persist over multiple G29 +class G29_State { +public: + int verbose_level; + xy_pos_t probePos; + float measured_z; + bool dryrun, + reenable; + + #if ANY(PROBE_MANUALLY, AUTO_BED_LEVELING_LINEAR) + int abl_probe_index; + #endif + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + grid_count_t abl_points; + #elif ENABLED(AUTO_BED_LEVELING_3POINT) + static constexpr grid_count_t abl_points = 3; + #elif ABL_USES_GRID + static constexpr grid_count_t abl_points = GRID_MAX_POINTS; + #endif + + #if ABL_USES_GRID + + xy_int8_t meshCount; + + xy_pos_t probe_position_lf, + probe_position_rb; + + xy_float_t gridSpacing; // = { 0.0f, 0.0f } + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + bool topography_map; + xy_uint8_t grid_points; + #else // Bilinear + static constexpr xy_uint8_t grid_points = { GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y }; + #endif + + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + float Z_offset; + bed_mesh_t z_values; + #endif + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + int indexIntoAB[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; + float eqnAMatrix[GRID_MAX_POINTS * 3], // "A" matrix of the linear system of equations + eqnBVector[GRID_MAX_POINTS], // "B" vector of Z points + mean; + #endif + #endif +}; + +#if ABL_USES_GRID && ANY(AUTO_BED_LEVELING_3POINT, AUTO_BED_LEVELING_BILINEAR) + constexpr xy_uint8_t G29_State::grid_points; + constexpr grid_count_t G29_State::abl_points; +#endif + +/** + * G29: Detailed Z probe, probes the bed at 3 or more points. + * Will fail if the printer has not been homed with G28. + * + * Enhanced G29 Auto Bed Leveling Probe Routine + * + * O Auto-level only if needed + * + * D Dry-Run mode. Just evaluate the bed Topology - Don't apply + * or alter the bed level data. Useful to check the topology + * after a first run of G29. + * + * J Jettison current bed leveling data + * + * V Set the verbose level (0-4). Example: "G29 V3" + * + * Parameters With LINEAR leveling only: + * + * P Set the size of the grid that will be probed (P x P points). + * Example: "G29 P4" + * + * X Set the X size of the grid that will be probed (X x Y points). + * Example: "G29 X7 Y5" + * + * Y Set the Y size of the grid that will be probed (X x Y points). + * + * T Generate a Bed Topology Report. Example: "G29 P5 T" for a detailed report. + * This is useful for manual bed leveling and finding flaws in the bed (to + * assist with part placement). + * Not supported by non-linear delta printer bed leveling. + * + * Parameters With LINEAR and BILINEAR leveling only: + * + * S Set the XY travel speed between probe points (in units/min) + * + * H Set bounds to a centered square H x H units in size + * + * -or- + * + * F Set the Front limit of the probing grid + * B Set the Back limit of the probing grid + * L Set the Left limit of the probing grid + * R Set the Right limit of the probing grid + * + * Parameters with DEBUG_LEVELING_FEATURE only: + * + * C Make a totally fake grid with no actual probing. + * For use in testing when no probing is possible. + * + * Parameters with BILINEAR leveling only: + * + * Z Supply an additional Z probe offset + * + * Extra parameters with PROBE_MANUALLY: + * + * To do manual probing simply repeat G29 until the procedure is complete. + * The first G29 accepts parameters. 'G29 Q' for status, 'G29 A' to abort. + * + * Q Query leveling and G29 state + * + * A Abort current leveling procedure + * + * Extra parameters with BILINEAR only: + * + * W Write a mesh point. (If G29 is idle.) + * I X index for mesh point + * J Y index for mesh point + * X X for mesh point, overrides I + * Y Y for mesh point, overrides J + * Z Z for mesh point. Otherwise, raw current Z. + * + * Without PROBE_MANUALLY: + * + * E By default G29 will engage the Z probe, test the bed, then disengage. + * Include "E" to engage/disengage the Z probe for each sample. + * There's no extra effect if you have a fixed Z probe. + */ +G29_TYPE GcodeSuite::G29() { + + DEBUG_SECTION(log_G29, "G29", DEBUGGING(LEVELING)); + + // Leveling state is persistent when done manually with multiple G29 commands + TERN_(PROBE_MANUALLY, static) G29_State abl; + + // Keep powered steppers from timing out + reset_stepper_timeout(); + + // Q = Query leveling and G29 state + const bool seenQ = ANY(DEBUG_LEVELING_FEATURE, PROBE_MANUALLY) && parser.seen_test('Q'); + + // G29 Q is also available if debugging + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (seenQ || DEBUGGING(LEVELING)) log_machine_info(); + if (DISABLED(PROBE_MANUALLY) && seenQ) G29_RETURN(false, false); + #endif + + // A = Abort manual probing + // C = Generate fake probe points (DEBUG_LEVELING_FEATURE) + const bool seenA = TERN0(PROBE_MANUALLY, parser.seen_test('A')), + no_action = seenA || seenQ, + faux = ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) ? parser.boolval('C') : no_action; + + // O = Don't level if leveling is already active + if (!no_action && planner.leveling_active && parser.boolval('O')) { + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> Auto-level not needed, skip"); + G29_RETURN(false, false); + } + + #ifdef USE_PROBE_FOR_MESH_REF + // Send 'N' to force homing before G29 (internal only) + if (parser.seen_test('N')){ + process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); + } + else { + process_subcommands_now(F("G28L0 X Y")); // Home X and Y only + } + // Set the probe trigger height as Z home before leveling + probe.probe_at_point(current_position, PROBE_PT_NONE,0 ,false ,true, Z_PROBE_LOW_POINT, Z_TWEEN_SAFE_CLEARANCE, false); + set_axis_is_at_home(Z_AXIS); + sync_plan_position(); + #else + // Send 'N' to force homing before G29 (internal only) + if (parser.seen_test('N')){ + process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); + } + #endif + + // Don't allow auto-leveling without homing first + if (homing_needed_error()) G29_RETURN(false, false); + + // 3-point leveling gets points from the probe class + #if ENABLED(AUTO_BED_LEVELING_3POINT) + vector_3 points[3]; + probe.get_three_points(points); + #endif + + // Storage for ABL Linear results + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + struct linear_fit_data lsf_results; + #endif + + // Set and report "probing" state to host + TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_PROBE, false)); + + #if DISABLED(PROBE_MANUALLY) && FT_MOTION_DISABLE_FOR_PROBING + FTMotionDisableInScope FT_Disabler; // Disable Fixed-Time Motion for probing + #endif + + /** + * On the initial G29 fetch command parameters. + */ + if (!g29_in_progress) { + + probe.use_probing_tool(); + + #ifdef EVENT_GCODE_BEFORE_G29 + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Before G29 G-code: ", EVENT_GCODE_BEFORE_G29); + gcode.process_subcommands_now(F(EVENT_GCODE_BEFORE_G29)); + #endif + + #if ANY(PROBE_MANUALLY, AUTO_BED_LEVELING_LINEAR) + abl.abl_probe_index = -1; + #endif + + abl.reenable = planner.leveling_active; + + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + + const bool seen_w = parser.seen_test('W'); + if (seen_w) { + if (!leveling_is_valid()) { + SERIAL_ERROR_MSG("No bilinear grid"); + G29_RETURN(false, false); + } + + const float rz = parser.seenval('Z') ? RAW_Z_POSITION(parser.value_linear_units()) : current_position.z; + if (!WITHIN(rz, -10, 10)) { + SERIAL_ERROR_MSG("Bad Z value"); + G29_RETURN(false, false); + } + + const float rx = RAW_X_POSITION(parser.linearval('X', NAN)), + ry = RAW_Y_POSITION(parser.linearval('Y', NAN)); + int8_t i = parser.byteval('I', -1), j = parser.byteval('J', -1); + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" + + if (!isnan(rx) && !isnan(ry)) { + // Get nearest i / j from rx / ry + i = (rx - bedlevel.grid_start.x) / bedlevel.grid_spacing.x + 0.5f; + j = (ry - bedlevel.grid_start.y) / bedlevel.grid_spacing.y + 0.5f; + LIMIT(i, 0, (GRID_MAX_POINTS_X) - 1); + LIMIT(j, 0, (GRID_MAX_POINTS_Y) - 1); + } + + #pragma GCC diagnostic pop + + if (WITHIN(i, 0, (GRID_MAX_POINTS_X) - 1) && WITHIN(j, 0, (GRID_MAX_POINTS_Y) - 1)) { + set_bed_leveling_enabled(false); + bedlevel.z_values[i][j] = rz; + bedlevel.refresh_bed_level(); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(i, j, rz)); + if (abl.reenable) { + set_bed_leveling_enabled(true); + report_current_position(); + } + } + G29_RETURN(false, false); + } // parser.seen_test('W') + + #else + + constexpr bool seen_w = false; + + #endif + + // Jettison bed leveling data + if (!seen_w && parser.seen_test('J')) { + reset_bed_level(); + G29_RETURN(false, false); + } + + abl.verbose_level = parser.intval('V'); + if (!WITHIN(abl.verbose_level, 0, 4)) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(V)erbose level implausible (0-4).")); + G29_RETURN(false, false); + } + + abl.dryrun = parser.boolval('D') || TERN0(PROBE_MANUALLY, no_action); + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + + incremental_LSF_reset(&lsf_results); + + abl.topography_map = abl.verbose_level > 2 || parser.boolval('T'); + + // X and Y specify points in each direction, overriding the default + // These values may be saved with the completed mesh + abl.grid_points.set( + parser.byteval('X', GRID_MAX_POINTS_X), + parser.byteval('Y', GRID_MAX_POINTS_Y) + ); + if (parser.seenval('P')) abl.grid_points.x = abl.grid_points.y = parser.value_int(); + + if (!WITHIN(abl.grid_points.x, 2, GRID_MAX_POINTS_X)) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Probe points (X) implausible (2-" STRINGIFY(GRID_MAX_POINTS_X) ").")); + G29_RETURN(false, false); + } + if (!WITHIN(abl.grid_points.y, 2, GRID_MAX_POINTS_Y)) { + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Probe points (Y) implausible (2-" STRINGIFY(GRID_MAX_POINTS_Y) ").")); + G29_RETURN(false, false); + } + + abl.abl_points = abl.grid_points.x * abl.grid_points.y; + abl.mean = 0; + + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + + abl.Z_offset = parser.linearval('Z'); + + #endif + + #if ABL_USES_GRID + + constexpr feedRate_t min_probe_feedrate_mm_s = XY_PROBE_FEEDRATE_MIN; + xy_probe_feedrate_mm_s = MMM_TO_MMS(parser.linearval('S', XY_PROBE_FEEDRATE)); + if (xy_probe_feedrate_mm_s < min_probe_feedrate_mm_s) { + xy_probe_feedrate_mm_s = min_probe_feedrate_mm_s; + SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Feedrate (S) too low. (Using ", min_probe_feedrate_mm_s, ")")); + } + + const float x_min = probe.min_x(), x_max = probe.max_x(), + y_min = probe.min_y(), y_max = probe.max_y(); + + if (parser.seen('H')) { + const int16_t size = (int16_t)parser.value_linear_units(); + abl.probe_position_lf.set(_MAX((X_CENTER) - size / 2, x_min), _MAX((Y_CENTER) - size / 2, y_min)); + abl.probe_position_rb.set(_MIN(abl.probe_position_lf.x + size, x_max), _MIN(abl.probe_position_lf.y + size, y_max)); + } + else { + abl.probe_position_lf.set(parser.linearval('L', x_min), parser.linearval('F', y_min)); + abl.probe_position_rb.set(parser.linearval('R', x_max), parser.linearval('B', y_max)); + } + + if (!probe.good_bounds(abl.probe_position_lf, abl.probe_position_rb)) { + if (DEBUGGING(LEVELING)) { + DEBUG_ECHOLNPGM("G29 L", abl.probe_position_lf.x, " R", abl.probe_position_rb.x, + " F", abl.probe_position_lf.y, " B", abl.probe_position_rb.y); + } + SERIAL_ECHOLNPGM(GCODE_ERR_MSG(" (L,R,F,B) out of bounds.")); + G29_RETURN(false, false); + } + + // Probe at the points of a lattice grid + abl.gridSpacing.set((abl.probe_position_rb.x - abl.probe_position_lf.x) / (abl.grid_points.x - 1), + (abl.probe_position_rb.y - abl.probe_position_lf.y) / (abl.grid_points.y - 1)); + + #endif // ABL_USES_GRID + + if (abl.verbose_level > 0) { + SERIAL_ECHOPGM("G29 Auto Bed Leveling"); + if (abl.dryrun) SERIAL_ECHOPGM(" (DRYRUN)"); + SERIAL_EOL(); + } + + planner.synchronize(); + + #if ENABLED(AUTO_BED_LEVELING_3POINT) + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> 3-point Leveling"); + points[0].z = points[1].z = points[2].z = 0; // Probe at 3 arbitrary points + #endif + + TERN_(EXTENSIBLE_UI, ExtUI::onLevelingStart()); + + if (!faux) { + remember_feedrate_scaling_off(); + + #if ENABLED(PREHEAT_BEFORE_LEVELING) + #if ENABLED(SOVOL_SV06_RTS) + rts.updateTempE0(); + rts.updateTempBed(); + rts.sendData(1, Wait_VP); + rts.gotoPage(ID_ABL_HeatWait_L, ID_ABL_HeatWait_D); + #endif + if (!abl.dryrun) probe.preheat_for_probing(LEVELING_NOZZLE_TEMP, + TERN(EXTENSIBLE_UI, ExtUI::getLevelingBedTemp(), LEVELING_BED_TEMP) + ); + #endif + } + + // Position bed horizontally and Z probe vertically. + #if HAS_SAFE_BED_LEVELING + xyze_pos_t safe_position = current_position; + #ifdef SAFE_BED_LEVELING_START_X + safe_position.x = SAFE_BED_LEVELING_START_X; + #endif + #ifdef SAFE_BED_LEVELING_START_Y + safe_position.y = SAFE_BED_LEVELING_START_Y; + #endif + #ifdef SAFE_BED_LEVELING_START_Z + safe_position.z = SAFE_BED_LEVELING_START_Z; + #endif + #ifdef SAFE_BED_LEVELING_START_I + safe_position.i = SAFE_BED_LEVELING_START_I; + #endif + #ifdef SAFE_BED_LEVELING_START_J + safe_position.j = SAFE_BED_LEVELING_START_J; + #endif + #ifdef SAFE_BED_LEVELING_START_K + safe_position.k = SAFE_BED_LEVELING_START_K; + #endif + #ifdef SAFE_BED_LEVELING_START_U + safe_position.u = SAFE_BED_LEVELING_START_U; + #endif + #ifdef SAFE_BED_LEVELING_START_V + safe_position.v = SAFE_BED_LEVELING_START_V; + #endif + #ifdef SAFE_BED_LEVELING_START_W + safe_position.w = SAFE_BED_LEVELING_START_W; + #endif + + do_blocking_move_to(safe_position); + #endif // HAS_SAFE_BED_LEVELING + + // Disable auto bed leveling during G29. + // Be formal so G29 can be done successively without G28. + if (!no_action) set_bed_leveling_enabled(false); + + // Deploy certain probes before starting probing + #if ENABLED(BLTOUCH) || ALL(HAS_Z_SERVO_PROBE, Z_SERVO_INTERMEDIATE_STOW) + do_z_clearance(Z_CLEARANCE_DEPLOY_PROBE); + #elif HAS_BED_PROBE + if (probe.deploy()) { // (returns true on deploy failure) + set_bed_leveling_enabled(abl.reenable); + G29_RETURN(false, true); + } + #endif + + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + if (!abl.dryrun && (abl.gridSpacing != bedlevel.grid_spacing || abl.probe_position_lf != bedlevel.grid_start)) { + reset_bed_level(); // Reset grid to 0.0 or "not probed". (Also disables ABL) + abl.reenable = false; // Can't re-enable (on error) until the new grid is written + } + // Pre-populate local Z values from the stored mesh + TERN_(IS_KINEMATIC, COPY(abl.z_values, bedlevel.z_values)); + #endif + + } // !g29_in_progress + + #if ENABLED(PROBE_MANUALLY) + + // For manual probing, get the next index to probe now. + // On the first probe this will be incremented to 0. + if (!no_action) { + ++abl.abl_probe_index; + g29_in_progress = true; + } + + // Abort current G29 procedure, go back to idle state + if (seenA && g29_in_progress) { + SERIAL_ECHOLNPGM("Manual G29 aborted"); + SET_SOFT_ENDSTOP_LOOSE(false); + set_bed_leveling_enabled(abl.reenable); + g29_in_progress = false; + TERN_(LCD_BED_LEVELING, ui.wait_for_move = false); + } + + // Query G29 status + if (abl.verbose_level || seenQ) { + SERIAL_ECHOPGM("Manual G29 "); + if (g29_in_progress) + SERIAL_ECHOLNPGM("point ", _MIN(abl.abl_probe_index + 1, abl.abl_points), " of ", abl.abl_points); + else + SERIAL_ECHOLNPGM("idle"); + } + + // For 'A' or 'Q' exit with success state + if (no_action) G29_RETURN(false, true); + + if (abl.abl_probe_index == 0) { + // For the initial G29 S2 save software endstop state + SET_SOFT_ENDSTOP_LOOSE(true); + // Move close to the bed before the first point + do_blocking_move_to_z(0); + } + else { + + #if ANY(AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_3POINT) + const uint16_t index = abl.abl_probe_index - 1; + #endif + + // For G29 after adjusting Z. + // Save the previous Z before going to the next point + abl.measured_z = current_position.z; + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + + abl.mean += abl.measured_z; + abl.eqnBVector[index] = abl.measured_z; + abl.eqnAMatrix[index + 0 * abl.abl_points] = abl.probePos.x; + abl.eqnAMatrix[index + 1 * abl.abl_points] = abl.probePos.y; + abl.eqnAMatrix[index + 2 * abl.abl_points] = 1; + + incremental_LSF(&lsf_results, abl.probePos, abl.measured_z); + + #elif ENABLED(AUTO_BED_LEVELING_3POINT) + + points[index].z = abl.measured_z; + + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + + #ifdef USE_PROBE_FOR_MESH_REF + const float newz = abl.measured_z + mesh_zero_ref_offset; + #else + const float newz = abl.measured_z + abl.Z_offset; + #endif + abl.z_values[abl.meshCount.x][abl.meshCount.y] = newz; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(abl.meshCount, newz)); + + #ifdef USE_PROBE_FOR_MESH_REF + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM_P(PSTR("Save X"), abl.meshCount.x, SP_Y_STR, abl.meshCount.y, SP_Z_STR, abl.measured_z + mesh_zero_ref_offset); + #else + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM_P(PSTR("Save X"), abl.meshCount.x, SP_Y_STR, abl.meshCount.y, SP_Z_STR, abl.measured_z + abl.Z_offset); + #endif + + #endif + } + + // + // If there's another point to sample, move there with optional lift. + // + + #if ABL_USES_GRID + + // Skip any unreachable points + while (abl.abl_probe_index < abl.abl_points) { + + // Set abl.meshCount.x, abl.meshCount.y based on abl.abl_probe_index, with zig-zag + PR_OUTER_VAR = abl.abl_probe_index / PR_INNER_SIZE; + PR_INNER_VAR = abl.abl_probe_index - (PR_OUTER_VAR * PR_INNER_SIZE); + + // Probe in reverse order for every other row/column + const bool zig = (PR_OUTER_VAR & 1); // != ((PR_OUTER_SIZE) & 1); + if (zig) PR_INNER_VAR = (PR_INNER_SIZE - 1) - PR_INNER_VAR; + + abl.probePos = abl.probe_position_lf + abl.gridSpacing * abl.meshCount.asFloat(); + + TERN_(AUTO_BED_LEVELING_LINEAR, abl.indexIntoAB[abl.meshCount.x][abl.meshCount.y] = abl.abl_probe_index); + + // Keep looping till a reachable point is found + if (position_is_reachable(abl.probePos)) break; + ++abl.abl_probe_index; + } + + // Is there a next point to move to? + if (abl.abl_probe_index < abl.abl_points) { + _manual_goto_xy(abl.probePos); // Can be used here too! + // Disable software endstops to allow manual adjustment + // If G29 is not completed, they will not be re-enabled + SET_SOFT_ENDSTOP_LOOSE(true); + G29_RETURN(false, true); + } + else { + // Leveling done! Fall through to G29 finishing code below + SERIAL_ECHOLNPGM("Grid probing done."); + // Re-enable software endstops, if needed + SET_SOFT_ENDSTOP_LOOSE(false); + } + + #elif ENABLED(AUTO_BED_LEVELING_3POINT) + + // Probe at 3 arbitrary points + if (abl.abl_probe_index < abl.abl_points) { + abl.probePos = xy_pos_t(points[abl.abl_probe_index]); + _manual_goto_xy(abl.probePos); + // Disable software endstops to allow manual adjustment + // If G29 is not completed, they will not be re-enabled + SET_SOFT_ENDSTOP_LOOSE(true); + G29_RETURN(false, true); + } + else { + + SERIAL_ECHOLNPGM("3-point probing done."); + + // Re-enable software endstops, if needed + SET_SOFT_ENDSTOP_LOOSE(false); + + if (!abl.dryrun) { + vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal(); + if (planeNormal.z < 0) planeNormal *= -1; + planner.bed_level_matrix = matrix_3x3::create_look_at(planeNormal); + + // Can't re-enable (on error) until the new grid is written + abl.reenable = false; + } + + } + + #endif // AUTO_BED_LEVELING_3POINT + + #else // !PROBE_MANUALLY + { + const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE; + + abl.measured_z = 0; + + #if ABL_USES_GRID + + bool zig = PR_OUTER_SIZE & 1; // Always end at RIGHT and BACK_PROBE_BED_POSITION + + // Outer loop is X with PROBE_Y_FIRST enabled + // Outer loop is Y with PROBE_Y_FIRST disabled + for (PR_OUTER_VAR = 0; PR_OUTER_VAR < PR_OUTER_SIZE && !isnan(abl.measured_z); PR_OUTER_VAR++) { + + int8_t inStart, inStop, inInc; + + if (zig) { // Zig away from origin + inStart = 0; // Left or front + inStop = PR_INNER_SIZE; // Right or back + inInc = 1; // Zig right + } + else { // Zag towards origin + inStart = PR_INNER_SIZE - 1; // Right or back + inStop = -1; // Left or front + inInc = -1; // Zag left + } + + FLIP(zig); // zag + + // An index to print current state + grid_count_t pt_index = (PR_OUTER_VAR) * (PR_INNER_SIZE) + 1; + + // Inner loop is Y with PROBE_Y_FIRST enabled + // Inner loop is X with PROBE_Y_FIRST disabled + for (PR_INNER_VAR = inStart; PR_INNER_VAR != inStop; pt_index++, PR_INNER_VAR += inInc) { + + abl.probePos = abl.probe_position_lf + abl.gridSpacing * abl.meshCount.asFloat(); + + TERN_(AUTO_BED_LEVELING_LINEAR, abl.indexIntoAB[abl.meshCount.x][abl.meshCount.y] = ++abl.abl_probe_index); // 0... + + // Avoid probing outside the round or hexagonal area + if (TERN0(IS_KINEMATIC, !probe.can_reach(abl.probePos))) continue; + + if (abl.verbose_level) SERIAL_ECHOLNPGM("Probing mesh point ", pt_index, "/", abl.abl_points, "."); + TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/%i"), GET_TEXT_F(MSG_PROBING_POINT), int(pt_index), int(abl.abl_points))); + + #if ENABLED(BD_SENSOR_PROBE_NO_STOP) + if (PR_INNER_VAR == inStart) { + char tmp_1[32]; + + // move to the start point of new line + abl.measured_z = faux ? 0.001f * random(-100, 101) : probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); + // Go to the end of the row/column ... and back up by one + // TODO: Why not just use... PR_INNER_VAR = inStop - inInc + for (PR_INNER_VAR = inStart; PR_INNER_VAR != inStop; PR_INNER_VAR += inInc); + PR_INNER_VAR -= inInc; + + // Get the coordinate of the resulting grid point + abl.probePos = abl.probe_position_lf + abl.gridSpacing * abl.meshCount.asFloat(); + + // Coordinate that puts the probe at the grid point + abl.probePos -= probe.offset_xy; + + // Put a G1 move into the buffer + // TODO: Instead of G1, we can just add the move directly to the planner... + // { + // destination = current_position; destination = abl.probePos; + // REMEMBER(fr, feedrate_mm_s, XY_PROBE_FEEDRATE_MM_S); + // prepare_line_to_destination(); + // } + sprintf_P(tmp_1, PSTR("G1X%d.%d Y%d.%d F%d"), + int(abl.probePos.x), int(abl.probePos.x * 10) % 10, + int(abl.probePos.y), int(abl.probePos.y * 10) % 10, + XY_PROBE_FEEDRATE + ); + gcode.process_subcommands_now(tmp_1); + + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("destX: ", abl.probePos.x, " Y:", abl.probePos.y); + + // Reset the inner counter back to the start + PR_INNER_VAR = inStart; + + // Get the coordinate of the start of the row/column + abl.probePos = abl.probe_position_lf + abl.gridSpacing * abl.meshCount.asFloat(); + } + + // Wait around until the real axis position reaches the comparison point + // TODO: Use NEAR() because float is imprecise + constexpr AxisEnum axis = TERN(PROBE_Y_FIRST, Y_AXIS, X_AXIS); + const float cmp = abl.probePos[axis] - probe.offset_xy[axis]; + float pos; + for (;;) { + pos = planner.get_axis_position_mm(axis); + if (inInc > 0 ? (pos >= cmp) : (pos <= cmp)) break; + idle_no_sleep(); + } + //if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM_P(axis == Y_AXIS ? PSTR("Y=") : PSTR("X=", pos); + + safe_delay(4); + abl.measured_z = current_position.z - bdl.read(); + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("x_cur ", planner.get_axis_position_mm(X_AXIS), " z ", abl.measured_z); + + #else // !BD_SENSOR_PROBE_NO_STOP + + abl.measured_z = faux ? 0.001f * random(-100, 101) : probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); + + #endif + + if (isnan(abl.measured_z)) { + set_bed_leveling_enabled(abl.reenable); + break; // Breaks out of both loops + } + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + + abl.mean += abl.measured_z; + abl.eqnBVector[abl.abl_probe_index] = abl.measured_z; + abl.eqnAMatrix[abl.abl_probe_index + 0 * abl.abl_points] = abl.probePos.x; + abl.eqnAMatrix[abl.abl_probe_index + 1 * abl.abl_points] = abl.probePos.y; + abl.eqnAMatrix[abl.abl_probe_index + 2 * abl.abl_points] = 1; + + incremental_LSF(&lsf_results, abl.probePos, abl.measured_z); + + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + + #ifdef USE_PROBE_FOR_MESH_REF + const float z = abl.measured_z + mesh_zero_ref_offset; + #else + const float z = abl.measured_z + abl.Z_offset; + #endif + + abl.z_values[abl.meshCount.x][abl.meshCount.y] = z; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(abl.meshCount, z)); + + #if ENABLED(SOVOL_SV06_RTS) + if (pt_index <= GRID_MAX_POINTS) rts.sendData(pt_index, AUTO_BED_LEVEL_ICON_VP); + rts.sendData(z * 100.0f, AUTO_BED_LEVEL_1POINT_VP + (pt_index - 1) * 2); + rts.gotoPage(ID_ABL_Wait_L, ID_ABL_Wait_D); + #endif + + #endif + + abl.reenable = false; // Don't re-enable after modifying the mesh + idle_no_sleep(); + + } // inner + } // outer + + #elif ENABLED(AUTO_BED_LEVELING_3POINT) + + // Probe at 3 arbitrary points + + for (uint8_t i = 0; i < 3; ++i) { + if (abl.verbose_level) SERIAL_ECHOLNPGM("Probing point ", i + 1, "/3."); + TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/3"), GET_TEXT_F(MSG_PROBING_POINT), int(i + 1))); + + // Retain the last probe position + abl.probePos = xy_pos_t(points[i]); + abl.measured_z = faux ? 0.001 * random(-100, 101) : probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); + if (isnan(abl.measured_z)) { + set_bed_leveling_enabled(abl.reenable); + break; + } + points[i].z = abl.measured_z; + } + + if (!abl.dryrun && !isnan(abl.measured_z)) { + vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal(); + if (planeNormal.z < 0) planeNormal *= -1; + planner.bed_level_matrix = matrix_3x3::create_look_at(planeNormal); + + // Can't re-enable (on error) until the new grid is written + abl.reenable = false; + } + + #endif // AUTO_BED_LEVELING_3POINT + + ui.reset_status(); + + // Stow the probe. No raise for FIX_MOUNTED_PROBE. + if (probe.stow()) { + set_bed_leveling_enabled(abl.reenable); + abl.measured_z = NAN; + } + } + #endif // !PROBE_MANUALLY + + // + // G29 Finishing Code + // + // Unless this is a dry run, auto bed leveling will + // definitely be enabled after this point. + // + // If code above wants to continue leveling, it should + // return or loop before this point. + // + + if (DEBUGGING(LEVELING)) DEBUG_POS("> probing complete", current_position); + + #if ENABLED(PROBE_MANUALLY) + g29_in_progress = false; + TERN_(LCD_BED_LEVELING, ui.wait_for_move = false); + #endif + + // Calculate leveling, print reports, correct the position + if (!isnan(abl.measured_z)) { + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + + if (abl.dryrun) + bedlevel.print_leveling_grid(&abl.z_values); + else { + bedlevel.set_grid(abl.gridSpacing, abl.probe_position_lf); + COPY(bedlevel.z_values, abl.z_values); + TERN_(IS_KINEMATIC, bedlevel.extrapolate_unprobed_bed_level()); + bedlevel.refresh_bed_level(); + + bedlevel.print_leveling_grid(); + } + + #elif ENABLED(AUTO_BED_LEVELING_LINEAR) + + // For LINEAR leveling calculate matrix, print reports, correct the position + + /** + * solve the plane equation ax + by + d = z + * A is the matrix with rows [x y 1] for all the probed points + * B is the vector of the Z positions + * the normal vector to the plane is formed by the coefficients of the + * plane equation in the standard form, which is Vx*x+Vy*y+Vz*z+d = 0 + * so Vx = -a Vy = -b Vz = 1 (we want the vector facing towards positive Z + */ + struct { float a, b, d; } plane_equation_coefficients; + + finish_incremental_LSF(&lsf_results); + plane_equation_coefficients.a = -lsf_results.A; // We should be able to eliminate the '-' on these three lines and down below + plane_equation_coefficients.b = -lsf_results.B; // but that is not yet tested. + plane_equation_coefficients.d = -lsf_results.D; + + abl.mean /= abl.abl_points; + + if (abl.verbose_level) { + SERIAL_ECHOPGM("Eqn coefficients: a: ", p_float_t(plane_equation_coefficients.a, 8), + " b: ", p_float_t(plane_equation_coefficients.b, 8), + " d: ", p_float_t(plane_equation_coefficients.d, 8)); + if (abl.verbose_level > 2) + SERIAL_ECHOPGM("\nMean of sampled points: ", p_float_t(abl.mean, 8)); + SERIAL_EOL(); + } + + // Create the matrix but don't correct the position yet + if (!abl.dryrun) + planner.bed_level_matrix = matrix_3x3::create_look_at( + vector_3(-plane_equation_coefficients.a, -plane_equation_coefficients.b, 1) // We can eliminate the '-' here and up above + ); + + // Show the Topography map if enabled + if (abl.topography_map) { + + float min_diff = 999; + + auto print_topo_map = [&](FSTR_P const title, const bool get_min) { + SERIAL_ECHO(title); + for (int8_t yy = abl.grid_points.y - 1; yy >= 0; yy--) { + for (uint8_t xx = 0; xx < abl.grid_points.x; ++xx) { + const int ind = abl.indexIntoAB[xx][yy]; + xyz_float_t tmp = { abl.eqnAMatrix[ind + 0 * abl.abl_points], + abl.eqnAMatrix[ind + 1 * abl.abl_points], 0 }; + planner.bed_level_matrix.apply_rotation_xyz(tmp.x, tmp.y, tmp.z); + if (get_min) NOMORE(min_diff, abl.eqnBVector[ind] - tmp.z); + const float subval = get_min ? abl.mean : tmp.z + min_diff, + diff = abl.eqnBVector[ind] - subval; + SERIAL_CHAR(' '); if (diff >= 0.0) SERIAL_CHAR('+'); // Include + for column alignment + SERIAL_ECHO(p_float_t(diff, 5)); + } // xx + SERIAL_EOL(); + } // yy + SERIAL_EOL(); + }; + + print_topo_map(F("\nBed Height Topography:\n" + " +--- BACK --+\n" + " | |\n" + " L | (+) | R\n" + " E | | I\n" + " F | (-) N (+) | G\n" + " T | | H\n" + " | (-) | T\n" + " | |\n" + " O-- FRONT --+\n" + " (0,0)\n"), true); + if (abl.verbose_level > 3) + print_topo_map(F("\nCorrected Bed Height vs. Bed Topology:\n"), false); + + } // abl.topography_map + + #endif // AUTO_BED_LEVELING_LINEAR + + #if ABL_PLANAR + + // For LINEAR and 3POINT leveling correct the current position + + if (abl.verbose_level > 0) + planner.bed_level_matrix.debug(F("\n\nBed Level Correction Matrix:")); + + if (!abl.dryrun) { + // + // Correct the current XYZ position based on the tilted plane. + // + + if (DEBUGGING(LEVELING)) DEBUG_POS("G29 uncorrected XYZ", current_position); + + xyze_pos_t converted = current_position; + planner.force_unapply_leveling(converted); // use conversion machinery + + // Use the last measured distance to the bed, if possible + if ( NEAR(current_position.x, abl.probePos.x - probe.offset_xy.x) + && NEAR(current_position.y, abl.probePos.y - probe.offset_xy.y) + ) { + const float simple_z = current_position.z - abl.measured_z; + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Probed Z", simple_z, " Matrix Z", converted.z, " Discrepancy ", simple_z - converted.z); + converted.z = simple_z; + } + + // The rotated XY and corrected Z are now current_position + current_position = converted; + + if (DEBUGGING(LEVELING)) DEBUG_POS("G29 corrected XYZ", current_position); + + abl.reenable = true; + } + + // Auto Bed Leveling is complete! Enable if possible. + if (abl.reenable) { + planner.leveling_active = true; + sync_plan_position(); + } + + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + + // Auto Bed Leveling is complete! Enable if possible. + if (!abl.dryrun || abl.reenable) set_bed_leveling_enabled(true); + + #endif + + } // !isnan(abl.measured_z) + + // Restore state after probing + if (!faux) restore_feedrate_and_scaling(); + + TERN_(HAS_BED_PROBE, probe.move_z_after_probing()); + + #ifdef EVENT_GCODE_AFTER_G29 + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("After G29 G-code: ", EVENT_GCODE_AFTER_G29); + planner.synchronize(); + process_subcommands_now(F(EVENT_GCODE_AFTER_G29)); + #endif + + TERN_(SOVOL_SV06_RTS, RTS_AutoBedLevelPage()); + + probe.use_probing_tool(false); + + report_current_position(); + + G29_RETURN(isnan(abl.measured_z), true); +} + +#endif // HAS_ABL_NOT_UBL diff --git a/Marlin/Modified files/Modified files.zip b/Marlin/Modified files/Modified files.zip new file mode 100644 index 0000000000000000000000000000000000000000..b8e75f711dedc1119bf92335e0f4e8dad6d91746 GIT binary patch literal 93207 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 H&jJ1itzVrg literal 0 HcmV?d00001 diff --git a/Marlin/Modified files/gcode.h b/Marlin/Modified files/gcode.h new file mode 100644 index 0000000000..c4c81b2647 --- /dev/null +++ b/Marlin/Modified files/gcode.h @@ -0,0 +1,1347 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +#pragma once + +/** + * ----------------- + * G-Codes in Marlin + * ----------------- + * + * Helpful G-code references: + * - https://marlinfw.org/meta/gcode + * - https://reprap.org/wiki/G-code + * - https://linuxcnc.org/docs/html/gcode.html + * + * Help to document Marlin's G-codes online: + * - https://github.com/MarlinFirmware/MarlinDocumentation + * + * ----------------- + * + * "G" Codes + * + * G0 -> G1 + * G1 - Coordinated Movement X Y Z E + * G2 - CW ARC + * G3 - CCW ARC + * G4 - Dwell S or P + * G5 - Cubic B-spline with XYZE destination and IJPQ offsets + * G10 - Retract filament according to settings of M207 (Requires FWRETRACT) + * G11 - Retract recover filament according to settings of M208 (Requires FWRETRACT) + * G12 - Clean tool (Requires NOZZLE_CLEAN_FEATURE) + * G17 - Select Plane XY (Requires CNC_WORKSPACE_PLANES) + * G18 - Select Plane ZX (Requires CNC_WORKSPACE_PLANES) + * G19 - Select Plane YZ (Requires CNC_WORKSPACE_PLANES) + * G20 - Set input units to inches (Requires INCH_MODE_SUPPORT) + * G21 - Set input units to millimeters (Requires INCH_MODE_SUPPORT) + * G26 - Mesh Validation Pattern (Requires G26_MESH_VALIDATION) + * G27 - Park Nozzle (Requires NOZZLE_PARK_FEATURE) + * G28 - Home one or more axes + * G29 - Start or continue the bed leveling probe procedure (Requires bed leveling) + * G30 - Single Z probe, probes bed at X Y location (defaults to current XY location) + * G31 - Dock sled (Z_PROBE_SLED only) + * G32 - Undock sled (Z_PROBE_SLED only) + * G33 - Delta Auto-Calibration (Requires DELTA_AUTO_CALIBRATION) + * G34 - Z Stepper automatic alignment using probe: I T A (Requires Z_STEPPER_AUTO_ALIGN) + * G35 - Read bed corners to help adjust bed screws: T (Requires ASSISTED_TRAMMING) + * G38 - Probe in any direction using the Z_MIN_PROBE (Requires G38_PROBE_TARGET) + * G42 - Coordinated move to a mesh point (Requires MESH_BED_LEVELING, AUTO_BED_LEVELING_BLINEAR, or AUTO_BED_LEVELING_UBL) + * G60 - Save current position. (Requires SAVED_POSITIONS) + * G61 - Apply/restore saved coordinates. (Requires SAVED_POSITIONS) + * G76 - Calibrate first layer temperature offsets. (Requires PTC_PROBE and PTC_BED) + * G80 - Cancel current motion mode (Requires GCODE_MOTION_MODES) + * G90 - Use Absolute Coordinates + * G91 - Use Relative Coordinates + * G92 - Set current position to coordinates given + * + * "M" Codes + * + * M0 - Unconditional stop - Wait for user to press a button on the LCD (Only if ULTRA_LCD is enabled) + * M1 -> M0 + * M3 - Turn ON Laser | Spindle (clockwise), set Power | Speed. (Requires SPINDLE_FEATURE or LASER_FEATURE) + * M4 - Turn ON Laser | Spindle (counter-clockwise), set Power | Speed. (Requires SPINDLE_FEATURE or LASER_FEATURE) + * M5 - Turn OFF Laser | Spindle. (Requires SPINDLE_FEATURE or LASER_FEATURE) + * M7 - Turn mist coolant ON. (Requires COOLANT_CONTROL) + * M8 - Turn flood coolant ON. (Requires COOLANT_CONTROL) + * M9 - Turn coolant OFF. (Requires COOLANT_CONTROL) + * M10 - Turn Vacuum or Blower motor ON (Requires AIR_EVACUATION) + * M11 - Turn Vacuum or Blower motor OFF (Requires AIR_EVACUATION) + * M12 - Set up closed loop control system. (Requires EXTERNAL_CLOSED_LOOP_CONTROLLER) + * M16 - Expected printer check. (Requires EXPECTED_PRINTER_CHECK) + * M17 - Enable/Power all stepper motors + * M18 - Disable all stepper motors; same as M84 + * + *** Print from Media (SDSUPPORT) *** + * M20 - List SD card. (Requires SDSUPPORT) + * M21 - Init SD card. (Requires SDSUPPORT) With MULTI_VOLUME select a drive with `M21 Pn` / 'M21 S' / 'M21 U'. + * M22 - Release SD card. (Requires SDSUPPORT) + * M23 - Select SD file: "M23 /path/file.gco". (Requires SDSUPPORT) + * M24 - Start/resume SD print. (Requires SDSUPPORT) + * M25 - Pause SD print. (Requires SDSUPPORT) + * M26 - Set SD position in bytes: "M26 S12345". (Requires SDSUPPORT) + * M27 - Report SD print status. (Requires SDSUPPORT) + * OR, with 'S' set the SD status auto-report interval. (Requires AUTO_REPORT_SD_STATUS) + * OR, with 'C' get the current filename. + * M28 - Start SD write: "M28 /path/file.gco". (Requires SDSUPPORT) + * M29 - Stop SD write. (Requires SDSUPPORT) + * M30 - Delete file from SD: "M30 /path/file.gco" (Requires SDSUPPORT) + * M31 - Report time since last M109 or SD card start to serial. + * M32 - Select file and start SD print: "M32 [S] !/path/file.gco#". (Requires SDSUPPORT) + * Use P to run other files as sub-programs: "M32 P !filename#" + * The '#' is necessary when calling from within sd files, as it stops buffer prereading + * M33 - Get the longname version of a path. (Requires LONG_FILENAME_HOST_SUPPORT) + * M34 - Set SD Card sorting options. (Requires SDCARD_SORT_ALPHA) + * + * M42 - Change pin status via G-code: M42 P S. LED pin assumed if P is omitted. (Requires DIRECT_PIN_CONTROL) + * M43 - Display pin status, watch pins for changes, watch endstops & toggle LED, Z servo probe test, toggle pins (Requires PINS_DEBUGGING) + * M48 - Measure Z Probe repeatability: M48 P X Y V E L S. (Requires Z_MIN_PROBE_REPEATABILITY_TEST) + * + * M73 - Set the progress percentage. (Requires SET_PROGRESS_MANUALLY) + * M75 - Start the print job timer. + * M76 - Pause the print job timer. + * M77 - Stop the print job timer. + * M78 - Show statistical information about the print jobs. (Requires PRINTCOUNTER) + * + * M80 - Turn on Power Supply. (Requires PSU_CONTROL) + * M81 - Turn off Power Supply. (Requires PSU_CONTROL) + * + * M82 - Set E codes absolute (default). + * M83 - Set E codes relative while in Absolute (G90) mode. + * M84 - Disable steppers until next move, or use S to specify an idle + * duration after which steppers should turn off. S0 disables the timeout. + * M85 - Set inactivity shutdown timer with parameter S. To disable set zero (default) + * M86 - Set / Report Hotend Idle Timeout. (Requires HOTEND_IDLE_TIMEOUT) + * M87 - Cancel Hotend Idle Timeout (by setting the timeout period to 0). (Requires HOTEND_IDLE_TIMEOUT) + * M92 - Set planner.settings.axis_steps_per_mm for one or more axes. (Requires EDITABLE_STEPS_PER_UNIT) + * + * M100 - Watch Free Memory (for debugging) (Requires M100_FREE_MEMORY_WATCHER) + * + * M102 - Configure Bed Distance Sensor. (Requires BD_SENSOR) + * + * M104 - Set extruder target temp. + * M105 - Report current temperatures. + * M106 - Set print fan speed. + * M107 - Print fan off. + * M108 - Break out of heating loops (M109, M190, M303). With no controller, breaks out of M0/M1. (Requires EMERGENCY_PARSER) + * M109 - S Wait for extruder current temp to reach target temp. ** Wait only when heating! ** + * R Wait for extruder current temp to reach target temp. ** Wait for heating or cooling. ** + * If AUTOTEMP is enabled, S B F. Exit autotemp by any M109 without F + * + * M110 - Get or set the current line number. (Used by host printing) + * M111 - Set debug flags: "M111 S". See flag bits defined in enum.h. + * M112 - Full Shutdown. + * + * M113 - Get or set the timeout interval for Host Keepalive "busy" messages. (Requires HOST_KEEPALIVE_FEATURE) + * M114 - Report current position. + * M115 - Report capabilities. (Requires CAPABILITIES_REPORT) + * M117 - Display a message on the controller screen. (Requires an LCD) + * M118 - Display a message in the host console. + * + * M119 - Report endstops status. + * M120 - Enable endstops detection. + * M121 - Disable endstops detection. + * + * M122 - Debug stepper (Requires at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660) + * M123 - Report fan tachometers. (Requires En_FAN_TACHO_PIN) Optionally set auto-report interval. (Requires AUTO_REPORT_FANS) + * M125 - Save current position and move to filament change position. (Requires PARK_HEAD_ON_PAUSE) + * + * M126 - Solenoid Air Valve Open. (Requires BARICUDA) + * M127 - Solenoid Air Valve Closed. (Requires BARICUDA) + * M128 - EtoP Open. (Requires BARICUDA) + * M129 - EtoP Closed. (Requires BARICUDA) + * + * M140 - Set bed target temp. S + * M141 - Set heated chamber target temp. S (Requires a chamber heater) + * M143 - Set cooler target temp. S (Requires a laser cooling device) + * M145 - Set heatup values for materials on the LCD. H B F for S (0=PLA, 1=ABS) + * M149 - Set temperature units. (Requires TEMPERATURE_UNITS_SUPPORT) + * M150 - Set Status LED Color as R U B W P. Values 0-255. (Requires BLINKM, RGB_LED, RGBW_LED, NEOPIXEL_LED, PCA9533, or PCA9632). + * M154 - Auto-report position with interval of S. (Requires AUTO_REPORT_POSITION) + * M155 - Auto-report temperatures with interval of S. (Requires AUTO_REPORT_TEMPERATURES) + * M163 - Set a single proportion for a mixing extruder. (Requires MIXING_EXTRUDER) + * M164 - Commit the mix and save to a virtual tool (current, or as specified by 'S'). (Requires MIXING_EXTRUDER) + * M165 - Set the mix for the mixing extruder (and current virtual tool) with parameters ABCDHI. (Requires MIXING_EXTRUDER and DIRECT_MIXING_IN_G1) + * M166 - Set the Gradient Mix for the mixing extruder. (Requires GRADIENT_MIX) + * M190 - Set bed target temperature and wait. R Set target temperature and wait. S Set, but only wait when heating. (Requires TEMP_SENSOR_BED) + * M192 - Wait for probe to reach target temperature. (Requires TEMP_SENSOR_PROBE) + * M193 - R Wait for cooler to reach target temp. ** Wait for cooling. ** + * M200 - Set filament diameter, D, setting E axis units to cubic. (Use S0 to revert to linear units.) + * M201 - Set max acceleration in units/s^2 for print moves: "M201 X Y Z E" + * M202 - Set max acceleration in units/s^2 for travel moves: "M202 X Y Z E" ** UNUSED IN MARLIN! ** + * M203 - Set maximum feedrate: "M203 X Y Z E" in units/sec. + * M204 - Set default acceleration in units/sec^2: P R T + * M205 - Set advanced settings. Current units apply: + S T minimum speeds + B + X, Y, Z, E + * M206 - Set additional homing offset. (Disabled by NO_WORKSPACE_OFFSETS or DELTA) + * M207 - Set Retract Length: S, Feedrate: F, and Z lift: Z. (Requires FWRETRACT) + * M208 - Set Recover (unretract) Additional (!) Length: S and Feedrate: F. (Requires FWRETRACT) + * M209 - Turn Automatic Retract Detection on/off: S<0|1> (For slicers that don't support G10/11). (Requires FWRETRACT_AUTORETRACT) + Every normal extrude-only move will be classified as retract depending on the direction. + * M210 - Set or Report the homing feedrate (Requires EDITABLE_HOMING_FEEDRATE) + * M211 - Enable, Disable, and/or Report software endstops: S<0|1> (Requires MIN_SOFTWARE_ENDSTOPS or MAX_SOFTWARE_ENDSTOPS) + * M217 - Set filament swap parameters: "M217 S P R". (Requires SINGLENOZZLE) + * M218 - Set/get a tool offset: "M218 T X Y". (Requires 2 or more extruders) + * M220 - Set Feedrate Percentage: "M220 S" (i.e., "FR" on the LCD) + * Use "M220 B" to back up the Feedrate Percentage and "M220 R" to restore it. (Requires an MMU_MODEL version 2 or 2S) + * M221 - Set Flow Percentage: "M221 S" (Requires an extruder) + * M226 - Wait until a pin is in a given state: "M226 P S" (Requires DIRECT_PIN_CONTROL) + * M240 - Trigger a camera to take a photograph. (Requires PHOTO_GCODE) + * M250 - Set LCD contrast: "M250 C" (0-63). (Requires LCD support) + * M255 - Set LCD sleep time: "M255 S" (0-99). (Requires an LCD with brightness or sleep/wake) + * M256 - Set LCD brightness: "M256 B" (0-255). (Requires an LCD with brightness control) + * M260 - i2c Send Data (Requires EXPERIMENTAL_I2CBUS) + * M261 - i2c Request Data (Requires EXPERIMENTAL_I2CBUS) + * M280 - Set servo position absolute: "M280 P S". (Requires servos) + * M281 - Set servo min|max position: "M281 P L U". (Requires EDITABLE_SERVO_ANGLES) + * M282 - Detach servo: "M282 P". (Requires SERVO_DETACH_GCODE) + * M290 - Babystepping (Requires BABYSTEPPING) + * M293 - Babystep Z UP (Requires EP_BABYSTEPPING) + * M294 - Babystep Z DOWN (Requires EP_BABYSTEPPING) + * M300 - Play beep sound S P + * M301 - Set PID parameters P I and D. (Requires PIDTEMP) + * M302 - Allow cold extrudes, or set the minimum extrude S. (Requires PREVENT_COLD_EXTRUSION) + * M303 - PID relay autotune S sets the target temperature. Default 150C. (Requires PIDTEMP) + * M304 - Set bed PID parameters P I and D. (Requires PIDTEMPBED) + * M305 - Set user thermistor parameters R T and P. (Requires TEMP_SENSOR_x 1000) + * M306 - MPC autotune. (Requires MPCTEMP) + * M309 - Set chamber PID parameters P I and D. (Requires PIDTEMPCHAMBER) + * M350 - Set microstepping mode. (Requires digital microstepping pins.) + * M351 - Toggle MS1 MS2 pins directly. (Requires digital microstepping pins.) + * M355 - Set Case Light on/off and set brightness. (Requires CASE_LIGHT_PIN) + * M380 - Activate solenoid on active tool (Requires EXT_SOLENOID) or the tool specified by 'S' (Requires MANUAL_SOLENOID_CONTROL). + * M381 - Disable solenoids on all tools (Requires EXT_SOLENOID) or the tool specified by 'S' (Requires MANUAL_SOLENOID_CONTROL). + * M400 - Finish all moves. + * M401 - Deploy and activate Z probe. (Requires a probe) + * M402 - Deactivate and stow Z probe. (Requires a probe) + * M403 - Set filament type for PRUSA MMU2 + * M404 - Display or set the Nominal Filament Width: "W". (Requires FILAMENT_WIDTH_SENSOR) + * M405 - Enable Filament Sensor flow control. "M405 D". (Requires FILAMENT_WIDTH_SENSOR) + * M406 - Disable Filament Sensor flow control. (Requires FILAMENT_WIDTH_SENSOR) + * M407 - Display measured filament diameter in millimeters. (Requires FILAMENT_WIDTH_SENSOR) + * M410 - Quickstop. Abort all planned moves. + * M412 - Enable / Disable Filament Runout Detection. (Requires FILAMENT_RUNOUT_SENSOR) + * M413 - Enable / Disable Power-Loss Recovery. (Requires POWER_LOSS_RECOVERY) + * M414 - Set language by index. (Requires LCD_LANGUAGE_2...) + * M420 - Enable/Disable Leveling (with current values) S1=enable S0=disable (Requires MESH_BED_LEVELING or ABL) + * M421 - Set a single Z coordinate in the Mesh Leveling grid. X Y Z (Requires MESH_BED_LEVELING, AUTO_BED_LEVELING_BILINEAR, or AUTO_BED_LEVELING_UBL) + * M422 - Set Z Stepper automatic alignment position using probe. X Y A (Requires Z_STEPPER_AUTO_ALIGN) + * M425 - Enable/Disable and tune backlash correction. (Requires BACKLASH_COMPENSATION and BACKLASH_GCODE) + * M428 - Set the home_offset based on the current_position. Nearest edge applies. (Disabled by NO_WORKSPACE_OFFSETS or DELTA) + * M430 - Read the system current, voltage, and power (Requires POWER_MONITOR_CURRENT, POWER_MONITOR_VOLTAGE, or POWER_MONITOR_FIXED_VOLTAGE) + * M485 - Send RS485 packets (Requires RS485_SERIAL_PORT) + * M486 - Identify and cancel objects. (Requires CANCEL_OBJECTS) + * M493 - Get or set input FT Motion / Shaping parameters. (Requires FT_MOTION) + * M500 - Store parameters in EEPROM. (Requires EEPROM_SETTINGS) + * M501 - Restore parameters from EEPROM. (Requires EEPROM_SETTINGS) + * M502 - Revert to the default "factory settings". ** Does not write them to EEPROM! ** + * M503 - Print the current settings (in memory): "M503 S". S0 specifies compact output. + * M504 - Validate EEPROM contents. (Requires EEPROM_SETTINGS) + * M510 - Lock Printer (Requires PASSWORD_FEATURE) + * M511 - Unlock Printer (Requires PASSWORD_UNLOCK_GCODE) + * M512 - Set/Change/Remove Password (Requires PASSWORD_CHANGE_GCODE) + * M524 - Abort the current SD print job started with M24. (Requires SDSUPPORT) + * M540 - Enable/disable SD card abort on endstop hit: "M540 S". (Requires SD_ABORT_ON_ENDSTOP_HIT) + * M552 - Get or set IP address. Enable/disable network interface. (Requires enabled Ethernet port) + * M553 - Get or set IP netmask. (Requires enabled Ethernet port) + * M554 - Get or set IP gateway. (Requires enabled Ethernet port) + * M569 - Enable stealthChop on an axis. (Requires at least one _DRIVER_TYPE to be TMC2130/2160/2208/2209/5130/5160) + * M575 - Change the serial baud rate. (Requires BAUD_RATE_GCODE) + * M592 - Get or set Nonlinear Extrusion parameters. (Requires NONLINEAR_EXTRUSION) + * M593 - Get or set input shaping parameters. (Requires INPUT_SHAPING_[XY]) + * M600 - Pause for filament change: "M600 X Y Z E L". (Requires ADVANCED_PAUSE_FEATURE) + * M603 - Configure filament change: "M603 T U L". (Requires ADVANCED_PAUSE_FEATURE) + * M605 - Set Dual X-Carriage movement mode: "M605 S [X] [R]". (Requires DUAL_X_CARRIAGE) + * M665 - Set delta configurations: "M665 H L R S B X Y Z (Requires DELTA) + * Set SCARA configurations: "M665 S P T Z (Requires MORGAN_SCARA or MP_SCARA) + * Set Polargraph draw area and belt length: "M665 S L R T B H" + * M666 - Set/get offsets for delta (Requires DELTA) or dual endstops. (Requires [XYZ]_DUAL_ENDSTOPS) + * M672 - Set/Reset Duet Smart Effector's sensitivity. (Requires DUET_SMART_EFFECTOR and SMART_EFFECTOR_MOD_PIN) + * M701 - Load filament (Requires FILAMENT_LOAD_UNLOAD_GCODES) + * M702 - Unload filament (Requires FILAMENT_LOAD_UNLOAD_GCODES) + * + *** PRUSA_MMU3 *** + * M704 - Preload to MMU + * M705 - Eject filament + * M706 - Cut filament + * M707 - Read from MMU register + * M708 - Write to MMU register + * M709 - MMU power & reset + * + * M808 - Set or Goto a Repeat Marker (Requires GCODE_REPEAT_MARKERS) + * M810-M819 - Define/execute a G-code macro (Requires GCODE_MACROS) + * M820 - Report all defined M810-M819 G-code macros (Requires GCODE_MACROS) + * M851 - Set Z probe's XYZ offsets in current units. (Negative values: X=left, Y=front, Z=below) + * M852 - Set skew factors: "M852 [I] [J] [K]". (Requires SKEW_CORRECTION_GCODE, plus SKEW_CORRECTION_FOR_Z for IJ) + * + *** I2C_POSITION_ENCODERS *** + * M860 - Report the position of position encoder modules. + * M861 - Report the status of position encoder modules. + * M862 - Perform an axis continuity test for position encoder modules. + * M863 - Perform steps-per-mm calibration for position encoder modules. + * M864 - Change position encoder module I2C address. + * M865 - Check position encoder module firmware version. + * M866 - Report or reset position encoder module error count. + * M867 - Enable/disable or toggle error correction for position encoder modules. + * M868 - Report or set position encoder module error correction threshold. + * M869 - Report position encoder module error. + * + * M871 - Print/reset/clear first layer temperature offset values. (Requires PTC_PROBE, PTC_BED, or PTC_HOTEND) + * M876 - Handle Prompt Response. (Requires HOST_PROMPT_SUPPORT and not EMERGENCY_PARSER) + * M900 - Get or Set Linear Advance K-factor. (Requires LIN_ADVANCE) + * M906 - Set or get motor current in milliamps using axis codes XYZE, etc. Report values if no axis codes given. (Requires at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660) + * M907 - Set digital trimpot motor current using axis codes. (Requires a board with digital trimpots) + * M908 - Control digital trimpot directly. (Requires HAS_MOTOR_CURRENT_DAC or DIGIPOTSS_PIN) + * M909 - Print digipot/DAC current value. (Requires HAS_MOTOR_CURRENT_DAC) + * M910 - Commit digipot/DAC value to external EEPROM via I2C. (Requires HAS_MOTOR_CURRENT_DAC) + * M911 - Report stepper driver overtemperature pre-warn condition. (Requires at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660) + * M912 - Clear stepper driver overtemperature pre-warn condition flag. (Requires at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660) + * M913 - Set HYBRID_THRESHOLD speed. (Requires HYBRID_THRESHOLD) + * M914 - Set StallGuard sensitivity. (Requires SENSORLESS_HOMING or SENSORLESS_PROBING) + * M919 - Get or Set motor Chopper Times (time_off, hysteresis_end, hysteresis_start) using axis codes XYZE, etc. If no parameters are given, report. (Requires at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660) + * M936 - OTA update firmware. (Requires OTA_FIRMWARE_UPDATE) + * M951 - Set Magnetic Parking Extruder parameters. (Requires MAGNETIC_PARKING_EXTRUDER) + * M3426 - Read MCP3426 ADC over I2C. (Requires HAS_MCP3426_ADC) + * M7219 - Control Max7219 Matrix LEDs. (Requires MAX7219_GCODE) + * + *** SCARA *** + * M360 - SCARA calibration: Move to cal-position ThetaA (0 deg calibration) + * M361 - SCARA calibration: Move to cal-position ThetaB (90 deg calibration - steps per degree) + * M362 - SCARA calibration: Move to cal-position PsiA (0 deg calibration) + * M363 - SCARA calibration: Move to cal-position PsiB (90 deg calibration - steps per degree) + * M364 - SCARA calibration: Move to cal-position PSIC (90 deg to Theta calibration position) + * + *** Custom codes (can be changed to suit future G-code standards) *** + * G425 - Calibrate using a conductive object. (Requires CALIBRATION_GCODE) + * M928 - Start SD logging: "M928 filename.gco". Stop with M29. (Requires SDSUPPORT) + * M993 - Backup SPI Flash to SD + * M994 - Load a Backup from SD to SPI Flash + * M995 - Touch screen calibration for TFT display + * M997 - Perform in-application firmware update + * M999 - Restart after being stopped by error + * + * D... - Custom Development G-code. Add hooks to 'gcode_D.cpp' for developers to test features. (Requires MARLIN_DEV_MODE) + * D576 - Set buffer monitoring options. (Requires BUFFER_MONITORING) + * + *** "T" Codes *** + * + * T0-T3 - Select an extruder (tool) by index: "T F" + */ + +#include "../inc/MarlinConfig.h" +#include "parser.h" + +#if ENABLED(I2C_POSITION_ENCODERS) + #include "../feature/encoder_i2c.h" +#endif + +#if ANY(IS_SCARA, POLAR) || defined(G0_FEEDRATE) + #define HAS_FAST_MOVES 1 +#endif + +#if ENABLED(MARLIN_SMALL_BUILD) + #define GCODE_ERR_MSG(V...) "?" +#else + #define GCODE_ERR_MSG(V...) "?" V +#endif + +enum AxisRelative : uint8_t { + LOGICAL_AXIS_LIST(REL_E, REL_X, REL_Y, REL_Z, REL_I, REL_J, REL_K, REL_U, REL_V, REL_W) + #if HAS_EXTRUDERS + , E_MODE_ABS, E_MODE_REL + #endif + , NUM_REL_MODES +}; +typedef bits_t(NUM_REL_MODES) relative_t; + +extern const char G28_STR[]; + +#ifdef USE_PROBE_FOR_MESH_REF + extern float mesh_zero_ref_offset; +#endif + +class GcodeSuite { +public: + + static relative_t axis_relative; + + GcodeSuite() { // Relative motion mode for each logical axis + axis_relative = AxisBits(AXIS_RELATIVE_MODES).bits; + } + + static bool axis_is_relative(const AxisEnum a) { + #if HAS_EXTRUDERS + if (a == E_AXIS) { + if (TEST(axis_relative, E_MODE_REL)) return true; + if (TEST(axis_relative, E_MODE_ABS)) return false; + } + #endif + return TEST(axis_relative, a); + } + static void set_relative_mode(const bool rel) { + axis_relative = rel ? (0 LOGICAL_AXIS_GANG( + | _BV(REL_E), + | _BV(REL_X), | _BV(REL_Y), | _BV(REL_Z), + | _BV(REL_I), | _BV(REL_J), | _BV(REL_K), + | _BV(REL_U), | _BV(REL_V), | _BV(REL_W) + )) : 0; + } + #if HAS_EXTRUDERS + static void set_e_relative() { + CBI(axis_relative, E_MODE_ABS); + SBI(axis_relative, E_MODE_REL); + } + static void set_e_absolute() { + CBI(axis_relative, E_MODE_REL); + SBI(axis_relative, E_MODE_ABS); + } + #endif + + #if ENABLED(CNC_WORKSPACE_PLANES) + /** + * Workspace planes only apply to G2/G3 moves + * (and "canned cycles" - not a current feature) + */ + enum WorkspacePlane : char { PLANE_XY, PLANE_ZX, PLANE_YZ }; + static WorkspacePlane workspace_plane; + #endif + + #define MAX_COORDINATE_SYSTEMS 9 + #if ENABLED(CNC_COORDINATE_SYSTEMS) + static int8_t active_coordinate_system; + static xyz_pos_t coordinate_system[MAX_COORDINATE_SYSTEMS]; + static bool select_coordinate_system(const int8_t _new); + #endif + + static millis_t previous_move_ms, max_inactive_time; + FORCE_INLINE static bool stepper_max_timed_out(const millis_t ms=millis()) { + return max_inactive_time && ELAPSED(ms, previous_move_ms + max_inactive_time); + } + FORCE_INLINE static void reset_stepper_timeout(const millis_t ms=millis()) { previous_move_ms = ms; } + + #if HAS_DISABLE_IDLE_AXES + static millis_t stepper_inactive_time; + FORCE_INLINE static bool stepper_inactive_timeout(const millis_t ms=millis()) { + return ELAPSED(ms, previous_move_ms + stepper_inactive_time); + } + #else + static bool stepper_inactive_timeout(const millis_t) { return false; } + #endif + + static void report_echo_start(const bool forReplay); + static void report_heading(const bool forReplay, FSTR_P const fstr, const bool eol=true); + static void report_heading_etc(const bool forReplay, FSTR_P const fstr, const bool eol=true) { + report_heading(forReplay, fstr, eol); + report_echo_start(forReplay); + } + static void say_units(); + + static int8_t get_target_extruder_from_command(); + static int8_t get_target_e_stepper_from_command(const int8_t dval=-1); + static void get_destination_from_command(); + + static void process_parsed_command(const bool no_ok=false); + static void process_next_command(); + + // Execute G-code in-place, preserving current G-code parameters + static void process_subcommands_now(FSTR_P fgcode); + static void process_subcommands_now(char * gcode); + + static void home_all_axes(const bool keep_leveling=false) { + process_subcommands_now(keep_leveling ? FPSTR(G28_STR) : TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); + } + + #if ANY(HAS_AUTO_REPORTING, HOST_KEEPALIVE_FEATURE) + static bool autoreport_paused; + static bool set_autoreport_paused(const bool p) { + const bool was = autoreport_paused; + autoreport_paused = p; + return was; + } + #else + static constexpr bool autoreport_paused = false; + static bool set_autoreport_paused(const bool) { return false; } + #endif + + #if ENABLED(HOST_KEEPALIVE_FEATURE) + /** + * States for managing Marlin and host communication + * Marlin sends messages if blocked or busy + */ + enum MarlinBusyState : char { + NOT_BUSY, // Not in a handler + IN_HANDLER, // Processing a G-Code + IN_PROCESS, // Known to be blocking command input (as in G29) + PAUSED_FOR_USER, // Blocking pending any input + PAUSED_FOR_INPUT // Blocking pending text input (concept) + }; + + static MarlinBusyState busy_state; + static uint8_t host_keepalive_interval; + + static void host_keepalive(); + static bool host_keepalive_is_paused() { return busy_state >= PAUSED_FOR_USER; } + + #define KEEPALIVE_STATE(N) REMEMBER(_KA_, gcode.busy_state, gcode.N) + #else + #define KEEPALIVE_STATE(N) NOOP + #endif + + static void dwell(millis_t time); + +private: + + friend class MarlinSettings; + #if ENABLED(ARC_SUPPORT) + friend void plan_arc(const xyze_pos_t&, const ab_float_t&, const bool, const uint8_t); + #endif + + #if ENABLED(MARLIN_DEV_MODE) + static void D(const int16_t dcode); + #endif + + static void G0_G1(TERN_(HAS_FAST_MOVES, const bool fast_move=false)); + + #if ENABLED(ARC_SUPPORT) + static void G2_G3(const bool clockwise); + #endif + + static void G4(); + + #if ENABLED(BEZIER_CURVE_SUPPORT) + static void G5(); + #endif + + #if ENABLED(DIRECT_STEPPING) + static void G6(); + #endif + + #if ENABLED(FWRETRACT) + static void G10(); + static void G11(); + #endif + + #if ENABLED(NOZZLE_CLEAN_FEATURE) + static void G12(); + #endif + + #if ENABLED(CNC_WORKSPACE_PLANES) + static void G17(); + static void G18(); + static void G19(); + #endif + + #if ENABLED(INCH_MODE_SUPPORT) + static void G20(); + static void G21(); + #endif + + #if ENABLED(G26_MESH_VALIDATION) + static void G26(); + #endif + + #if ENABLED(NOZZLE_PARK_FEATURE) + static void G27(); + #endif + + static void G28(); + + #if HAS_LEVELING + #if ENABLED(G29_RETRY_AND_RECOVER) + static void event_probe_failure(); + static void event_probe_recover(); + static void G29_with_retry(); + #define G29_TYPE bool + #else + #define G29_TYPE void + #endif + static G29_TYPE G29(); + #endif + + #if HAS_BED_PROBE + static void G30(); + #if ENABLED(Z_PROBE_SLED) + static void G31(); + static void G32(); + #endif + #endif + + #if ENABLED(DELTA_AUTO_CALIBRATION) + static void G33(); + #endif + + #if ANY(Z_MULTI_ENDSTOPS, Z_STEPPER_AUTO_ALIGN, MECHANICAL_GANTRY_CALIBRATION) + static void G34(); + #endif + + #if ENABLED(Z_STEPPER_AUTO_ALIGN) + static void M422(); + static void M422_report(const bool forReplay=true); + #endif + + #if ENABLED(ASSISTED_TRAMMING) + static void G35(); + #endif + + #if ENABLED(G38_PROBE_TARGET) + static void G38(const int8_t subcode); + #endif + + #if HAS_MESH + static void G42(); + #endif + + #if ENABLED(CNC_COORDINATE_SYSTEMS) + static void G53(); + static void G54(); + static void G55(); + static void G56(); + static void G57(); + static void G58(); + static void G59(); + #endif + + #if ALL(PTC_PROBE, PTC_BED) + static void G76(); + #endif + + #if SAVED_POSITIONS + static void G60(); + static void G61(int8_t slot=-1); + #endif + + #if ENABLED(GCODE_MOTION_MODES) + static void G80(); + #endif + + static void G92(); + + #if ENABLED(CALIBRATION_GCODE) + static void G425(); + #endif + + #if HAS_RESUME_CONTINUE + static void M0_M1(); + #endif + + #if HAS_CUTTER + static void M3_M4(const bool is_M4); + static void M5(); + #endif + + #if ENABLED(COOLANT_MIST) + static void M7(); + #endif + + #if ANY(AIR_ASSIST, COOLANT_FLOOD) + static void M8(); + #endif + + #if ANY(AIR_ASSIST, COOLANT_CONTROL) + static void M9(); + #endif + + #if ENABLED(AIR_EVACUATION) + static void M10(); + static void M11(); + #endif + + #if ENABLED(EXTERNAL_CLOSED_LOOP_CONTROLLER) + static void M12(); + #endif + + #if ENABLED(EXPECTED_PRINTER_CHECK) + static void M16(); + #endif + + static void M17(); + + static void M18_M84(); + + #if HAS_MEDIA + static void M20(); + static void M21(); + static void M22(); + static void M23(); + static void M24(); + static void M25(); + static void M26(); + static void M27(); + static void M28(); + static void M29(); + static void M30(); + #endif + + static void M31(); + + #if HAS_MEDIA + #if HAS_MEDIA_SUBCALLS + static void M32(); + #endif + #if ENABLED(LONG_FILENAME_HOST_SUPPORT) + static void M33(); + #endif + #if ALL(SDCARD_SORT_ALPHA, SDSORT_GCODE) + static void M34(); + #endif + #endif + + #if ENABLED(DIRECT_PIN_CONTROL) + static void M42(); + #endif + #if ENABLED(PINS_DEBUGGING) + static void M43(); + #endif + + #if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST) + static void M48(); + #endif + + #if ENABLED(SET_PROGRESS_MANUALLY) + static void M73(); + #endif + + static void M75(); + static void M76(); + static void M77(); + + #if ENABLED(PRINTCOUNTER) + static void M78(); + #endif + + #if ENABLED(PSU_CONTROL) + static void M80(); + #endif + static void M81(); + + #if HAS_EXTRUDERS + static void M82(); + static void M83(); + #endif + + static void M85(); + + #if ENABLED(HOTEND_IDLE_TIMEOUT) + static void M86(); + static void M86_report(const bool forReplay=true); + static void M87(); + #endif + + #if ENABLED(EDITABLE_STEPS_PER_UNIT) + static void M92(); + static void M92_report(const bool forReplay=true, const int8_t e=-1); + #endif + + #if ENABLED(M100_FREE_MEMORY_WATCHER) + static void M100(); + #endif + + #if ENABLED(BD_SENSOR) + static void M102(); + #endif + + #if HAS_HOTEND + static void M104_M109(const bool isM109); + FORCE_INLINE static void M104() { M104_M109(false); } + FORCE_INLINE static void M109() { M104_M109(true); } + #endif + + static void M105(); + + #if HAS_FAN + static void M106(); + static void M107(); + #endif + + #if DISABLED(EMERGENCY_PARSER) + static void M108(); + static void M112(); + static void M410(); + #if ENABLED(HOST_PROMPT_SUPPORT) + static void M876(); + #endif + #endif + + static void M110(); + static void M111(); + + #if ENABLED(HOST_KEEPALIVE_FEATURE) + static void M113(); + #endif + + static void M114(); + + #if ENABLED(CAPABILITIES_REPORT) + static void M115(); + #endif + + #if HAS_STATUS_MESSAGE + static void M117(); + #endif + + static void M118(); + static void M119(); + static void M120(); + static void M121(); + + #if HAS_FANCHECK + static void M123(); + #endif + + #if ENABLED(PARK_HEAD_ON_PAUSE) + static void M125(); + #endif + + #if ENABLED(BARICUDA) + #if HAS_HEATER_1 + static void M126(); + static void M127(); + #endif + #if HAS_HEATER_2 + static void M128(); + static void M129(); + #endif + #endif + + #if HAS_HEATED_BED + static void M140_M190(const bool isM190); + FORCE_INLINE static void M140() { M140_M190(false); } + FORCE_INLINE static void M190() { M140_M190(true); } + #endif + + #if HAS_HEATED_CHAMBER + static void M141(); + static void M191(); + #endif + + #if HAS_TEMP_PROBE + static void M192(); + #endif + + #if HAS_COOLER + static void M143(); + static void M193(); + #endif + + #if HAS_PREHEAT + static void M145(); + static void M145_report(const bool forReplay=true); + #endif + + #if ENABLED(TEMPERATURE_UNITS_SUPPORT) + static void M149(); + static void M149_report(const bool forReplay=true); + #endif + + #if HAS_COLOR_LEDS + static void M150(); + #endif + + #if ENABLED(AUTO_REPORT_POSITION) + static void M154(); + #endif + + #if ALL(AUTO_REPORT_TEMPERATURES, HAS_TEMP_SENSOR) + static void M155(); + #endif + + #if ENABLED(MIXING_EXTRUDER) + static void M163(); + static void M164(); + #if ENABLED(DIRECT_MIXING_IN_G1) + static void M165(); + #endif + #if ENABLED(GRADIENT_MIX) + static void M166(); + #endif + #endif + + #if DISABLED(NO_VOLUMETRICS) + static void M200(); + static void M200_report(const bool forReplay=true); + #endif + static void M201(); + static void M201_report(const bool forReplay=true); + + #if 0 + static void M202(); // Not used for Sprinter/grbl gen6 + #endif + + static void M203(); + static void M203_report(const bool forReplay=true); + static void M204(); + static void M204_report(const bool forReplay=true); + static void M205(); + static void M205_report(const bool forReplay=true); + + #if HAS_HOME_OFFSET + static void M206(); + static void M206_report(const bool forReplay=true); + #endif + + #if ENABLED(FWRETRACT) + static void M207(); + static void M207_report(const bool forReplay=true); + static void M208(); + static void M208_report(const bool forReplay=true); + #if ENABLED(FWRETRACT_AUTORETRACT) + static void M209(); + static void M209_report(const bool forReplay=true); + #endif + #endif + + #if ENABLED(EDITABLE_HOMING_FEEDRATE) + static void M210(); + static void M210_report(const bool forReplay=true); + #endif + + #if HAS_SOFTWARE_ENDSTOPS + static void M211(); + static void M211_report(const bool forReplay=true); + #endif + + #if HAS_MULTI_EXTRUDER + static void M217(); + static void M217_report(const bool forReplay=true); + #endif + + #if HAS_HOTEND_OFFSET + static void M218(); + static void M218_report(const bool forReplay=true); + #endif + + static void M220(); + + #if HAS_EXTRUDERS + static void M221(); + #endif + + #if ENABLED(DIRECT_PIN_CONTROL) + static void M226(); + #endif + + #if ENABLED(PHOTO_GCODE) + static void M240(); + #endif + + #if HAS_LCD_CONTRAST + static void M250(); + static void M250_report(const bool forReplay=true); + #endif + + #if ENABLED(EDITABLE_DISPLAY_TIMEOUT) + static void M255(); + static void M255_report(const bool forReplay=true); + #endif + + #if HAS_LCD_BRIGHTNESS + static void M256(); + static void M256_report(const bool forReplay=true); + #endif + + #if ENABLED(EXPERIMENTAL_I2CBUS) + static void M260(); + static void M261(); + #endif + + #if HAS_SERVOS + static void M280(); + #if ENABLED(EDITABLE_SERVO_ANGLES) + static void M281(); + static void M281_report(const bool forReplay=true); + #endif + #if ENABLED(SERVO_DETACH_GCODE) + static void M282(); + #endif + #endif + + #if ENABLED(BABYSTEPPING) + static void M290(); + #if ENABLED(EP_BABYSTEPPING) + static void M293(); + static void M294(); + #endif + #endif + + #if HAS_SOUND + static void M300(); + #endif + + #if ENABLED(PIDTEMP) + static void M301(); + static void M301_report(const bool forReplay=true E_OPTARG(const int8_t eindex=-1)); + #endif + + #if ENABLED(PREVENT_COLD_EXTRUSION) + static void M302(); + #endif + + #if HAS_PID_HEATING + static void M303(); + #endif + + #if ENABLED(PIDTEMPBED) + static void M304(); + static void M304_report(const bool forReplay=true); + #endif + + #if HAS_USER_THERMISTORS + static void M305(); + #endif + + #if ENABLED(MPCTEMP) + static void M306(); + static void M306_report(const bool forReplay=true); + #endif + + #if ENABLED(PIDTEMPCHAMBER) + static void M309(); + static void M309_report(const bool forReplay=true); + #endif + + #if HAS_MICROSTEPS + static void M350(); + static void M351(); + #endif + + #if ENABLED(CASE_LIGHT_ENABLE) + static void M355(); + #endif + + #if ENABLED(REPETIER_GCODE_M360) + static void M360(); + #endif + + #if ENABLED(MORGAN_SCARA) + static bool M360(); + static bool M361(); + static bool M362(); + static bool M363(); + static bool M364(); + #endif + + #if ANY(EXT_SOLENOID, MANUAL_SOLENOID_CONTROL) + static void M380(); + static void M381(); + #endif + + static void M400(); + + #if HAS_BED_PROBE + static void M401(); + static void M402(); + #endif + + #if HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3 + static void M403(); + #endif + + #if ENABLED(FILAMENT_WIDTH_SENSOR) + static void M404(); + static void M405(); + static void M406(); + static void M407(); + #endif + + #if HAS_FILAMENT_SENSOR + static void M412(); + static void M412_report(const bool forReplay=true); + #endif + + #if HAS_MULTI_LANGUAGE + static void M414(); + static void M414_report(const bool forReplay=true); + #endif + + #if HAS_LEVELING + static void M420(); + static void M420_report(const bool forReplay=true); + static void M421(); + #endif + + #if ENABLED(BACKLASH_GCODE) + static void M425(); + static void M425_report(const bool forReplay=true); + #endif + + #if HAS_HOME_OFFSET + static void M428(); + #endif + + #if HAS_POWER_MONITOR + static void M430(); + #endif + + #if HAS_RS485_SERIAL + static void M485(); + #endif + + #if ENABLED(CANCEL_OBJECTS) + static void M486(); + #endif + + #if ENABLED(FT_MOTION) + static void M493(); + static void M493_report(const bool forReplay=true); + #endif + + static void M500(); + static void M501(); + static void M502(); + #if DISABLED(DISABLE_M503) + static void M503(); + #endif + #if ENABLED(EEPROM_SETTINGS) + static void M504(); + #endif + + #if ENABLED(PASSWORD_FEATURE) + static void M510(); + #if ENABLED(PASSWORD_UNLOCK_GCODE) + static void M511(); + #endif + #if ENABLED(PASSWORD_CHANGE_GCODE) + static void M512(); + #endif + #endif + + #if HAS_MEDIA + static void M524(); + #endif + + #if ENABLED(SD_ABORT_ON_ENDSTOP_HIT) + static void M540(); + #endif + + #if HAS_ETHERNET + static void M552(); + static void M552_report(); + static void M553(); + static void M553_report(); + static void M554(); + static void M554_report(); + #endif + + #if HAS_STEALTHCHOP + static void M569(); + static void M569_report(const bool forReplay=true); + #endif + + #if ENABLED(BAUD_RATE_GCODE) + static void M575(); + #endif + + #if ENABLED(NONLINEAR_EXTRUSION) + static void M592(); + static void M592_report(const bool forReplay=true); + #endif + + #if HAS_ZV_SHAPING + static void M593(); + static void M593_report(const bool forReplay=true); + #endif + + #if ENABLED(ADVANCED_PAUSE_FEATURE) + static void M600(); + static void M603(); + static void M603_report(const bool forReplay=true); + #endif + + #if HAS_DUPLICATION_MODE + static void M605(); + #endif + + #if IS_KINEMATIC + static void M665(); + static void M665_report(const bool forReplay=true); + #endif + + #if ANY(DELTA, HAS_EXTRA_ENDSTOPS) + static void M666(); + static void M666_report(const bool forReplay=true); + #endif + + #if ENABLED(DUET_SMART_EFFECTOR) && PIN_EXISTS(SMART_EFFECTOR_MOD) + static void M672(); + #endif + + #if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES) + static void M701(); + static void M702(); + #endif + + #if HAS_PRUSA_MMU3 + static void M704(); + static void M705(); + static void M706(); + static void M707(); + static void M708(); + static void M709(); + static void MMU3_report(const bool forReplay=true); + #endif + + #if ENABLED(GCODE_REPEAT_MARKERS) + static void M808(); + #endif + + #if ENABLED(GCODE_MACROS) + static void M810_819(); + static void M820(); + #endif + + #if HAS_BED_PROBE + static void M851(); + static void M851_report(const bool forReplay=true); + #endif + + #if ENABLED(SKEW_CORRECTION_GCODE) + static void M852(); + static void M852_report(const bool forReplay=true); + #endif + + #if ENABLED(I2C_POSITION_ENCODERS) + FORCE_INLINE static void M860() { I2CPEM.M860(); } + FORCE_INLINE static void M861() { I2CPEM.M861(); } + FORCE_INLINE static void M862() { I2CPEM.M862(); } + FORCE_INLINE static void M863() { I2CPEM.M863(); } + FORCE_INLINE static void M864() { I2CPEM.M864(); } + FORCE_INLINE static void M865() { I2CPEM.M865(); } + FORCE_INLINE static void M866() { I2CPEM.M866(); } + FORCE_INLINE static void M867() { I2CPEM.M867(); } + FORCE_INLINE static void M868() { I2CPEM.M868(); } + FORCE_INLINE static void M869() { I2CPEM.M869(); } + #endif + + #if HAS_PTC + static void M871(); + #endif + + #if ENABLED(LIN_ADVANCE) + static void M900(); + static void M900_report(const bool forReplay=true); + #endif + + #if HAS_TRINAMIC_CONFIG + static void M122(); + static void M906(); + static void M906_report(const bool forReplay=true); + #if ENABLED(MONITOR_DRIVER_STATUS) + static void M911(); + static void M912(); + #endif + #if ENABLED(HYBRID_THRESHOLD) + static void M913(); + static void M913_report(const bool forReplay=true); + #endif + #if USE_SENSORLESS + static void M914(); + static void M914_report(const bool forReplay=true); + #endif + static void M919(); + #endif + + #if HAS_MOTOR_CURRENT_SPI || HAS_MOTOR_CURRENT_PWM || HAS_MOTOR_CURRENT_I2C || HAS_MOTOR_CURRENT_DAC + static void M907(); + #if HAS_MOTOR_CURRENT_SPI || HAS_MOTOR_CURRENT_PWM + static void M907_report(const bool forReplay=true); + #endif + #endif + #if HAS_MOTOR_CURRENT_SPI || HAS_MOTOR_CURRENT_DAC + static void M908(); + #endif + #if HAS_MOTOR_CURRENT_DAC + static void M909(); + static void M910(); + #endif + + #if HAS_MEDIA + static void M928(); + #endif + + #if ENABLED(OTA_FIRMWARE_UPDATE) + static void M936(); + #endif + + #if ENABLED(MAGNETIC_PARKING_EXTRUDER) + static void M951(); + #endif + + #if ENABLED(TOUCH_SCREEN_CALIBRATION) + static void M995(); + #endif + + #if SPI_FLASH_BACKUP + static void M993(); + static void M994(); + #endif + + #if ENABLED(PLATFORM_M997_SUPPORT) + static void M997(); + #endif + + static void M999(); + + #if ENABLED(POWER_LOSS_RECOVERY) + static void M413(); + static void M413_report(const bool forReplay=true); + static void M1000(); + #endif + + #if ENABLED(X_AXIS_TWIST_COMPENSATION) + static void M423(); + static void M423_report(const bool forReplay=true); + #endif + + #if HAS_MEDIA + static void M1001(); + #endif + + #if DGUS_LCD_UI_MKS + static void M1002(); + #endif + + #if ENABLED(ONE_CLICK_PRINT) + static void M1003(); + #endif + + #if ENABLED(UBL_MESH_WIZARD) + static void M1004(); + #endif + + #if ENABLED(HAS_MCP3426_ADC) + static void M3426(); + #endif + + #if ENABLED(MAX7219_GCODE) + static void M7219(); + #endif + + #if ENABLED(CONTROLLER_FAN_EDITABLE) + static void M710(); + static void M710_report(const bool forReplay=true); + #endif + + static void T(const int8_t tool_index) IF_DISABLED(HAS_TOOLCHANGE, { UNUSED(tool_index); }); + +}; + +extern GcodeSuite gcode; diff --git a/Marlin/Modified files/menu_probe_level.cpp b/Marlin/Modified files/menu_probe_level.cpp new file mode 100644 index 0000000000..51b7c08952 --- /dev/null +++ b/Marlin/Modified files/menu_probe_level.cpp @@ -0,0 +1,423 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +// +// Probe and Level (Calibrate?) Menu +// + +#include "../../inc/MarlinConfigPre.h" + +#ifdef USE_PROBE_FOR_MESH_REF + #include "../../gcode/gcode.h" +#endif + +#if HAS_MARLINUI_MENU && ANY(HAS_LEVELING, HAS_BED_PROBE, ASSISTED_TRAMMING_WIZARD, LCD_BED_TRAMMING) + +#include "menu_item.h" + +#include "../../feature/bedlevel/bedlevel.h" + +#if HAS_LEVELING + #include "../../module/planner.h" // for leveling_active, z_fade_height +#endif + +#if HAS_BED_PROBE + #include "../../module/probe.h" +#endif + +#if ENABLED(BABYSTEP_ZPROBE_OFFSET) + #include "../../feature/babystep.h" +#endif + +#if ALL(TOUCH_SCREEN, HAS_GRAPHICAL_TFT) + #include "../tft/tft.h" + #include "../tft/touch.h" +#endif + +#if ENABLED(LCD_BED_LEVELING) && ANY(PROBE_MANUALLY, MESH_BED_LEVELING) + + #include "../../module/motion.h" + #include "../../gcode/queue.h" + + // + // Motion > Level Bed handlers + // + + // LCD probed points are from defaults + constexpr grid_count_t total_probe_points = TERN(AUTO_BED_LEVELING_3POINT, 3, GRID_MAX_POINTS); + + // + // Bed leveling is done. Wait for G29 to complete. + // A flag is used so that this can release control + // and allow the command queue to be processed. + // + // When G29 finishes the last move: + // - Raise Z to the "Z after probing" height + // - Don't return until done. + // + // ** This blocks the command queue! ** + // + void _lcd_level_bed_done() { + if (!ui.wait_for_move) { + #if DISABLED(MESH_BED_LEVELING) && defined(Z_AFTER_PROBING) + if (Z_AFTER_PROBING) { + // Display "Done" screen and wait for moves to complete + line_to_z(Z_AFTER_PROBING); + ui.synchronize(GET_TEXT_F(MSG_LEVEL_BED_DONE)); + } + #endif + ui.goto_previous_screen_no_defer(); + ui.completion_feedback(); + } + if (ui.should_draw()) MenuItem_static::draw(LCD_HEIGHT >= 4, GET_TEXT_F(MSG_LEVEL_BED_DONE)); + ui.refresh(LCDVIEW_CALL_REDRAW_NEXT); + } + + void _lcd_level_goto_next_point(); + + // + // Step 7: Get the Z coordinate, click goes to the next point or exits + // + void _lcd_level_bed_get_z() { + + if (ui.use_click()) { + + // + // Save the current Z position and move + // + + // If done... + if (++manual_probe_index >= total_probe_points) { + // + // The last G29 records the point and enables bed leveling + // + ui.wait_for_move = true; + ui.goto_screen(_lcd_level_bed_done); + #if ENABLED(MESH_BED_LEVELING) + queue.inject(F("G29S2")); + #elif ENABLED(PROBE_MANUALLY) + queue.inject(F("G29V1")); + #endif + } + else + _lcd_level_goto_next_point(); + + return; + } + + // + // Encoder knob or keypad buttons adjust the Z position + // + if (ui.encoderPosition) { + const float z = current_position.z + float(int32_t(ui.encoderPosition)) * (MESH_EDIT_Z_STEP); + line_to_z(constrain(z, -(LCD_PROBE_Z_RANGE) * 0.5f, (LCD_PROBE_Z_RANGE) * 0.5f)); + ui.refresh(LCDVIEW_CALL_REDRAW_NEXT); + ui.encoderPosition = 0; + } + + // + // Draw on first display, then only on Z change + // + if (ui.should_draw()) { + const float v = current_position.z; + MenuEditItemBase::draw_edit_screen(GET_TEXT_F(MSG_MOVE_Z), ftostr43sign(v + (v < 0 ? -0.0001f : 0.0001f), '+')); + } + } + + // + // Step 6: Display "Next point: 1 / 9" while waiting for move to finish + // + void _lcd_level_bed_moving() { + if (ui.should_draw()) { + MString<10> msg; + msg.setf(F(" %i / %u"), int(manual_probe_index + 1), total_probe_points); + MenuItem_static::draw(LCD_HEIGHT / 2, GET_TEXT_F(MSG_LEVEL_BED_NEXT_POINT), SS_CENTER, msg); + } + ui.refresh(LCDVIEW_CALL_NO_REDRAW); + if (!ui.wait_for_move) ui.goto_screen(_lcd_level_bed_get_z); + } + + // + // Step 5: Initiate a move to the next point + // + void _lcd_level_goto_next_point() { + ui.goto_screen(_lcd_level_bed_moving); + + // G29 Records Z, moves, and signals when it pauses + ui.wait_for_move = true; + #if ENABLED(MESH_BED_LEVELING) + queue.inject(manual_probe_index ? F("G29S2") : F("G29S1")); + #elif ENABLED(PROBE_MANUALLY) + queue.inject(F("G29V1")); + #endif + } + + // + // Step 4: Display "Click to Begin", wait for click + // Move to the first probe position + // + void _lcd_level_bed_homing_done() { + if (ui.should_draw()) { + MenuItem_static::draw(1, GET_TEXT_F(MSG_LEVEL_BED_WAITING)); + // Color UI needs a control to detect a touch + #if ALL(TOUCH_SCREEN, HAS_GRAPHICAL_TFT) + touch.add_control(CLICK, 0, 0, TFT_WIDTH, TFT_HEIGHT); + #endif + } + if (ui.use_click()) { + manual_probe_index = 0; + _lcd_level_goto_next_point(); + } + } + + // + // Step 3: Display "Homing XYZ" - Wait for homing to finish + // + void _lcd_level_bed_homing() { + _lcd_draw_homing(); + if (all_axes_homed()) ui.goto_screen(_lcd_level_bed_homing_done); + } + + #if ENABLED(PROBE_MANUALLY) + extern bool g29_in_progress; + #endif + + // + // Step 2: Continue Bed Leveling... + // + void _lcd_level_bed_continue() { + ui.defer_status_screen(); + set_all_unhomed(); + ui.goto_screen(_lcd_level_bed_homing); + queue.inject_P(G28_STR); + } + +#endif // LCD_BED_LEVELING && (PROBE_MANUALLY || MESH_BED_LEVELING) + +#if ENABLED(MESH_EDIT_MENU) + + inline void refresh_planner() { + set_current_from_steppers_for_axis(ALL_AXES_ENUM); + sync_plan_position(); + } + + void menu_edit_mesh() { + static uint8_t xind, yind; // =0 + START_MENU(); + BACK_ITEM(MSG_BED_LEVELING); + EDIT_ITEM(uint8, MSG_MESH_X, &xind, 0, (GRID_MAX_POINTS_X) - 1); + EDIT_ITEM(uint8, MSG_MESH_Y, &yind, 0, (GRID_MAX_POINTS_Y) - 1); + EDIT_ITEM_FAST(float43, MSG_MESH_EDIT_Z, &bedlevel.z_values[xind][yind], -(LCD_PROBE_Z_RANGE) * 0.5, (LCD_PROBE_Z_RANGE) * 0.5, refresh_planner); + END_MENU(); + } + +#endif // MESH_EDIT_MENU + +#if ENABLED(AUTO_BED_LEVELING_UBL) + void _lcd_ubl_level_bed(); +#endif + +#if ENABLED(ASSISTED_TRAMMING_WIZARD) + void goto_tramming_wizard(); +#endif + +// Include a sub-menu when there's manual probing + +void menu_probe_level() { + const bool can_babystep_z = TERN0(BABYSTEP_ZPROBE_OFFSET, babystep.can_babystep(Z_AXIS)); + + #if HAS_LEVELING + const bool is_homed = all_axes_homed(), + is_valid = leveling_is_valid(); + #endif + + #if NONE(PROBE_MANUALLY, MESH_BED_LEVELING) + const bool is_trusted = all_axes_trusted(); + #endif + + START_MENU(); + + // + // ^ Main + // + BACK_ITEM(MSG_MAIN_MENU); + + if (!g29_in_progress) { + + // Auto Home if not using manual probing + #if NONE(PROBE_MANUALLY, MESH_BED_LEVELING) + if (!is_trusted) GCODES_ITEM(MSG_AUTO_HOME, FPSTR(G28_STR)); + #endif + + #if HAS_LEVELING + + // Homed and leveling is valid? Then leveling can be toggled. + if (is_homed && is_valid) { + bool show_state = planner.leveling_active; + EDIT_ITEM(bool, MSG_BED_LEVELING, &show_state, _lcd_toggle_bed_leveling); + } + + // + // Level Bed + // + #if ENABLED(AUTO_BED_LEVELING_UBL) + // UBL uses a guided procedure + SUBMENU(MSG_UBL_LEVELING, _lcd_ubl_level_bed); + #elif ANY(PROBE_MANUALLY, MESH_BED_LEVELING) + #if ENABLED(LCD_BED_LEVELING) + // Manual leveling uses a guided procedure + SUBMENU(MSG_LEVEL_BED, _lcd_level_bed_continue); + #endif + #else + // Automatic leveling can just run the G-code + GCODES_ITEM(MSG_LEVEL_BED, is_homed ? F("G29") : F("G29N")); + #endif + + // + // Edit Mesh (non-UBL) + // + #if ENABLED(MESH_EDIT_MENU) + if (is_valid) SUBMENU(MSG_EDIT_MESH, menu_edit_mesh); + #endif + + // + // Mesh Bed Leveling Z-Offset + // + #if ENABLED(MESH_BED_LEVELING) + #if WITHIN(PROBE_OFFSET_ZMIN, -9, 9) + #define LCD_Z_OFFSET_TYPE float43 // Values from -9.000 to +9.000 + #else + #define LCD_Z_OFFSET_TYPE float42_52 // Values from -99.99 to 99.99 + #endif + EDIT_ITEM(LCD_Z_OFFSET_TYPE, MSG_MESH_Z_OFFSET, &bedlevel.z_offset, PROBE_OFFSET_ZMIN, PROBE_OFFSET_ZMAX); + #endif + + #endif + + } // no G29 in progress + + // Z Fade Height + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) && DISABLED(SLIM_LCD_MENUS) + // Shadow for editing the fade height + editable.decimal = planner.z_fade_height; + EDIT_ITEM_FAST(float3, MSG_Z_FADE_HEIGHT, &editable.decimal, 0, 100, []{ set_z_fade_height(editable.decimal); }); + #endif + + if (!g29_in_progress) { + // + // Probe Deploy/Stow + // + #if ENABLED(PROBE_DEPLOY_STOW_MENU) + GCODES_ITEM(MSG_MANUAL_DEPLOY, F("M401")); + GCODES_ITEM(MSG_MANUAL_STOW, F("M402")); + #endif + + // Tare the probe on-demand + #if ENABLED(PROBE_TARE_MENU) + ACTION_ITEM(MSG_TARE_PROBE, probe.tare); + #endif + + // + // Probe XY Offsets + // + #if HAS_PROBE_XY_OFFSET + EDIT_ITEM_N(float31sign, X_AXIS, MSG_ZPROBE_OFFSET_N, &probe.offset.x, PROBE_OFFSET_XMIN, PROBE_OFFSET_XMAX); + EDIT_ITEM_N(float31sign, Y_AXIS, MSG_ZPROBE_OFFSET_N, &probe.offset.y, PROBE_OFFSET_YMIN, PROBE_OFFSET_YMAX); + #endif + + // + // Probe Z Offset - Babystep or Edit + // + if (can_babystep_z) { + #if ENABLED(BABYSTEP_ZPROBE_OFFSET) + SUBMENU(MSG_BABYSTEP_PROBE_Z, lcd_babystep_zoffset); + #endif + } + else { + #if HAS_BED_PROBE + #ifdef USE_PROBE_FOR_MESH_REF + #ifdef HOME_SWITCH_TO_BED_OFFSET_MENU + // Change the name of the menu option as the offsett relates to the fixed Z stop/Homing switch but + // reduce the range to -1/+1 and use the same offset variable so function stays the same + EDIT_ITEM_N(LCD_Z_OFFSET_TYPE, Z_AXIS, MSG_HOME_OFFSET_Z, &mesh_zero_ref_offset, -1, 0); + #endif + #else + EDIT_ITEM_N(LCD_Z_OFFSET_TYPE, Z_AXIS, MSG_ZPROBE_OFFSET_N, &probe.offset.z, PROBE_OFFSET_ZMIN, PROBE_OFFSET_ZMAX); + #endif + #endif + } + + // + // Probe Z Offset Wizard + // + #if ENABLED(PROBE_OFFSET_WIZARD) + SUBMENU(MSG_PROBE_WIZARD, goto_probe_offset_wizard); + #endif + + // + // Probe Repeatability Test + // + #if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST) + GCODES_ITEM(MSG_M48_TEST, F("G28O\nM48 P10")); + #endif + + // + // Assisted Bed Tramming + // + #if ENABLED(ASSISTED_TRAMMING_WIZARD) + SUBMENU(MSG_TRAMMING_WIZARD, goto_tramming_wizard); + #endif + + // + // Bed Tramming Submenu + // + #if ENABLED(LCD_BED_TRAMMING) + SUBMENU(MSG_BED_TRAMMING, _lcd_bed_tramming); + #endif + + // + // Auto Z-Align + // + #if ANY(Z_STEPPER_AUTO_ALIGN, MECHANICAL_GANTRY_CALIBRATION) + GCODES_ITEM(MSG_AUTO_Z_ALIGN, F("G34")); + #endif + + // + // Twist Compensation + // + #if ENABLED(X_AXIS_TWIST_COMPENSATION) + SUBMENU(MSG_XATC, xatc_wizard_continue); + #endif + + // + // Store to EEPROM + // + #if ENABLED(EEPROM_SETTINGS) + ACTION_ITEM(MSG_STORE_EEPROM, ui.store_settings); + #endif + + } + + END_MENU(); +} + +#endif // HAS_MARLINUI_MENU && (HAS_LEVELING || HAS_BED_PROBE || ASSISTED_TRAMMING_WIZARD || LCD_BED_TRAMMING) diff --git a/Marlin/Modified files/ubl_G29.cpp b/Marlin/Modified files/ubl_G29.cpp new file mode 100644 index 0000000000..733aaa8abe --- /dev/null +++ b/Marlin/Modified files/ubl_G29.cpp @@ -0,0 +1,1898 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] + * + * Based on Sprinter and grbl. + * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "../../../inc/MarlinConfig.h" + +#if ENABLED(AUTO_BED_LEVELING_UBL) + +#include "../bedlevel.h" + +#include "../../../MarlinCore.h" +#include "../../../HAL/shared/eeprom_api.h" +#include "../../../libs/hex_print.h" +#include "../../../module/settings.h" +#include "../../../lcd/marlinui.h" +#include "../../../module/planner.h" +#include "../../../module/motion.h" +#include "../../../module/probe.h" +#include "../../../gcode/gcode.h" +#include "../../../libs/least_squares_fit.h" + +#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) +#include "../../../core/debug_out.h" + +#if ENABLED(EXTENSIBLE_UI) + #include "../../../lcd/extui/ui_api.h" +#endif + +#if ENABLED(UBL_HILBERT_CURVE) + #include "../hilbert_curve.h" +#endif + +#if FT_MOTION_DISABLE_FOR_PROBING + #include "../../../module/ft_motion.h" +#endif + +#include + +#define UBL_G29_P31 + +#if HAS_MARLINUI_MENU + + bool unified_bed_leveling::lcd_map_control = false; + + void unified_bed_leveling::steppers_were_disabled() { + if (lcd_map_control) { + lcd_map_control = false; + ui.defer_status_screen(false); + } + } + + void ubl_map_screen(); + +#endif + +#define SIZE_OF_LITTLE_RAISE 1 +#define BIG_RAISE_NOT_NEEDED 0 + +#ifdef USE_PROBE_FOR_MESH_REF + float mesh_zero_ref_offset = 0; // declared in gcode.h as external so it can be set in menu_probe_level.cpp +#endif + +/** + * G29: Unified Bed Leveling by Roxy + * + * Parameters understood by this leveling system: + * + * A Activate Activate the Unified Bed Leveling system. + * + * B # Business Use the 'Business Card' mode of the Manual Probe subsystem with P2. + * Note: A non-compressible Spark Gap feeler gauge is recommended over a business card. + * In this mode of G29 P2, a business or index card is used as a shim that the nozzle can + * grab onto as it is lowered. In principle, the nozzle-bed distance is the same when the + * same resistance is felt in the shim. You can omit the numerical value on first invocation + * of G29 P2 B to measure shim thickness. Subsequent use of 'B' will apply the previously- + * measured thickness by default. + * + * C Continue G29 P1 C continues the generation of a partially-constructed Mesh without invalidating + * previous measurements. + * + * C G29 P2 C tells the Manual Probe subsystem to not use the current nozzle + * location in its search for the closest unmeasured Mesh Point. Instead, attempt to + * start at one end of the uprobed points and Continue sequentially. + * + * G29 P3 C specifies the Constant for the fill. Otherwise, uses a "reasonable" value. + * + * C Current G29 Z C uses the Current location (instead of bed center or nearest edge). + * + * D Disable Disable the Unified Bed Leveling system. + * + * E Stow_probe Stow the probe after each sampled point. + * + * F # Fade Fade the amount of Mesh Based Compensation over a specified height. At the + * specified height, no correction is applied and natural printer kenimatics take over. If no + * number is specified for the command, 10mm is assumed to be reasonable. + * + * H # Height With P2, 'H' specifies the Height to raise the nozzle after each manual probe of the bed. + * If omitted, the nozzle will raise by Z_CLEARANCE_BETWEEN_PROBES. + * + * H # Offset With P4, 'H' specifies the Offset above the mesh height to place the nozzle. + * If omitted, Z_TWEEN_SAFE_CLEARANCE will be used. + * + * I # Invalidate Invalidate the specified number of Mesh Points near the given 'X' 'Y'. If X or Y are omitted, + * the nozzle location is used. If no 'I' value is given, only the point nearest to the location + * is invalidated. Use 'T' to produce a map afterward. This command is useful to invalidate a + * portion of the Mesh so it can be adjusted using other UBL tools. When attempting to invalidate + * an isolated bad mesh point, the 'T' option shows the nozzle position in the Mesh with (#). You + * can move the nozzle around and use this feature to select the center of the area (or cell) to + * invalidate. + * + * J # Grid Perform a Grid Based Leveling of the current Mesh using a grid with n points on a side. + * Not specifying a grid size will invoke the 3-Point leveling function. + * + * L Load Load Mesh from the previously activated location in the EEPROM. + * + * L # Load Load Mesh from the specified location in the EEPROM. Set this location as activated + * for subsequent Load and Store operations. + * + * The P or Phase commands are used for the bulk of the work to setup a Mesh. In general, your Mesh will + * start off being initialized with a G29 P0 or a G29 P1. Further refinement of the Mesh happens with + * each additional Phase that processes it. + * + * P0 Phase 0 Zero Mesh Data and turn off the Mesh Compensation System. This reverts the + * 3D Printer to the same state it was in before the Unified Bed Leveling Compensation + * was turned on. Setting the entire Mesh to Zero is a special case that allows + * a subsequent G or T leveling operation for backward compatibility. + * + * P1 Phase 1 Invalidate entire Mesh and continue with automatic generation of the Mesh data using + * the Z-Probe. Usually the probe can't reach all areas that the nozzle can reach. For delta + * printers only the areas where the probe and nozzle can both reach will be automatically probed. + * + * Unreachable points will be handled in Phase 2 and Phase 3. + * + * Use 'C' to leave the previous mesh intact and automatically probe needed points. This allows you + * to invalidate parts of the Mesh but still use Automatic Probing. + * + * The 'X' and 'Y' parameters prioritize where to try and measure points. If omitted, the current + * probe position is used. + * + * Use 'T' (Topology) to generate a report of mesh generation. + * + * P1 will suspend Mesh generation if the controller button is held down. Note that you may need + * to press and hold the switch for several seconds if moves are underway. + * + * P2 Phase 2 Probe unreachable points. + * + * Use 'H' to set the height between Mesh points. If omitted, Z_CLEARANCE_BETWEEN_PROBES is used. + * Smaller values will be quicker. Move the nozzle down till it barely touches the bed. Make sure the + * nozzle is clean and unobstructed. Use caution and move slowly. This can damage your printer! + * (Uses SIZE_OF_LITTLE_RAISE mm if the nozzle is moving less than BIG_RAISE_NOT_NEEDED mm.) + * + * The 'H' value can be negative if the Mesh dips in a large area. Press and hold the + * controller button to terminate the current Phase 2 command. You can then re-issue "G29 P 2" + * with an 'H' parameter more suitable for the area you're manually probing. Note that the command + * tries to start in a corner of the bed where movement will be predictable. Override the distance + * calculation location with the X and Y parameters. You can print a Mesh Map (G29 T) to see where + * the mesh is invalidated and where the nozzle needs to move to complete the command. Use 'C' to + * indicate that the search should be based on the current position. + * + * The 'B' parameter for this command is described above. It places the manual probe subsystem into + * Business Card mode where the thickness of a business card is measured and then used to accurately + * set the nozzle height in all manual probing for the duration of the command. A Business card can + * be used, but you'll get better results with a flexible Shim that doesn't compress. This makes it + * easier to produce similar amounts of force and get more accurate measurements. Google if you're + * not sure how to use a shim. + * + * The 'T' (Map) parameter helps track Mesh building progress. + * + * NOTE: P2 requires an LCD controller! + * + * P3 Phase 3 Fill the unpopulated regions of the Mesh with a fixed value. There are two different paths to + * go down: + * + * - If a 'C' constant is specified, the closest invalid mesh points to the nozzle will be filled, + * and a repeat count can then also be specified with 'R'. + * + * - Leaving out 'C' invokes Smart Fill, which scans the mesh from the edges inward looking for + * invalid mesh points. Adjacent points are used to determine the bed slope. If the bed is sloped + * upward from the invalid point, it takes the value of the nearest point. If sloped downward, it's + * replaced by a value that puts all three points in a line. This version of G29 P3 is a quick, easy + * and (usually) safe way to populate unprobed mesh regions before continuing to G26 Mesh Validation + * Pattern. Note that this populates the mesh with unverified values. Pay attention and use caution. + * + * P4 Phase 4 Fine tune the Mesh. The Delta Mesh Compensation System assumes the existence of + * an LCD Panel. It is possible to fine tune the mesh without an LCD Panel using + * G42 and M421. See the UBL documentation for further details. + * + * Phase 4 is meant to be used with G26 Mesh Validation to fine tune the mesh by direct editing + * of Mesh Points. Raise and lower points to fine tune the mesh until it gives consistently reliable + * adhesion. + * + * P4 moves to the closest Mesh Point (and/or the given X Y), raises the nozzle above the mesh height + * by the given 'H' offset (or default 0), and waits while the controller is used to adjust the nozzle + * height. On click the displayed height is saved in the mesh. + * + * Start Phase 4 at a specific location with X and Y. Adjust a specific number of Mesh Points with + * the 'R' (Repeat) parameter. (If 'R' is left out, the whole matrix is assumed.) This command can be + * terminated early (e.g., after editing the area of interest) by pressing and holding the encoder button. + * + * The general form is G29 P4 [R points] [X position] [Y position] + * + * The H [offset] parameter is useful if a shim is used to fine-tune the mesh. For a 0.4mm shim the + * command would be G29 P4 H0.4. The nozzle is moved to the shim height, you adjust height to the shim, + * and on click the height minus the shim thickness will be saved in the mesh. + * + * !!Use with caution, as a very poor mesh could cause the nozzle to crash into the bed!! + * + * NOTE: P4 is not available unless you have LCD support enabled! + * + * P5 Phase 5 Find Mean Mesh Height and Standard Deviation. Typically, it is easier to use and + * work with the Mesh if it is Mean Adjusted. You can specify a C parameter to + * Correct the Mesh to a 0.00 Mean Height. Adding a C parameter will automatically + * execute a G29 P6 C . + * + * P6 Phase 6 Shift Mesh height. The entire Mesh's height is adjusted by the height specified + * with the C parameter. Being able to adjust the height of a Mesh is useful tool. It + * can be used to compensate for poorly calibrated Z-Probes and other errors. Ideally, + * you should have the Mesh adjusted for a Mean Height of 0.00 and the Z-Probe measuring + * 0.000 at the Z Home location. + * + * Q Test Load specified Test Pattern to assist in checking correct operation of system. This + * command is not anticipated to be of much value to the typical user. It is intended + * for developers to help them verify correct operation of the Unified Bed Leveling System. + * + * R # Repeat Repeat this command the specified number of times. If no number is specified the + * command will be repeated GRID_MAX_POINTS_X * GRID_MAX_POINTS_Y times. + * + * S Store Store the current Mesh in the Activated area of the EEPROM. It will also store the + * current state of the Unified Bed Leveling system in the EEPROM. + * + * S # Store Store the current Mesh at the specified location in EEPROM. Activate this location + * for subsequent Load and Store operations. Valid storage slot numbers begin at 0 and + * extend to a limit related to the available EEPROM storage. + * + * S -1 Store Print the current Mesh as G-code that can be used to restore the mesh anytime. + * + * T Topology Display the Mesh Map Topology. + * 'T' can be used alone (e.g., G29 T) or in combination with most of the other commands. + * This option works with all Phase commands (e.g., G29 P4 R 5 T X 50 Y100 C -.1 O) + * This parameter can also specify a Map Type. T0 (the default) is user-readable. T1 + * is suitable to paste into a spreadsheet for a 3D graph of the mesh. + * + * U Unlevel Perform a probe of the outer perimeter to assist in physically leveling unlevel beds. + * Only used for G29 P1 T U. This speeds up the probing of the edge of the bed. Useful + * when the entire bed doesn't need to be probed because it will be adjusted. + * + * V # Verbosity Set the verbosity level (0-4) for extra details. (Default 0) + * + * X # X Location for this command + * + * Y # Y Location for this command + * + * With UBL_DEVEL_DEBUGGING: + * + * K # Kompare Kompare current Mesh with stored Mesh #, replacing current Mesh with the result. + * This command literally performs a diff between two Meshes. + * + * Q-1 Dump EEPROM Dump the UBL contents stored in EEPROM as HEX format. Useful for developers to help + * verify correct operation of the UBL. + * + * W What? Display valuable UBL data. + * + * + * Release Notes: + * You MUST do M502, M500 to initialize the storage. Failure to do this will cause all + * kinds of problems. Enabling EEPROM Storage is required. + * + * When you do a G28 and G29 P1 to automatically build your first mesh, you are going to notice that + * UBL probes points increasingly further from the starting location. (The starting location defaults + * to the center of the bed.) In contrast, ABL and MBL follow a zigzag pattern. The spiral pattern is + * especially better for Delta printers, since it populates the center of the mesh first, allowing for + * a quicker test print to verify settings. You don't need to populate the entire mesh to use it. + * After all, you don't want to spend a lot of time generating a mesh only to realize the resolution + * or probe offsets are incorrect. Mesh-generation gathers points starting closest to the nozzle unless + * an (X,Y) coordinate pair is given. + * + * Unified Bed Leveling uses a lot of EEPROM storage to hold its data, and it takes some effort to get + * the mesh just right. To prevent this valuable data from being destroyed as the EEPROM structure + * evolves, UBL stores all mesh data at the end of EEPROM. + * + * UBL is founded on Edward Patel's Mesh Bed Leveling code. A big 'Thanks!' to him and the creators of + * 3-Point and Grid Based leveling. Combining their contributions we now have the functionality and + * features of all three systems combined. + */ + +G29_parameters_t unified_bed_leveling::param; + +void unified_bed_leveling::G29() { + + #ifdef EVENT_GCODE_AFTER_G29 + bool probe_deployed = false; + #define SET_PROBE_DEPLOYED(N) probe_deployed = N + #else + #define SET_PROBE_DEPLOYED(N) + #endif + + if (G29_parse_parameters()) return; // Abort on parameter error + + const uint8_t p_val = parser.byteval('P'); + const bool may_move = p_val == 1 || p_val == 2 || p_val == 4 || parser.seen_test('J'); + + #if FT_MOTION_DISABLE_FOR_PROBING + FTMotionDisableInScope FT_Disabler; // Disable Fixed-Time Motion for probing + #endif + + // Check for commands that require the printer to be homed + if (may_move) { + planner.synchronize(); + + #ifdef USE_PROBE_FOR_MESH_REF + // Send 'N' to force homing before G29 (internal only) + if (axes_should_home() || parser.seen_test('N')){ + gcode.home_all_axes(); + } + else { + gcode.process_subcommands_now(F("G28L0 X Y")); // Home X and Y only + } + // Set the probe trigger height as Z home before leveling + probe.probe_at_point(current_position, PROBE_PT_NONE,0 ,false ,true, Z_PROBE_LOW_POINT, Z_TWEEN_SAFE_CLEARANCE, false); + set_axis_is_at_home(Z_AXIS); + sync_plan_position(); + #else + #if ALL(DWIN_LCD_PROUI, ZHOME_BEFORE_LEVELING) + save_ubl_active_state_and_disable(); + gcode.process_subcommands_now(F("G28Z")); + restore_ubl_active_state(false); // ...without telling ExtUI "done" + #else + // Send 'N' to force homing before G29 (internal only) + if (axes_should_home() || parser.seen_test('N')) gcode.home_all_axes(); + #endif + #endif + + probe.use_probing_tool(); + + #ifdef EVENT_GCODE_BEFORE_G29 + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Before G29 G-code: ", EVENT_GCODE_BEFORE_G29); + gcode.process_subcommands_now(F(EVENT_GCODE_BEFORE_G29)); + #endif + + // Position bed horizontally and Z probe vertically. + #if HAS_SAFE_BED_LEVELING + xyze_pos_t safe_position = current_position; + #ifdef SAFE_BED_LEVELING_START_X + safe_position.x = SAFE_BED_LEVELING_START_X; + #endif + #ifdef SAFE_BED_LEVELING_START_Y + safe_position.y = SAFE_BED_LEVELING_START_Y; + #endif + #ifdef SAFE_BED_LEVELING_START_Z + safe_position.z = SAFE_BED_LEVELING_START_Z; + #endif + #ifdef SAFE_BED_LEVELING_START_I + safe_position.i = SAFE_BED_LEVELING_START_I; + #endif + #ifdef SAFE_BED_LEVELING_START_J + safe_position.j = SAFE_BED_LEVELING_START_J; + #endif + #ifdef SAFE_BED_LEVELING_START_K + safe_position.k = SAFE_BED_LEVELING_START_K; + #endif + #ifdef SAFE_BED_LEVELING_START_U + safe_position.u = SAFE_BED_LEVELING_START_U; + #endif + #ifdef SAFE_BED_LEVELING_START_V + safe_position.v = SAFE_BED_LEVELING_START_V; + #endif + #ifdef SAFE_BED_LEVELING_START_W + safe_position.w = SAFE_BED_LEVELING_START_W; + #endif + + do_blocking_move_to(safe_position); + #endif // HAS_SAFE_BED_LEVELING + } + + // Invalidate one or more nearby mesh points, possibly all. + if (parser.seen('I')) { + grid_count_t count = parser.has_value() ? parser.value_ushort() : 1; + bool invalidate_all = count >= GRID_MAX_POINTS; + if (!invalidate_all) { + while (count--) { + if ((count & 0x0F) == 0x0F) idle(); + const mesh_index_pair closest = find_closest_mesh_point_of_type(REAL, param.XY_pos); + // No more REAL mesh points to invalidate? Assume the user meant + // to invalidate the ENTIRE mesh, which can't be done with + // find_closest_mesh_point (which only returns REAL points). + if (closest.pos.x < 0) { invalidate_all = true; break; } + z_values[closest.pos.x][closest.pos.y] = NAN; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(closest.pos, 0.0f)); + } + } + if (invalidate_all) { + invalidate(); + SERIAL_ECHOPGM("Entire Mesh"); + } + else + SERIAL_ECHOPGM("Locations"); + SERIAL_ECHOLNPGM(" invalidated.\n"); + } + + if (parser.seen('Q')) { + const int16_t test_pattern = parser.has_value() ? parser.value_int() : -99; + if (!WITHIN(test_pattern, TERN0(UBL_DEVEL_DEBUGGING, -1), 2)) { + SERIAL_ECHOLNPGM("?Invalid (Q) test pattern. (" TERN(UBL_DEVEL_DEBUGGING, "-1", "0") " to 2)\n"); + return; + } + SERIAL_ECHOLNPGM("Applying test pattern.\n"); + switch (test_pattern) { + + default: + case -1: TERN_(UBL_DEVEL_DEBUGGING, g29_eeprom_dump()); break; + + case 0: + GRID_LOOP(x, y) { // Create a bowl shape similar to a poorly-calibrated Delta + const float p1 = 0.5f * (GRID_MAX_POINTS_X) - x, + p2 = 0.5f * (GRID_MAX_POINTS_Y) - y; + z_values[x][y] += 2.0f * HYPOT(p1, p2); + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); + } + break; + + case 1: + for (uint8_t x = 0; x < GRID_MAX_POINTS_X; ++x) { // Create a diagonal line several Mesh cells thick that is raised + const uint8_t x2 = x + (x < (GRID_MAX_POINTS_Y) - 1 ? 1 : -1); + z_values[x][x] += 9.999f; + z_values[x][x2] += 9.999f; // We want the altered line several mesh points thick + #if ENABLED(EXTENSIBLE_UI) + ExtUI::onMeshUpdate(x, x, z_values[x][x]); + ExtUI::onMeshUpdate(x, x2, z_values[x][x2]); + #endif + } + break; + + case 2: + // Allow the user to specify the height because 10mm is a little extreme in some cases. + for (uint8_t x = (GRID_MAX_POINTS_X) / 3; x < 2 * (GRID_MAX_POINTS_X) / 3; x++) // Create a rectangular raised area in + for (uint8_t y = (GRID_MAX_POINTS_Y) / 3; y < 2 * (GRID_MAX_POINTS_Y) / 3; y++) { // the center of the bed + z_values[x][y] += parser.seen_test('C') ? param.C_constant : 9.99f; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); + } + break; + } + } + + #if HAS_BED_PROBE + + if (parser.seen_test('J')) { + save_ubl_active_state_and_disable(); + tilt_mesh_based_on_probed_grid(param.J_grid_size == 0); // Zero size does 3-Point + restore_ubl_active_state(); + #if ENABLED(UBL_G29_J_RECENTER) + do_blocking_move_to_xy(0.5f * ((MESH_MIN_X) + (MESH_MAX_X)), 0.5f * ((MESH_MIN_Y) + (MESH_MAX_Y))); + #endif + report_current_position(); + SET_PROBE_DEPLOYED(true); + } + + #endif // HAS_BED_PROBE + + if (parser.seen_test('P')) { + if (WITHIN(param.P_phase, 0, 1) && storage_slot == -1) { + storage_slot = 0; + SERIAL_ECHOLNPGM("Default storage slot 0 selected."); + } + + switch (param.P_phase) { + case 0: + // + // Zero Mesh Data + // + reset(); + SERIAL_ECHOLNPGM("Mesh zeroed."); + break; + + #if HAS_BED_PROBE + + case 1: { + // + // Invalidate Entire Mesh and Automatically Probe Mesh in areas that can be reached by the probe + // + if (!parser.seen_test('C')) { + invalidate(); + SERIAL_ECHOLNPGM("Mesh invalidated. Probing mesh."); + } + if (param.V_verbosity > 1) + SERIAL_ECHOLN(F("Probing around ("), param.XY_pos.x, C(','), param.XY_pos.y, F(").\n")); + probe_entire_mesh(param.XY_pos, parser.seen_test('T'), parser.seen_test('E'), parser.seen_test('U')); + + report_current_position(); + SET_PROBE_DEPLOYED(true); + } break; + + #endif // HAS_BED_PROBE + + case 2: { + #if HAS_MARLINUI_MENU + // + // Manually Probe Mesh in areas that can't be reached by the probe + // + SERIAL_ECHOLNPGM("Manually probing unreachable points."); + do_z_clearance(Z_CLEARANCE_BETWEEN_PROBES); + + if (parser.seen_test('C') && !param.XY_seen) { + + /** + * Use a good default location for the path. + * The flipped > and < operators in these comparisons is intentional. + * It should cause the probed points to follow a nice path on Cartesian printers. + * It may make sense to have Delta printers default to the center of the bed. + * Until that is decided, this can be forced with the X and Y parameters. + */ + param.XY_pos.set( + #if IS_KINEMATIC + X_HOME_POS, Y_HOME_POS + #else + probe.offset_xy.x > 0 ? X_BED_SIZE : 0, + probe.offset_xy.y < 0 ? Y_BED_SIZE : 0 + #endif + ); + } + + if (parser.seen('B')) { + param.B_shim_thickness = parser.has_value() ? parser.value_float() : measure_business_card_thickness(); + if (ABS(param.B_shim_thickness) > 1.5f) { + SERIAL_ECHOLNPGM("?Error in Business Card measurement."); + return; + } + SET_PROBE_DEPLOYED(true); + } + + if (!position_is_reachable(param.XY_pos)) { + SERIAL_ECHOLNPGM("XY outside printable radius."); + return; + } + + const float height = parser.floatval('H', Z_CLEARANCE_BETWEEN_PROBES); + manually_probe_remaining_mesh(param.XY_pos, height, param.B_shim_thickness, parser.seen_test('T')); + + SERIAL_ECHOLNPGM("G29 P2 finished."); + + report_current_position(); + + #else + + SERIAL_ECHOLNPGM("?P2 is only available when an LCD is present."); + return; + + #endif + } break; + + case 3: { + /** + * Populate invalid mesh areas. Proceed with caution. + * Two choices are available: + * - Specify a constant with the 'C' parameter. + * - Allow 'G29 P3' to choose a 'reasonable' constant. + */ + + if (param.C_seen) { + if (param.R_repetition >= GRID_MAX_POINTS) { + set_all_mesh_points_to_value(param.C_constant); + } + else { + while (param.R_repetition--) { // this only populates reachable mesh points near + const mesh_index_pair closest = find_closest_mesh_point_of_type(INVALID, param.XY_pos); + const xy_int8_t &cpos = closest.pos; + if (cpos.x < 0) { + // No more REAL INVALID mesh points to populate, so we ASSUME + // user meant to populate ALL INVALID mesh points to value + GRID_LOOP(x, y) if (isnan(z_values[x][y])) z_values[x][y] = param.C_constant; + break; // No more invalid Mesh Points to populate + } + else { + z_values[cpos.x][cpos.y] = param.C_constant; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(cpos, param.C_constant)); + } + } + } + } + else { + const float cvf = parser.value_float(); + switch ((int)TRUNC(cvf * 10.0f) - 30) { // 3.1 -> 1 + #if ENABLED(UBL_G29_P31) + case 1: { + + // P3.1 use least squares fit to fill missing mesh values + // P3.10 zero weighting for distance, all grid points equal, best fit tilted plane + // P3.11 10X weighting for nearest grid points versus farthest grid points + // P3.12 100X distance weighting + // P3.13 1000X distance weighting, approaches simple average of nearest points + + const float weight_power = (cvf - 3.10f) * 100.0f, // 3.12345 -> 2.345 + weight_factor = weight_power ? POW(10.0f, weight_power) : 0; + smart_fill_wlsf(weight_factor); + } + break; + #endif + case 0: // P3 or P3.0 + default: // and anything P3.x that's not P3.1 + smart_fill_mesh(); // Do a 'Smart' fill using nearby known values + break; + } + } + break; + } + + case 4: // Fine Tune (i.e., Edit) the Mesh + #if HAS_MARLINUI_MENU + fine_tune_mesh(param.XY_pos, parser.seen_test('T')); + #else + SERIAL_ECHOLNPGM("?P4 is only available when an LCD is present."); + return; + #endif + break; + + case 5: adjust_mesh_to_mean(param.C_seen, param.C_constant); break; + + case 6: shift_mesh_height(); break; + } + } + + #if ENABLED(UBL_DEVEL_DEBUGGING) + + // + // Much of the 'What?' command can be eliminated. But until we are fully debugged, it is + // good to have the extra information. Soon... we prune this to just a few items + // + if (parser.seen_test('W')) g29_what_command(); + + // + // When we are fully debugged, this may go away. But there are some valid + // use cases for the users. So we can wait and see what to do with it. + // + + if (parser.seen('K')) // Kompare Current Mesh Data to Specified Stored Mesh + g29_compare_current_mesh_to_stored_mesh(); + + #endif // UBL_DEVEL_DEBUGGING + + // + // Load a Mesh from the EEPROM + // + + if (parser.seen('L')) { // Load Current Mesh Data + param.KLS_storage_slot = parser.has_value() ? (int8_t)parser.value_int() : storage_slot; + + int16_t a = settings.calc_num_meshes(); + + if (!a) { + SERIAL_ECHOLNPGM("?EEPROM storage not available."); + return; + } + + if (!WITHIN(param.KLS_storage_slot, 0, a - 1)) { + SERIAL_ECHOLNPGM("?Invalid storage slot.\n?Use 0 to ", a - 1); + return; + } + + settings.load_mesh(param.KLS_storage_slot); + storage_slot = param.KLS_storage_slot; + + SERIAL_ECHOLNPGM(STR_DONE); + } + + // + // Store a Mesh in the EEPROM + // + + if (parser.seen('S')) { // Store (or Save) Current Mesh Data + param.KLS_storage_slot = parser.has_value() ? (int8_t)parser.value_int() : storage_slot; + + if (param.KLS_storage_slot == -1) // Special case: 'Export' the mesh to the + return report_current_mesh(); // host so it can be saved in a file. + + int16_t a = settings.calc_num_meshes(); + + if (!a) { + SERIAL_ECHOLNPGM("?EEPROM storage not available."); + goto LEAVE; + } + + if (!WITHIN(param.KLS_storage_slot, 0, a - 1)) { + SERIAL_ECHOLNPGM("?Invalid storage slot.\n?Use 0 to ", a - 1); + goto LEAVE; + } + + settings.store_mesh(param.KLS_storage_slot); + storage_slot = param.KLS_storage_slot; + + SERIAL_ECHOLNPGM(STR_DONE); + } + + if (parser.seen_test('T')) + display_map(param.T_map_type); + + LEAVE: + + #if HAS_MARLINUI_MENU + ui.reset_alert_level(); + ui.quick_feedback(); + ui.reset_status(); + ui.release(); + #endif + + #ifdef EVENT_GCODE_AFTER_G29 + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("After G29 G-code: ", EVENT_GCODE_AFTER_G29); + if (probe_deployed) { + planner.synchronize(); + gcode.process_subcommands_now(F(EVENT_GCODE_AFTER_G29)); + } + #endif + + probe.use_probing_tool(false); + return; +} + +/** + * M420 C + * G29 P5 C : Adjust Mesh To Mean (and subtract the given offset). + * Find the mean average and shift the mesh to center on that value. + */ +void unified_bed_leveling::adjust_mesh_to_mean(const bool cflag, const_float_t offset) { + float sum = 0; + uint8_t n = 0; + GRID_LOOP(x, y) + if (!isnan(z_values[x][y])) { + sum += z_values[x][y]; + n++; + } + + const float mean = sum / n; + + // + // Sum the squares of difference from mean + // + float sum_of_diff_squared = 0; + GRID_LOOP(x, y) + if (!isnan(z_values[x][y])) + sum_of_diff_squared += sq(z_values[x][y] - mean); + + SERIAL_ECHOLNPGM("# of samples: ", n); + SERIAL_ECHOLNPGM("Mean Mesh Height: ", p_float_t(mean, 6)); + + const float sigma = SQRT(sum_of_diff_squared / (n + 1)); + SERIAL_ECHOLNPGM("Standard Deviation: ", p_float_t(sigma, 6)); + + if (cflag) + GRID_LOOP(x, y) + if (!isnan(z_values[x][y])) { + z_values[x][y] -= mean + offset; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); + } +} + +/** + * G29 P6 C : Shift Mesh Height by a uniform constant. + */ +void unified_bed_leveling::shift_mesh_height() { + GRID_LOOP(x, y) + if (!isnan(z_values[x][y])) { + z_values[x][y] += param.C_constant; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); + } +} + +#if HAS_BED_PROBE + /** + * G29 P1 T V : Probe Entire Mesh + * Probe all invalidated locations of the mesh that can be reached by the probe. + * This attempts to fill in locations closest to the nozzle's start location first. + */ + void unified_bed_leveling::probe_entire_mesh(const xy_pos_t &nearby, const bool do_ubl_mesh_map, const bool stow_probe, const bool do_furthest) { + probe.deploy(); // Deploy before ui.capture() to allow for PAUSE_BEFORE_DEPLOY_STOW + + TERN_(HAS_MARLINUI_MENU, ui.capture()); + TERN_(EXTENSIBLE_UI, ExtUI::onLevelingStart()); + + save_ubl_active_state_and_disable(); // No bed level correction so only raw data is obtained + grid_count_t count = GRID_MAX_POINTS; + + mesh_index_pair best; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::G29_START)); + do { + if (do_ubl_mesh_map) display_map(param.T_map_type); + + const grid_count_t point_num = (GRID_MAX_POINTS - count) + 1; + SERIAL_ECHOLNPGM("Probing mesh point ", point_num, "/", GRID_MAX_POINTS, "."); + TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/%i"), GET_TEXT_F(MSG_PROBING_POINT), point_num, int(GRID_MAX_POINTS))); + TERN_(HAS_BACKLIGHT_TIMEOUT, ui.refresh_backlight_timeout()); + + #if HAS_MARLINUI_MENU + if (ui.button_pressed()) { + ui.quick_feedback(false); // Preserve button state for click-and-hold + SERIAL_ECHOLNPGM("\nMesh only partially populated.\n"); + ui.wait_for_release(); + ui.quick_feedback(); + ui.release(); + probe.stow(); // Release UI before stow to allow for PAUSE_BEFORE_DEPLOY_STOW + return restore_ubl_active_state(); + } + #endif + + #ifndef HUGE_VALF + #define HUGE_VALF __FLT_MAX__ + #endif + + best = do_furthest // Points with valid data or HUGE_VALF are skipped + ? find_furthest_invalid_mesh_point() + : find_closest_mesh_point_of_type(INVALID, nearby, true); + + if (best.pos.x >= 0) { // mesh point found and is reachable by probe + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::G29_POINT_START)); + #ifdef USE_PROBE_FOR_MESH_REF // adjust the mesh point value + const float measured_z = probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity) - mesh_zero_ref_offset; + #else + const float measured_z = probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity); + #endif + z_values[best.pos.x][best.pos.y] = isnan(measured_z) ? HUGE_VALF : measured_z; // Mark invalid point already probed with HUGE_VALF to omit it in the next loop + #if ENABLED(EXTENSIBLE_UI) + ExtUI::onMeshUpdate(best.pos, ExtUI::G29_POINT_FINISH); + ExtUI::onMeshUpdate(best.pos, measured_z); + #endif + } + SERIAL_FLUSH(); // Prevent host M105 buffer overrun. + + } while (best.pos.x >= 0 && --count); + + GRID_LOOP(x, y) if (z_values[x][y] == HUGE_VALF) z_values[x][y] = NAN; // Restore NAN for HUGE_VALF marks + + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::G29_FINISH)); + + // Release UI during stow to allow for PAUSE_BEFORE_DEPLOY_STOW + TERN_(HAS_MARLINUI_MENU, ui.release()); + probe.stow(); + TERN_(HAS_MARLINUI_MENU, ui.capture()); + + probe.move_z_after_probing(); + + do_blocking_move_to_xy( + constrain(nearby.x - probe.offset_xy.x, MESH_MIN_X, MESH_MAX_X), + constrain(nearby.y - probe.offset_xy.y, MESH_MIN_Y, MESH_MAX_Y) + ); + + restore_ubl_active_state(); + } + +#endif // HAS_BED_PROBE + +void set_message_with_feedback(FSTR_P const fstr) { + #if HAS_MARLINUI_MENU + ui.set_status(fstr); + ui.quick_feedback(); + #else + UNUSED(fstr); + #endif +} + +#if HAS_MARLINUI_MENU + + typedef void (*clickFunc_t)(); + + bool _click_and_hold(const clickFunc_t func=nullptr) { + if (ui.button_pressed()) { + ui.quick_feedback(false); // Preserve button state for click-and-hold + const millis_t nxt = millis() + 1500UL; + while (ui.button_pressed()) { // Loop while the encoder is pressed. Uses hardware flag! + idle(); // idle, of course + if (ELAPSED(millis(), nxt)) { // After 1.5 seconds + ui.quick_feedback(); + if (func) (*func)(); + ui.wait_for_release(); + return true; + } + } + } + serial_delay(15); + return false; + } + + void unified_bed_leveling::move_z_with_encoder(const_float_t multiplier) { + ui.wait_for_release(); + while (!ui.button_pressed()) { + idle(); + gcode.reset_stepper_timeout(); // Keep steppers powered + if (encoder_diff) { + do_blocking_move_to_z(current_position.z + float(encoder_diff) * multiplier); + encoder_diff = 0; + } + } + } + + float unified_bed_leveling::measure_point_with_encoder() { + KEEPALIVE_STATE(PAUSED_FOR_USER); + const float z_step = 0.01f; + move_z_with_encoder(z_step); + return current_position.z; + } + + static void echo_and_take_a_measurement() { SERIAL_ECHOLNPGM(" and take a measurement."); } + + float unified_bed_leveling::measure_business_card_thickness() { + ui.capture(); + save_ubl_active_state_and_disable(); // Disable bed level correction for probing + + do_blocking_move_to( + xyz_pos_t({ + 0.5f * ((MESH_MAX_X) - (MESH_MIN_X)), + 0.5f * ((MESH_MAX_Y) - (MESH_MIN_Y)), + MANUAL_PROBE_START_Z + #ifdef SAFE_BED_LEVELING_START_I + , SAFE_BED_LEVELING_START_I + #endif + #ifdef SAFE_BED_LEVELING_START_J + , SAFE_BED_LEVELING_START_J + #endif + #ifdef SAFE_BED_LEVELING_START_K + , SAFE_BED_LEVELING_START_K + #endif + #ifdef SAFE_BED_LEVELING_START_U + , SAFE_BED_LEVELING_START_U + #endif + #ifdef SAFE_BED_LEVELING_START_V + , SAFE_BED_LEVELING_START_V + #endif + #ifdef SAFE_BED_LEVELING_START_W + , SAFE_BED_LEVELING_START_W + #endif + }) + //, _MIN(planner.settings.max_feedrate_mm_s[X_AXIS], planner.settings.max_feedrate_mm_s[Y_AXIS]) * 0.5f + ); + planner.synchronize(); + + SERIAL_ECHOPGM("Place shim under nozzle"); + LCD_MESSAGE(MSG_UBL_BC_INSERT); + ui.return_to_status(); + echo_and_take_a_measurement(); + + const float z1 = measure_point_with_encoder(); + do_z_clearance_by(SIZE_OF_LITTLE_RAISE); + + SERIAL_ECHOPGM("Remove shim"); + LCD_MESSAGE(MSG_UBL_BC_REMOVE); + echo_and_take_a_measurement(); + + const float z2 = measure_point_with_encoder(); + do_z_clearance_by(Z_CLEARANCE_BETWEEN_PROBES); + + const float thickness = ABS(z1 - z2); + + if (param.V_verbosity > 1) + SERIAL_ECHOLNPGM("Business Card is ", p_float_t(thickness, 4), "mm thick."); + + restore_ubl_active_state(); + + return thickness; + } + + /** + * G29 P2 : Manually Probe Remaining Mesh Points. + * Move to INVALID points and + * NOTE: Blocks the G-code queue and captures Marlin UI during use. + */ + void unified_bed_leveling::manually_probe_remaining_mesh(const xy_pos_t &pos, const_float_t z_clearance, const_float_t thick, const bool do_ubl_mesh_map) { + ui.capture(); + TERN_(EXTENSIBLE_UI, ExtUI::onLevelingStart()); + + save_ubl_active_state_and_disable(); // No bed level correction so only raw data is obtained + do_blocking_move_to_xy_z(current_position, z_clearance); + + ui.return_to_status(); + + mesh_index_pair location; + const xy_int8_t &lpos = location.pos; + do { + location = find_closest_mesh_point_of_type(INVALID, pos); + // It doesn't matter if the probe can't reach the NAN location. This is a manual probe. + if (!location.valid()) continue; + + const xyz_pos_t ppos = { get_mesh_x(lpos.x), get_mesh_y(lpos.y), z_clearance }; + + if (!position_is_reachable(ppos)) break; // SHOULD NOT OCCUR (find_closest_mesh_point only returns reachable points) + + LCD_MESSAGE(MSG_UBL_MOVING_TO_NEXT); + + do_blocking_move_to(ppos); + do_z_clearance(z_clearance); + + KEEPALIVE_STATE(PAUSED_FOR_USER); + ui.capture(); + + if (do_ubl_mesh_map) display_map(param.T_map_type); // Show user where we're probing + + if (parser.seen_test('B')) { + SERIAL_ECHOPGM("Place Shim & Measure"); + LCD_MESSAGE(MSG_UBL_BC_INSERT); + } + else { + SERIAL_ECHOPGM("Measure"); + LCD_MESSAGE(MSG_UBL_BC_INSERT2); + } + + const float z_step = 0.01f; // 0.01mm per encoder tick, occasionally step + move_z_with_encoder(z_step); + + if (_click_and_hold([]{ + SERIAL_ECHOLNPGM("\nMesh only partially populated."); + do_z_clearance(Z_CLEARANCE_DEPLOY_PROBE); + })) return restore_ubl_active_state(); + + // Store the Z position minus the shim height + z_values[lpos.x][lpos.y] = current_position.z - thick; + + // Tell the external UI to update + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, z_values[lpos.x][lpos.y])); + + if (param.V_verbosity > 2) + SERIAL_ECHOLNPGM("Mesh Point Measured at: ", p_float_t(z_values[lpos.x][lpos.y], 6)); + SERIAL_FLUSH(); // Prevent host M105 buffer overrun. + } while (location.valid()); + + if (do_ubl_mesh_map) display_map(param.T_map_type); // show user where we're probing + + restore_ubl_active_state(); + do_blocking_move_to_xy_z(pos, Z_CLEARANCE_DEPLOY_PROBE); + } + + /** + * G29 P4 : Mesh Fine-Tuning. Go to point(s) and adjust values with the LCD. + * NOTE: Blocks the G-code queue and captures Marlin UI during use. + */ + void unified_bed_leveling::fine_tune_mesh(const xy_pos_t &pos, const bool do_ubl_mesh_map) { + if (!parser.seen_test('R')) // fine_tune_mesh() is special. If no repetition count flag is specified + param.R_repetition = 1; // do exactly one mesh location. Otherwise use what the parser decided. + + #if ENABLED(UBL_MESH_EDIT_MOVES_Z) + const float h_offset = parser.seenval('H') ? parser.value_linear_units() : MANUAL_PROBE_START_Z; + if (!WITHIN(h_offset, 0, 10)) { + SERIAL_ECHOLNPGM("Offset out of bounds. (0 to 10mm)\n"); + return; + } + #endif + + mesh_index_pair location; + + if (!position_is_reachable(pos)) { + SERIAL_ECHOLNPGM("(X,Y) outside printable radius."); + return; + } + + save_ubl_active_state_and_disable(); + + LCD_MESSAGE(MSG_UBL_FINE_TUNE_MESH); + ui.capture(); // Take over control of the LCD encoder + + do_blocking_move_to_xy_z(pos, Z_TWEEN_SAFE_CLEARANCE); // Move to the given XY with probe clearance + + MeshFlags done_flags{0}; + const xy_int8_t &lpos = location.pos; + + #if IS_TFTGLCD_PANEL + ui.ubl_mesh_edit_start(0); // Change current screen before calling ui.ubl_plot + safe_delay(50); + #endif + + do { + location = find_closest_mesh_point_of_type(SET_IN_BITMAP, pos, false, &done_flags); + + if (lpos.x < 0) break; // Stop when there are no more reachable points + + done_flags.mark(lpos); // Mark this location as 'adjusted' so a new + // location is used on the next loop + const xyz_pos_t raw = { get_mesh_x(lpos.x), get_mesh_y(lpos.y), Z_TWEEN_SAFE_CLEARANCE }; + + if (!position_is_reachable(raw)) break; // SHOULD NOT OCCUR (find_closest_mesh_point_of_type only returns reachable) + + do_blocking_move_to(raw); // Move the nozzle to the edit point with probe clearance + + TERN_(UBL_MESH_EDIT_MOVES_Z, do_blocking_move_to_z(h_offset)); // Move Z to the given 'H' offset before editing + + KEEPALIVE_STATE(PAUSED_FOR_USER); + + if (do_ubl_mesh_map) display_map(param.T_map_type); // Display the current point + + #if IS_TFTGLCD_PANEL + ui.ubl_plot(lpos.x, lpos.y); // update plot screen + #endif + + ui.refresh(); + + float new_z = z_values[lpos.x][lpos.y]; + if (isnan(new_z)) new_z = 0; // Invalid points begin at 0 + new_z = FLOOR(new_z * 1000) * 0.001f; // Chop off digits after the 1000ths place + + ui.ubl_mesh_edit_start(new_z); + + SET_SOFT_ENDSTOP_LOOSE(true); + + do { + idle_no_sleep(); + new_z = ui.ubl_mesh_value(); + TERN_(UBL_MESH_EDIT_MOVES_Z, do_blocking_move_to_z(h_offset + new_z)); // Move the nozzle as the point is edited + SERIAL_FLUSH(); // Prevent host M105 buffer overrun. + } while (!ui.button_pressed()); + + SET_SOFT_ENDSTOP_LOOSE(false); + + if (!lcd_map_control) ui.return_to_status(); // Just editing a single point? Return to status + + // Button held down? Abort editing + if (_click_and_hold([]{ + ui.return_to_status(); + do_z_clearance(Z_TWEEN_SAFE_CLEARANCE); + set_message_with_feedback(GET_TEXT_F(MSG_EDITING_STOPPED)); + })) break; + + // TODO: Disable leveling here so the Z value becomes the 'native' Z value. + + z_values[lpos.x][lpos.y] = new_z; // Save the updated Z value + + // TODO: Re-enable leveling here so Z is correctly based on the updated mesh. + + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, new_z)); + + serial_delay(20); // No switch noise + ui.refresh(); + + } while (lpos.x >= 0 && --param.R_repetition > 0); + + if (do_ubl_mesh_map) display_map(param.T_map_type); + restore_ubl_active_state(); + + do_blocking_move_to_xy_z(pos, Z_TWEEN_SAFE_CLEARANCE); + + LCD_MESSAGE(MSG_UBL_DONE_EDITING_MESH); + SERIAL_ECHOLNPGM("Done Editing Mesh"); + + if (lcd_map_control) + ui.goto_screen(ubl_map_screen); + else + ui.return_to_status(); + } + +#endif // HAS_MARLINUI_MENU + +/** + * Parse and validate most G29 parameters, store for use by G29 functions. + */ +bool unified_bed_leveling::G29_parse_parameters() { + bool err_flag = false; + + set_message_with_feedback(GET_TEXT_F(MSG_UBL_DOING_G29)); + + param.C_constant = 0; + param.R_repetition = 0; + + if (parser.seen('R')) { + param.R_repetition = parser.has_value() ? parser.value_ushort() : GRID_MAX_POINTS; + NOMORE(param.R_repetition, GRID_MAX_POINTS); + if (param.R_repetition < 1) { + SERIAL_ECHOLNPGM("?(R)epetition count invalid (1+).\n"); + return UBL_ERR; + } + } + + param.V_verbosity = parser.byteval('V'); + if (!WITHIN(param.V_verbosity, 0, 4)) { + SERIAL_ECHOLNPGM("?(V)erbose level implausible (0-4).\n"); + err_flag = true; + } + + if (parser.seen('P')) { + const uint8_t pv = parser.value_byte(); + #if !HAS_BED_PROBE + if (pv == 1) { + SERIAL_ECHOLNPGM("G29 P1 requires a probe.\n"); + err_flag = true; + } + else + #endif + { + param.P_phase = pv; + if (!WITHIN(param.P_phase, 0, 6)) { + SERIAL_ECHOLNPGM("?(P)hase value invalid (0-6).\n"); + err_flag = true; + } + } + } + + if (parser.seen('J')) { + #if HAS_BED_PROBE + param.J_grid_size = parser.value_byte(); + if (param.J_grid_size && !WITHIN(param.J_grid_size, 2, 9)) { + SERIAL_ECHOLNPGM("?Invalid grid size (J) specified (2-9).\n"); + err_flag = true; + } + #else + SERIAL_ECHOLNPGM("G29 J action requires a probe.\n"); + err_flag = true; + #endif + } + + param.XY_seen.x = parser.seenval('X'); + float sx = param.XY_seen.x ? parser.value_float() : current_position.x; + param.XY_seen.y = parser.seenval('Y'); + float sy = param.XY_seen.y ? parser.value_float() : current_position.y; + + if (param.XY_seen.x != param.XY_seen.y) { + SERIAL_ECHOLNPGM("Both X & Y locations must be specified.\n"); + err_flag = true; + } + + // If X or Y are not valid, use center of the bed values + // (for UBL_HILBERT_CURVE default to lower-left corner instead) + if (!COORDINATE_OKAY(sx, X_MIN_BED, X_MAX_BED)) sx = TERN(UBL_HILBERT_CURVE, 0, X_CENTER); + if (!COORDINATE_OKAY(sy, Y_MIN_BED, Y_MAX_BED)) sy = TERN(UBL_HILBERT_CURVE, 0, Y_CENTER); + + if (err_flag) return UBL_ERR; + + param.XY_pos.set(sx, sy); + + /** + * Activate or deactivate UBL + * Note: UBL's G29 restores the state set here when done. + * Leveling is being enabled here with old data, possibly + * none. Error handling should disable for safety... + */ + if (parser.seen_test('A')) { + if (parser.seen_test('D')) { + SERIAL_ECHOLNPGM("?Can't activate and deactivate at the same time.\n"); + return UBL_ERR; + } + set_bed_leveling_enabled(true); + report_state(); + } + else if (parser.seen_test('D')) { + set_bed_leveling_enabled(false); + report_state(); + } + + // Set global 'C' flag and its value + if ((param.C_seen = parser.seen('C'))) + param.C_constant = parser.value_float(); + + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + if (parser.seenval('F')) { + const float fh = parser.value_float(); + if (!WITHIN(fh, 0, 100)) { + SERIAL_ECHOLNPGM("?(F)ade height for Bed Level Correction not plausible.\n"); + return UBL_ERR; + } + set_z_fade_height(fh); + } + #endif + + param.T_map_type = parser.byteval('T'); + if (!WITHIN(param.T_map_type, 0, 2)) { + SERIAL_ECHOLNPGM("Invalid map type.\n"); + return UBL_ERR; + } + return UBL_OK; +} + +static uint8_t ubl_state_at_invocation = 0; + +#if ENABLED(UBL_DEVEL_DEBUGGING) + static uint8_t ubl_state_recursion_chk = 0; +#endif + +void unified_bed_leveling::save_ubl_active_state_and_disable() { + #if ENABLED(UBL_DEVEL_DEBUGGING) + ubl_state_recursion_chk++; + if (ubl_state_recursion_chk != 1) { + SERIAL_ECHOLNPGM("save_ubl_active_state_and_disabled() called multiple times in a row."); + set_message_with_feedback(GET_TEXT_F(MSG_UBL_SAVE_ERROR)); + return; + } + #endif + ubl_state_at_invocation = planner.leveling_active; + set_bed_leveling_enabled(false); +} + +void unified_bed_leveling::restore_ubl_active_state(const bool is_done/*=true*/) { + TERN_(HAS_MARLINUI_MENU, ui.release()); + #if ENABLED(UBL_DEVEL_DEBUGGING) + if (--ubl_state_recursion_chk) { + SERIAL_ECHOLNPGM("restore_ubl_active_state() called too many times."); + set_message_with_feedback(GET_TEXT_F(MSG_UBL_RESTORE_ERROR)); + return; + } + #endif + set_bed_leveling_enabled(ubl_state_at_invocation); + + if (is_done) { + TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone()); + } +} + +mesh_index_pair unified_bed_leveling::find_furthest_invalid_mesh_point() { + + bool found_a_NAN = false, found_a_real = false; + + mesh_index_pair farthest { -1, -1, -99999.99 }; + + GRID_LOOP(i, j) { + if (!isnan(z_values[i][j])) continue; // Skip valid mesh points + + // Skip unreachable points + if (!probe.can_reach(get_mesh_x(i), get_mesh_y(j))) + continue; + + found_a_NAN = true; + + xy_int8_t nearby { -1, -1 }; + float d1, d2 = 99999.9f; + GRID_LOOP(k, l) { + if (isnan(z_values[k][l])) continue; + + found_a_real = true; + + // Add in a random weighting factor that scrambles the probing of the + // last half of the mesh (when every unprobed mesh point is one index + // from a probed location). + + d1 = HYPOT(i - k, j - l) + (1.0f / ((millis() % 47) + 13)); + + if (d1 < d2) { // Invalid mesh point (i,j) is closer to the defined point (k,l) + d2 = d1; + nearby.set(i, j); + } + } + + // + // At this point d2 should have the near defined mesh point to invalid mesh point (i,j) + // + + if (found_a_real && nearby.x >= 0 && d2 > farthest.distance) { + farthest.pos = nearby; // Found an invalid location farther from the defined mesh point + farthest.distance = d2; + } + } // GRID_LOOP + + if (!found_a_real && found_a_NAN) { // if the mesh is totally unpopulated, start the probing + farthest.pos.set((GRID_MAX_POINTS_X) / 2, (GRID_MAX_POINTS_Y) / 2); + farthest.distance = 1; + } + return farthest; +} + +#if ENABLED(UBL_HILBERT_CURVE) + + typedef struct { + MeshPointType type; + MeshFlags *done_flags; + bool probe_relative; + mesh_index_pair closest; + } find_closest_t; + + static bool test_func(uint8_t i, uint8_t j, void *data) { + find_closest_t *d = (find_closest_t*)data; + if ( d->type == CLOSEST || d->type == (isnan(bedlevel.z_values[i][j]) ? INVALID : REAL) + || (d->type == SET_IN_BITMAP && !d->done_flags->marked(i, j)) + ) { + // Found a Mesh Point of the specified type! + const xy_pos_t mpos = { bedlevel.get_mesh_x(i), bedlevel.get_mesh_y(j) }; + + // If using the probe as the reference there are some unreachable locations. + // Also for round beds, there are grid points outside the bed the nozzle can't reach. + // Prune them from the list and ignore them till the next Phase (manual nozzle probing). + + if (!(d->probe_relative ? probe.can_reach(mpos) : position_is_reachable(mpos))) + return false; + d->closest.pos.set(i, j); + return true; + } + return false; + } + +#endif + +mesh_index_pair unified_bed_leveling::find_closest_mesh_point_of_type(const MeshPointType type, const xy_pos_t &pos, const bool probe_relative/*=false*/, MeshFlags *done_flags/*=nullptr*/) { + + #if ENABLED(UBL_HILBERT_CURVE) + + find_closest_t d; + d.type = type; + d.done_flags = done_flags; + d.probe_relative = probe_relative; + d.closest.invalidate(); + hilbert_curve::search_from_closest(pos, test_func, &d); + return d.closest; + + #else + + mesh_index_pair closest; + closest.invalidate(); + closest.distance = -99999.9f; + + // Get the reference position, either nozzle or probe + const xy_pos_t ref = probe_relative ? pos + probe.offset_xy : pos; + + float best_so_far = 99999.99f; + + GRID_LOOP(i, j) { + if ( type == CLOSEST || type == (isnan(z_values[i][j]) ? INVALID : REAL) + || (type == SET_IN_BITMAP && !done_flags->marked(i, j)) + ) { + // Found a Mesh Point of the specified type! + const xy_pos_t mpos = { get_mesh_x(i), get_mesh_y(j) }; + + // If using the probe as the reference there are some unreachable locations. + // Also for round beds, there are grid points outside the bed the nozzle can't reach. + // Prune them from the list and ignore them till the next Phase (manual nozzle probing). + + if (!(probe_relative ? probe.can_reach(mpos) : position_is_reachable(mpos))) + continue; + + // Reachable. Check if it's the best_so_far location to the nozzle. + + const xy_pos_t diff = current_position - mpos; + const float distance = (ref - mpos).magnitude() + diff.magnitude() * 0.1f; + + // factor in the distance from the current location for the normal case + // so the nozzle isn't running all over the bed. + if (distance < best_so_far) { + best_so_far = distance; // Found a closer location with the desired value type. + closest.pos.set(i, j); + closest.distance = best_so_far; + } + } + } // GRID_LOOP + + return closest; + + #endif +} + +/** + * 'Smart Fill': Scan from the outward edges of the mesh towards the center. + * If an invalid location is found, use the next two points (if valid) to + * calculate a 'reasonable' value for the unprobed mesh point. + */ + +bool unified_bed_leveling::smart_fill_one(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir) { + const float v = z_values[x][y]; + if (isnan(v)) { // A NAN... + const int8_t dx = x + xdir, dy = y + ydir; + const float v1 = z_values[dx][dy]; + if (!isnan(v1)) { // ...next to a pair of real values? + const float v2 = z_values[dx + xdir][dy + ydir]; + if (!isnan(v2)) { + z_values[x][y] = v1 < v2 ? v1 : v1 + v1 - v2; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); + return true; + } + } + } + return false; +} + +typedef struct { uint8_t sx, ex, sy, ey; bool yfirst; } smart_fill_info; + +void unified_bed_leveling::smart_fill_mesh() { + static const smart_fill_info + info0 PROGMEM = { 0, GRID_MAX_POINTS_X, 0, (GRID_MAX_POINTS_Y) - 2, false }, // Bottom of the mesh looking up + info1 PROGMEM = { 0, GRID_MAX_POINTS_X, (GRID_MAX_POINTS_Y) - 1, 0, false }, // Top of the mesh looking down + info2 PROGMEM = { 0, (GRID_MAX_POINTS_X) - 2, 0, GRID_MAX_POINTS_Y, true }, // Left side of the mesh looking right + info3 PROGMEM = { (GRID_MAX_POINTS_X) - 1, 0, 0, GRID_MAX_POINTS_Y, true }; // Right side of the mesh looking left + static const smart_fill_info * const info[] PROGMEM = { &info0, &info1, &info2, &info3 }; + + for (uint8_t i = 0; i < COUNT(info); ++i) { + const smart_fill_info *f = (smart_fill_info*)pgm_read_ptr(&info[i]); + const int8_t sx = pgm_read_byte(&f->sx), sy = pgm_read_byte(&f->sy), + ex = pgm_read_byte(&f->ex), ey = pgm_read_byte(&f->ey); + if (pgm_read_byte(&f->yfirst)) { + const int8_t dir = ex > sx ? 1 : -1; + for (uint8_t y = sy; y != ey; ++y) + for (uint8_t x = sx; x != ex; x += dir) + if (smart_fill_one(x, y, dir, 0)) break; + } + else { + const int8_t dir = ey > sy ? 1 : -1; + for (uint8_t x = sx; x != ex; ++x) + for (uint8_t y = sy; y != ey; y += dir) + if (smart_fill_one(x, y, 0, dir)) break; + } + } +} + +#if HAS_BED_PROBE + + //#define VALIDATE_MESH_TILT + + #include "../../../libs/vector_3.h" + + void unified_bed_leveling::tilt_mesh_based_on_probed_grid(const bool do_3_pt_leveling) { + + float measured_z; + bool abort_flag = false; + + struct linear_fit_data lsf_results; + incremental_LSF_reset(&lsf_results); + + if (do_3_pt_leveling) { + xy_float_t points[3]; + probe.get_three_points(points); + + #if ENABLED(UBL_TILT_ON_MESH_POINTS_3POINT) + mesh_index_pair cpos[3]; + for (uint8_t ix = 0; ix < 3; ++ix) { // Convert points to coordinates of mesh points + cpos[ix] = find_closest_mesh_point_of_type(REAL, points[ix], true); + points[ix] = cpos[ix].meshpos(); + } + #endif + + #if ENABLED(VALIDATE_MESH_TILT) + float gotz[3]; // Used for algorithm validation below + #endif + + for (uint8_t i = 0; i < 3; ++i) { + SERIAL_ECHOLNPGM("Tilting mesh (", i + 1, "/3)"); + TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/3"), GET_TEXT_F(MSG_LCD_TILTING_MESH), i + 1)); + + measured_z = probe.probe_at_point(points[i], i < 2 ? PROBE_PT_RAISE : PROBE_PT_LAST_STOW, param.V_verbosity); + if ((abort_flag = isnan(measured_z))) break; + + measured_z -= TERN(UBL_TILT_ON_MESH_POINTS_3POINT, z_values[cpos[i].pos.x][cpos[i].pos.y], get_z_correction(points[i])); + TERN_(VALIDATE_MESH_TILT, gotz[i] = measured_z); + + if (param.V_verbosity > 3) { SERIAL_ECHO_SP(16); SERIAL_ECHOLNPGM("Corrected_Z=", measured_z); } + + incremental_LSF(&lsf_results, points[i], measured_z); + } + + probe.stow(); + probe.move_z_after_probing(); + + if (abort_flag) { + SERIAL_ECHOLNPGM("?Error probing point. Aborting operation."); + return; + } + } + else { // !do_3_pt_leveling + + #ifndef G29J_MESH_TILT_MARGIN + #define G29J_MESH_TILT_MARGIN 0 + #endif + const float x_min = _MAX((X_MIN_POS) + (G29J_MESH_TILT_MARGIN), MESH_MIN_X, probe.min_x()), + x_max = _MIN((X_MAX_POS) - (G29J_MESH_TILT_MARGIN), MESH_MAX_X, probe.max_x()), + y_min = _MAX((Y_MIN_POS) + (G29J_MESH_TILT_MARGIN), MESH_MIN_Y, probe.min_y()), + y_max = _MIN((Y_MAX_POS) - (G29J_MESH_TILT_MARGIN), MESH_MAX_Y, probe.max_y()), + dx = (x_max - x_min) / (param.J_grid_size - 1), + dy = (y_max - y_min) / (param.J_grid_size - 1); + + bool zig_zag = false; + + const uint16_t total_points = sq(param.J_grid_size); + uint16_t point_num = 1; + + for (uint8_t ix = 0; ix < param.J_grid_size; ++ix) { + xy_pos_t rpos; + rpos.x = x_min + ix * dx; + for (uint8_t iy = 0; iy < param.J_grid_size; ++iy) { + rpos.y = y_min + dy * (zig_zag ? param.J_grid_size - 1 - iy : iy); + + #if ENABLED(UBL_TILT_ON_MESH_POINTS) + #if ENABLED(DEBUG_LEVELING_FEATURE) + xy_pos_t oldRpos; + if (DEBUGGING(LEVELING)) oldRpos = rpos; + #endif + mesh_index_pair cpos; + rpos -= probe.offset; + cpos = find_closest_mesh_point_of_type(REAL, rpos, true); + rpos = cpos.meshpos(); + #endif + + SERIAL_ECHOLNPGM("Tilting mesh point ", point_num, "/", total_points, "\n"); + TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/%i"), GET_TEXT_F(MSG_LCD_TILTING_MESH), point_num, total_points)); + + measured_z = probe.probe_at_point(rpos, parser.seen_test('E') ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity); // TODO: Needs error handling + + if ((abort_flag = isnan(measured_z))) break; + + const float zcorr = TERN(UBL_TILT_ON_MESH_POINTS, z_values[cpos.pos.x][cpos.pos.y], get_z_correction(rpos)); + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + #if ENABLED(UBL_TILT_ON_MESH_POINTS) + const xy_pos_t oldLpos = oldRpos.asLogical(); + DEBUG_ECHO(F("Calculated point: ("), p_float_t(oldRpos.x, 7), C(','), p_float_t(oldRpos.y, 7), + F(") logical: ("), p_float_t(oldLpos.x, 7), C(','), p_float_t(oldLpos.y, 7), + F(")\nSelected mesh point: ") + ); + #endif + const xy_pos_t lpos = rpos.asLogical(); + DEBUG_ECHO( C('('), p_float_t(rpos.x, 7), C(','), p_float_t(rpos.y, 7), + F(") logical: ("), p_float_t(lpos.x, 7), C(','), p_float_t(lpos.y, 7), + F(") measured: "), p_float_t(measured_z, 7), + F(" correction: "), p_float_t(zcorr, 7) + ); + } + #endif + + measured_z -= zcorr; + + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM(" final >>>---> ", p_float_t(measured_z, 7)); + + if (param.V_verbosity > 3) { + SERIAL_ECHO_SP(16); + SERIAL_ECHOLNPGM("Corrected_Z=", measured_z); + } + incremental_LSF(&lsf_results, rpos, measured_z); + + point_num++; + } + + if (abort_flag) break; + FLIP(zig_zag); + } + } + probe.stow(); + probe.move_z_after_probing(); + + if (abort_flag || finish_incremental_LSF(&lsf_results)) { + SERIAL_ECHOLNPGM("Could not complete LSF!"); + return; + } + + vector_3 normal = vector_3(lsf_results.A, lsf_results.B, 1).get_normal(); + + if (param.V_verbosity > 2) + SERIAL_ECHOLN(F("bed plane normal = ["), p_float_t(normal.x, 7), C(','), p_float_t(normal.y, 7), C(','), p_float_t(normal.z, 7), C(']')); + + matrix_3x3 rotation = matrix_3x3::create_look_at(vector_3(lsf_results.A, lsf_results.B, 1)); + + GRID_LOOP(i, j) { + float mx = get_mesh_x(i), my = get_mesh_y(j), mz = z_values[i][j]; + + if (DEBUGGING(LEVELING)) { + DEBUG_ECHOLN(F("before rotation = ["), p_float_t(mx, 7), C(','), p_float_t(my, 7), C(','), p_float_t(mz, 7), F("] ---> ")); + DEBUG_DELAY(20); + } + + rotation.apply_rotation_xyz(mx, my, mz); + + if (DEBUGGING(LEVELING)) { + DEBUG_ECHOLN(F("after rotation = ["), p_float_t(mx, 7), C(','), p_float_t(my, 7), C(','), p_float_t(mz, 7), F("] ---> ")); + DEBUG_DELAY(20); + } + + z_values[i][j] = mz - lsf_results.D; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(i, j, z_values[i][j])); + } + + if (DEBUGGING(LEVELING)) { + rotation.debug(F("rotation matrix:\n")); + DEBUG_ECHOLN(F("LSF Results A="), p_float_t(lsf_results.A, 7), F(" B="), p_float_t(lsf_results.B, 7), F(" D="), p_float_t(lsf_results.D, 7)); + DEBUG_DELAY(55); + DEBUG_ECHOLN(F("bed plane normal = ["), p_float_t(normal.x, 7), C(','), p_float_t(normal.y, 7), C(','), p_float_t(normal.z, 7), C(']')); + DEBUG_EOL(); + + /** + * Use the code below to check the validity of the mesh tilting algorithm. + * 3-Point Mesh Tilt uses the same algorithm as grid-based tilting, but only + * three points are used in the calculation. This guarantees that each probed point + * has an exact match when get_z_correction() for that location is calculated. + * The Z error between the probed point locations and the get_z_correction() + * numbers for those locations should be 0. + */ + #if ENABLED(VALIDATE_MESH_TILT) + auto d_from = []{ DEBUG_ECHOPGM("D from "); }; + auto normed = [&](const xy_pos_t &pos, const_float_t zadd) { + return normal.x * pos.x + normal.y * pos.y + zadd; + }; + auto debug_pt = [](const int num, const xy_pos_t &pos, const_float_t zadd) { + d_from(); + DEBUG_ECHOLN(F("Point "), num, C(':'), p_float_t(normed(pos, zadd), 6), F(" Z error = "), p_float_t(zadd - get_z_correction(pos), 6)); + }; + debug_pt(1, probe_pt[0], normal.z * gotz[0]); + debug_pt(2, probe_pt[1], normal.z * gotz[1]); + debug_pt(3, probe_pt[2], normal.z * gotz[2]); + #if ENABLED(Z_SAFE_HOMING) + constexpr xy_float_t safe_xy = { Z_SAFE_HOMING_X_POINT, Z_SAFE_HOMING_Y_POINT }; + d_from(); DEBUG_ECHOLN(F("safe home with Z="), F("0 : "), p_float_t(normed(safe_xy, 0), 6)); + d_from(); DEBUG_ECHOLN(F("safe home with Z="), F("mesh value "), p_float_t(normed(safe_xy, get_z_correction(safe_xy)), 6)); + DEBUG_ECHO(F(" Z error = ("), Z_SAFE_HOMING_X_POINT, C(','), Z_SAFE_HOMING_Y_POINT, F(") = "), p_float_t(get_z_correction(safe_xy), 6)); + #endif + #endif + } // DEBUGGING(LEVELING) + + } + +#endif // HAS_BED_PROBE + +#if ENABLED(UBL_G29_P31) + void unified_bed_leveling::smart_fill_wlsf(const_float_t weight_factor) { + + // For each undefined mesh point, compute a distance-weighted least squares fit + // from all the originally populated mesh points, weighted toward the point + // being extrapolated so that nearby points will have greater influence on + // the point being extrapolated. Then extrapolate the mesh point from WLSF. + + static_assert((GRID_MAX_POINTS_Y) <= 16, "GRID_MAX_POINTS_Y too big"); + uint16_t bitmap[GRID_MAX_POINTS_X] = { 0 }; + struct linear_fit_data lsf_results; + + SERIAL_ECHOPGM("Extrapolating mesh..."); + + const float weight_scaled = weight_factor * _MAX(MESH_X_DIST, MESH_Y_DIST); + + GRID_LOOP(jx, jy) if (!isnan(z_values[jx][jy])) SBI(bitmap[jx], jy); + + xy_pos_t ppos; + for (uint8_t ix = 0; ix < GRID_MAX_POINTS_X; ++ix) { + ppos.x = get_mesh_x(ix); + for (uint8_t iy = 0; iy < GRID_MAX_POINTS_Y; ++iy) { + ppos.y = get_mesh_y(iy); + if (isnan(z_values[ix][iy])) { + // undefined mesh point at (ppos.x,ppos.y), compute weighted LSF from original valid mesh points. + incremental_LSF_reset(&lsf_results); + xy_pos_t rpos; + for (uint8_t jx = 0; jx < GRID_MAX_POINTS_X; ++jx) { + rpos.x = get_mesh_x(jx); + for (uint8_t jy = 0; jy < GRID_MAX_POINTS_Y; ++jy) { + if (TEST(bitmap[jx], jy)) { + rpos.y = get_mesh_y(jy); + const float rz = z_values[jx][jy], + w = 1.0f + weight_scaled / (rpos - ppos).magnitude(); + incremental_WLSF(&lsf_results, rpos, rz, w); + } + } + } + if (finish_incremental_LSF(&lsf_results)) { + SERIAL_ECHOLNPGM(" Insufficient data"); + return; + } + const float ez = -lsf_results.D - lsf_results.A * ppos.x - lsf_results.B * ppos.y; + z_values[ix][iy] = ez; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ix, iy, z_values[ix][iy])); + idle(); // housekeeping + } + } + } + + SERIAL_ECHOLNPGM(" done."); + } +#endif // UBL_G29_P31 + +#if ENABLED(UBL_DEVEL_DEBUGGING) + /** + * Much of the 'What?' command can be eliminated. But until we are fully debugged, it is + * good to have the extra information. Soon... we prune this to just a few items + */ + void unified_bed_leveling::g29_what_command() { + report_state(); + + if (storage_slot == -1) + SERIAL_ECHOLNPGM("No Mesh Loaded."); + else + SERIAL_ECHOLNPGM("Mesh ", storage_slot, " Loaded."); + serial_delay(50); + + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + SERIAL_ECHOLN(F("Fade Height M420 Z"), p_float_t(planner.z_fade_height, 4)); + #endif + + adjust_mesh_to_mean(param.C_seen, param.C_constant); + + #if HAS_BED_PROBE + SERIAL_ECHOLNPGM("Probe Offset M851 Z", p_float_t(probe.offset.z, 7)); + #endif + + SERIAL_ECHOLNPGM("MESH_MIN_X " STRINGIFY(MESH_MIN_X) "=", MESH_MIN_X); serial_delay(50); + SERIAL_ECHOLNPGM("MESH_MIN_Y " STRINGIFY(MESH_MIN_Y) "=", MESH_MIN_Y); serial_delay(50); + SERIAL_ECHOLNPGM("MESH_MAX_X " STRINGIFY(MESH_MAX_X) "=", MESH_MAX_X); serial_delay(50); + SERIAL_ECHOLNPGM("MESH_MAX_Y " STRINGIFY(MESH_MAX_Y) "=", MESH_MAX_Y); serial_delay(50); + SERIAL_ECHOLNPGM("GRID_MAX_POINTS_X ", GRID_MAX_POINTS_X); serial_delay(50); + SERIAL_ECHOLNPGM("GRID_MAX_POINTS_Y ", GRID_MAX_POINTS_Y); serial_delay(50); + SERIAL_ECHOLNPGM("MESH_X_DIST ", MESH_X_DIST); + SERIAL_ECHOLNPGM("MESH_Y_DIST ", MESH_Y_DIST); serial_delay(50); + + SERIAL_ECHOPGM("X-Axis Mesh Points at: "); + for (uint8_t i = 0; i < GRID_MAX_POINTS_X; ++i) { + SERIAL_ECHO(p_float_t(LOGICAL_X_POSITION(get_mesh_x(i)), 3), F(" ")); + serial_delay(25); + } + SERIAL_EOL(); + + SERIAL_ECHOPGM("Y-Axis Mesh Points at: "); + for (uint8_t i = 0; i < GRID_MAX_POINTS_Y; ++i) { + SERIAL_ECHO(p_float_t(LOGICAL_Y_POSITION(get_mesh_y(i)), 3), F(" ")); + serial_delay(25); + } + SERIAL_EOL(); + + #if HAS_KILL + SERIAL_ECHOLNPGM("Kill pin on :", KILL_PIN, " state:", kill_state()); + #endif + + SERIAL_EOL(); + serial_delay(50); + + SERIAL_ECHOLNPGM("ubl_state_at_invocation :", ubl_state_at_invocation, "\nubl_state_recursion_chk :", ubl_state_recursion_chk); + serial_delay(50); + + SERIAL_ECHOLNPGM("Meshes go from ", _hex_word(settings.meshes_start_index()), " to ", _hex_word(settings.meshes_end_index())); + serial_delay(50); + + SERIAL_ECHOLNPGM("sizeof(unified_bed_leveling) : ", sizeof(unified_bed_leveling)); + SERIAL_ECHOLNPGM("z_value[][] size: ", sizeof(z_values)); + serial_delay(25); + + SERIAL_ECHOLNPGM("EEPROM free for UBL: ", _hex_word(settings.meshes_end_index() - settings.meshes_start_index())); + serial_delay(50); + + SERIAL_ECHOLNPGM("EEPROM can hold ", settings.calc_num_meshes(), " meshes.\n"); + serial_delay(25); + + if (!sanity_check()) { + echo_name(); + SERIAL_ECHOLNPGM(" sanity checks passed."); + } + } + + /** + * When we are fully debugged, the EEPROM dump command will get deleted also. But + * right now, it is good to have the extra information. Soon... we prune this. + */ + void unified_bed_leveling::g29_eeprom_dump() { + uint8_t cccc; + + SERIAL_ECHO_MSG("EEPROM Dump:"); + persistentStore.access_start(); + for (uint16_t i = 0; i < persistentStore.capacity(); i += 16) { + if (!(i & 0x3)) idle(); + print_hex_word(i); + SERIAL_ECHOPGM(": "); + for (uint16_t j = 0; j < 16; j++) { + int pos = i + j; + persistentStore.read_data(pos, &cccc, sizeof(uint8_t)); + print_hex_byte(cccc); + SERIAL_CHAR(' '); + } + SERIAL_EOL(); + } + SERIAL_EOL(); + persistentStore.access_finish(); + } + + /** + * When we are fully debugged, this may go away. But there are some valid + * use cases for the users. So we can wait and see what to do with it. + */ + void unified_bed_leveling::g29_compare_current_mesh_to_stored_mesh() { + const int16_t a = settings.calc_num_meshes(); + + if (!a) { + SERIAL_ECHOLNPGM("?EEPROM storage not available."); + return; + } + + if (!parser.has_value() || !WITHIN(parser.value_int(), 0, a - 1)) { + SERIAL_ECHOLNPGM("?Invalid storage slot.\n?Use 0 to ", a - 1); + return; + } + + param.KLS_storage_slot = (int8_t)parser.value_int(); + + float tmp_z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; + settings.load_mesh(param.KLS_storage_slot, &tmp_z_values); + + SERIAL_ECHOLNPGM("Subtracting mesh in slot ", param.KLS_storage_slot, " from current mesh."); + + GRID_LOOP(x, y) { + z_values[x][y] -= tmp_z_values[x][y]; + TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); + } + } + +#endif // UBL_DEVEL_DEBUGGING + +#endif // AUTO_BED_LEVELING_UBL diff --git a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp index e6f93a001b..733aaa8abe 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp +++ b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp @@ -74,6 +74,10 @@ #define SIZE_OF_LITTLE_RAISE 1 #define BIG_RAISE_NOT_NEEDED 0 +#ifdef USE_PROBE_FOR_MESH_REF + float mesh_zero_ref_offset = 0; // declared in gcode.h as external so it can be set in menu_probe_level.cpp +#endif + /** * G29: Unified Bed Leveling by Roxy * @@ -320,14 +324,30 @@ void unified_bed_leveling::G29() { // Check for commands that require the printer to be homed if (may_move) { planner.synchronize(); - #if ALL(DWIN_LCD_PROUI, ZHOME_BEFORE_LEVELING) - save_ubl_active_state_and_disable(); - gcode.process_subcommands_now(F("G28Z")); - restore_ubl_active_state(false); // ...without telling ExtUI "done" - #else - // Send 'N' to force homing before G29 (internal only) - if (axes_should_home() || parser.seen_test('N')) gcode.home_all_axes(); - #endif + + #ifdef USE_PROBE_FOR_MESH_REF + // Send 'N' to force homing before G29 (internal only) + if (axes_should_home() || parser.seen_test('N')){ + gcode.home_all_axes(); + } + else { + gcode.process_subcommands_now(F("G28L0 X Y")); // Home X and Y only + } + // Set the probe trigger height as Z home before leveling + probe.probe_at_point(current_position, PROBE_PT_NONE,0 ,false ,true, Z_PROBE_LOW_POINT, Z_TWEEN_SAFE_CLEARANCE, false); + set_axis_is_at_home(Z_AXIS); + sync_plan_position(); + #else + #if ALL(DWIN_LCD_PROUI, ZHOME_BEFORE_LEVELING) + save_ubl_active_state_and_disable(); + gcode.process_subcommands_now(F("G28Z")); + restore_ubl_active_state(false); // ...without telling ExtUI "done" + #else + // Send 'N' to force homing before G29 (internal only) + if (axes_should_home() || parser.seen_test('N')) gcode.home_all_axes(); + #endif + #endif + probe.use_probing_tool(); #ifdef EVENT_GCODE_BEFORE_G29 @@ -807,7 +827,11 @@ void unified_bed_leveling::shift_mesh_height() { if (best.pos.x >= 0) { // mesh point found and is reachable by probe TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::G29_POINT_START)); - const float measured_z = probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity); + #ifdef USE_PROBE_FOR_MESH_REF // adjust the mesh point value + const float measured_z = probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity) - mesh_zero_ref_offset; + #else + const float measured_z = probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity); + #endif z_values[best.pos.x][best.pos.y] = isnan(measured_z) ? HUGE_VALF : measured_z; // Mark invalid point already probed with HUGE_VALF to omit it in the next loop #if ENABLED(EXTENSIBLE_UI) ExtUI::onMeshUpdate(best.pos, ExtUI::G29_POINT_FINISH); From ab117e419d58e5470a026e7f6623222e8458d678 Mon Sep 17 00:00:00 2001 From: Maker-Paul Date: Thu, 27 Feb 2025 23:52:17 +0000 Subject: [PATCH 6/8] Remove temp files --- Marlin/Modified files/Configuration.h | 3721 -------------------- Marlin/Modified files/G29.cpp | 1069 ------ Marlin/Modified files/Modified files.zip | Bin 93207 -> 0 bytes Marlin/Modified files/gcode.h | 1347 ------- Marlin/Modified files/menu_probe_level.cpp | 423 --- Marlin/Modified files/ubl_G29.cpp | 1898 ---------- 6 files changed, 8458 deletions(-) delete mode 100644 Marlin/Modified files/Configuration.h delete mode 100644 Marlin/Modified files/G29.cpp delete mode 100644 Marlin/Modified files/Modified files.zip delete mode 100644 Marlin/Modified files/gcode.h delete mode 100644 Marlin/Modified files/menu_probe_level.cpp delete mode 100644 Marlin/Modified files/ubl_G29.cpp diff --git a/Marlin/Modified files/Configuration.h b/Marlin/Modified files/Configuration.h deleted file mode 100644 index 27ff2a8889..0000000000 --- a/Marlin/Modified files/Configuration.h +++ /dev/null @@ -1,3721 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * Based on Sprinter and grbl. - * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -#pragma once - -/** - * Configuration.h - * - * Basic settings such as: - * - * - Type of electronics - * - Type of temperature sensor - * - Printer geometry - * - Endstop configuration - * - LCD controller - * - Extra features - * - * Advanced settings can be found in Configuration_adv.h - */ -#define CONFIGURATION_H_VERSION 02010300 - -//=========================================================================== -//============================= Getting Started ============================= -//=========================================================================== - -/** - * Here are some useful links to help get your machine configured and calibrated: - * - * Example Configs: https://github.com/MarlinFirmware/Configurations/branches/all - * - * Průša Calculator: https://blog.prusa3d.com/calculator_3416/ - * - * Calibration Guides: https://reprap.org/wiki/Calibration - * https://reprap.org/wiki/Triffid_Hunter%27s_Calibration_Guide - * https://web.archive.org/web/20220907014303/sites.google.com/site/repraplogphase/calibration-of-your-reprap - * https://youtu.be/wAL9d7FgInk - * https://teachingtechyt.github.io/calibration.html - * - * Calibration Objects: https://www.thingiverse.com/thing:5573 - * https://www.thingiverse.com/thing:1278865 - */ - -// @section info - -// Author info of this build printed to the host during boot and M115 -#define STRING_CONFIG_H_AUTHOR "(none, default config)" // Original author or contributor. -//#define CUSTOM_VERSION_FILE Version.h // Path from the root directory (no quotes) - -// @section machine - -// Choose the name from boards.h that matches your setup -#ifndef MOTHERBOARD - #define MOTHERBOARD BOARD_RAMPS_14_EFB -#endif - -// @section serial - -/** - * Select the serial port on the board to use for communication with the host. - * This allows the connection of wireless adapters (for instance) to non-default port pins. - * Serial port -1 is the USB emulated serial port, if available. - * Note: The first serial port (-1 or 0) will always be used by the Arduino bootloader. - * - * :[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - */ -#define SERIAL_PORT 0 - -/** - * Serial Port Baud Rate - * This is the default communication speed for all serial ports. - * Set the baud rate defaults for additional serial ports below. - * - * 250000 works in most cases, but you might try a lower speed if - * you commonly experience drop-outs during host printing. - * You may try up to 1000000 to speed up SD file transfer. - * - * :[2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000] - */ -#define BAUDRATE 250000 - -//#define BAUD_RATE_GCODE // Enable G-code M575 to set the baud rate - -/** - * Select a secondary serial port on the board to use for communication with the host. - * Currently Ethernet (-2) is only supported on Teensy 4.1 boards. - * :[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - */ -//#define SERIAL_PORT_2 -1 -//#define BAUDRATE_2 250000 // :[2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000] Enable to override BAUDRATE - -/** - * Select a third serial port on the board to use for communication with the host. - * Currently supported for AVR, DUE, SAMD51, LPC1768/9, STM32/STM32F1/HC32, and Teensy 4.x - * :[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - */ -//#define SERIAL_PORT_3 1 -//#define BAUDRATE_3 250000 // :[2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000] Enable to override BAUDRATE - -/** - * Select a serial port to communicate with RS485 protocol - * :[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - */ -//#define RS485_SERIAL_PORT 1 -#ifdef RS485_SERIAL_PORT - //#define M485_PROTOCOL 1 // Check your host for protocol compatibility - //#define RS485_BUS_BUFFER_SIZE 128 -#endif - -// Enable the Bluetooth serial interface on AT90USB devices -//#define BLUETOOTH - -// Name displayed in the LCD "Ready" message and Info menu -//#define CUSTOM_MACHINE_NAME "3D Printer" - -// Printer's unique ID, used by some programs to differentiate between machines. -// Choose your own or use a service like https://www.uuidgenerator.net/version4 -//#define MACHINE_UUID "00000000-0000-0000-0000-000000000000" - -// @section stepper drivers - -/** - * Stepper Drivers - * - * These settings allow Marlin to tune stepper driver timing and enable advanced options for - * stepper drivers that support them. You may also override timing options in Configuration_adv.h. - * - * Use TMC2208/TMC2208_STANDALONE for TMC2225 drivers and TMC2209/TMC2209_STANDALONE for TMC2226 drivers. - * - * Options: A4988, A5984, DRV8825, LV8729, TB6560, TB6600, TMC2100, - * TMC2130, TMC2130_STANDALONE, TMC2160, TMC2160_STANDALONE, - * TMC2208, TMC2208_STANDALONE, TMC2209, TMC2209_STANDALONE, - * TMC2660, TMC2660_STANDALONE, TMC5130, TMC5130_STANDALONE, - * TMC5160, TMC5160_STANDALONE - * :['A4988', 'A5984', 'DRV8825', 'LV8729', 'TB6560', 'TB6600', 'TMC2100', 'TMC2130', 'TMC2130_STANDALONE', 'TMC2160', 'TMC2160_STANDALONE', 'TMC2208', 'TMC2208_STANDALONE', 'TMC2209', 'TMC2209_STANDALONE', 'TMC2660', 'TMC2660_STANDALONE', 'TMC5130', 'TMC5130_STANDALONE', 'TMC5160', 'TMC5160_STANDALONE'] - */ -#define X_DRIVER_TYPE A4988 -#define Y_DRIVER_TYPE A4988 -#define Z_DRIVER_TYPE A4988 -//#define X2_DRIVER_TYPE A4988 -//#define Y2_DRIVER_TYPE A4988 -//#define Z2_DRIVER_TYPE A4988 -//#define Z3_DRIVER_TYPE A4988 -//#define Z4_DRIVER_TYPE A4988 -//#define I_DRIVER_TYPE A4988 -//#define J_DRIVER_TYPE A4988 -//#define K_DRIVER_TYPE A4988 -//#define U_DRIVER_TYPE A4988 -//#define V_DRIVER_TYPE A4988 -//#define W_DRIVER_TYPE A4988 -#define E0_DRIVER_TYPE A4988 -//#define E1_DRIVER_TYPE A4988 -//#define E2_DRIVER_TYPE A4988 -//#define E3_DRIVER_TYPE A4988 -//#define E4_DRIVER_TYPE A4988 -//#define E5_DRIVER_TYPE A4988 -//#define E6_DRIVER_TYPE A4988 -//#define E7_DRIVER_TYPE A4988 - -/** - * Additional Axis Settings - * - * Define AXISn_ROTATES for all axes that rotate or pivot. - * Rotational axis coordinates are expressed in degrees. - * - * AXISn_NAME defines the letter used to refer to the axis in (most) G-code commands. - * By convention the names and roles are typically: - * 'A' : Rotational axis parallel to X - * 'B' : Rotational axis parallel to Y - * 'C' : Rotational axis parallel to Z - * 'U' : Secondary linear axis parallel to X - * 'V' : Secondary linear axis parallel to Y - * 'W' : Secondary linear axis parallel to Z - * - * Regardless of these settings the axes are internally named I, J, K, U, V, W. - */ -#ifdef I_DRIVER_TYPE - #define AXIS4_NAME 'A' // :['A', 'B', 'C', 'U', 'V', 'W'] - #define AXIS4_ROTATES -#endif -#ifdef J_DRIVER_TYPE - #define AXIS5_NAME 'B' // :['B', 'C', 'U', 'V', 'W'] - #define AXIS5_ROTATES -#endif -#ifdef K_DRIVER_TYPE - #define AXIS6_NAME 'C' // :['C', 'U', 'V', 'W'] - #define AXIS6_ROTATES -#endif -#ifdef U_DRIVER_TYPE - #define AXIS7_NAME 'U' // :['U', 'V', 'W'] - //#define AXIS7_ROTATES -#endif -#ifdef V_DRIVER_TYPE - #define AXIS8_NAME 'V' // :['V', 'W'] - //#define AXIS8_ROTATES -#endif -#ifdef W_DRIVER_TYPE - #define AXIS9_NAME 'W' // :['W'] - //#define AXIS9_ROTATES -#endif - -// @section extruder - -// This defines the number of extruders -// :[0, 1, 2, 3, 4, 5, 6, 7, 8] -#define EXTRUDERS 1 - -// Generally expected filament diameter (1.75, 2.85, 3.0, ...). Used for Volumetric, Filament Width Sensor, etc. -#define DEFAULT_NOMINAL_FILAMENT_DIA 1.75 - -// For Cyclops or any "multi-extruder" that shares a single nozzle. -//#define SINGLENOZZLE - -// Save and restore temperature and fan speed on tool-change. -// Set standby for the unselected tool with M104/106/109 T... -#if ENABLED(SINGLENOZZLE) - //#define SINGLENOZZLE_STANDBY_TEMP - //#define SINGLENOZZLE_STANDBY_FAN -#endif - -// A dual extruder that uses a single stepper motor -//#define SWITCHING_EXTRUDER -#if ENABLED(SWITCHING_EXTRUDER) - #define SWITCHING_EXTRUDER_SERVO_NR 0 - #define SWITCHING_EXTRUDER_SERVO_ANGLES { 0, 90 } // Angles for E0, E1[, E2, E3] - #if EXTRUDERS > 3 - #define SWITCHING_EXTRUDER_E23_SERVO_NR 1 - #endif -#endif - -// Switch extruders by bumping the toolhead. Requires EVENT_GCODE_TOOLCHANGE_#. -//#define MECHANICAL_SWITCHING_EXTRUDER - -/** - * A dual-nozzle that uses a servomotor to raise/lower one (or both) of the nozzles. - * Can be combined with SWITCHING_EXTRUDER. - */ -//#define SWITCHING_NOZZLE -#if ENABLED(SWITCHING_NOZZLE) - #define SWITCHING_NOZZLE_SERVO_NR 0 - //#define SWITCHING_NOZZLE_E1_SERVO_NR 1 // If two servos are used, the index of the second - #define SWITCHING_NOZZLE_SERVO_ANGLES { 0, 90 } // A pair of angles for { E0, E1 }. - // For Dual Servo use two pairs: { { lower, raise }, { lower, raise } } - #define SWITCHING_NOZZLE_SERVO_DWELL 2500 // Dwell time to wait for servo to make physical move -#endif - -// Switch nozzles by bumping the toolhead. Requires EVENT_GCODE_TOOLCHANGE_#. -//#define MECHANICAL_SWITCHING_NOZZLE - -/** - * Two separate X-carriages with extruders that connect to a moving part - * via a solenoid docking mechanism. Requires SOL1_PIN and SOL2_PIN. - */ -//#define PARKING_EXTRUDER - -/** - * Two separate X-carriages with extruders that connect to a moving part - * via a magnetic docking mechanism using movements and no solenoid - * - * project : https://www.thingiverse.com/thing:3080893 - * movements : https://youtu.be/0xCEiG9VS3k - * https://youtu.be/Bqbcs0CU2FE - */ -//#define MAGNETIC_PARKING_EXTRUDER - -#if ANY(PARKING_EXTRUDER, MAGNETIC_PARKING_EXTRUDER) - - #define PARKING_EXTRUDER_PARKING_X { -78, 184 } // X positions for parking the extruders - #define PARKING_EXTRUDER_GRAB_DISTANCE 1 // (mm) Distance to move beyond the parking point to grab the extruder - - #if ENABLED(PARKING_EXTRUDER) - - #define PARKING_EXTRUDER_SOLENOIDS_INVERT // If enabled, the solenoid is NOT magnetized with applied voltage - #define PARKING_EXTRUDER_SOLENOIDS_PINS_ACTIVE LOW // LOW or HIGH pin signal energizes the coil - #define PARKING_EXTRUDER_SOLENOIDS_DELAY 250 // (ms) Delay for magnetic field. No delay if 0 or not defined. - //#define MANUAL_SOLENOID_CONTROL // Manual control of docking solenoids with M380 S / M381 - - #elif ENABLED(MAGNETIC_PARKING_EXTRUDER) - - #define MPE_FAST_SPEED 9000 // (mm/min) Speed for travel before last distance point - #define MPE_SLOW_SPEED 4500 // (mm/min) Speed for last distance travel to park and couple - #define MPE_TRAVEL_DISTANCE 10 // (mm) Last distance point - #define MPE_COMPENSATION 0 // Offset Compensation -1 , 0 , 1 (multiplier) only for coupling - - #endif - -#endif - -/** - * Switching Toolhead - * - * Support for swappable and dockable toolheads, such as - * the E3D Tool Changer. Toolheads are locked with a servo. - */ -//#define SWITCHING_TOOLHEAD - -/** - * Magnetic Switching Toolhead - * - * Support swappable and dockable toolheads with a magnetic - * docking mechanism using movement and no servo. - */ -//#define MAGNETIC_SWITCHING_TOOLHEAD - -/** - * Electromagnetic Switching Toolhead - * - * Parking for CoreXY / HBot kinematics. - * Toolheads are parked at one edge and held with an electromagnet. - * Supports more than 2 Toolheads. See https://youtu.be/JolbsAKTKf4 - */ -//#define ELECTROMAGNETIC_SWITCHING_TOOLHEAD - -#if ANY(SWITCHING_TOOLHEAD, MAGNETIC_SWITCHING_TOOLHEAD, ELECTROMAGNETIC_SWITCHING_TOOLHEAD) - #define SWITCHING_TOOLHEAD_Y_POS 235 // (mm) Y position of the toolhead dock - #define SWITCHING_TOOLHEAD_Y_SECURITY 10 // (mm) Security distance Y axis - #define SWITCHING_TOOLHEAD_Y_CLEAR 60 // (mm) Minimum distance from dock for unobstructed X axis - #define SWITCHING_TOOLHEAD_X_POS { 215, 0 } // (mm) X positions for parking the extruders - #if ENABLED(SWITCHING_TOOLHEAD) - #define SWITCHING_TOOLHEAD_SERVO_NR 2 // Index of the servo connector - #define SWITCHING_TOOLHEAD_SERVO_ANGLES { 0, 180 } // (degrees) Angles for Lock, Unlock - #elif ENABLED(MAGNETIC_SWITCHING_TOOLHEAD) - #define SWITCHING_TOOLHEAD_Y_RELEASE 5 // (mm) Security distance Y axis - #define SWITCHING_TOOLHEAD_X_SECURITY { 90, 150 } // (mm) Security distance X axis (T0,T1) - //#define PRIME_BEFORE_REMOVE // Prime the nozzle before release from the dock - #if ENABLED(PRIME_BEFORE_REMOVE) - #define SWITCHING_TOOLHEAD_PRIME_MM 20 // (mm) Extruder prime length - #define SWITCHING_TOOLHEAD_RETRACT_MM 10 // (mm) Retract after priming length - #define SWITCHING_TOOLHEAD_PRIME_FEEDRATE 300 // (mm/min) Extruder prime feedrate - #define SWITCHING_TOOLHEAD_RETRACT_FEEDRATE 2400 // (mm/min) Extruder retract feedrate - #endif - #elif ENABLED(ELECTROMAGNETIC_SWITCHING_TOOLHEAD) - #define SWITCHING_TOOLHEAD_Z_HOP 2 // (mm) Z raise for switching - #endif -#endif - -/** - * "Mixing Extruder" - * - Adds G-codes M163 and M164 to set and "commit" the current mix factors. - * - Extends the stepping routines to move multiple steppers in proportion to the mix. - * - Optional support for Repetier Firmware's 'M164 S' supporting virtual tools. - * - This implementation supports up to two mixing extruders. - * - Enable DIRECT_MIXING_IN_G1 for M165 and mixing in G1 (from Pia Taubert's reference implementation). - */ -//#define MIXING_EXTRUDER -#if ENABLED(MIXING_EXTRUDER) - #define MIXING_STEPPERS 2 // Number of steppers in your mixing extruder - #define MIXING_VIRTUAL_TOOLS 16 // Use the Virtual Tool method with M163 and M164 - //#define DIRECT_MIXING_IN_G1 // Allow ABCDHI mix factors in G1 movement commands - //#define GRADIENT_MIX // Support for gradient mixing with M166 and LCD - //#define MIXING_PRESETS // Assign 8 default V-tool presets for 2 or 3 MIXING_STEPPERS - #if ENABLED(GRADIENT_MIX) - //#define GRADIENT_VTOOL // Add M166 T to use a V-tool index as a Gradient alias - #endif -#endif - -// Offset of the extruders (uncomment if using more than one and relying on firmware to position when changing). -// The offset has to be X=0, Y=0 for the extruder 0 hotend (default extruder). -// For the other hotends it is their distance from the extruder 0 hotend. -//#define HOTEND_OFFSET_X { 0.0, 20.00 } // (mm) relative X-offset for each nozzle -//#define HOTEND_OFFSET_Y { 0.0, 5.00 } // (mm) relative Y-offset for each nozzle -//#define HOTEND_OFFSET_Z { 0.0, 0.00 } // (mm) relative Z-offset for each nozzle - -// @section multi-material - -/** - * Multi-Material Unit - * Set to one of these predefined models: - * - * PRUSA_MMU1 : Průša MMU1 (The "multiplexer" version) - * PRUSA_MMU2 : Průša MMU2 - * PRUSA_MMU2S : Průša MMU2S (Requires MK3S extruder with motion sensor, EXTRUDERS = 5) - * PRUSA_MMU3 : Průša MMU3 (Requires MK3S extruder with motion sensor and MMU firmware version 3.x.x, EXTRUDERS = 5) - * EXTENDABLE_EMU_MMU2 : MMU with configurable number of filaments (ERCF, SMuFF or similar with Průša MMU2 compatible firmware) - * EXTENDABLE_EMU_MMU2S : MMUS with configurable number of filaments (ERCF, SMuFF or similar with Průša MMU2 compatible firmware) - * - * Requires NOZZLE_PARK_FEATURE to park print head in case MMU unit fails. - * See additional options in Configuration_adv.h. - * :["PRUSA_MMU1", "PRUSA_MMU2", "PRUSA_MMU2S", "PRUSA_MMU3", "EXTENDABLE_EMU_MMU2", "EXTENDABLE_EMU_MMU2S"] - */ -//#define MMU_MODEL PRUSA_MMU3 - -// @section psu control - -/** - * Power Supply Control - * - * Enable and connect the power supply to the PS_ON_PIN. - * Specify whether the power supply is active HIGH or active LOW. - */ -//#define PSU_CONTROL -//#define PSU_NAME "Power Supply" - -#if ENABLED(PSU_CONTROL) - //#define MKS_PWC // Using the MKS PWC add-on - //#define PS_OFF_CONFIRM // Confirm dialog when power off - //#define PS_OFF_SOUND // Beep 1s when power off - #define PSU_ACTIVE_STATE LOW // Set 'LOW' for ATX, 'HIGH' for X-Box - - //#define PSU_DEFAULT_OFF // Keep power off until enabled directly with M80 - //#define PSU_POWERUP_DELAY 250 // (ms) Delay for the PSU to warm up to full power - //#define LED_POWEROFF_TIMEOUT 10000 // (ms) Turn off LEDs after power-off, with this amount of delay - - //#define PSU_OFF_REDUNDANT // Second pin for redundant power control - //#define PSU_OFF_REDUNDANT_INVERTED // Redundant pin state is the inverse of PSU_ACTIVE_STATE - - //#define PS_ON1_PIN 6 // Redundant pin required to enable power in combination with PS_ON_PIN - - //#define PS_ON_EDM_PIN 8 // External Device Monitoring pins for external power control relay feedback. Fault on mismatch. - //#define PS_ON1_EDM_PIN 9 - #define PS_EDM_RESPONSE 250 // (ms) Time to allow for relay action - - //#define POWER_OFF_TIMER // Enable M81 D to power off after a delay - //#define POWER_OFF_WAIT_FOR_COOLDOWN // Enable M81 S to power off only after cooldown - - //#define PSU_POWERUP_GCODE "M355 S1" // G-code to run after power-on (e.g., case light on) - //#define PSU_POWEROFF_GCODE "M355 S0" // G-code to run before power-off (e.g., case light off) - - //#define AUTO_POWER_CONTROL // Enable automatic control of the PS_ON pin - #if ENABLED(AUTO_POWER_CONTROL) - #define AUTO_POWER_FANS // Turn on PSU for fans - #define AUTO_POWER_E_FANS // Turn on PSU for E Fans - #define AUTO_POWER_CONTROLLERFAN // Turn on PSU for Controller Fan - #define AUTO_POWER_CHAMBER_FAN // Turn on PSU for Chamber Fan - #define AUTO_POWER_COOLER_FAN // Turn on PSU for Cooler Fan - #define AUTO_POWER_SPINDLE_LASER // Turn on PSU for Spindle/Laser - #define POWER_TIMEOUT 30 // (s) Turn off power if the machine is idle for this duration - //#define POWER_OFF_DELAY 60 // (s) Delay of poweroff after M81 command. Useful to let fans run for extra time. - #endif - #if ANY(AUTO_POWER_CONTROL, POWER_OFF_WAIT_FOR_COOLDOWN) - //#define AUTO_POWER_E_TEMP 50 // (°C) PSU on if any extruder is over this temperature - //#define AUTO_POWER_CHAMBER_TEMP 30 // (°C) PSU on if the chamber is over this temperature - //#define AUTO_POWER_COOLER_TEMP 26 // (°C) PSU on if the cooler is over this temperature - #endif -#endif - -//=========================================================================== -//============================= Thermal Settings ============================ -//=========================================================================== -// @section temperature - -/** - * Temperature Sensors: - * - * NORMAL IS 4.7kΩ PULLUP! Hotend sensors can use 1kΩ pullup with correct resistor and table. - * - * ================================================================ - * Analog Thermistors - 4.7kΩ pullup - Normal - * ================================================================ - * 1 : 100kΩ EPCOS - Best choice for EPCOS thermistors - * 331 : 100kΩ Same as #1, but 3.3V scaled for MEGA - * 332 : 100kΩ Same as #1, but 3.3V scaled for DUE - * 2 : 200kΩ ATC Semitec 204GT-2 - * 202 : 200kΩ Copymaster 3D - * 3 : ???Ω Mendel-parts thermistor - * 4 : 10kΩ Generic Thermistor !! DO NOT use for a hotend - it gives bad resolution at high temp. !! - * 5 : 100kΩ ATC Semitec 104GT-2/104NT-4-R025H42G - Used in ParCan, J-Head, and E3D, SliceEngineering 300°C - * 501 : 100kΩ Zonestar - Tronxy X3A - * 502 : 100kΩ Zonestar - used by hot bed in Zonestar Průša P802M - * 503 : 100kΩ Zonestar (Z8XM2) Heated Bed thermistor - * 504 : 100kΩ Zonestar P802QR2 (Part# QWG-104F-B3950) Hotend Thermistor - * 505 : 100kΩ Zonestar P802QR2 (Part# QWG-104F-3950) Bed Thermistor - * 512 : 100kΩ RPW-Ultra hotend - * 6 : 100kΩ EPCOS - Not as accurate as table #1 (created using a fluke thermocouple) - * 7 : 100kΩ Honeywell 135-104LAG-J01 - * 71 : 100kΩ Honeywell 135-104LAF-J01 - * 8 : 100kΩ Vishay 0603 SMD NTCS0603E3104FXT - * 9 : 100kΩ GE Sensing AL03006-58.2K-97-G1 - * 10 : 100kΩ RS PRO 198-961 - * 11 : 100kΩ Keenovo AC silicone mats, most Wanhao i3 machines - beta 3950, 1% - * 12 : 100kΩ Vishay 0603 SMD NTCS0603E3104FXT (#8) - calibrated for Makibox hot bed - * 13 : 100kΩ Hisens up to 300°C - for "Simple ONE" & "All In ONE" hotend - beta 3950, 1% - * 14 : 100kΩ (R25), 4092K (beta25), 4.7kΩ pull-up, bed thermistor as used in Ender-5 S1 - * 15 : 100kΩ Calibrated for JGAurora A5 hotend - * 17 : 100kΩ Dagoma NTC white thermistor - * 18 : 200kΩ ATC Semitec 204GT-2 Dagoma.Fr - MKS_Base_DKU001327 - * 22 : 100kΩ GTM32 Pro vB - hotend - 4.7kΩ pullup to 3.3V and 220Ω to analog input - * 23 : 100kΩ GTM32 Pro vB - bed - 4.7kΩ pullup to 3.3v and 220Ω to analog input - * 30 : 100kΩ Kis3d Silicone heating mat 200W/300W with 6mm precision cast plate (EN AW 5083) NTC100K - beta 3950 - * 60 : 100kΩ Maker's Tool Works Kapton Bed Thermistor - beta 3950 - * 61 : 100kΩ Formbot/Vivedino 350°C Thermistor - beta 3950 - * 66 : 4.7MΩ Dyze Design / Trianglelab T-D500 500°C High Temperature Thermistor - * 67 : 500kΩ SliceEngineering 450°C Thermistor - * 68 : PT100 Smplifier board from Dyze Design - * 70 : 100kΩ bq Hephestos 2 - * 75 : 100kΩ Generic Silicon Heat Pad with NTC100K MGB18-104F39050L32 - * 666 : 200kΩ Einstart S custom thermistor with 10k pullup. - * 2000 : 100kΩ Ultimachine Rambo TDK NTCG104LH104KT1 NTC100K motherboard Thermistor - * - * ================================================================ - * Analog Thermistors - 1kΩ pullup - * Atypical, and requires changing out the 4.7kΩ pullup for 1kΩ. - * (but gives greater accuracy and more stable PID) - * ================================================================ - * 51 : 100kΩ EPCOS (1kΩ pullup) - * 52 : 200kΩ ATC Semitec 204GT-2 (1kΩ pullup) - * 55 : 100kΩ ATC Semitec 104GT-2 - Used in ParCan & J-Head (1kΩ pullup) - * - * ================================================================ - * Analog Thermistors - 10kΩ pullup - Atypical - * ================================================================ - * 99 : 100kΩ Found on some Wanhao i3 machines with a 10kΩ pull-up resistor - * - * ================================================================ - * Analog RTDs (Pt100/Pt1000) - * ================================================================ - * 110 : Pt100 with 1kΩ pullup (atypical) - * 147 : Pt100 with 4.7kΩ pullup - * 1010 : Pt1000 with 1kΩ pullup (atypical) - * 1022 : Pt1000 with 2.2kΩ pullup - * 1047 : Pt1000 with 4.7kΩ pullup (E3D) - * 20 : Pt100 with circuit in the Ultimainboard V2.x with mainboard ADC reference voltage = INA826 amplifier-board supply voltage. - * NOTE: (1) Must use an ADC input with no pullup. (2) Some INA826 amplifiers are unreliable at 3.3V so consider using sensor 147, 110, or 21. - * 21 : Pt100 with circuit in the Ultimainboard V2.x with 3.3v ADC reference voltage (STM32, LPC176x....) and 5V INA826 amplifier board supply. - * NOTE: ADC pins are not 5V tolerant. Not recommended because it's possible to damage the CPU by going over 500°C. - * 201 : Pt100 with circuit in Overlord, similar to Ultimainboard V2.x - * - * ================================================================ - * SPI RTD/Thermocouple Boards - * ================================================================ - * -5 : MAX31865 with Pt100/Pt1000, 2, 3, or 4-wire (only for sensors 0-2 and bed) - * NOTE: You must uncomment/set the MAX31865_*_OHMS_n defines below. - * -3 : MAX31855 with Thermocouple, -200°C to +700°C (only for sensors 0-2 and bed) - * -2 : MAX6675 with Thermocouple, 0°C to +700°C (only for sensors 0-2 and bed) - * - * NOTE: Ensure TEMP_n_CS_PIN is set in your pins file for each TEMP_SENSOR_n using an SPI Thermocouple. By default, - * Hardware SPI on the default serial bus is used. If you have also set TEMP_n_SCK_PIN and TEMP_n_MISO_PIN, - * Software SPI will be used on those ports instead. You can force Hardware SPI on the default bus in the - * Configuration_adv.h file. At this time, separate Hardware SPI buses for sensors are not supported. - * - * ================================================================ - * Analog Thermocouple Boards - * ================================================================ - * -4 : AD8495 with Thermocouple - * -1 : AD595 with Thermocouple - * - * ================================================================ - * SoC internal sensor - * ================================================================ - * 100 : SoC internal sensor - * - * ================================================================ - * Custom/Dummy/Other Thermal Sensors - * ================================================================ - * 0 : not used - * 1000 : Custom - Specify parameters in Configuration_adv.h - * - * !!! Use these for Testing or Development purposes. NEVER for production machine. !!! - * 998 : Dummy Table that ALWAYS reads 25°C or the temperature defined below. - * 999 : Dummy Table that ALWAYS reads 100°C or the temperature defined below. - */ -#define TEMP_SENSOR_0 1 -#define TEMP_SENSOR_1 0 -#define TEMP_SENSOR_2 0 -#define TEMP_SENSOR_3 0 -#define TEMP_SENSOR_4 0 -#define TEMP_SENSOR_5 0 -#define TEMP_SENSOR_6 0 -#define TEMP_SENSOR_7 0 -#define TEMP_SENSOR_BED 1 -#define TEMP_SENSOR_PROBE 0 -#define TEMP_SENSOR_CHAMBER 0 -#define TEMP_SENSOR_COOLER 0 -#define TEMP_SENSOR_BOARD 0 -#define TEMP_SENSOR_SOC 0 -#define TEMP_SENSOR_REDUNDANT 0 - -// Dummy thermistor constant temperature readings, for use with 998 and 999 -#define DUMMY_THERMISTOR_998_VALUE 25 -#define DUMMY_THERMISTOR_999_VALUE 100 - -// Resistor values when using MAX31865 sensors (-5) on TEMP_SENSOR_0 / 1 -#if TEMP_SENSOR_IS_MAX_TC(0) - #define MAX31865_SENSOR_OHMS_0 100 // (Ω) Typically 100 or 1000 (PT100 or PT1000) - #define MAX31865_CALIBRATION_OHMS_0 430 // (Ω) Typically 430 for Adafruit PT100; 4300 for Adafruit PT1000 -#endif -#if TEMP_SENSOR_IS_MAX_TC(1) - #define MAX31865_SENSOR_OHMS_1 100 - #define MAX31865_CALIBRATION_OHMS_1 430 -#endif -#if TEMP_SENSOR_IS_MAX_TC(2) - #define MAX31865_SENSOR_OHMS_2 100 - #define MAX31865_CALIBRATION_OHMS_2 430 -#endif -#if TEMP_SENSOR_IS_MAX_TC(BED) - #define MAX31865_SENSOR_OHMS_BED 100 - #define MAX31865_CALIBRATION_OHMS_BED 430 -#endif - -#if HAS_E_TEMP_SENSOR - #define TEMP_RESIDENCY_TIME 10 // (seconds) Time to wait for hotend to "settle" in M109 - #define TEMP_WINDOW 1 // (°C) Temperature proximity for the "temperature reached" timer - #define TEMP_HYSTERESIS 3 // (°C) Temperature proximity considered "close enough" to the target -#endif - -#if TEMP_SENSOR_BED - #define TEMP_BED_RESIDENCY_TIME 10 // (seconds) Time to wait for bed to "settle" in M190 - #define TEMP_BED_WINDOW 1 // (°C) Temperature proximity for the "temperature reached" timer - #define TEMP_BED_HYSTERESIS 3 // (°C) Temperature proximity considered "close enough" to the target -#endif - -#if TEMP_SENSOR_CHAMBER - #define TEMP_CHAMBER_RESIDENCY_TIME 10 // (seconds) Time to wait for chamber to "settle" in M191 - #define TEMP_CHAMBER_WINDOW 1 // (°C) Temperature proximity for the "temperature reached" timer - #define TEMP_CHAMBER_HYSTERESIS 3 // (°C) Temperature proximity considered "close enough" to the target -#endif - -/** - * Redundant Temperature Sensor (TEMP_SENSOR_REDUNDANT) - * - * Use a temp sensor as a redundant sensor for another reading. Select an unused temperature sensor, and another - * sensor you'd like it to be redundant for. If the two thermistors differ by TEMP_SENSOR_REDUNDANT_MAX_DIFF (°C), - * the print will be aborted. Whichever sensor is selected will have its normal functions disabled; i.e. selecting - * the Bed sensor (-1) will disable bed heating/monitoring. - * - * For selecting source/target use: COOLER, PROBE, BOARD, CHAMBER, BED, E0, E1, E2, E3, E4, E5, E6, E7 - */ -#if TEMP_SENSOR_REDUNDANT - #define TEMP_SENSOR_REDUNDANT_SOURCE E1 // The sensor that will provide the redundant reading. - #define TEMP_SENSOR_REDUNDANT_TARGET E0 // The sensor that we are providing a redundant reading for. - #define TEMP_SENSOR_REDUNDANT_MAX_DIFF 10 // (°C) Temperature difference that will trigger a print abort. -#endif - -// Below this temperature the heater will be switched off -// because it probably indicates a broken thermistor wire. -#define HEATER_0_MINTEMP 5 -#define HEATER_1_MINTEMP 5 -#define HEATER_2_MINTEMP 5 -#define HEATER_3_MINTEMP 5 -#define HEATER_4_MINTEMP 5 -#define HEATER_5_MINTEMP 5 -#define HEATER_6_MINTEMP 5 -#define HEATER_7_MINTEMP 5 -#define BED_MINTEMP 5 -#define CHAMBER_MINTEMP 5 - -// Above this temperature the heater will be switched off. -// This can protect components from overheating, but NOT from shorts and failures. -// (Use MINTEMP for thermistor short/failure protection.) -#define HEATER_0_MAXTEMP 275 -#define HEATER_1_MAXTEMP 275 -#define HEATER_2_MAXTEMP 275 -#define HEATER_3_MAXTEMP 275 -#define HEATER_4_MAXTEMP 275 -#define HEATER_5_MAXTEMP 275 -#define HEATER_6_MAXTEMP 275 -#define HEATER_7_MAXTEMP 275 -#define BED_MAXTEMP 150 -#define CHAMBER_MAXTEMP 60 - -/** - * Thermal Overshoot - * During heatup (and printing) the temperature can often "overshoot" the target by many degrees - * (especially before PID tuning). Setting the target temperature too close to MAXTEMP guarantees - * a MAXTEMP shutdown! Use these values to forbid temperatures being set too close to MAXTEMP. - */ -#define HOTEND_OVERSHOOT 15 // (°C) Forbid temperatures over MAXTEMP - OVERSHOOT -#define BED_OVERSHOOT 10 // (°C) Forbid temperatures over MAXTEMP - OVERSHOOT -#define COOLER_OVERSHOOT 2 // (°C) Forbid temperatures closer than OVERSHOOT - -//=========================================================================== -//============================= PID Settings ================================ -//=========================================================================== - -// @section hotend temp - -/** - * Temperature Control - * - * (NONE) : Bang-bang heating - * PIDTEMP : PID temperature control (~4.1K) - * MPCTEMP : Predictive Model temperature control. (~1.8K without auto-tune) - */ -#define PIDTEMP // See the PID Tuning Guide at https://reprap.org/wiki/PID_Tuning -//#define MPCTEMP // See https://marlinfw.org/docs/features/model_predictive_control.html - -#define PID_MAX 255 // Limit hotend current while PID is active (see PID_FUNCTIONAL_RANGE below); 255=full current -#define PID_K1 0.95 // Smoothing factor within any PID loop - -#if ENABLED(PIDTEMP) - //#define PID_DEBUG // Print PID debug data to the serial port. Use 'M303 D' to toggle activation. - //#define PID_PARAMS_PER_HOTEND // Use separate PID parameters for each extruder (useful for mismatched extruders) - // Set/get with G-code: M301 E[extruder number, 0-2] - - #if ENABLED(PID_PARAMS_PER_HOTEND) - // Specify up to one value per hotend here, according to your setup. - // If there are fewer values, the last one applies to the remaining hotends. - #define DEFAULT_Kp_LIST { 22.20, 22.20 } - #define DEFAULT_Ki_LIST { 1.08, 1.08 } - #define DEFAULT_Kd_LIST { 114.00, 114.00 } - #else - #define DEFAULT_Kp 22.20 - #define DEFAULT_Ki 1.08 - #define DEFAULT_Kd 114.00 - #endif -#else - #define BANG_MAX 255 // Limit hotend current while in bang-bang mode; 255=full current -#endif - -/** - * Model Predictive Control for hotend - * - * Use a physical model of the hotend to control temperature. When configured correctly this gives - * better responsiveness and stability than PID and removes the need for PID_EXTRUSION_SCALING - * and PID_FAN_SCALING. Enable MPC_AUTOTUNE and use M306 T to autotune the model. - * @section mpc temp - */ -#if ENABLED(MPCTEMP) - #define MPC_AUTOTUNE // Include a method to do MPC auto-tuning (~6.3K bytes of flash) - #if ENABLED(MPC_AUTOTUNE) - //#define MPC_AUTOTUNE_DEBUG // Enable MPC debug logging (~870 bytes of flash) - #endif - //#define MPC_EDIT_MENU // Add MPC editing to the "Advanced Settings" menu. (~1.3K bytes of flash) - //#define MPC_AUTOTUNE_MENU // Add MPC auto-tuning to the "Advanced Settings" menu. (~350 bytes of flash) - - #define MPC_MAX 255 // (0..255) Current to nozzle while MPC is active. - #define MPC_HEATER_POWER { 40.0f } // (W) Heat cartridge powers. - - #define MPC_INCLUDE_FAN // Model the fan speed? - - // Measured physical constants from M306 - #define MPC_BLOCK_HEAT_CAPACITY { 16.7f } // (J/K) Heat block heat capacities. - #define MPC_SENSOR_RESPONSIVENESS { 0.22f } // (K/s per ∆K) Rate of change of sensor temperature from heat block. - #define MPC_AMBIENT_XFER_COEFF { 0.068f } // (W/K) Heat transfer coefficients from heat block to room air with fan off. - #if ENABLED(MPC_INCLUDE_FAN) - #define MPC_AMBIENT_XFER_COEFF_FAN255 { 0.097f } // (W/K) Heat transfer coefficients from heat block to room air with fan on full. - #endif - - // For one fan and multiple hotends MPC needs to know how to apply the fan cooling effect. - #if ENABLED(MPC_INCLUDE_FAN) - //#define MPC_FAN_0_ALL_HOTENDS - //#define MPC_FAN_0_ACTIVE_HOTEND - #endif - - // Filament Heat Capacity (joules/kelvin/mm) - // Set at runtime with M306 H - #define FILAMENT_HEAT_CAPACITY_PERMM { 5.6e-3f } // 0.0056 J/K/mm for 1.75mm PLA (0.0149 J/K/mm for 2.85mm PLA). - // 0.0036 J/K/mm for 1.75mm PETG (0.0094 J/K/mm for 2.85mm PETG). - // 0.00515 J/K/mm for 1.75mm ABS (0.0137 J/K/mm for 2.85mm ABS). - // 0.00522 J/K/mm for 1.75mm Nylon (0.0138 J/K/mm for 2.85mm Nylon). - - // Advanced options - #define MPC_SMOOTHING_FACTOR 0.5f // (0.0...1.0) Noisy temperature sensors may need a lower value for stabilization. - #define MPC_MIN_AMBIENT_CHANGE 1.0f // (K/s) Modeled ambient temperature rate of change, when correcting model inaccuracies. - #define MPC_STEADYSTATE 0.5f // (K/s) Temperature change rate for steady state logic to be enforced. - - #define MPC_TUNING_POS { X_CENTER, Y_CENTER, 1.0f } // (mm) M306 Autotuning position, ideally bed center at first layer height. - #define MPC_TUNING_END_Z 10.0f // (mm) M306 Autotuning final Z position. -#endif - -//=========================================================================== -//====================== PID > Bed Temperature Control ====================== -//=========================================================================== - -// @section bed temp - -/** - * Max Bed Power - * Applies to all forms of bed control (PID, bang-bang, and bang-bang with hysteresis). - * When set to any value below 255, enables a form of PWM to the bed that acts like a divider - * so don't use it unless you are OK with PWM on your bed. (See the comment on enabling PIDTEMPBED) - */ -#define MAX_BED_POWER 255 // limits duty cycle to bed; 255=full current - -/** - * PID Bed Heating - * - * The PID frequency will be the same as the extruder PWM. - * If PID_dT is the default, and correct for the hardware/configuration, that means 7.689Hz, - * which is fine for driving a square wave into a resistive load and does not significantly - * impact FET heating. This also works fine on a Fotek SSR-10DA Solid State Relay into a 250W - * heater. If your configuration is significantly different than this and you don't understand - * the issues involved, don't use bed PID until someone else verifies that your hardware works. - * - * With this option disabled, bang-bang will be used. BED_LIMIT_SWITCHING enables hysteresis. - */ -//#define PIDTEMPBED - -#if ENABLED(PIDTEMPBED) - //#define MIN_BED_POWER 0 - //#define PID_BED_DEBUG // Print Bed PID debug data to the serial port. - - // 120V 250W silicone heater into 4mm borosilicate (MendelMax 1.5+) - // from FOPDT model - kp=.39 Tp=405 Tdead=66, Tc set to 79.2, aggressive factor of .15 (vs .1, 1, 10) - #define DEFAULT_bedKp 10.00 - #define DEFAULT_bedKi .023 - #define DEFAULT_bedKd 305.4 - - // FIND YOUR OWN: "M303 E-1 C8 S90" to run autotune on the bed at 90 degreesC for 8 cycles. -#else - //#define BED_LIMIT_SWITCHING // Keep the bed temperature within BED_HYSTERESIS of the target -#endif - -/** - * Peltier Bed - Heating and Cooling - * - * A Peltier device transfers heat from one side to the other in proportion to the amount of - * current flowing through the device and the direction of current flow. So the same device - * can both heat and cool. - * - * When "cooling" in addition to rejecting the heat transferred from the hot side to the cold - * side, the dissipated power (voltage * current) must also be rejected. Be sure to set up a - * fan that can be powered in sync with the Peltier unit. - * - * This feature is only set up to run in bang-bang mode because Peltiers don't handle PWM - * well without filter circuitry. - * - * Since existing 3D printers are made to handle relatively high current for the heated bed, - * we can use the heated bed power pins to control the Peltier power using the same G-codes - * as the heated bed (M140, M190, etc.). - * - * A second GPIO pin is required to control current direction. - * Two configurations are possible: Relay and H-Bridge - * - * (At this time only relay is supported. H-bridge requires 4 MOS switches configured in H-Bridge.) - * - * Power is handled by the bang-bang control loop: 0 or 255. - * Cooling applications are more common than heating, so the pin states are commonly: - * LOW = Heating = Relay Energized - * HIGH = Cooling = Relay in "Normal" state - */ -//#define PELTIER_BED -#if ENABLED(PELTIER_BED) - #define PELTIER_DIR_PIN -1 // Relay control pin for Peltier - #define PELTIER_DIR_HEAT_STATE LOW // The relay pin state that causes the Peltier to heat -#endif - -// Add 'M190 R T' for more gradual M190 R bed cooling. -//#define BED_ANNEALING_GCODE - -//=========================================================================== -//==================== PID > Chamber Temperature Control ==================== -//=========================================================================== - -/** - * PID Chamber Heating - * - * If this option is enabled set PID constants below. - * If this option is disabled, bang-bang will be used and CHAMBER_LIMIT_SWITCHING will enable - * hysteresis. - * - * The PID frequency will be the same as the extruder PWM. - * If PID_dT is the default, and correct for the hardware/configuration, that means 7.689Hz, - * which is fine for driving a square wave into a resistive load and does not significantly - * impact FET heating. This also works fine on a Fotek SSR-10DA Solid State Relay into a 200W - * heater. If your configuration is significantly different than this and you don't understand - * the issues involved, don't use chamber PID until someone else verifies that your hardware works. - * @section chamber temp - */ -//#define PIDTEMPCHAMBER -//#define CHAMBER_LIMIT_SWITCHING - -/** - * Max Chamber Power - * Applies to all forms of chamber control (PID, bang-bang, and bang-bang with hysteresis). - * When set to any value below 255, enables a form of PWM to the chamber heater that acts like a divider - * so don't use it unless you are OK with PWM on your heater. (See the comment on enabling PIDTEMPCHAMBER) - */ -#define MAX_CHAMBER_POWER 255 // limits duty cycle to chamber heater; 255=full current - -#if ENABLED(PIDTEMPCHAMBER) - #define MIN_CHAMBER_POWER 0 - //#define PID_CHAMBER_DEBUG // Print Chamber PID debug data to the serial port. - - // Lasko "MyHeat Personal Heater" (200w) modified with a Fotek SSR-10DA to control only the heating element - // and placed inside the small Creality printer enclosure tent. - // - #define DEFAULT_chamberKp 37.04 - #define DEFAULT_chamberKi 1.40 - #define DEFAULT_chamberKd 655.17 - // M309 P37.04 I1.04 D655.17 - - // FIND YOUR OWN: "M303 E-2 C8 S50" to run autotune on the chamber at 50 degreesC for 8 cycles. -#endif // PIDTEMPCHAMBER - -// @section pid temp - -#if ANY(PIDTEMP, PIDTEMPBED, PIDTEMPCHAMBER) - //#define PID_OPENLOOP // Puts PID in open loop. M104/M140 sets the output power from 0 to PID_MAX - //#define SLOW_PWM_HEATERS // PWM with very low frequency (roughly 0.125Hz=8s) and minimum state time of approximately 1s useful for heaters driven by a relay - #define PID_FUNCTIONAL_RANGE 10 // If the temperature difference between the target temperature and the actual temperature - // is more than PID_FUNCTIONAL_RANGE then the PID will be shut off and the heater will be set to min/max. - - //#define PID_EDIT_MENU // Add PID editing to the "Advanced Settings" menu. (~700 bytes of flash) - //#define PID_AUTOTUNE_MENU // Add PID auto-tuning to the "Advanced Settings" menu. (~250 bytes of flash) -#endif - -// @section safety - -/** - * Prevent extrusion if the temperature is below EXTRUDE_MINTEMP. - * Add M302 to set the minimum extrusion temperature and/or turn - * cold extrusion prevention on and off. - * - * *** IT IS HIGHLY RECOMMENDED TO LEAVE THIS OPTION ENABLED! *** - */ -#define PREVENT_COLD_EXTRUSION -#define EXTRUDE_MINTEMP 170 - -/** - * Prevent a single extrusion longer than EXTRUDE_MAXLENGTH. - * Note: For Bowden Extruders make this large enough to allow load/unload. - */ -#define PREVENT_LENGTHY_EXTRUDE -#define EXTRUDE_MAXLENGTH 200 - -//=========================================================================== -//======================== Thermal Runaway Protection ======================= -//=========================================================================== - -/** - * Thermal Protection provides additional protection to your printer from damage - * and fire. Marlin always includes safe min and max temperature ranges which - * protect against a broken or disconnected thermistor wire. - * - * The issue: If a thermistor falls out, it will report the much lower - * temperature of the air in the room, and the the firmware will keep - * the heater on. - * - * If you get "Thermal Runaway" or "Heating failed" errors the - * details can be tuned in Configuration_adv.h - */ - -#define THERMAL_PROTECTION_HOTENDS // Enable thermal protection for all extruders -#define THERMAL_PROTECTION_BED // Enable thermal protection for the heated bed -#define THERMAL_PROTECTION_CHAMBER // Enable thermal protection for the heated chamber -#define THERMAL_PROTECTION_COOLER // Enable thermal protection for the laser cooling - -//=========================================================================== -//============================= Mechanical Settings ========================= -//=========================================================================== - -// @section kinematics - -// Enable one of the options below for CoreXY, CoreXZ, or CoreYZ kinematics, -// either in the usual order or reversed -//#define COREXY -//#define COREXZ -//#define COREYZ -//#define COREYX -//#define COREZX -//#define COREZY - -// -// MarkForged Kinematics -// See https://reprap.org/forum/read.php?152,504042 -// -//#define MARKFORGED_XY -//#define MARKFORGED_YX -#if ANY(MARKFORGED_XY, MARKFORGED_YX) - //#define MARKFORGED_INVERSE // Enable for an inverted Markforged kinematics belt path -#endif - -// Enable for a belt style printer with endless "Z" motion -//#define BELTPRINTER - -// Articulated robot (arm). Joints are directly mapped to axes with no kinematics. -//#define ARTICULATED_ROBOT_ARM - -// For a hot wire cutter with parallel horizontal axes (X, I) where the heights of the two wire -// ends are controlled by parallel axes (Y, J). Joints are directly mapped to axes (no kinematics). -//#define FOAMCUTTER_XYUV - -// @section polargraph - -// Enable for Polargraph Kinematics -//#define POLARGRAPH -#if ENABLED(POLARGRAPH) - #define POLARGRAPH_MAX_BELT_LEN 1035.0 // (mm) Belt length at full extension. Override with M665 H. - #define DEFAULT_SEGMENTS_PER_SECOND 5 // Move segmentation based on duration - #define PEN_UP_DOWN_MENU // Add "Pen Up" and "Pen Down" to the MarlinUI menu -#endif - -// @section delta - -// Enable for DELTA kinematics and configure below -//#define DELTA -#if ENABLED(DELTA) - - // Make delta curves from many straight lines (linear interpolation). - // This is a trade-off between visible corners (not enough segments) - // and processor overload (too many expensive sqrt calls). - #define DEFAULT_SEGMENTS_PER_SECOND 200 - - // After homing move down to a height where XY movement is unconstrained - //#define DELTA_HOME_TO_SAFE_ZONE - - // Delta calibration menu - // Add three-point calibration to the MarlinUI menu. - // See http://minow.blogspot.com/index.html#4918805519571907051 - //#define DELTA_CALIBRATION_MENU - - // G33 Delta Auto-Calibration. Enable EEPROM_SETTINGS to store results. - //#define DELTA_AUTO_CALIBRATION - - #if ENABLED(DELTA_AUTO_CALIBRATION) - // Default number of probe points : n*n (1 -> 7) - #define DELTA_CALIBRATION_DEFAULT_POINTS 4 - #endif - - #if ANY(DELTA_AUTO_CALIBRATION, DELTA_CALIBRATION_MENU) - // Step size for paper-test probing - #define PROBE_MANUALLY_STEP 0.05 // (mm) - #endif - - // Print surface diameter/2 minus unreachable space (avoid collisions with vertical towers). - #define PRINTABLE_RADIUS 140.0 // (mm) - - // Maximum reachable area - #define DELTA_MAX_RADIUS 140.0 // (mm) - - // Center-to-center distance of the holes in the diagonal push rods. - #define DELTA_DIAGONAL_ROD 250.0 // (mm) - - // Distance between bed and nozzle Z home position - #define DELTA_HEIGHT 250.00 // (mm) Get this value from G33 auto calibrate - - #define DELTA_ENDSTOP_ADJ { 0.0, 0.0, 0.0 } // (mm) Get these values from G33 auto calibrate - - // Horizontal distance bridged by diagonal push rods when effector is centered. - #define DELTA_RADIUS 124.0 // (mm) Get this value from G33 auto calibrate - - // Trim adjustments for individual towers - // tower angle corrections for X and Y tower / rotate XYZ so Z tower angle = 0 - // measured in degrees anticlockwise looking from above the printer - #define DELTA_TOWER_ANGLE_TRIM { 0.0, 0.0, 0.0 } // (mm) Get these values from G33 auto calibrate - - // Delta radius and diagonal rod adjustments - //#define DELTA_RADIUS_TRIM_TOWER { 0.0, 0.0, 0.0 } // (mm) - //#define DELTA_DIAGONAL_ROD_TRIM_TOWER { 0.0, 0.0, 0.0 } // (mm) -#endif - -// @section scara - -/** - * MORGAN_SCARA was developed by QHARLEY in South Africa in 2012-2013. - * Implemented and slightly reworked by JCERNY in June, 2014. - * - * Mostly Printed SCARA is an open source design by Tyler Williams. See: - * https://www.thingiverse.com/thing:2487048 - * https://www.thingiverse.com/thing:1241491 - */ -//#define MORGAN_SCARA -//#define MP_SCARA -#if ANY(MORGAN_SCARA, MP_SCARA) - // If movement is choppy try lowering this value - #define DEFAULT_SEGMENTS_PER_SECOND 200 - - // Length of inner and outer support arms. Measure arm lengths precisely. - #define SCARA_LINKAGE_1 150 // (mm) - #define SCARA_LINKAGE_2 150 // (mm) - - // SCARA tower offset (position of Tower relative to bed zero position) - // This needs to be reasonably accurate as it defines the printbed position in the SCARA space. - #define SCARA_OFFSET_X 100 // (mm) - #define SCARA_OFFSET_Y -56 // (mm) - - #if ENABLED(MORGAN_SCARA) - - //#define DEBUG_SCARA_KINEMATICS - #define FEEDRATE_SCALING // Convert XY feedrate from mm/s to degrees/s on the fly - - // Radius around the center where the arm cannot reach - #define MIDDLE_DEAD_ZONE_R 0 // (mm) - - #elif ENABLED(MP_SCARA) - - #define SCARA_OFFSET_THETA1 12 // degrees - #define SCARA_OFFSET_THETA2 131 // degrees - - #endif - -#endif - -// @section tpara - -// Enable for TPARA kinematics and configure below -//#define AXEL_TPARA -#if ENABLED(AXEL_TPARA) - #define DEBUG_TPARA_KINEMATICS - #define DEFAULT_SEGMENTS_PER_SECOND 200 - - // Length of inner and outer support arms. Measure arm lengths precisely. - #define TPARA_LINKAGE_1 120 // (mm) - #define TPARA_LINKAGE_2 120 // (mm) - - // TPARA tower offset (position of Tower relative to bed zero position) - // This needs to be reasonably accurate as it defines the printbed position in the TPARA space. - #define TPARA_OFFSET_X 0 // (mm) - #define TPARA_OFFSET_Y 0 // (mm) - #define TPARA_OFFSET_Z 0 // (mm) - - #define FEEDRATE_SCALING // Convert XY feedrate from mm/s to degrees/s on the fly - - // Radius around the center where the arm cannot reach - #define MIDDLE_DEAD_ZONE_R 0 // (mm) -#endif - -// @section polar - -/** - * POLAR Kinematics - * developed by Kadir ilkimen for PolarBear CNC and babyBear - * https://github.com/kadirilkimen/Polar-Bear-Cnc-Machine - * https://github.com/kadirilkimen/babyBear-3D-printer - * - * A polar machine can have different configurations. - * This kinematics is only compatible with the following configuration: - * X : Independent linear - * Y or B : Polar - * Z : Independent linear - * - * For example, PolarBear has CoreXZ plus Polar Y or B. - * - * Motion problem for Polar axis near center / origin: - * - * 3D printing: - * Movements very close to the center of the polar axis take more time than others. - * This brief delay results in more material deposition due to the pressure in the nozzle. - * - * Current Kinematics and feedrate scaling deals with this by making the movement as fast - * as possible. It works for slow movements but doesn't work well with fast ones. A more - * complicated extrusion compensation must be implemented. - * - * Ideally, it should estimate that a long rotation near the center is ahead and will cause - * unwanted deposition. Therefore it can compensate the extrusion beforehand. - * - * Laser cutting: - * Same thing would be a problem for laser engraving too. As it spends time rotating at the - * center point, more likely it will burn more material than it should. Therefore similar - * compensation would be implemented for laser-cutting operations. - * - * Milling: - * This shouldn't be a problem for cutting/milling operations. - */ -//#define POLAR -#if ENABLED(POLAR) - #define DEFAULT_SEGMENTS_PER_SECOND 180 // If movement is choppy try lowering this value - #define PRINTABLE_RADIUS 82.0f // (mm) Maximum travel of X axis - - // Movements fall inside POLAR_FAST_RADIUS are assigned the highest possible feedrate - // to compensate unwanted deposition related to the near-origin motion problem. - #define POLAR_FAST_RADIUS 3.0f // (mm) - - // Radius which is unreachable by the tool. - // Needed if the tool is not perfectly aligned to the center of the polar axis. - #define POLAR_CENTER_OFFSET 0.0f // (mm) - - #define FEEDRATE_SCALING // Convert XY feedrate from mm/s to degrees/s on the fly -#endif - -//=========================================================================== -//============================== Endstop Settings =========================== -//=========================================================================== - -// @section endstops - -// Enable pullup for all endstops to prevent a floating state -#define ENDSTOPPULLUPS -#if DISABLED(ENDSTOPPULLUPS) - // Disable ENDSTOPPULLUPS to set pullups individually - //#define ENDSTOPPULLUP_XMIN - //#define ENDSTOPPULLUP_YMIN - //#define ENDSTOPPULLUP_ZMIN - //#define ENDSTOPPULLUP_IMIN - //#define ENDSTOPPULLUP_JMIN - //#define ENDSTOPPULLUP_KMIN - //#define ENDSTOPPULLUP_UMIN - //#define ENDSTOPPULLUP_VMIN - //#define ENDSTOPPULLUP_WMIN - //#define ENDSTOPPULLUP_XMAX - //#define ENDSTOPPULLUP_YMAX - //#define ENDSTOPPULLUP_ZMAX - //#define ENDSTOPPULLUP_IMAX - //#define ENDSTOPPULLUP_JMAX - //#define ENDSTOPPULLUP_KMAX - //#define ENDSTOPPULLUP_UMAX - //#define ENDSTOPPULLUP_VMAX - //#define ENDSTOPPULLUP_WMAX - //#define ENDSTOPPULLUP_ZMIN_PROBE -#endif - -// Enable pulldown for all endstops to prevent a floating state -//#define ENDSTOPPULLDOWNS -#if DISABLED(ENDSTOPPULLDOWNS) - // Disable ENDSTOPPULLDOWNS to set pulldowns individually - //#define ENDSTOPPULLDOWN_XMIN - //#define ENDSTOPPULLDOWN_YMIN - //#define ENDSTOPPULLDOWN_ZMIN - //#define ENDSTOPPULLDOWN_IMIN - //#define ENDSTOPPULLDOWN_JMIN - //#define ENDSTOPPULLDOWN_KMIN - //#define ENDSTOPPULLDOWN_UMIN - //#define ENDSTOPPULLDOWN_VMIN - //#define ENDSTOPPULLDOWN_WMIN - //#define ENDSTOPPULLDOWN_XMAX - //#define ENDSTOPPULLDOWN_YMAX - //#define ENDSTOPPULLDOWN_ZMAX - //#define ENDSTOPPULLDOWN_IMAX - //#define ENDSTOPPULLDOWN_JMAX - //#define ENDSTOPPULLDOWN_KMAX - //#define ENDSTOPPULLDOWN_UMAX - //#define ENDSTOPPULLDOWN_VMAX - //#define ENDSTOPPULLDOWN_WMAX - //#define ENDSTOPPULLDOWN_ZMIN_PROBE -#endif - -/** - * Endstop "Hit" State - * Set to the state (HIGH or LOW) that applies to each endstop. - */ -#define X_MIN_ENDSTOP_HIT_STATE HIGH -#define X_MAX_ENDSTOP_HIT_STATE HIGH -#define Y_MIN_ENDSTOP_HIT_STATE HIGH -#define Y_MAX_ENDSTOP_HIT_STATE HIGH -#define Z_MIN_ENDSTOP_HIT_STATE HIGH -#define Z_MAX_ENDSTOP_HIT_STATE HIGH -#define I_MIN_ENDSTOP_HIT_STATE HIGH -#define I_MAX_ENDSTOP_HIT_STATE HIGH -#define J_MIN_ENDSTOP_HIT_STATE HIGH -#define J_MAX_ENDSTOP_HIT_STATE HIGH -#define K_MIN_ENDSTOP_HIT_STATE HIGH -#define K_MAX_ENDSTOP_HIT_STATE HIGH -#define U_MIN_ENDSTOP_HIT_STATE HIGH -#define U_MAX_ENDSTOP_HIT_STATE HIGH -#define V_MIN_ENDSTOP_HIT_STATE HIGH -#define V_MAX_ENDSTOP_HIT_STATE HIGH -#define W_MIN_ENDSTOP_HIT_STATE HIGH -#define W_MAX_ENDSTOP_HIT_STATE HIGH -#define Z_MIN_PROBE_ENDSTOP_HIT_STATE HIGH - -// Enable this feature if all enabled endstop pins are interrupt-capable. -// This will remove the need to poll the interrupt pins, saving many CPU cycles. -//#define ENDSTOP_INTERRUPTS_FEATURE - -/** - * Endstop Noise Threshold - * - * Enable if your probe or endstops falsely trigger due to noise. - * - * - Higher values may affect repeatability or accuracy of some bed probes. - * - To fix noise install a 100nF ceramic capacitor in parallel with the switch. - * - This feature is not required for common micro-switches mounted on PCBs - * based on the Makerbot design, which already have the 100nF capacitor. - * - * :[2,3,4,5,6,7] - */ -//#define ENDSTOP_NOISE_THRESHOLD 2 - -// Check for stuck or disconnected endstops during homing moves. -//#define DETECT_BROKEN_ENDSTOP - -//============================================================================= -//============================== Movement Settings ============================ -//============================================================================= -// @section motion - -/** - * Default Settings - * - * These settings can be reset by M502 - * - * Note that if EEPROM is enabled, saved values will override these. - */ - -/** - * With this option each E stepper can have its own factors for the - * following movement settings. If fewer factors are given than the - * total number of extruders, the last value applies to the rest. - */ -//#define DISTINCT_E_FACTORS - -/** - * Default Axis Steps Per Unit (linear=steps/mm, rotational=steps/°) - * Override with M92 (when enabled below) - * X, Y, Z [, I [, J [, K...]]], E0 [, E1[, E2...]] - */ -#define DEFAULT_AXIS_STEPS_PER_UNIT { 80, 80, 400, 500 } - -/** - * Enable support for M92. Disable to save at least ~530 bytes of flash. - */ -#define EDITABLE_STEPS_PER_UNIT - -/** - * Default Max Feed Rate (linear=mm/s, rotational=°/s) - * Override with M203 - * X, Y, Z [, I [, J [, K...]]], E0 [, E1[, E2...]] - */ -#define DEFAULT_MAX_FEEDRATE { 300, 300, 5, 25 } - -//#define LIMITED_MAX_FR_EDITING // Limit edit via M203 or LCD to DEFAULT_MAX_FEEDRATE * 2 -#if ENABLED(LIMITED_MAX_FR_EDITING) - #define MAX_FEEDRATE_EDIT_VALUES { 600, 600, 10, 50 } // ...or, set your own edit limits -#endif - -/** - * Default Max Acceleration (speed change with time) (linear=mm/(s^2), rotational=°/(s^2)) - * (Maximum start speed for accelerated moves) - * Override with M201 - * X, Y, Z [, I [, J [, K...]]], E0 [, E1[, E2...]] - */ -#define DEFAULT_MAX_ACCELERATION { 3000, 3000, 100, 10000 } - -//#define LIMITED_MAX_ACCEL_EDITING // Limit edit via M201 or LCD to DEFAULT_MAX_ACCELERATION * 2 -#if ENABLED(LIMITED_MAX_ACCEL_EDITING) - #define MAX_ACCEL_EDIT_VALUES { 6000, 6000, 200, 20000 } // ...or, set your own edit limits -#endif - -/** - * Default Acceleration (speed change with time) (linear=mm/(s^2), rotational=°/(s^2)) - * Override with M204 - * - * M204 P Acceleration - * M204 R Retract Acceleration - * M204 T Travel Acceleration - */ -#define DEFAULT_ACCELERATION 3000 // X, Y, Z and E acceleration for printing moves -#define DEFAULT_RETRACT_ACCELERATION 3000 // E acceleration for retracts -#define DEFAULT_TRAVEL_ACCELERATION 3000 // X, Y, Z acceleration for travel (non printing) moves - -/** - * Default Jerk limits (mm/s) - * Override with M205 X Y Z . . . E - * - * "Jerk" specifies the minimum speed change that requires acceleration. - * When changing speed and direction, if the difference is less than the - * value set here, it may happen instantaneously. - */ -//#define CLASSIC_JERK -#if ENABLED(CLASSIC_JERK) - #define DEFAULT_XJERK 10.0 - #define DEFAULT_YJERK 10.0 - #define DEFAULT_ZJERK 0.3 - #define DEFAULT_EJERK 5.0 - //#define DEFAULT_IJERK 0.3 - //#define DEFAULT_JJERK 0.3 - //#define DEFAULT_KJERK 0.3 - //#define DEFAULT_UJERK 0.3 - //#define DEFAULT_VJERK 0.3 - //#define DEFAULT_WJERK 0.3 - - //#define TRAVEL_EXTRA_XYJERK 0.0 // Additional jerk allowance for all travel moves - - //#define LIMITED_JERK_EDITING // Limit edit via M205 or LCD to DEFAULT_aJERK * 2 - #if ENABLED(LIMITED_JERK_EDITING) - #define MAX_JERK_EDIT_VALUES { 20, 20, 0.6, 10 } // ...or, set your own edit limits - #endif -#endif - -/** - * Junction Deviation Factor - * - * See: - * https://reprap.org/forum/read.php?1,739819 - * https://blog.kyneticcnc.com/2018/10/computing-junction-deviation-for-marlin.html - */ -#if DISABLED(CLASSIC_JERK) - #define JUNCTION_DEVIATION_MM 0.013 // (mm) Distance from real junction edge - #define JD_HANDLE_SMALL_SEGMENTS // Use curvature estimation instead of just the junction angle - // for small segments (< 1mm) with large junction angles (> 135°). -#endif - -/** - * S-Curve Acceleration - * - * This option eliminates vibration during printing by fitting a Bézier - * curve to move acceleration, producing much smoother direction changes. - * - * See https://github.com/synthetos/TinyG/wiki/Jerk-Controlled-Motion-Explained - */ -//#define S_CURVE_ACCELERATION - -//=========================================================================== -//============================= Z Probe Options ============================= -//=========================================================================== -// @section probes - -// -// See https://marlinfw.org/docs/configuration/probes.html -// - -/** - * Enable this option for a probe connected to the Z-MIN pin. - * The probe replaces the Z-MIN endstop and is used for Z homing. - * (Automatically enables USE_PROBE_FOR_Z_HOMING.) - */ -#define Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN - -// Force the use of the probe for Z-axis homing -//#define USE_PROBE_FOR_Z_HOMING - -/** - * Z_MIN_PROBE_PIN - * - * Override this pin only if the probe cannot be connected to - * the default Z_MIN_PROBE_PIN for the selected MOTHERBOARD. - * - * - The simplest option is to use a free endstop connector. - * - Use 5V for powered (usually inductive) sensors. - * - * - For simple switches... - * - Normally-closed (NC) also connect to GND. - * - Normally-open (NO) also connect to 5V. - */ -//#define Z_MIN_PROBE_PIN -1 - -/** - * Probe Type - * - * Allen Key Probes, Servo Probes, Z-Sled Probes, FIX_MOUNTED_PROBE, etc. - * Activate one of these to use Auto Bed Leveling below. - */ - -/** - * The "Manual Probe" provides a means to do "Auto" Bed Leveling without a probe. - * Use G29 repeatedly, adjusting the Z height at each point with movement commands - * or (with LCD_BED_LEVELING) the LCD controller. - */ -//#define PROBE_MANUALLY - -/** - * A Fix-Mounted Probe either doesn't deploy or needs manual deployment. - * (e.g., an inductive probe or a nozzle-based probe-switch.) - */ -//#define FIX_MOUNTED_PROBE - -/** - * Use the nozzle as the probe, as with a conductive - * nozzle system or a piezo-electric smart effector. - */ -//#define NOZZLE_AS_PROBE - -/** - * Z Servo Probe, such as an endstop switch on a rotating arm. - */ -//#define Z_PROBE_SERVO_NR 0 -#ifdef Z_PROBE_SERVO_NR - //#define Z_SERVO_ANGLES { 70, 0 } // Z Servo Deploy and Stow angles - //#define Z_SERVO_MEASURE_ANGLE 45 // Use if the servo must move to a "free" position for measuring after deploy - //#define Z_SERVO_INTERMEDIATE_STOW // Stow the probe between points - //#define Z_SERVO_DEACTIVATE_AFTER_STOW // Deactivate the servo when probe is stowed -#endif - -/** - * The BLTouch probe uses a Hall effect sensor and emulates a servo. - */ -//#define BLTOUCH - -/** - * MagLev V4 probe by MDD - * - * This probe is deployed and activated by powering a built-in electromagnet. - */ -//#define MAGLEV4 -#if ENABLED(MAGLEV4) - //#define MAGLEV_TRIGGER_PIN 11 // Set to the connected digital output - #define MAGLEV_TRIGGER_DELAY 15 // Changing this risks overheating the coil -#endif - -/** - * Touch-MI Probe by hotends.fr - * - * This probe is deployed and activated by moving the X-axis to a magnet at the edge of the bed. - * By default, the magnet is assumed to be on the left and activated by a home. If the magnet is - * on the right, enable and set TOUCH_MI_DEPLOY_XPOS to the deploy position. - * - * Also requires: BABYSTEPPING, BABYSTEP_ZPROBE_OFFSET, Z_SAFE_HOMING, - * and a minimum Z_CLEARANCE_FOR_HOMING of 10. - */ -//#define TOUCH_MI_PROBE -#if ENABLED(TOUCH_MI_PROBE) - #define TOUCH_MI_RETRACT_Z 0.5 // Height at which the probe retracts - //#define TOUCH_MI_DEPLOY_XPOS (X_MAX_BED + 2) // For a magnet on the right side of the bed - //#define TOUCH_MI_MANUAL_DEPLOY // For manual deploy (LCD menu) -#endif - -/** - * Bed Distance Sensor - * - * Measures the distance from bed to nozzle with accuracy of 0.01mm. - * For information about this sensor https://github.com/markniu/Bed_Distance_sensor - * Uses I2C port, so it requires I2C library markyue/Panda_SoftMasterI2C. - */ -//#define BD_SENSOR -#if ENABLED(BD_SENSOR) - //#define BD_SENSOR_PROBE_NO_STOP // Probe bed without stopping at each probe point -#endif - -/** - * BIQU MicroProbe - * - * A lightweight, solenoid-driven probe. - * For information about this sensor https://github.com/bigtreetech/MicroProbe - * - * Also requires PROBE_ENABLE_DISABLE - */ -//#define BIQU_MICROPROBE_V1 // Triggers HIGH -//#define BIQU_MICROPROBE_V2 // Triggers LOW - -// A probe that is deployed and stowed with a solenoid pin (SOL1_PIN) -//#define SOLENOID_PROBE - -// A sled-mounted probe like those designed by Charles Bell. -//#define Z_PROBE_SLED -//#define SLED_DOCKING_OFFSET 5 // The extra distance the X axis must travel to pickup the sled. 0 should be fine but you can push it further if you'd like. - -// A probe deployed by moving the x-axis, such as the Wilson II's rack-and-pinion probe designed by Marty Rice. -//#define RACK_AND_PINION_PROBE -#if ENABLED(RACK_AND_PINION_PROBE) - #define Z_PROBE_DEPLOY_X X_MIN_POS - #define Z_PROBE_RETRACT_X X_MAX_POS -#endif - -/** - * Magnetically Mounted Probe - * For probes such as Euclid, Klicky, Klackender, etc. - */ -//#define MAG_MOUNTED_PROBE -#if ENABLED(MAG_MOUNTED_PROBE) - #define PROBE_DEPLOY_FEEDRATE (133*60) // (mm/min) Probe deploy speed - #define PROBE_STOW_FEEDRATE (133*60) // (mm/min) Probe stow speed - - #define MAG_MOUNTED_DEPLOY_1 { PROBE_DEPLOY_FEEDRATE, { 245, 114, 30 } } // Move to side Dock & Attach probe - #define MAG_MOUNTED_DEPLOY_2 { PROBE_DEPLOY_FEEDRATE, { 210, 114, 30 } } // Move probe off dock - #define MAG_MOUNTED_DEPLOY_3 { PROBE_DEPLOY_FEEDRATE, { 0, 0, 0 } } // Extra move if needed - #define MAG_MOUNTED_DEPLOY_4 { PROBE_DEPLOY_FEEDRATE, { 0, 0, 0 } } // Extra move if needed - #define MAG_MOUNTED_DEPLOY_5 { PROBE_DEPLOY_FEEDRATE, { 0, 0, 0 } } // Extra move if needed - #define MAG_MOUNTED_STOW_1 { PROBE_STOW_FEEDRATE, { 245, 114, 20 } } // Move to dock - #define MAG_MOUNTED_STOW_2 { PROBE_STOW_FEEDRATE, { 245, 114, 0 } } // Place probe beside remover - #define MAG_MOUNTED_STOW_3 { PROBE_STOW_FEEDRATE, { 230, 114, 0 } } // Side move to remove probe - #define MAG_MOUNTED_STOW_4 { PROBE_STOW_FEEDRATE, { 210, 114, 20 } } // Side move to remove probe - #define MAG_MOUNTED_STOW_5 { PROBE_STOW_FEEDRATE, { 0, 0, 0 } } // Extra move if needed -#endif - -// Duet Smart Effector (for delta printers) - https://docs.duet3d.com/en/Duet3D_hardware/Accessories/Smart_Effector -// When the pin is defined you can use M672 to set/reset the probe sensitivity. -//#define DUET_SMART_EFFECTOR -#if ENABLED(DUET_SMART_EFFECTOR) - #define SMART_EFFECTOR_MOD_PIN -1 // Connect a GPIO pin to the Smart Effector MOD pin -#endif - -/** - * Use StallGuard2 to probe the bed with the nozzle. - * Requires stallGuard-capable Trinamic stepper drivers. - * CAUTION: This can damage machines with Z lead screws. - * Take extreme care when setting up this feature. - */ -//#define SENSORLESS_PROBING - -/** - * Allen key retractable z-probe as seen on many Kossel delta printers - https://reprap.org/wiki/Kossel#Autolevel_probe - * Deploys by touching z-axis belt. Retracts by pushing the probe down. - */ -//#define Z_PROBE_ALLEN_KEY -#if ENABLED(Z_PROBE_ALLEN_KEY) - // 2 or 3 sets of coordinates for deploying and retracting the spring loaded touch probe on G29, - // if servo actuated touch probe is not defined. Uncomment as appropriate for your printer/probe. - - #define Z_PROBE_ALLEN_KEY_DEPLOY_1 { 30.0, PRINTABLE_RADIUS, 100.0 } - #define Z_PROBE_ALLEN_KEY_DEPLOY_1_FEEDRATE XY_PROBE_FEEDRATE - - #define Z_PROBE_ALLEN_KEY_DEPLOY_2 { 0.0, PRINTABLE_RADIUS, 100.0 } - #define Z_PROBE_ALLEN_KEY_DEPLOY_2_FEEDRATE (XY_PROBE_FEEDRATE)/10 - - #define Z_PROBE_ALLEN_KEY_DEPLOY_3 { 0.0, (PRINTABLE_RADIUS) * 0.75, 100.0 } - #define Z_PROBE_ALLEN_KEY_DEPLOY_3_FEEDRATE XY_PROBE_FEEDRATE - - #define Z_PROBE_ALLEN_KEY_STOW_1 { -64.0, 56.0, 23.0 } // Move the probe into position - #define Z_PROBE_ALLEN_KEY_STOW_1_FEEDRATE XY_PROBE_FEEDRATE - - #define Z_PROBE_ALLEN_KEY_STOW_2 { -64.0, 56.0, 3.0 } // Push it down - #define Z_PROBE_ALLEN_KEY_STOW_2_FEEDRATE (XY_PROBE_FEEDRATE)/10 - - #define Z_PROBE_ALLEN_KEY_STOW_3 { -64.0, 56.0, 50.0 } // Move it up to clear - #define Z_PROBE_ALLEN_KEY_STOW_3_FEEDRATE XY_PROBE_FEEDRATE - - #define Z_PROBE_ALLEN_KEY_STOW_4 { 0.0, 0.0, 50.0 } - #define Z_PROBE_ALLEN_KEY_STOW_4_FEEDRATE XY_PROBE_FEEDRATE - -#endif // Z_PROBE_ALLEN_KEY - -/** - * Nozzle-to-Probe offsets { X, Y, Z } - * - * X and Y offset - * Use a caliper or ruler to measure the distance from the tip of - * the Nozzle to the center-point of the Probe in the X and Y axes. - * - * Z offset - * - For the Z offset use your best known value and adjust at runtime. - * - Common probes trigger below the nozzle and have negative values for Z offset. - * - Probes triggering above the nozzle height are uncommon but do exist. When using - * probes such as this, carefully set Z_CLEARANCE_DEPLOY_PROBE and Z_CLEARANCE_BETWEEN_PROBES - * to avoid collisions during probing. - * - * Tune and Adjust - * - Probe Offsets can be tuned at runtime with 'M851', LCD menus, babystepping, etc. - * - PROBE_OFFSET_WIZARD (Configuration_adv.h) can be used for setting the Z offset. - * - * Assuming the typical work area orientation: - * - Probe to RIGHT of the Nozzle has a Positive X offset - * - Probe to LEFT of the Nozzle has a Negative X offset - * - Probe in BACK of the Nozzle has a Positive Y offset - * - Probe in FRONT of the Nozzle has a Negative Y offset - * - * Some examples: - * #define NOZZLE_TO_PROBE_OFFSET { 10, 10, -1 } // Example "1" - * #define NOZZLE_TO_PROBE_OFFSET {-10, 5, -1 } // Example "2" - * #define NOZZLE_TO_PROBE_OFFSET { 5, -5, -1 } // Example "3" - * #define NOZZLE_TO_PROBE_OFFSET {-15,-10, -1 } // Example "4" - * - * +-- BACK ---+ - * | [+] | - * L | 1 | R <-- Example "1" (right+, back+) - * E | 2 | I <-- Example "2" ( left-, back+) - * F |[-] N [+]| G <-- Nozzle - * T | 3 | H <-- Example "3" (right+, front-) - * | 4 | T <-- Example "4" ( left-, front-) - * | [-] | - * O-- FRONT --+ - */ -#define NOZZLE_TO_PROBE_OFFSET { 10, 10, 0 } - -// Enable and set to use a specific tool for probing. Disable to allow any tool. -#define PROBING_TOOL 0 -#ifdef PROBING_TOOL - //#define PROBE_TOOLCHANGE_NO_MOVE // Suppress motion on probe tool-change -#endif - -// Most probes should stay away from the edges of the bed, but -// with NOZZLE_AS_PROBE this can be negative for a wider probing area. -#define PROBING_MARGIN 10 - -// X and Y axis travel speed between probes. -// Leave undefined to use the average of the current XY homing feedrate. -#define XY_PROBE_FEEDRATE (133*60) // (mm/min) - -// Feedrate for the first approach when double-probing (MULTIPLE_PROBING == 2) -#define Z_PROBE_FEEDRATE_FAST (4*60) // (mm/min) - -// Feedrate for the "accurate" probe of each point -#define Z_PROBE_FEEDRATE_SLOW (Z_PROBE_FEEDRATE_FAST / 2) // (mm/min) - -/** - * Probe Activation Switch - * A switch indicating proper deployment, or an optical - * switch triggered when the carriage is near the bed. - */ -//#define PROBE_ACTIVATION_SWITCH -#if ENABLED(PROBE_ACTIVATION_SWITCH) - #define PROBE_ACTIVATION_SWITCH_STATE LOW // State indicating probe is active - //#define PROBE_ACTIVATION_SWITCH_PIN PC6 // Override default pin -#endif - -/** - * Tare Probe (determine zero-point) prior to each probe. - * Useful for a strain gauge or piezo sensor that needs to factor out - * elements such as cables pulling on the carriage. - */ -//#define PROBE_TARE -#if ENABLED(PROBE_TARE) - #define PROBE_TARE_TIME 200 // (ms) Time to hold tare pin - #define PROBE_TARE_DELAY 200 // (ms) Delay after tare before - #define PROBE_TARE_STATE HIGH // State to write pin for tare - //#define PROBE_TARE_PIN PA5 // Override default pin - //#define PROBE_TARE_MENU // Display a menu item to tare the probe - #if ENABLED(PROBE_ACTIVATION_SWITCH) - //#define PROBE_TARE_ONLY_WHILE_INACTIVE // Fail to tare/probe if PROBE_ACTIVATION_SWITCH is active - #endif -#endif - -/** - * Probe Enable / Disable - * The probe only provides a triggered signal when enabled. - */ -//#define PROBE_ENABLE_DISABLE -#if ENABLED(PROBE_ENABLE_DISABLE) - //#define PROBE_ENABLE_PIN -1 // Override the default pin here -#endif - -/** - * Multiple Probing - * - * You may get improved results by probing 2 or more times. - * With EXTRA_PROBING the more atypical reading(s) will be disregarded. - * - * A total of 2 does fast/slow probes with a weighted average. - * A total of 3 or more adds more slow probes, taking the average. - */ -//#define MULTIPLE_PROBING 2 -//#define EXTRA_PROBING 1 - -/** - * Z probes require clearance when deploying, stowing, and moving between - * probe points to avoid hitting the bed and other hardware. - * Servo-mounted probes require extra space for the arm to rotate. - * Inductive probes need space to keep from triggering early. - * - * Use these settings to specify the distance (mm) to raise the probe (or - * lower the bed). The values set here apply over and above any (negative) - * probe Z Offset set with NOZZLE_TO_PROBE_OFFSET, M851, or the LCD. - * Only integer values >= 1 are valid here. - * - * Example: 'M851 Z-5' with a CLEARANCE of 4 => 9mm from bed to nozzle. - * But: 'M851 Z+1' with a CLEARANCE of 2 => 2mm from bed to nozzle. - */ -#define Z_CLEARANCE_DEPLOY_PROBE 10 // (mm) Z Clearance for Deploy/Stow -#define Z_CLEARANCE_BETWEEN_PROBES 5 // (mm) Z Clearance between probe points -#define Z_CLEARANCE_MULTI_PROBE 5 // (mm) Z Clearance between multiple probes -#define Z_PROBE_ERROR_TOLERANCE 3 // (mm) Tolerance for early trigger (<= -probe.offset.z + ZPET) -//#define Z_AFTER_PROBING 5 // (mm) Z position after probing is done - -#define Z_PROBE_LOW_POINT -2 // (mm) Farthest distance below the trigger-point to go before stopping - -// For M851 provide ranges for adjusting the X, Y, and Z probe offsets -//#define PROBE_OFFSET_XMIN -50 // (mm) -//#define PROBE_OFFSET_XMAX 50 // (mm) -//#define PROBE_OFFSET_YMIN -50 // (mm) -//#define PROBE_OFFSET_YMAX 50 // (mm) -//#define PROBE_OFFSET_ZMIN -20 // (mm) -//#define PROBE_OFFSET_ZMAX 20 // (mm) - -// Enable the M48 repeatability test to test probe accuracy -//#define Z_MIN_PROBE_REPEATABILITY_TEST - -// Before deploy/stow pause for user confirmation -//#define PAUSE_BEFORE_DEPLOY_STOW -#if ENABLED(PAUSE_BEFORE_DEPLOY_STOW) - //#define PAUSE_PROBE_DEPLOY_WHEN_TRIGGERED // For Manual Deploy Allenkey Probe -#endif - -/** - * Enable one or more of the following if probing seems unreliable. - * Heaters and/or fans can be disabled during probing to minimize electrical - * noise. A delay can also be added to allow noise and vibration to settle. - * These options are most useful for the BLTouch probe, but may also improve - * readings with inductive probes and piezo sensors. - */ -//#define PROBING_HEATERS_OFF // Turn heaters off when probing -#if ENABLED(PROBING_HEATERS_OFF) - //#define WAIT_FOR_BED_HEATER // Wait for bed to heat back up between probes (to improve accuracy) - //#define WAIT_FOR_HOTEND // Wait for hotend to heat back up between probes (to improve accuracy & prevent cold extrude) -#endif -//#define PROBING_FANS_OFF // Turn fans off when probing -//#define PROBING_ESTEPPERS_OFF // Turn all extruder steppers off when probing -//#define PROBING_STEPPERS_OFF // Turn all steppers off (unless needed to hold position) when probing (including extruders) -//#define DELAY_BEFORE_PROBING 200 // (ms) To prevent vibrations from triggering piezo sensors - -// Require minimum nozzle and/or bed temperature for probing -//#define PREHEAT_BEFORE_PROBING -#if ENABLED(PREHEAT_BEFORE_PROBING) - #define PROBING_NOZZLE_TEMP 120 // (°C) Only applies to E0 at this time - #define PROBING_BED_TEMP 50 -#endif - -// @section stepper drivers - -// For Inverting Stepper Enable Pins (Active Low) use 0, Non Inverting (Active High) use 1 -// :['LOW', 'HIGH'] -#define X_ENABLE_ON LOW -#define Y_ENABLE_ON LOW -#define Z_ENABLE_ON LOW -#define E_ENABLE_ON LOW // For all extruders -//#define I_ENABLE_ON LOW -//#define J_ENABLE_ON LOW -//#define K_ENABLE_ON LOW -//#define U_ENABLE_ON LOW -//#define V_ENABLE_ON LOW -//#define W_ENABLE_ON LOW - -// Disable axis steppers immediately when they're not being stepped. -// WARNING: When motors turn off there is a chance of losing position accuracy! -//#define DISABLE_X -//#define DISABLE_Y -//#define DISABLE_Z -//#define DISABLE_I -//#define DISABLE_J -//#define DISABLE_K -//#define DISABLE_U -//#define DISABLE_V -//#define DISABLE_W - -// Turn off the display blinking that warns about possible accuracy reduction -//#define DISABLE_REDUCED_ACCURACY_WARNING - -// @section extruder - -//#define DISABLE_E // Disable the extruder when not stepping -#define DISABLE_OTHER_EXTRUDERS // Keep only the active extruder enabled - -// @section motion - -// Invert the stepper direction. Change (or reverse the motor connector) if an axis goes the wrong way. -#define INVERT_X_DIR false -#define INVERT_Y_DIR true -#define INVERT_Z_DIR false -//#define INVERT_I_DIR false -//#define INVERT_J_DIR false -//#define INVERT_K_DIR false -//#define INVERT_U_DIR false -//#define INVERT_V_DIR false -//#define INVERT_W_DIR false - -// @section extruder - -// For direct drive extruder v9 set to true, for geared extruder set to false. -#define INVERT_E0_DIR false -#define INVERT_E1_DIR false -#define INVERT_E2_DIR false -#define INVERT_E3_DIR false -#define INVERT_E4_DIR false -#define INVERT_E5_DIR false -#define INVERT_E6_DIR false -#define INVERT_E7_DIR false - -// @section homing - -//#define NO_MOTION_BEFORE_HOMING // Inhibit movement until all axes have been homed. Also enable HOME_AFTER_DEACTIVATE for extra safety. -//#define HOME_AFTER_DEACTIVATE // Require rehoming after steppers are deactivated. Also enable NO_MOTION_BEFORE_HOMING for extra safety. - -/** - * Set Z_IDLE_HEIGHT if the Z-Axis moves on its own when steppers are disabled. - * - Use a low value (i.e., Z_MIN_POS) if the nozzle falls down to the bed. - * - Use a large value (i.e., Z_MAX_POS) if the bed falls down, away from the nozzle. - */ -//#define Z_IDLE_HEIGHT Z_HOME_POS - -//#define Z_CLEARANCE_FOR_HOMING 4 // (mm) Minimal Z height before homing (G28) for Z clearance above the bed, clamps, ... - // You'll need this much clearance above Z_MAX_POS to avoid grinding. - -//#define Z_AFTER_HOMING 10 // (mm) Height to move to after homing (if Z was homed) -//#define XY_AFTER_HOMING { 10, 10 } // (mm) Move to an XY position after homing (and raising Z) - -//#define EVENT_GCODE_AFTER_HOMING "M300 P440 S200" // Commands to run after G28 (and move to XY_AFTER_HOMING) - -// Direction of endstops when homing; 1=MAX, -1=MIN -// :[-1,1] -#define X_HOME_DIR -1 -#define Y_HOME_DIR -1 -#define Z_HOME_DIR -1 -//#define I_HOME_DIR -1 -//#define J_HOME_DIR -1 -//#define K_HOME_DIR -1 -//#define U_HOME_DIR -1 -//#define V_HOME_DIR -1 -//#define W_HOME_DIR -1 - -/** - * Safety Stops - * If an axis has endstops on both ends the one specified above is used for - * homing, while the other can be used for things like SD_ABORT_ON_ENDSTOP_HIT. - */ -//#define X_SAFETY_STOP -//#define Y_SAFETY_STOP -//#define Z_SAFETY_STOP -//#define I_SAFETY_STOP -//#define J_SAFETY_STOP -//#define K_SAFETY_STOP -//#define U_SAFETY_STOP -//#define V_SAFETY_STOP -//#define W_SAFETY_STOP - -// @section geometry - -// The size of the printable area -#define X_BED_SIZE 200 -#define Y_BED_SIZE 200 - -// Travel limits (linear=mm, rotational=°) after homing, corresponding to endstop positions. -#define X_MIN_POS 0 -#define Y_MIN_POS 0 -#define Z_MIN_POS 0 -#define X_MAX_POS X_BED_SIZE -#define Y_MAX_POS Y_BED_SIZE -#define Z_MAX_POS 200 -//#define I_MIN_POS 0 -//#define I_MAX_POS 50 -//#define J_MIN_POS 0 -//#define J_MAX_POS 50 -//#define K_MIN_POS 0 -//#define K_MAX_POS 50 -//#define U_MIN_POS 0 -//#define U_MAX_POS 50 -//#define V_MIN_POS 0 -//#define V_MAX_POS 50 -//#define W_MIN_POS 0 -//#define W_MAX_POS 50 - -/** - * Software Endstops - * - * - Prevent moves outside the set machine bounds. - * - Individual axes can be disabled, if desired. - * - X and Y only apply to Cartesian robots. - * - Use 'M211' to set software endstops on/off or report current state - */ - -// Min software endstops constrain movement within minimum coordinate bounds -#define MIN_SOFTWARE_ENDSTOPS -#if ENABLED(MIN_SOFTWARE_ENDSTOPS) - #define MIN_SOFTWARE_ENDSTOP_X - #define MIN_SOFTWARE_ENDSTOP_Y - #define MIN_SOFTWARE_ENDSTOP_Z - #define MIN_SOFTWARE_ENDSTOP_I - #define MIN_SOFTWARE_ENDSTOP_J - #define MIN_SOFTWARE_ENDSTOP_K - #define MIN_SOFTWARE_ENDSTOP_U - #define MIN_SOFTWARE_ENDSTOP_V - #define MIN_SOFTWARE_ENDSTOP_W -#endif - -// Max software endstops constrain movement within maximum coordinate bounds -#define MAX_SOFTWARE_ENDSTOPS -#if ENABLED(MAX_SOFTWARE_ENDSTOPS) - #define MAX_SOFTWARE_ENDSTOP_X - #define MAX_SOFTWARE_ENDSTOP_Y - #define MAX_SOFTWARE_ENDSTOP_Z - #define MAX_SOFTWARE_ENDSTOP_I - #define MAX_SOFTWARE_ENDSTOP_J - #define MAX_SOFTWARE_ENDSTOP_K - #define MAX_SOFTWARE_ENDSTOP_U - #define MAX_SOFTWARE_ENDSTOP_V - #define MAX_SOFTWARE_ENDSTOP_W -#endif - -#if ANY(MIN_SOFTWARE_ENDSTOPS, MAX_SOFTWARE_ENDSTOPS) - //#define SOFT_ENDSTOPS_MENU_ITEM // Enable/Disable software endstops from the LCD -#endif - -/** - * @section filament runout sensors - * - * Filament Runout Sensors - * Mechanical or opto endstops are used to check for the presence of filament. - * - * IMPORTANT: Runout will only trigger if Marlin is aware that a print job is running. - * Marlin knows a print job is running when: - * 1. Running a print job from media started with M24. - * 2. The Print Job Timer has been started with M75. - * 3. The heaters were turned on and PRINTJOB_TIMER_AUTOSTART is enabled. - * - * RAMPS-based boards use SERVO3_PIN for the first runout sensor. - * For other boards you may need to define FIL_RUNOUT_PIN, FIL_RUNOUT2_PIN, etc. - */ -//#define FILAMENT_RUNOUT_SENSOR -#if ENABLED(FILAMENT_RUNOUT_SENSOR) - #define FIL_RUNOUT_ENABLED_DEFAULT true // Enable the sensor on startup. Override with M412 followed by M500. - #define NUM_RUNOUT_SENSORS 1 // Number of sensors, up to one per extruder. Define a FIL_RUNOUT#_PIN for each. - - #define FIL_RUNOUT_STATE LOW // Pin state indicating that filament is NOT present. - #define FIL_RUNOUT_PULLUP // Use internal pullup for filament runout pins. - //#define FIL_RUNOUT_PULLDOWN // Use internal pulldown for filament runout pins. - //#define WATCH_ALL_RUNOUT_SENSORS // Execute runout script on any triggering sensor, not only for the active extruder. - // This is automatically enabled for MIXING_EXTRUDERs. - - // Override individually if the runout sensors vary - //#define FIL_RUNOUT1_STATE LOW - //#define FIL_RUNOUT1_PULLUP - //#define FIL_RUNOUT1_PULLDOWN - - //#define FIL_RUNOUT2_STATE LOW - //#define FIL_RUNOUT2_PULLUP - //#define FIL_RUNOUT2_PULLDOWN - - //#define FIL_RUNOUT3_STATE LOW - //#define FIL_RUNOUT3_PULLUP - //#define FIL_RUNOUT3_PULLDOWN - - //#define FIL_RUNOUT4_STATE LOW - //#define FIL_RUNOUT4_PULLUP - //#define FIL_RUNOUT4_PULLDOWN - - //#define FIL_RUNOUT5_STATE LOW - //#define FIL_RUNOUT5_PULLUP - //#define FIL_RUNOUT5_PULLDOWN - - //#define FIL_RUNOUT6_STATE LOW - //#define FIL_RUNOUT6_PULLUP - //#define FIL_RUNOUT6_PULLDOWN - - //#define FIL_RUNOUT7_STATE LOW - //#define FIL_RUNOUT7_PULLUP - //#define FIL_RUNOUT7_PULLDOWN - - //#define FIL_RUNOUT8_STATE LOW - //#define FIL_RUNOUT8_PULLUP - //#define FIL_RUNOUT8_PULLDOWN - - // Commands to execute on filament runout. - // With multiple runout sensors use the %c placeholder for the current tool in commands (e.g., "M600 T%c") - // NOTE: After 'M412 H1' the host handles filament runout and this script does not apply. - #define FILAMENT_RUNOUT_SCRIPT "M600" - - // After a runout is detected, continue printing this length of filament - // before executing the runout script. Useful for a sensor at the end of - // a feed tube. Requires 4 bytes SRAM per sensor, plus 4 bytes overhead. - //#define FILAMENT_RUNOUT_DISTANCE_MM 25 - - #ifdef FILAMENT_RUNOUT_DISTANCE_MM - // Enable this option to use an encoder disc that toggles the runout pin - // as the filament moves. (Be sure to set FILAMENT_RUNOUT_DISTANCE_MM - // large enough to avoid false positives.) - //#define FILAMENT_MOTION_SENSOR - - #if ENABLED(FILAMENT_MOTION_SENSOR) - //#define FILAMENT_SWITCH_AND_MOTION - #if ENABLED(FILAMENT_SWITCH_AND_MOTION) - #define NUM_MOTION_SENSORS 1 // Number of sensors, up to one per extruder. Define a FIL_MOTION#_PIN for each. - //#define FIL_MOTION1_PIN -1 - - // Override individually if the motion sensors vary - //#define FIL_MOTION1_STATE LOW - //#define FIL_MOTION1_PULLUP - //#define FIL_MOTION1_PULLDOWN - - //#define FIL_MOTION2_STATE LOW - //#define FIL_MOTION2_PULLUP - //#define FIL_MOTION2_PULLDOWN - - //#define FIL_MOTION3_STATE LOW - //#define FIL_MOTION3_PULLUP - //#define FIL_MOTION3_PULLDOWN - - //#define FIL_MOTION4_STATE LOW - //#define FIL_MOTION4_PULLUP - //#define FIL_MOTION4_PULLDOWN - - //#define FIL_MOTION5_STATE LOW - //#define FIL_MOTION5_PULLUP - //#define FIL_MOTION5_PULLDOWN - - //#define FIL_MOTION6_STATE LOW - //#define FIL_MOTION6_PULLUP - //#define FIL_MOTION6_PULLDOWN - - //#define FIL_MOTION7_STATE LOW - //#define FIL_MOTION7_PULLUP - //#define FIL_MOTION7_PULLDOWN - - //#define FIL_MOTION8_STATE LOW - //#define FIL_MOTION8_PULLUP - //#define FIL_MOTION8_PULLDOWN - #endif - #endif // FILAMENT_MOTION_SENSOR - #endif // FILAMENT_RUNOUT_DISTANCE_MM -#endif // FILAMENT_RUNOUT_SENSOR - -//=========================================================================== -//=============================== Bed Leveling ============================== -//=========================================================================== -// @section calibrate - -/** - * Choose one of the options below to enable G29 Bed Leveling. The parameters - * and behavior of G29 will change depending on your selection. - * - * If using a Probe for Z Homing, enable Z_SAFE_HOMING also! - * - * - AUTO_BED_LEVELING_3POINT - * Probe 3 arbitrary points on the bed (that aren't collinear) - * You specify the XY coordinates of all 3 points. - * The result is a single tilted plane. Best for a flat bed. - * - * - AUTO_BED_LEVELING_LINEAR - * Probe several points in a grid. - * You specify the rectangle and the density of sample points. - * The result is a single tilted plane. Best for a flat bed. - * - * - AUTO_BED_LEVELING_BILINEAR - * Probe several points in a grid. - * You specify the rectangle and the density of sample points. - * The result is a mesh, best for large or uneven beds. - * - * - AUTO_BED_LEVELING_UBL (Unified Bed Leveling) - * A comprehensive bed leveling system combining the features and benefits - * of other systems. UBL also includes integrated Mesh Generation, Mesh - * Validation and Mesh Editing systems. - * - * - MESH_BED_LEVELING - * Probe a grid manually - * The result is a mesh, suitable for large or uneven beds. (See BILINEAR.) - * For machines without a probe, Mesh Bed Leveling provides a method to perform - * leveling in steps so you can manually adjust the Z height at each grid-point. - * With an LCD controller the process is guided step-by-step. - */ -//#define AUTO_BED_LEVELING_3POINT -//#define AUTO_BED_LEVELING_LINEAR -//#define AUTO_BED_LEVELING_BILINEAR -//#define AUTO_BED_LEVELING_UBL -//#define MESH_BED_LEVELING - -/* When a level probe is fitted but not used for homing, enabling USE_PROBE_FOR_MESH_REF will avoid a level mesh - * being created with an unwanted offset between the fixed Z endstop/Homing Switch and probe trigger height. The - * probe Z offset menu option is removed from the level sub menu, as entering a Z offset there would shift the mesh - * and the Homeing position giving confusing status values for Z. It is recomended not to enter a Z offset value in - * the probe offset section of config.h for the same reason. - * - * If no Z offsets are entered elsewhere the status screen will always show the actual Z possition. e.g when homing - * the Z status will show 0 at the Home position, unless the height of the fixed Z stop/Homeing switch above the bed - * has been set by the HOME_SWITCH_TO_BED_OFFSET_MENU option while creating a level mesh. In which case the Home - * position will show as that height. The printer will move down to the print height when printing starts and Z status - * will show the first layer height. The Nozzle will follow the mesh and baby stepping can be used to temporarily lower - * or raise the nozzle to adjust first layer squish. - * - * If your Z stop/Homeing switch has a fine adjustment capability, the HOME_SWITCH_TO_BED_OFFSET_MENU menu option can be - * left at zero or disabled. Using the paper pinch test and setting the Z stop/Homeing switch position works just as well. - */ -#ifndef USE_PROBE_FOR_Z_HOMING - #if defined AUTO_BED_LEVELING_BILINEAR || defined AUTO_BED_LEVELING_UBL || defined AUTO_BED_LEVELING_LINEAR || defined AUTO_BED_LEVELING_3POINT - //#define USE_PROBE_FOR_MESH_REF - #ifdef USE_PROBE_FOR_MESH_REF - /*optional HOME_SWITCH_TO_BED_OFFSET_MENU enables a menu option called "Home Offset Z" to adjust the Z offset between - * the Fixed Z endstop/Homing switch and the bed. This offset is only used to adjust the level mesh down - * to the Zero plane (Bed) as the mesh is created, so a new level mesh must be created when if it is changed to apply it.*/ - #define HOME_SWITCH_TO_BED_OFFSET_MENU - #endif - #endif -#endif - -/** - * Commands to execute at the start of G29 probing, - * after switching to the PROBING_TOOL. - */ -//#define EVENT_GCODE_BEFORE_G29 "M300 P440 S200" - -/** - * Commands to execute at the end of G29 probing. - * Useful to retract or move the Z probe out of the way. - */ -//#define EVENT_GCODE_AFTER_G29 "G1 Z10 F12000\nG1 X15 Y330\nG1 Z0.5\nG1 Z10" - -/** - * Normally G28 leaves leveling disabled on completion. Enable one of - * these options to restore the prior leveling state or to always enable - * leveling immediately after G28. - */ -//#define RESTORE_LEVELING_AFTER_G28 -//#define ENABLE_LEVELING_AFTER_G28 - -/** - * Auto-leveling needs preheating - */ -//#define PREHEAT_BEFORE_LEVELING -#if ENABLED(PREHEAT_BEFORE_LEVELING) - #define LEVELING_NOZZLE_TEMP 120 // (°C) Only applies to E0 at this time - #define LEVELING_BED_TEMP 50 -#endif - -/** - * Enable detailed logging of G28, G29, M48, etc. - * Turn on with the command 'M111 S32'. - * NOTE: Requires a lot of flash! - */ -//#define DEBUG_LEVELING_FEATURE - -#if ANY(MESH_BED_LEVELING, AUTO_BED_LEVELING_UBL, PROBE_MANUALLY) - // Set a height for the start of manual adjustment - #define MANUAL_PROBE_START_Z 0.2 // (mm) Comment out to use the last-measured height -#endif - -#if ANY(MESH_BED_LEVELING, AUTO_BED_LEVELING_BILINEAR, AUTO_BED_LEVELING_UBL) - /** - * Gradually reduce leveling correction until a set height is reached, - * at which point movement will be level to the machine's XY plane. - * The height can be set with M420 Z - */ - #define ENABLE_LEVELING_FADE_HEIGHT - #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) - #define DEFAULT_LEVELING_FADE_HEIGHT 10.0 // (mm) Default fade height. - #endif - - /** - * For Cartesian machines, instead of dividing moves on mesh boundaries, - * split up moves into short segments like a Delta. This follows the - * contours of the bed more closely than edge-to-edge straight moves. - */ - #define SEGMENT_LEVELED_MOVES - #define LEVELED_SEGMENT_LENGTH 5.0 // (mm) Length of all segments (except the last one) - - /** - * Enable the G26 Mesh Validation Pattern tool. - */ - //#define G26_MESH_VALIDATION - #if ENABLED(G26_MESH_VALIDATION) - #define MESH_TEST_NOZZLE_SIZE 0.4 // (mm) Diameter of primary nozzle. - #define MESH_TEST_LAYER_HEIGHT 0.2 // (mm) Default layer height for G26. - #define MESH_TEST_HOTEND_TEMP 205 // (°C) Default nozzle temperature for G26. - #define MESH_TEST_BED_TEMP 60 // (°C) Default bed temperature for G26. - #define G26_XY_FEEDRATE 20 // (mm/s) Feedrate for G26 XY moves. - #define G26_XY_FEEDRATE_TRAVEL 100 // (mm/s) Feedrate for G26 XY travel moves. - #define G26_RETRACT_MULTIPLIER 1.0 // G26 Q (retraction) used by default between mesh test elements. - #endif - -#endif - -#if ANY(AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_BILINEAR) - - // Set the number of grid points per dimension. - #define GRID_MAX_POINTS_X 3 - #define GRID_MAX_POINTS_Y GRID_MAX_POINTS_X - - // Probe along the Y axis, advancing X after each column - //#define PROBE_Y_FIRST - - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - - // Beyond the probed grid, continue the implied tilt? - // Default is to maintain the height of the nearest edge. - //#define EXTRAPOLATE_BEYOND_GRID - - // - // Subdivision of the grid by Catmull-Rom method. - // Synthesizes intermediate points to produce a more detailed mesh. - // - //#define ABL_BILINEAR_SUBDIVISION - #if ENABLED(ABL_BILINEAR_SUBDIVISION) - // Number of subdivisions between probe points - #define BILINEAR_SUBDIVISIONS 3 - #endif - - #endif - -#elif ENABLED(AUTO_BED_LEVELING_UBL) - - //=========================================================================== - //========================= Unified Bed Leveling ============================ - //=========================================================================== - - //#define MESH_EDIT_GFX_OVERLAY // Display a graphics overlay while editing the mesh - - #define MESH_INSET 1 // Set Mesh bounds as an inset region of the bed - #define GRID_MAX_POINTS_X 10 // Don't use more than 15 points per axis, implementation limited. - #define GRID_MAX_POINTS_Y GRID_MAX_POINTS_X - - //#define UBL_HILBERT_CURVE // Use Hilbert distribution for less travel when probing multiple points - - //#define UBL_TILT_ON_MESH_POINTS // Use nearest mesh points with G29 J for better Z reference - //#define UBL_TILT_ON_MESH_POINTS_3POINT // Use nearest mesh points with G29 J0 (3-point) - - #define UBL_MESH_EDIT_MOVES_Z // Sophisticated users prefer no movement of nozzle - #define UBL_SAVE_ACTIVE_ON_M500 // Save the currently active mesh in the current slot on M500 - - //#define UBL_Z_RAISE_WHEN_OFF_MESH 2.5 // When the nozzle is off the mesh, this value is used - // as the Z-Height correction value. - - //#define UBL_MESH_WIZARD // Run several commands in a row to get a complete mesh - - /** - * Probing not allowed within the position of an obstacle. - */ - //#define AVOID_OBSTACLES - #if ENABLED(AVOID_OBSTACLES) - #define CLIP_W 23 // Bed clip width, should be padded a few mm over its physical size - #define CLIP_H 14 // Bed clip height, should be padded a few mm over its physical size - - // Obstacle Rectangles defined as { X1, Y1, X2, Y2 } - #define OBSTACLE1 { (X_BED_SIZE) / 4 - (CLIP_W) / 2, 0, (X_BED_SIZE) / 4 + (CLIP_W) / 2, CLIP_H } - #define OBSTACLE2 { (X_BED_SIZE) * 3 / 4 - (CLIP_W) / 2, 0, (X_BED_SIZE) * 3 / 4 + (CLIP_W) / 2, CLIP_H } - #define OBSTACLE3 { (X_BED_SIZE) / 4 - (CLIP_W) / 2, (Y_BED_SIZE) - (CLIP_H), (X_BED_SIZE) / 4 + (CLIP_W) / 2, Y_BED_SIZE } - #define OBSTACLE4 { (X_BED_SIZE) * 3 / 4 - (CLIP_W) / 2, (Y_BED_SIZE) - (CLIP_H), (X_BED_SIZE) * 3 / 4 + (CLIP_W) / 2, Y_BED_SIZE } - - // The probed grid must be inset for G29 J. This is okay, since it is - // only used to compute a linear transformation for the mesh itself. - #define G29J_MESH_TILT_MARGIN ((CLIP_H) + 1) - #endif - -#elif ENABLED(MESH_BED_LEVELING) - - //=========================================================================== - //=================================== Mesh ================================== - //=========================================================================== - - #define MESH_INSET 10 // Set Mesh bounds as an inset region of the bed - #define GRID_MAX_POINTS_X 3 // Don't use more than 7 points per axis, implementation limited. - #define GRID_MAX_POINTS_Y GRID_MAX_POINTS_X - - //#define MESH_G28_REST_ORIGIN // After homing all axes ('G28' or 'G28 XYZ') rest Z at Z_MIN_POS - -#endif // BED_LEVELING - -/** - * Add a bed leveling sub-menu for ABL or MBL. - * Include a guided procedure if manual probing is enabled. - */ -//#define LCD_BED_LEVELING - -#if ENABLED(LCD_BED_LEVELING) - #define MESH_EDIT_Z_STEP 0.025 // (mm) Step size while manually probing Z axis. - #define LCD_PROBE_Z_RANGE 4 // (mm) Z Range centered on Z_MIN_POS for LCD Z adjustment - //#define MESH_EDIT_MENU // Add a menu to edit mesh points -#endif - -// Add a menu item to move between bed corners for manual bed adjustment -//#define LCD_BED_TRAMMING - -#if ENABLED(LCD_BED_TRAMMING) - #define BED_TRAMMING_INSET_LFRB { 30, 30, 30, 30 } // (mm) Left, Front, Right, Back insets - #define BED_TRAMMING_HEIGHT 0.0 // (mm) Z height of nozzle at tramming points - #define BED_TRAMMING_Z_HOP 4.0 // (mm) Z raise between tramming points - //#define BED_TRAMMING_INCLUDE_CENTER // Move to the center after the last corner - //#define BED_TRAMMING_USE_PROBE - #if ENABLED(BED_TRAMMING_USE_PROBE) - #define BED_TRAMMING_PROBE_TOLERANCE 0.1 // (mm) - #define BED_TRAMMING_VERIFY_RAISED // After adjustment triggers the probe, re-probe to verify - //#define BED_TRAMMING_AUDIO_FEEDBACK - #endif - - /** - * Corner Leveling Order - * - * Set 2 or 4 points. When 2 points are given, the 3rd is the center of the opposite edge. - * - * LF Left-Front RF Right-Front - * LB Left-Back RB Right-Back - * - * Examples: - * - * Default {LF,RB,LB,RF} {LF,RF} {LB,LF} - * LB --------- RB LB --------- RB LB --------- RB LB --------- RB - * | 4 3 | | 3 2 | | <3> | | 1 | - * | | | | | | | <3>| - * | 1 2 | | 1 4 | | 1 2 | | 2 | - * LF --------- RF LF --------- RF LF --------- RF LF --------- RF - */ - #define BED_TRAMMING_LEVELING_ORDER { LF, RF, RB, LB } -#endif - -// @section homing - -// The center of the bed is at (X=0, Y=0) -//#define BED_CENTER_AT_0_0 - -// Manually set the home position. Leave these undefined for automatic settings. -// For DELTA this is the top-center of the Cartesian print volume. -//#define MANUAL_X_HOME_POS 0 -//#define MANUAL_Y_HOME_POS 0 -//#define MANUAL_Z_HOME_POS 0 -//#define MANUAL_I_HOME_POS 0 -//#define MANUAL_J_HOME_POS 0 -//#define MANUAL_K_HOME_POS 0 -//#define MANUAL_U_HOME_POS 0 -//#define MANUAL_V_HOME_POS 0 -//#define MANUAL_W_HOME_POS 0 - -/** - * Use "Z Safe Homing" to avoid homing with a Z probe outside the bed area. - * - * - Moves the Z probe (or nozzle) to a defined XY point before Z homing. - * - Allows Z homing only when XY positions are known and trusted. - * - If stepper drivers sleep, XY homing may be required again before Z homing. - */ -//#define Z_SAFE_HOMING - -#if ENABLED(Z_SAFE_HOMING) - #define Z_SAFE_HOMING_X_POINT X_CENTER // (mm) X point for Z homing - #define Z_SAFE_HOMING_Y_POINT Y_CENTER // (mm) Y point for Z homing - //#define Z_SAFE_HOMING_POINT_ABSOLUTE // Ignore home offsets (M206) for Z homing position -#endif - -// Homing speeds (linear=mm/min, rotational=°/min) -#define HOMING_FEEDRATE_MM_M { (50*60), (50*60), (4*60) } - -// Edit homing feedrates with M210 and MarlinUI menu items -//#define EDITABLE_HOMING_FEEDRATE - -// Validate that endstops are triggered on homing moves -#define VALIDATE_HOMING_ENDSTOPS - -// @section calibrate - -/** - * Bed Skew Compensation - * - * This feature corrects for misalignment in the XYZ axes. - * - * Take the following steps to get the bed skew in the XY plane: - * 1. Print a test square (e.g., https://www.thingiverse.com/thing:2563185) - * 2. For XY_DIAG_AC measure the diagonal A to C - * 3. For XY_DIAG_BD measure the diagonal B to D - * 4. For XY_SIDE_AD measure the edge A to D - * - * Marlin automatically computes skew factors from these measurements. - * Skew factors may also be computed and set manually: - * - * - Compute AB : SQRT(2*AC*AC+2*BD*BD-4*AD*AD)/2 - * - XY_SKEW_FACTOR : TAN(PI/2-ACOS((AC*AC-AB*AB-AD*AD)/(2*AB*AD))) - * - * If desired, follow the same procedure for XZ and YZ. - * Use these diagrams for reference: - * - * Y Z Z - * ^ B-------C ^ B-------C ^ B-------C - * | / / | / / | / / - * | / / | / / | / / - * | A-------D | A-------D | A-------D - * +-------------->X +-------------->X +-------------->Y - * XY_SKEW_FACTOR XZ_SKEW_FACTOR YZ_SKEW_FACTOR - */ -//#define SKEW_CORRECTION - -#if ENABLED(SKEW_CORRECTION) - // Input all length measurements here: - #define XY_DIAG_AC 282.8427124746 - #define XY_DIAG_BD 282.8427124746 - #define XY_SIDE_AD 200 - - // Or, set the XY skew factor directly: - //#define XY_SKEW_FACTOR 0.0 - - //#define SKEW_CORRECTION_FOR_Z - #if ENABLED(SKEW_CORRECTION_FOR_Z) - #define XZ_DIAG_AC 282.8427124746 - #define XZ_DIAG_BD 282.8427124746 - #define YZ_DIAG_AC 282.8427124746 - #define YZ_DIAG_BD 282.8427124746 - #define YZ_SIDE_AD 200 - - // Or, set the Z skew factors directly: - //#define XZ_SKEW_FACTOR 0.0 - //#define YZ_SKEW_FACTOR 0.0 - #endif - - // Enable this option for M852 to set skew at runtime - //#define SKEW_CORRECTION_GCODE -#endif - -//============================================================================= -//============================= Additional Features =========================== -//============================================================================= - -// @section eeprom - -/** - * EEPROM - * - * Persistent storage to preserve configurable settings across reboots. - * - * M500 - Store settings to EEPROM. - * M501 - Read settings from EEPROM. (i.e., Throw away unsaved changes) - * M502 - Revert settings to "factory" defaults. (Follow with M500 to init the EEPROM.) - */ -//#define EEPROM_SETTINGS // Persistent storage with M500 and M501 -//#define DISABLE_M503 // Saves ~2700 bytes of flash. Disable for release! -#define EEPROM_CHITCHAT // Give feedback on EEPROM commands. Disable to save flash. -#define EEPROM_BOOT_SILENT // Keep M503 quiet and only give errors during first load -#if ENABLED(EEPROM_SETTINGS) - //#define EEPROM_AUTO_INIT // Init EEPROM automatically on any errors. - //#define EEPROM_INIT_NOW // Init EEPROM on first boot after a new build. -#endif - -// @section host - -// -// Host Keepalive -// -// When enabled Marlin will send a busy status message to the host -// every couple of seconds when it can't accept commands. -// -#define HOST_KEEPALIVE_FEATURE // Disable this if your host doesn't like keepalive messages -#define DEFAULT_KEEPALIVE_INTERVAL 2 // Number of seconds between "busy" messages. Set with M113. -#define BUSY_WHILE_HEATING // Some hosts require "busy" messages even during heating - -// @section units - -// -// G20/G21 Inch mode support -// -//#define INCH_MODE_SUPPORT - -// -// M149 Set temperature units support -// -//#define TEMPERATURE_UNITS_SUPPORT - -// @section temperature - -// -// Preheat Constants - Up to 10 are supported without changes -// -#define PREHEAT_1_LABEL "PLA" -#define PREHEAT_1_TEMP_HOTEND 180 -#define PREHEAT_1_TEMP_BED 70 -#define PREHEAT_1_TEMP_CHAMBER 35 -#define PREHEAT_1_FAN_SPEED 0 // Value from 0 to 255 - -#define PREHEAT_2_LABEL "ABS" -#define PREHEAT_2_TEMP_HOTEND 240 -#define PREHEAT_2_TEMP_BED 110 -#define PREHEAT_2_TEMP_CHAMBER 35 -#define PREHEAT_2_FAN_SPEED 0 // Value from 0 to 255 - -/** - * @section nozzle park - * - * Nozzle Park - * - * Park the nozzle at the given XYZ position on idle or G27. - * - * The "P" parameter controls the action applied to the Z axis: - * - * P0 (Default) If Z is below park Z raise the nozzle. - * P1 Raise the nozzle always to Z-park height. - * P2 Raise the nozzle by Z-park amount, limited to Z_MAX_POS. - */ -//#define NOZZLE_PARK_FEATURE - -#if ENABLED(NOZZLE_PARK_FEATURE) - // Specify a park position as { X, Y, Z_raise } - #define NOZZLE_PARK_POINT { (X_MIN_POS + 10), (Y_MAX_POS - 10), 20 } - #define NOZZLE_PARK_MOVE 0 // Park motion: 0 = XY Move, 1 = X Only, 2 = Y Only, 3 = X before Y, 4 = Y before X - #define NOZZLE_PARK_Z_RAISE_MIN 2 // (mm) Always raise Z by at least this distance - #define NOZZLE_PARK_XY_FEEDRATE 100 // (mm/s) X and Y axes feedrate (also used for delta Z axis) - #define NOZZLE_PARK_Z_FEEDRATE 5 // (mm/s) Z axis feedrate (not used for delta printers) -#endif - -/** - * @section nozzle clean - * - * Clean Nozzle Feature - * - * Adds the G12 command to perform a nozzle cleaning process. - * - * Parameters: - * P Pattern - * S Strokes / Repetitions - * T Triangles (P1 only) - * - * Patterns: - * P0 Straight line (default). This process requires a sponge type material - * at a fixed bed location. "S" specifies strokes (i.e. back-forth motions) - * between the start / end points. - * - * P1 Zig-zag pattern between (X0, Y0) and (X1, Y1), "T" specifies the - * number of zig-zag triangles to do. "S" defines the number of strokes. - * Zig-zags are done in whichever is the narrower dimension. - * For example, "G12 P1 S1 T3" will execute: - * - * -- - * | (X0, Y1) | /\ /\ /\ | (X1, Y1) - * | | / \ / \ / \ | - * A | | / \ / \ / \ | - * | | / \ / \ / \ | - * | (X0, Y0) | / \/ \/ \ | (X1, Y0) - * -- +--------------------------------+ - * |________|_________|_________| - * T1 T2 T3 - * - * P2 Circular pattern with middle at NOZZLE_CLEAN_CIRCLE_MIDDLE. - * "R" specifies the radius. "S" specifies the stroke count. - * Before starting, the nozzle moves to NOZZLE_CLEAN_START_POINT. - * - * Caveats: The ending Z should be the same as starting Z. - */ -//#define NOZZLE_CLEAN_FEATURE - -#if ENABLED(NOZZLE_CLEAN_FEATURE) - #define NOZZLE_CLEAN_PATTERN_LINE // Provide 'G12 P0' - a simple linear cleaning pattern - #define NOZZLE_CLEAN_PATTERN_ZIGZAG // Provide 'G12 P1' - a zigzag cleaning pattern - #define NOZZLE_CLEAN_PATTERN_CIRCLE // Provide 'G12 P2' - a circular cleaning pattern - - // Default pattern to use when 'P' is not provided to G12. One of the enabled options above. - #define NOZZLE_CLEAN_DEFAULT_PATTERN 0 - - #define NOZZLE_CLEAN_STROKES 12 // Default number of pattern repetitions - - #if ENABLED(NOZZLE_CLEAN_PATTERN_ZIGZAG) - #define NOZZLE_CLEAN_TRIANGLES 3 // Default number of triangles - #endif - - // Specify positions for each tool as { { X, Y, Z }, { X, Y, Z } } - // Dual hotend system may use { { -20, (Y_BED_SIZE / 2), (Z_MIN_POS + 1) }, { 420, (Y_BED_SIZE / 2), (Z_MIN_POS + 1) }} - #define NOZZLE_CLEAN_START_POINT { { 30, 30, (Z_MIN_POS + 1) } } - #define NOZZLE_CLEAN_END_POINT { { 100, 60, (Z_MIN_POS + 1) } } - - #if ENABLED(NOZZLE_CLEAN_PATTERN_CIRCLE) - #define NOZZLE_CLEAN_CIRCLE_RADIUS 6.5 // (mm) Circular pattern radius - #define NOZZLE_CLEAN_CIRCLE_FN 10 // Circular pattern circle number of segments - #define NOZZLE_CLEAN_CIRCLE_MIDDLE NOZZLE_CLEAN_START_POINT // Middle point of circle - #endif - - // Move the nozzle to the initial position after cleaning - #define NOZZLE_CLEAN_GOBACK - - // For a purge/clean station that's always at the gantry height (thus no Z move) - //#define NOZZLE_CLEAN_NO_Z - - // For a purge/clean station mounted on the X axis - //#define NOZZLE_CLEAN_NO_Y - - // Require a minimum hotend temperature for cleaning - #define NOZZLE_CLEAN_MIN_TEMP 170 - //#define NOZZLE_CLEAN_HEATUP // Heat up the nozzle instead of skipping wipe - - // Explicit wipe G-code script applies to a G12 with no arguments. - //#define WIPE_SEQUENCE_COMMANDS "G1 X-17 Y25 Z10 F4000\nG1 Z1\nM114\nG1 X-17 Y25\nG1 X-17 Y95\nG1 X-17 Y25\nG1 X-17 Y95\nG1 X-17 Y25\nG1 X-17 Y95\nG1 X-17 Y25\nG1 X-17 Y95\nG1 X-17 Y25\nG1 X-17 Y95\nG1 X-17 Y25\nG1 X-17 Y95\nG1 Z15\nM400\nG0 X-10.0 Y-9.0" - -#endif - -// @section host - -/** - * Print Job Timer - * - * Automatically start and stop the print job timer on M104/M109/M140/M190/M141/M191. - * The print job timer will only be stopped if the bed/chamber target temp is - * below BED_MINTEMP/CHAMBER_MINTEMP. - * - * M104 (hotend, no wait) - high temp = none, low temp = stop timer - * M109 (hotend, wait) - high temp = start timer, low temp = stop timer - * M140 (bed, no wait) - high temp = none, low temp = stop timer - * M190 (bed, wait) - high temp = start timer, low temp = none - * M141 (chamber, no wait) - high temp = none, low temp = stop timer - * M191 (chamber, wait) - high temp = start timer, low temp = none - * - * For M104/M109, high temp is anything over EXTRUDE_MINTEMP / 2. - * For M140/M190, high temp is anything over BED_MINTEMP. - * For M141/M191, high temp is anything over CHAMBER_MINTEMP. - * - * The timer can also be controlled with the following commands: - * - * M75 - Start the print job timer - * M76 - Pause the print job timer - * M77 - Stop the print job timer - */ -#define PRINTJOB_TIMER_AUTOSTART - -// @section stats - -/** - * Print Counter - * - * Track statistical data such as: - * - * - Total print jobs - * - Total successful print jobs - * - Total failed print jobs - * - Total time printing - * - * View the current statistics with M78. - */ -//#define PRINTCOUNTER -#if ENABLED(PRINTCOUNTER) - #define PRINTCOUNTER_SAVE_INTERVAL 60 // (minutes) EEPROM save interval during print. A value of 0 will save stats at end of print. -#endif - -// @section security - -/** - * Password - * - * Set a numerical password for the printer which can be requested: - * - * - When the printer boots up - * - Upon opening the 'Print from Media' Menu - * - When SD printing is completed or aborted - * - * The following G-codes can be used: - * - * M510 - Lock Printer. Blocks all commands except M511. - * M511 - Unlock Printer. - * M512 - Set, Change and Remove Password. - * - * If you forget the password and get locked out you'll need to re-flash - * the firmware with the feature disabled, reset EEPROM, and (optionally) - * re-flash the firmware again with this feature enabled. - */ -//#define PASSWORD_FEATURE -#if ENABLED(PASSWORD_FEATURE) - #define PASSWORD_LENGTH 4 // (#) Number of digits (1-9). 3 or 4 is recommended - #define PASSWORD_ON_STARTUP - #define PASSWORD_UNLOCK_GCODE // Unlock with the M511 P command. Disable to prevent brute-force attack. - #define PASSWORD_CHANGE_GCODE // Change the password with M512 P S. - //#define PASSWORD_ON_SD_PRINT_MENU // This does not prevent G-codes from running - //#define PASSWORD_AFTER_SD_PRINT_END - //#define PASSWORD_AFTER_SD_PRINT_ABORT - //#include "Configuration_Secure.h" // External file with PASSWORD_DEFAULT_VALUE -#endif - -// @section media - -/** - * SD CARD - * - * SD Card support is disabled by default. If your controller has an SD slot, - * you must uncomment the following option or it won't work. - */ -//#define SDSUPPORT - -/** - * SD CARD: ENABLE CRC - * - * Use CRC checks and retries on the SD communication. - */ -#if ENABLED(SDSUPPORT) - //#define SD_CHECK_AND_RETRY -#endif - -// @section interface - -/** - * LCD LANGUAGE - * - * Select the language to display on the LCD. These languages are available: - * - * en, an, bg, ca, cz, da, de, el, el_CY, es, eu, fi, fr, gl, hr, hu, it, - * jp_kana, ko_KR, nl, pl, pt, pt_br, ro, ru, sk, sv, tr, uk, vi, zh_CN, zh_TW - * - * :{ 'en':'English', 'an':'Aragonese', 'bg':'Bulgarian', 'ca':'Catalan', 'cz':'Czech', 'da':'Danish', 'de':'German', 'el':'Greek (Greece)', 'el_CY':'Greek (Cyprus)', 'es':'Spanish', 'eu':'Basque-Euskera', 'fi':'Finnish', 'fr':'French', 'gl':'Galician', 'hr':'Croatian', 'hu':'Hungarian', 'it':'Italian', 'jp_kana':'Japanese', 'ko_KR':'Korean (South Korea)', 'nl':'Dutch', 'pl':'Polish', 'pt':'Portuguese', 'pt_br':'Portuguese (Brazilian)', 'ro':'Romanian', 'ru':'Russian', 'sk':'Slovak', 'sv':'Swedish', 'tr':'Turkish', 'uk':'Ukrainian', 'vi':'Vietnamese', 'zh_CN':'Chinese (Simplified)', 'zh_TW':'Chinese (Traditional)' } - */ -#define LCD_LANGUAGE en - -/** - * LCD Character Set - * - * Note: This option is NOT applicable to Graphical Displays. - * - * All character-based LCDs provide ASCII plus one of these - * language extensions: - * - * - JAPANESE ... the most common - * - WESTERN ... with more accented characters - * - CYRILLIC ... for the Russian language - * - * To determine the language extension installed on your controller: - * - * - Compile and upload with LCD_LANGUAGE set to 'test' - * - Click the controller to view the LCD menu - * - The LCD will display Japanese, Western, or Cyrillic text - * - * See https://marlinfw.org/docs/development/lcd_language.html - * - * :['JAPANESE', 'WESTERN', 'CYRILLIC'] - */ -#define DISPLAY_CHARSET_HD44780 JAPANESE - -/** - * Info Screen Style (0:Classic, 1:Průša, 2:CNC) - * - * :[0:'Classic', 1:'Průša', 2:'CNC'] - */ -#define LCD_INFO_SCREEN_STYLE 0 - -/** - * LCD Menu Items - * - * Disable all menus and only display the Status Screen, or - * just remove some extraneous menu items to recover space. - */ -//#define NO_LCD_MENUS -//#define SLIM_LCD_MENUS - -// -// ENCODER SETTINGS -// -// This option overrides the default number of encoder pulses needed to -// produce one step. Should be increased for high-resolution encoders. -// -//#define ENCODER_PULSES_PER_STEP 4 - -// -// Use this option to override the number of step signals required to -// move between next/prev menu items. -// -//#define ENCODER_STEPS_PER_MENU_ITEM 1 - -/** - * Encoder Direction Options - * - * Test your encoder's behavior first with both options disabled. - * - * Reversed Value Edit and Menu Nav? Enable REVERSE_ENCODER_DIRECTION. - * Reversed Menu Navigation only? Enable REVERSE_MENU_DIRECTION. - * Reversed Value Editing only? Enable BOTH options. - */ - -// -// This option reverses the encoder direction everywhere. -// -// Set this option if CLOCKWISE causes values to DECREASE -// -//#define REVERSE_ENCODER_DIRECTION - -// -// This option reverses the encoder direction for navigating LCD menus. -// -// If CLOCKWISE normally moves DOWN this makes it go UP. -// If CLOCKWISE normally moves UP this makes it go DOWN. -// -//#define REVERSE_MENU_DIRECTION - -// -// This option reverses the encoder direction for Select Screen. -// -// If CLOCKWISE normally moves LEFT this makes it go RIGHT. -// If CLOCKWISE normally moves RIGHT this makes it go LEFT. -// -//#define REVERSE_SELECT_DIRECTION - -// -// Encoder EMI Noise Filter -// -// This option increases encoder samples to filter out phantom encoder clicks caused by EMI noise. -// -//#define ENCODER_NOISE_FILTER -#if ENABLED(ENCODER_NOISE_FILTER) - #define ENCODER_SAMPLES 10 -#endif - -// -// Individual Axis Homing -// -// Add individual axis homing items (Home X, Home Y, and Home Z) to the LCD menu. -// -//#define INDIVIDUAL_AXIS_HOMING_MENU -//#define INDIVIDUAL_AXIS_HOMING_SUBMENU - -// -// SPEAKER/BUZZER -// -// If you have a speaker that can produce tones, enable it here. -// By default Marlin assumes you have a buzzer with a fixed frequency. -// -//#define SPEAKER - -// -// The duration and frequency for the UI feedback sound. -// Set these to 0 to disable audio feedback in the LCD menus. -// -// Note: Test audio output with the G-Code: -// M300 S P -// -//#define LCD_FEEDBACK_FREQUENCY_DURATION_MS 2 -//#define LCD_FEEDBACK_FREQUENCY_HZ 5000 - -// -// Tone queue size, used to keep beeps from blocking execution. -// Default is 4, or override here. Costs 4 bytes of SRAM per entry. -// -//#define TONE_QUEUE_LENGTH 4 - -// -// A sequence of tones to play at startup, in pairs of tone (Hz), duration (ms). -// Silence in-between tones. -// -//#define STARTUP_TUNE { 698, 300, 0, 50, 523, 50, 0, 25, 494, 50, 0, 25, 523, 100, 0, 50, 554, 300, 0, 100, 523, 300 } - -//============================================================================= -//======================== LCD / Controller Selection ========================= -//======================== (Character-based LCDs) ========================= -//============================================================================= -// @section lcd - -// -// RepRapDiscount Smart Controller. -// https://reprap.org/wiki/RepRapDiscount_Smart_Controller -// -// Note: Usually sold with a white PCB. -// -//#define REPRAP_DISCOUNT_SMART_CONTROLLER - -// -// GT2560 (YHCB2004) LCD Display -// -// Requires Testato, Koepel softwarewire library and -// Andriy Golovnya's LiquidCrystal_AIP31068 library. -// -//#define YHCB2004 - -// -// Original RADDS LCD Display+Encoder+SDCardReader -// https://web.archive.org/web/20200719145306/doku.radds.org/dokumentation/lcd-display/ -// -//#define RADDS_DISPLAY - -// -// ULTIMAKER Controller. -// -//#define ULTIMAKERCONTROLLER - -// -// ULTIPANEL as seen on Thingiverse. -// -//#define ULTIPANEL - -// -// PanelOne from T3P3 (via RAMPS 1.4 AUX2/AUX3) -// https://reprap.org/wiki/PanelOne -// -//#define PANEL_ONE - -// -// GADGETS3D G3D LCD/SD Controller -// https://reprap.org/wiki/RAMPS_1.3/1.4_GADGETS3D_Shield_with_Panel -// -// Note: Usually sold with a blue PCB. -// -//#define G3D_PANEL - -// -// RigidBot Panel V1.0 -// -//#define RIGIDBOT_PANEL - -// -// Makeboard 3D Printer Parts 3D Printer Mini Display 1602 Mini Controller -// https://www.aliexpress.com/item/32765887917.html -// -//#define MAKEBOARD_MINI_2_LINE_DISPLAY_1602 - -// -// ANET and Tronxy 20x4 Controller -// -//#define ZONESTAR_LCD // Requires ADC_KEYPAD_PIN to be assigned to an analog pin. - // This LCD is known to be susceptible to electrical interference - // which scrambles the display. Pressing any button clears it up. - // This is a LCD2004 display with 5 analog buttons. - -// -// Generic 16x2, 16x4, 20x2, or 20x4 character-based LCD. -// -//#define ULTRA_LCD - -//============================================================================= -//======================== LCD / Controller Selection ========================= -//===================== (I2C and Shift-Register LCDs) ===================== -//============================================================================= - -// -// CONTROLLER TYPE: I2C -// -// Note: These controllers require the installation of Arduino's LiquidCrystal_I2C -// library. For more info: https://github.com/kiyoshigawa/LiquidCrystal_I2C -// - -// -// Elefu RA Board Control Panel -// https://web.archive.org/web/20140823033947/www.elefu.com/index.php?route=product/product&product_id=53 -// -//#define RA_CONTROL_PANEL - -// -// Sainsmart (YwRobot) LCD Displays -// -// These require LiquidCrystal_I2C library: -// https://github.com/MarlinFirmware/New-LiquidCrystal -// https://github.com/fmalpartida/New-LiquidCrystal/wiki -// -//#define LCD_SAINSMART_I2C_1602 -//#define LCD_SAINSMART_I2C_2004 - -// -// Generic LCM1602 LCD adapter -// -//#define LCM1602 - -// -// PANELOLU2 LCD with status LEDs, -// separate encoder and click inputs. -// -// Note: This controller requires Arduino's LiquidTWI2 library v1.2.3 or later. -// For more info: https://github.com/lincomatic/LiquidTWI2 -// -// Note: The PANELOLU2 encoder click input can either be directly connected to -// a pin (if BTN_ENC defined to != -1) or read through I2C (when BTN_ENC == -1). -// -//#define LCD_I2C_PANELOLU2 - -// -// Panucatt VIKI LCD with status LEDs, -// integrated click & L/R/U/D buttons, separate encoder inputs. -// -//#define LCD_I2C_VIKI - -// -// CONTROLLER TYPE: Shift register panels -// - -// -// 2-wire Non-latching LCD SR from https://github.com/fmalpartida/New-LiquidCrystal/wiki/schematics#user-content-ShiftRegister_connection -// LCD configuration: https://reprap.org/wiki/SAV_3D_LCD -// -//#define SAV_3DLCD - -// -// 3-wire SR LCD with strobe using 74HC4094 -// https://github.com/mikeshub/SailfishLCD -// Uses the code directly from Sailfish -// -//#define FF_INTERFACEBOARD - -// -// TFT GLCD Panel with Marlin UI -// Panel connected to main board by SPI or I2C interface. -// See https://github.com/Serhiy-K/TFTGLCDAdapter -// -//#define TFTGLCD_PANEL_SPI -//#define TFTGLCD_PANEL_I2C - -//============================================================================= -//======================= LCD / Controller Selection ======================= -//========================= (Graphical LCDs) ======================== -//============================================================================= - -// -// CONTROLLER TYPE: Graphical 128x64 (DOGM) -// -// IMPORTANT: The U8glib library is required for Graphical Display! -// https://github.com/olikraus/U8glib_Arduino -// -// NOTE: If the LCD is unresponsive you may need to reverse the plugs. -// - -// -// RepRapDiscount FULL GRAPHIC Smart Controller -// https://reprap.org/wiki/RepRapDiscount_Full_Graphic_Smart_Controller -// -//#define REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER - -// -// K.3D Full Graphic Smart Controller -// -//#define K3D_FULL_GRAPHIC_SMART_CONTROLLER - -// -// ReprapWorld Graphical LCD -// https://reprapworld.com/electronics/3d-printer-modules/autonomous-printing/graphical-lcd-screen-v1-0/ -// -//#define REPRAPWORLD_GRAPHICAL_LCD - -// -// Activate one of these if you have a Panucatt Devices -// Viki 2.0 or mini Viki with Graphic LCD -// https://www.panucatt.com -// -//#define VIKI2 -//#define miniVIKI - -// -// Alfawise Ex8 printer LCD marked as WYH L12864 COG -// -//#define WYH_L12864 - -// -// MakerLab Mini Panel with graphic -// controller and SD support - https://reprap.org/wiki/Mini_panel -// -//#define MINIPANEL - -// -// MaKr3d Makr-Panel with graphic controller and SD support. -// https://reprap.org/wiki/MaKrPanel -// -//#define MAKRPANEL - -// -// Adafruit ST7565 Full Graphic Controller. -// https://github.com/eboston/Adafruit-ST7565-Full-Graphic-Controller/ -// -//#define ELB_FULL_GRAPHIC_CONTROLLER - -// -// BQ LCD Smart Controller shipped by -// default with the BQ Hephestos 2 and Witbox 2. -// -//#define BQ_LCD_SMART_CONTROLLER - -// -// Cartesio UI -// https://web.archive.org/web/20180605050442/mauk.cc/webshop/cartesio-shop/electronics/user-interface -// -//#define CARTESIO_UI - -// -// LCD for Melzi Card with Graphical LCD -// -//#define LCD_FOR_MELZI - -// -// Original Ulticontroller from Ultimaker 2 printer with SSD1309 I2C display and encoder -// https://github.com/Ultimaker/Ultimaker2/tree/master/1249_Ulticontroller_Board_(x1) -// -//#define ULTI_CONTROLLER - -// -// MKS MINI12864 with graphic controller and SD support -// https://reprap.org/wiki/MKS_MINI_12864 -// -//#define MKS_MINI_12864 - -// -// MKS MINI12864 V3 is an alias for FYSETC_MINI_12864_2_1. Type A/B. NeoPixel RGB Backlight. -// -//#define MKS_MINI_12864_V3 - -// -// MKS LCD12864A/B with graphic controller and SD support. Follows MKS_MINI_12864 pinout. -// https://www.aliexpress.com/item/33018110072.html -// -//#define MKS_LCD12864A -//#define MKS_LCD12864B - -// -// FYSETC variant of the MINI12864 graphic controller with SD support -// https://wiki.fysetc.com/Mini12864_Panel/ -// -//#define FYSETC_MINI_12864_X_X // Type C/D/E/F. No tunable RGB Backlight by default -//#define FYSETC_MINI_12864_1_2 // Type C/D/E/F. Simple RGB Backlight (always on) -//#define FYSETC_MINI_12864_2_0 // Type A/B. Discreet RGB Backlight -//#define FYSETC_MINI_12864_2_1 // Type A/B. NeoPixel RGB Backlight -//#define FYSETC_GENERIC_12864_1_1 // Larger display with basic ON/OFF backlight. - -// -// BigTreeTech Mini 12864 V1.0 / V2.0 is an alias for FYSETC_MINI_12864_2_1. Type A/B. NeoPixel RGB Backlight. -// https://github.com/bigtreetech/MINI-12864 -// -//#define BTT_MINI_12864 - -// -// BEEZ MINI 12864 is an alias for FYSETC_MINI_12864_2_1. Type A/B. NeoPixel RGB Backlight. -// -//#define BEEZ_MINI_12864 - -// -// Factory display for Creality CR-10 / CR-7 / Ender-3 -// https://marlinfw.org/docs/hardware/controllers.html#cr10_stockdisplay -// -// Connect to EXP1 on RAMPS and compatible boards. -// -//#define CR10_STOCKDISPLAY - -// -// Ender-2 OEM display, a variant of the MKS_MINI_12864 -// -//#define ENDER2_STOCKDISPLAY - -// -// ANET and Tronxy 128×64 Full Graphics Controller as used on Anet A6 -// -//#define ANET_FULL_GRAPHICS_LCD - -// -// GUCOCO CTC 128×64 Full Graphics Controller as used on GUCOCO CTC A10S -// -//#define CTC_A10S_A13 - -// -// AZSMZ 12864 LCD with SD -// https://www.aliexpress.com/item/32837222770.html -// -//#define AZSMZ_12864 - -// -// Silvergate GLCD controller -// https://github.com/android444/Silvergate -// -//#define SILVER_GATE_GLCD_CONTROLLER - -// -// eMotion Tech LCD with SD -// https://www.reprap-france.com/produit/1234568748-ecran-graphique-128-x-64-points-2-1 -// -//#define EMOTION_TECH_LCD - -//============================================================================= -//============================== OLED Displays ============================== -//============================================================================= - -// -// SSD1306 OLED full graphics generic display -// -//#define U8GLIB_SSD1306 - -// -// SAV OLEd LCD module support using either SSD1306 or SH1106 based LCD modules -// -//#define SAV_3DGLCD -#if ENABLED(SAV_3DGLCD) - #define U8GLIB_SSD1306 - //#define U8GLIB_SH1106 -#endif - -// -// TinyBoy2 128x64 OLED / Encoder Panel -// -//#define OLED_PANEL_TINYBOY2 - -// -// MKS OLED 1.3" 128×64 Full Graphics Controller -// https://reprap.org/wiki/MKS_12864OLED -// -// Tiny, but very sharp OLED display -// -//#define MKS_12864OLED // Uses the SH1106 controller -//#define MKS_12864OLED_SSD1306 // Uses the SSD1306 controller - -// -// Zonestar OLED 128×64 Full Graphics Controller -// -//#define ZONESTAR_12864LCD // Graphical (DOGM) with ST7920 controller -//#define ZONESTAR_12864OLED // 1.3" OLED with SH1106 controller -//#define ZONESTAR_12864OLED_SSD1306 // 0.96" OLED with SSD1306 controller - -// -// Einstart S OLED SSD1306 -// -//#define U8GLIB_SH1106_EINSTART - -// -// Overlord OLED display/controller with i2c buzzer and LEDs -// -//#define OVERLORD_OLED - -// -// FYSETC OLED 2.42" 128×64 Full Graphics Controller with WS2812 RGB -// Where to find : https://www.aliexpress.com/item/4000345255731.html -//#define FYSETC_242_OLED_12864 // Uses the SSD1309 controller - -// -// K.3D SSD1309 OLED 2.42" 128×64 Full Graphics Controller -// -//#define K3D_242_OLED_CONTROLLER // Software SPI - -//============================================================================= -//========================== Extensible UI Displays =========================== -//============================================================================= - -/** - * DGUS Touch Display with DWIN OS. (Choose one.) - * - * ORIGIN (Marlin DWIN_SET) - * - Download https://github.com/coldtobi/Marlin_DGUS_Resources - * - Copy the downloaded DWIN_SET folder to the SD card. - * - Product: https://www.aliexpress.com/item/32993409517.html - * - * FYSETC (Supplier default) - * - Download https://github.com/FYSETC/FYSTLCD-2.0 - * - Copy the downloaded SCREEN folder to the SD card. - * - Product: https://www.aliexpress.com/item/32961471929.html - * - * HIPRECY (Supplier default) - * - Download https://github.com/HiPrecy/Touch-Lcd-LEO - * - Copy the downloaded DWIN_SET folder to the SD card. - * - * MKS (MKS-H43) (Supplier default) - * - Download https://github.com/makerbase-mks/MKS-H43 - * - Copy the downloaded DWIN_SET folder to the SD card. - * - Product: https://www.aliexpress.com/item/1005002008179262.html - * - * RELOADED (T5UID1) - * - Download https://github.com/Neo2003/DGUS-reloaded/releases - * - Copy the downloaded DWIN_SET folder to the SD card. - * - * IA_CREALITY (T5UID1) - * - Download https://github.com/InsanityAutomation/Marlin/raw/CrealityDwin_2.0/TM3D_Combined480272_Landscape_V7.7z - * - Copy the downloaded DWIN_SET folder to the SD card. - * - * E3S1PRO (T5L) - * - Download https://github.com/CrealityOfficial/Ender-3S1/archive/3S1_Plus_Screen.zip - * - Copy the downloaded DWIN_SET folder to the SD card. - * - * Flash display with DGUS Displays for Marlin: - * - Format the SD card to FAT32 with an allocation size of 4kb. - * - Download files as specified for your type of display. - * - Plug the microSD card into the back of the display. - * - Boot the display and wait for the update to complete. - * - * :[ 'ORIGIN', 'FYSETC', 'HYPRECY', 'MKS', 'RELOADED', 'IA_CREALITY', 'E3S1PRO' ] - */ -//#define DGUS_LCD_UI ORIGIN -#if DGUS_UI_IS(MKS) - #define USE_MKS_GREEN_UI -#elif DGUS_UI_IS(IA_CREALITY) - //#define LCD_SCREEN_ROTATE 90 // Portrait Mode or 800x480 displays - //#define IA_CREALITY_BOOT_DELAY 1500 // (ms) -#endif - -// -// Touch-screen LCD for Malyan M200/M300 printers -// -//#define MALYAN_LCD - -// -// Touch UI for FTDI EVE (FT800/FT810) displays -// See Configuration_adv.h for all configuration options. -// -//#define TOUCH_UI_FTDI_EVE - -// -// Touch-screen LCD for Anycubic Chiron -// -//#define ANYCUBIC_LCD_CHIRON - -// -// Touch-screen LCD for Anycubic i3 Mega -// -//#define ANYCUBIC_LCD_I3MEGA -#if ENABLED(ANYCUBIC_LCD_I3MEGA) - //#define ANYCUBIC_LCD_GCODE_EXT // Add ".gcode" to menu entries for DGUS clone compatibility -#endif - -// -// Touch-screen LCD for Anycubic Vyper -// -//#define ANYCUBIC_LCD_VYPER - -// -// Sovol SV-06 Resistive Touch Screen -// -//#define SOVOL_SV06_RTS - -// -// 320x240 Nextion 2.8" serial TFT Resistive Touch Screen NX3224T028 -// -//#define NEXTION_TFT - -// -// Third-party or vendor-customized controller interfaces. -// Sources should be installed in 'src/lcd/extui'. -// -//#define EXTENSIBLE_UI - -#if ENABLED(EXTENSIBLE_UI) - //#define EXTUI_LOCAL_BEEPER // Enables use of local Beeper pin with external display -#endif - -//============================================================================= -//=============================== Graphical TFTs ============================== -//============================================================================= - -/** - * Specific TFT Model Presets. Enable one of the following options - * or enable TFT_GENERIC and set sub-options. - */ - -// -// 480x320, 3.5", SPI Display with Rotary Encoder from MKS -// Usually paired with MKS Robin Nano V2 & V3 -// https://github.com/makerbase-mks/MKS-TFT-Hardware/tree/master/MKS%20TS35 -// -//#define MKS_TS35_V2_0 - -// -// 320x240, 2.4", FSMC Display From MKS -// Usually paired with MKS Robin Nano V1.2 -// -//#define MKS_ROBIN_TFT24 - -// -// 320x240, 2.8", FSMC Display From MKS -// Usually paired with MKS Robin Nano V1.2 -// -//#define MKS_ROBIN_TFT28 - -// -// 320x240, 3.2", FSMC Display From MKS -// Usually paired with MKS Robin Nano V1.2 -// -//#define MKS_ROBIN_TFT32 - -// -// 480x320, 3.5", FSMC Display From MKS -// Usually paired with MKS Robin Nano V1.2 -// -//#define MKS_ROBIN_TFT35 - -// -// 480x272, 4.3", FSMC Display From MKS -// -//#define MKS_ROBIN_TFT43 - -// -// 320x240, 3.2", FSMC Display From MKS -// Usually paired with MKS Robin -// -//#define MKS_ROBIN_TFT_V1_1R - -// -// 480x320, 3.5", FSMC Stock Display from Tronxy -// -//#define TFT_TRONXY_X5SA - -// -// 480x320, 3.5", FSMC Stock Display from AnyCubic -// -//#define ANYCUBIC_TFT35 - -// -// 320x240, 2.8", FSMC Stock Display from Longer/Alfawise -// -//#define LONGER_LK_TFT28 - -// -// 320x240, 2.8", FSMC Stock Display from ET4 -// -//#define ANET_ET4_TFT28 - -// -// 480x320, 3.5", FSMC Stock Display from ET5 -// -//#define ANET_ET5_TFT35 - -// -// 1024x600, 7", RGB Stock Display with Rotary Encoder from BIQU BX -// https://github.com/bigtreetech/BIQU-BX/tree/master/Hardware -// -//#define BIQU_BX_TFT70 - -// -// 480x320, 3.5", SPI Stock Display with Rotary Encoder from BIQU B1 SE Series -// https://github.com/bigtreetech/TFT35-SPI/tree/master/v1 -// -//#define BTT_TFT35_SPI_V1_0 - -// -// Generic TFT with detailed options -// -//#define TFT_GENERIC -#if ENABLED(TFT_GENERIC) - // :[ 'AUTO', 'ST7735', 'ST7789', 'ST7796', 'R61505', 'ILI9328', 'ILI9341', 'ILI9488' ] - #define TFT_DRIVER AUTO - - // Interface. Enable one of the following options: - //#define TFT_INTERFACE_FSMC - //#define TFT_INTERFACE_SPI - - // TFT Resolution. Enable one of the following options: - //#define TFT_RES_320x240 - //#define TFT_RES_480x272 - //#define TFT_RES_480x320 - //#define TFT_RES_1024x600 -#endif - -/** - * TFT UI - User Interface Selection. Enable one of the following options: - * - * TFT_CLASSIC_UI - Emulated DOGM - 128x64 Upscaled - * TFT_COLOR_UI - Marlin Default Menus, Touch Friendly, using full TFT capabilities - * TFT_LVGL_UI - A Modern UI using LVGL - * - * For LVGL_UI also copy the 'assets' folder from the build directory to the - * root of your SD card, together with the compiled firmware. - */ -//#define TFT_CLASSIC_UI -//#define TFT_COLOR_UI -//#define TFT_LVGL_UI - -#if ENABLED(TFT_COLOR_UI) - /** - * TFT Font for Color UI. Choose one of the following: - * - * NOTOSANS - Default font with anti-aliasing. Supports Latin Extended and non-Latin characters. - * UNIFONT - Lightweight font, no anti-aliasing. Supports Latin Extended and non-Latin characters. - * HELVETICA - Lightweight font, no anti-aliasing. Supports Basic Latin (0x0020-0x007F) and Latin-1 Supplement (0x0080-0x00FF) characters only. - */ - #define TFT_FONT NOTOSANS - - /** - * TFT Theme for Color UI. Choose one of the following or add a new one to 'Marlin/src/lcd/tft/themes' directory - * - * BLUE_MARLIN - Default theme with 'midnight blue' background - * BLACK_MARLIN - Theme with 'black' background - * ANET_BLACK - Theme used for Anet ET4/5 - */ - #define TFT_THEME BLACK_MARLIN - - //#define TFT_SHARED_IO // I/O is shared between TFT display and other devices. Disable async data transfer. - - #define COMPACT_MARLIN_BOOT_LOGO // Use compressed data to save Flash space -#endif - -#if ENABLED(TFT_LVGL_UI) - //#define MKS_WIFI_MODULE // MKS WiFi module -#endif - -/** - * TFT Rotation. Set to one of the following values: - * - * TFT_ROTATE_90, TFT_ROTATE_90_MIRROR_X, TFT_ROTATE_90_MIRROR_Y, - * TFT_ROTATE_180, TFT_ROTATE_180_MIRROR_X, TFT_ROTATE_180_MIRROR_Y, - * TFT_ROTATE_270, TFT_ROTATE_270_MIRROR_X, TFT_ROTATE_270_MIRROR_Y, - * TFT_MIRROR_X, TFT_MIRROR_Y, TFT_NO_ROTATION - * - * :{ 'TFT_NO_ROTATION':'None', 'TFT_ROTATE_90':'90°', 'TFT_ROTATE_90_MIRROR_X':'90° (Mirror X)', 'TFT_ROTATE_90_MIRROR_Y':'90° (Mirror Y)', 'TFT_ROTATE_180':'180°', 'TFT_ROTATE_180_MIRROR_X':'180° (Mirror X)', 'TFT_ROTATE_180_MIRROR_Y':'180° (Mirror Y)', 'TFT_ROTATE_270':'270°', 'TFT_ROTATE_270_MIRROR_X':'270° (Mirror X)', 'TFT_ROTATE_270_MIRROR_Y':'270° (Mirror Y)', 'TFT_MIRROR_X':'Mirror X', 'TFT_MIRROR_Y':'Mirror Y' } - */ -//#define TFT_ROTATION TFT_NO_ROTATION - -//============================================================================= -//============================ Other Controllers ============================ -//============================================================================= - -// -// Ender-3 v2 OEM display. A DWIN display with Rotary Encoder. -// -//#define DWIN_CREALITY_LCD // Creality UI -//#define DWIN_LCD_PROUI // Pro UI by MRiscoC -//#define DWIN_CREALITY_LCD_JYERSUI // Jyers UI by Jacob Myers -//#define DWIN_MARLINUI_PORTRAIT // MarlinUI (portrait orientation) -//#define DWIN_MARLINUI_LANDSCAPE // MarlinUI (landscape orientation) - -// -// Touch Screen Settings -// -//#define TOUCH_SCREEN -#if ENABLED(TOUCH_SCREEN) - #define BUTTON_DELAY_EDIT 50 // (ms) Button repeat delay for edit screens - #define BUTTON_DELAY_MENU 250 // (ms) Button repeat delay for menus - - #if ANY(TFT_CLASSIC_UI, TFT_COLOR_UI) - //#define NO_BACK_MENU_ITEM // Don't display a top menu item to go back to the parent menu - #endif - - #define TOUCH_SCREEN_CALIBRATION - - //#define TOUCH_CALIBRATION_X 12316 - //#define TOUCH_CALIBRATION_Y -8981 - //#define TOUCH_OFFSET_X -43 - //#define TOUCH_OFFSET_Y 257 - //#define TOUCH_ORIENTATION TOUCH_LANDSCAPE - - #if ALL(TOUCH_SCREEN_CALIBRATION, EEPROM_SETTINGS) - #define TOUCH_CALIBRATION_AUTO_SAVE // Auto save successful calibration values to EEPROM - #endif - - #if ENABLED(TFT_COLOR_UI) - //#define SINGLE_TOUCH_NAVIGATION - #endif -#endif - -// -// RepRapWorld REPRAPWORLD_KEYPAD v1.1 -// https://reprapworld.com/products/electronics/ramps/keypad_v1_0_fully_assembled/ -// -//#define REPRAPWORLD_KEYPAD -#if ENABLED(REPRAPWORLD_KEYPAD) - //#define REPRAPWORLD_KEYPAD_MOVE_STEP 10.0 // (mm) Distance to move per key-press -#endif - -// -// EasyThreeD ET-4000+ with button input and status LED -// -//#define EASYTHREED_UI - -//============================================================================= -//=============================== Extra Features ============================== -//============================================================================= - -// @section fans - -// Set number of user-controlled fans. Disable to use all board-defined fans. -// :[1,2,3,4,5,6,7,8] -//#define NUM_M106_FANS 1 - -// Use software PWM to drive the fan, as for the heaters. This uses a very low frequency -// which is not as annoying as with the hardware PWM. On the other hand, if this frequency -// is too low, you should also increment SOFT_PWM_SCALE. -//#define FAN_SOFT_PWM - -// Incrementing this by 1 will double the software PWM frequency, -// affecting heaters, and the fan if FAN_SOFT_PWM is enabled. -// However, control resolution will be halved for each increment; -// at zero value, there are 128 effective control positions. -// :[0,1,2,3,4,5,6,7] -#define SOFT_PWM_SCALE 0 - -// If SOFT_PWM_SCALE is set to a value higher than 0, dithering can -// be used to mitigate the associated resolution loss. If enabled, -// some of the PWM cycles are stretched so on average the desired -// duty cycle is attained. -//#define SOFT_PWM_DITHER - -// @section extras - -// Support for the BariCUDA Paste Extruder -//#define BARICUDA - -// @section lights - -// Temperature status LEDs that display the hotend and bed temperature. -// If all hotends, bed temperature, and target temperature are under 54C -// then the BLUE led is on. Otherwise the RED led is on. (1C hysteresis) -//#define TEMP_STAT_LEDS - -// Support for BlinkM/CyzRgb -//#define BLINKM - -// Support for PCA9632 PWM LED driver -//#define PCA9632 - -// Support for PCA9533 PWM LED driver -//#define PCA9533 - -/** - * RGB LED / LED Strip Control - * - * Enable support for an RGB LED connected to 5V digital pins, or - * an RGB Strip connected to MOSFETs controlled by digital pins. - * - * Adds the M150 command to set the LED (or LED strip) color. - * If pins are PWM capable (e.g., 4, 5, 6, 11) then a range of - * luminance values can be set from 0 to 255. - * For NeoPixel LED an overall brightness parameter is also available. - * - * === CAUTION === - * LED Strips require a MOSFET Chip between PWM lines and LEDs, - * as the Arduino cannot handle the current the LEDs will require. - * Failure to follow this precaution can destroy your Arduino! - * - * NOTE: A separate 5V power supply is required! The NeoPixel LED needs - * more current than the Arduino 5V linear regulator can produce. - * - * Requires PWM frequency between 50 <> 100Hz (Check HAL or variant) - * Use FAST_PWM_FAN, if possible, to reduce fan noise. - */ - -// LED Type. Enable only one of the following two options: -//#define RGB_LED -//#define RGBW_LED - -#if ANY(RGB_LED, RGBW_LED) - //#define RGB_LED_R_PIN 34 - //#define RGB_LED_G_PIN 43 - //#define RGB_LED_B_PIN 35 - //#define RGB_LED_W_PIN -1 -#endif - -#if ANY(RGB_LED, RGBW_LED, PCA9632) - //#define RGB_STARTUP_TEST // For PWM pins, fade between all colors - #if ENABLED(RGB_STARTUP_TEST) - #define RGB_STARTUP_TEST_INNER_MS 10 // (ms) Reduce or increase fading speed - #endif -#endif - -// Support for Adafruit NeoPixel LED driver -//#define NEOPIXEL_LED -#if ENABLED(NEOPIXEL_LED) - #define NEOPIXEL_TYPE NEO_GRBW // NEO_GRBW, NEO_RGBW, NEO_GRB, NEO_RBG, etc. - // See https://github.com/adafruit/Adafruit_NeoPixel/blob/master/Adafruit_NeoPixel.h - //#define NEOPIXEL_PIN 4 // LED driving pin - //#define NEOPIXEL2_TYPE NEOPIXEL_TYPE - //#define NEOPIXEL2_PIN 5 - #define NEOPIXEL_PIXELS 30 // Number of LEDs in the strip. (Longest strip when NEOPIXEL2_SEPARATE is disabled.) - #define NEOPIXEL_IS_SEQUENTIAL // Sequential display for temperature change - LED by LED. Disable to change all LEDs at once. - #define NEOPIXEL_BRIGHTNESS 127 // Initial brightness (0-255) - //#define NEOPIXEL_STARTUP_TEST // Cycle through colors at startup - - // Support for second Adafruit NeoPixel LED driver controlled with M150 S1 ... - //#define NEOPIXEL2_SEPARATE - #if ENABLED(NEOPIXEL2_SEPARATE) - #define NEOPIXEL2_PIXELS 15 // Number of LEDs in the second strip - #define NEOPIXEL2_BRIGHTNESS 127 // Initial brightness (0-255) - #define NEOPIXEL2_STARTUP_TEST // Cycle through colors at startup - #define NEOPIXEL_M150_DEFAULT -1 // Default strip for M150 without 'S'. Use -1 to set all by default. - #else - //#define NEOPIXEL2_INSERIES // Default behavior is NeoPixel 2 in parallel - #endif - - // Use some of the NeoPixel LEDs for static (background) lighting - //#define NEOPIXEL_BKGD_INDEX_FIRST 0 // Index of the first background LED - //#define NEOPIXEL_BKGD_INDEX_LAST 5 // Index of the last background LED - //#define NEOPIXEL_BKGD_COLOR { 255, 255, 255, 0 } // R, G, B, W - //#define NEOPIXEL_BKGD_TIMEOUT_COLOR { 25, 25, 25, 0 } // R, G, B, W - //#define NEOPIXEL_BKGD_ALWAYS_ON // Keep the backlight on when other NeoPixels are off -#endif - -/** - * Printer Event LEDs - * - * During printing, the LEDs will reflect the printer status: - * - * - Gradually change from blue to violet as the heated bed gets to target temp - * - Gradually change from violet to red as the hotend gets to temperature - * - Change to white to illuminate work surface - * - Change to green once print has finished - * - Turn off after the print has finished and the user has pushed a button - */ -#if ANY(BLINKM, RGB_LED, RGBW_LED, PCA9632, PCA9533, NEOPIXEL_LED) - #define PRINTER_EVENT_LEDS -#endif - -// @section servos - -/** - * Number of servos - * - * For some servo-related options NUM_SERVOS will be set automatically. - * Set this manually if there are extra servos needing manual control. - * Set to 0 to turn off servo support. - */ -//#define NUM_SERVOS 3 // Note: Servo index starts with 0 for M280-M282 commands - -// (ms) Delay before the next move will start, to give the servo time to reach its target angle. -// 300ms is a good value but you can try less delay. -// If the servo can't reach the requested position, increase it. -#define SERVO_DELAY { 300 } - -// Only power servos during movement, otherwise leave off to prevent jitter -//#define DEACTIVATE_SERVOS_AFTER_MOVE - -// Edit servo angles with M281 and save to EEPROM with M500 -//#define EDITABLE_SERVO_ANGLES - -// Disable servo with M282 to reduce power consumption, noise, and heat when not in use -//#define SERVO_DETACH_GCODE diff --git a/Marlin/Modified files/G29.cpp b/Marlin/Modified files/G29.cpp deleted file mode 100644 index d2ba9a27fe..0000000000 --- a/Marlin/Modified files/G29.cpp +++ /dev/null @@ -1,1069 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * Based on Sprinter and grbl. - * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -/** - * G29.cpp - Auto Bed Leveling - */ - -#include "../../../inc/MarlinConfig.h" - -#if HAS_ABL_NOT_UBL - -#include "../../gcode.h" -#include "../../../feature/bedlevel/bedlevel.h" -#include "../../../module/motion.h" -#include "../../../module/planner.h" -#include "../../../module/probe.h" -#include "../../../module/temperature.h" -#include "../../queue.h" - -#if ENABLED(AUTO_BED_LEVELING_LINEAR) - #include "../../../libs/least_squares_fit.h" -#endif - -#if ABL_PLANAR - #include "../../../libs/vector_3.h" -#endif -#if ENABLED(BD_SENSOR_PROBE_NO_STOP) - #include "../../../feature/bedlevel/bdl/bdl.h" -#endif - -#include "../../../lcd/marlinui.h" -#if ENABLED(EXTENSIBLE_UI) - #include "../../../lcd/extui/ui_api.h" -#elif ENABLED(DWIN_CREALITY_LCD) - #include "../../../lcd/e3v2/creality/dwin.h" -#elif ENABLED(SOVOL_SV06_RTS) - #include "../../../lcd/sovol_rts/sovol_rts.h" -#endif - -#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) -#include "../../../core/debug_out.h" - -#if DISABLED(PROBE_MANUALLY) && FT_MOTION_DISABLE_FOR_PROBING - #include "../../../module/ft_motion.h" -#endif - -#if ABL_USES_GRID - #if ENABLED(PROBE_Y_FIRST) - #define PR_OUTER_VAR abl.meshCount.x - #define PR_OUTER_SIZE abl.grid_points.x - #define PR_INNER_VAR abl.meshCount.y - #define PR_INNER_SIZE abl.grid_points.y - #else - #define PR_OUTER_VAR abl.meshCount.y - #define PR_OUTER_SIZE abl.grid_points.y - #define PR_INNER_VAR abl.meshCount.x - #define PR_INNER_SIZE abl.grid_points.x - #endif -#endif - -#ifdef USE_PROBE_FOR_MESH_REF - float mesh_zero_ref_offset = 0; // declared in gcode.h as external so it can be set in menu_probe_level.cpp -#endif - -/** - * @brief Do some things before returning from G29. - * @param retry : true if the G29 can and should be retried. false if the failure is too serious. - * @param did : true if the leveling procedure completed successfully. - */ -static void pre_g29_return(const bool retry, const bool did) { - if (!retry) { - TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_IDLE, false)); - } - #if DISABLED(G29_RETRY_AND_RECOVER) - if (!retry || did) { - TERN_(DWIN_CREALITY_LCD, dwinLevelingDone()); - TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone()); - } - #endif -} - -#define G29_RETURN(retry, did) do{ \ - pre_g29_return(TERN0(G29_RETRY_AND_RECOVER, retry), did); \ - return TERN_(G29_RETRY_AND_RECOVER, retry); \ -}while(0) - -// For manual probing values persist over multiple G29 -class G29_State { -public: - int verbose_level; - xy_pos_t probePos; - float measured_z; - bool dryrun, - reenable; - - #if ANY(PROBE_MANUALLY, AUTO_BED_LEVELING_LINEAR) - int abl_probe_index; - #endif - - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - grid_count_t abl_points; - #elif ENABLED(AUTO_BED_LEVELING_3POINT) - static constexpr grid_count_t abl_points = 3; - #elif ABL_USES_GRID - static constexpr grid_count_t abl_points = GRID_MAX_POINTS; - #endif - - #if ABL_USES_GRID - - xy_int8_t meshCount; - - xy_pos_t probe_position_lf, - probe_position_rb; - - xy_float_t gridSpacing; // = { 0.0f, 0.0f } - - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - bool topography_map; - xy_uint8_t grid_points; - #else // Bilinear - static constexpr xy_uint8_t grid_points = { GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y }; - #endif - - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - float Z_offset; - bed_mesh_t z_values; - #endif - - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - int indexIntoAB[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; - float eqnAMatrix[GRID_MAX_POINTS * 3], // "A" matrix of the linear system of equations - eqnBVector[GRID_MAX_POINTS], // "B" vector of Z points - mean; - #endif - #endif -}; - -#if ABL_USES_GRID && ANY(AUTO_BED_LEVELING_3POINT, AUTO_BED_LEVELING_BILINEAR) - constexpr xy_uint8_t G29_State::grid_points; - constexpr grid_count_t G29_State::abl_points; -#endif - -/** - * G29: Detailed Z probe, probes the bed at 3 or more points. - * Will fail if the printer has not been homed with G28. - * - * Enhanced G29 Auto Bed Leveling Probe Routine - * - * O Auto-level only if needed - * - * D Dry-Run mode. Just evaluate the bed Topology - Don't apply - * or alter the bed level data. Useful to check the topology - * after a first run of G29. - * - * J Jettison current bed leveling data - * - * V Set the verbose level (0-4). Example: "G29 V3" - * - * Parameters With LINEAR leveling only: - * - * P Set the size of the grid that will be probed (P x P points). - * Example: "G29 P4" - * - * X Set the X size of the grid that will be probed (X x Y points). - * Example: "G29 X7 Y5" - * - * Y Set the Y size of the grid that will be probed (X x Y points). - * - * T Generate a Bed Topology Report. Example: "G29 P5 T" for a detailed report. - * This is useful for manual bed leveling and finding flaws in the bed (to - * assist with part placement). - * Not supported by non-linear delta printer bed leveling. - * - * Parameters With LINEAR and BILINEAR leveling only: - * - * S Set the XY travel speed between probe points (in units/min) - * - * H Set bounds to a centered square H x H units in size - * - * -or- - * - * F Set the Front limit of the probing grid - * B Set the Back limit of the probing grid - * L Set the Left limit of the probing grid - * R Set the Right limit of the probing grid - * - * Parameters with DEBUG_LEVELING_FEATURE only: - * - * C Make a totally fake grid with no actual probing. - * For use in testing when no probing is possible. - * - * Parameters with BILINEAR leveling only: - * - * Z Supply an additional Z probe offset - * - * Extra parameters with PROBE_MANUALLY: - * - * To do manual probing simply repeat G29 until the procedure is complete. - * The first G29 accepts parameters. 'G29 Q' for status, 'G29 A' to abort. - * - * Q Query leveling and G29 state - * - * A Abort current leveling procedure - * - * Extra parameters with BILINEAR only: - * - * W Write a mesh point. (If G29 is idle.) - * I X index for mesh point - * J Y index for mesh point - * X X for mesh point, overrides I - * Y Y for mesh point, overrides J - * Z Z for mesh point. Otherwise, raw current Z. - * - * Without PROBE_MANUALLY: - * - * E By default G29 will engage the Z probe, test the bed, then disengage. - * Include "E" to engage/disengage the Z probe for each sample. - * There's no extra effect if you have a fixed Z probe. - */ -G29_TYPE GcodeSuite::G29() { - - DEBUG_SECTION(log_G29, "G29", DEBUGGING(LEVELING)); - - // Leveling state is persistent when done manually with multiple G29 commands - TERN_(PROBE_MANUALLY, static) G29_State abl; - - // Keep powered steppers from timing out - reset_stepper_timeout(); - - // Q = Query leveling and G29 state - const bool seenQ = ANY(DEBUG_LEVELING_FEATURE, PROBE_MANUALLY) && parser.seen_test('Q'); - - // G29 Q is also available if debugging - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (seenQ || DEBUGGING(LEVELING)) log_machine_info(); - if (DISABLED(PROBE_MANUALLY) && seenQ) G29_RETURN(false, false); - #endif - - // A = Abort manual probing - // C = Generate fake probe points (DEBUG_LEVELING_FEATURE) - const bool seenA = TERN0(PROBE_MANUALLY, parser.seen_test('A')), - no_action = seenA || seenQ, - faux = ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) ? parser.boolval('C') : no_action; - - // O = Don't level if leveling is already active - if (!no_action && planner.leveling_active && parser.boolval('O')) { - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> Auto-level not needed, skip"); - G29_RETURN(false, false); - } - - #ifdef USE_PROBE_FOR_MESH_REF - // Send 'N' to force homing before G29 (internal only) - if (parser.seen_test('N')){ - process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); - } - else { - process_subcommands_now(F("G28L0 X Y")); // Home X and Y only - } - // Set the probe trigger height as Z home before leveling - probe.probe_at_point(current_position, PROBE_PT_NONE,0 ,false ,true, Z_PROBE_LOW_POINT, Z_TWEEN_SAFE_CLEARANCE, false); - set_axis_is_at_home(Z_AXIS); - sync_plan_position(); - #else - // Send 'N' to force homing before G29 (internal only) - if (parser.seen_test('N')){ - process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); - } - #endif - - // Don't allow auto-leveling without homing first - if (homing_needed_error()) G29_RETURN(false, false); - - // 3-point leveling gets points from the probe class - #if ENABLED(AUTO_BED_LEVELING_3POINT) - vector_3 points[3]; - probe.get_three_points(points); - #endif - - // Storage for ABL Linear results - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - struct linear_fit_data lsf_results; - #endif - - // Set and report "probing" state to host - TERN_(FULL_REPORT_TO_HOST_FEATURE, set_and_report_grblstate(M_PROBE, false)); - - #if DISABLED(PROBE_MANUALLY) && FT_MOTION_DISABLE_FOR_PROBING - FTMotionDisableInScope FT_Disabler; // Disable Fixed-Time Motion for probing - #endif - - /** - * On the initial G29 fetch command parameters. - */ - if (!g29_in_progress) { - - probe.use_probing_tool(); - - #ifdef EVENT_GCODE_BEFORE_G29 - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Before G29 G-code: ", EVENT_GCODE_BEFORE_G29); - gcode.process_subcommands_now(F(EVENT_GCODE_BEFORE_G29)); - #endif - - #if ANY(PROBE_MANUALLY, AUTO_BED_LEVELING_LINEAR) - abl.abl_probe_index = -1; - #endif - - abl.reenable = planner.leveling_active; - - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - - const bool seen_w = parser.seen_test('W'); - if (seen_w) { - if (!leveling_is_valid()) { - SERIAL_ERROR_MSG("No bilinear grid"); - G29_RETURN(false, false); - } - - const float rz = parser.seenval('Z') ? RAW_Z_POSITION(parser.value_linear_units()) : current_position.z; - if (!WITHIN(rz, -10, 10)) { - SERIAL_ERROR_MSG("Bad Z value"); - G29_RETURN(false, false); - } - - const float rx = RAW_X_POSITION(parser.linearval('X', NAN)), - ry = RAW_Y_POSITION(parser.linearval('Y', NAN)); - int8_t i = parser.byteval('I', -1), j = parser.byteval('J', -1); - - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" - - if (!isnan(rx) && !isnan(ry)) { - // Get nearest i / j from rx / ry - i = (rx - bedlevel.grid_start.x) / bedlevel.grid_spacing.x + 0.5f; - j = (ry - bedlevel.grid_start.y) / bedlevel.grid_spacing.y + 0.5f; - LIMIT(i, 0, (GRID_MAX_POINTS_X) - 1); - LIMIT(j, 0, (GRID_MAX_POINTS_Y) - 1); - } - - #pragma GCC diagnostic pop - - if (WITHIN(i, 0, (GRID_MAX_POINTS_X) - 1) && WITHIN(j, 0, (GRID_MAX_POINTS_Y) - 1)) { - set_bed_leveling_enabled(false); - bedlevel.z_values[i][j] = rz; - bedlevel.refresh_bed_level(); - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(i, j, rz)); - if (abl.reenable) { - set_bed_leveling_enabled(true); - report_current_position(); - } - } - G29_RETURN(false, false); - } // parser.seen_test('W') - - #else - - constexpr bool seen_w = false; - - #endif - - // Jettison bed leveling data - if (!seen_w && parser.seen_test('J')) { - reset_bed_level(); - G29_RETURN(false, false); - } - - abl.verbose_level = parser.intval('V'); - if (!WITHIN(abl.verbose_level, 0, 4)) { - SERIAL_ECHOLNPGM(GCODE_ERR_MSG("(V)erbose level implausible (0-4).")); - G29_RETURN(false, false); - } - - abl.dryrun = parser.boolval('D') || TERN0(PROBE_MANUALLY, no_action); - - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - - incremental_LSF_reset(&lsf_results); - - abl.topography_map = abl.verbose_level > 2 || parser.boolval('T'); - - // X and Y specify points in each direction, overriding the default - // These values may be saved with the completed mesh - abl.grid_points.set( - parser.byteval('X', GRID_MAX_POINTS_X), - parser.byteval('Y', GRID_MAX_POINTS_Y) - ); - if (parser.seenval('P')) abl.grid_points.x = abl.grid_points.y = parser.value_int(); - - if (!WITHIN(abl.grid_points.x, 2, GRID_MAX_POINTS_X)) { - SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Probe points (X) implausible (2-" STRINGIFY(GRID_MAX_POINTS_X) ").")); - G29_RETURN(false, false); - } - if (!WITHIN(abl.grid_points.y, 2, GRID_MAX_POINTS_Y)) { - SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Probe points (Y) implausible (2-" STRINGIFY(GRID_MAX_POINTS_Y) ").")); - G29_RETURN(false, false); - } - - abl.abl_points = abl.grid_points.x * abl.grid_points.y; - abl.mean = 0; - - #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - - abl.Z_offset = parser.linearval('Z'); - - #endif - - #if ABL_USES_GRID - - constexpr feedRate_t min_probe_feedrate_mm_s = XY_PROBE_FEEDRATE_MIN; - xy_probe_feedrate_mm_s = MMM_TO_MMS(parser.linearval('S', XY_PROBE_FEEDRATE)); - if (xy_probe_feedrate_mm_s < min_probe_feedrate_mm_s) { - xy_probe_feedrate_mm_s = min_probe_feedrate_mm_s; - SERIAL_ECHOLNPGM(GCODE_ERR_MSG("Feedrate (S) too low. (Using ", min_probe_feedrate_mm_s, ")")); - } - - const float x_min = probe.min_x(), x_max = probe.max_x(), - y_min = probe.min_y(), y_max = probe.max_y(); - - if (parser.seen('H')) { - const int16_t size = (int16_t)parser.value_linear_units(); - abl.probe_position_lf.set(_MAX((X_CENTER) - size / 2, x_min), _MAX((Y_CENTER) - size / 2, y_min)); - abl.probe_position_rb.set(_MIN(abl.probe_position_lf.x + size, x_max), _MIN(abl.probe_position_lf.y + size, y_max)); - } - else { - abl.probe_position_lf.set(parser.linearval('L', x_min), parser.linearval('F', y_min)); - abl.probe_position_rb.set(parser.linearval('R', x_max), parser.linearval('B', y_max)); - } - - if (!probe.good_bounds(abl.probe_position_lf, abl.probe_position_rb)) { - if (DEBUGGING(LEVELING)) { - DEBUG_ECHOLNPGM("G29 L", abl.probe_position_lf.x, " R", abl.probe_position_rb.x, - " F", abl.probe_position_lf.y, " B", abl.probe_position_rb.y); - } - SERIAL_ECHOLNPGM(GCODE_ERR_MSG(" (L,R,F,B) out of bounds.")); - G29_RETURN(false, false); - } - - // Probe at the points of a lattice grid - abl.gridSpacing.set((abl.probe_position_rb.x - abl.probe_position_lf.x) / (abl.grid_points.x - 1), - (abl.probe_position_rb.y - abl.probe_position_lf.y) / (abl.grid_points.y - 1)); - - #endif // ABL_USES_GRID - - if (abl.verbose_level > 0) { - SERIAL_ECHOPGM("G29 Auto Bed Leveling"); - if (abl.dryrun) SERIAL_ECHOPGM(" (DRYRUN)"); - SERIAL_EOL(); - } - - planner.synchronize(); - - #if ENABLED(AUTO_BED_LEVELING_3POINT) - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("> 3-point Leveling"); - points[0].z = points[1].z = points[2].z = 0; // Probe at 3 arbitrary points - #endif - - TERN_(EXTENSIBLE_UI, ExtUI::onLevelingStart()); - - if (!faux) { - remember_feedrate_scaling_off(); - - #if ENABLED(PREHEAT_BEFORE_LEVELING) - #if ENABLED(SOVOL_SV06_RTS) - rts.updateTempE0(); - rts.updateTempBed(); - rts.sendData(1, Wait_VP); - rts.gotoPage(ID_ABL_HeatWait_L, ID_ABL_HeatWait_D); - #endif - if (!abl.dryrun) probe.preheat_for_probing(LEVELING_NOZZLE_TEMP, - TERN(EXTENSIBLE_UI, ExtUI::getLevelingBedTemp(), LEVELING_BED_TEMP) - ); - #endif - } - - // Position bed horizontally and Z probe vertically. - #if HAS_SAFE_BED_LEVELING - xyze_pos_t safe_position = current_position; - #ifdef SAFE_BED_LEVELING_START_X - safe_position.x = SAFE_BED_LEVELING_START_X; - #endif - #ifdef SAFE_BED_LEVELING_START_Y - safe_position.y = SAFE_BED_LEVELING_START_Y; - #endif - #ifdef SAFE_BED_LEVELING_START_Z - safe_position.z = SAFE_BED_LEVELING_START_Z; - #endif - #ifdef SAFE_BED_LEVELING_START_I - safe_position.i = SAFE_BED_LEVELING_START_I; - #endif - #ifdef SAFE_BED_LEVELING_START_J - safe_position.j = SAFE_BED_LEVELING_START_J; - #endif - #ifdef SAFE_BED_LEVELING_START_K - safe_position.k = SAFE_BED_LEVELING_START_K; - #endif - #ifdef SAFE_BED_LEVELING_START_U - safe_position.u = SAFE_BED_LEVELING_START_U; - #endif - #ifdef SAFE_BED_LEVELING_START_V - safe_position.v = SAFE_BED_LEVELING_START_V; - #endif - #ifdef SAFE_BED_LEVELING_START_W - safe_position.w = SAFE_BED_LEVELING_START_W; - #endif - - do_blocking_move_to(safe_position); - #endif // HAS_SAFE_BED_LEVELING - - // Disable auto bed leveling during G29. - // Be formal so G29 can be done successively without G28. - if (!no_action) set_bed_leveling_enabled(false); - - // Deploy certain probes before starting probing - #if ENABLED(BLTOUCH) || ALL(HAS_Z_SERVO_PROBE, Z_SERVO_INTERMEDIATE_STOW) - do_z_clearance(Z_CLEARANCE_DEPLOY_PROBE); - #elif HAS_BED_PROBE - if (probe.deploy()) { // (returns true on deploy failure) - set_bed_leveling_enabled(abl.reenable); - G29_RETURN(false, true); - } - #endif - - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - if (!abl.dryrun && (abl.gridSpacing != bedlevel.grid_spacing || abl.probe_position_lf != bedlevel.grid_start)) { - reset_bed_level(); // Reset grid to 0.0 or "not probed". (Also disables ABL) - abl.reenable = false; // Can't re-enable (on error) until the new grid is written - } - // Pre-populate local Z values from the stored mesh - TERN_(IS_KINEMATIC, COPY(abl.z_values, bedlevel.z_values)); - #endif - - } // !g29_in_progress - - #if ENABLED(PROBE_MANUALLY) - - // For manual probing, get the next index to probe now. - // On the first probe this will be incremented to 0. - if (!no_action) { - ++abl.abl_probe_index; - g29_in_progress = true; - } - - // Abort current G29 procedure, go back to idle state - if (seenA && g29_in_progress) { - SERIAL_ECHOLNPGM("Manual G29 aborted"); - SET_SOFT_ENDSTOP_LOOSE(false); - set_bed_leveling_enabled(abl.reenable); - g29_in_progress = false; - TERN_(LCD_BED_LEVELING, ui.wait_for_move = false); - } - - // Query G29 status - if (abl.verbose_level || seenQ) { - SERIAL_ECHOPGM("Manual G29 "); - if (g29_in_progress) - SERIAL_ECHOLNPGM("point ", _MIN(abl.abl_probe_index + 1, abl.abl_points), " of ", abl.abl_points); - else - SERIAL_ECHOLNPGM("idle"); - } - - // For 'A' or 'Q' exit with success state - if (no_action) G29_RETURN(false, true); - - if (abl.abl_probe_index == 0) { - // For the initial G29 S2 save software endstop state - SET_SOFT_ENDSTOP_LOOSE(true); - // Move close to the bed before the first point - do_blocking_move_to_z(0); - } - else { - - #if ANY(AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_3POINT) - const uint16_t index = abl.abl_probe_index - 1; - #endif - - // For G29 after adjusting Z. - // Save the previous Z before going to the next point - abl.measured_z = current_position.z; - - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - - abl.mean += abl.measured_z; - abl.eqnBVector[index] = abl.measured_z; - abl.eqnAMatrix[index + 0 * abl.abl_points] = abl.probePos.x; - abl.eqnAMatrix[index + 1 * abl.abl_points] = abl.probePos.y; - abl.eqnAMatrix[index + 2 * abl.abl_points] = 1; - - incremental_LSF(&lsf_results, abl.probePos, abl.measured_z); - - #elif ENABLED(AUTO_BED_LEVELING_3POINT) - - points[index].z = abl.measured_z; - - #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - - #ifdef USE_PROBE_FOR_MESH_REF - const float newz = abl.measured_z + mesh_zero_ref_offset; - #else - const float newz = abl.measured_z + abl.Z_offset; - #endif - abl.z_values[abl.meshCount.x][abl.meshCount.y] = newz; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(abl.meshCount, newz)); - - #ifdef USE_PROBE_FOR_MESH_REF - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM_P(PSTR("Save X"), abl.meshCount.x, SP_Y_STR, abl.meshCount.y, SP_Z_STR, abl.measured_z + mesh_zero_ref_offset); - #else - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM_P(PSTR("Save X"), abl.meshCount.x, SP_Y_STR, abl.meshCount.y, SP_Z_STR, abl.measured_z + abl.Z_offset); - #endif - - #endif - } - - // - // If there's another point to sample, move there with optional lift. - // - - #if ABL_USES_GRID - - // Skip any unreachable points - while (abl.abl_probe_index < abl.abl_points) { - - // Set abl.meshCount.x, abl.meshCount.y based on abl.abl_probe_index, with zig-zag - PR_OUTER_VAR = abl.abl_probe_index / PR_INNER_SIZE; - PR_INNER_VAR = abl.abl_probe_index - (PR_OUTER_VAR * PR_INNER_SIZE); - - // Probe in reverse order for every other row/column - const bool zig = (PR_OUTER_VAR & 1); // != ((PR_OUTER_SIZE) & 1); - if (zig) PR_INNER_VAR = (PR_INNER_SIZE - 1) - PR_INNER_VAR; - - abl.probePos = abl.probe_position_lf + abl.gridSpacing * abl.meshCount.asFloat(); - - TERN_(AUTO_BED_LEVELING_LINEAR, abl.indexIntoAB[abl.meshCount.x][abl.meshCount.y] = abl.abl_probe_index); - - // Keep looping till a reachable point is found - if (position_is_reachable(abl.probePos)) break; - ++abl.abl_probe_index; - } - - // Is there a next point to move to? - if (abl.abl_probe_index < abl.abl_points) { - _manual_goto_xy(abl.probePos); // Can be used here too! - // Disable software endstops to allow manual adjustment - // If G29 is not completed, they will not be re-enabled - SET_SOFT_ENDSTOP_LOOSE(true); - G29_RETURN(false, true); - } - else { - // Leveling done! Fall through to G29 finishing code below - SERIAL_ECHOLNPGM("Grid probing done."); - // Re-enable software endstops, if needed - SET_SOFT_ENDSTOP_LOOSE(false); - } - - #elif ENABLED(AUTO_BED_LEVELING_3POINT) - - // Probe at 3 arbitrary points - if (abl.abl_probe_index < abl.abl_points) { - abl.probePos = xy_pos_t(points[abl.abl_probe_index]); - _manual_goto_xy(abl.probePos); - // Disable software endstops to allow manual adjustment - // If G29 is not completed, they will not be re-enabled - SET_SOFT_ENDSTOP_LOOSE(true); - G29_RETURN(false, true); - } - else { - - SERIAL_ECHOLNPGM("3-point probing done."); - - // Re-enable software endstops, if needed - SET_SOFT_ENDSTOP_LOOSE(false); - - if (!abl.dryrun) { - vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal(); - if (planeNormal.z < 0) planeNormal *= -1; - planner.bed_level_matrix = matrix_3x3::create_look_at(planeNormal); - - // Can't re-enable (on error) until the new grid is written - abl.reenable = false; - } - - } - - #endif // AUTO_BED_LEVELING_3POINT - - #else // !PROBE_MANUALLY - { - const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE; - - abl.measured_z = 0; - - #if ABL_USES_GRID - - bool zig = PR_OUTER_SIZE & 1; // Always end at RIGHT and BACK_PROBE_BED_POSITION - - // Outer loop is X with PROBE_Y_FIRST enabled - // Outer loop is Y with PROBE_Y_FIRST disabled - for (PR_OUTER_VAR = 0; PR_OUTER_VAR < PR_OUTER_SIZE && !isnan(abl.measured_z); PR_OUTER_VAR++) { - - int8_t inStart, inStop, inInc; - - if (zig) { // Zig away from origin - inStart = 0; // Left or front - inStop = PR_INNER_SIZE; // Right or back - inInc = 1; // Zig right - } - else { // Zag towards origin - inStart = PR_INNER_SIZE - 1; // Right or back - inStop = -1; // Left or front - inInc = -1; // Zag left - } - - FLIP(zig); // zag - - // An index to print current state - grid_count_t pt_index = (PR_OUTER_VAR) * (PR_INNER_SIZE) + 1; - - // Inner loop is Y with PROBE_Y_FIRST enabled - // Inner loop is X with PROBE_Y_FIRST disabled - for (PR_INNER_VAR = inStart; PR_INNER_VAR != inStop; pt_index++, PR_INNER_VAR += inInc) { - - abl.probePos = abl.probe_position_lf + abl.gridSpacing * abl.meshCount.asFloat(); - - TERN_(AUTO_BED_LEVELING_LINEAR, abl.indexIntoAB[abl.meshCount.x][abl.meshCount.y] = ++abl.abl_probe_index); // 0... - - // Avoid probing outside the round or hexagonal area - if (TERN0(IS_KINEMATIC, !probe.can_reach(abl.probePos))) continue; - - if (abl.verbose_level) SERIAL_ECHOLNPGM("Probing mesh point ", pt_index, "/", abl.abl_points, "."); - TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/%i"), GET_TEXT_F(MSG_PROBING_POINT), int(pt_index), int(abl.abl_points))); - - #if ENABLED(BD_SENSOR_PROBE_NO_STOP) - if (PR_INNER_VAR == inStart) { - char tmp_1[32]; - - // move to the start point of new line - abl.measured_z = faux ? 0.001f * random(-100, 101) : probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); - // Go to the end of the row/column ... and back up by one - // TODO: Why not just use... PR_INNER_VAR = inStop - inInc - for (PR_INNER_VAR = inStart; PR_INNER_VAR != inStop; PR_INNER_VAR += inInc); - PR_INNER_VAR -= inInc; - - // Get the coordinate of the resulting grid point - abl.probePos = abl.probe_position_lf + abl.gridSpacing * abl.meshCount.asFloat(); - - // Coordinate that puts the probe at the grid point - abl.probePos -= probe.offset_xy; - - // Put a G1 move into the buffer - // TODO: Instead of G1, we can just add the move directly to the planner... - // { - // destination = current_position; destination = abl.probePos; - // REMEMBER(fr, feedrate_mm_s, XY_PROBE_FEEDRATE_MM_S); - // prepare_line_to_destination(); - // } - sprintf_P(tmp_1, PSTR("G1X%d.%d Y%d.%d F%d"), - int(abl.probePos.x), int(abl.probePos.x * 10) % 10, - int(abl.probePos.y), int(abl.probePos.y * 10) % 10, - XY_PROBE_FEEDRATE - ); - gcode.process_subcommands_now(tmp_1); - - if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("destX: ", abl.probePos.x, " Y:", abl.probePos.y); - - // Reset the inner counter back to the start - PR_INNER_VAR = inStart; - - // Get the coordinate of the start of the row/column - abl.probePos = abl.probe_position_lf + abl.gridSpacing * abl.meshCount.asFloat(); - } - - // Wait around until the real axis position reaches the comparison point - // TODO: Use NEAR() because float is imprecise - constexpr AxisEnum axis = TERN(PROBE_Y_FIRST, Y_AXIS, X_AXIS); - const float cmp = abl.probePos[axis] - probe.offset_xy[axis]; - float pos; - for (;;) { - pos = planner.get_axis_position_mm(axis); - if (inInc > 0 ? (pos >= cmp) : (pos <= cmp)) break; - idle_no_sleep(); - } - //if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM_P(axis == Y_AXIS ? PSTR("Y=") : PSTR("X=", pos); - - safe_delay(4); - abl.measured_z = current_position.z - bdl.read(); - if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("x_cur ", planner.get_axis_position_mm(X_AXIS), " z ", abl.measured_z); - - #else // !BD_SENSOR_PROBE_NO_STOP - - abl.measured_z = faux ? 0.001f * random(-100, 101) : probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); - - #endif - - if (isnan(abl.measured_z)) { - set_bed_leveling_enabled(abl.reenable); - break; // Breaks out of both loops - } - - #if ENABLED(AUTO_BED_LEVELING_LINEAR) - - abl.mean += abl.measured_z; - abl.eqnBVector[abl.abl_probe_index] = abl.measured_z; - abl.eqnAMatrix[abl.abl_probe_index + 0 * abl.abl_points] = abl.probePos.x; - abl.eqnAMatrix[abl.abl_probe_index + 1 * abl.abl_points] = abl.probePos.y; - abl.eqnAMatrix[abl.abl_probe_index + 2 * abl.abl_points] = 1; - - incremental_LSF(&lsf_results, abl.probePos, abl.measured_z); - - #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - - #ifdef USE_PROBE_FOR_MESH_REF - const float z = abl.measured_z + mesh_zero_ref_offset; - #else - const float z = abl.measured_z + abl.Z_offset; - #endif - - abl.z_values[abl.meshCount.x][abl.meshCount.y] = z; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(abl.meshCount, z)); - - #if ENABLED(SOVOL_SV06_RTS) - if (pt_index <= GRID_MAX_POINTS) rts.sendData(pt_index, AUTO_BED_LEVEL_ICON_VP); - rts.sendData(z * 100.0f, AUTO_BED_LEVEL_1POINT_VP + (pt_index - 1) * 2); - rts.gotoPage(ID_ABL_Wait_L, ID_ABL_Wait_D); - #endif - - #endif - - abl.reenable = false; // Don't re-enable after modifying the mesh - idle_no_sleep(); - - } // inner - } // outer - - #elif ENABLED(AUTO_BED_LEVELING_3POINT) - - // Probe at 3 arbitrary points - - for (uint8_t i = 0; i < 3; ++i) { - if (abl.verbose_level) SERIAL_ECHOLNPGM("Probing point ", i + 1, "/3."); - TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/3"), GET_TEXT_F(MSG_PROBING_POINT), int(i + 1))); - - // Retain the last probe position - abl.probePos = xy_pos_t(points[i]); - abl.measured_z = faux ? 0.001 * random(-100, 101) : probe.probe_at_point(abl.probePos, raise_after, abl.verbose_level); - if (isnan(abl.measured_z)) { - set_bed_leveling_enabled(abl.reenable); - break; - } - points[i].z = abl.measured_z; - } - - if (!abl.dryrun && !isnan(abl.measured_z)) { - vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal(); - if (planeNormal.z < 0) planeNormal *= -1; - planner.bed_level_matrix = matrix_3x3::create_look_at(planeNormal); - - // Can't re-enable (on error) until the new grid is written - abl.reenable = false; - } - - #endif // AUTO_BED_LEVELING_3POINT - - ui.reset_status(); - - // Stow the probe. No raise for FIX_MOUNTED_PROBE. - if (probe.stow()) { - set_bed_leveling_enabled(abl.reenable); - abl.measured_z = NAN; - } - } - #endif // !PROBE_MANUALLY - - // - // G29 Finishing Code - // - // Unless this is a dry run, auto bed leveling will - // definitely be enabled after this point. - // - // If code above wants to continue leveling, it should - // return or loop before this point. - // - - if (DEBUGGING(LEVELING)) DEBUG_POS("> probing complete", current_position); - - #if ENABLED(PROBE_MANUALLY) - g29_in_progress = false; - TERN_(LCD_BED_LEVELING, ui.wait_for_move = false); - #endif - - // Calculate leveling, print reports, correct the position - if (!isnan(abl.measured_z)) { - #if ENABLED(AUTO_BED_LEVELING_BILINEAR) - - if (abl.dryrun) - bedlevel.print_leveling_grid(&abl.z_values); - else { - bedlevel.set_grid(abl.gridSpacing, abl.probe_position_lf); - COPY(bedlevel.z_values, abl.z_values); - TERN_(IS_KINEMATIC, bedlevel.extrapolate_unprobed_bed_level()); - bedlevel.refresh_bed_level(); - - bedlevel.print_leveling_grid(); - } - - #elif ENABLED(AUTO_BED_LEVELING_LINEAR) - - // For LINEAR leveling calculate matrix, print reports, correct the position - - /** - * solve the plane equation ax + by + d = z - * A is the matrix with rows [x y 1] for all the probed points - * B is the vector of the Z positions - * the normal vector to the plane is formed by the coefficients of the - * plane equation in the standard form, which is Vx*x+Vy*y+Vz*z+d = 0 - * so Vx = -a Vy = -b Vz = 1 (we want the vector facing towards positive Z - */ - struct { float a, b, d; } plane_equation_coefficients; - - finish_incremental_LSF(&lsf_results); - plane_equation_coefficients.a = -lsf_results.A; // We should be able to eliminate the '-' on these three lines and down below - plane_equation_coefficients.b = -lsf_results.B; // but that is not yet tested. - plane_equation_coefficients.d = -lsf_results.D; - - abl.mean /= abl.abl_points; - - if (abl.verbose_level) { - SERIAL_ECHOPGM("Eqn coefficients: a: ", p_float_t(plane_equation_coefficients.a, 8), - " b: ", p_float_t(plane_equation_coefficients.b, 8), - " d: ", p_float_t(plane_equation_coefficients.d, 8)); - if (abl.verbose_level > 2) - SERIAL_ECHOPGM("\nMean of sampled points: ", p_float_t(abl.mean, 8)); - SERIAL_EOL(); - } - - // Create the matrix but don't correct the position yet - if (!abl.dryrun) - planner.bed_level_matrix = matrix_3x3::create_look_at( - vector_3(-plane_equation_coefficients.a, -plane_equation_coefficients.b, 1) // We can eliminate the '-' here and up above - ); - - // Show the Topography map if enabled - if (abl.topography_map) { - - float min_diff = 999; - - auto print_topo_map = [&](FSTR_P const title, const bool get_min) { - SERIAL_ECHO(title); - for (int8_t yy = abl.grid_points.y - 1; yy >= 0; yy--) { - for (uint8_t xx = 0; xx < abl.grid_points.x; ++xx) { - const int ind = abl.indexIntoAB[xx][yy]; - xyz_float_t tmp = { abl.eqnAMatrix[ind + 0 * abl.abl_points], - abl.eqnAMatrix[ind + 1 * abl.abl_points], 0 }; - planner.bed_level_matrix.apply_rotation_xyz(tmp.x, tmp.y, tmp.z); - if (get_min) NOMORE(min_diff, abl.eqnBVector[ind] - tmp.z); - const float subval = get_min ? abl.mean : tmp.z + min_diff, - diff = abl.eqnBVector[ind] - subval; - SERIAL_CHAR(' '); if (diff >= 0.0) SERIAL_CHAR('+'); // Include + for column alignment - SERIAL_ECHO(p_float_t(diff, 5)); - } // xx - SERIAL_EOL(); - } // yy - SERIAL_EOL(); - }; - - print_topo_map(F("\nBed Height Topography:\n" - " +--- BACK --+\n" - " | |\n" - " L | (+) | R\n" - " E | | I\n" - " F | (-) N (+) | G\n" - " T | | H\n" - " | (-) | T\n" - " | |\n" - " O-- FRONT --+\n" - " (0,0)\n"), true); - if (abl.verbose_level > 3) - print_topo_map(F("\nCorrected Bed Height vs. Bed Topology:\n"), false); - - } // abl.topography_map - - #endif // AUTO_BED_LEVELING_LINEAR - - #if ABL_PLANAR - - // For LINEAR and 3POINT leveling correct the current position - - if (abl.verbose_level > 0) - planner.bed_level_matrix.debug(F("\n\nBed Level Correction Matrix:")); - - if (!abl.dryrun) { - // - // Correct the current XYZ position based on the tilted plane. - // - - if (DEBUGGING(LEVELING)) DEBUG_POS("G29 uncorrected XYZ", current_position); - - xyze_pos_t converted = current_position; - planner.force_unapply_leveling(converted); // use conversion machinery - - // Use the last measured distance to the bed, if possible - if ( NEAR(current_position.x, abl.probePos.x - probe.offset_xy.x) - && NEAR(current_position.y, abl.probePos.y - probe.offset_xy.y) - ) { - const float simple_z = current_position.z - abl.measured_z; - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Probed Z", simple_z, " Matrix Z", converted.z, " Discrepancy ", simple_z - converted.z); - converted.z = simple_z; - } - - // The rotated XY and corrected Z are now current_position - current_position = converted; - - if (DEBUGGING(LEVELING)) DEBUG_POS("G29 corrected XYZ", current_position); - - abl.reenable = true; - } - - // Auto Bed Leveling is complete! Enable if possible. - if (abl.reenable) { - planner.leveling_active = true; - sync_plan_position(); - } - - #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - - // Auto Bed Leveling is complete! Enable if possible. - if (!abl.dryrun || abl.reenable) set_bed_leveling_enabled(true); - - #endif - - } // !isnan(abl.measured_z) - - // Restore state after probing - if (!faux) restore_feedrate_and_scaling(); - - TERN_(HAS_BED_PROBE, probe.move_z_after_probing()); - - #ifdef EVENT_GCODE_AFTER_G29 - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("After G29 G-code: ", EVENT_GCODE_AFTER_G29); - planner.synchronize(); - process_subcommands_now(F(EVENT_GCODE_AFTER_G29)); - #endif - - TERN_(SOVOL_SV06_RTS, RTS_AutoBedLevelPage()); - - probe.use_probing_tool(false); - - report_current_position(); - - G29_RETURN(isnan(abl.measured_z), true); -} - -#endif // HAS_ABL_NOT_UBL diff --git a/Marlin/Modified files/Modified files.zip b/Marlin/Modified files/Modified files.zip deleted file mode 100644 index b8e75f711dedc1119bf92335e0f4e8dad6d91746..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93207 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 H&jJ1itzVrg diff --git a/Marlin/Modified files/gcode.h b/Marlin/Modified files/gcode.h deleted file mode 100644 index c4c81b2647..0000000000 --- a/Marlin/Modified files/gcode.h +++ /dev/null @@ -1,1347 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * Based on Sprinter and grbl. - * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ -#pragma once - -/** - * ----------------- - * G-Codes in Marlin - * ----------------- - * - * Helpful G-code references: - * - https://marlinfw.org/meta/gcode - * - https://reprap.org/wiki/G-code - * - https://linuxcnc.org/docs/html/gcode.html - * - * Help to document Marlin's G-codes online: - * - https://github.com/MarlinFirmware/MarlinDocumentation - * - * ----------------- - * - * "G" Codes - * - * G0 -> G1 - * G1 - Coordinated Movement X Y Z E - * G2 - CW ARC - * G3 - CCW ARC - * G4 - Dwell S or P - * G5 - Cubic B-spline with XYZE destination and IJPQ offsets - * G10 - Retract filament according to settings of M207 (Requires FWRETRACT) - * G11 - Retract recover filament according to settings of M208 (Requires FWRETRACT) - * G12 - Clean tool (Requires NOZZLE_CLEAN_FEATURE) - * G17 - Select Plane XY (Requires CNC_WORKSPACE_PLANES) - * G18 - Select Plane ZX (Requires CNC_WORKSPACE_PLANES) - * G19 - Select Plane YZ (Requires CNC_WORKSPACE_PLANES) - * G20 - Set input units to inches (Requires INCH_MODE_SUPPORT) - * G21 - Set input units to millimeters (Requires INCH_MODE_SUPPORT) - * G26 - Mesh Validation Pattern (Requires G26_MESH_VALIDATION) - * G27 - Park Nozzle (Requires NOZZLE_PARK_FEATURE) - * G28 - Home one or more axes - * G29 - Start or continue the bed leveling probe procedure (Requires bed leveling) - * G30 - Single Z probe, probes bed at X Y location (defaults to current XY location) - * G31 - Dock sled (Z_PROBE_SLED only) - * G32 - Undock sled (Z_PROBE_SLED only) - * G33 - Delta Auto-Calibration (Requires DELTA_AUTO_CALIBRATION) - * G34 - Z Stepper automatic alignment using probe: I T A (Requires Z_STEPPER_AUTO_ALIGN) - * G35 - Read bed corners to help adjust bed screws: T (Requires ASSISTED_TRAMMING) - * G38 - Probe in any direction using the Z_MIN_PROBE (Requires G38_PROBE_TARGET) - * G42 - Coordinated move to a mesh point (Requires MESH_BED_LEVELING, AUTO_BED_LEVELING_BLINEAR, or AUTO_BED_LEVELING_UBL) - * G60 - Save current position. (Requires SAVED_POSITIONS) - * G61 - Apply/restore saved coordinates. (Requires SAVED_POSITIONS) - * G76 - Calibrate first layer temperature offsets. (Requires PTC_PROBE and PTC_BED) - * G80 - Cancel current motion mode (Requires GCODE_MOTION_MODES) - * G90 - Use Absolute Coordinates - * G91 - Use Relative Coordinates - * G92 - Set current position to coordinates given - * - * "M" Codes - * - * M0 - Unconditional stop - Wait for user to press a button on the LCD (Only if ULTRA_LCD is enabled) - * M1 -> M0 - * M3 - Turn ON Laser | Spindle (clockwise), set Power | Speed. (Requires SPINDLE_FEATURE or LASER_FEATURE) - * M4 - Turn ON Laser | Spindle (counter-clockwise), set Power | Speed. (Requires SPINDLE_FEATURE or LASER_FEATURE) - * M5 - Turn OFF Laser | Spindle. (Requires SPINDLE_FEATURE or LASER_FEATURE) - * M7 - Turn mist coolant ON. (Requires COOLANT_CONTROL) - * M8 - Turn flood coolant ON. (Requires COOLANT_CONTROL) - * M9 - Turn coolant OFF. (Requires COOLANT_CONTROL) - * M10 - Turn Vacuum or Blower motor ON (Requires AIR_EVACUATION) - * M11 - Turn Vacuum or Blower motor OFF (Requires AIR_EVACUATION) - * M12 - Set up closed loop control system. (Requires EXTERNAL_CLOSED_LOOP_CONTROLLER) - * M16 - Expected printer check. (Requires EXPECTED_PRINTER_CHECK) - * M17 - Enable/Power all stepper motors - * M18 - Disable all stepper motors; same as M84 - * - *** Print from Media (SDSUPPORT) *** - * M20 - List SD card. (Requires SDSUPPORT) - * M21 - Init SD card. (Requires SDSUPPORT) With MULTI_VOLUME select a drive with `M21 Pn` / 'M21 S' / 'M21 U'. - * M22 - Release SD card. (Requires SDSUPPORT) - * M23 - Select SD file: "M23 /path/file.gco". (Requires SDSUPPORT) - * M24 - Start/resume SD print. (Requires SDSUPPORT) - * M25 - Pause SD print. (Requires SDSUPPORT) - * M26 - Set SD position in bytes: "M26 S12345". (Requires SDSUPPORT) - * M27 - Report SD print status. (Requires SDSUPPORT) - * OR, with 'S' set the SD status auto-report interval. (Requires AUTO_REPORT_SD_STATUS) - * OR, with 'C' get the current filename. - * M28 - Start SD write: "M28 /path/file.gco". (Requires SDSUPPORT) - * M29 - Stop SD write. (Requires SDSUPPORT) - * M30 - Delete file from SD: "M30 /path/file.gco" (Requires SDSUPPORT) - * M31 - Report time since last M109 or SD card start to serial. - * M32 - Select file and start SD print: "M32 [S] !/path/file.gco#". (Requires SDSUPPORT) - * Use P to run other files as sub-programs: "M32 P !filename#" - * The '#' is necessary when calling from within sd files, as it stops buffer prereading - * M33 - Get the longname version of a path. (Requires LONG_FILENAME_HOST_SUPPORT) - * M34 - Set SD Card sorting options. (Requires SDCARD_SORT_ALPHA) - * - * M42 - Change pin status via G-code: M42 P S. LED pin assumed if P is omitted. (Requires DIRECT_PIN_CONTROL) - * M43 - Display pin status, watch pins for changes, watch endstops & toggle LED, Z servo probe test, toggle pins (Requires PINS_DEBUGGING) - * M48 - Measure Z Probe repeatability: M48 P X Y V E L S. (Requires Z_MIN_PROBE_REPEATABILITY_TEST) - * - * M73 - Set the progress percentage. (Requires SET_PROGRESS_MANUALLY) - * M75 - Start the print job timer. - * M76 - Pause the print job timer. - * M77 - Stop the print job timer. - * M78 - Show statistical information about the print jobs. (Requires PRINTCOUNTER) - * - * M80 - Turn on Power Supply. (Requires PSU_CONTROL) - * M81 - Turn off Power Supply. (Requires PSU_CONTROL) - * - * M82 - Set E codes absolute (default). - * M83 - Set E codes relative while in Absolute (G90) mode. - * M84 - Disable steppers until next move, or use S to specify an idle - * duration after which steppers should turn off. S0 disables the timeout. - * M85 - Set inactivity shutdown timer with parameter S. To disable set zero (default) - * M86 - Set / Report Hotend Idle Timeout. (Requires HOTEND_IDLE_TIMEOUT) - * M87 - Cancel Hotend Idle Timeout (by setting the timeout period to 0). (Requires HOTEND_IDLE_TIMEOUT) - * M92 - Set planner.settings.axis_steps_per_mm for one or more axes. (Requires EDITABLE_STEPS_PER_UNIT) - * - * M100 - Watch Free Memory (for debugging) (Requires M100_FREE_MEMORY_WATCHER) - * - * M102 - Configure Bed Distance Sensor. (Requires BD_SENSOR) - * - * M104 - Set extruder target temp. - * M105 - Report current temperatures. - * M106 - Set print fan speed. - * M107 - Print fan off. - * M108 - Break out of heating loops (M109, M190, M303). With no controller, breaks out of M0/M1. (Requires EMERGENCY_PARSER) - * M109 - S Wait for extruder current temp to reach target temp. ** Wait only when heating! ** - * R Wait for extruder current temp to reach target temp. ** Wait for heating or cooling. ** - * If AUTOTEMP is enabled, S B F. Exit autotemp by any M109 without F - * - * M110 - Get or set the current line number. (Used by host printing) - * M111 - Set debug flags: "M111 S". See flag bits defined in enum.h. - * M112 - Full Shutdown. - * - * M113 - Get or set the timeout interval for Host Keepalive "busy" messages. (Requires HOST_KEEPALIVE_FEATURE) - * M114 - Report current position. - * M115 - Report capabilities. (Requires CAPABILITIES_REPORT) - * M117 - Display a message on the controller screen. (Requires an LCD) - * M118 - Display a message in the host console. - * - * M119 - Report endstops status. - * M120 - Enable endstops detection. - * M121 - Disable endstops detection. - * - * M122 - Debug stepper (Requires at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660) - * M123 - Report fan tachometers. (Requires En_FAN_TACHO_PIN) Optionally set auto-report interval. (Requires AUTO_REPORT_FANS) - * M125 - Save current position and move to filament change position. (Requires PARK_HEAD_ON_PAUSE) - * - * M126 - Solenoid Air Valve Open. (Requires BARICUDA) - * M127 - Solenoid Air Valve Closed. (Requires BARICUDA) - * M128 - EtoP Open. (Requires BARICUDA) - * M129 - EtoP Closed. (Requires BARICUDA) - * - * M140 - Set bed target temp. S - * M141 - Set heated chamber target temp. S (Requires a chamber heater) - * M143 - Set cooler target temp. S (Requires a laser cooling device) - * M145 - Set heatup values for materials on the LCD. H B F for S (0=PLA, 1=ABS) - * M149 - Set temperature units. (Requires TEMPERATURE_UNITS_SUPPORT) - * M150 - Set Status LED Color as R U B W P. Values 0-255. (Requires BLINKM, RGB_LED, RGBW_LED, NEOPIXEL_LED, PCA9533, or PCA9632). - * M154 - Auto-report position with interval of S. (Requires AUTO_REPORT_POSITION) - * M155 - Auto-report temperatures with interval of S. (Requires AUTO_REPORT_TEMPERATURES) - * M163 - Set a single proportion for a mixing extruder. (Requires MIXING_EXTRUDER) - * M164 - Commit the mix and save to a virtual tool (current, or as specified by 'S'). (Requires MIXING_EXTRUDER) - * M165 - Set the mix for the mixing extruder (and current virtual tool) with parameters ABCDHI. (Requires MIXING_EXTRUDER and DIRECT_MIXING_IN_G1) - * M166 - Set the Gradient Mix for the mixing extruder. (Requires GRADIENT_MIX) - * M190 - Set bed target temperature and wait. R Set target temperature and wait. S Set, but only wait when heating. (Requires TEMP_SENSOR_BED) - * M192 - Wait for probe to reach target temperature. (Requires TEMP_SENSOR_PROBE) - * M193 - R Wait for cooler to reach target temp. ** Wait for cooling. ** - * M200 - Set filament diameter, D, setting E axis units to cubic. (Use S0 to revert to linear units.) - * M201 - Set max acceleration in units/s^2 for print moves: "M201 X Y Z E" - * M202 - Set max acceleration in units/s^2 for travel moves: "M202 X Y Z E" ** UNUSED IN MARLIN! ** - * M203 - Set maximum feedrate: "M203 X Y Z E" in units/sec. - * M204 - Set default acceleration in units/sec^2: P R T - * M205 - Set advanced settings. Current units apply: - S T minimum speeds - B - X, Y, Z, E - * M206 - Set additional homing offset. (Disabled by NO_WORKSPACE_OFFSETS or DELTA) - * M207 - Set Retract Length: S, Feedrate: F, and Z lift: Z. (Requires FWRETRACT) - * M208 - Set Recover (unretract) Additional (!) Length: S and Feedrate: F. (Requires FWRETRACT) - * M209 - Turn Automatic Retract Detection on/off: S<0|1> (For slicers that don't support G10/11). (Requires FWRETRACT_AUTORETRACT) - Every normal extrude-only move will be classified as retract depending on the direction. - * M210 - Set or Report the homing feedrate (Requires EDITABLE_HOMING_FEEDRATE) - * M211 - Enable, Disable, and/or Report software endstops: S<0|1> (Requires MIN_SOFTWARE_ENDSTOPS or MAX_SOFTWARE_ENDSTOPS) - * M217 - Set filament swap parameters: "M217 S P R". (Requires SINGLENOZZLE) - * M218 - Set/get a tool offset: "M218 T X Y". (Requires 2 or more extruders) - * M220 - Set Feedrate Percentage: "M220 S" (i.e., "FR" on the LCD) - * Use "M220 B" to back up the Feedrate Percentage and "M220 R" to restore it. (Requires an MMU_MODEL version 2 or 2S) - * M221 - Set Flow Percentage: "M221 S" (Requires an extruder) - * M226 - Wait until a pin is in a given state: "M226 P S" (Requires DIRECT_PIN_CONTROL) - * M240 - Trigger a camera to take a photograph. (Requires PHOTO_GCODE) - * M250 - Set LCD contrast: "M250 C" (0-63). (Requires LCD support) - * M255 - Set LCD sleep time: "M255 S" (0-99). (Requires an LCD with brightness or sleep/wake) - * M256 - Set LCD brightness: "M256 B" (0-255). (Requires an LCD with brightness control) - * M260 - i2c Send Data (Requires EXPERIMENTAL_I2CBUS) - * M261 - i2c Request Data (Requires EXPERIMENTAL_I2CBUS) - * M280 - Set servo position absolute: "M280 P S". (Requires servos) - * M281 - Set servo min|max position: "M281 P L U". (Requires EDITABLE_SERVO_ANGLES) - * M282 - Detach servo: "M282 P". (Requires SERVO_DETACH_GCODE) - * M290 - Babystepping (Requires BABYSTEPPING) - * M293 - Babystep Z UP (Requires EP_BABYSTEPPING) - * M294 - Babystep Z DOWN (Requires EP_BABYSTEPPING) - * M300 - Play beep sound S P - * M301 - Set PID parameters P I and D. (Requires PIDTEMP) - * M302 - Allow cold extrudes, or set the minimum extrude S. (Requires PREVENT_COLD_EXTRUSION) - * M303 - PID relay autotune S sets the target temperature. Default 150C. (Requires PIDTEMP) - * M304 - Set bed PID parameters P I and D. (Requires PIDTEMPBED) - * M305 - Set user thermistor parameters R T and P. (Requires TEMP_SENSOR_x 1000) - * M306 - MPC autotune. (Requires MPCTEMP) - * M309 - Set chamber PID parameters P I and D. (Requires PIDTEMPCHAMBER) - * M350 - Set microstepping mode. (Requires digital microstepping pins.) - * M351 - Toggle MS1 MS2 pins directly. (Requires digital microstepping pins.) - * M355 - Set Case Light on/off and set brightness. (Requires CASE_LIGHT_PIN) - * M380 - Activate solenoid on active tool (Requires EXT_SOLENOID) or the tool specified by 'S' (Requires MANUAL_SOLENOID_CONTROL). - * M381 - Disable solenoids on all tools (Requires EXT_SOLENOID) or the tool specified by 'S' (Requires MANUAL_SOLENOID_CONTROL). - * M400 - Finish all moves. - * M401 - Deploy and activate Z probe. (Requires a probe) - * M402 - Deactivate and stow Z probe. (Requires a probe) - * M403 - Set filament type for PRUSA MMU2 - * M404 - Display or set the Nominal Filament Width: "W". (Requires FILAMENT_WIDTH_SENSOR) - * M405 - Enable Filament Sensor flow control. "M405 D". (Requires FILAMENT_WIDTH_SENSOR) - * M406 - Disable Filament Sensor flow control. (Requires FILAMENT_WIDTH_SENSOR) - * M407 - Display measured filament diameter in millimeters. (Requires FILAMENT_WIDTH_SENSOR) - * M410 - Quickstop. Abort all planned moves. - * M412 - Enable / Disable Filament Runout Detection. (Requires FILAMENT_RUNOUT_SENSOR) - * M413 - Enable / Disable Power-Loss Recovery. (Requires POWER_LOSS_RECOVERY) - * M414 - Set language by index. (Requires LCD_LANGUAGE_2...) - * M420 - Enable/Disable Leveling (with current values) S1=enable S0=disable (Requires MESH_BED_LEVELING or ABL) - * M421 - Set a single Z coordinate in the Mesh Leveling grid. X Y Z (Requires MESH_BED_LEVELING, AUTO_BED_LEVELING_BILINEAR, or AUTO_BED_LEVELING_UBL) - * M422 - Set Z Stepper automatic alignment position using probe. X Y A (Requires Z_STEPPER_AUTO_ALIGN) - * M425 - Enable/Disable and tune backlash correction. (Requires BACKLASH_COMPENSATION and BACKLASH_GCODE) - * M428 - Set the home_offset based on the current_position. Nearest edge applies. (Disabled by NO_WORKSPACE_OFFSETS or DELTA) - * M430 - Read the system current, voltage, and power (Requires POWER_MONITOR_CURRENT, POWER_MONITOR_VOLTAGE, or POWER_MONITOR_FIXED_VOLTAGE) - * M485 - Send RS485 packets (Requires RS485_SERIAL_PORT) - * M486 - Identify and cancel objects. (Requires CANCEL_OBJECTS) - * M493 - Get or set input FT Motion / Shaping parameters. (Requires FT_MOTION) - * M500 - Store parameters in EEPROM. (Requires EEPROM_SETTINGS) - * M501 - Restore parameters from EEPROM. (Requires EEPROM_SETTINGS) - * M502 - Revert to the default "factory settings". ** Does not write them to EEPROM! ** - * M503 - Print the current settings (in memory): "M503 S". S0 specifies compact output. - * M504 - Validate EEPROM contents. (Requires EEPROM_SETTINGS) - * M510 - Lock Printer (Requires PASSWORD_FEATURE) - * M511 - Unlock Printer (Requires PASSWORD_UNLOCK_GCODE) - * M512 - Set/Change/Remove Password (Requires PASSWORD_CHANGE_GCODE) - * M524 - Abort the current SD print job started with M24. (Requires SDSUPPORT) - * M540 - Enable/disable SD card abort on endstop hit: "M540 S". (Requires SD_ABORT_ON_ENDSTOP_HIT) - * M552 - Get or set IP address. Enable/disable network interface. (Requires enabled Ethernet port) - * M553 - Get or set IP netmask. (Requires enabled Ethernet port) - * M554 - Get or set IP gateway. (Requires enabled Ethernet port) - * M569 - Enable stealthChop on an axis. (Requires at least one _DRIVER_TYPE to be TMC2130/2160/2208/2209/5130/5160) - * M575 - Change the serial baud rate. (Requires BAUD_RATE_GCODE) - * M592 - Get or set Nonlinear Extrusion parameters. (Requires NONLINEAR_EXTRUSION) - * M593 - Get or set input shaping parameters. (Requires INPUT_SHAPING_[XY]) - * M600 - Pause for filament change: "M600 X Y Z E L". (Requires ADVANCED_PAUSE_FEATURE) - * M603 - Configure filament change: "M603 T U L". (Requires ADVANCED_PAUSE_FEATURE) - * M605 - Set Dual X-Carriage movement mode: "M605 S [X] [R]". (Requires DUAL_X_CARRIAGE) - * M665 - Set delta configurations: "M665 H L R S B X Y Z (Requires DELTA) - * Set SCARA configurations: "M665 S P T Z (Requires MORGAN_SCARA or MP_SCARA) - * Set Polargraph draw area and belt length: "M665 S L R T B H" - * M666 - Set/get offsets for delta (Requires DELTA) or dual endstops. (Requires [XYZ]_DUAL_ENDSTOPS) - * M672 - Set/Reset Duet Smart Effector's sensitivity. (Requires DUET_SMART_EFFECTOR and SMART_EFFECTOR_MOD_PIN) - * M701 - Load filament (Requires FILAMENT_LOAD_UNLOAD_GCODES) - * M702 - Unload filament (Requires FILAMENT_LOAD_UNLOAD_GCODES) - * - *** PRUSA_MMU3 *** - * M704 - Preload to MMU - * M705 - Eject filament - * M706 - Cut filament - * M707 - Read from MMU register - * M708 - Write to MMU register - * M709 - MMU power & reset - * - * M808 - Set or Goto a Repeat Marker (Requires GCODE_REPEAT_MARKERS) - * M810-M819 - Define/execute a G-code macro (Requires GCODE_MACROS) - * M820 - Report all defined M810-M819 G-code macros (Requires GCODE_MACROS) - * M851 - Set Z probe's XYZ offsets in current units. (Negative values: X=left, Y=front, Z=below) - * M852 - Set skew factors: "M852 [I] [J] [K]". (Requires SKEW_CORRECTION_GCODE, plus SKEW_CORRECTION_FOR_Z for IJ) - * - *** I2C_POSITION_ENCODERS *** - * M860 - Report the position of position encoder modules. - * M861 - Report the status of position encoder modules. - * M862 - Perform an axis continuity test for position encoder modules. - * M863 - Perform steps-per-mm calibration for position encoder modules. - * M864 - Change position encoder module I2C address. - * M865 - Check position encoder module firmware version. - * M866 - Report or reset position encoder module error count. - * M867 - Enable/disable or toggle error correction for position encoder modules. - * M868 - Report or set position encoder module error correction threshold. - * M869 - Report position encoder module error. - * - * M871 - Print/reset/clear first layer temperature offset values. (Requires PTC_PROBE, PTC_BED, or PTC_HOTEND) - * M876 - Handle Prompt Response. (Requires HOST_PROMPT_SUPPORT and not EMERGENCY_PARSER) - * M900 - Get or Set Linear Advance K-factor. (Requires LIN_ADVANCE) - * M906 - Set or get motor current in milliamps using axis codes XYZE, etc. Report values if no axis codes given. (Requires at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660) - * M907 - Set digital trimpot motor current using axis codes. (Requires a board with digital trimpots) - * M908 - Control digital trimpot directly. (Requires HAS_MOTOR_CURRENT_DAC or DIGIPOTSS_PIN) - * M909 - Print digipot/DAC current value. (Requires HAS_MOTOR_CURRENT_DAC) - * M910 - Commit digipot/DAC value to external EEPROM via I2C. (Requires HAS_MOTOR_CURRENT_DAC) - * M911 - Report stepper driver overtemperature pre-warn condition. (Requires at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660) - * M912 - Clear stepper driver overtemperature pre-warn condition flag. (Requires at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660) - * M913 - Set HYBRID_THRESHOLD speed. (Requires HYBRID_THRESHOLD) - * M914 - Set StallGuard sensitivity. (Requires SENSORLESS_HOMING or SENSORLESS_PROBING) - * M919 - Get or Set motor Chopper Times (time_off, hysteresis_end, hysteresis_start) using axis codes XYZE, etc. If no parameters are given, report. (Requires at least one _DRIVER_TYPE defined as TMC2130/2160/5130/5160/2208/2209/2660) - * M936 - OTA update firmware. (Requires OTA_FIRMWARE_UPDATE) - * M951 - Set Magnetic Parking Extruder parameters. (Requires MAGNETIC_PARKING_EXTRUDER) - * M3426 - Read MCP3426 ADC over I2C. (Requires HAS_MCP3426_ADC) - * M7219 - Control Max7219 Matrix LEDs. (Requires MAX7219_GCODE) - * - *** SCARA *** - * M360 - SCARA calibration: Move to cal-position ThetaA (0 deg calibration) - * M361 - SCARA calibration: Move to cal-position ThetaB (90 deg calibration - steps per degree) - * M362 - SCARA calibration: Move to cal-position PsiA (0 deg calibration) - * M363 - SCARA calibration: Move to cal-position PsiB (90 deg calibration - steps per degree) - * M364 - SCARA calibration: Move to cal-position PSIC (90 deg to Theta calibration position) - * - *** Custom codes (can be changed to suit future G-code standards) *** - * G425 - Calibrate using a conductive object. (Requires CALIBRATION_GCODE) - * M928 - Start SD logging: "M928 filename.gco". Stop with M29. (Requires SDSUPPORT) - * M993 - Backup SPI Flash to SD - * M994 - Load a Backup from SD to SPI Flash - * M995 - Touch screen calibration for TFT display - * M997 - Perform in-application firmware update - * M999 - Restart after being stopped by error - * - * D... - Custom Development G-code. Add hooks to 'gcode_D.cpp' for developers to test features. (Requires MARLIN_DEV_MODE) - * D576 - Set buffer monitoring options. (Requires BUFFER_MONITORING) - * - *** "T" Codes *** - * - * T0-T3 - Select an extruder (tool) by index: "T F" - */ - -#include "../inc/MarlinConfig.h" -#include "parser.h" - -#if ENABLED(I2C_POSITION_ENCODERS) - #include "../feature/encoder_i2c.h" -#endif - -#if ANY(IS_SCARA, POLAR) || defined(G0_FEEDRATE) - #define HAS_FAST_MOVES 1 -#endif - -#if ENABLED(MARLIN_SMALL_BUILD) - #define GCODE_ERR_MSG(V...) "?" -#else - #define GCODE_ERR_MSG(V...) "?" V -#endif - -enum AxisRelative : uint8_t { - LOGICAL_AXIS_LIST(REL_E, REL_X, REL_Y, REL_Z, REL_I, REL_J, REL_K, REL_U, REL_V, REL_W) - #if HAS_EXTRUDERS - , E_MODE_ABS, E_MODE_REL - #endif - , NUM_REL_MODES -}; -typedef bits_t(NUM_REL_MODES) relative_t; - -extern const char G28_STR[]; - -#ifdef USE_PROBE_FOR_MESH_REF - extern float mesh_zero_ref_offset; -#endif - -class GcodeSuite { -public: - - static relative_t axis_relative; - - GcodeSuite() { // Relative motion mode for each logical axis - axis_relative = AxisBits(AXIS_RELATIVE_MODES).bits; - } - - static bool axis_is_relative(const AxisEnum a) { - #if HAS_EXTRUDERS - if (a == E_AXIS) { - if (TEST(axis_relative, E_MODE_REL)) return true; - if (TEST(axis_relative, E_MODE_ABS)) return false; - } - #endif - return TEST(axis_relative, a); - } - static void set_relative_mode(const bool rel) { - axis_relative = rel ? (0 LOGICAL_AXIS_GANG( - | _BV(REL_E), - | _BV(REL_X), | _BV(REL_Y), | _BV(REL_Z), - | _BV(REL_I), | _BV(REL_J), | _BV(REL_K), - | _BV(REL_U), | _BV(REL_V), | _BV(REL_W) - )) : 0; - } - #if HAS_EXTRUDERS - static void set_e_relative() { - CBI(axis_relative, E_MODE_ABS); - SBI(axis_relative, E_MODE_REL); - } - static void set_e_absolute() { - CBI(axis_relative, E_MODE_REL); - SBI(axis_relative, E_MODE_ABS); - } - #endif - - #if ENABLED(CNC_WORKSPACE_PLANES) - /** - * Workspace planes only apply to G2/G3 moves - * (and "canned cycles" - not a current feature) - */ - enum WorkspacePlane : char { PLANE_XY, PLANE_ZX, PLANE_YZ }; - static WorkspacePlane workspace_plane; - #endif - - #define MAX_COORDINATE_SYSTEMS 9 - #if ENABLED(CNC_COORDINATE_SYSTEMS) - static int8_t active_coordinate_system; - static xyz_pos_t coordinate_system[MAX_COORDINATE_SYSTEMS]; - static bool select_coordinate_system(const int8_t _new); - #endif - - static millis_t previous_move_ms, max_inactive_time; - FORCE_INLINE static bool stepper_max_timed_out(const millis_t ms=millis()) { - return max_inactive_time && ELAPSED(ms, previous_move_ms + max_inactive_time); - } - FORCE_INLINE static void reset_stepper_timeout(const millis_t ms=millis()) { previous_move_ms = ms; } - - #if HAS_DISABLE_IDLE_AXES - static millis_t stepper_inactive_time; - FORCE_INLINE static bool stepper_inactive_timeout(const millis_t ms=millis()) { - return ELAPSED(ms, previous_move_ms + stepper_inactive_time); - } - #else - static bool stepper_inactive_timeout(const millis_t) { return false; } - #endif - - static void report_echo_start(const bool forReplay); - static void report_heading(const bool forReplay, FSTR_P const fstr, const bool eol=true); - static void report_heading_etc(const bool forReplay, FSTR_P const fstr, const bool eol=true) { - report_heading(forReplay, fstr, eol); - report_echo_start(forReplay); - } - static void say_units(); - - static int8_t get_target_extruder_from_command(); - static int8_t get_target_e_stepper_from_command(const int8_t dval=-1); - static void get_destination_from_command(); - - static void process_parsed_command(const bool no_ok=false); - static void process_next_command(); - - // Execute G-code in-place, preserving current G-code parameters - static void process_subcommands_now(FSTR_P fgcode); - static void process_subcommands_now(char * gcode); - - static void home_all_axes(const bool keep_leveling=false) { - process_subcommands_now(keep_leveling ? FPSTR(G28_STR) : TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); - } - - #if ANY(HAS_AUTO_REPORTING, HOST_KEEPALIVE_FEATURE) - static bool autoreport_paused; - static bool set_autoreport_paused(const bool p) { - const bool was = autoreport_paused; - autoreport_paused = p; - return was; - } - #else - static constexpr bool autoreport_paused = false; - static bool set_autoreport_paused(const bool) { return false; } - #endif - - #if ENABLED(HOST_KEEPALIVE_FEATURE) - /** - * States for managing Marlin and host communication - * Marlin sends messages if blocked or busy - */ - enum MarlinBusyState : char { - NOT_BUSY, // Not in a handler - IN_HANDLER, // Processing a G-Code - IN_PROCESS, // Known to be blocking command input (as in G29) - PAUSED_FOR_USER, // Blocking pending any input - PAUSED_FOR_INPUT // Blocking pending text input (concept) - }; - - static MarlinBusyState busy_state; - static uint8_t host_keepalive_interval; - - static void host_keepalive(); - static bool host_keepalive_is_paused() { return busy_state >= PAUSED_FOR_USER; } - - #define KEEPALIVE_STATE(N) REMEMBER(_KA_, gcode.busy_state, gcode.N) - #else - #define KEEPALIVE_STATE(N) NOOP - #endif - - static void dwell(millis_t time); - -private: - - friend class MarlinSettings; - #if ENABLED(ARC_SUPPORT) - friend void plan_arc(const xyze_pos_t&, const ab_float_t&, const bool, const uint8_t); - #endif - - #if ENABLED(MARLIN_DEV_MODE) - static void D(const int16_t dcode); - #endif - - static void G0_G1(TERN_(HAS_FAST_MOVES, const bool fast_move=false)); - - #if ENABLED(ARC_SUPPORT) - static void G2_G3(const bool clockwise); - #endif - - static void G4(); - - #if ENABLED(BEZIER_CURVE_SUPPORT) - static void G5(); - #endif - - #if ENABLED(DIRECT_STEPPING) - static void G6(); - #endif - - #if ENABLED(FWRETRACT) - static void G10(); - static void G11(); - #endif - - #if ENABLED(NOZZLE_CLEAN_FEATURE) - static void G12(); - #endif - - #if ENABLED(CNC_WORKSPACE_PLANES) - static void G17(); - static void G18(); - static void G19(); - #endif - - #if ENABLED(INCH_MODE_SUPPORT) - static void G20(); - static void G21(); - #endif - - #if ENABLED(G26_MESH_VALIDATION) - static void G26(); - #endif - - #if ENABLED(NOZZLE_PARK_FEATURE) - static void G27(); - #endif - - static void G28(); - - #if HAS_LEVELING - #if ENABLED(G29_RETRY_AND_RECOVER) - static void event_probe_failure(); - static void event_probe_recover(); - static void G29_with_retry(); - #define G29_TYPE bool - #else - #define G29_TYPE void - #endif - static G29_TYPE G29(); - #endif - - #if HAS_BED_PROBE - static void G30(); - #if ENABLED(Z_PROBE_SLED) - static void G31(); - static void G32(); - #endif - #endif - - #if ENABLED(DELTA_AUTO_CALIBRATION) - static void G33(); - #endif - - #if ANY(Z_MULTI_ENDSTOPS, Z_STEPPER_AUTO_ALIGN, MECHANICAL_GANTRY_CALIBRATION) - static void G34(); - #endif - - #if ENABLED(Z_STEPPER_AUTO_ALIGN) - static void M422(); - static void M422_report(const bool forReplay=true); - #endif - - #if ENABLED(ASSISTED_TRAMMING) - static void G35(); - #endif - - #if ENABLED(G38_PROBE_TARGET) - static void G38(const int8_t subcode); - #endif - - #if HAS_MESH - static void G42(); - #endif - - #if ENABLED(CNC_COORDINATE_SYSTEMS) - static void G53(); - static void G54(); - static void G55(); - static void G56(); - static void G57(); - static void G58(); - static void G59(); - #endif - - #if ALL(PTC_PROBE, PTC_BED) - static void G76(); - #endif - - #if SAVED_POSITIONS - static void G60(); - static void G61(int8_t slot=-1); - #endif - - #if ENABLED(GCODE_MOTION_MODES) - static void G80(); - #endif - - static void G92(); - - #if ENABLED(CALIBRATION_GCODE) - static void G425(); - #endif - - #if HAS_RESUME_CONTINUE - static void M0_M1(); - #endif - - #if HAS_CUTTER - static void M3_M4(const bool is_M4); - static void M5(); - #endif - - #if ENABLED(COOLANT_MIST) - static void M7(); - #endif - - #if ANY(AIR_ASSIST, COOLANT_FLOOD) - static void M8(); - #endif - - #if ANY(AIR_ASSIST, COOLANT_CONTROL) - static void M9(); - #endif - - #if ENABLED(AIR_EVACUATION) - static void M10(); - static void M11(); - #endif - - #if ENABLED(EXTERNAL_CLOSED_LOOP_CONTROLLER) - static void M12(); - #endif - - #if ENABLED(EXPECTED_PRINTER_CHECK) - static void M16(); - #endif - - static void M17(); - - static void M18_M84(); - - #if HAS_MEDIA - static void M20(); - static void M21(); - static void M22(); - static void M23(); - static void M24(); - static void M25(); - static void M26(); - static void M27(); - static void M28(); - static void M29(); - static void M30(); - #endif - - static void M31(); - - #if HAS_MEDIA - #if HAS_MEDIA_SUBCALLS - static void M32(); - #endif - #if ENABLED(LONG_FILENAME_HOST_SUPPORT) - static void M33(); - #endif - #if ALL(SDCARD_SORT_ALPHA, SDSORT_GCODE) - static void M34(); - #endif - #endif - - #if ENABLED(DIRECT_PIN_CONTROL) - static void M42(); - #endif - #if ENABLED(PINS_DEBUGGING) - static void M43(); - #endif - - #if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST) - static void M48(); - #endif - - #if ENABLED(SET_PROGRESS_MANUALLY) - static void M73(); - #endif - - static void M75(); - static void M76(); - static void M77(); - - #if ENABLED(PRINTCOUNTER) - static void M78(); - #endif - - #if ENABLED(PSU_CONTROL) - static void M80(); - #endif - static void M81(); - - #if HAS_EXTRUDERS - static void M82(); - static void M83(); - #endif - - static void M85(); - - #if ENABLED(HOTEND_IDLE_TIMEOUT) - static void M86(); - static void M86_report(const bool forReplay=true); - static void M87(); - #endif - - #if ENABLED(EDITABLE_STEPS_PER_UNIT) - static void M92(); - static void M92_report(const bool forReplay=true, const int8_t e=-1); - #endif - - #if ENABLED(M100_FREE_MEMORY_WATCHER) - static void M100(); - #endif - - #if ENABLED(BD_SENSOR) - static void M102(); - #endif - - #if HAS_HOTEND - static void M104_M109(const bool isM109); - FORCE_INLINE static void M104() { M104_M109(false); } - FORCE_INLINE static void M109() { M104_M109(true); } - #endif - - static void M105(); - - #if HAS_FAN - static void M106(); - static void M107(); - #endif - - #if DISABLED(EMERGENCY_PARSER) - static void M108(); - static void M112(); - static void M410(); - #if ENABLED(HOST_PROMPT_SUPPORT) - static void M876(); - #endif - #endif - - static void M110(); - static void M111(); - - #if ENABLED(HOST_KEEPALIVE_FEATURE) - static void M113(); - #endif - - static void M114(); - - #if ENABLED(CAPABILITIES_REPORT) - static void M115(); - #endif - - #if HAS_STATUS_MESSAGE - static void M117(); - #endif - - static void M118(); - static void M119(); - static void M120(); - static void M121(); - - #if HAS_FANCHECK - static void M123(); - #endif - - #if ENABLED(PARK_HEAD_ON_PAUSE) - static void M125(); - #endif - - #if ENABLED(BARICUDA) - #if HAS_HEATER_1 - static void M126(); - static void M127(); - #endif - #if HAS_HEATER_2 - static void M128(); - static void M129(); - #endif - #endif - - #if HAS_HEATED_BED - static void M140_M190(const bool isM190); - FORCE_INLINE static void M140() { M140_M190(false); } - FORCE_INLINE static void M190() { M140_M190(true); } - #endif - - #if HAS_HEATED_CHAMBER - static void M141(); - static void M191(); - #endif - - #if HAS_TEMP_PROBE - static void M192(); - #endif - - #if HAS_COOLER - static void M143(); - static void M193(); - #endif - - #if HAS_PREHEAT - static void M145(); - static void M145_report(const bool forReplay=true); - #endif - - #if ENABLED(TEMPERATURE_UNITS_SUPPORT) - static void M149(); - static void M149_report(const bool forReplay=true); - #endif - - #if HAS_COLOR_LEDS - static void M150(); - #endif - - #if ENABLED(AUTO_REPORT_POSITION) - static void M154(); - #endif - - #if ALL(AUTO_REPORT_TEMPERATURES, HAS_TEMP_SENSOR) - static void M155(); - #endif - - #if ENABLED(MIXING_EXTRUDER) - static void M163(); - static void M164(); - #if ENABLED(DIRECT_MIXING_IN_G1) - static void M165(); - #endif - #if ENABLED(GRADIENT_MIX) - static void M166(); - #endif - #endif - - #if DISABLED(NO_VOLUMETRICS) - static void M200(); - static void M200_report(const bool forReplay=true); - #endif - static void M201(); - static void M201_report(const bool forReplay=true); - - #if 0 - static void M202(); // Not used for Sprinter/grbl gen6 - #endif - - static void M203(); - static void M203_report(const bool forReplay=true); - static void M204(); - static void M204_report(const bool forReplay=true); - static void M205(); - static void M205_report(const bool forReplay=true); - - #if HAS_HOME_OFFSET - static void M206(); - static void M206_report(const bool forReplay=true); - #endif - - #if ENABLED(FWRETRACT) - static void M207(); - static void M207_report(const bool forReplay=true); - static void M208(); - static void M208_report(const bool forReplay=true); - #if ENABLED(FWRETRACT_AUTORETRACT) - static void M209(); - static void M209_report(const bool forReplay=true); - #endif - #endif - - #if ENABLED(EDITABLE_HOMING_FEEDRATE) - static void M210(); - static void M210_report(const bool forReplay=true); - #endif - - #if HAS_SOFTWARE_ENDSTOPS - static void M211(); - static void M211_report(const bool forReplay=true); - #endif - - #if HAS_MULTI_EXTRUDER - static void M217(); - static void M217_report(const bool forReplay=true); - #endif - - #if HAS_HOTEND_OFFSET - static void M218(); - static void M218_report(const bool forReplay=true); - #endif - - static void M220(); - - #if HAS_EXTRUDERS - static void M221(); - #endif - - #if ENABLED(DIRECT_PIN_CONTROL) - static void M226(); - #endif - - #if ENABLED(PHOTO_GCODE) - static void M240(); - #endif - - #if HAS_LCD_CONTRAST - static void M250(); - static void M250_report(const bool forReplay=true); - #endif - - #if ENABLED(EDITABLE_DISPLAY_TIMEOUT) - static void M255(); - static void M255_report(const bool forReplay=true); - #endif - - #if HAS_LCD_BRIGHTNESS - static void M256(); - static void M256_report(const bool forReplay=true); - #endif - - #if ENABLED(EXPERIMENTAL_I2CBUS) - static void M260(); - static void M261(); - #endif - - #if HAS_SERVOS - static void M280(); - #if ENABLED(EDITABLE_SERVO_ANGLES) - static void M281(); - static void M281_report(const bool forReplay=true); - #endif - #if ENABLED(SERVO_DETACH_GCODE) - static void M282(); - #endif - #endif - - #if ENABLED(BABYSTEPPING) - static void M290(); - #if ENABLED(EP_BABYSTEPPING) - static void M293(); - static void M294(); - #endif - #endif - - #if HAS_SOUND - static void M300(); - #endif - - #if ENABLED(PIDTEMP) - static void M301(); - static void M301_report(const bool forReplay=true E_OPTARG(const int8_t eindex=-1)); - #endif - - #if ENABLED(PREVENT_COLD_EXTRUSION) - static void M302(); - #endif - - #if HAS_PID_HEATING - static void M303(); - #endif - - #if ENABLED(PIDTEMPBED) - static void M304(); - static void M304_report(const bool forReplay=true); - #endif - - #if HAS_USER_THERMISTORS - static void M305(); - #endif - - #if ENABLED(MPCTEMP) - static void M306(); - static void M306_report(const bool forReplay=true); - #endif - - #if ENABLED(PIDTEMPCHAMBER) - static void M309(); - static void M309_report(const bool forReplay=true); - #endif - - #if HAS_MICROSTEPS - static void M350(); - static void M351(); - #endif - - #if ENABLED(CASE_LIGHT_ENABLE) - static void M355(); - #endif - - #if ENABLED(REPETIER_GCODE_M360) - static void M360(); - #endif - - #if ENABLED(MORGAN_SCARA) - static bool M360(); - static bool M361(); - static bool M362(); - static bool M363(); - static bool M364(); - #endif - - #if ANY(EXT_SOLENOID, MANUAL_SOLENOID_CONTROL) - static void M380(); - static void M381(); - #endif - - static void M400(); - - #if HAS_BED_PROBE - static void M401(); - static void M402(); - #endif - - #if HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3 - static void M403(); - #endif - - #if ENABLED(FILAMENT_WIDTH_SENSOR) - static void M404(); - static void M405(); - static void M406(); - static void M407(); - #endif - - #if HAS_FILAMENT_SENSOR - static void M412(); - static void M412_report(const bool forReplay=true); - #endif - - #if HAS_MULTI_LANGUAGE - static void M414(); - static void M414_report(const bool forReplay=true); - #endif - - #if HAS_LEVELING - static void M420(); - static void M420_report(const bool forReplay=true); - static void M421(); - #endif - - #if ENABLED(BACKLASH_GCODE) - static void M425(); - static void M425_report(const bool forReplay=true); - #endif - - #if HAS_HOME_OFFSET - static void M428(); - #endif - - #if HAS_POWER_MONITOR - static void M430(); - #endif - - #if HAS_RS485_SERIAL - static void M485(); - #endif - - #if ENABLED(CANCEL_OBJECTS) - static void M486(); - #endif - - #if ENABLED(FT_MOTION) - static void M493(); - static void M493_report(const bool forReplay=true); - #endif - - static void M500(); - static void M501(); - static void M502(); - #if DISABLED(DISABLE_M503) - static void M503(); - #endif - #if ENABLED(EEPROM_SETTINGS) - static void M504(); - #endif - - #if ENABLED(PASSWORD_FEATURE) - static void M510(); - #if ENABLED(PASSWORD_UNLOCK_GCODE) - static void M511(); - #endif - #if ENABLED(PASSWORD_CHANGE_GCODE) - static void M512(); - #endif - #endif - - #if HAS_MEDIA - static void M524(); - #endif - - #if ENABLED(SD_ABORT_ON_ENDSTOP_HIT) - static void M540(); - #endif - - #if HAS_ETHERNET - static void M552(); - static void M552_report(); - static void M553(); - static void M553_report(); - static void M554(); - static void M554_report(); - #endif - - #if HAS_STEALTHCHOP - static void M569(); - static void M569_report(const bool forReplay=true); - #endif - - #if ENABLED(BAUD_RATE_GCODE) - static void M575(); - #endif - - #if ENABLED(NONLINEAR_EXTRUSION) - static void M592(); - static void M592_report(const bool forReplay=true); - #endif - - #if HAS_ZV_SHAPING - static void M593(); - static void M593_report(const bool forReplay=true); - #endif - - #if ENABLED(ADVANCED_PAUSE_FEATURE) - static void M600(); - static void M603(); - static void M603_report(const bool forReplay=true); - #endif - - #if HAS_DUPLICATION_MODE - static void M605(); - #endif - - #if IS_KINEMATIC - static void M665(); - static void M665_report(const bool forReplay=true); - #endif - - #if ANY(DELTA, HAS_EXTRA_ENDSTOPS) - static void M666(); - static void M666_report(const bool forReplay=true); - #endif - - #if ENABLED(DUET_SMART_EFFECTOR) && PIN_EXISTS(SMART_EFFECTOR_MOD) - static void M672(); - #endif - - #if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES) - static void M701(); - static void M702(); - #endif - - #if HAS_PRUSA_MMU3 - static void M704(); - static void M705(); - static void M706(); - static void M707(); - static void M708(); - static void M709(); - static void MMU3_report(const bool forReplay=true); - #endif - - #if ENABLED(GCODE_REPEAT_MARKERS) - static void M808(); - #endif - - #if ENABLED(GCODE_MACROS) - static void M810_819(); - static void M820(); - #endif - - #if HAS_BED_PROBE - static void M851(); - static void M851_report(const bool forReplay=true); - #endif - - #if ENABLED(SKEW_CORRECTION_GCODE) - static void M852(); - static void M852_report(const bool forReplay=true); - #endif - - #if ENABLED(I2C_POSITION_ENCODERS) - FORCE_INLINE static void M860() { I2CPEM.M860(); } - FORCE_INLINE static void M861() { I2CPEM.M861(); } - FORCE_INLINE static void M862() { I2CPEM.M862(); } - FORCE_INLINE static void M863() { I2CPEM.M863(); } - FORCE_INLINE static void M864() { I2CPEM.M864(); } - FORCE_INLINE static void M865() { I2CPEM.M865(); } - FORCE_INLINE static void M866() { I2CPEM.M866(); } - FORCE_INLINE static void M867() { I2CPEM.M867(); } - FORCE_INLINE static void M868() { I2CPEM.M868(); } - FORCE_INLINE static void M869() { I2CPEM.M869(); } - #endif - - #if HAS_PTC - static void M871(); - #endif - - #if ENABLED(LIN_ADVANCE) - static void M900(); - static void M900_report(const bool forReplay=true); - #endif - - #if HAS_TRINAMIC_CONFIG - static void M122(); - static void M906(); - static void M906_report(const bool forReplay=true); - #if ENABLED(MONITOR_DRIVER_STATUS) - static void M911(); - static void M912(); - #endif - #if ENABLED(HYBRID_THRESHOLD) - static void M913(); - static void M913_report(const bool forReplay=true); - #endif - #if USE_SENSORLESS - static void M914(); - static void M914_report(const bool forReplay=true); - #endif - static void M919(); - #endif - - #if HAS_MOTOR_CURRENT_SPI || HAS_MOTOR_CURRENT_PWM || HAS_MOTOR_CURRENT_I2C || HAS_MOTOR_CURRENT_DAC - static void M907(); - #if HAS_MOTOR_CURRENT_SPI || HAS_MOTOR_CURRENT_PWM - static void M907_report(const bool forReplay=true); - #endif - #endif - #if HAS_MOTOR_CURRENT_SPI || HAS_MOTOR_CURRENT_DAC - static void M908(); - #endif - #if HAS_MOTOR_CURRENT_DAC - static void M909(); - static void M910(); - #endif - - #if HAS_MEDIA - static void M928(); - #endif - - #if ENABLED(OTA_FIRMWARE_UPDATE) - static void M936(); - #endif - - #if ENABLED(MAGNETIC_PARKING_EXTRUDER) - static void M951(); - #endif - - #if ENABLED(TOUCH_SCREEN_CALIBRATION) - static void M995(); - #endif - - #if SPI_FLASH_BACKUP - static void M993(); - static void M994(); - #endif - - #if ENABLED(PLATFORM_M997_SUPPORT) - static void M997(); - #endif - - static void M999(); - - #if ENABLED(POWER_LOSS_RECOVERY) - static void M413(); - static void M413_report(const bool forReplay=true); - static void M1000(); - #endif - - #if ENABLED(X_AXIS_TWIST_COMPENSATION) - static void M423(); - static void M423_report(const bool forReplay=true); - #endif - - #if HAS_MEDIA - static void M1001(); - #endif - - #if DGUS_LCD_UI_MKS - static void M1002(); - #endif - - #if ENABLED(ONE_CLICK_PRINT) - static void M1003(); - #endif - - #if ENABLED(UBL_MESH_WIZARD) - static void M1004(); - #endif - - #if ENABLED(HAS_MCP3426_ADC) - static void M3426(); - #endif - - #if ENABLED(MAX7219_GCODE) - static void M7219(); - #endif - - #if ENABLED(CONTROLLER_FAN_EDITABLE) - static void M710(); - static void M710_report(const bool forReplay=true); - #endif - - static void T(const int8_t tool_index) IF_DISABLED(HAS_TOOLCHANGE, { UNUSED(tool_index); }); - -}; - -extern GcodeSuite gcode; diff --git a/Marlin/Modified files/menu_probe_level.cpp b/Marlin/Modified files/menu_probe_level.cpp deleted file mode 100644 index 51b7c08952..0000000000 --- a/Marlin/Modified files/menu_probe_level.cpp +++ /dev/null @@ -1,423 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * Based on Sprinter and grbl. - * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -// -// Probe and Level (Calibrate?) Menu -// - -#include "../../inc/MarlinConfigPre.h" - -#ifdef USE_PROBE_FOR_MESH_REF - #include "../../gcode/gcode.h" -#endif - -#if HAS_MARLINUI_MENU && ANY(HAS_LEVELING, HAS_BED_PROBE, ASSISTED_TRAMMING_WIZARD, LCD_BED_TRAMMING) - -#include "menu_item.h" - -#include "../../feature/bedlevel/bedlevel.h" - -#if HAS_LEVELING - #include "../../module/planner.h" // for leveling_active, z_fade_height -#endif - -#if HAS_BED_PROBE - #include "../../module/probe.h" -#endif - -#if ENABLED(BABYSTEP_ZPROBE_OFFSET) - #include "../../feature/babystep.h" -#endif - -#if ALL(TOUCH_SCREEN, HAS_GRAPHICAL_TFT) - #include "../tft/tft.h" - #include "../tft/touch.h" -#endif - -#if ENABLED(LCD_BED_LEVELING) && ANY(PROBE_MANUALLY, MESH_BED_LEVELING) - - #include "../../module/motion.h" - #include "../../gcode/queue.h" - - // - // Motion > Level Bed handlers - // - - // LCD probed points are from defaults - constexpr grid_count_t total_probe_points = TERN(AUTO_BED_LEVELING_3POINT, 3, GRID_MAX_POINTS); - - // - // Bed leveling is done. Wait for G29 to complete. - // A flag is used so that this can release control - // and allow the command queue to be processed. - // - // When G29 finishes the last move: - // - Raise Z to the "Z after probing" height - // - Don't return until done. - // - // ** This blocks the command queue! ** - // - void _lcd_level_bed_done() { - if (!ui.wait_for_move) { - #if DISABLED(MESH_BED_LEVELING) && defined(Z_AFTER_PROBING) - if (Z_AFTER_PROBING) { - // Display "Done" screen and wait for moves to complete - line_to_z(Z_AFTER_PROBING); - ui.synchronize(GET_TEXT_F(MSG_LEVEL_BED_DONE)); - } - #endif - ui.goto_previous_screen_no_defer(); - ui.completion_feedback(); - } - if (ui.should_draw()) MenuItem_static::draw(LCD_HEIGHT >= 4, GET_TEXT_F(MSG_LEVEL_BED_DONE)); - ui.refresh(LCDVIEW_CALL_REDRAW_NEXT); - } - - void _lcd_level_goto_next_point(); - - // - // Step 7: Get the Z coordinate, click goes to the next point or exits - // - void _lcd_level_bed_get_z() { - - if (ui.use_click()) { - - // - // Save the current Z position and move - // - - // If done... - if (++manual_probe_index >= total_probe_points) { - // - // The last G29 records the point and enables bed leveling - // - ui.wait_for_move = true; - ui.goto_screen(_lcd_level_bed_done); - #if ENABLED(MESH_BED_LEVELING) - queue.inject(F("G29S2")); - #elif ENABLED(PROBE_MANUALLY) - queue.inject(F("G29V1")); - #endif - } - else - _lcd_level_goto_next_point(); - - return; - } - - // - // Encoder knob or keypad buttons adjust the Z position - // - if (ui.encoderPosition) { - const float z = current_position.z + float(int32_t(ui.encoderPosition)) * (MESH_EDIT_Z_STEP); - line_to_z(constrain(z, -(LCD_PROBE_Z_RANGE) * 0.5f, (LCD_PROBE_Z_RANGE) * 0.5f)); - ui.refresh(LCDVIEW_CALL_REDRAW_NEXT); - ui.encoderPosition = 0; - } - - // - // Draw on first display, then only on Z change - // - if (ui.should_draw()) { - const float v = current_position.z; - MenuEditItemBase::draw_edit_screen(GET_TEXT_F(MSG_MOVE_Z), ftostr43sign(v + (v < 0 ? -0.0001f : 0.0001f), '+')); - } - } - - // - // Step 6: Display "Next point: 1 / 9" while waiting for move to finish - // - void _lcd_level_bed_moving() { - if (ui.should_draw()) { - MString<10> msg; - msg.setf(F(" %i / %u"), int(manual_probe_index + 1), total_probe_points); - MenuItem_static::draw(LCD_HEIGHT / 2, GET_TEXT_F(MSG_LEVEL_BED_NEXT_POINT), SS_CENTER, msg); - } - ui.refresh(LCDVIEW_CALL_NO_REDRAW); - if (!ui.wait_for_move) ui.goto_screen(_lcd_level_bed_get_z); - } - - // - // Step 5: Initiate a move to the next point - // - void _lcd_level_goto_next_point() { - ui.goto_screen(_lcd_level_bed_moving); - - // G29 Records Z, moves, and signals when it pauses - ui.wait_for_move = true; - #if ENABLED(MESH_BED_LEVELING) - queue.inject(manual_probe_index ? F("G29S2") : F("G29S1")); - #elif ENABLED(PROBE_MANUALLY) - queue.inject(F("G29V1")); - #endif - } - - // - // Step 4: Display "Click to Begin", wait for click - // Move to the first probe position - // - void _lcd_level_bed_homing_done() { - if (ui.should_draw()) { - MenuItem_static::draw(1, GET_TEXT_F(MSG_LEVEL_BED_WAITING)); - // Color UI needs a control to detect a touch - #if ALL(TOUCH_SCREEN, HAS_GRAPHICAL_TFT) - touch.add_control(CLICK, 0, 0, TFT_WIDTH, TFT_HEIGHT); - #endif - } - if (ui.use_click()) { - manual_probe_index = 0; - _lcd_level_goto_next_point(); - } - } - - // - // Step 3: Display "Homing XYZ" - Wait for homing to finish - // - void _lcd_level_bed_homing() { - _lcd_draw_homing(); - if (all_axes_homed()) ui.goto_screen(_lcd_level_bed_homing_done); - } - - #if ENABLED(PROBE_MANUALLY) - extern bool g29_in_progress; - #endif - - // - // Step 2: Continue Bed Leveling... - // - void _lcd_level_bed_continue() { - ui.defer_status_screen(); - set_all_unhomed(); - ui.goto_screen(_lcd_level_bed_homing); - queue.inject_P(G28_STR); - } - -#endif // LCD_BED_LEVELING && (PROBE_MANUALLY || MESH_BED_LEVELING) - -#if ENABLED(MESH_EDIT_MENU) - - inline void refresh_planner() { - set_current_from_steppers_for_axis(ALL_AXES_ENUM); - sync_plan_position(); - } - - void menu_edit_mesh() { - static uint8_t xind, yind; // =0 - START_MENU(); - BACK_ITEM(MSG_BED_LEVELING); - EDIT_ITEM(uint8, MSG_MESH_X, &xind, 0, (GRID_MAX_POINTS_X) - 1); - EDIT_ITEM(uint8, MSG_MESH_Y, &yind, 0, (GRID_MAX_POINTS_Y) - 1); - EDIT_ITEM_FAST(float43, MSG_MESH_EDIT_Z, &bedlevel.z_values[xind][yind], -(LCD_PROBE_Z_RANGE) * 0.5, (LCD_PROBE_Z_RANGE) * 0.5, refresh_planner); - END_MENU(); - } - -#endif // MESH_EDIT_MENU - -#if ENABLED(AUTO_BED_LEVELING_UBL) - void _lcd_ubl_level_bed(); -#endif - -#if ENABLED(ASSISTED_TRAMMING_WIZARD) - void goto_tramming_wizard(); -#endif - -// Include a sub-menu when there's manual probing - -void menu_probe_level() { - const bool can_babystep_z = TERN0(BABYSTEP_ZPROBE_OFFSET, babystep.can_babystep(Z_AXIS)); - - #if HAS_LEVELING - const bool is_homed = all_axes_homed(), - is_valid = leveling_is_valid(); - #endif - - #if NONE(PROBE_MANUALLY, MESH_BED_LEVELING) - const bool is_trusted = all_axes_trusted(); - #endif - - START_MENU(); - - // - // ^ Main - // - BACK_ITEM(MSG_MAIN_MENU); - - if (!g29_in_progress) { - - // Auto Home if not using manual probing - #if NONE(PROBE_MANUALLY, MESH_BED_LEVELING) - if (!is_trusted) GCODES_ITEM(MSG_AUTO_HOME, FPSTR(G28_STR)); - #endif - - #if HAS_LEVELING - - // Homed and leveling is valid? Then leveling can be toggled. - if (is_homed && is_valid) { - bool show_state = planner.leveling_active; - EDIT_ITEM(bool, MSG_BED_LEVELING, &show_state, _lcd_toggle_bed_leveling); - } - - // - // Level Bed - // - #if ENABLED(AUTO_BED_LEVELING_UBL) - // UBL uses a guided procedure - SUBMENU(MSG_UBL_LEVELING, _lcd_ubl_level_bed); - #elif ANY(PROBE_MANUALLY, MESH_BED_LEVELING) - #if ENABLED(LCD_BED_LEVELING) - // Manual leveling uses a guided procedure - SUBMENU(MSG_LEVEL_BED, _lcd_level_bed_continue); - #endif - #else - // Automatic leveling can just run the G-code - GCODES_ITEM(MSG_LEVEL_BED, is_homed ? F("G29") : F("G29N")); - #endif - - // - // Edit Mesh (non-UBL) - // - #if ENABLED(MESH_EDIT_MENU) - if (is_valid) SUBMENU(MSG_EDIT_MESH, menu_edit_mesh); - #endif - - // - // Mesh Bed Leveling Z-Offset - // - #if ENABLED(MESH_BED_LEVELING) - #if WITHIN(PROBE_OFFSET_ZMIN, -9, 9) - #define LCD_Z_OFFSET_TYPE float43 // Values from -9.000 to +9.000 - #else - #define LCD_Z_OFFSET_TYPE float42_52 // Values from -99.99 to 99.99 - #endif - EDIT_ITEM(LCD_Z_OFFSET_TYPE, MSG_MESH_Z_OFFSET, &bedlevel.z_offset, PROBE_OFFSET_ZMIN, PROBE_OFFSET_ZMAX); - #endif - - #endif - - } // no G29 in progress - - // Z Fade Height - #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) && DISABLED(SLIM_LCD_MENUS) - // Shadow for editing the fade height - editable.decimal = planner.z_fade_height; - EDIT_ITEM_FAST(float3, MSG_Z_FADE_HEIGHT, &editable.decimal, 0, 100, []{ set_z_fade_height(editable.decimal); }); - #endif - - if (!g29_in_progress) { - // - // Probe Deploy/Stow - // - #if ENABLED(PROBE_DEPLOY_STOW_MENU) - GCODES_ITEM(MSG_MANUAL_DEPLOY, F("M401")); - GCODES_ITEM(MSG_MANUAL_STOW, F("M402")); - #endif - - // Tare the probe on-demand - #if ENABLED(PROBE_TARE_MENU) - ACTION_ITEM(MSG_TARE_PROBE, probe.tare); - #endif - - // - // Probe XY Offsets - // - #if HAS_PROBE_XY_OFFSET - EDIT_ITEM_N(float31sign, X_AXIS, MSG_ZPROBE_OFFSET_N, &probe.offset.x, PROBE_OFFSET_XMIN, PROBE_OFFSET_XMAX); - EDIT_ITEM_N(float31sign, Y_AXIS, MSG_ZPROBE_OFFSET_N, &probe.offset.y, PROBE_OFFSET_YMIN, PROBE_OFFSET_YMAX); - #endif - - // - // Probe Z Offset - Babystep or Edit - // - if (can_babystep_z) { - #if ENABLED(BABYSTEP_ZPROBE_OFFSET) - SUBMENU(MSG_BABYSTEP_PROBE_Z, lcd_babystep_zoffset); - #endif - } - else { - #if HAS_BED_PROBE - #ifdef USE_PROBE_FOR_MESH_REF - #ifdef HOME_SWITCH_TO_BED_OFFSET_MENU - // Change the name of the menu option as the offsett relates to the fixed Z stop/Homing switch but - // reduce the range to -1/+1 and use the same offset variable so function stays the same - EDIT_ITEM_N(LCD_Z_OFFSET_TYPE, Z_AXIS, MSG_HOME_OFFSET_Z, &mesh_zero_ref_offset, -1, 0); - #endif - #else - EDIT_ITEM_N(LCD_Z_OFFSET_TYPE, Z_AXIS, MSG_ZPROBE_OFFSET_N, &probe.offset.z, PROBE_OFFSET_ZMIN, PROBE_OFFSET_ZMAX); - #endif - #endif - } - - // - // Probe Z Offset Wizard - // - #if ENABLED(PROBE_OFFSET_WIZARD) - SUBMENU(MSG_PROBE_WIZARD, goto_probe_offset_wizard); - #endif - - // - // Probe Repeatability Test - // - #if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST) - GCODES_ITEM(MSG_M48_TEST, F("G28O\nM48 P10")); - #endif - - // - // Assisted Bed Tramming - // - #if ENABLED(ASSISTED_TRAMMING_WIZARD) - SUBMENU(MSG_TRAMMING_WIZARD, goto_tramming_wizard); - #endif - - // - // Bed Tramming Submenu - // - #if ENABLED(LCD_BED_TRAMMING) - SUBMENU(MSG_BED_TRAMMING, _lcd_bed_tramming); - #endif - - // - // Auto Z-Align - // - #if ANY(Z_STEPPER_AUTO_ALIGN, MECHANICAL_GANTRY_CALIBRATION) - GCODES_ITEM(MSG_AUTO_Z_ALIGN, F("G34")); - #endif - - // - // Twist Compensation - // - #if ENABLED(X_AXIS_TWIST_COMPENSATION) - SUBMENU(MSG_XATC, xatc_wizard_continue); - #endif - - // - // Store to EEPROM - // - #if ENABLED(EEPROM_SETTINGS) - ACTION_ITEM(MSG_STORE_EEPROM, ui.store_settings); - #endif - - } - - END_MENU(); -} - -#endif // HAS_MARLINUI_MENU && (HAS_LEVELING || HAS_BED_PROBE || ASSISTED_TRAMMING_WIZARD || LCD_BED_TRAMMING) diff --git a/Marlin/Modified files/ubl_G29.cpp b/Marlin/Modified files/ubl_G29.cpp deleted file mode 100644 index 733aaa8abe..0000000000 --- a/Marlin/Modified files/ubl_G29.cpp +++ /dev/null @@ -1,1898 +0,0 @@ -/** - * Marlin 3D Printer Firmware - * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] - * - * Based on Sprinter and grbl. - * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#include "../../../inc/MarlinConfig.h" - -#if ENABLED(AUTO_BED_LEVELING_UBL) - -#include "../bedlevel.h" - -#include "../../../MarlinCore.h" -#include "../../../HAL/shared/eeprom_api.h" -#include "../../../libs/hex_print.h" -#include "../../../module/settings.h" -#include "../../../lcd/marlinui.h" -#include "../../../module/planner.h" -#include "../../../module/motion.h" -#include "../../../module/probe.h" -#include "../../../gcode/gcode.h" -#include "../../../libs/least_squares_fit.h" - -#define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE) -#include "../../../core/debug_out.h" - -#if ENABLED(EXTENSIBLE_UI) - #include "../../../lcd/extui/ui_api.h" -#endif - -#if ENABLED(UBL_HILBERT_CURVE) - #include "../hilbert_curve.h" -#endif - -#if FT_MOTION_DISABLE_FOR_PROBING - #include "../../../module/ft_motion.h" -#endif - -#include - -#define UBL_G29_P31 - -#if HAS_MARLINUI_MENU - - bool unified_bed_leveling::lcd_map_control = false; - - void unified_bed_leveling::steppers_were_disabled() { - if (lcd_map_control) { - lcd_map_control = false; - ui.defer_status_screen(false); - } - } - - void ubl_map_screen(); - -#endif - -#define SIZE_OF_LITTLE_RAISE 1 -#define BIG_RAISE_NOT_NEEDED 0 - -#ifdef USE_PROBE_FOR_MESH_REF - float mesh_zero_ref_offset = 0; // declared in gcode.h as external so it can be set in menu_probe_level.cpp -#endif - -/** - * G29: Unified Bed Leveling by Roxy - * - * Parameters understood by this leveling system: - * - * A Activate Activate the Unified Bed Leveling system. - * - * B # Business Use the 'Business Card' mode of the Manual Probe subsystem with P2. - * Note: A non-compressible Spark Gap feeler gauge is recommended over a business card. - * In this mode of G29 P2, a business or index card is used as a shim that the nozzle can - * grab onto as it is lowered. In principle, the nozzle-bed distance is the same when the - * same resistance is felt in the shim. You can omit the numerical value on first invocation - * of G29 P2 B to measure shim thickness. Subsequent use of 'B' will apply the previously- - * measured thickness by default. - * - * C Continue G29 P1 C continues the generation of a partially-constructed Mesh without invalidating - * previous measurements. - * - * C G29 P2 C tells the Manual Probe subsystem to not use the current nozzle - * location in its search for the closest unmeasured Mesh Point. Instead, attempt to - * start at one end of the uprobed points and Continue sequentially. - * - * G29 P3 C specifies the Constant for the fill. Otherwise, uses a "reasonable" value. - * - * C Current G29 Z C uses the Current location (instead of bed center or nearest edge). - * - * D Disable Disable the Unified Bed Leveling system. - * - * E Stow_probe Stow the probe after each sampled point. - * - * F # Fade Fade the amount of Mesh Based Compensation over a specified height. At the - * specified height, no correction is applied and natural printer kenimatics take over. If no - * number is specified for the command, 10mm is assumed to be reasonable. - * - * H # Height With P2, 'H' specifies the Height to raise the nozzle after each manual probe of the bed. - * If omitted, the nozzle will raise by Z_CLEARANCE_BETWEEN_PROBES. - * - * H # Offset With P4, 'H' specifies the Offset above the mesh height to place the nozzle. - * If omitted, Z_TWEEN_SAFE_CLEARANCE will be used. - * - * I # Invalidate Invalidate the specified number of Mesh Points near the given 'X' 'Y'. If X or Y are omitted, - * the nozzle location is used. If no 'I' value is given, only the point nearest to the location - * is invalidated. Use 'T' to produce a map afterward. This command is useful to invalidate a - * portion of the Mesh so it can be adjusted using other UBL tools. When attempting to invalidate - * an isolated bad mesh point, the 'T' option shows the nozzle position in the Mesh with (#). You - * can move the nozzle around and use this feature to select the center of the area (or cell) to - * invalidate. - * - * J # Grid Perform a Grid Based Leveling of the current Mesh using a grid with n points on a side. - * Not specifying a grid size will invoke the 3-Point leveling function. - * - * L Load Load Mesh from the previously activated location in the EEPROM. - * - * L # Load Load Mesh from the specified location in the EEPROM. Set this location as activated - * for subsequent Load and Store operations. - * - * The P or Phase commands are used for the bulk of the work to setup a Mesh. In general, your Mesh will - * start off being initialized with a G29 P0 or a G29 P1. Further refinement of the Mesh happens with - * each additional Phase that processes it. - * - * P0 Phase 0 Zero Mesh Data and turn off the Mesh Compensation System. This reverts the - * 3D Printer to the same state it was in before the Unified Bed Leveling Compensation - * was turned on. Setting the entire Mesh to Zero is a special case that allows - * a subsequent G or T leveling operation for backward compatibility. - * - * P1 Phase 1 Invalidate entire Mesh and continue with automatic generation of the Mesh data using - * the Z-Probe. Usually the probe can't reach all areas that the nozzle can reach. For delta - * printers only the areas where the probe and nozzle can both reach will be automatically probed. - * - * Unreachable points will be handled in Phase 2 and Phase 3. - * - * Use 'C' to leave the previous mesh intact and automatically probe needed points. This allows you - * to invalidate parts of the Mesh but still use Automatic Probing. - * - * The 'X' and 'Y' parameters prioritize where to try and measure points. If omitted, the current - * probe position is used. - * - * Use 'T' (Topology) to generate a report of mesh generation. - * - * P1 will suspend Mesh generation if the controller button is held down. Note that you may need - * to press and hold the switch for several seconds if moves are underway. - * - * P2 Phase 2 Probe unreachable points. - * - * Use 'H' to set the height between Mesh points. If omitted, Z_CLEARANCE_BETWEEN_PROBES is used. - * Smaller values will be quicker. Move the nozzle down till it barely touches the bed. Make sure the - * nozzle is clean and unobstructed. Use caution and move slowly. This can damage your printer! - * (Uses SIZE_OF_LITTLE_RAISE mm if the nozzle is moving less than BIG_RAISE_NOT_NEEDED mm.) - * - * The 'H' value can be negative if the Mesh dips in a large area. Press and hold the - * controller button to terminate the current Phase 2 command. You can then re-issue "G29 P 2" - * with an 'H' parameter more suitable for the area you're manually probing. Note that the command - * tries to start in a corner of the bed where movement will be predictable. Override the distance - * calculation location with the X and Y parameters. You can print a Mesh Map (G29 T) to see where - * the mesh is invalidated and where the nozzle needs to move to complete the command. Use 'C' to - * indicate that the search should be based on the current position. - * - * The 'B' parameter for this command is described above. It places the manual probe subsystem into - * Business Card mode where the thickness of a business card is measured and then used to accurately - * set the nozzle height in all manual probing for the duration of the command. A Business card can - * be used, but you'll get better results with a flexible Shim that doesn't compress. This makes it - * easier to produce similar amounts of force and get more accurate measurements. Google if you're - * not sure how to use a shim. - * - * The 'T' (Map) parameter helps track Mesh building progress. - * - * NOTE: P2 requires an LCD controller! - * - * P3 Phase 3 Fill the unpopulated regions of the Mesh with a fixed value. There are two different paths to - * go down: - * - * - If a 'C' constant is specified, the closest invalid mesh points to the nozzle will be filled, - * and a repeat count can then also be specified with 'R'. - * - * - Leaving out 'C' invokes Smart Fill, which scans the mesh from the edges inward looking for - * invalid mesh points. Adjacent points are used to determine the bed slope. If the bed is sloped - * upward from the invalid point, it takes the value of the nearest point. If sloped downward, it's - * replaced by a value that puts all three points in a line. This version of G29 P3 is a quick, easy - * and (usually) safe way to populate unprobed mesh regions before continuing to G26 Mesh Validation - * Pattern. Note that this populates the mesh with unverified values. Pay attention and use caution. - * - * P4 Phase 4 Fine tune the Mesh. The Delta Mesh Compensation System assumes the existence of - * an LCD Panel. It is possible to fine tune the mesh without an LCD Panel using - * G42 and M421. See the UBL documentation for further details. - * - * Phase 4 is meant to be used with G26 Mesh Validation to fine tune the mesh by direct editing - * of Mesh Points. Raise and lower points to fine tune the mesh until it gives consistently reliable - * adhesion. - * - * P4 moves to the closest Mesh Point (and/or the given X Y), raises the nozzle above the mesh height - * by the given 'H' offset (or default 0), and waits while the controller is used to adjust the nozzle - * height. On click the displayed height is saved in the mesh. - * - * Start Phase 4 at a specific location with X and Y. Adjust a specific number of Mesh Points with - * the 'R' (Repeat) parameter. (If 'R' is left out, the whole matrix is assumed.) This command can be - * terminated early (e.g., after editing the area of interest) by pressing and holding the encoder button. - * - * The general form is G29 P4 [R points] [X position] [Y position] - * - * The H [offset] parameter is useful if a shim is used to fine-tune the mesh. For a 0.4mm shim the - * command would be G29 P4 H0.4. The nozzle is moved to the shim height, you adjust height to the shim, - * and on click the height minus the shim thickness will be saved in the mesh. - * - * !!Use with caution, as a very poor mesh could cause the nozzle to crash into the bed!! - * - * NOTE: P4 is not available unless you have LCD support enabled! - * - * P5 Phase 5 Find Mean Mesh Height and Standard Deviation. Typically, it is easier to use and - * work with the Mesh if it is Mean Adjusted. You can specify a C parameter to - * Correct the Mesh to a 0.00 Mean Height. Adding a C parameter will automatically - * execute a G29 P6 C . - * - * P6 Phase 6 Shift Mesh height. The entire Mesh's height is adjusted by the height specified - * with the C parameter. Being able to adjust the height of a Mesh is useful tool. It - * can be used to compensate for poorly calibrated Z-Probes and other errors. Ideally, - * you should have the Mesh adjusted for a Mean Height of 0.00 and the Z-Probe measuring - * 0.000 at the Z Home location. - * - * Q Test Load specified Test Pattern to assist in checking correct operation of system. This - * command is not anticipated to be of much value to the typical user. It is intended - * for developers to help them verify correct operation of the Unified Bed Leveling System. - * - * R # Repeat Repeat this command the specified number of times. If no number is specified the - * command will be repeated GRID_MAX_POINTS_X * GRID_MAX_POINTS_Y times. - * - * S Store Store the current Mesh in the Activated area of the EEPROM. It will also store the - * current state of the Unified Bed Leveling system in the EEPROM. - * - * S # Store Store the current Mesh at the specified location in EEPROM. Activate this location - * for subsequent Load and Store operations. Valid storage slot numbers begin at 0 and - * extend to a limit related to the available EEPROM storage. - * - * S -1 Store Print the current Mesh as G-code that can be used to restore the mesh anytime. - * - * T Topology Display the Mesh Map Topology. - * 'T' can be used alone (e.g., G29 T) or in combination with most of the other commands. - * This option works with all Phase commands (e.g., G29 P4 R 5 T X 50 Y100 C -.1 O) - * This parameter can also specify a Map Type. T0 (the default) is user-readable. T1 - * is suitable to paste into a spreadsheet for a 3D graph of the mesh. - * - * U Unlevel Perform a probe of the outer perimeter to assist in physically leveling unlevel beds. - * Only used for G29 P1 T U. This speeds up the probing of the edge of the bed. Useful - * when the entire bed doesn't need to be probed because it will be adjusted. - * - * V # Verbosity Set the verbosity level (0-4) for extra details. (Default 0) - * - * X # X Location for this command - * - * Y # Y Location for this command - * - * With UBL_DEVEL_DEBUGGING: - * - * K # Kompare Kompare current Mesh with stored Mesh #, replacing current Mesh with the result. - * This command literally performs a diff between two Meshes. - * - * Q-1 Dump EEPROM Dump the UBL contents stored in EEPROM as HEX format. Useful for developers to help - * verify correct operation of the UBL. - * - * W What? Display valuable UBL data. - * - * - * Release Notes: - * You MUST do M502, M500 to initialize the storage. Failure to do this will cause all - * kinds of problems. Enabling EEPROM Storage is required. - * - * When you do a G28 and G29 P1 to automatically build your first mesh, you are going to notice that - * UBL probes points increasingly further from the starting location. (The starting location defaults - * to the center of the bed.) In contrast, ABL and MBL follow a zigzag pattern. The spiral pattern is - * especially better for Delta printers, since it populates the center of the mesh first, allowing for - * a quicker test print to verify settings. You don't need to populate the entire mesh to use it. - * After all, you don't want to spend a lot of time generating a mesh only to realize the resolution - * or probe offsets are incorrect. Mesh-generation gathers points starting closest to the nozzle unless - * an (X,Y) coordinate pair is given. - * - * Unified Bed Leveling uses a lot of EEPROM storage to hold its data, and it takes some effort to get - * the mesh just right. To prevent this valuable data from being destroyed as the EEPROM structure - * evolves, UBL stores all mesh data at the end of EEPROM. - * - * UBL is founded on Edward Patel's Mesh Bed Leveling code. A big 'Thanks!' to him and the creators of - * 3-Point and Grid Based leveling. Combining their contributions we now have the functionality and - * features of all three systems combined. - */ - -G29_parameters_t unified_bed_leveling::param; - -void unified_bed_leveling::G29() { - - #ifdef EVENT_GCODE_AFTER_G29 - bool probe_deployed = false; - #define SET_PROBE_DEPLOYED(N) probe_deployed = N - #else - #define SET_PROBE_DEPLOYED(N) - #endif - - if (G29_parse_parameters()) return; // Abort on parameter error - - const uint8_t p_val = parser.byteval('P'); - const bool may_move = p_val == 1 || p_val == 2 || p_val == 4 || parser.seen_test('J'); - - #if FT_MOTION_DISABLE_FOR_PROBING - FTMotionDisableInScope FT_Disabler; // Disable Fixed-Time Motion for probing - #endif - - // Check for commands that require the printer to be homed - if (may_move) { - planner.synchronize(); - - #ifdef USE_PROBE_FOR_MESH_REF - // Send 'N' to force homing before G29 (internal only) - if (axes_should_home() || parser.seen_test('N')){ - gcode.home_all_axes(); - } - else { - gcode.process_subcommands_now(F("G28L0 X Y")); // Home X and Y only - } - // Set the probe trigger height as Z home before leveling - probe.probe_at_point(current_position, PROBE_PT_NONE,0 ,false ,true, Z_PROBE_LOW_POINT, Z_TWEEN_SAFE_CLEARANCE, false); - set_axis_is_at_home(Z_AXIS); - sync_plan_position(); - #else - #if ALL(DWIN_LCD_PROUI, ZHOME_BEFORE_LEVELING) - save_ubl_active_state_and_disable(); - gcode.process_subcommands_now(F("G28Z")); - restore_ubl_active_state(false); // ...without telling ExtUI "done" - #else - // Send 'N' to force homing before G29 (internal only) - if (axes_should_home() || parser.seen_test('N')) gcode.home_all_axes(); - #endif - #endif - - probe.use_probing_tool(); - - #ifdef EVENT_GCODE_BEFORE_G29 - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Before G29 G-code: ", EVENT_GCODE_BEFORE_G29); - gcode.process_subcommands_now(F(EVENT_GCODE_BEFORE_G29)); - #endif - - // Position bed horizontally and Z probe vertically. - #if HAS_SAFE_BED_LEVELING - xyze_pos_t safe_position = current_position; - #ifdef SAFE_BED_LEVELING_START_X - safe_position.x = SAFE_BED_LEVELING_START_X; - #endif - #ifdef SAFE_BED_LEVELING_START_Y - safe_position.y = SAFE_BED_LEVELING_START_Y; - #endif - #ifdef SAFE_BED_LEVELING_START_Z - safe_position.z = SAFE_BED_LEVELING_START_Z; - #endif - #ifdef SAFE_BED_LEVELING_START_I - safe_position.i = SAFE_BED_LEVELING_START_I; - #endif - #ifdef SAFE_BED_LEVELING_START_J - safe_position.j = SAFE_BED_LEVELING_START_J; - #endif - #ifdef SAFE_BED_LEVELING_START_K - safe_position.k = SAFE_BED_LEVELING_START_K; - #endif - #ifdef SAFE_BED_LEVELING_START_U - safe_position.u = SAFE_BED_LEVELING_START_U; - #endif - #ifdef SAFE_BED_LEVELING_START_V - safe_position.v = SAFE_BED_LEVELING_START_V; - #endif - #ifdef SAFE_BED_LEVELING_START_W - safe_position.w = SAFE_BED_LEVELING_START_W; - #endif - - do_blocking_move_to(safe_position); - #endif // HAS_SAFE_BED_LEVELING - } - - // Invalidate one or more nearby mesh points, possibly all. - if (parser.seen('I')) { - grid_count_t count = parser.has_value() ? parser.value_ushort() : 1; - bool invalidate_all = count >= GRID_MAX_POINTS; - if (!invalidate_all) { - while (count--) { - if ((count & 0x0F) == 0x0F) idle(); - const mesh_index_pair closest = find_closest_mesh_point_of_type(REAL, param.XY_pos); - // No more REAL mesh points to invalidate? Assume the user meant - // to invalidate the ENTIRE mesh, which can't be done with - // find_closest_mesh_point (which only returns REAL points). - if (closest.pos.x < 0) { invalidate_all = true; break; } - z_values[closest.pos.x][closest.pos.y] = NAN; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(closest.pos, 0.0f)); - } - } - if (invalidate_all) { - invalidate(); - SERIAL_ECHOPGM("Entire Mesh"); - } - else - SERIAL_ECHOPGM("Locations"); - SERIAL_ECHOLNPGM(" invalidated.\n"); - } - - if (parser.seen('Q')) { - const int16_t test_pattern = parser.has_value() ? parser.value_int() : -99; - if (!WITHIN(test_pattern, TERN0(UBL_DEVEL_DEBUGGING, -1), 2)) { - SERIAL_ECHOLNPGM("?Invalid (Q) test pattern. (" TERN(UBL_DEVEL_DEBUGGING, "-1", "0") " to 2)\n"); - return; - } - SERIAL_ECHOLNPGM("Applying test pattern.\n"); - switch (test_pattern) { - - default: - case -1: TERN_(UBL_DEVEL_DEBUGGING, g29_eeprom_dump()); break; - - case 0: - GRID_LOOP(x, y) { // Create a bowl shape similar to a poorly-calibrated Delta - const float p1 = 0.5f * (GRID_MAX_POINTS_X) - x, - p2 = 0.5f * (GRID_MAX_POINTS_Y) - y; - z_values[x][y] += 2.0f * HYPOT(p1, p2); - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); - } - break; - - case 1: - for (uint8_t x = 0; x < GRID_MAX_POINTS_X; ++x) { // Create a diagonal line several Mesh cells thick that is raised - const uint8_t x2 = x + (x < (GRID_MAX_POINTS_Y) - 1 ? 1 : -1); - z_values[x][x] += 9.999f; - z_values[x][x2] += 9.999f; // We want the altered line several mesh points thick - #if ENABLED(EXTENSIBLE_UI) - ExtUI::onMeshUpdate(x, x, z_values[x][x]); - ExtUI::onMeshUpdate(x, x2, z_values[x][x2]); - #endif - } - break; - - case 2: - // Allow the user to specify the height because 10mm is a little extreme in some cases. - for (uint8_t x = (GRID_MAX_POINTS_X) / 3; x < 2 * (GRID_MAX_POINTS_X) / 3; x++) // Create a rectangular raised area in - for (uint8_t y = (GRID_MAX_POINTS_Y) / 3; y < 2 * (GRID_MAX_POINTS_Y) / 3; y++) { // the center of the bed - z_values[x][y] += parser.seen_test('C') ? param.C_constant : 9.99f; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); - } - break; - } - } - - #if HAS_BED_PROBE - - if (parser.seen_test('J')) { - save_ubl_active_state_and_disable(); - tilt_mesh_based_on_probed_grid(param.J_grid_size == 0); // Zero size does 3-Point - restore_ubl_active_state(); - #if ENABLED(UBL_G29_J_RECENTER) - do_blocking_move_to_xy(0.5f * ((MESH_MIN_X) + (MESH_MAX_X)), 0.5f * ((MESH_MIN_Y) + (MESH_MAX_Y))); - #endif - report_current_position(); - SET_PROBE_DEPLOYED(true); - } - - #endif // HAS_BED_PROBE - - if (parser.seen_test('P')) { - if (WITHIN(param.P_phase, 0, 1) && storage_slot == -1) { - storage_slot = 0; - SERIAL_ECHOLNPGM("Default storage slot 0 selected."); - } - - switch (param.P_phase) { - case 0: - // - // Zero Mesh Data - // - reset(); - SERIAL_ECHOLNPGM("Mesh zeroed."); - break; - - #if HAS_BED_PROBE - - case 1: { - // - // Invalidate Entire Mesh and Automatically Probe Mesh in areas that can be reached by the probe - // - if (!parser.seen_test('C')) { - invalidate(); - SERIAL_ECHOLNPGM("Mesh invalidated. Probing mesh."); - } - if (param.V_verbosity > 1) - SERIAL_ECHOLN(F("Probing around ("), param.XY_pos.x, C(','), param.XY_pos.y, F(").\n")); - probe_entire_mesh(param.XY_pos, parser.seen_test('T'), parser.seen_test('E'), parser.seen_test('U')); - - report_current_position(); - SET_PROBE_DEPLOYED(true); - } break; - - #endif // HAS_BED_PROBE - - case 2: { - #if HAS_MARLINUI_MENU - // - // Manually Probe Mesh in areas that can't be reached by the probe - // - SERIAL_ECHOLNPGM("Manually probing unreachable points."); - do_z_clearance(Z_CLEARANCE_BETWEEN_PROBES); - - if (parser.seen_test('C') && !param.XY_seen) { - - /** - * Use a good default location for the path. - * The flipped > and < operators in these comparisons is intentional. - * It should cause the probed points to follow a nice path on Cartesian printers. - * It may make sense to have Delta printers default to the center of the bed. - * Until that is decided, this can be forced with the X and Y parameters. - */ - param.XY_pos.set( - #if IS_KINEMATIC - X_HOME_POS, Y_HOME_POS - #else - probe.offset_xy.x > 0 ? X_BED_SIZE : 0, - probe.offset_xy.y < 0 ? Y_BED_SIZE : 0 - #endif - ); - } - - if (parser.seen('B')) { - param.B_shim_thickness = parser.has_value() ? parser.value_float() : measure_business_card_thickness(); - if (ABS(param.B_shim_thickness) > 1.5f) { - SERIAL_ECHOLNPGM("?Error in Business Card measurement."); - return; - } - SET_PROBE_DEPLOYED(true); - } - - if (!position_is_reachable(param.XY_pos)) { - SERIAL_ECHOLNPGM("XY outside printable radius."); - return; - } - - const float height = parser.floatval('H', Z_CLEARANCE_BETWEEN_PROBES); - manually_probe_remaining_mesh(param.XY_pos, height, param.B_shim_thickness, parser.seen_test('T')); - - SERIAL_ECHOLNPGM("G29 P2 finished."); - - report_current_position(); - - #else - - SERIAL_ECHOLNPGM("?P2 is only available when an LCD is present."); - return; - - #endif - } break; - - case 3: { - /** - * Populate invalid mesh areas. Proceed with caution. - * Two choices are available: - * - Specify a constant with the 'C' parameter. - * - Allow 'G29 P3' to choose a 'reasonable' constant. - */ - - if (param.C_seen) { - if (param.R_repetition >= GRID_MAX_POINTS) { - set_all_mesh_points_to_value(param.C_constant); - } - else { - while (param.R_repetition--) { // this only populates reachable mesh points near - const mesh_index_pair closest = find_closest_mesh_point_of_type(INVALID, param.XY_pos); - const xy_int8_t &cpos = closest.pos; - if (cpos.x < 0) { - // No more REAL INVALID mesh points to populate, so we ASSUME - // user meant to populate ALL INVALID mesh points to value - GRID_LOOP(x, y) if (isnan(z_values[x][y])) z_values[x][y] = param.C_constant; - break; // No more invalid Mesh Points to populate - } - else { - z_values[cpos.x][cpos.y] = param.C_constant; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(cpos, param.C_constant)); - } - } - } - } - else { - const float cvf = parser.value_float(); - switch ((int)TRUNC(cvf * 10.0f) - 30) { // 3.1 -> 1 - #if ENABLED(UBL_G29_P31) - case 1: { - - // P3.1 use least squares fit to fill missing mesh values - // P3.10 zero weighting for distance, all grid points equal, best fit tilted plane - // P3.11 10X weighting for nearest grid points versus farthest grid points - // P3.12 100X distance weighting - // P3.13 1000X distance weighting, approaches simple average of nearest points - - const float weight_power = (cvf - 3.10f) * 100.0f, // 3.12345 -> 2.345 - weight_factor = weight_power ? POW(10.0f, weight_power) : 0; - smart_fill_wlsf(weight_factor); - } - break; - #endif - case 0: // P3 or P3.0 - default: // and anything P3.x that's not P3.1 - smart_fill_mesh(); // Do a 'Smart' fill using nearby known values - break; - } - } - break; - } - - case 4: // Fine Tune (i.e., Edit) the Mesh - #if HAS_MARLINUI_MENU - fine_tune_mesh(param.XY_pos, parser.seen_test('T')); - #else - SERIAL_ECHOLNPGM("?P4 is only available when an LCD is present."); - return; - #endif - break; - - case 5: adjust_mesh_to_mean(param.C_seen, param.C_constant); break; - - case 6: shift_mesh_height(); break; - } - } - - #if ENABLED(UBL_DEVEL_DEBUGGING) - - // - // Much of the 'What?' command can be eliminated. But until we are fully debugged, it is - // good to have the extra information. Soon... we prune this to just a few items - // - if (parser.seen_test('W')) g29_what_command(); - - // - // When we are fully debugged, this may go away. But there are some valid - // use cases for the users. So we can wait and see what to do with it. - // - - if (parser.seen('K')) // Kompare Current Mesh Data to Specified Stored Mesh - g29_compare_current_mesh_to_stored_mesh(); - - #endif // UBL_DEVEL_DEBUGGING - - // - // Load a Mesh from the EEPROM - // - - if (parser.seen('L')) { // Load Current Mesh Data - param.KLS_storage_slot = parser.has_value() ? (int8_t)parser.value_int() : storage_slot; - - int16_t a = settings.calc_num_meshes(); - - if (!a) { - SERIAL_ECHOLNPGM("?EEPROM storage not available."); - return; - } - - if (!WITHIN(param.KLS_storage_slot, 0, a - 1)) { - SERIAL_ECHOLNPGM("?Invalid storage slot.\n?Use 0 to ", a - 1); - return; - } - - settings.load_mesh(param.KLS_storage_slot); - storage_slot = param.KLS_storage_slot; - - SERIAL_ECHOLNPGM(STR_DONE); - } - - // - // Store a Mesh in the EEPROM - // - - if (parser.seen('S')) { // Store (or Save) Current Mesh Data - param.KLS_storage_slot = parser.has_value() ? (int8_t)parser.value_int() : storage_slot; - - if (param.KLS_storage_slot == -1) // Special case: 'Export' the mesh to the - return report_current_mesh(); // host so it can be saved in a file. - - int16_t a = settings.calc_num_meshes(); - - if (!a) { - SERIAL_ECHOLNPGM("?EEPROM storage not available."); - goto LEAVE; - } - - if (!WITHIN(param.KLS_storage_slot, 0, a - 1)) { - SERIAL_ECHOLNPGM("?Invalid storage slot.\n?Use 0 to ", a - 1); - goto LEAVE; - } - - settings.store_mesh(param.KLS_storage_slot); - storage_slot = param.KLS_storage_slot; - - SERIAL_ECHOLNPGM(STR_DONE); - } - - if (parser.seen_test('T')) - display_map(param.T_map_type); - - LEAVE: - - #if HAS_MARLINUI_MENU - ui.reset_alert_level(); - ui.quick_feedback(); - ui.reset_status(); - ui.release(); - #endif - - #ifdef EVENT_GCODE_AFTER_G29 - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("After G29 G-code: ", EVENT_GCODE_AFTER_G29); - if (probe_deployed) { - planner.synchronize(); - gcode.process_subcommands_now(F(EVENT_GCODE_AFTER_G29)); - } - #endif - - probe.use_probing_tool(false); - return; -} - -/** - * M420 C - * G29 P5 C : Adjust Mesh To Mean (and subtract the given offset). - * Find the mean average and shift the mesh to center on that value. - */ -void unified_bed_leveling::adjust_mesh_to_mean(const bool cflag, const_float_t offset) { - float sum = 0; - uint8_t n = 0; - GRID_LOOP(x, y) - if (!isnan(z_values[x][y])) { - sum += z_values[x][y]; - n++; - } - - const float mean = sum / n; - - // - // Sum the squares of difference from mean - // - float sum_of_diff_squared = 0; - GRID_LOOP(x, y) - if (!isnan(z_values[x][y])) - sum_of_diff_squared += sq(z_values[x][y] - mean); - - SERIAL_ECHOLNPGM("# of samples: ", n); - SERIAL_ECHOLNPGM("Mean Mesh Height: ", p_float_t(mean, 6)); - - const float sigma = SQRT(sum_of_diff_squared / (n + 1)); - SERIAL_ECHOLNPGM("Standard Deviation: ", p_float_t(sigma, 6)); - - if (cflag) - GRID_LOOP(x, y) - if (!isnan(z_values[x][y])) { - z_values[x][y] -= mean + offset; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); - } -} - -/** - * G29 P6 C : Shift Mesh Height by a uniform constant. - */ -void unified_bed_leveling::shift_mesh_height() { - GRID_LOOP(x, y) - if (!isnan(z_values[x][y])) { - z_values[x][y] += param.C_constant; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); - } -} - -#if HAS_BED_PROBE - /** - * G29 P1 T V : Probe Entire Mesh - * Probe all invalidated locations of the mesh that can be reached by the probe. - * This attempts to fill in locations closest to the nozzle's start location first. - */ - void unified_bed_leveling::probe_entire_mesh(const xy_pos_t &nearby, const bool do_ubl_mesh_map, const bool stow_probe, const bool do_furthest) { - probe.deploy(); // Deploy before ui.capture() to allow for PAUSE_BEFORE_DEPLOY_STOW - - TERN_(HAS_MARLINUI_MENU, ui.capture()); - TERN_(EXTENSIBLE_UI, ExtUI::onLevelingStart()); - - save_ubl_active_state_and_disable(); // No bed level correction so only raw data is obtained - grid_count_t count = GRID_MAX_POINTS; - - mesh_index_pair best; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::G29_START)); - do { - if (do_ubl_mesh_map) display_map(param.T_map_type); - - const grid_count_t point_num = (GRID_MAX_POINTS - count) + 1; - SERIAL_ECHOLNPGM("Probing mesh point ", point_num, "/", GRID_MAX_POINTS, "."); - TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/%i"), GET_TEXT_F(MSG_PROBING_POINT), point_num, int(GRID_MAX_POINTS))); - TERN_(HAS_BACKLIGHT_TIMEOUT, ui.refresh_backlight_timeout()); - - #if HAS_MARLINUI_MENU - if (ui.button_pressed()) { - ui.quick_feedback(false); // Preserve button state for click-and-hold - SERIAL_ECHOLNPGM("\nMesh only partially populated.\n"); - ui.wait_for_release(); - ui.quick_feedback(); - ui.release(); - probe.stow(); // Release UI before stow to allow for PAUSE_BEFORE_DEPLOY_STOW - return restore_ubl_active_state(); - } - #endif - - #ifndef HUGE_VALF - #define HUGE_VALF __FLT_MAX__ - #endif - - best = do_furthest // Points with valid data or HUGE_VALF are skipped - ? find_furthest_invalid_mesh_point() - : find_closest_mesh_point_of_type(INVALID, nearby, true); - - if (best.pos.x >= 0) { // mesh point found and is reachable by probe - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::G29_POINT_START)); - #ifdef USE_PROBE_FOR_MESH_REF // adjust the mesh point value - const float measured_z = probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity) - mesh_zero_ref_offset; - #else - const float measured_z = probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity); - #endif - z_values[best.pos.x][best.pos.y] = isnan(measured_z) ? HUGE_VALF : measured_z; // Mark invalid point already probed with HUGE_VALF to omit it in the next loop - #if ENABLED(EXTENSIBLE_UI) - ExtUI::onMeshUpdate(best.pos, ExtUI::G29_POINT_FINISH); - ExtUI::onMeshUpdate(best.pos, measured_z); - #endif - } - SERIAL_FLUSH(); // Prevent host M105 buffer overrun. - - } while (best.pos.x >= 0 && --count); - - GRID_LOOP(x, y) if (z_values[x][y] == HUGE_VALF) z_values[x][y] = NAN; // Restore NAN for HUGE_VALF marks - - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::G29_FINISH)); - - // Release UI during stow to allow for PAUSE_BEFORE_DEPLOY_STOW - TERN_(HAS_MARLINUI_MENU, ui.release()); - probe.stow(); - TERN_(HAS_MARLINUI_MENU, ui.capture()); - - probe.move_z_after_probing(); - - do_blocking_move_to_xy( - constrain(nearby.x - probe.offset_xy.x, MESH_MIN_X, MESH_MAX_X), - constrain(nearby.y - probe.offset_xy.y, MESH_MIN_Y, MESH_MAX_Y) - ); - - restore_ubl_active_state(); - } - -#endif // HAS_BED_PROBE - -void set_message_with_feedback(FSTR_P const fstr) { - #if HAS_MARLINUI_MENU - ui.set_status(fstr); - ui.quick_feedback(); - #else - UNUSED(fstr); - #endif -} - -#if HAS_MARLINUI_MENU - - typedef void (*clickFunc_t)(); - - bool _click_and_hold(const clickFunc_t func=nullptr) { - if (ui.button_pressed()) { - ui.quick_feedback(false); // Preserve button state for click-and-hold - const millis_t nxt = millis() + 1500UL; - while (ui.button_pressed()) { // Loop while the encoder is pressed. Uses hardware flag! - idle(); // idle, of course - if (ELAPSED(millis(), nxt)) { // After 1.5 seconds - ui.quick_feedback(); - if (func) (*func)(); - ui.wait_for_release(); - return true; - } - } - } - serial_delay(15); - return false; - } - - void unified_bed_leveling::move_z_with_encoder(const_float_t multiplier) { - ui.wait_for_release(); - while (!ui.button_pressed()) { - idle(); - gcode.reset_stepper_timeout(); // Keep steppers powered - if (encoder_diff) { - do_blocking_move_to_z(current_position.z + float(encoder_diff) * multiplier); - encoder_diff = 0; - } - } - } - - float unified_bed_leveling::measure_point_with_encoder() { - KEEPALIVE_STATE(PAUSED_FOR_USER); - const float z_step = 0.01f; - move_z_with_encoder(z_step); - return current_position.z; - } - - static void echo_and_take_a_measurement() { SERIAL_ECHOLNPGM(" and take a measurement."); } - - float unified_bed_leveling::measure_business_card_thickness() { - ui.capture(); - save_ubl_active_state_and_disable(); // Disable bed level correction for probing - - do_blocking_move_to( - xyz_pos_t({ - 0.5f * ((MESH_MAX_X) - (MESH_MIN_X)), - 0.5f * ((MESH_MAX_Y) - (MESH_MIN_Y)), - MANUAL_PROBE_START_Z - #ifdef SAFE_BED_LEVELING_START_I - , SAFE_BED_LEVELING_START_I - #endif - #ifdef SAFE_BED_LEVELING_START_J - , SAFE_BED_LEVELING_START_J - #endif - #ifdef SAFE_BED_LEVELING_START_K - , SAFE_BED_LEVELING_START_K - #endif - #ifdef SAFE_BED_LEVELING_START_U - , SAFE_BED_LEVELING_START_U - #endif - #ifdef SAFE_BED_LEVELING_START_V - , SAFE_BED_LEVELING_START_V - #endif - #ifdef SAFE_BED_LEVELING_START_W - , SAFE_BED_LEVELING_START_W - #endif - }) - //, _MIN(planner.settings.max_feedrate_mm_s[X_AXIS], planner.settings.max_feedrate_mm_s[Y_AXIS]) * 0.5f - ); - planner.synchronize(); - - SERIAL_ECHOPGM("Place shim under nozzle"); - LCD_MESSAGE(MSG_UBL_BC_INSERT); - ui.return_to_status(); - echo_and_take_a_measurement(); - - const float z1 = measure_point_with_encoder(); - do_z_clearance_by(SIZE_OF_LITTLE_RAISE); - - SERIAL_ECHOPGM("Remove shim"); - LCD_MESSAGE(MSG_UBL_BC_REMOVE); - echo_and_take_a_measurement(); - - const float z2 = measure_point_with_encoder(); - do_z_clearance_by(Z_CLEARANCE_BETWEEN_PROBES); - - const float thickness = ABS(z1 - z2); - - if (param.V_verbosity > 1) - SERIAL_ECHOLNPGM("Business Card is ", p_float_t(thickness, 4), "mm thick."); - - restore_ubl_active_state(); - - return thickness; - } - - /** - * G29 P2 : Manually Probe Remaining Mesh Points. - * Move to INVALID points and - * NOTE: Blocks the G-code queue and captures Marlin UI during use. - */ - void unified_bed_leveling::manually_probe_remaining_mesh(const xy_pos_t &pos, const_float_t z_clearance, const_float_t thick, const bool do_ubl_mesh_map) { - ui.capture(); - TERN_(EXTENSIBLE_UI, ExtUI::onLevelingStart()); - - save_ubl_active_state_and_disable(); // No bed level correction so only raw data is obtained - do_blocking_move_to_xy_z(current_position, z_clearance); - - ui.return_to_status(); - - mesh_index_pair location; - const xy_int8_t &lpos = location.pos; - do { - location = find_closest_mesh_point_of_type(INVALID, pos); - // It doesn't matter if the probe can't reach the NAN location. This is a manual probe. - if (!location.valid()) continue; - - const xyz_pos_t ppos = { get_mesh_x(lpos.x), get_mesh_y(lpos.y), z_clearance }; - - if (!position_is_reachable(ppos)) break; // SHOULD NOT OCCUR (find_closest_mesh_point only returns reachable points) - - LCD_MESSAGE(MSG_UBL_MOVING_TO_NEXT); - - do_blocking_move_to(ppos); - do_z_clearance(z_clearance); - - KEEPALIVE_STATE(PAUSED_FOR_USER); - ui.capture(); - - if (do_ubl_mesh_map) display_map(param.T_map_type); // Show user where we're probing - - if (parser.seen_test('B')) { - SERIAL_ECHOPGM("Place Shim & Measure"); - LCD_MESSAGE(MSG_UBL_BC_INSERT); - } - else { - SERIAL_ECHOPGM("Measure"); - LCD_MESSAGE(MSG_UBL_BC_INSERT2); - } - - const float z_step = 0.01f; // 0.01mm per encoder tick, occasionally step - move_z_with_encoder(z_step); - - if (_click_and_hold([]{ - SERIAL_ECHOLNPGM("\nMesh only partially populated."); - do_z_clearance(Z_CLEARANCE_DEPLOY_PROBE); - })) return restore_ubl_active_state(); - - // Store the Z position minus the shim height - z_values[lpos.x][lpos.y] = current_position.z - thick; - - // Tell the external UI to update - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, z_values[lpos.x][lpos.y])); - - if (param.V_verbosity > 2) - SERIAL_ECHOLNPGM("Mesh Point Measured at: ", p_float_t(z_values[lpos.x][lpos.y], 6)); - SERIAL_FLUSH(); // Prevent host M105 buffer overrun. - } while (location.valid()); - - if (do_ubl_mesh_map) display_map(param.T_map_type); // show user where we're probing - - restore_ubl_active_state(); - do_blocking_move_to_xy_z(pos, Z_CLEARANCE_DEPLOY_PROBE); - } - - /** - * G29 P4 : Mesh Fine-Tuning. Go to point(s) and adjust values with the LCD. - * NOTE: Blocks the G-code queue and captures Marlin UI during use. - */ - void unified_bed_leveling::fine_tune_mesh(const xy_pos_t &pos, const bool do_ubl_mesh_map) { - if (!parser.seen_test('R')) // fine_tune_mesh() is special. If no repetition count flag is specified - param.R_repetition = 1; // do exactly one mesh location. Otherwise use what the parser decided. - - #if ENABLED(UBL_MESH_EDIT_MOVES_Z) - const float h_offset = parser.seenval('H') ? parser.value_linear_units() : MANUAL_PROBE_START_Z; - if (!WITHIN(h_offset, 0, 10)) { - SERIAL_ECHOLNPGM("Offset out of bounds. (0 to 10mm)\n"); - return; - } - #endif - - mesh_index_pair location; - - if (!position_is_reachable(pos)) { - SERIAL_ECHOLNPGM("(X,Y) outside printable radius."); - return; - } - - save_ubl_active_state_and_disable(); - - LCD_MESSAGE(MSG_UBL_FINE_TUNE_MESH); - ui.capture(); // Take over control of the LCD encoder - - do_blocking_move_to_xy_z(pos, Z_TWEEN_SAFE_CLEARANCE); // Move to the given XY with probe clearance - - MeshFlags done_flags{0}; - const xy_int8_t &lpos = location.pos; - - #if IS_TFTGLCD_PANEL - ui.ubl_mesh_edit_start(0); // Change current screen before calling ui.ubl_plot - safe_delay(50); - #endif - - do { - location = find_closest_mesh_point_of_type(SET_IN_BITMAP, pos, false, &done_flags); - - if (lpos.x < 0) break; // Stop when there are no more reachable points - - done_flags.mark(lpos); // Mark this location as 'adjusted' so a new - // location is used on the next loop - const xyz_pos_t raw = { get_mesh_x(lpos.x), get_mesh_y(lpos.y), Z_TWEEN_SAFE_CLEARANCE }; - - if (!position_is_reachable(raw)) break; // SHOULD NOT OCCUR (find_closest_mesh_point_of_type only returns reachable) - - do_blocking_move_to(raw); // Move the nozzle to the edit point with probe clearance - - TERN_(UBL_MESH_EDIT_MOVES_Z, do_blocking_move_to_z(h_offset)); // Move Z to the given 'H' offset before editing - - KEEPALIVE_STATE(PAUSED_FOR_USER); - - if (do_ubl_mesh_map) display_map(param.T_map_type); // Display the current point - - #if IS_TFTGLCD_PANEL - ui.ubl_plot(lpos.x, lpos.y); // update plot screen - #endif - - ui.refresh(); - - float new_z = z_values[lpos.x][lpos.y]; - if (isnan(new_z)) new_z = 0; // Invalid points begin at 0 - new_z = FLOOR(new_z * 1000) * 0.001f; // Chop off digits after the 1000ths place - - ui.ubl_mesh_edit_start(new_z); - - SET_SOFT_ENDSTOP_LOOSE(true); - - do { - idle_no_sleep(); - new_z = ui.ubl_mesh_value(); - TERN_(UBL_MESH_EDIT_MOVES_Z, do_blocking_move_to_z(h_offset + new_z)); // Move the nozzle as the point is edited - SERIAL_FLUSH(); // Prevent host M105 buffer overrun. - } while (!ui.button_pressed()); - - SET_SOFT_ENDSTOP_LOOSE(false); - - if (!lcd_map_control) ui.return_to_status(); // Just editing a single point? Return to status - - // Button held down? Abort editing - if (_click_and_hold([]{ - ui.return_to_status(); - do_z_clearance(Z_TWEEN_SAFE_CLEARANCE); - set_message_with_feedback(GET_TEXT_F(MSG_EDITING_STOPPED)); - })) break; - - // TODO: Disable leveling here so the Z value becomes the 'native' Z value. - - z_values[lpos.x][lpos.y] = new_z; // Save the updated Z value - - // TODO: Re-enable leveling here so Z is correctly based on the updated mesh. - - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(location, new_z)); - - serial_delay(20); // No switch noise - ui.refresh(); - - } while (lpos.x >= 0 && --param.R_repetition > 0); - - if (do_ubl_mesh_map) display_map(param.T_map_type); - restore_ubl_active_state(); - - do_blocking_move_to_xy_z(pos, Z_TWEEN_SAFE_CLEARANCE); - - LCD_MESSAGE(MSG_UBL_DONE_EDITING_MESH); - SERIAL_ECHOLNPGM("Done Editing Mesh"); - - if (lcd_map_control) - ui.goto_screen(ubl_map_screen); - else - ui.return_to_status(); - } - -#endif // HAS_MARLINUI_MENU - -/** - * Parse and validate most G29 parameters, store for use by G29 functions. - */ -bool unified_bed_leveling::G29_parse_parameters() { - bool err_flag = false; - - set_message_with_feedback(GET_TEXT_F(MSG_UBL_DOING_G29)); - - param.C_constant = 0; - param.R_repetition = 0; - - if (parser.seen('R')) { - param.R_repetition = parser.has_value() ? parser.value_ushort() : GRID_MAX_POINTS; - NOMORE(param.R_repetition, GRID_MAX_POINTS); - if (param.R_repetition < 1) { - SERIAL_ECHOLNPGM("?(R)epetition count invalid (1+).\n"); - return UBL_ERR; - } - } - - param.V_verbosity = parser.byteval('V'); - if (!WITHIN(param.V_verbosity, 0, 4)) { - SERIAL_ECHOLNPGM("?(V)erbose level implausible (0-4).\n"); - err_flag = true; - } - - if (parser.seen('P')) { - const uint8_t pv = parser.value_byte(); - #if !HAS_BED_PROBE - if (pv == 1) { - SERIAL_ECHOLNPGM("G29 P1 requires a probe.\n"); - err_flag = true; - } - else - #endif - { - param.P_phase = pv; - if (!WITHIN(param.P_phase, 0, 6)) { - SERIAL_ECHOLNPGM("?(P)hase value invalid (0-6).\n"); - err_flag = true; - } - } - } - - if (parser.seen('J')) { - #if HAS_BED_PROBE - param.J_grid_size = parser.value_byte(); - if (param.J_grid_size && !WITHIN(param.J_grid_size, 2, 9)) { - SERIAL_ECHOLNPGM("?Invalid grid size (J) specified (2-9).\n"); - err_flag = true; - } - #else - SERIAL_ECHOLNPGM("G29 J action requires a probe.\n"); - err_flag = true; - #endif - } - - param.XY_seen.x = parser.seenval('X'); - float sx = param.XY_seen.x ? parser.value_float() : current_position.x; - param.XY_seen.y = parser.seenval('Y'); - float sy = param.XY_seen.y ? parser.value_float() : current_position.y; - - if (param.XY_seen.x != param.XY_seen.y) { - SERIAL_ECHOLNPGM("Both X & Y locations must be specified.\n"); - err_flag = true; - } - - // If X or Y are not valid, use center of the bed values - // (for UBL_HILBERT_CURVE default to lower-left corner instead) - if (!COORDINATE_OKAY(sx, X_MIN_BED, X_MAX_BED)) sx = TERN(UBL_HILBERT_CURVE, 0, X_CENTER); - if (!COORDINATE_OKAY(sy, Y_MIN_BED, Y_MAX_BED)) sy = TERN(UBL_HILBERT_CURVE, 0, Y_CENTER); - - if (err_flag) return UBL_ERR; - - param.XY_pos.set(sx, sy); - - /** - * Activate or deactivate UBL - * Note: UBL's G29 restores the state set here when done. - * Leveling is being enabled here with old data, possibly - * none. Error handling should disable for safety... - */ - if (parser.seen_test('A')) { - if (parser.seen_test('D')) { - SERIAL_ECHOLNPGM("?Can't activate and deactivate at the same time.\n"); - return UBL_ERR; - } - set_bed_leveling_enabled(true); - report_state(); - } - else if (parser.seen_test('D')) { - set_bed_leveling_enabled(false); - report_state(); - } - - // Set global 'C' flag and its value - if ((param.C_seen = parser.seen('C'))) - param.C_constant = parser.value_float(); - - #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) - if (parser.seenval('F')) { - const float fh = parser.value_float(); - if (!WITHIN(fh, 0, 100)) { - SERIAL_ECHOLNPGM("?(F)ade height for Bed Level Correction not plausible.\n"); - return UBL_ERR; - } - set_z_fade_height(fh); - } - #endif - - param.T_map_type = parser.byteval('T'); - if (!WITHIN(param.T_map_type, 0, 2)) { - SERIAL_ECHOLNPGM("Invalid map type.\n"); - return UBL_ERR; - } - return UBL_OK; -} - -static uint8_t ubl_state_at_invocation = 0; - -#if ENABLED(UBL_DEVEL_DEBUGGING) - static uint8_t ubl_state_recursion_chk = 0; -#endif - -void unified_bed_leveling::save_ubl_active_state_and_disable() { - #if ENABLED(UBL_DEVEL_DEBUGGING) - ubl_state_recursion_chk++; - if (ubl_state_recursion_chk != 1) { - SERIAL_ECHOLNPGM("save_ubl_active_state_and_disabled() called multiple times in a row."); - set_message_with_feedback(GET_TEXT_F(MSG_UBL_SAVE_ERROR)); - return; - } - #endif - ubl_state_at_invocation = planner.leveling_active; - set_bed_leveling_enabled(false); -} - -void unified_bed_leveling::restore_ubl_active_state(const bool is_done/*=true*/) { - TERN_(HAS_MARLINUI_MENU, ui.release()); - #if ENABLED(UBL_DEVEL_DEBUGGING) - if (--ubl_state_recursion_chk) { - SERIAL_ECHOLNPGM("restore_ubl_active_state() called too many times."); - set_message_with_feedback(GET_TEXT_F(MSG_UBL_RESTORE_ERROR)); - return; - } - #endif - set_bed_leveling_enabled(ubl_state_at_invocation); - - if (is_done) { - TERN_(EXTENSIBLE_UI, ExtUI::onLevelingDone()); - } -} - -mesh_index_pair unified_bed_leveling::find_furthest_invalid_mesh_point() { - - bool found_a_NAN = false, found_a_real = false; - - mesh_index_pair farthest { -1, -1, -99999.99 }; - - GRID_LOOP(i, j) { - if (!isnan(z_values[i][j])) continue; // Skip valid mesh points - - // Skip unreachable points - if (!probe.can_reach(get_mesh_x(i), get_mesh_y(j))) - continue; - - found_a_NAN = true; - - xy_int8_t nearby { -1, -1 }; - float d1, d2 = 99999.9f; - GRID_LOOP(k, l) { - if (isnan(z_values[k][l])) continue; - - found_a_real = true; - - // Add in a random weighting factor that scrambles the probing of the - // last half of the mesh (when every unprobed mesh point is one index - // from a probed location). - - d1 = HYPOT(i - k, j - l) + (1.0f / ((millis() % 47) + 13)); - - if (d1 < d2) { // Invalid mesh point (i,j) is closer to the defined point (k,l) - d2 = d1; - nearby.set(i, j); - } - } - - // - // At this point d2 should have the near defined mesh point to invalid mesh point (i,j) - // - - if (found_a_real && nearby.x >= 0 && d2 > farthest.distance) { - farthest.pos = nearby; // Found an invalid location farther from the defined mesh point - farthest.distance = d2; - } - } // GRID_LOOP - - if (!found_a_real && found_a_NAN) { // if the mesh is totally unpopulated, start the probing - farthest.pos.set((GRID_MAX_POINTS_X) / 2, (GRID_MAX_POINTS_Y) / 2); - farthest.distance = 1; - } - return farthest; -} - -#if ENABLED(UBL_HILBERT_CURVE) - - typedef struct { - MeshPointType type; - MeshFlags *done_flags; - bool probe_relative; - mesh_index_pair closest; - } find_closest_t; - - static bool test_func(uint8_t i, uint8_t j, void *data) { - find_closest_t *d = (find_closest_t*)data; - if ( d->type == CLOSEST || d->type == (isnan(bedlevel.z_values[i][j]) ? INVALID : REAL) - || (d->type == SET_IN_BITMAP && !d->done_flags->marked(i, j)) - ) { - // Found a Mesh Point of the specified type! - const xy_pos_t mpos = { bedlevel.get_mesh_x(i), bedlevel.get_mesh_y(j) }; - - // If using the probe as the reference there are some unreachable locations. - // Also for round beds, there are grid points outside the bed the nozzle can't reach. - // Prune them from the list and ignore them till the next Phase (manual nozzle probing). - - if (!(d->probe_relative ? probe.can_reach(mpos) : position_is_reachable(mpos))) - return false; - d->closest.pos.set(i, j); - return true; - } - return false; - } - -#endif - -mesh_index_pair unified_bed_leveling::find_closest_mesh_point_of_type(const MeshPointType type, const xy_pos_t &pos, const bool probe_relative/*=false*/, MeshFlags *done_flags/*=nullptr*/) { - - #if ENABLED(UBL_HILBERT_CURVE) - - find_closest_t d; - d.type = type; - d.done_flags = done_flags; - d.probe_relative = probe_relative; - d.closest.invalidate(); - hilbert_curve::search_from_closest(pos, test_func, &d); - return d.closest; - - #else - - mesh_index_pair closest; - closest.invalidate(); - closest.distance = -99999.9f; - - // Get the reference position, either nozzle or probe - const xy_pos_t ref = probe_relative ? pos + probe.offset_xy : pos; - - float best_so_far = 99999.99f; - - GRID_LOOP(i, j) { - if ( type == CLOSEST || type == (isnan(z_values[i][j]) ? INVALID : REAL) - || (type == SET_IN_BITMAP && !done_flags->marked(i, j)) - ) { - // Found a Mesh Point of the specified type! - const xy_pos_t mpos = { get_mesh_x(i), get_mesh_y(j) }; - - // If using the probe as the reference there are some unreachable locations. - // Also for round beds, there are grid points outside the bed the nozzle can't reach. - // Prune them from the list and ignore them till the next Phase (manual nozzle probing). - - if (!(probe_relative ? probe.can_reach(mpos) : position_is_reachable(mpos))) - continue; - - // Reachable. Check if it's the best_so_far location to the nozzle. - - const xy_pos_t diff = current_position - mpos; - const float distance = (ref - mpos).magnitude() + diff.magnitude() * 0.1f; - - // factor in the distance from the current location for the normal case - // so the nozzle isn't running all over the bed. - if (distance < best_so_far) { - best_so_far = distance; // Found a closer location with the desired value type. - closest.pos.set(i, j); - closest.distance = best_so_far; - } - } - } // GRID_LOOP - - return closest; - - #endif -} - -/** - * 'Smart Fill': Scan from the outward edges of the mesh towards the center. - * If an invalid location is found, use the next two points (if valid) to - * calculate a 'reasonable' value for the unprobed mesh point. - */ - -bool unified_bed_leveling::smart_fill_one(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir) { - const float v = z_values[x][y]; - if (isnan(v)) { // A NAN... - const int8_t dx = x + xdir, dy = y + ydir; - const float v1 = z_values[dx][dy]; - if (!isnan(v1)) { // ...next to a pair of real values? - const float v2 = z_values[dx + xdir][dy + ydir]; - if (!isnan(v2)) { - z_values[x][y] = v1 < v2 ? v1 : v1 + v1 - v2; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); - return true; - } - } - } - return false; -} - -typedef struct { uint8_t sx, ex, sy, ey; bool yfirst; } smart_fill_info; - -void unified_bed_leveling::smart_fill_mesh() { - static const smart_fill_info - info0 PROGMEM = { 0, GRID_MAX_POINTS_X, 0, (GRID_MAX_POINTS_Y) - 2, false }, // Bottom of the mesh looking up - info1 PROGMEM = { 0, GRID_MAX_POINTS_X, (GRID_MAX_POINTS_Y) - 1, 0, false }, // Top of the mesh looking down - info2 PROGMEM = { 0, (GRID_MAX_POINTS_X) - 2, 0, GRID_MAX_POINTS_Y, true }, // Left side of the mesh looking right - info3 PROGMEM = { (GRID_MAX_POINTS_X) - 1, 0, 0, GRID_MAX_POINTS_Y, true }; // Right side of the mesh looking left - static const smart_fill_info * const info[] PROGMEM = { &info0, &info1, &info2, &info3 }; - - for (uint8_t i = 0; i < COUNT(info); ++i) { - const smart_fill_info *f = (smart_fill_info*)pgm_read_ptr(&info[i]); - const int8_t sx = pgm_read_byte(&f->sx), sy = pgm_read_byte(&f->sy), - ex = pgm_read_byte(&f->ex), ey = pgm_read_byte(&f->ey); - if (pgm_read_byte(&f->yfirst)) { - const int8_t dir = ex > sx ? 1 : -1; - for (uint8_t y = sy; y != ey; ++y) - for (uint8_t x = sx; x != ex; x += dir) - if (smart_fill_one(x, y, dir, 0)) break; - } - else { - const int8_t dir = ey > sy ? 1 : -1; - for (uint8_t x = sx; x != ex; ++x) - for (uint8_t y = sy; y != ey; y += dir) - if (smart_fill_one(x, y, 0, dir)) break; - } - } -} - -#if HAS_BED_PROBE - - //#define VALIDATE_MESH_TILT - - #include "../../../libs/vector_3.h" - - void unified_bed_leveling::tilt_mesh_based_on_probed_grid(const bool do_3_pt_leveling) { - - float measured_z; - bool abort_flag = false; - - struct linear_fit_data lsf_results; - incremental_LSF_reset(&lsf_results); - - if (do_3_pt_leveling) { - xy_float_t points[3]; - probe.get_three_points(points); - - #if ENABLED(UBL_TILT_ON_MESH_POINTS_3POINT) - mesh_index_pair cpos[3]; - for (uint8_t ix = 0; ix < 3; ++ix) { // Convert points to coordinates of mesh points - cpos[ix] = find_closest_mesh_point_of_type(REAL, points[ix], true); - points[ix] = cpos[ix].meshpos(); - } - #endif - - #if ENABLED(VALIDATE_MESH_TILT) - float gotz[3]; // Used for algorithm validation below - #endif - - for (uint8_t i = 0; i < 3; ++i) { - SERIAL_ECHOLNPGM("Tilting mesh (", i + 1, "/3)"); - TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/3"), GET_TEXT_F(MSG_LCD_TILTING_MESH), i + 1)); - - measured_z = probe.probe_at_point(points[i], i < 2 ? PROBE_PT_RAISE : PROBE_PT_LAST_STOW, param.V_verbosity); - if ((abort_flag = isnan(measured_z))) break; - - measured_z -= TERN(UBL_TILT_ON_MESH_POINTS_3POINT, z_values[cpos[i].pos.x][cpos[i].pos.y], get_z_correction(points[i])); - TERN_(VALIDATE_MESH_TILT, gotz[i] = measured_z); - - if (param.V_verbosity > 3) { SERIAL_ECHO_SP(16); SERIAL_ECHOLNPGM("Corrected_Z=", measured_z); } - - incremental_LSF(&lsf_results, points[i], measured_z); - } - - probe.stow(); - probe.move_z_after_probing(); - - if (abort_flag) { - SERIAL_ECHOLNPGM("?Error probing point. Aborting operation."); - return; - } - } - else { // !do_3_pt_leveling - - #ifndef G29J_MESH_TILT_MARGIN - #define G29J_MESH_TILT_MARGIN 0 - #endif - const float x_min = _MAX((X_MIN_POS) + (G29J_MESH_TILT_MARGIN), MESH_MIN_X, probe.min_x()), - x_max = _MIN((X_MAX_POS) - (G29J_MESH_TILT_MARGIN), MESH_MAX_X, probe.max_x()), - y_min = _MAX((Y_MIN_POS) + (G29J_MESH_TILT_MARGIN), MESH_MIN_Y, probe.min_y()), - y_max = _MIN((Y_MAX_POS) - (G29J_MESH_TILT_MARGIN), MESH_MAX_Y, probe.max_y()), - dx = (x_max - x_min) / (param.J_grid_size - 1), - dy = (y_max - y_min) / (param.J_grid_size - 1); - - bool zig_zag = false; - - const uint16_t total_points = sq(param.J_grid_size); - uint16_t point_num = 1; - - for (uint8_t ix = 0; ix < param.J_grid_size; ++ix) { - xy_pos_t rpos; - rpos.x = x_min + ix * dx; - for (uint8_t iy = 0; iy < param.J_grid_size; ++iy) { - rpos.y = y_min + dy * (zig_zag ? param.J_grid_size - 1 - iy : iy); - - #if ENABLED(UBL_TILT_ON_MESH_POINTS) - #if ENABLED(DEBUG_LEVELING_FEATURE) - xy_pos_t oldRpos; - if (DEBUGGING(LEVELING)) oldRpos = rpos; - #endif - mesh_index_pair cpos; - rpos -= probe.offset; - cpos = find_closest_mesh_point_of_type(REAL, rpos, true); - rpos = cpos.meshpos(); - #endif - - SERIAL_ECHOLNPGM("Tilting mesh point ", point_num, "/", total_points, "\n"); - TERN_(HAS_STATUS_MESSAGE, ui.status_printf(0, F(S_FMT " %i/%i"), GET_TEXT_F(MSG_LCD_TILTING_MESH), point_num, total_points)); - - measured_z = probe.probe_at_point(rpos, parser.seen_test('E') ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity); // TODO: Needs error handling - - if ((abort_flag = isnan(measured_z))) break; - - const float zcorr = TERN(UBL_TILT_ON_MESH_POINTS, z_values[cpos.pos.x][cpos.pos.y], get_z_correction(rpos)); - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - #if ENABLED(UBL_TILT_ON_MESH_POINTS) - const xy_pos_t oldLpos = oldRpos.asLogical(); - DEBUG_ECHO(F("Calculated point: ("), p_float_t(oldRpos.x, 7), C(','), p_float_t(oldRpos.y, 7), - F(") logical: ("), p_float_t(oldLpos.x, 7), C(','), p_float_t(oldLpos.y, 7), - F(")\nSelected mesh point: ") - ); - #endif - const xy_pos_t lpos = rpos.asLogical(); - DEBUG_ECHO( C('('), p_float_t(rpos.x, 7), C(','), p_float_t(rpos.y, 7), - F(") logical: ("), p_float_t(lpos.x, 7), C(','), p_float_t(lpos.y, 7), - F(") measured: "), p_float_t(measured_z, 7), - F(" correction: "), p_float_t(zcorr, 7) - ); - } - #endif - - measured_z -= zcorr; - - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM(" final >>>---> ", p_float_t(measured_z, 7)); - - if (param.V_verbosity > 3) { - SERIAL_ECHO_SP(16); - SERIAL_ECHOLNPGM("Corrected_Z=", measured_z); - } - incremental_LSF(&lsf_results, rpos, measured_z); - - point_num++; - } - - if (abort_flag) break; - FLIP(zig_zag); - } - } - probe.stow(); - probe.move_z_after_probing(); - - if (abort_flag || finish_incremental_LSF(&lsf_results)) { - SERIAL_ECHOLNPGM("Could not complete LSF!"); - return; - } - - vector_3 normal = vector_3(lsf_results.A, lsf_results.B, 1).get_normal(); - - if (param.V_verbosity > 2) - SERIAL_ECHOLN(F("bed plane normal = ["), p_float_t(normal.x, 7), C(','), p_float_t(normal.y, 7), C(','), p_float_t(normal.z, 7), C(']')); - - matrix_3x3 rotation = matrix_3x3::create_look_at(vector_3(lsf_results.A, lsf_results.B, 1)); - - GRID_LOOP(i, j) { - float mx = get_mesh_x(i), my = get_mesh_y(j), mz = z_values[i][j]; - - if (DEBUGGING(LEVELING)) { - DEBUG_ECHOLN(F("before rotation = ["), p_float_t(mx, 7), C(','), p_float_t(my, 7), C(','), p_float_t(mz, 7), F("] ---> ")); - DEBUG_DELAY(20); - } - - rotation.apply_rotation_xyz(mx, my, mz); - - if (DEBUGGING(LEVELING)) { - DEBUG_ECHOLN(F("after rotation = ["), p_float_t(mx, 7), C(','), p_float_t(my, 7), C(','), p_float_t(mz, 7), F("] ---> ")); - DEBUG_DELAY(20); - } - - z_values[i][j] = mz - lsf_results.D; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(i, j, z_values[i][j])); - } - - if (DEBUGGING(LEVELING)) { - rotation.debug(F("rotation matrix:\n")); - DEBUG_ECHOLN(F("LSF Results A="), p_float_t(lsf_results.A, 7), F(" B="), p_float_t(lsf_results.B, 7), F(" D="), p_float_t(lsf_results.D, 7)); - DEBUG_DELAY(55); - DEBUG_ECHOLN(F("bed plane normal = ["), p_float_t(normal.x, 7), C(','), p_float_t(normal.y, 7), C(','), p_float_t(normal.z, 7), C(']')); - DEBUG_EOL(); - - /** - * Use the code below to check the validity of the mesh tilting algorithm. - * 3-Point Mesh Tilt uses the same algorithm as grid-based tilting, but only - * three points are used in the calculation. This guarantees that each probed point - * has an exact match when get_z_correction() for that location is calculated. - * The Z error between the probed point locations and the get_z_correction() - * numbers for those locations should be 0. - */ - #if ENABLED(VALIDATE_MESH_TILT) - auto d_from = []{ DEBUG_ECHOPGM("D from "); }; - auto normed = [&](const xy_pos_t &pos, const_float_t zadd) { - return normal.x * pos.x + normal.y * pos.y + zadd; - }; - auto debug_pt = [](const int num, const xy_pos_t &pos, const_float_t zadd) { - d_from(); - DEBUG_ECHOLN(F("Point "), num, C(':'), p_float_t(normed(pos, zadd), 6), F(" Z error = "), p_float_t(zadd - get_z_correction(pos), 6)); - }; - debug_pt(1, probe_pt[0], normal.z * gotz[0]); - debug_pt(2, probe_pt[1], normal.z * gotz[1]); - debug_pt(3, probe_pt[2], normal.z * gotz[2]); - #if ENABLED(Z_SAFE_HOMING) - constexpr xy_float_t safe_xy = { Z_SAFE_HOMING_X_POINT, Z_SAFE_HOMING_Y_POINT }; - d_from(); DEBUG_ECHOLN(F("safe home with Z="), F("0 : "), p_float_t(normed(safe_xy, 0), 6)); - d_from(); DEBUG_ECHOLN(F("safe home with Z="), F("mesh value "), p_float_t(normed(safe_xy, get_z_correction(safe_xy)), 6)); - DEBUG_ECHO(F(" Z error = ("), Z_SAFE_HOMING_X_POINT, C(','), Z_SAFE_HOMING_Y_POINT, F(") = "), p_float_t(get_z_correction(safe_xy), 6)); - #endif - #endif - } // DEBUGGING(LEVELING) - - } - -#endif // HAS_BED_PROBE - -#if ENABLED(UBL_G29_P31) - void unified_bed_leveling::smart_fill_wlsf(const_float_t weight_factor) { - - // For each undefined mesh point, compute a distance-weighted least squares fit - // from all the originally populated mesh points, weighted toward the point - // being extrapolated so that nearby points will have greater influence on - // the point being extrapolated. Then extrapolate the mesh point from WLSF. - - static_assert((GRID_MAX_POINTS_Y) <= 16, "GRID_MAX_POINTS_Y too big"); - uint16_t bitmap[GRID_MAX_POINTS_X] = { 0 }; - struct linear_fit_data lsf_results; - - SERIAL_ECHOPGM("Extrapolating mesh..."); - - const float weight_scaled = weight_factor * _MAX(MESH_X_DIST, MESH_Y_DIST); - - GRID_LOOP(jx, jy) if (!isnan(z_values[jx][jy])) SBI(bitmap[jx], jy); - - xy_pos_t ppos; - for (uint8_t ix = 0; ix < GRID_MAX_POINTS_X; ++ix) { - ppos.x = get_mesh_x(ix); - for (uint8_t iy = 0; iy < GRID_MAX_POINTS_Y; ++iy) { - ppos.y = get_mesh_y(iy); - if (isnan(z_values[ix][iy])) { - // undefined mesh point at (ppos.x,ppos.y), compute weighted LSF from original valid mesh points. - incremental_LSF_reset(&lsf_results); - xy_pos_t rpos; - for (uint8_t jx = 0; jx < GRID_MAX_POINTS_X; ++jx) { - rpos.x = get_mesh_x(jx); - for (uint8_t jy = 0; jy < GRID_MAX_POINTS_Y; ++jy) { - if (TEST(bitmap[jx], jy)) { - rpos.y = get_mesh_y(jy); - const float rz = z_values[jx][jy], - w = 1.0f + weight_scaled / (rpos - ppos).magnitude(); - incremental_WLSF(&lsf_results, rpos, rz, w); - } - } - } - if (finish_incremental_LSF(&lsf_results)) { - SERIAL_ECHOLNPGM(" Insufficient data"); - return; - } - const float ez = -lsf_results.D - lsf_results.A * ppos.x - lsf_results.B * ppos.y; - z_values[ix][iy] = ez; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(ix, iy, z_values[ix][iy])); - idle(); // housekeeping - } - } - } - - SERIAL_ECHOLNPGM(" done."); - } -#endif // UBL_G29_P31 - -#if ENABLED(UBL_DEVEL_DEBUGGING) - /** - * Much of the 'What?' command can be eliminated. But until we are fully debugged, it is - * good to have the extra information. Soon... we prune this to just a few items - */ - void unified_bed_leveling::g29_what_command() { - report_state(); - - if (storage_slot == -1) - SERIAL_ECHOLNPGM("No Mesh Loaded."); - else - SERIAL_ECHOLNPGM("Mesh ", storage_slot, " Loaded."); - serial_delay(50); - - #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) - SERIAL_ECHOLN(F("Fade Height M420 Z"), p_float_t(planner.z_fade_height, 4)); - #endif - - adjust_mesh_to_mean(param.C_seen, param.C_constant); - - #if HAS_BED_PROBE - SERIAL_ECHOLNPGM("Probe Offset M851 Z", p_float_t(probe.offset.z, 7)); - #endif - - SERIAL_ECHOLNPGM("MESH_MIN_X " STRINGIFY(MESH_MIN_X) "=", MESH_MIN_X); serial_delay(50); - SERIAL_ECHOLNPGM("MESH_MIN_Y " STRINGIFY(MESH_MIN_Y) "=", MESH_MIN_Y); serial_delay(50); - SERIAL_ECHOLNPGM("MESH_MAX_X " STRINGIFY(MESH_MAX_X) "=", MESH_MAX_X); serial_delay(50); - SERIAL_ECHOLNPGM("MESH_MAX_Y " STRINGIFY(MESH_MAX_Y) "=", MESH_MAX_Y); serial_delay(50); - SERIAL_ECHOLNPGM("GRID_MAX_POINTS_X ", GRID_MAX_POINTS_X); serial_delay(50); - SERIAL_ECHOLNPGM("GRID_MAX_POINTS_Y ", GRID_MAX_POINTS_Y); serial_delay(50); - SERIAL_ECHOLNPGM("MESH_X_DIST ", MESH_X_DIST); - SERIAL_ECHOLNPGM("MESH_Y_DIST ", MESH_Y_DIST); serial_delay(50); - - SERIAL_ECHOPGM("X-Axis Mesh Points at: "); - for (uint8_t i = 0; i < GRID_MAX_POINTS_X; ++i) { - SERIAL_ECHO(p_float_t(LOGICAL_X_POSITION(get_mesh_x(i)), 3), F(" ")); - serial_delay(25); - } - SERIAL_EOL(); - - SERIAL_ECHOPGM("Y-Axis Mesh Points at: "); - for (uint8_t i = 0; i < GRID_MAX_POINTS_Y; ++i) { - SERIAL_ECHO(p_float_t(LOGICAL_Y_POSITION(get_mesh_y(i)), 3), F(" ")); - serial_delay(25); - } - SERIAL_EOL(); - - #if HAS_KILL - SERIAL_ECHOLNPGM("Kill pin on :", KILL_PIN, " state:", kill_state()); - #endif - - SERIAL_EOL(); - serial_delay(50); - - SERIAL_ECHOLNPGM("ubl_state_at_invocation :", ubl_state_at_invocation, "\nubl_state_recursion_chk :", ubl_state_recursion_chk); - serial_delay(50); - - SERIAL_ECHOLNPGM("Meshes go from ", _hex_word(settings.meshes_start_index()), " to ", _hex_word(settings.meshes_end_index())); - serial_delay(50); - - SERIAL_ECHOLNPGM("sizeof(unified_bed_leveling) : ", sizeof(unified_bed_leveling)); - SERIAL_ECHOLNPGM("z_value[][] size: ", sizeof(z_values)); - serial_delay(25); - - SERIAL_ECHOLNPGM("EEPROM free for UBL: ", _hex_word(settings.meshes_end_index() - settings.meshes_start_index())); - serial_delay(50); - - SERIAL_ECHOLNPGM("EEPROM can hold ", settings.calc_num_meshes(), " meshes.\n"); - serial_delay(25); - - if (!sanity_check()) { - echo_name(); - SERIAL_ECHOLNPGM(" sanity checks passed."); - } - } - - /** - * When we are fully debugged, the EEPROM dump command will get deleted also. But - * right now, it is good to have the extra information. Soon... we prune this. - */ - void unified_bed_leveling::g29_eeprom_dump() { - uint8_t cccc; - - SERIAL_ECHO_MSG("EEPROM Dump:"); - persistentStore.access_start(); - for (uint16_t i = 0; i < persistentStore.capacity(); i += 16) { - if (!(i & 0x3)) idle(); - print_hex_word(i); - SERIAL_ECHOPGM(": "); - for (uint16_t j = 0; j < 16; j++) { - int pos = i + j; - persistentStore.read_data(pos, &cccc, sizeof(uint8_t)); - print_hex_byte(cccc); - SERIAL_CHAR(' '); - } - SERIAL_EOL(); - } - SERIAL_EOL(); - persistentStore.access_finish(); - } - - /** - * When we are fully debugged, this may go away. But there are some valid - * use cases for the users. So we can wait and see what to do with it. - */ - void unified_bed_leveling::g29_compare_current_mesh_to_stored_mesh() { - const int16_t a = settings.calc_num_meshes(); - - if (!a) { - SERIAL_ECHOLNPGM("?EEPROM storage not available."); - return; - } - - if (!parser.has_value() || !WITHIN(parser.value_int(), 0, a - 1)) { - SERIAL_ECHOLNPGM("?Invalid storage slot.\n?Use 0 to ", a - 1); - return; - } - - param.KLS_storage_slot = (int8_t)parser.value_int(); - - float tmp_z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; - settings.load_mesh(param.KLS_storage_slot, &tmp_z_values); - - SERIAL_ECHOLNPGM("Subtracting mesh in slot ", param.KLS_storage_slot, " from current mesh."); - - GRID_LOOP(x, y) { - z_values[x][y] -= tmp_z_values[x][y]; - TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(x, y, z_values[x][y])); - } - } - -#endif // UBL_DEVEL_DEBUGGING - -#endif // AUTO_BED_LEVELING_UBL From 682e8a96c1dac3f214a0d261e37244e0cdfa6163 Mon Sep 17 00:00:00 2001 From: Scott Lahteine Date: Fri, 28 Feb 2025 21:17:09 -0600 Subject: [PATCH 7/8] format, clean up --- Marlin/Configuration.h | 39 +++++++++------- Marlin/src/feature/bedlevel/bedlevel.cpp | 4 ++ Marlin/src/feature/bedlevel/bedlevel.h | 4 ++ Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp | 52 +++++++++------------ Marlin/src/gcode/bedlevel/abl/G29.cpp | 34 ++++---------- Marlin/src/gcode/gcode.h | 4 -- Marlin/src/lcd/menu/menu_probe_level.cpp | 36 +++++++------- 7 files changed, 77 insertions(+), 96 deletions(-) diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index 27ff2a8889..5c40a73e61 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -2107,32 +2107,35 @@ //#define AUTO_BED_LEVELING_UBL //#define MESH_BED_LEVELING -/* When a level probe is fitted but not used for homing, enabling USE_PROBE_FOR_MESH_REF will avoid a level mesh - * being created with an unwanted offset between the fixed Z endstop/Homing Switch and probe trigger height. The +/** + * When a level probe is fitted but not used for homing, enabling USE_PROBE_FOR_MESH_REF will avoid a level mesh + * being created with an unwanted offset between the fixed Z endstop/Homing Switch and probe trigger height. The * probe Z offset menu option is removed from the level sub menu, as entering a Z offset there would shift the mesh * and the Homeing position giving confusing status values for Z. It is recomended not to enter a Z offset value in * the probe offset section of config.h for the same reason. - * - * If no Z offsets are entered elsewhere the status screen will always show the actual Z possition. e.g when homing - * the Z status will show 0 at the Home position, unless the height of the fixed Z stop/Homeing switch above the bed - * has been set by the HOME_SWITCH_TO_BED_OFFSET_MENU option while creating a level mesh. In which case the Home - * position will show as that height. The printer will move down to the print height when printing starts and Z status + * + * If no Z offsets are entered elsewhere the status screen will always show the actual Z possition. e.g when homing + * the Z status will show 0 at the Home position, unless the height of the fixed Z stop/Homeing switch above the bed + * has been set by the HOME_SWITCH_TO_BED_OFFSET_MENU option while creating a level mesh. In which case the Home + * position will show as that height. The printer will move down to the print height when printing starts and Z status * will show the first layer height. The Nozzle will follow the mesh and baby stepping can be used to temporarily lower * or raise the nozzle to adjust first layer squish. - * + * * If your Z stop/Homeing switch has a fine adjustment capability, the HOME_SWITCH_TO_BED_OFFSET_MENU menu option can be * left at zero or disabled. Using the paper pinch test and setting the Z stop/Homeing switch position works just as well. */ -#ifndef USE_PROBE_FOR_Z_HOMING - #if defined AUTO_BED_LEVELING_BILINEAR || defined AUTO_BED_LEVELING_UBL || defined AUTO_BED_LEVELING_LINEAR || defined AUTO_BED_LEVELING_3POINT - //#define USE_PROBE_FOR_MESH_REF - #ifdef USE_PROBE_FOR_MESH_REF - /*optional HOME_SWITCH_TO_BED_OFFSET_MENU enables a menu option called "Home Offset Z" to adjust the Z offset between - * the Fixed Z endstop/Homing switch and the bed. This offset is only used to adjust the level mesh down - * to the Zero plane (Bed) as the mesh is created, so a new level mesh must be created when if it is changed to apply it.*/ - #define HOME_SWITCH_TO_BED_OFFSET_MENU - #endif - #endif +#if DISABLED(USE_PROBE_FOR_Z_HOMING) + #if ANY(AUTO_BED_LEVELING_BILINEAR, AUTO_BED_LEVELING_UBL, AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_3POINT) + //#define USE_PROBE_FOR_MESH_REF + #if ENABLED(USE_PROBE_FOR_MESH_REF) + /** + * HOME_SWITCH_TO_BED_OFFSET_MENU enables a menu option called "Home Offset Z" to adjust the Z offset between + * the Fixed Z endstop/Homing switch and the bed. This offset is only used to adjust the level mesh down + * to the Zero plane (Bed) as the mesh is created, so a new level mesh must be created when if it is changed to apply it. + */ + //#define HOME_SWITCH_TO_BED_OFFSET_MENU + #endif + #endif #endif /** diff --git a/Marlin/src/feature/bedlevel/bedlevel.cpp b/Marlin/src/feature/bedlevel/bedlevel.cpp index 12d620e5af..c0ed2a4cad 100644 --- a/Marlin/src/feature/bedlevel/bedlevel.cpp +++ b/Marlin/src/feature/bedlevel/bedlevel.cpp @@ -35,6 +35,10 @@ bool g29_in_progress = false; #endif +#if ENABLED(USE_PROBE_FOR_MESH_REF) + float mesh_zero_ref_offset = 0; // declared in gcode.h as external so it can be set in menu_probe_level.cpp +#endif + #if ENABLED(LCD_BED_LEVELING) #include "../../lcd/marlinui.h" #endif diff --git a/Marlin/src/feature/bedlevel/bedlevel.h b/Marlin/src/feature/bedlevel/bedlevel.h index ccb9543e72..8121725774 100644 --- a/Marlin/src/feature/bedlevel/bedlevel.h +++ b/Marlin/src/feature/bedlevel/bedlevel.h @@ -33,6 +33,10 @@ constexpr bool g29_in_progress = false; #endif +#if ENABLED(USE_PROBE_FOR_MESH_REF) + extern float mesh_zero_ref_offset; +#endif + bool leveling_is_valid(); void set_bed_leveling_enabled(const bool enable=true); void reset_bed_level(); diff --git a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp index 733aaa8abe..ea029a7f04 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp +++ b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp @@ -74,10 +74,6 @@ #define SIZE_OF_LITTLE_RAISE 1 #define BIG_RAISE_NOT_NEEDED 0 -#ifdef USE_PROBE_FOR_MESH_REF - float mesh_zero_ref_offset = 0; // declared in gcode.h as external so it can be set in menu_probe_level.cpp -#endif - /** * G29: Unified Bed Leveling by Roxy * @@ -325,28 +321,26 @@ void unified_bed_leveling::G29() { if (may_move) { planner.synchronize(); - #ifdef USE_PROBE_FOR_MESH_REF - // Send 'N' to force homing before G29 (internal only) - if (axes_should_home() || parser.seen_test('N')){ - gcode.home_all_axes(); - } - else { - gcode.process_subcommands_now(F("G28L0 X Y")); // Home X and Y only - } - // Set the probe trigger height as Z home before leveling - probe.probe_at_point(current_position, PROBE_PT_NONE,0 ,false ,true, Z_PROBE_LOW_POINT, Z_TWEEN_SAFE_CLEARANCE, false); - set_axis_is_at_home(Z_AXIS); - sync_plan_position(); - #else - #if ALL(DWIN_LCD_PROUI, ZHOME_BEFORE_LEVELING) - save_ubl_active_state_and_disable(); - gcode.process_subcommands_now(F("G28Z")); - restore_ubl_active_state(false); // ...without telling ExtUI "done" - #else - // Send 'N' to force homing before G29 (internal only) - if (axes_should_home() || parser.seen_test('N')) gcode.home_all_axes(); - #endif - #endif + #if ENABLED(USE_PROBE_FOR_MESH_REF) + // Send 'N' to force homing before G29 (internal only) + if (axes_should_home() || parser.seen_test('N')) { + gcode.home_all_axes(); + } + else { + gcode.process_subcommands_now(F("G28L0 X Y")); // Home X and Y only + } + // Set the probe trigger height as Z home before leveling + probe.probe_at_point(current_position, PROBE_PT_NONE,0 ,false ,true, Z_PROBE_LOW_POINT, Z_TWEEN_SAFE_CLEARANCE, false); + set_axis_is_at_home(Z_AXIS); + sync_plan_position(); + #elif ALL(DWIN_LCD_PROUI, ZHOME_BEFORE_LEVELING) + save_ubl_active_state_and_disable(); + gcode.process_subcommands_now(F("G28Z")); + restore_ubl_active_state(false); // ...without telling ExtUI "done" + #else + // Send 'N' to force homing before G29 (internal only) + if (axes_should_home() || parser.seen_test('N')) gcode.home_all_axes(); + #endif probe.use_probing_tool(); @@ -827,11 +821,7 @@ void unified_bed_leveling::shift_mesh_height() { if (best.pos.x >= 0) { // mesh point found and is reachable by probe TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::G29_POINT_START)); - #ifdef USE_PROBE_FOR_MESH_REF // adjust the mesh point value - const float measured_z = probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity) - mesh_zero_ref_offset; - #else - const float measured_z = probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity); - #endif + const float measured_z = DIFF_TERN(USE_PROBE_FOR_MESH_REF, probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity), mesh_zero_ref_offset); z_values[best.pos.x][best.pos.y] = isnan(measured_z) ? HUGE_VALF : measured_z; // Mark invalid point already probed with HUGE_VALF to omit it in the next loop #if ENABLED(EXTENSIBLE_UI) ExtUI::onMeshUpdate(best.pos, ExtUI::G29_POINT_FINISH); diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp index d2ba9a27fe..e915eb3e2a 100644 --- a/Marlin/src/gcode/bedlevel/abl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp @@ -77,10 +77,6 @@ #endif #endif -#ifdef USE_PROBE_FOR_MESH_REF - float mesh_zero_ref_offset = 0; // declared in gcode.h as external so it can be set in menu_probe_level.cpp -#endif - /** * @brief Do some things before returning from G29. * @param retry : true if the G29 can and should be retried. false if the failure is too serious. @@ -267,13 +263,13 @@ G29_TYPE GcodeSuite::G29() { G29_RETURN(false, false); } - #ifdef USE_PROBE_FOR_MESH_REF + #if ENABLED(USE_PROBE_FOR_MESH_REF) // Send 'N' to force homing before G29 (internal only) if (parser.seen_test('N')){ - process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); - } + process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); + } else { - process_subcommands_now(F("G28L0 X Y")); // Home X and Y only + process_subcommands_now(F("G28L0 X Y")); // Home X and Y only } // Set the probe trigger height as Z home before leveling probe.probe_at_point(current_position, PROBE_PT_NONE,0 ,false ,true, Z_PROBE_LOW_POINT, Z_TWEEN_SAFE_CLEARANCE, false); @@ -282,8 +278,8 @@ G29_TYPE GcodeSuite::G29() { #else // Send 'N' to force homing before G29 (internal only) if (parser.seen_test('N')){ - process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); - } + process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); + } #endif // Don't allow auto-leveling without homing first @@ -611,19 +607,11 @@ G29_TYPE GcodeSuite::G29() { #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - #ifdef USE_PROBE_FOR_MESH_REF - const float newz = abl.measured_z + mesh_zero_ref_offset; - #else - const float newz = abl.measured_z + abl.Z_offset; - #endif + const float newz = abl.measured_z + TERN(USE_PROBE_FOR_MESH_REF, mesh_zero_ref_offset, abl.Z_offset); abl.z_values[abl.meshCount.x][abl.meshCount.y] = newz; TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(abl.meshCount, newz)); - #ifdef USE_PROBE_FOR_MESH_REF - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM_P(PSTR("Save X"), abl.meshCount.x, SP_Y_STR, abl.meshCount.y, SP_Z_STR, abl.measured_z + mesh_zero_ref_offset); - #else - if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM_P(PSTR("Save X"), abl.meshCount.x, SP_Y_STR, abl.meshCount.y, SP_Z_STR, abl.measured_z + abl.Z_offset); - #endif + if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM_P(PSTR("Save X"), abl.meshCount.x, SP_Y_STR, abl.meshCount.y, SP_Z_STR, newz); #endif } @@ -825,11 +813,7 @@ G29_TYPE GcodeSuite::G29() { #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - #ifdef USE_PROBE_FOR_MESH_REF - const float z = abl.measured_z + mesh_zero_ref_offset; - #else - const float z = abl.measured_z + abl.Z_offset; - #endif + const float z = abl.measured_z + TERN(USE_PROBE_FOR_MESH_REF, mesh_zero_ref_offset, abl.Z_offset); abl.z_values[abl.meshCount.x][abl.meshCount.y] = z; TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(abl.meshCount, z)); diff --git a/Marlin/src/gcode/gcode.h b/Marlin/src/gcode/gcode.h index c4c81b2647..589cd2bc48 100644 --- a/Marlin/src/gcode/gcode.h +++ b/Marlin/src/gcode/gcode.h @@ -375,10 +375,6 @@ typedef bits_t(NUM_REL_MODES) relative_t; extern const char G28_STR[]; -#ifdef USE_PROBE_FOR_MESH_REF - extern float mesh_zero_ref_offset; -#endif - class GcodeSuite { public: diff --git a/Marlin/src/lcd/menu/menu_probe_level.cpp b/Marlin/src/lcd/menu/menu_probe_level.cpp index 51b7c08952..bb48cd8706 100644 --- a/Marlin/src/lcd/menu/menu_probe_level.cpp +++ b/Marlin/src/lcd/menu/menu_probe_level.cpp @@ -26,7 +26,7 @@ #include "../../inc/MarlinConfigPre.h" -#ifdef USE_PROBE_FOR_MESH_REF +#if ENABLED(USE_PROBE_FOR_MESH_REF) #include "../../gcode/gcode.h" #endif @@ -347,24 +347,24 @@ void menu_probe_level() { // // Probe Z Offset - Babystep or Edit // - if (can_babystep_z) { - #if ENABLED(BABYSTEP_ZPROBE_OFFSET) - SUBMENU(MSG_BABYSTEP_PROBE_Z, lcd_babystep_zoffset); - #endif - } - else { - #if HAS_BED_PROBE - #ifdef USE_PROBE_FOR_MESH_REF - #ifdef HOME_SWITCH_TO_BED_OFFSET_MENU - // Change the name of the menu option as the offsett relates to the fixed Z stop/Homing switch but - // reduce the range to -1/+1 and use the same offset variable so function stays the same - EDIT_ITEM_N(LCD_Z_OFFSET_TYPE, Z_AXIS, MSG_HOME_OFFSET_Z, &mesh_zero_ref_offset, -1, 0); - #endif - #else + #if HAS_BED_PROBE + + if (can_babystep_z) { + #if ENABLED(BABYSTEP_ZPROBE_OFFSET) + SUBMENU(MSG_BABYSTEP_PROBE_Z, lcd_babystep_zoffset); + #endif + } + else { + #if DISABLED(USE_PROBE_FOR_MESH_REF) EDIT_ITEM_N(LCD_Z_OFFSET_TYPE, Z_AXIS, MSG_ZPROBE_OFFSET_N, &probe.offset.z, PROBE_OFFSET_ZMIN, PROBE_OFFSET_ZMAX); - #endif - #endif - } + #elif ENABLED(HOME_SWITCH_TO_BED_OFFSET_MENU) + // Change the name of the menu option as the offsett relates to the fixed Z stop/Homing switch but + // reduce the range to -1/+1 and use the same offset variable so function stays the same + EDIT_ITEM_N(LCD_Z_OFFSET_TYPE, Z_AXIS, MSG_HOME_OFFSET_Z, &mesh_zero_ref_offset, -1, 0); + #endif + } + + #endif // HAS_BED_PROBE // // Probe Z Offset Wizard From 82cca9616dd27594d92b963885480546b8e09724 Mon Sep 17 00:00:00 2001 From: Scott Lahteine Date: Sun, 28 Sep 2025 16:56:07 -0500 Subject: [PATCH 8/8] authoritative Z endstop --- Marlin/Configuration.h | 32 +++-------------- Marlin/src/feature/bedlevel/bedlevel.cpp | 4 --- Marlin/src/feature/bedlevel/bedlevel.h | 4 --- Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp | 40 ++++++++++----------- Marlin/src/gcode/bedlevel/abl/G29.cpp | 30 +++++----------- Marlin/src/inc/SanityCheck.h | 2 ++ Marlin/src/lcd/menu/menu_probe_level.cpp | 32 ++++++----------- Marlin/src/module/probe.cpp | 19 ++++++++++ Marlin/src/module/probe.h | 4 +++ 9 files changed, 67 insertions(+), 100 deletions(-) diff --git a/Marlin/Configuration.h b/Marlin/Configuration.h index a3ca5b5c60..954129bab3 100644 --- a/Marlin/Configuration.h +++ b/Marlin/Configuration.h @@ -2109,35 +2109,11 @@ //#define MESH_BED_LEVELING /** - * When a level probe is fitted but not used for homing, enabling USE_PROBE_FOR_MESH_REF will avoid a level mesh - * being created with an unwanted offset between the fixed Z endstop/Homing Switch and probe trigger height. The - * probe Z offset menu option is removed from the level sub menu, as entering a Z offset there would shift the mesh - * and the Homeing position giving confusing status values for Z. It is recomended not to enter a Z offset value in - * the probe offset section of config.h for the same reason. - * - * If no Z offsets are entered elsewhere the status screen will always show the actual Z possition. e.g when homing - * the Z status will show 0 at the Home position, unless the height of the fixed Z stop/Homeing switch above the bed - * has been set by the HOME_SWITCH_TO_BED_OFFSET_MENU option while creating a level mesh. In which case the Home - * position will show as that height. The printer will move down to the print height when printing starts and Z status - * will show the first layer height. The Nozzle will follow the mesh and baby stepping can be used to temporarily lower - * or raise the nozzle to adjust first layer squish. - * - * If your Z stop/Homeing switch has a fine adjustment capability, the HOME_SWITCH_TO_BED_OFFSET_MENU menu option can be - * left at zero or disabled. Using the paper pinch test and setting the Z stop/Homeing switch position works just as well. + * Base the Probe Z Offset on a trustworthy Z endstop + * Requires the Z axis to be homed with an endstop. + * Use 'M206 Z' to adjust the Z endstop to match the height at bed center. */ -#if DISABLED(USE_PROBE_FOR_Z_HOMING) - #if ANY(AUTO_BED_LEVELING_BILINEAR, AUTO_BED_LEVELING_UBL, AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_3POINT) - //#define USE_PROBE_FOR_MESH_REF - #if ENABLED(USE_PROBE_FOR_MESH_REF) - /** - * HOME_SWITCH_TO_BED_OFFSET_MENU enables a menu option called "Home Offset Z" to adjust the Z offset between - * the Fixed Z endstop/Homing switch and the bed. This offset is only used to adjust the level mesh down - * to the Zero plane (Bed) as the mesh is created, so a new level mesh must be created when if it is changed to apply it. - */ - //#define HOME_SWITCH_TO_BED_OFFSET_MENU - #endif - #endif -#endif +//#define AUTO_Z_PROBE_OFFSET /** * Commands to execute at the start of G29 probing, diff --git a/Marlin/src/feature/bedlevel/bedlevel.cpp b/Marlin/src/feature/bedlevel/bedlevel.cpp index c0ed2a4cad..12d620e5af 100644 --- a/Marlin/src/feature/bedlevel/bedlevel.cpp +++ b/Marlin/src/feature/bedlevel/bedlevel.cpp @@ -35,10 +35,6 @@ bool g29_in_progress = false; #endif -#if ENABLED(USE_PROBE_FOR_MESH_REF) - float mesh_zero_ref_offset = 0; // declared in gcode.h as external so it can be set in menu_probe_level.cpp -#endif - #if ENABLED(LCD_BED_LEVELING) #include "../../lcd/marlinui.h" #endif diff --git a/Marlin/src/feature/bedlevel/bedlevel.h b/Marlin/src/feature/bedlevel/bedlevel.h index 8121725774..ccb9543e72 100644 --- a/Marlin/src/feature/bedlevel/bedlevel.h +++ b/Marlin/src/feature/bedlevel/bedlevel.h @@ -33,10 +33,6 @@ constexpr bool g29_in_progress = false; #endif -#if ENABLED(USE_PROBE_FOR_MESH_REF) - extern float mesh_zero_ref_offset; -#endif - bool leveling_is_valid(); void set_bed_leveling_enabled(const bool enable=true); void reset_bed_level(); diff --git a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp index ea029a7f04..b1f002fe8b 100644 --- a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp +++ b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp @@ -321,29 +321,27 @@ void unified_bed_leveling::G29() { if (may_move) { planner.synchronize(); - #if ENABLED(USE_PROBE_FOR_MESH_REF) - // Send 'N' to force homing before G29 (internal only) - if (axes_should_home() || parser.seen_test('N')) { - gcode.home_all_axes(); - } - else { - gcode.process_subcommands_now(F("G28L0 X Y")); // Home X and Y only - } - // Set the probe trigger height as Z home before leveling - probe.probe_at_point(current_position, PROBE_PT_NONE,0 ,false ,true, Z_PROBE_LOW_POINT, Z_TWEEN_SAFE_CLEARANCE, false); - set_axis_is_at_home(Z_AXIS); - sync_plan_position(); - #elif ALL(DWIN_LCD_PROUI, ZHOME_BEFORE_LEVELING) - save_ubl_active_state_and_disable(); - gcode.process_subcommands_now(F("G28Z")); - restore_ubl_active_state(false); // ...without telling ExtUI "done" - #else - // Send 'N' to force homing before G29 (internal only) - if (axes_should_home() || parser.seen_test('N')) gcode.home_all_axes(); - #endif + // Send 'N' to force homing before G29 (internal only) + const bool force_home = axes_should_home() || parser.seen_test('N'); + if (force_home) { + gcode.home_all_axes(); + } + else { + #if ALL(DWIN_LCD_PROUI, ZHOME_BEFORE_LEVELING) + save_ubl_active_state_and_disable(); + gcode.process_subcommands_now(F("G28Z")); + restore_ubl_active_state(false); // ...without telling ExtUI "done" + #elif ENABLED(AUTO_Z_PROBE_OFFSET) + gcode.process_subcommands_now(F("G28XYL0")); // Home X and Y only + #endif + } probe.use_probing_tool(); + #if ENABLED(AUTO_Z_PROBE_OFFSET) + (void)probe.probe_to_obtain_z_offset(); + #endif + #ifdef EVENT_GCODE_BEFORE_G29 if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPGM("Before G29 G-code: ", EVENT_GCODE_BEFORE_G29); gcode.process_subcommands_now(F(EVENT_GCODE_BEFORE_G29)); @@ -821,7 +819,7 @@ void unified_bed_leveling::shift_mesh_height() { if (best.pos.x >= 0) { // mesh point found and is reachable by probe TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(best.pos, ExtUI::G29_POINT_START)); - const float measured_z = DIFF_TERN(USE_PROBE_FOR_MESH_REF, probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity), mesh_zero_ref_offset); + const float measured_z = probe.probe_at_point(best.meshpos(), stow_probe ? PROBE_PT_STOW : PROBE_PT_RAISE, param.V_verbosity); z_values[best.pos.x][best.pos.y] = isnan(measured_z) ? HUGE_VALF : measured_z; // Mark invalid point already probed with HUGE_VALF to omit it in the next loop #if ENABLED(EXTENSIBLE_UI) ExtUI::onMeshUpdate(best.pos, ExtUI::G29_POINT_FINISH); diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp index e915eb3e2a..d6e5202b75 100644 --- a/Marlin/src/gcode/bedlevel/abl/G29.cpp +++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp @@ -263,24 +263,9 @@ G29_TYPE GcodeSuite::G29() { G29_RETURN(false, false); } - #if ENABLED(USE_PROBE_FOR_MESH_REF) - // Send 'N' to force homing before G29 (internal only) - if (parser.seen_test('N')){ - process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); - } - else { - process_subcommands_now(F("G28L0 X Y")); // Home X and Y only - } - // Set the probe trigger height as Z home before leveling - probe.probe_at_point(current_position, PROBE_PT_NONE,0 ,false ,true, Z_PROBE_LOW_POINT, Z_TWEEN_SAFE_CLEARANCE, false); - set_axis_is_at_home(Z_AXIS); - sync_plan_position(); - #else - // Send 'N' to force homing before G29 (internal only) - if (parser.seen_test('N')){ - process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); - } - #endif + // Send 'N' to force homing before G29 (internal only) + if (parser.seen_test('N')) + process_subcommands_now(TERN(CAN_SET_LEVELING_AFTER_G28, F("G28L0"), FPSTR(G28_STR))); // Don't allow auto-leveling without homing first if (homing_needed_error()) G29_RETURN(false, false); @@ -315,6 +300,10 @@ G29_TYPE GcodeSuite::G29() { gcode.process_subcommands_now(F(EVENT_GCODE_BEFORE_G29)); #endif + #if ENABLED(AUTO_Z_PROBE_OFFSET) + (void)probe.probe_to_obtain_z_offset(); + #endif + #if ANY(PROBE_MANUALLY, AUTO_BED_LEVELING_LINEAR) abl.abl_probe_index = -1; #endif @@ -607,7 +596,7 @@ G29_TYPE GcodeSuite::G29() { #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - const float newz = abl.measured_z + TERN(USE_PROBE_FOR_MESH_REF, mesh_zero_ref_offset, abl.Z_offset); + const float newz = abl.measured_z + abl.Z_offset; abl.z_values[abl.meshCount.x][abl.meshCount.y] = newz; TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(abl.meshCount, newz)); @@ -813,8 +802,7 @@ G29_TYPE GcodeSuite::G29() { #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) - const float z = abl.measured_z + TERN(USE_PROBE_FOR_MESH_REF, mesh_zero_ref_offset, abl.Z_offset); - + const float z = abl.measured_z + abl.Z_offset; abl.z_values[abl.meshCount.x][abl.meshCount.y] = z; TERN_(EXTENSIBLE_UI, ExtUI::onMeshUpdate(abl.meshCount, z)); diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h index 76b9c136d0..f2c7e25ad1 100644 --- a/Marlin/src/inc/SanityCheck.h +++ b/Marlin/src/inc/SanityCheck.h @@ -2528,6 +2528,8 @@ static_assert(NUM_SERVOS <= NUM_SERVO_PLUGS, "NUM_SERVOS (or some servo index) i #error "Z_HOME_DIR must be -1 when homing Z with the probe." #elif ALL(USE_PROBE_FOR_Z_HOMING, HOME_Z_FIRST) #error "HOME_Z_FIRST can't be used when homing Z with a probe." +#elif ALL(USE_PROBE_FOR_Z_HOMING, AUTO_Z_PROBE_OFFSET) + #error "AUTO_Z_PROBE_OFFSET requires Z Endstop Homing." #endif #if Z_HOME_TO_MAX && defined(Z_AFTER_HOMING) && DISABLED(ALLOW_Z_AFTER_HOMING) diff --git a/Marlin/src/lcd/menu/menu_probe_level.cpp b/Marlin/src/lcd/menu/menu_probe_level.cpp index bb48cd8706..ba806d665f 100644 --- a/Marlin/src/lcd/menu/menu_probe_level.cpp +++ b/Marlin/src/lcd/menu/menu_probe_level.cpp @@ -26,10 +26,6 @@ #include "../../inc/MarlinConfigPre.h" -#if ENABLED(USE_PROBE_FOR_MESH_REF) - #include "../../gcode/gcode.h" -#endif - #if HAS_MARLINUI_MENU && ANY(HAS_LEVELING, HAS_BED_PROBE, ASSISTED_TRAMMING_WIZARD, LCD_BED_TRAMMING) #include "menu_item.h" @@ -347,24 +343,16 @@ void menu_probe_level() { // // Probe Z Offset - Babystep or Edit // - #if HAS_BED_PROBE - - if (can_babystep_z) { - #if ENABLED(BABYSTEP_ZPROBE_OFFSET) - SUBMENU(MSG_BABYSTEP_PROBE_Z, lcd_babystep_zoffset); - #endif - } - else { - #if DISABLED(USE_PROBE_FOR_MESH_REF) - EDIT_ITEM_N(LCD_Z_OFFSET_TYPE, Z_AXIS, MSG_ZPROBE_OFFSET_N, &probe.offset.z, PROBE_OFFSET_ZMIN, PROBE_OFFSET_ZMAX); - #elif ENABLED(HOME_SWITCH_TO_BED_OFFSET_MENU) - // Change the name of the menu option as the offsett relates to the fixed Z stop/Homing switch but - // reduce the range to -1/+1 and use the same offset variable so function stays the same - EDIT_ITEM_N(LCD_Z_OFFSET_TYPE, Z_AXIS, MSG_HOME_OFFSET_Z, &mesh_zero_ref_offset, -1, 0); - #endif - } - - #endif // HAS_BED_PROBE + if (can_babystep_z) { + #if ENABLED(BABYSTEP_ZPROBE_OFFSET) + SUBMENU(MSG_BABYSTEP_PROBE_Z, lcd_babystep_zoffset); + #endif + } + else { + #if HAS_BED_PROBE + EDIT_ITEM_N(LCD_Z_OFFSET_TYPE, Z_AXIS, MSG_ZPROBE_OFFSET_N, &probe.offset.z, PROBE_OFFSET_ZMIN, PROBE_OFFSET_ZMAX); + #endif + } // // Probe Z Offset Wizard diff --git a/Marlin/src/module/probe.cpp b/Marlin/src/module/probe.cpp index a66c11a782..36443d672a 100644 --- a/Marlin/src/module/probe.cpp +++ b/Marlin/src/module/probe.cpp @@ -734,6 +734,25 @@ bool Probe::probe_down_to_z(const_float_t z, const_feedRate_t fr_mm_s) { } #endif +#if ENABLED(AUTO_Z_PROBE_OFFSET) + + // Determine the probe.offset.z based on the trusted Z0 + // Return 'true' on error condition. + bool Probe::probe_to_obtain_z_offset() { + use_probing_tool(); + do_z_clearance(Z_CLEARANCE_DEPLOY_PROBE); + const xy_pos_t ref_xy = { + TERN(Z_SAFE_HOMING, Z_SAFE_HOMING_X_POINT, X_CENTER), + TERN(Z_SAFE_HOMING, Z_SAFE_HOMING_Y_POINT, Y_CENTER) + }; + const float zval = probe_at_point(ref_xy, PROBE_PT_RAISE); + if (zval == NAN) return true; + offset.z = -zval; + return false; + } + +#endif // AUTO_Z_PROBE_OFFSET + /** * @brief Probe at the current XY (possibly more than once) to find the bed Z. * diff --git a/Marlin/src/module/probe.h b/Marlin/src/module/probe.h index 08a02b4d40..ad1334bef4 100644 --- a/Marlin/src/module/probe.h +++ b/Marlin/src/module/probe.h @@ -354,6 +354,10 @@ public: static bool tare(); #endif + #if ENABLED(AUTO_Z_PROBE_OFFSET) + static bool probe_to_obtain_z_offset(); + #endif + // Basic functions for Sensorless Homing and Probing #if HAS_DELTA_SENSORLESS_PROBING static void set_offset_sensorless_adj(const_float_t sz);