diff --git a/Configuration.h b/Configuration.h new file mode 100644 index 0000000000..900302093d --- /dev/null +++ b/Configuration.h @@ -0,0 +1,1807 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 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 . + * + */ + +/** + * 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 + * + */ +#ifndef CONFIGURATION_H +#define CONFIGURATION_H +#define CONFIGURATION_H_VERSION 010107 + +//=========================================================================== +//============================= Getting Started ============================= +//=========================================================================== + +/** + * Here are some standard links for getting your machine calibrated: + * + * http://reprap.org/wiki/Calibration + * http://youtu.be/wAL9d7FgInk + * http://calculator.josefprusa.cz + * http://reprap.org/wiki/Triffid_Hunter%27s_Calibration_Guide + * http://www.thingiverse.com/thing:5573 + * https://sites.google.com/site/repraplogphase/calibration-of-your-reprap + * http://www.thingiverse.com/thing:298812 + */ + +//=========================================================================== +//============================= DELTA Printer =============================== +//=========================================================================== +// For a Delta printer start with one of the configuration files in the +// example_configurations/delta directory and customize for your machine. +// + +//=========================================================================== +//============================= SCARA Printer =============================== +//=========================================================================== +// For a SCARA printer start with the configuration files in +// example_configurations/SCARA and customize for your machine. +// + +// @section info + +// User-specified version info of this build to display in [Pronterface, etc] terminal window during +// startup. Implementation of an idea by Prof Braino to inform user that any changes made to this +// build by the user have been successfully uploaded into firmware. +#define STRING_CONFIG_H_AUTHOR "TinyMachines3D" // Who made the changes. +#define SHOW_BOOTSCREEN +#define STRING_SPLASH_LINE1 SHORT_BUILD_VERSION // will be shown during bootup in line 1 +#define STRING_SPLASH_LINE2 WEBSITE_URL // will be shown during bootup in line 2 + +/** + * *** VENDORS PLEASE READ *** + * + * Marlin allows you to add a custom boot image for Graphical LCDs. + * With this option Marlin will first show your custom screen followed + * by the standard Marlin logo with version number and web URL. + * + * We encourage you to take advantage of this new feature and we also + * respecfully request that you retain the unmodified Marlin boot screen. + */ + +// Enable to show the bitmap in Marlin/_Bootscreen.h on startup. +#define SHOW_CUSTOM_BOOTSCREEN + +// Enable to show the bitmap in Marlin/_Statusscreen.h on the status screen. +#define CUSTOM_STATUS_SCREEN_IMAGE + +// @section machine + +/** + * 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 0 is always used by the Arduino bootloader regardless of this setting. + * + * :[0, 1, 2, 3, 4, 5, 6, 7] + */ +#define SERIAL_PORT 0 + +/** + * This setting determines the communication speed of the printer. + * + * 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 115200 + +// Enable the Bluetooth serial interface on AT90USB devices +//#define BLUETOOTH + +// The following define selects which electronics board you have. +// Please choose the name from boards.h that matches your setup +#ifndef MOTHERBOARD + #define MOTHERBOARD BOARD_RUMBA +#endif + +// Optional custom name for your RepStrap or other custom machine +// Displayed in the LCD "Ready" message +#define CUSTOM_MACHINE_NAME "TM3D T-REX 2+" + +// Define this to set a unique identifier for this printer, (Used by some programs to differentiate between machines) +// You can use an online service to generate a random UUID. (eg http://www.uuidgenerator.net/version4) +//#define MACHINE_UUID "00000000-0000-0000-0000-000000000000" + +// @section extruder + +// This defines the number of extruders +// :[1, 2, 3, 4, 5] +#define EXTRUDERS 2 + +// 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 + +/** + * Průša MK2 Single Nozzle Multi-Material Multiplexer, and variants. + * + * This device allows one stepper driver on a control board to drive + * two to eight stepper motors, one at a time, in a manner suitable + * for extruders. + * + * This option only allows the multiplexer to switch on tool-change. + * Additional options to configure custom E moves are pending. + */ +//#define MK2_MULTIPLEXER +#if ENABLED(MK2_MULTIPLEXER) + // Override the default DIO selector pins here, if needed. + // Some pins files may provide defaults for these pins. + //#define E_MUX0_PIN 40 // Always Required + //#define E_MUX1_PIN 42 // Needed for 3 to 8 steppers + //#define E_MUX2_PIN 44 // Needed for 5 to 8 steppers +#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 + +// A dual-nozzle that uses a servomotor to raise/lower one of the nozzles +//#define SWITCHING_NOZZLE +#if ENABLED(SWITCHING_NOZZLE) + #define SWITCHING_NOZZLE_SERVO_NR 0 + #define SWITCHING_NOZZLE_SERVO_ANGLES { 0, 90 } // Angles for E0, E1 + //#define HOTEND_OFFSET_Z { 0.0, 0.0 } +#endif + +/** + * Two separate X-carriages with extruders that connect to a moving part + * via a magnetic docking mechanism. Requires SOL1_PIN and SOL2_PIN. + */ +//#define PARKING_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 // Delay (ms) for magnetic field. No delay if 0 or not defined. + #define PARKING_EXTRUDER_PARKING_X { -78, 184 } // X positions for parking the extruders + #define PARKING_EXTRUDER_GRAB_DISTANCE 1 // mm to move beyond the parking point to grab the extruder + #define PARKING_EXTRUDER_SECURITY_RAISE 5 // Z-raise before parking + #define HOTEND_OFFSET_Z { 0.0, 1.3 } // Z-offsets of the two hotends. The first must be 0. +#endif + +/** + * "Mixing Extruder" + * - Adds a new code, M165, to set the current mix factors. + * - Extends the stepping routines to move multiple steppers in proportion to the mix. + * - Optional support for Repetier Firmware M163, M164, and virtual extruder. + * - This implementation supports only a single extruder. + * - Enable DIRECT_MIXING_IN_G1 for 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 +#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} // (in mm) for each extruder, offset of the hotend on the X axis +//#define HOTEND_OFFSET_Y {0.0, 5.00} // (in mm) for each extruder, offset of the hotend on the Y axis + +// @section machine + +/** + * Select your power supply here. Use 0 if you haven't connected the PS_ON_PIN + * + * 0 = No Power Switch + * 1 = ATX + * 2 = X-Box 360 203Watts (the blue wire connected to PS_ON and the red wire to VCC) + * + * :{ 0:'No power switch', 1:'ATX', 2:'X-Box 360' } + */ +#define POWER_SUPPLY 0 + +#if POWER_SUPPLY > 0 + // Enable this option to leave the PSU off at startup. + // Power to steppers and heaters will need to be turned on with M80. + //#define PS_DEFAULT_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 if fans need power + #define AUTO_POWER_E_FANS + #define AUTO_POWER_CONTROLLERFAN + #define POWER_TIMEOUT 30 + #endif + +#endif + +// @section temperature + +//=========================================================================== +//============================= Thermal Settings ============================ +//=========================================================================== + +/** + * --NORMAL IS 4.7kohm PULLUP!-- 1kohm pullup can be used on hotend sensor, using correct resistor and table + * + * Temperature sensors available: + * + * -3 : thermocouple with MAX31855 (only for sensor 0) + * -2 : thermocouple with MAX6675 (only for sensor 0) + * -1 : thermocouple with AD595 + * 0 : not used + * 1 : 100k thermistor - best choice for EPCOS 100k (4.7k pullup) + * 2 : 200k thermistor - ATC Semitec 204GT-2 (4.7k pullup) + * 3 : Mendel-parts thermistor (4.7k pullup) + * 4 : 10k thermistor !! do not use it for a hotend. It gives bad resolution at high temp. !! + * 5 : 100K thermistor - ATC Semitec 104GT-2/104NT-4-R025H42G (Used in ParCan & J-Head) (4.7k pullup) + * 6 : 100k EPCOS - Not as accurate as table 1 (created using a fluke thermocouple) (4.7k pullup) + * 7 : 100k Honeywell thermistor 135-104LAG-J01 (4.7k pullup) + * 71 : 100k Honeywell thermistor 135-104LAF-J01 (4.7k pullup) + * 8 : 100k 0603 SMD Vishay NTCS0603E3104FXT (4.7k pullup) + * 9 : 100k GE Sensing AL03006-58.2K-97-G1 (4.7k pullup) + * 10 : 100k RS thermistor 198-961 (4.7k pullup) + * 11 : 100k beta 3950 1% thermistor (4.7k pullup) + * 12 : 100k 0603 SMD Vishay NTCS0603E3104FXT (4.7k pullup) (calibrated for Makibox hot bed) + * 13 : 100k Hisens 3950 1% up to 300°C for hotend "Simple ONE " & "Hotend "All In ONE" + * 15 : 100k thermistor calibration for JGAurora A5 hotend + * 20 : the PT100 circuit found in the Ultimainboard V2.x + * 60 : 100k Maker's Tool Works Kapton Bed Thermistor beta=3950 + * 66 : 4.7M High Temperature thermistor from Dyze Design + * 70 : the 100K thermistor found in the bq Hephestos 2 + * 75 : 100k Generic Silicon Heat Pad with NTC 100K MGB18-104F39050L32 thermistor + * + * 1k ohm pullup tables - This is atypical, and requires changing out the 4.7k pullup for 1k. + * (but gives greater accuracy and more stable PID) + * 51 : 100k thermistor - EPCOS (1k pullup) + * 52 : 200k thermistor - ATC Semitec 204GT-2 (1k pullup) + * 55 : 100k thermistor - ATC Semitec 104GT-2 (Used in ParCan & J-Head) (1k pullup) + * + * 1047 : Pt1000 with 4k7 pullup + * 1010 : Pt1000 with 1k pullup (non standard) + * 147 : Pt100 with 4k7 pullup + * 110 : Pt100 with 1k pullup (non standard) + * + * 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. + * + * :{ '0': "Not used", '1':"100k / 4.7k - EPCOS", '2':"200k / 4.7k - ATC Semitec 204GT-2", '3':"Mendel-parts / 4.7k", '4':"10k !! do not use for a hotend. Bad resolution at high temp. !!", '5':"100K / 4.7k - ATC Semitec 104GT-2 (Used in ParCan & J-Head)", '6':"100k / 4.7k EPCOS - Not as accurate as Table 1", '7':"100k / 4.7k Honeywell 135-104LAG-J01", '8':"100k / 4.7k 0603 SMD Vishay NTCS0603E3104FXT", '9':"100k / 4.7k GE Sensing AL03006-58.2K-97-G1", '10':"100k / 4.7k RS 198-961", '11':"100k / 4.7k beta 3950 1%", '12':"100k / 4.7k 0603 SMD Vishay NTCS0603E3104FXT (calibrated for Makibox hot bed)", '13':"100k Hisens 3950 1% up to 300°C for hotend 'Simple ONE ' & hotend 'All In ONE'", '20':"PT100 (Ultimainboard V2.x)", '51':"100k / 1k - EPCOS", '52':"200k / 1k - ATC Semitec 204GT-2", '55':"100k / 1k - ATC Semitec 104GT-2 (Used in ParCan & J-Head)", '60':"100k Maker's Tool Works Kapton Bed Thermistor beta=3950", '66':"Dyze Design 4.7M High Temperature thermistor", '70':"the 100K thermistor found in the bq Hephestos 2", '71':"100k / 4.7k Honeywell 135-104LAF-J01", '147':"Pt100 / 4.7k", '1047':"Pt1000 / 4.7k", '110':"Pt100 / 1k (non-standard)", '1010':"Pt1000 / 1k (non standard)", '-3':"Thermocouple + MAX31855 (only for sensor 0)", '-2':"Thermocouple + MAX6675 (only for sensor 0)", '-1':"Thermocouple + AD595",'998':"Dummy 1", '999':"Dummy 2" } + */ +#define TEMP_SENSOR_0 1 +#define TEMP_SENSOR_1 1 +#define TEMP_SENSOR_2 0 +#define TEMP_SENSOR_3 0 +#define TEMP_SENSOR_4 0 +#define TEMP_SENSOR_BED 0 + +// Dummy thermistor constant temperature readings, for use with 998 and 999 +#define DUMMY_THERMISTOR_998_VALUE 25 +#define DUMMY_THERMISTOR_999_VALUE 100 + +// Use temp sensor 1 as a redundant sensor with sensor 0. If the readings +// from the two sensors differ too much the print will be aborted. +//#define TEMP_SENSOR_1_AS_REDUNDANT +#define MAX_REDUNDANT_TEMP_SENSOR_DIFF 10 + +// Extruder temperature must be close to target for this long before M109 returns success +#define TEMP_RESIDENCY_TIME 10 // (seconds) +#define TEMP_HYSTERESIS 3 // (degC) range of +/- temperatures considered "close" to the target one +#define TEMP_WINDOW 1 // (degC) Window around target to start the residency timer x degC early. + +// Bed temperature must be close to target for this long before M190 returns success +#define TEMP_BED_RESIDENCY_TIME 10 // (seconds) +#define TEMP_BED_HYSTERESIS 3 // (degC) range of +/- temperatures considered "close" to the target one +#define TEMP_BED_WINDOW 1 // (degC) Window around target to start the residency timer x degC early. + +// The minimal temperature defines the temperature below which the heater will not be enabled It is used +// to check that the wiring to the thermistor is not broken. +// Otherwise this would lead to the heater being powered on all the time. +#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 BED_MINTEMP 5 + +// When temperature exceeds max temp, your heater will be switched off. +// This feature exists to protect your hotend from overheating accidentally, but *NOT* from thermistor short/failure! +// You should use MINTEMP for thermistor short/failure protection. +#define HEATER_0_MAXTEMP 410 +#define HEATER_1_MAXTEMP 275 +#define HEATER_2_MAXTEMP 275 +#define HEATER_3_MAXTEMP 275 +#define HEATER_4_MAXTEMP 275 +#define BED_MAXTEMP 150 + +//=========================================================================== +//============================= PID Settings ================================ +//=========================================================================== +// PID Tuning Guide here: http://reprap.org/wiki/PID_Tuning + +// Comment the following line to disable PID and enable bang-bang. +#define PIDTEMP +#define BANG_MAX 255 // Limits current to nozzle while in bang-bang mode; 255=full current +#define PID_MAX BANG_MAX // Limits current to nozzle 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_AUTOTUNE_MENU // Add PID Autotune to the LCD "Temperature" menu to run M303 and apply the result. + //#define PID_DEBUG // Sends debug data to the serial port. + //#define PID_OPENLOOP 1 // 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_PARAMS_PER_HOTEND // Uses separate PID parameters for each extruder (useful for mismatched extruders) + // Set/get with gcode: M301 E[extruder number, 0-2] + #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. + + // If you are using a pre-configured hotend then you can use one of the value sets by uncommenting it + + // Ultimaker + #define DEFAULT_Kp 22.2 + #define DEFAULT_Ki 1.08 + #define DEFAULT_Kd 114 + + // MakerGear + //#define DEFAULT_Kp 7.0 + //#define DEFAULT_Ki 0.1 + //#define DEFAULT_Kd 12 + + // Mendel Parts V9 on 12V + //#define DEFAULT_Kp 63.0 + //#define DEFAULT_Ki 2.25 + //#define DEFAULT_Kd 440 + +#endif // PIDTEMP + +//=========================================================================== +//============================= PID > Bed Temperature Control =============== +//=========================================================================== +// Select PID or bang-bang with PIDTEMPBED. If bang-bang, BED_LIMIT_SWITCHING will enable hysteresis +// +// Uncomment this to enable PID on the bed. It uses the same frequency PWM as the extruder. +// If your PID_dT is the default, and correct for your hardware/configuration, that means 7.689Hz, +// which is fine for driving a square wave into a resistive load and does not significantly impact you 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, you probably +// shouldn't use bed PID until someone else verifies your hardware works. +// If this is enabled, find your own PID constants below. +//#define PIDTEMPBED + +//#define BED_LIMIT_SWITCHING + +// This sets the max power delivered to the bed, and replaces the HEATER_BED_DUTY_CYCLE_DIVIDER option. +// all forms of bed control obey this (PID, bang-bang, bang-bang with hysteresis) +// setting this to anything other than 255 enables a form of PWM to the bed just like HEATER_BED_DUTY_CYCLE_DIVIDER did, +// so you shouldn'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 + +#if ENABLED(PIDTEMPBED) + + //#define PID_BED_DEBUG // Sends 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 + + //120V 250W silicone heater into 4mm borosilicate (MendelMax 1.5+) + //from pidautotune + //#define DEFAULT_bedKp 97.1 + //#define DEFAULT_bedKi 1.41 + //#define DEFAULT_bedKd 1675.16 + + // FIND YOUR OWN: "M303 E-1 C8 S90" to run autotune on the bed at 90 degreesC for 8 cycles. +#endif // PIDTEMPBED + +// @section extruder + +// This option prevents extrusion if the temperature is below EXTRUDE_MINTEMP. +// It also enables the M302 command to set the minimum extrusion temperature +// or to allow moving the extruder regardless of the hotend temperature. +// *** IT IS HIGHLY RECOMMENDED TO LEAVE THIS OPTION ENABLED! *** +#define PREVENT_COLD_EXTRUSION +#define EXTRUDE_MINTEMP 170 + +// This option prevents a single extrusion longer than EXTRUDE_MAXLENGTH. +// Note that for Bowden Extruders a too-small value here may prevent loading. +#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 + +//=========================================================================== +//============================= Mechanical Settings ========================= +//=========================================================================== + +// @section machine + +// Uncomment one of these options to enable CoreXY, CoreXZ, or CoreYZ kinematics +// either in the usual order or reversed +//#define COREXY +//#define COREXZ +//#define COREYZ +//#define COREYX +//#define COREZX +//#define COREZY + +//=========================================================================== +//============================== Endstop Settings =========================== +//=========================================================================== + +// @section homing + +// Specify here all the endstop connectors that are connected to any endstop or probe. +// Almost all printers will be using one per axis. Probes will use one or more of the +// extra connectors. Leave undefined any used for non-endstop and non-probe purposes. +#define USE_XMIN_PLUG +//#define USE_YMIN_PLUG +#define USE_ZMIN_PLUG +#define USE_XMAX_PLUG +#define USE_YMAX_PLUG +//#define USE_ZMAX_PLUG + +// Enable pullup for all endstops to prevent a floating state +#define ENDSTOPPULLUPS +#if DISABLED(ENDSTOPPULLUPS) + // Disable ENDSTOPPULLUPS to set pullups individually + //#define ENDSTOPPULLUP_XMAX + //#define ENDSTOPPULLUP_YMAX + //#define ENDSTOPPULLUP_ZMAX + //#define ENDSTOPPULLUP_XMIN + //#define ENDSTOPPULLUP_YMIN + //#define ENDSTOPPULLUP_ZMIN + //#define ENDSTOPPULLUP_ZMIN_PROBE +#endif + +// Mechanical endstop with COM to ground and NC to Signal uses "false" here (most common setup). +#define X_MIN_ENDSTOP_INVERTING false // set to true to invert the logic of the endstop. +#define Y_MIN_ENDSTOP_INVERTING true // set to true to invert the logic of the endstop. +#define Z_MIN_ENDSTOP_INVERTING true // set to true to invert the logic of the endstop. +#define X_MAX_ENDSTOP_INVERTING false // set to true to invert the logic of the endstop. +#define Y_MAX_ENDSTOP_INVERTING true // set to true to invert the logic of the endstop. +#define Z_MAX_ENDSTOP_INVERTING true // set to true to invert the logic of the endstop. +#define Z_MIN_PROBE_ENDSTOP_INVERTING false // set to true to invert the logic of the probe. + +// 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 + +//============================================================================= +//============================== 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 (steps/mm) + * Override with M92 + * X, Y, Z, E0 [, E1[, E2[, E3[, E4]]]] + */ +#define DEFAULT_AXIS_STEPS_PER_UNIT { 80, 160, 1600, 92.599 } + +/** + * Default Max Feed Rate (mm/s) + * Override with M203 + * X, Y, Z, E0 [, E1[, E2[, E3[, E4]]]] + */ +#define DEFAULT_MAX_FEEDRATE { 250, 150, 5, 25 } + +/** + * Default Max Acceleration (change/s) change = mm/s + * (Maximum start speed for accelerated moves) + * Override with M201 + * X, Y, Z, E0 [, E1[, E2[, E3[, E4]]]] + */ +#define DEFAULT_MAX_ACCELERATION { 1500, 500, 400, 4000 } + +/** + * Default Acceleration (change/s) change = mm/s + * 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 (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 DEFAULT_XJERK 20.0 +#define DEFAULT_YJERK 10.0 +#define DEFAULT_ZJERK 0.4 +#define DEFAULT_EJERK 5.0 + +//=========================================================================== +//============================= Z Probe Options ============================= +//=========================================================================== +// @section probes + +// +// See http://marlinfw.org/docs/configuration/probes.html +// + +/** + * Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN + * + * Enable this option for a probe connected to the Z Min endstop pin. + */ +#define Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN + +/** + * Z_MIN_PROBE_ENDSTOP + * + * Enable this option for a probe connected to any pin except Z-Min. + * (By default Marlin assumes the Z-Max endstop pin.) + * To use a custom Z Probe pin, set Z_MIN_PROBE_PIN below. + * + * - The simplest option is to use a free endstop connector. + * - Use 5V for powered (usually inductive) sensors. + * + * - RAMPS 1.3/1.4 boards may use the 5V, GND, and Aux4->D32 pin: + * - For simple switches connect... + * - normally-closed switches to GND and D32. + * - normally-open switches to 5V and D32. + * + * WARNING: Setting the wrong pin may have unexpected and potentially + * disastrous consequences. Use with caution and do your homework. + * + */ +//#define Z_MIN_PROBE_ENDSTOP + +/** + * 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 + +/** + * Z Servo Probe, such as an endstop switch on a rotating arm. + */ +//#define Z_ENDSTOP_SERVO_NR 0 // Defaults to SERVO 0 connector. +//#define Z_SERVO_ANGLES {70,0} // Z Servo Deploy and Stow angles + +/** + * The BLTouch probe uses a Hall effect sensor and emulates a servo. + */ +#define BLTOUCH +#if ENABLED(BLTOUCH) + //#define BLTOUCH_DELAY 375 // (ms) Enable and increase if needed +#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 +//#define PROBING_FANS_OFF // Turn fans off when probing +//#define DELAY_BEFORE_PROBING 200 // (ms) To prevent vibrations from triggering piezo sensors + +// 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. + +// +// For Z_PROBE_ALLEN_KEY see the Delta example configurations. +// + +/** + * Z Probe to nozzle (X,Y) offset, relative to (0, 0). + * X and Y offsets must be integers. + * + * In the following example the X and Y offsets are both positive: + * #define X_PROBE_OFFSET_FROM_EXTRUDER 10 + * #define Y_PROBE_OFFSET_FROM_EXTRUDER 10 + * + * +-- BACK ---+ + * | | + * L | (+) P | R <-- probe (20,20) + * E | | I + * F | (-) N (+) | G <-- nozzle (10,10) + * T | | H + * | (-) | T + * | | + * O-- FRONT --+ + * (0,0) + */ +#define X_PROBE_OFFSET_FROM_EXTRUDER -7 // X offset: -left +right [of the nozzle] +#define Y_PROBE_OFFSET_FROM_EXTRUDER 29 // Y offset: -front +behind [the nozzle] +#define Z_PROBE_OFFSET_FROM_EXTRUDER -1.5 // Z offset: -below +above [the nozzle] + +// Certain types of probes need to stay away from edges +#define MIN_PROBE_EDGE 10 + +// X and Y axis travel speed (mm/m) between probes +#define XY_PROBE_SPEED 8000 + +// Speed for the first approach when double-probing (MULTIPLE_PROBING == 2) +#define Z_PROBE_SPEED_FAST HOMING_FEEDRATE_Z + +// Speed for the "accurate" probe of each point +#define Z_PROBE_SPEED_SLOW (Z_PROBE_SPEED_FAST / 2) + +// The number of probes to perform at each point. +// Set to 2 for a fast/slow probe, using the second probe result. +// Set to 3 or more for slow probes, averaging the results. +#define MULTIPLE_PROBING 2 + +/** + * 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 Z_PROBE_OFFSET_FROM_EXTRUDER, 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 5 // Z Clearance for Deploy/Stow +#define Z_CLEARANCE_BETWEEN_PROBES 5 // Z Clearance between probe points +//#define Z_AFTER_PROBING 5 // Z position after probing is done + +// For M851 give a range for adjusting the Z probe offset +#define Z_PROBE_OFFSET_RANGE_MIN -20 +#define Z_PROBE_OFFSET_RANGE_MAX 20 + +// Enable the M48 repeatability test to test probe accuracy +#define Z_MIN_PROBE_REPEATABILITY_TEST + +// For Inverting Stepper Enable Pins (Active Low) use 0, Non Inverting (Active High) use 1 +// :{ 0:'Low', 1:'High' } +#define X_ENABLE_ON 0 +#define Y_ENABLE_ON 0 +#define Z_ENABLE_ON 0 +#define E_ENABLE_ON 0 // For all extruders + +// Disables axis stepper immediately when it's not being used. +// WARNING: When motors turn off there is a chance of losing position accuracy! +#define DISABLE_X false +#define DISABLE_Y false +#define DISABLE_Z false +// Warn on display about possibly reduced accuracy +//#define DISABLE_REDUCED_ACCURACY_WARNING + +// @section extruder + +#define DISABLE_E false // For all extruders +#define DISABLE_INACTIVE_EXTRUDER true // Keep only the active extruder enabled. + +// @section machine + +// 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 false +#define INVERT_Z_DIR true + +// Enable this option for Toshiba stepper drivers +//#define CONFIG_STEPPERS_TOSHIBA + +// @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 true +#define INVERT_E2_DIR false +#define INVERT_E3_DIR false +#define INVERT_E4_DIR false + +// @section homing + +//#define NO_MOTION_BEFORE_HOMING // Inhibit movement until all axes have been homed + +//#define UNKNOWN_Z_NO_RAISE // Don't raise Z (lower the bed) if Z is "unknown." For beds that fall when Z is powered off. + +#define Z_HOMING_HEIGHT 4 // (in mm) Minimal z height before homing (G28) for Z clearance above the bed, clamps, ... + // Be sure you have this distance over your Z_MAX_POS in case. + +// 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 + +// @section machine + +// The size of the print bed +#define X_BED_SIZE 400 +#define Y_BED_SIZE 400 + +// Travel limits (mm) after homing, corresponding to endstop positions. +#define X_MIN_POS -42 +#define Y_MIN_POS 0 +#define Z_MIN_POS 0 +#define X_MAX_POS 450 +#define Y_MAX_POS Y_BED_SIZE +#define Z_MAX_POS 500 + +/** + * 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 +#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 +#endif + +/** + * Filament Runout Sensors + * Mechanical or opto endstops are used to check for the presence of filament. + * + * 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. + * By default the firmware assumes HIGH=FILAMENT PRESENT. + */ +//#define FILAMENT_RUNOUT_SENSOR +#if ENABLED(FILAMENT_RUNOUT_SENSOR) + #define NUM_RUNOUT_SENSORS 1 // Number of sensors, up to one per extruder. Define a FIL_RUNOUT#_PIN for each. + #define FIL_RUNOUT_INVERTING false // set to true to invert the logic of the sensor. + #define FIL_RUNOUT_PULLUP // Use internal pullup for filament runout pins. + #define FILAMENT_RUNOUT_SCRIPT "M600" +#endif + +//=========================================================================== +//=============================== 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 + +/** + * Normally G28 leaves leveling disabled on completion. Enable + * this option to have G28 restore the prior leveling state. + */ +#define RESTORE_LEVELING_AFTER_G28 + +/** + * Enable detailed logging of G28, G29, M48, etc. + * Turn on with the command 'M111 S32'. + * NOTE: Requires a lot of PROGMEM! + */ +//#define DEBUG_LEVELING_FEATURE + +#if ENABLED(MESH_BED_LEVELING) || ENABLED(AUTO_BED_LEVELING_BILINEAR) || ENABLED(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 + + // 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 the G26 Mesh Validation Tool. + #define MESH_TEST_HOTEND_TEMP 205.0 // (°C) Default nozzle temperature for the G26 Mesh Validation Tool. + #define MESH_TEST_BED_TEMP 60.0 // (°C) Default bed temperature for the G26 Mesh Validation Tool. + #endif + +#endif + +#if ENABLED(AUTO_BED_LEVELING_LINEAR) || ENABLED(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 + + // Set the boundaries for probing (where the probe can reach). + //#define LEFT_PROBE_BED_POSITION MIN_PROBE_EDGE + //#define RIGHT_PROBE_BED_POSITION (X_BED_SIZE - MIN_PROBE_EDGE) + //#define FRONT_PROBE_BED_POSITION MIN_PROBE_EDGE + //#define BACK_PROBE_BED_POSITION (Y_BED_SIZE - MIN_PROBE_EDGE) + + // 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 + + // + // Experimental 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 50 // Set Mesh bounds as an inset region of the bed + #define GRID_MAX_POINTS_X 15 // Don't use more than 15 points per axis, implementation limited. + #define GRID_MAX_POINTS_Y GRID_MAX_POINTS_X + + #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 0.0 // When the nozzle is off the mesh, this value is used + // as the Z-Height correction value. + +#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 + +/** + * Points to probe for all 3-point Leveling procedures. + * Override if the automatically selected points are inadequate. + */ +#if ENABLED(AUTO_BED_LEVELING_3POINT) || ENABLED(AUTO_BED_LEVELING_UBL) + #define PROBE_PT_1_X 30 + #define PROBE_PT_1_Y 365 + #define PROBE_PT_2_X 30 + #define PROBE_PT_2_Y 30 + #define PROBE_PT_3_X 365 + #define PROBE_PT_3_Y 30 +#endif + +/** + * Use the LCD controller for bed leveling + * Requires MESH_BED_LEVELING or PROBE_MANUALLY + */ +//#define LCD_BED_LEVELING + +#if ENABLED(LCD_BED_LEVELING) + #define MBL_Z_STEP 0.01 // Step size while manually probing Z axis. + #define LCD_PROBE_Z_RANGE 4 // Z Range centered on Z_MIN_POS for LCD Z adjustment +#endif + +// Add a menu item to move between bed corners for manual bed adjustment +//#define LEVEL_BED_CORNERS + +/** + * Commands to execute at the end of G29 probing. + * Useful to retract or move the Z probe out of the way. + */ +//#define Z_PROBE_END_SCRIPT "G1 Z10 F12000\nG1 X15 Y330\nG1 Z0.5\nG1 Z10" + + +// @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 + +// Use "Z Safe Homing" to avoid homing with a Z probe outside the bed area. +// +// With this feature enabled: +// +// - Allow Z homing only after X and Y homing AND stepper drivers still enabled. +// - If stepper drivers time out, it will need X and Y homing again before Z homing. +// - Move the Z probe (or nozzle) to a defined XY point before Z Homing when homing all axes (G28). +// - Prevent Z homing when the Z probe is outside bed area. +#define Z_SAFE_HOMING + +#if ENABLED(Z_SAFE_HOMING) + #define Z_SAFE_HOMING_X_POINT ((X_MIN_POS + X_MAX_POS) / 2) // X point for Z homing when homing all axis (G28). + #define Z_SAFE_HOMING_Y_POINT ((Y_MIN_POS + Y_MAX_POS) / 2) // Y point for Z homing when homing all axis (G28). +#endif + +// Homing speeds (mm/m) +#define HOMING_FEEDRATE_XY (50*60) +#define HOMING_FEEDRATE_Z (4*60) + +// @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 default skew factors directly here + // to override the above measurements: + #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 + #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 extras + +// +// EEPROM +// +// The microcontroller can store settings in the EEPROM, e.g. max velocity... +// M500 - stores parameters in EEPROM +// M501 - reads parameters from EEPROM (if you need reset them after you changed them temporarily). +// M502 - reverts to the default "factory settings". You still need to store them in EEPROM afterwards if you want to. +// +#define EEPROM_SETTINGS // Enable for M500 and M501 commands +//#define DISABLE_M503 // Saves ~2700 bytes of PROGMEM. Disable for release! +#define EEPROM_CHITCHAT // Give feedback on EEPROM commands. Disable to save PROGMEM. + +// +// 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 + +// +// M100 Free Memory Watcher +// +//#define M100_FREE_MEMORY_WATCHER // Add M100 (Free Memory Watcher) to debug memory usage + +// +// G20/G21 Inch mode support +// +//#define INCH_MODE_SUPPORT + +// +// M149 Set temperature units support +// +//#define TEMPERATURE_UNITS_SUPPORT + +// @section temperature + +// Preheat Constants +#define PREHEAT_1_TEMP_HOTEND 190 +#define PREHEAT_1_TEMP_BED 70 +#define PREHEAT_1_FAN_SPEED 125 // Value from 0 to 255 + +#define PREHEAT_2_TEMP_HOTEND 220 +#define PREHEAT_2_TEMP_BED 110 +#define PREHEAT_2_FAN_SPEED 125 // Value from 0 to 255 + +/** + * 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 } + #define NOZZLE_PARK_POINT { (X_MIN_POS + 50), (Y_MIN_POS + 10), 20 } + #define NOZZLE_PARK_XY_FEEDRATE 100 // X and Y axes feedrate in mm/s (also used for delta printers Z axis) + #define NOZZLE_PARK_Z_FEEDRATE 5 // Z axis feedrate in mm/s (not used for delta printers) +#endif + +/** + * Clean Nozzle Feature -- EXPERIMENTAL + * + * 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. + * Attention: EXPERIMENTAL. G-code arguments may change. + * + */ +//#define NOZZLE_CLEAN_FEATURE + +#if ENABLED(NOZZLE_CLEAN_FEATURE) + // Default number of pattern repetitions + #define NOZZLE_CLEAN_STROKES 12 + + // Default number of triangles + #define NOZZLE_CLEAN_TRIANGLES 3 + + // Specify positions as { X, Y, Z } + #define NOZZLE_CLEAN_START_POINT { 30, 30, (Z_MIN_POS + 1)} + #define NOZZLE_CLEAN_END_POINT {100, 60, (Z_MIN_POS + 1)} + + // Circular pattern radius + #define NOZZLE_CLEAN_CIRCLE_RADIUS 6.5 + // Circular pattern circle fragments number + #define NOZZLE_CLEAN_CIRCLE_FN 10 + // Middle point of circle + #define NOZZLE_CLEAN_CIRCLE_MIDDLE NOZZLE_CLEAN_START_POINT + + // Moves the nozzle to the initial position + #define NOZZLE_CLEAN_GOBACK +#endif + +/** + * Print Job Timer + * + * Automatically start and stop the print job timer on M104/M109/M190. + * + * M104 (hotend, no wait) - high temp = none, low temp = stop timer + * M109 (hotend, wait) - high temp = start timer, low temp = stop timer + * M190 (bed, wait) - high temp = start timer, low temp = none + * + * 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 + +/** + * 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 + +//============================================================================= +//============================= LCD and SD support ============================ +//============================================================================= + +// @section lcd + +/** + * LCD LANGUAGE + * + * Select the language to display on the LCD. These languages are available: + * + * en, an, bg, ca, cn, cz, cz_utf8, de, el, el-gr, es, es_utf8, eu, fi, fr, fr_utf8, + * gl, hr, it, kana, kana_utf8, nl, pl, pt, pt_utf8, pt-br, pt-br_utf8, ru, sk_utf8, + * tr, uk, zh_CN, zh_TW, test + * + * :{ 'en':'English', 'an':'Aragonese', 'bg':'Bulgarian', 'ca':'Catalan', 'cn':'Chinese', 'cz':'Czech', 'cz_utf8':'Czech (UTF8)', 'de':'German', 'el':'Greek', 'el-gr':'Greek (Greece)', 'es':'Spanish', 'es_utf8':'Spanish (UTF8)', 'eu':'Basque-Euskera', 'fi':'Finnish', 'fr':'French', 'fr_utf8':'French (UTF8)', 'gl':'Galician', 'hr':'Croatian', 'it':'Italian', 'kana':'Japanese', 'kana_utf8':'Japanese (UTF8)', 'nl':'Dutch', 'pl':'Polish', 'pt':'Portuguese', 'pt-br':'Portuguese (Brazilian)', 'pt-br_utf8':'Portuguese (Brazilian UTF8)', 'pt_utf8':'Portuguese (UTF8)', 'ru':'Russian', 'sk_utf8':'Slovak (UTF8)', 'tr':'Turkish', 'uk':'Ukrainian', 'zh_CN':'Chinese (Simplified)', 'zh_TW':'Chinese (Taiwan)', test':'TEST' } + */ +#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 http://marlinfw.org/docs/development/lcd_language.html + * + * :['JAPANESE', 'WESTERN', 'CYRILLIC'] + */ +#define DISPLAY_CHARSET_HD44780 JAPANESE + +/** + * LCD TYPE + * + * Enable ULTRA_LCD for a 16x2, 16x4, 20x2, or 20x4 character-based LCD. + * Enable DOGLCD for a 128x64 (ST7565R) Full Graphical Display. + * (These options will be enabled automatically for most displays.) + * + * IMPORTANT: The U8glib library is required for Full Graphic Display! + * https://github.com/olikraus/U8glib_Arduino + */ +//#define ULTRA_LCD // Character based +//#define DOGLCD // Full graphics display + +/** + * 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: SPI SPEED + * + * Enable one of the following items for a slower SPI transfer speed. + * This may be required to resolve "volume init" errors. + */ +//#define SPI_SPEED SPI_HALF_SPEED +//#define SPI_SPEED SPI_QUARTER_SPEED +//#define SPI_SPEED SPI_EIGHTH_SPEED + +/** + * SD CARD: ENABLE CRC + * + * Use CRC checks and retries on the SD communication. + */ +//#define SD_CHECK_AND_RETRY + +// +// 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 + +// +// Individual Axis Homing +// +// Add individual axis homing items (Home X, Home Y, and Home Z) to the LCD menu. +// +//#define INDIVIDUAL_AXIS_HOMING_MENU + +// +// 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 + +// +// CONTROLLER TYPE: Standard +// +// Marlin supports a wide variety of controllers. +// Enable one of the following options to specify your controller. +// + +// +// ULTIMAKER Controller. +// +//#define ULTIMAKERCONTROLLER + +// +// ULTIPANEL as seen on Thingiverse. +// +//#define ULTIPANEL + +// +// PanelOne from T3P3 (via RAMPS 1.4 AUX2/AUX3) +// http://reprap.org/wiki/PanelOne +// +//#define PANEL_ONE + +// +// MaKr3d Makr-Panel with graphic controller and SD support. +// http://reprap.org/wiki/MaKr3d_MaKrPanel +// +//#define MAKRPANEL + +// +// ReprapWorld Graphical LCD +// https://reprapworld.com/?products_details&products_id/1218 +// +//#define REPRAPWORLD_GRAPHICAL_LCD + +// +// Activate one of these if you have a Panucatt Devices +// Viki 2.0 or mini Viki with Graphic LCD +// http://panucatt.com +// +//#define VIKI2 +//#define miniVIKI + +// +// Adafruit ST7565 Full Graphic Controller. +// https://github.com/eboston/Adafruit-ST7565-Full-Graphic-Controller/ +// +//#define ELB_FULL_GRAPHIC_CONTROLLER + +// +// RepRapDiscount Smart Controller. +// http://reprap.org/wiki/RepRapDiscount_Smart_Controller +// +// Note: Usually sold with a white PCB. +// +//#define REPRAP_DISCOUNT_SMART_CONTROLLER + +// +// GADGETS3D G3D LCD/SD Controller +// http://reprap.org/wiki/RAMPS_1.3/1.4_GADGETS3D_Shield_with_Panel +// +// Note: Usually sold with a blue PCB. +// +//#define G3D_PANEL + +// +// RepRapDiscount FULL GRAPHIC Smart Controller +// http://reprap.org/wiki/RepRapDiscount_Full_Graphic_Smart_Controller +// +#define REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER + +// +// MakerLab Mini Panel with graphic +// controller and SD support - http://reprap.org/wiki/Mini_panel +// +//#define MINIPANEL + +// +// RepRapWorld REPRAPWORLD_KEYPAD v1.1 +// http://reprapworld.com/?products_details&products_id=202&cPath=1591_1626 +// +// REPRAPWORLD_KEYPAD_MOVE_STEP sets how much should the robot move when a key +// is pressed, a value of 10.0 means 10mm per click. +// +//#define REPRAPWORLD_KEYPAD +//#define REPRAPWORLD_KEYPAD_MOVE_STEP 1.0 + +// +// RigidBot Panel V1.0 +// http://www.inventapart.com/ +// +//#define RIGIDBOT_PANEL + +// +// BQ LCD Smart Controller shipped by +// default with the BQ Hephestos 2 and Witbox 2. +// +//#define BQ_LCD_SMART_CONTROLLER + +// +// Cartesio UI +// http://mauk.cc/webshop/cartesio-shop/electronics/user-interface +// +//#define CARTESIO_UI + +// +// ANET and Tronxy Controller supported displays. +// +//#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. + +//#define ANET_FULL_GRAPHICS_LCD // Anet 128x64 full graphics lcd with rotary encoder as used on Anet A6 + // A clone of the RepRapDiscount full graphics display but with + // different pins/wiring (see pins_ANET_10.h). + +// +// LCD for Melzi Card with Graphical LCD +// +//#define LCD_FOR_MELZI + +// +// LCD for Malyan M200 printers. +// This requires SDSUPPORT to be enabled +// +//#define MALYAN_LCD + +// +// 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 +// http://www.elefu.com/index.php?route=product/product&product_id=53 +// +//#define RA_CONTROL_PANEL + +// +// Sainsmart (YwRobot) LCD Displays +// +// These require F.Malpartida's LiquidCrystal_I2C library +// https://bitbucket.org/fmalpartida/new-liquidcrystal/wiki/Home +// +//#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 + +// +// 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 + +// +// 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 + +// +// CONTROLLER TYPE: Shift register panels +// +// 2 wire Non-latching LCD SR from https://goo.gl/aJJ4sH +// LCD configuration: http://reprap.org/wiki/SAV_3D_LCD +// +//#define SAV_3DLCD + +// +// TinyBoy2 128x64 OLED / Encoder Panel +// +//#define OLED_PANEL_TINYBOY2 + +// +// Makeboard 3D Printer Parts 3D Printer Mini Display 1602 Mini Controller +// https://www.aliexpress.com/item/Micromake-Makeboard-3D-Printer-Parts-3D-Printer-Mini-Display-1602-Mini-Controller-Compatible-with-Ramps-1/32765887917.html +// +//#define MAKEBOARD_MINI_2_LINE_DISPLAY_1602 + +// +// MKS MINI12864 with graphic controller and SD support +// http://reprap.org/wiki/MKS_MINI_12864 +// +//#define MKS_MINI_12864 + +// +// Factory display for Creality CR-10 +// https://www.aliexpress.com/item/Universal-LCD-12864-3D-Printer-Display-Screen-With-Encoder-For-CR-10-CR-7-Model/32833148327.html +// +// This is RAMPS-compatible using a single 10-pin connector. +// (For CR-10 owners who want to replace the Melzi Creality board but retain the display) +// +//#define CR10_STOCKDISPLAY + +// +// MKS OLED 1.3" 128 × 64 FULL GRAPHICS CONTROLLER +// http://reprap.org/wiki/MKS_12864OLED +// +// Tiny, but very sharp OLED display +// +//#define MKS_12864OLED // Uses the SH1106 controller (default) +//#define MKS_12864OLED_SSD1306 // Uses the SSD1306 controller + +// +// Silvergate GLCD controller +// http://github.com/android444/Silvergate +// +//#define SILVER_GATE_GLCD_CONTROLLER + +//============================================================================= +//=============================== Extra Features ============================== +//============================================================================= + +// @section extras + +// Increase the FAN PWM frequency. Removes the PWM noise but increases heating in the FET/Arduino +//#define FAST_PWM_FAN + +// 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. +#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 + +// 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 + +// M240 Triggers a camera by emulating a Canon RC-1 Remote +// Data from: http://www.doc-diy.net/photo/rc-1_hacked/ +//#define PHOTOGRAPH_PIN 23 + +// SkeinForge sends the wrong arc g-codes when using Arc Point as fillet procedure +//#define SF_ARC_FIX + +// Support for the BariCUDA Paste Extruder +//#define BARICUDA + +// Support for BlinkM/CyzRgb +//#define BLINKM + +// Support for PCA9632 PWM LED driver +//#define PCA9632 + +/** + * 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 MOFSET 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. + * *** CAUTION *** + * + * LED Type. Enable only one of the following two options. + * + */ +//#define RGB_LED +//#define RGBW_LED + +#if ENABLED(RGB_LED) || ENABLED(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 + +// Support for Adafruit Neopixel LED driver +//#define NEOPIXEL_LED +#if ENABLED(NEOPIXEL_LED) + #define NEOPIXEL_TYPE NEO_GRBW // NEO_GRBW / NEO_GRB - four/three channel driver type (defined in Adafruit_NeoPixel.h) + #define NEOPIXEL_PIN 4 // LED driving pin on motherboard 4 => D4 (EXP2-5 on Printrboard) / 30 => PC7 (EXP3-13 on Rumba) + #define NEOPIXEL_PIXELS 30 // Number of LEDs in the strip + #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 +#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 ENABLED(BLINKM) || ENABLED(RGB_LED) || ENABLED(RGBW_LED) || ENABLED(PCA9632) || ENABLED(NEOPIXEL_LED) + #define PRINTER_EVENT_LEDS +#endif + +/** + * R/C SERVO support + * Sponsored by TrinityLabs, Reworked by codexmas + */ + +/** + * 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. + * Leave undefined or set to 0 to entirely disable the servo subsystem. + */ +//#define NUM_SERVOS 3 // Servo index starts with 0 for M280 command + +// Delay (in milliseconds) 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 } + +// Servo deactivation +// +// With this option servos are powered only during movement, then turned off to prevent jitter. +//#define DEACTIVATE_SERVOS_AFTER_MOVE + +#endif // CONFIGURATION_H diff --git a/Configuration_adv.h b/Configuration_adv.h new file mode 100644 index 0000000000..3aef0437e8 --- /dev/null +++ b/Configuration_adv.h @@ -0,0 +1,1624 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 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 . + * + */ + +/** + * Configuration_adv.h + * + * Advanced settings. + * Only change these if you know exactly what you're doing. + * Some of these settings can damage your printer if improperly set! + * + * Basic settings can be found in Configuration.h + * + */ +#ifndef CONFIGURATION_ADV_H +#define CONFIGURATION_ADV_H +#define CONFIGURATION_ADV_H_VERSION 010107 + +// @section temperature + +//=========================================================================== +//=============================Thermal Settings ============================ +//=========================================================================== + +// +// Hephestos 2 24V heated bed upgrade kit. +// https://store.bq.com/en/heated-bed-kit-hephestos2 +// +//#define HEPHESTOS2_HEATED_BED_KIT +#if ENABLED(HEPHESTOS2_HEATED_BED_KIT) + #undef TEMP_SENSOR_BED + #define TEMP_SENSOR_BED 70 + #define HEATER_BED_INVERTING true +#endif + +#if DISABLED(PIDTEMPBED) + #define BED_CHECK_INTERVAL 5000 // ms between checks in bang-bang control + #if ENABLED(BED_LIMIT_SWITCHING) + #define BED_HYSTERESIS 2 // Only disable heating if T>target+BED_HYSTERESIS and enable heating if T>target-BED_HYSTERESIS + #endif +#endif + +/** + * 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. + * + * The solution: Once the temperature reaches the target, start observing. + * If the temperature stays too far below the target (hysteresis) for too + * long (period), the firmware will halt the machine as a safety precaution. + * + * If you get false positives for "Thermal Runaway", increase + * THERMAL_PROTECTION_HYSTERESIS and/or THERMAL_PROTECTION_PERIOD + */ +#if ENABLED(THERMAL_PROTECTION_HOTENDS) + #define THERMAL_PROTECTION_PERIOD 40 // Seconds + #define THERMAL_PROTECTION_HYSTERESIS 4 // Degrees Celsius + + /** + * Whenever an M104, M109, or M303 increases the target temperature, the + * firmware will wait for the WATCH_TEMP_PERIOD to expire. If the temperature + * hasn't increased by WATCH_TEMP_INCREASE degrees, the machine is halted and + * requires a hard reset. This test restarts with any M104/M109/M303, but only + * if the current temperature is far enough below the target for a reliable + * test. + * + * If you get false positives for "Heating failed", increase WATCH_TEMP_PERIOD + * and/or decrease WATCH_TEMP_INCREASE. WATCH_TEMP_INCREASE should not be set + * below 2. + */ + #define WATCH_TEMP_PERIOD 20 // Seconds + #define WATCH_TEMP_INCREASE 2 // Degrees Celsius +#endif + +/** + * Thermal Protection parameters for the bed are just as above for hotends. + */ +#if ENABLED(THERMAL_PROTECTION_BED) + #define THERMAL_PROTECTION_BED_PERIOD 20 // Seconds + #define THERMAL_PROTECTION_BED_HYSTERESIS 2 // Degrees Celsius + + /** + * As described above, except for the bed (M140/M190/M303). + */ + #define WATCH_BED_TEMP_PERIOD 60 // Seconds + #define WATCH_BED_TEMP_INCREASE 2 // Degrees Celsius +#endif + +#if ENABLED(PIDTEMP) + // this adds an experimental additional term to the heating power, proportional to the extrusion speed. + // if Kc is chosen well, the additional required power due to increased melting should be compensated. + //#define PID_EXTRUSION_SCALING + #if ENABLED(PID_EXTRUSION_SCALING) + #define DEFAULT_Kc (100) //heating power=Kc*(e_speed) + #define LPQ_MAX_LEN 50 + #endif +#endif + +/** + * Automatic Temperature: + * The hotend target temperature is calculated by all the buffered lines of gcode. + * The maximum buffered steps/sec of the extruder motor is called "se". + * Start autotemp mode with M109 S B F + * The target temperature is set to mintemp+factor*se[steps/sec] and is limited by + * mintemp and maxtemp. Turn this off by executing M109 without F* + * Also, if the temperature is set to a value below mintemp, it will not be changed by autotemp. + * On an Ultimaker, some initial testing worked with M109 S215 B260 F1 in the start.gcode + */ +#define AUTOTEMP +#if ENABLED(AUTOTEMP) + #define AUTOTEMP_OLDWEIGHT 0.98 +#endif + +// Show extra position information in M114 +//#define M114_DETAIL + +// Show Temperature ADC value +// Enable for M105 to include ADC values read from temperature sensors. +//#define SHOW_TEMP_ADC_VALUES + +/** + * High Temperature Thermistor Support + * + * Thermistors able to support high temperature tend to have a hard time getting + * good readings at room and lower temperatures. This means HEATER_X_RAW_LO_TEMP + * will probably be caught when the heating element first turns on during the + * preheating process, which will trigger a min_temp_error as a safety measure + * and force stop everything. + * To circumvent this limitation, we allow for a preheat time (during which, + * min_temp_error won't be triggered) and add a min_temp buffer to handle + * aberrant readings. + * + * If you want to enable this feature for your hotend thermistor(s) + * uncomment and set values > 0 in the constants below + */ + +// The number of consecutive low temperature errors that can occur +// before a min_temp_error is triggered. (Shouldn't be more than 10.) +//#define MAX_CONSECUTIVE_LOW_TEMPERATURE_ERROR_ALLOWED 0 + +// The number of milliseconds a hotend will preheat before starting to check +// the temperature. This value should NOT be set to the time it takes the +// hot end to reach the target temperature, but the time it takes to reach +// the minimum temperature your thermistor can read. The lower the better/safer. +// This shouldn't need to be more than 30 seconds (30000) +//#define MILLISECONDS_PREHEAT_TIME 0 + +// @section extruder + +// Extruder runout prevention. +// If the machine is idle and the temperature over MINTEMP +// then extrude some filament every couple of SECONDS. +//#define EXTRUDER_RUNOUT_PREVENT +#if ENABLED(EXTRUDER_RUNOUT_PREVENT) + #define EXTRUDER_RUNOUT_MINTEMP 190 + #define EXTRUDER_RUNOUT_SECONDS 30 + #define EXTRUDER_RUNOUT_SPEED 1500 // mm/m + #define EXTRUDER_RUNOUT_EXTRUDE 5 // mm +#endif + +// @section temperature + +//These defines help to calibrate the AD595 sensor in case you get wrong temperature measurements. +//The measured temperature is defined as "actualTemp = (measuredTemp * TEMP_SENSOR_AD595_GAIN) + TEMP_SENSOR_AD595_OFFSET" +#define TEMP_SENSOR_AD595_OFFSET 0.0 +#define TEMP_SENSOR_AD595_GAIN 1.0 + +/** + * Controller Fan + * To cool down the stepper drivers and MOSFETs. + * + * The fan will turn on automatically whenever any stepper is enabled + * and turn off after a set period after all steppers are turned off. + */ +//#define USE_CONTROLLER_FAN +#if ENABLED(USE_CONTROLLER_FAN) + //#define CONTROLLER_FAN_PIN -1 // Set a custom pin for the controller fan + #define CONTROLLERFAN_SECS 60 // Duration in seconds for the fan to run after all motors are disabled + #define CONTROLLERFAN_SPEED 255 // 255 == full speed +#endif + +// When first starting the main fan, run it at full speed for the +// given number of milliseconds. This gets the fan spinning reliably +// before setting a PWM value. (Does not work with software PWM for fan on Sanguinololu) +//#define FAN_KICKSTART_TIME 100 + +// This defines the minimal speed for the main fan, run in PWM mode +// to enable uncomment and set minimal PWM speed for reliable running (1-255) +// if fan speed is [1 - (FAN_MIN_PWM-1)] it is set to FAN_MIN_PWM +//#define FAN_MIN_PWM 50 + +// @section extruder + +/** + * Extruder cooling fans + * + * Extruder auto fans automatically turn on when their extruders' + * temperatures go above EXTRUDER_AUTO_FAN_TEMPERATURE. + * + * Your board's pins file specifies the recommended pins. Override those here + * or set to -1 to disable completely. + * + * Multiple extruders can be assigned to the same pin in which case + * the fan will turn on when any selected extruder is above the threshold. + */ +#define E0_AUTO_FAN_PIN -1 +#define E1_AUTO_FAN_PIN -1 +#define E2_AUTO_FAN_PIN -1 +#define E3_AUTO_FAN_PIN -1 +#define E4_AUTO_FAN_PIN -1 +#define EXTRUDER_AUTO_FAN_TEMPERATURE 50 +#define EXTRUDER_AUTO_FAN_SPEED 255 // == full speed + +/** + * Part-Cooling Fan Multiplexer + * + * This feature allows you to digitally multiplex the fan output. + * The multiplexer is automatically switched at tool-change. + * Set FANMUX[012]_PINs below for up to 2, 4, or 8 multiplexed fans. + */ +#define FANMUX0_PIN -1 +#define FANMUX1_PIN -1 +#define FANMUX2_PIN -1 + +/** + * M355 Case Light on-off / brightness + */ +#define CASE_LIGHT_ENABLE +#if ENABLED(CASE_LIGHT_ENABLE) + #define CASE_LIGHT_PIN 8 // Override the default pin if needed + #define INVERT_CASE_LIGHT false // Set true if Case Light is ON when pin is LOW + #define CASE_LIGHT_DEFAULT_ON true // Set default power-up state on + #define CASE_LIGHT_DEFAULT_BRIGHTNESS 255 // Set default power-up brightness (0-255, requires PWM pin) + #define MENU_ITEM_CASE_LIGHT // Add a Case Light option to the LCD main menu + //#define CASE_LIGHT_USE_NEOPIXEL // Use Neopixel LED as case light, requires NEOPIXEL_LED. + #if ENABLED(CASE_LIGHT_USE_NEOPIXEL) + #define CASE_LIGHT_NEOPIXEL_COLOR { 255, 255, 255, 255 } // { Red, Green, Blue, White } + #endif +#endif + +//=========================================================================== +//============================ Mechanical Settings ========================== +//=========================================================================== + +// @section homing + +// If you want endstops to stay on (by default) even when not homing +// enable this option. Override at any time with M120, M121. +//#define ENDSTOPS_ALWAYS_ON_DEFAULT + +// @section extras + +//#define Z_LATE_ENABLE // Enable Z the last moment. Needed if your Z driver overheats. + +/** + * Dual Steppers / Dual Endstops + * + * This section will allow you to use extra E drivers to drive a second motor for X, Y, or Z axes. + * + * For example, set X_DUAL_STEPPER_DRIVERS setting to use a second motor. If the motors need to + * spin in opposite directions set INVERT_X2_VS_X_DIR. If the second motor needs its own endstop + * set X_DUAL_ENDSTOPS. This can adjust for "racking." Use X2_USE_ENDSTOP to set the endstop plug + * that should be used for the second endstop. Extra endstops will appear in the output of 'M119'. + * + * Use X_DUAL_ENDSTOP_ADJUSTMENT to adjust for mechanical imperfection. After homing both motors + * this offset is applied to the X2 motor. To find the offset home the X axis, and measure the error + * in X2. Dual endstop offsets can be set at runtime with 'M666 X Y Z'. + */ + +//#define X_DUAL_STEPPER_DRIVERS +#if ENABLED(X_DUAL_STEPPER_DRIVERS) + #define INVERT_X2_VS_X_DIR true // Set 'true' if X motors should rotate in opposite directions + //#define X_DUAL_ENDSTOPS + #if ENABLED(X_DUAL_ENDSTOPS) + #define X2_USE_ENDSTOP _XMAX_ + #define X_DUAL_ENDSTOPS_ADJUSTMENT 0 + #endif +#endif + +//#define Y_DUAL_STEPPER_DRIVERS +#if ENABLED(Y_DUAL_STEPPER_DRIVERS) + #define INVERT_Y2_VS_Y_DIR true // Set 'true' if Y motors should rotate in opposite directions + //#define Y_DUAL_ENDSTOPS + #if ENABLED(Y_DUAL_ENDSTOPS) + #define Y2_USE_ENDSTOP _YMAX_ + #define Y_DUAL_ENDSTOPS_ADJUSTMENT 0 + #endif +#endif + +//#define Z_DUAL_STEPPER_DRIVERS +#if ENABLED(Z_DUAL_STEPPER_DRIVERS) + //#define Z_DUAL_ENDSTOPS + #if ENABLED(Z_DUAL_ENDSTOPS) + #define Z2_USE_ENDSTOP _XMAX_ + #define Z_DUAL_ENDSTOPS_ADJUSTMENT 0 + #endif +#endif + +// Enable this for dual x-carriage printers. +// A dual x-carriage design has the advantage that the inactive extruder can be parked which +// prevents hot-end ooze contaminating the print. It also reduces the weight of each x-carriage +// allowing faster printing speeds. Connect your X2 stepper to the first unused E plug. +#define DUAL_X_CARRIAGE +#if ENABLED(DUAL_X_CARRIAGE) + // Configuration for second X-carriage + // Note: the first x-carriage is defined as the x-carriage which homes to the minimum endstop; + // the second x-carriage always homes to the maximum endstop. + #define X2_MIN_POS 0 // set minimum to ensure second x-carriage doesn't hit the parked first X-carriage + #define X2_MAX_POS 442 // set maximum to the distance between toolheads when both heads are homed + #define X2_HOME_DIR 1 // the second X-carriage always homes to the maximum endstop position + #define X2_HOME_POS X2_MAX_POS // default home position is the maximum carriage position + // However: In this mode the HOTEND_OFFSET_X value for the second extruder provides a software + // override for X2_HOME_POS. This also allow recalibration of the distance between the two endstops + // without modifying the firmware (through the "M218 T1 X???" command). + // Remember: you should set the second extruder x-offset to 0 in your slicer. + + // There are a few selectable movement modes for dual x-carriages using M605 S + // Mode 0 (DXC_FULL_CONTROL_MODE): Full control. The slicer has full control over both x-carriages and can achieve optimal travel results + // as long as it supports dual x-carriages. (M605 S0) + // Mode 1 (DXC_AUTO_PARK_MODE) : Auto-park mode. The firmware will automatically park and unpark the x-carriages on tool changes so + // that additional slicer support is not required. (M605 S1) + // Mode 2 (DXC_DUPLICATION_MODE) : Duplication mode. The firmware will transparently make the second x-carriage and extruder copy all + // actions of the first x-carriage. This allows the printer to print 2 arbitrary items at + // once. (2nd extruder x offset and temp offset are set using: M605 S2 [Xnnn] [Rmmm]) + + // This is the default power-up mode which can be later using M605. + #define DEFAULT_DUAL_X_CARRIAGE_MODE DXC_AUTO_PARK_MODE + + // Default settings in "Auto-park Mode" + #define TOOLCHANGE_PARK_ZLIFT 0.2 // the distance to raise Z axis when parking an extruder + #define TOOLCHANGE_UNPARK_ZLIFT 1 // the distance to raise Z axis when unparking an extruder + + // Default x offset in duplication mode (typically set to half print bed width) + #define DEFAULT_DUPLICATION_X_OFFSET 200 + +#endif // DUAL_X_CARRIAGE + +// Activate a solenoid on the active extruder with M380. Disable all with M381. +// Define SOL0_PIN, SOL1_PIN, etc., for each extruder that has a solenoid. +//#define EXT_SOLENOID + +// @section homing + +// Homing hits each endstop, retracts by these distances, then does a slower bump. +#define X_HOME_BUMP_MM 5 +#define Y_HOME_BUMP_MM 5 +#define Z_HOME_BUMP_MM 2 +#define HOMING_BUMP_DIVISOR { 2, 2, 4 } // Re-Bump Speed Divisor (Divides the Homing Feedrate) +//#define QUICK_HOME // If homing includes X and Y, do a diagonal move initially + +// When G28 is called, this option will make Y home before X +//#define HOME_Y_BEFORE_X + +// Enable this if X or Y can't home without homing the other axis first. +//#define CODEPENDENT_XY_HOMING + +// @section machine + +#define AXIS_RELATIVE_MODES {false, false, false, false} + +// Allow duplication mode with a basic dual-nozzle extruder +//#define DUAL_NOZZLE_DUPLICATION_MODE + +// By default pololu step drivers require an active high signal. However, some high power drivers require an active low signal as step. +#define INVERT_X_STEP_PIN false +#define INVERT_Y_STEP_PIN false +#define INVERT_Z_STEP_PIN false +#define INVERT_E_STEP_PIN false + +// Default stepper release if idle. Set to 0 to deactivate. +// Steppers will shut down DEFAULT_STEPPER_DEACTIVE_TIME seconds after the last move when DISABLE_INACTIVE_? is true. +// Time can be set by M18 and M84. +#define DEFAULT_STEPPER_DEACTIVE_TIME 120 +#define DISABLE_INACTIVE_X true +#define DISABLE_INACTIVE_Y true +#define DISABLE_INACTIVE_Z true // set to false if the nozzle will fall down on your printed part when print has finished. +#define DISABLE_INACTIVE_E true + +#define DEFAULT_MINIMUMFEEDRATE 0.0 // minimum feedrate +#define DEFAULT_MINTRAVELFEEDRATE 0.0 + +//#define HOME_AFTER_DEACTIVATE // Require rehoming after steppers are deactivated + +// @section lcd + +#if ENABLED(ULTIPANEL) + #define MANUAL_FEEDRATE {50*60, 50*60, 4*60, 60} // Feedrates for manual moves along X, Y, Z, E from panel + #define ULTIPANEL_FEEDMULTIPLY // Comment to disable setting feedrate multiplier via encoder +#endif + +// @section extras + +// minimum time in microseconds that a movement needs to take if the buffer is emptied. +#define DEFAULT_MINSEGMENTTIME 20000 + +// If defined the movements slow down when the look ahead buffer is only half full +#define SLOWDOWN + +// Frequency limit +// See nophead's blog for more info +// Not working O +//#define XY_FREQUENCY_LIMIT 15 + +// Minimum planner junction speed. Sets the default minimum speed the planner plans for at the end +// of the buffer and all stops. This should not be much greater than zero and should only be changed +// if unwanted behavior is observed on a user's machine when running at very slow speeds. +#define MINIMUM_PLANNER_SPEED 0.05 // (mm/sec) + +// Microstep setting (Only functional when stepper driver microstep pins are connected to MCU. +#define MICROSTEP_MODES {16,16,16,16,16} // [1,2,4,8,16] + +/** + * @section stepper motor current + * + * Some boards have a means of setting the stepper motor current via firmware. + * + * The power on motor currents are set by: + * PWM_MOTOR_CURRENT - used by MINIRAMBO & ULTIMAIN_2 + * known compatible chips: A4982 + * DIGIPOT_MOTOR_CURRENT - used by BQ_ZUM_MEGA_3D, RAMBO & SCOOVO_X9H + * known compatible chips: AD5206 + * DAC_MOTOR_CURRENT_DEFAULT - used by PRINTRBOARD_REVF & RIGIDBOARD_V2 + * known compatible chips: MCP4728 + * DIGIPOT_I2C_MOTOR_CURRENTS - used by 5DPRINT, AZTEEG_X3_PRO, MIGHTYBOARD_REVE + * known compatible chips: MCP4451, MCP4018 + * + * Motor currents can also be set by M907 - M910 and by the LCD. + * M907 - applies to all. + * M908 - BQ_ZUM_MEGA_3D, RAMBO, PRINTRBOARD_REVF, RIGIDBOARD_V2 & SCOOVO_X9H + * M909, M910 & LCD - only PRINTRBOARD_REVF & RIGIDBOARD_V2 + */ +//#define PWM_MOTOR_CURRENT { 1300, 1300, 1250 } // Values in milliamps +//#define DIGIPOT_MOTOR_CURRENT { 135,135,135,135,135 } // Values 0-255 (RAMBO 135 = ~0.75A, 185 = ~1A) +//#define DAC_MOTOR_CURRENT_DEFAULT { 70, 80, 90, 80 } // Default drive percent - X, Y, Z, E axis + +// Use an I2C based DIGIPOT (e.g., Azteeg X3 Pro) +//#define DIGIPOT_I2C +#if ENABLED(DIGIPOT_I2C) && !defined(DIGIPOT_I2C_ADDRESS_A) + /** + * Common slave addresses: + * + * A (A shifted) B (B shifted) IC + * Smoothie 0x2C (0x58) 0x2D (0x5A) MCP4451 + * AZTEEG_X3_PRO 0x2C (0x58) 0x2E (0x5C) MCP4451 + * MIGHTYBOARD_REVE 0x2F (0x5E) MCP4018 + */ + #define DIGIPOT_I2C_ADDRESS_A 0x2C // unshifted slave address for first DIGIPOT + #define DIGIPOT_I2C_ADDRESS_B 0x2D // unshifted slave address for second DIGIPOT +#endif + +//#define DIGIPOT_MCP4018 // Requires library from https://github.com/stawel/SlowSoftI2CMaster +#define DIGIPOT_I2C_NUM_CHANNELS 8 // 5DPRINT: 4 AZTEEG_X3_PRO: 8 +// Actual motor currents in Amps. The number of entries must match DIGIPOT_I2C_NUM_CHANNELS. +// These correspond to the physical drivers, so be mindful if the order is changed. +#define DIGIPOT_I2C_MOTOR_CURRENTS { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 } // AZTEEG_X3_PRO + +//=========================================================================== +//=============================Additional Features=========================== +//=========================================================================== + +#define ENCODER_RATE_MULTIPLIER // If defined, certain menu edit operations automatically multiply the steps when the encoder is moved quickly +#define ENCODER_10X_STEPS_PER_SEC 75 // If the encoder steps per sec exceeds this value, multiply steps moved x10 to quickly advance the value +#define ENCODER_100X_STEPS_PER_SEC 160 // If the encoder steps per sec exceeds this value, multiply steps moved x100 to really quickly advance the value + +//#define CHDK 4 //Pin for triggering CHDK to take a picture see how to use it here http://captain-slow.dk/2014/03/09/3d-printing-timelapses/ +#define CHDK_DELAY 50 //How long in ms the pin should stay HIGH before going LOW again + +// @section lcd + +// Include a page of printer information in the LCD Main Menu +#define LCD_INFO_MENU + +// Leave out seldom-used LCD menu items to recover some Program Memory +//#define SLIM_LCD_MENUS + +// Scroll a longer status message into view +#define STATUS_MESSAGE_SCROLLING + +// On the Info Screen, display XY with one decimal place when possible +#define LCD_DECIMAL_SMALL_XY + +// The timeout (in ms) to return to the status screen from sub-menus +#define LCD_TIMEOUT_TO_STATUS 15000 + +// Add an 'M73' G-code to set the current percentage +#define LCD_SET_PROGRESS_MANUALLY + +#if ENABLED(SDSUPPORT) || ENABLED(LCD_SET_PROGRESS_MANUALLY) + //#define LCD_PROGRESS_BAR // Show a progress bar on HD44780 LCDs for SD printing + #if ENABLED(LCD_PROGRESS_BAR) + #define PROGRESS_BAR_BAR_TIME 2000 // (ms) Amount of time to show the bar + #define PROGRESS_BAR_MSG_TIME 3000 // (ms) Amount of time to show the status message + #define PROGRESS_MSG_EXPIRE 0 // (ms) Amount of time to retain the status message (0=forever) + //#define PROGRESS_MSG_ONCE // Show the message for MSG_TIME then clear it + //#define LCD_PROGRESS_BAR_TEST // Add a menu item to test the progress bar + #endif +#endif // SDSUPPORT || LCD_SET_PROGRESS_MANUALLY + +/** + * LED Control Menu + * Enable this feature to add LED Control to the LCD menu + */ +//#define LED_CONTROL_MENU +#if ENABLED(LED_CONTROL_MENU) + #define LED_COLOR_PRESETS // Enable the Preset Color menu option + #if ENABLED(LED_COLOR_PRESETS) + #define LED_USER_PRESET_RED 255 // User defined RED value + #define LED_USER_PRESET_GREEN 128 // User defined GREEN value + #define LED_USER_PRESET_BLUE 0 // User defined BLUE value + #define LED_USER_PRESET_WHITE 255 // User defined WHITE value + #define LED_USER_PRESET_BRIGHTNESS 255 // User defined intensity + //#define LED_USER_PRESET_STARTUP // Have the printer display the user preset color on startup + #endif +#endif // LED_CONTROL_MENU + +#if ENABLED(SDSUPPORT) + + // Some RAMPS and other boards don't detect when an SD card is inserted. You can work + // around this by connecting a push button or single throw switch to the pin defined + // as SD_DETECT_PIN in your board's pins definitions. + // This setting should be disabled unless you are using a push button, pulling the pin to ground. + // Note: This is always disabled for ULTIPANEL (except ELB_FULL_GRAPHIC_CONTROLLER). + #define SD_DETECT_INVERTED + + #define SD_FINISHED_STEPPERRELEASE true // Disable steppers when SD Print is finished + #define SD_FINISHED_RELEASECOMMAND "M84 X Y Z E" // You might want to keep the z enabled so your bed stays in place. + + // Reverse SD sort to show "more recent" files first, according to the card's FAT. + // Since the FAT gets out of order with usage, SDCARD_SORT_ALPHA is recommended. + #define SDCARD_RATHERRECENTFIRST + + // Add an option in the menu to run all auto#.g files + //#define MENU_ADDAUTOSTART + + /** + * Sort SD file listings in alphabetical order. + * + * With this option enabled, items on SD cards will be sorted + * by name for easier navigation. + * + * By default... + * + * - Use the slowest -but safest- method for sorting. + * - Folders are sorted to the top. + * - The sort key is statically allocated. + * - No added G-code (M34) support. + * - 40 item sorting limit. (Items after the first 40 are unsorted.) + * + * SD sorting uses static allocation (as set by SDSORT_LIMIT), allowing the + * compiler to calculate the worst-case usage and throw an error if the SRAM + * limit is exceeded. + * + * - SDSORT_USES_RAM provides faster sorting via a static directory buffer. + * - SDSORT_USES_STACK does the same, but uses a local stack-based buffer. + * - SDSORT_CACHE_NAMES will retain the sorted file listing in RAM. (Expensive!) + * - SDSORT_DYNAMIC_RAM only uses RAM when the SD menu is visible. (Use with caution!) + */ + #define SDCARD_SORT_ALPHA + + // SD Card Sorting options + #if ENABLED(SDCARD_SORT_ALPHA) + #define SDSORT_LIMIT 40 // Maximum number of sorted items (10-256). Costs 27 bytes each. + #define FOLDER_SORTING -1 // -1=above 0=none 1=below + #define SDSORT_GCODE false // Allow turning sorting on/off with LCD and M34 g-code. + #define SDSORT_USES_RAM false // Pre-allocate a static array for faster pre-sorting. + #define SDSORT_USES_STACK false // Prefer the stack for pre-sorting to give back some SRAM. (Negated by next 2 options.) + #define SDSORT_CACHE_NAMES false // Keep sorted items in RAM longer for speedy performance. Most expensive option. + #define SDSORT_DYNAMIC_RAM false // Use dynamic allocation (within SD menus). Least expensive option. Set SDSORT_LIMIT before use! + #define SDSORT_CACHE_VFATS 2 // Maximum number of 13-byte VFAT entries to use for sorting. + // Note: Only affects SCROLL_LONG_FILENAMES with SDSORT_CACHE_NAMES but not SDSORT_DYNAMIC_RAM. + #endif + + // This allows hosts to request long names for files and folders with M33 + //#define LONG_FILENAME_HOST_SUPPORT + + // Enable this option to scroll long filenames in the SD card menu + #define SCROLL_LONG_FILENAMES + + /** + * This option allows you to abort SD printing when any endstop is triggered. + * This feature must be enabled with "M540 S1" or from the LCD menu. + * To have any effect, endstops must be enabled during SD printing. + */ + //#define ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED + + /** + * This option makes it easier to print the same SD Card file again. + * On print completion the LCD Menu will open with the file selected. + * You can just click to start the print, or navigate elsewhere. + */ + //#define SD_REPRINT_LAST_SELECTED_FILE + + /** + * Auto-report SdCard status with M27 S + */ + //#define AUTO_REPORT_SD_STATUS + +#endif // SDSUPPORT + +/** + * Additional options for Graphical Displays + * + * Use the optimizations here to improve printing performance, + * which can be adversely affected by graphical display drawing, + * especially when doing several short moves, and when printing + * on DELTA and SCARA machines. + * + * Some of these options may result in the display lagging behind + * controller events, as there is a trade-off between reliable + * printing performance versus fast display updates. + */ +#if ENABLED(DOGLCD) + // Show SD percentage next to the progress bar + //#define DOGM_SD_PERCENT + + // Enable to save many cycles by drawing a hollow frame on the Info Screen + #define XYZ_HOLLOW_FRAME + + // Enable to save many cycles by drawing a hollow frame on Menu Screens + #define MENU_HOLLOW_FRAME + + // A bigger font is available for edit items. Costs 3120 bytes of PROGMEM. + // Western only. Not available for Cyrillic, Kana, Turkish, Greek, or Chinese. + //#define USE_BIG_EDIT_FONT + + // A smaller font may be used on the Info Screen. Costs 2300 bytes of PROGMEM. + // Western only. Not available for Cyrillic, Kana, Turkish, Greek, or Chinese. + //#define USE_SMALL_INFOFONT + + // Enable this option and reduce the value to optimize screen updates. + // The normal delay is 10µs. Use the lowest value that still gives a reliable display. + //#define DOGM_SPI_DELAY_US 5 + + // Swap the CW/CCW indicators in the graphics overlay + //#define OVERLAY_GFX_REVERSE + + #if ENABLED(U8GLIB_ST7920) + /** + * ST7920-based LCDs can emulate a 16 x 4 character display using + * the ST7920 character-generator for very fast screen updates. + * Enable LIGHTWEIGHT_UI to use this special display mode. + * + * Since LIGHTWEIGHT_UI has limited space, the position and status + * message occupy the same line. Set STATUS_EXPIRE_SECONDS to the + * length of time to display the status message before clearing. + * + * Set STATUS_EXPIRE_SECONDS to zero to never clear the status. + * This will prevent position updates from being displayed. + */ + //#define LIGHTWEIGHT_UI + #if ENABLED(LIGHTWEIGHT_UI) + #define STATUS_EXPIRE_SECONDS 20 + #endif + #endif + +#endif // DOGLCD + +// @section safety + +// The hardware watchdog should reset the microcontroller disabling all outputs, +// in case the firmware gets stuck and doesn't do temperature regulation. +#define USE_WATCHDOG + +#if ENABLED(USE_WATCHDOG) + // If you have a watchdog reboot in an ArduinoMega2560 then the device will hang forever, as a watchdog reset will leave the watchdog on. + // The "WATCHDOG_RESET_MANUAL" goes around this by not using the hardware reset. + // However, THIS FEATURE IS UNSAFE!, as it will only work if interrupts are disabled. And the code could hang in an interrupt routine with interrupts disabled. + //#define WATCHDOG_RESET_MANUAL +#endif + +// @section lcd + +/** + * Babystepping enables movement of the axes by tiny increments without changing + * the current position values. This feature is used primarily to adjust the Z + * axis in the first layer of a print in real-time. + * + * Warning: Does not respect endstops! + */ +#define BABYSTEPPING +#if ENABLED(BABYSTEPPING) + //#define BABYSTEP_XY // Also enable X/Y Babystepping. Not supported on DELTA! + #define BABYSTEP_INVERT_Z false // Change if Z babysteps should go the other way + #define BABYSTEP_MULTIPLICATOR 1 // Babysteps are very small. Increase for faster motion. + #define BABYSTEP_ZPROBE_OFFSET // Enable to combine M851 and Babystepping + #define DOUBLECLICK_FOR_Z_BABYSTEPPING // Double-click on the Status Screen for Z Babystepping. + #define DOUBLECLICK_MAX_INTERVAL 1250 // Maximum interval between clicks, in milliseconds. + // Note: Extra time may be added to mitigate controller latency. + #define BABYSTEP_ZPROBE_GFX_OVERLAY // Enable graphical overlay on Z-offset editor +#endif + +// @section extruder + +/** + * Linear Pressure Control v1.5 + * + * Assumption: advance [steps] = k * (delta velocity [steps/s]) + * K=0 means advance disabled. + * + * NOTE: K values for LIN_ADVANCE 1.5 differ from earlier versions! + * + * Set K around 0.22 for 3mm PLA Direct Drive with ~6.5cm between the drive gear and heatbreak. + * Larger K values will be needed for flexible filament and greater distances. + * If this algorithm produces a higher speed offset than the extruder can handle (compared to E jerk) + * print acceleration will be reduced during the affected moves to keep within the limit. + * + * See http://marlinfw.org/docs/features/lin_advance.html for full instructions. + * Mention @Sebastianv650 on GitHub to alert the author of any issues. + */ +//#define LIN_ADVANCE +#if ENABLED(LIN_ADVANCE) + #define LIN_ADVANCE_K 0.22 // Unit: mm compression per 1mm/s extruder speed + //#define LA_DEBUG // If enabled, this will generate debug information output over USB. +#endif + +// @section leveling + +#if ENABLED(MESH_BED_LEVELING) || ENABLED(AUTO_BED_LEVELING_UBL) + // Override the mesh area if the automatic (max) area is too large + //#define MESH_MIN_X 50 + //#define MESH_MIN_Y 0 + //#define MESH_MAX_X X_BED_SIZE - (50) + //#define MESH_MAX_Y Y_BED_SIZE - (0) +#endif + +// @section extras + +// +// G2/G3 Arc Support +// +#define ARC_SUPPORT // Disable this feature to save ~3226 bytes +#if ENABLED(ARC_SUPPORT) + #define MM_PER_ARC_SEGMENT 1 // Length of each arc segment + #define N_ARC_CORRECTION 25 // Number of intertpolated segments between corrections + //#define ARC_P_CIRCLES // Enable the 'P' parameter to specify complete circles + //#define CNC_WORKSPACE_PLANES // Allow G2/G3 to operate in XY, ZX, or YZ planes +#endif + +// Support for G5 with XYZE destination and IJPQ offsets. Requires ~2666 bytes. +//#define BEZIER_CURVE_SUPPORT + +// G38.2 and G38.3 Probe Target +// Set MULTIPLE_PROBING if you want G38 to double touch +//#define G38_PROBE_TARGET +#if ENABLED(G38_PROBE_TARGET) + #define G38_MINIMUM_MOVE 0.0275 // minimum distance in mm that will produce a move (determined using the print statement in check_move) +#endif + +// Moves (or segments) with fewer steps than this will be joined with the next move +#define MIN_STEPS_PER_SEGMENT 6 + +// The minimum pulse width (in µs) for stepping a stepper. +// Set this if you find stepping unreliable, or if using a very fast CPU. +// 0 is OK for AVR, 0 is OK for A4989 drivers, 2 is needed for DRV8825 drivers +#define MINIMUM_STEPPER_PULSE 2 + +// @section temperature + +// Control heater 0 and heater 1 in parallel. +//#define HEATERS_PARALLEL + +//=========================================================================== +//================================= Buffers ================================= +//=========================================================================== + +// @section hidden + +// The number of linear motions that can be in the plan at any give time. +// THE BLOCK_BUFFER_SIZE NEEDS TO BE A POWER OF 2 (e.g. 8, 16, 32) because shifts and ors are used to do the ring-buffering. +#if ENABLED(SDSUPPORT) + #define BLOCK_BUFFER_SIZE 16 // SD,LCD,Buttons take more memory, block buffer needs to be smaller +#else + #define BLOCK_BUFFER_SIZE 16 // maximize block buffer +#endif + +// @section serial + +// The ASCII buffer for serial input +#define MAX_CMD_SIZE 96 +#define BUFSIZE 4 + +// Transmission to Host Buffer Size +// To save 386 bytes of PROGMEM (and TX_BUFFER_SIZE+3 bytes of RAM) set to 0. +// To buffer a simple "ok" you need 4 bytes. +// For ADVANCED_OK (M105) you need 32 bytes. +// For debug-echo: 128 bytes for the optimal speed. +// Other output doesn't need to be that speedy. +// :[0, 2, 4, 8, 16, 32, 64, 128, 256] +#define TX_BUFFER_SIZE 0 + +// Host Receive Buffer Size +// Without XON/XOFF flow control (see SERIAL_XON_XOFF below) 32 bytes should be enough. +// To use flow control, set this buffer size to at least 1024 bytes. +// :[0, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048] +//#define RX_BUFFER_SIZE 1024 + +#if RX_BUFFER_SIZE >= 1024 + // Enable to have the controller send XON/XOFF control characters to + // the host to signal the RX buffer is becoming full. + //#define SERIAL_XON_XOFF +#endif + +#if ENABLED(SDSUPPORT) + // Enable this option to collect and display the maximum + // RX queue usage after transferring a file to SD. + //#define SERIAL_STATS_MAX_RX_QUEUED + + // Enable this option to collect and display the number + // of dropped bytes after a file transfer to SD. + //#define SERIAL_STATS_DROPPED_RX +#endif + +// Enable an emergency-command parser to intercept certain commands as they +// enter the serial receive buffer, so they cannot be blocked. +// Currently handles M108, M112, M410 +// Does not work on boards using AT90USB (USBCON) processors! +#define EMERGENCY_PARSER + +// Bad Serial-connections can miss a received command by sending an 'ok' +// Therefore some clients abort after 30 seconds in a timeout. +// Some other clients start sending commands while receiving a 'wait'. +// This "wait" is only sent when the buffer is empty. 1 second is a good value here. +//#define NO_TIMEOUTS 1000 // Milliseconds + +// Some clients will have this feature soon. This could make the NO_TIMEOUTS unnecessary. +#define ADVANCED_OK + +// @section extras + +/** + * Firmware-based and LCD-controlled retract + * + * Add G10 / G11 commands for automatic firmware-based retract / recover. + * Use M207 and M208 to define parameters for retract / recover. + * + * Use M209 to enable or disable auto-retract. + * With auto-retract enabled, all G1 E moves within the set range + * will be converted to firmware-based retract/recover moves. + * + * Be sure to turn off auto-retract during filament change. + * + * Note that M207 / M208 / M209 settings are saved to EEPROM. + * + */ +//#define FWRETRACT // ONLY PARTIALLY TESTED +#if ENABLED(FWRETRACT) + #define MIN_AUTORETRACT 0.1 // When auto-retract is on, convert E moves of this length and over + #define MAX_AUTORETRACT 10.0 // Upper limit for auto-retract conversion + #define RETRACT_LENGTH 3 // Default retract length (positive mm) + #define RETRACT_LENGTH_SWAP 13 // Default swap retract length (positive mm), for extruder change + #define RETRACT_FEEDRATE 45 // Default feedrate for retracting (mm/s) + #define RETRACT_ZLIFT 0 // Default retract Z-lift + #define RETRACT_RECOVER_LENGTH 0 // Default additional recover length (mm, added to retract length when recovering) + #define RETRACT_RECOVER_LENGTH_SWAP 0 // Default additional swap recover length (mm, added to retract length when recovering from extruder change) + #define RETRACT_RECOVER_FEEDRATE 8 // Default feedrate for recovering from retraction (mm/s) + #define RETRACT_RECOVER_FEEDRATE_SWAP 8 // Default feedrate for recovering from swap retraction (mm/s) +#endif + +/** + * Extra Fan Speed + * Adds a secondary fan speed for each print-cooling fan. + * 'M106 P T3-255' : Set a secondary speed for + * 'M106 P T2' : Use the set secondary speed + * 'M106 P T1' : Restore the previous fan speed + */ +//#define EXTRA_FAN_SPEED + +/** + * Advanced Pause + * Experimental feature for filament change support and for parking the nozzle when paused. + * Adds the GCode M600 for initiating filament change. + * If PARK_HEAD_ON_PAUSE enabled, adds the GCode M125 to pause printing and park the nozzle. + * + * Requires an LCD display. + * Requires NOZZLE_PARK_FEATURE. + * This feature is required for the default FILAMENT_RUNOUT_SCRIPT. + */ +#define ADVANCED_PAUSE_FEATURE +#if ENABLED(ADVANCED_PAUSE_FEATURE) + #define PAUSE_PARK_RETRACT_FEEDRATE 60 // (mm/s) Initial retract feedrate. + #define PAUSE_PARK_RETRACT_LENGTH 2 // (mm) Initial retract. + // This short retract is done immediately, before parking the nozzle. + #define FILAMENT_CHANGE_UNLOAD_FEEDRATE 10 // (mm/s) Unload filament feedrate. This can be pretty fast. + #define FILAMENT_CHANGE_UNLOAD_LENGTH 25 // (mm) The length of filament for a complete unload. + // For Bowden, the full length of the tube and nozzle. + // For direct drive, the full length of the nozzle. + // Set to 0 for manual unloading. + #define FILAMENT_CHANGE_LOAD_FEEDRATE 10 // (mm/s) Load filament feedrate. This can be pretty fast. + #define FILAMENT_CHANGE_LOAD_LENGTH 0 // (mm) Load length of filament, from extruder gear to nozzle. + // For Bowden, the full length of the tube and nozzle. + // For direct drive, the full length of the nozzle. + #define ADVANCED_PAUSE_EXTRUDE_FEEDRATE 5 // (mm/s) Extrude feedrate (after loading). Should be slower than load feedrate. + #define ADVANCED_PAUSE_EXTRUDE_LENGTH 25 // (mm) Length to extrude after loading. + // Set to 0 for manual extrusion. + // Filament can be extruded repeatedly from the Filament Change menu + // until extrusion is consistent, and to purge old filament. + + // Filament Unload does a Retract, Delay, and Purge first: + #define FILAMENT_UNLOAD_RETRACT_LENGTH 13 // (mm) Unload initial retract length. + #define FILAMENT_UNLOAD_DELAY 5000 // (ms) Delay for the filament to cool after retract. + #define FILAMENT_UNLOAD_PURGE_LENGTH 8 // (mm) An unretract is done, then this length is purged. + + #define PAUSE_PARK_NOZZLE_TIMEOUT 45 // (seconds) Time limit before the nozzle is turned off for safety. + #define FILAMENT_CHANGE_ALERT_BEEPS 2 // Number of alert beeps to play when a response is needed. + #define PAUSE_PARK_NO_STEPPER_TIMEOUT // Enable for XYZ steppers to stay powered on during filament change. + + #define PARK_HEAD_ON_PAUSE // Park the nozzle during pause and filament change. + #define HOME_BEFORE_FILAMENT_CHANGE // Ensure homing has been completed prior to parking for filament change + + //#define FILAMENT_LOAD_UNLOAD_GCODES // Add M701/M702 Load/Unload G-codes, plus Load/Unload in the LCD Prepare menu. + //#define FILAMENT_UNLOAD_ALL_EXTRUDERS // Allow M702 to unload all extruders above a minimum target temp (as set by M302) +#endif + +// @section tmc + +/** + * Enable this section if you have TMC26X motor drivers. + * You will need to import the TMC26XStepper library into the Arduino IDE for this + * (https://github.com/trinamic/TMC26XStepper.git) + */ +//#define HAVE_TMC26X +#if ENABLED(HAVE_TMC26X) // Choose your axes here. This is mandatory! + //#define X_IS_TMC26X + //#define X2_IS_TMC26X + //#define Y_IS_TMC26X + //#define Y2_IS_TMC26X + //#define Z_IS_TMC26X + //#define Z2_IS_TMC26X + //#define E0_IS_TMC26X + //#define E1_IS_TMC26X + //#define E2_IS_TMC26X + //#define E3_IS_TMC26X + //#define E4_IS_TMC26X + + #define X_MAX_CURRENT 1000 // in mA + #define X_SENSE_RESISTOR 91 // in mOhms + #define X_MICROSTEPS 16 // number of microsteps + + #define X2_MAX_CURRENT 1000 + #define X2_SENSE_RESISTOR 91 + #define X2_MICROSTEPS 16 + + #define Y_MAX_CURRENT 1000 + #define Y_SENSE_RESISTOR 91 + #define Y_MICROSTEPS 16 + + #define Y2_MAX_CURRENT 1000 + #define Y2_SENSE_RESISTOR 91 + #define Y2_MICROSTEPS 16 + + #define Z_MAX_CURRENT 1000 + #define Z_SENSE_RESISTOR 91 + #define Z_MICROSTEPS 16 + + #define Z2_MAX_CURRENT 1000 + #define Z2_SENSE_RESISTOR 91 + #define Z2_MICROSTEPS 16 + + #define E0_MAX_CURRENT 1000 + #define E0_SENSE_RESISTOR 91 + #define E0_MICROSTEPS 16 + + #define E1_MAX_CURRENT 1000 + #define E1_SENSE_RESISTOR 91 + #define E1_MICROSTEPS 16 + + #define E2_MAX_CURRENT 1000 + #define E2_SENSE_RESISTOR 91 + #define E2_MICROSTEPS 16 + + #define E3_MAX_CURRENT 1000 + #define E3_SENSE_RESISTOR 91 + #define E3_MICROSTEPS 16 + + #define E4_MAX_CURRENT 1000 + #define E4_SENSE_RESISTOR 91 + #define E4_MICROSTEPS 16 + +#endif + +// @section tmc_smart + +/** + * Enable this for SilentStepStick Trinamic TMC2130 SPI-configurable stepper drivers. + * + * You'll also need the TMC2130Stepper Arduino library + * (https://github.com/teemuatlut/TMC2130Stepper). + * + * To use TMC2130 stepper drivers in SPI mode connect your SPI pins to + * the hardware SPI interface on your board and define the required CS pins + * in your `pins_MYBOARD.h` file. (e.g., RAMPS 1.4 uses AUX3 pins `X_CS_PIN 53`, `Y_CS_PIN 49`, etc.). + * You may also use software SPI if you wish to use general purpose IO pins. + */ +//#define HAVE_TMC2130 +#if ENABLED(HAVE_TMC2130) // Choose your axes here. This is mandatory! + //#define X_IS_TMC2130 + //#define X2_IS_TMC2130 + //#define Y_IS_TMC2130 + //#define Y2_IS_TMC2130 + //#define Z_IS_TMC2130 + //#define Z2_IS_TMC2130 + //#define E0_IS_TMC2130 + //#define E1_IS_TMC2130 + //#define E2_IS_TMC2130 + //#define E3_IS_TMC2130 + //#define E4_IS_TMC2130 +#endif + +/** + * Enable this for SilentStepStick Trinamic TMC2208 UART-configurable stepper drivers. + * Connect #_SERIAL_TX_PIN to the driver side PDN_UART pin with a 1K resistor. + * To use the reading capabilities, also connect #_SERIAL_RX_PIN + * to PDN_UART without a resistor. + * The drivers can also be used with hardware serial. + * + * You'll also need the TMC2208Stepper Arduino library + * (https://github.com/teemuatlut/TMC2208Stepper). + */ +//#define HAVE_TMC2208 +#if ENABLED(HAVE_TMC2208) // Choose your axes here. This is mandatory! + //#define X_IS_TMC2208 + //#define X2_IS_TMC2208 + //#define Y_IS_TMC2208 + //#define Y2_IS_TMC2208 + //#define Z_IS_TMC2208 + //#define Z2_IS_TMC2208 + //#define E0_IS_TMC2208 + //#define E1_IS_TMC2208 + //#define E2_IS_TMC2208 + //#define E3_IS_TMC2208 + //#define E4_IS_TMC2208 +#endif + +#if ENABLED(HAVE_TMC2130) || ENABLED(HAVE_TMC2208) + + #define R_SENSE 0.11 // R_sense resistor for SilentStepStick2130 + #define HOLD_MULTIPLIER 0.5 // Scales down the holding current from run current + #define INTERPOLATE true // Interpolate X/Y/Z_MICROSTEPS to 256 + + #define X_CURRENT 800 // rms current in mA. Multiply by 1.41 for peak current. + #define X_MICROSTEPS 16 // 0..256 + + #define Y_CURRENT 800 + #define Y_MICROSTEPS 16 + + #define Z_CURRENT 800 + #define Z_MICROSTEPS 16 + + #define X2_CURRENT 800 + #define X2_MICROSTEPS 16 + + #define Y2_CURRENT 800 + #define Y2_MICROSTEPS 16 + + #define Z2_CURRENT 800 + #define Z2_MICROSTEPS 16 + + #define E0_CURRENT 800 + #define E0_MICROSTEPS 16 + + #define E1_CURRENT 800 + #define E1_MICROSTEPS 16 + + #define E2_CURRENT 800 + #define E2_MICROSTEPS 16 + + #define E3_CURRENT 800 + #define E3_MICROSTEPS 16 + + #define E4_CURRENT 800 + #define E4_MICROSTEPS 16 + + /** + * Use software SPI for TMC2130. + * The default SW SPI pins are defined the respective pins files, + * but you can override or define them here. + */ + //#define TMC_USE_SW_SPI + //#define TMC_SW_MOSI -1 + //#define TMC_SW_MISO -1 + //#define TMC_SW_SCK -1 + + /** + * Use Trinamic's ultra quiet stepping mode. + * When disabled, Marlin will use spreadCycle stepping mode. + */ + #define STEALTHCHOP + + /** + * Monitor Trinamic TMC2130 and TMC2208 drivers for error conditions, + * like overtemperature and short to ground. TMC2208 requires hardware serial. + * In the case of overtemperature Marlin can decrease the driver current until error condition clears. + * Other detected conditions can be used to stop the current print. + * Relevant g-codes: + * M906 - Set or get motor current in milliamps using axis codes X, Y, Z, E. Report values if no axis codes given. + * M911 - Report stepper driver overtemperature pre-warn condition. + * M912 - Clear stepper driver overtemperature pre-warn condition flag. + * M122 S0/1 - Report driver parameters (Requires TMC_DEBUG) + */ + //#define MONITOR_DRIVER_STATUS + + #if ENABLED(MONITOR_DRIVER_STATUS) + #define CURRENT_STEP_DOWN 50 // [mA] + #define REPORT_CURRENT_CHANGE + #define STOP_ON_ERROR + #endif + + /** + * The driver will switch to spreadCycle when stepper speed is over HYBRID_THRESHOLD. + * This mode allows for faster movements at the expense of higher noise levels. + * STEALTHCHOP needs to be enabled. + * M913 X/Y/Z/E to live tune the setting + */ + //#define HYBRID_THRESHOLD + + #define X_HYBRID_THRESHOLD 100 // [mm/s] + #define X2_HYBRID_THRESHOLD 100 + #define Y_HYBRID_THRESHOLD 100 + #define Y2_HYBRID_THRESHOLD 100 + #define Z_HYBRID_THRESHOLD 3 + #define Z2_HYBRID_THRESHOLD 3 + #define E0_HYBRID_THRESHOLD 30 + #define E1_HYBRID_THRESHOLD 30 + #define E2_HYBRID_THRESHOLD 30 + #define E3_HYBRID_THRESHOLD 30 + #define E4_HYBRID_THRESHOLD 30 + + /** + * Use stallGuard2 to sense an obstacle and trigger an endstop. + * You need to place a wire from the driver's DIAG1 pin to the X/Y endstop pin. + * X, Y, and Z homing will always be done in spreadCycle mode. + * + * X/Y/Z_HOMING_SENSITIVITY is used for tuning the trigger sensitivity. + * Higher values make the system LESS sensitive. + * Lower value make the system MORE sensitive. + * Too low values can lead to false positives, while too high values will collide the axis without triggering. + * It is advised to set X/Y/Z_HOME_BUMP_MM to 0. + * M914 X/Y/Z to live tune the setting + */ + //#define SENSORLESS_HOMING // TMC2130 only + + #if ENABLED(SENSORLESS_HOMING) + #define X_HOMING_SENSITIVITY 8 + #define Y_HOMING_SENSITIVITY 8 + #define Z_HOMING_SENSITIVITY 8 + #endif + + /** + * Enable M122 debugging command for TMC stepper drivers. + * M122 S0/1 will enable continous reporting. + */ + //#define TMC_DEBUG + + /** + * M915 Z Axis Calibration + * + * - Adjust Z stepper current, + * - Drive the Z axis to its physical maximum, and + * - Home Z to account for the lost steps. + * + * Use M915 Snn to specify the current. + * Use M925 Znn to add extra Z height to Z_MAX_POS. + */ + //#define TMC_Z_CALIBRATION + #if ENABLED(TMC_Z_CALIBRATION) + #define CALIBRATION_CURRENT 250 + #define CALIBRATION_EXTRA_HEIGHT 10 + #endif + + /** + * You can set your own advanced settings by filling in predefined functions. + * A list of available functions can be found on the library github page + * https://github.com/teemuatlut/TMC2130Stepper + * https://github.com/teemuatlut/TMC2208Stepper + * + * Example: + * #define TMC_ADV() { \ + * stepperX.diag0_temp_prewarn(1); \ + * stepperY.interpolate(0); \ + * } + */ + #define TMC_ADV() { } + +#endif // TMC2130 || TMC2208 + +// @section L6470 + +/** + * Enable this section if you have L6470 motor drivers. + * You need to import the L6470 library into the Arduino IDE for this. + * (https://github.com/ameyer/Arduino-L6470) + */ + +//#define HAVE_L6470DRIVER +#if ENABLED(HAVE_L6470DRIVER) + + //#define X_IS_L6470 + //#define X2_IS_L6470 + //#define Y_IS_L6470 + //#define Y2_IS_L6470 + //#define Z_IS_L6470 + //#define Z2_IS_L6470 + //#define E0_IS_L6470 + //#define E1_IS_L6470 + //#define E2_IS_L6470 + //#define E3_IS_L6470 + //#define E4_IS_L6470 + + #define X_MICROSTEPS 16 // number of microsteps + #define X_OVERCURRENT 2000 // maxc current in mA. If the current goes over this value, the driver will switch off + #define X_STALLCURRENT 1500 // current in mA where the driver will detect a stall + + #define X2_MICROSTEPS 16 + #define X2_OVERCURRENT 2000 + #define X2_STALLCURRENT 1500 + + #define Y_MICROSTEPS 16 + #define Y_OVERCURRENT 2000 + #define Y_STALLCURRENT 1500 + + #define Y2_MICROSTEPS 16 + #define Y2_OVERCURRENT 2000 + #define Y2_STALLCURRENT 1500 + + #define Z_MICROSTEPS 16 + #define Z_OVERCURRENT 2000 + #define Z_STALLCURRENT 1500 + + #define Z2_MICROSTEPS 16 + #define Z2_OVERCURRENT 2000 + #define Z2_STALLCURRENT 1500 + + #define E0_MICROSTEPS 16 + #define E0_OVERCURRENT 2000 + #define E0_STALLCURRENT 1500 + + #define E1_MICROSTEPS 16 + #define E1_OVERCURRENT 2000 + #define E1_STALLCURRENT 1500 + + #define E2_MICROSTEPS 16 + #define E2_OVERCURRENT 2000 + #define E2_STALLCURRENT 1500 + + #define E3_MICROSTEPS 16 + #define E3_OVERCURRENT 2000 + #define E3_STALLCURRENT 1500 + + #define E4_MICROSTEPS 16 + #define E4_OVERCURRENT 2000 + #define E4_STALLCURRENT 1500 + +#endif + +/** + * TWI/I2C BUS + * + * This feature is an EXPERIMENTAL feature so it shall not be used on production + * machines. Enabling this will allow you to send and receive I2C data from slave + * devices on the bus. + * + * ; Example #1 + * ; This macro send the string "Marlin" to the slave device with address 0x63 (99) + * ; It uses multiple M260 commands with one B arg + * M260 A99 ; Target slave address + * M260 B77 ; M + * M260 B97 ; a + * M260 B114 ; r + * M260 B108 ; l + * M260 B105 ; i + * M260 B110 ; n + * M260 S1 ; Send the current buffer + * + * ; Example #2 + * ; Request 6 bytes from slave device with address 0x63 (99) + * M261 A99 B5 + * + * ; Example #3 + * ; Example serial output of a M261 request + * echo:i2c-reply: from:99 bytes:5 data:hello + */ + +// @section i2cbus + +//#define EXPERIMENTAL_I2CBUS +#define I2C_SLAVE_ADDRESS 0 // Set a value from 8 to 127 to act as a slave + +// @section extras + +/** + * Spindle & Laser control + * + * Add the M3, M4, and M5 commands to turn the spindle/laser on and off, and + * to set spindle speed, spindle direction, and laser power. + * + * SuperPid is a router/spindle speed controller used in the CNC milling community. + * Marlin can be used to turn the spindle on and off. It can also be used to set + * the spindle speed from 5,000 to 30,000 RPM. + * + * You'll need to select a pin for the ON/OFF function and optionally choose a 0-5V + * hardware PWM pin for the speed control and a pin for the rotation direction. + * + * See http://marlinfw.org/docs/configuration/laser_spindle.html for more config details. + */ +//#define SPINDLE_LASER_ENABLE +#if ENABLED(SPINDLE_LASER_ENABLE) +#define SPINDLE_LASER_ENABLE_PIN 0 + #define SPINDLE_LASER_ENABLE_INVERT false // set to "true" if the on/off function is reversed + #define SPINDLE_LASER_PWM false // set to true if your controller supports setting the speed/power + #define SPINDLE_LASER_PWM_INVERT false // set to "true" if the speed/power goes up when you want it to go slower + #define SPINDLE_LASER_POWERUP_DELAY 5 // delay in milliseconds to allow the spindle/laser to come up to speed/power + #define SPINDLE_LASER_POWERDOWN_DELAY 5 // delay in milliseconds to allow the spindle to stop + #define SPINDLE_DIR_CHANGE false // set to true if your spindle controller supports changing spindle direction + #define SPINDLE_INVERT_DIR false + #define SPINDLE_STOP_ON_DIR_CHANGE true // set to true if Marlin should stop the spindle before changing rotation direction + + /** + * The M3 & M4 commands use the following equation to convert PWM duty cycle to speed/power + * + * SPEED/POWER = PWM duty cycle * SPEED_POWER_SLOPE + SPEED_POWER_INTERCEPT + * where PWM duty cycle varies from 0 to 255 + * + * set the following for your controller (ALL MUST BE SET) + */ + + #define SPEED_POWER_SLOPE 118.4 + #define SPEED_POWER_INTERCEPT 0 + #define SPEED_POWER_MIN 5000 + #define SPEED_POWER_MAX 30000 // SuperPID router controller 0 - 30,000 RPM + + //#define SPEED_POWER_SLOPE 0.3922 + //#define SPEED_POWER_INTERCEPT 0 + //#define SPEED_POWER_MIN 10 + //#define SPEED_POWER_MAX 100 // 0-100% +#endif + + +#define FAN_AS_LASER +#if ENABLED(FAN_AS_LASER) + #define FAN_NUM_AS_LASER 1 +#endif +/** + * Filament Width Sensor + * + * Measures the filament width in real-time and adjusts + * flow rate to compensate for any irregularities. + * + * Also allows the measured filament diameter to set the + * extrusion rate, so the slicer only has to specify the + * volume. + * + * Only a single extruder is supported at this time. + * + * 34 RAMPS_14 : Analog input 5 on the AUX2 connector + * 81 PRINTRBOARD : Analog input 2 on the Exp1 connector (version B,C,D,E) + * 301 RAMBO : Analog input 3 + * + * Note: May require analog pins to be defined for other boards. + */ +//#define FILAMENT_WIDTH_SENSOR + +#if ENABLED(FILAMENT_WIDTH_SENSOR) + #define FILAMENT_SENSOR_EXTRUDER_NUM 0 // Index of the extruder that has the filament sensor. :[0,1,2,3,4] + #define MEASUREMENT_DELAY_CM 14 // (cm) The distance from the filament sensor to the melting chamber + + #define FILWIDTH_ERROR_MARGIN 1.0 // (mm) If a measurement differs too much from nominal width ignore it + #define MAX_MEASUREMENT_DELAY 20 // (bytes) Buffer size for stored measurements (1 byte per cm). Must be larger than MEASUREMENT_DELAY_CM. + + #define DEFAULT_MEASURED_FILAMENT_DIA DEFAULT_NOMINAL_FILAMENT_DIA // Set measured to nominal initially + + // Display filament width on the LCD status line. Status messages will expire after 5 seconds. + //#define FILAMENT_LCD_DISPLAY +#endif + +/** + * CNC Coordinate Systems + * + * Enables G53 and G54-G59.3 commands to select coordinate systems + * and G92.1 to reset the workspace to native machine space. + */ +//#define CNC_COORDINATE_SYSTEMS + +/** + * M43 - display pin status, watch pins for changes, watch endstops & toggle LED, Z servo probe test, toggle pins + */ +//#define PINS_DEBUGGING + +/** + * Auto-report temperatures with M155 S + */ +#define AUTO_REPORT_TEMPERATURES + +/** + * Include capabilities in M115 output + */ +#define EXTENDED_CAPABILITIES_REPORT + +/** + * Disable all Volumetric extrusion options + */ +//#define NO_VOLUMETRICS + +#if DISABLED(NO_VOLUMETRICS) + /** + * Volumetric extrusion default state + * Activate to make volumetric extrusion the default method, + * with DEFAULT_NOMINAL_FILAMENT_DIA as the default diameter. + * + * M200 D0 to disable, M200 Dn to set a new diameter. + */ + //#define VOLUMETRIC_DEFAULT_ON +#endif + +/** + * Enable this option for a leaner build of Marlin that removes all + * workspace offsets, simplifying coordinate transformations, leveling, etc. + * + * - M206 and M428 are disabled. + * - G92 will revert to its behavior from Marlin 1.0. + */ +//#define NO_WORKSPACE_OFFSETS + +/** + * Set the number of proportional font spaces required to fill up a typical character space. + * This can help to better align the output of commands like `G29 O` Mesh Output. + * + * For clients that use a fixed-width font (like OctoPrint), leave this set to 1.0. + * Otherwise, adjust according to your client and font. + */ +#define PROPORTIONAL_FONT_RATIO 1.0 + +/** + * Spend 28 bytes of SRAM to optimize the GCode parser + */ +#define FASTER_GCODE_PARSER + +/** + * User-defined menu items that execute custom GCode + */ +//#define CUSTOM_USER_MENUS +#if ENABLED(CUSTOM_USER_MENUS) + #define USER_SCRIPT_DONE "M117 User Script Done" + #define USER_SCRIPT_AUDIBLE_FEEDBACK + //#define USER_SCRIPT_RETURN // Return to status screen after a script + + #define USER_DESC_1 "Home & UBL Info" + #define USER_GCODE_1 "G28\nG29 W" + + #define USER_DESC_2 "Preheat for PLA" + #define USER_GCODE_2 "M140 S" STRINGIFY(PREHEAT_1_TEMP_BED) "\nM104 S" STRINGIFY(PREHEAT_1_TEMP_HOTEND) + + #define USER_DESC_3 "Preheat for ABS" + #define USER_GCODE_3 "M140 S" STRINGIFY(PREHEAT_2_TEMP_BED) "\nM104 S" STRINGIFY(PREHEAT_2_TEMP_HOTEND) + + #define USER_DESC_4 "Heat Bed/Home/Level" + #define USER_GCODE_4 "M140 S" STRINGIFY(PREHEAT_2_TEMP_BED) "\nG28\nG29" + + #define USER_DESC_5 "Home & Info" + #define USER_GCODE_5 "G28\nM503" +#endif + +/** + * Specify an action command to send to the host when the printer is killed. + * Will be sent in the form '//action:ACTION_ON_KILL', e.g. '//action:poweroff'. + * The host must be configured to handle the action command. + */ +//#define ACTION_ON_KILL "poweroff" + +/** + * Specify an action command to send to the host on pause and resume. + * Will be sent in the form '//action:ACTION_ON_PAUSE', e.g. '//action:pause'. + * The host must be configured to handle the action command. + */ +#define ACTION_ON_PAUSE "pause" +#define ACTION_ON_RESUME "resume" + +//=========================================================================== +//====================== I2C Position Encoder Settings ====================== +//=========================================================================== + +/** + * I2C position encoders for closed loop control. + * Developed by Chris Barr at Aus3D. + * + * Wiki: http://wiki.aus3d.com.au/Magnetic_Encoder + * Github: https://github.com/Aus3D/MagneticEncoder + * + * Supplier: http://aus3d.com.au/magnetic-encoder-module + * Alternative Supplier: http://reliabuild3d.com/ + * + * Reilabuild encoders have been modified to improve reliability. + */ + +//#define I2C_POSITION_ENCODERS +#if ENABLED(I2C_POSITION_ENCODERS) + + #define I2CPE_ENCODER_CNT 1 // The number of encoders installed; max of 5 + // encoders supported currently. + + #define I2CPE_ENC_1_ADDR I2CPE_PRESET_ADDR_X // I2C address of the encoder. 30-200. + #define I2CPE_ENC_1_AXIS X_AXIS // Axis the encoder module is installed on. _AXIS. + #define I2CPE_ENC_1_TYPE I2CPE_ENC_TYPE_LINEAR // Type of encoder: I2CPE_ENC_TYPE_LINEAR -or- + // I2CPE_ENC_TYPE_ROTARY. + #define I2CPE_ENC_1_TICKS_UNIT 2048 // 1024 for magnetic strips with 2mm poles; 2048 for + // 1mm poles. For linear encoders this is ticks / mm, + // for rotary encoders this is ticks / revolution. + //#define I2CPE_ENC_1_TICKS_REV (16 * 200) // Only needed for rotary encoders; number of stepper + // steps per full revolution (motor steps/rev * microstepping) + //#define I2CPE_ENC_1_INVERT // Invert the direction of axis travel. + #define I2CPE_ENC_1_EC_METHOD I2CPE_ECM_MICROSTEP // Type of error error correction. + #define I2CPE_ENC_1_EC_THRESH 0.10 // Threshold size for error (in mm) above which the + // printer will attempt to correct the error; errors + // smaller than this are ignored to minimize effects of + // measurement noise / latency (filter). + + #define I2CPE_ENC_2_ADDR I2CPE_PRESET_ADDR_Y // Same as above, but for encoder 2. + #define I2CPE_ENC_2_AXIS Y_AXIS + #define I2CPE_ENC_2_TYPE I2CPE_ENC_TYPE_LINEAR + #define I2CPE_ENC_2_TICKS_UNIT 2048 + //#define I2CPE_ENC_2_TICKS_REV (16 * 200) + //#define I2CPE_ENC_2_INVERT + #define I2CPE_ENC_2_EC_METHOD I2CPE_ECM_MICROSTEP + #define I2CPE_ENC_2_EC_THRESH 0.10 + + #define I2CPE_ENC_3_ADDR I2CPE_PRESET_ADDR_Z // Encoder 3. Add additional configuration options + #define I2CPE_ENC_3_AXIS Z_AXIS // as above, or use defaults below. + + #define I2CPE_ENC_4_ADDR I2CPE_PRESET_ADDR_E // Encoder 4. + #define I2CPE_ENC_4_AXIS E_AXIS + + #define I2CPE_ENC_5_ADDR 34 // Encoder 5. + #define I2CPE_ENC_5_AXIS E_AXIS + + // Default settings for encoders which are enabled, but without settings configured above. + #define I2CPE_DEF_TYPE I2CPE_ENC_TYPE_LINEAR + #define I2CPE_DEF_ENC_TICKS_UNIT 2048 + #define I2CPE_DEF_TICKS_REV (16 * 200) + #define I2CPE_DEF_EC_METHOD I2CPE_ECM_NONE + #define I2CPE_DEF_EC_THRESH 0.1 + + //#define I2CPE_ERR_THRESH_ABORT 100.0 // Threshold size for error (in mm) error on any given + // axis after which the printer will abort. Comment out to + // disable abort behaviour. + + #define I2CPE_TIME_TRUSTED 10000 // After an encoder fault, there must be no further fault + // for this amount of time (in ms) before the encoder + // is trusted again. + + /** + * Position is checked every time a new command is executed from the buffer but during long moves, + * this setting determines the minimum update time between checks. A value of 100 works well with + * error rolling average when attempting to correct only for skips and not for vibration. + */ + #define I2CPE_MIN_UPD_TIME_MS 4 // (ms) Minimum time between encoder checks. + + // Use a rolling average to identify persistant errors that indicate skips, as opposed to vibration and noise. + #define I2CPE_ERR_ROLLING_AVERAGE + +#endif // I2C_POSITION_ENCODERS + +/** + * MAX7219 Debug Matrix + * + * Add support for a low-cost 8x8 LED Matrix based on the Max7219 chip, which can be used as a status + * display. Requires 3 signal wires. Some useful debug options are included to demonstrate its usage. + * + * Fully assembled MAX7219 boards can be found on the internet for under $2(US). + * For example, see https://www.ebay.com/sch/i.html?_nkw=332349290049 + */ +//#define MAX7219_DEBUG +#if ENABLED(MAX7219_DEBUG) + #define MAX7219_CLK_PIN 64 // 77 on Re-ARM // Configuration of the 3 pins to control the display + #define MAX7219_DIN_PIN 57 // 78 on Re-ARM + #define MAX7219_LOAD_PIN 44 // 79 on Re-ARM + + /** + * Sample debug features + * If you add more debug displays, be careful to avoid conflicts! + */ + #define MAX7219_DEBUG_PRINTER_ALIVE // Blink corner LED of 8x8 matrix to show that the firmware is functioning + #define MAX7219_DEBUG_STEPPER_HEAD 3 // Show the stepper queue head position on this and the next LED matrix row + #define MAX7219_DEBUG_STEPPER_TAIL 5 // Show the stepper queue tail position on this and the next LED matrix row + + #define MAX7219_DEBUG_STEPPER_QUEUE 0 // Show the current stepper queue depth on this and the next LED matrix row + // If you experience stuttering, reboots, etc. this option can reveal how + // tweaks made to the configuration are affecting the printer in real-time. +#endif + +/** + * NanoDLP Sync support + * + * Add support for Synchronized Z moves when using with NanoDLP. G0/G1 axis moves will output "Z_move_comp" + * string to enable synchronization with DLP projector exposure. This change will allow to use + * [[WaitForDoneMessage]] instead of populating your gcode with M400 commands + */ +//#define NANODLP_Z_SYNC +#if ENABLED(NANODLP_Z_SYNC) + //#define NANODLP_ALL_AXIS // Enables "Z_move_comp" output on any axis move. + // Default behaviour is limited to Z axis only. +#endif + +#endif // CONFIGURATION_ADV_H diff --git a/Marlin_main.cpp b/Marlin_main.cpp new file mode 100644 index 0000000000..079e5070c6 --- /dev/null +++ b/Marlin_main.cpp @@ -0,0 +1,14170 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016, 2017 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 . + * + */ + +/** + * About Marlin + * + * This firmware is a mashup between Sprinter and grbl. + * - https://github.com/kliment/Sprinter + * - https://github.com/grbl/grbl + */ + +/** + * ----------------- + * G-Codes in Marlin + * ----------------- + * + * Helpful G-code references: + * - http://linuxcnc.org/handbook/gcode/g-code.html + * - http://objects.reprap.org/wiki/Mendel_User_Manual:_RepRapGCodes + * + * Help to document Marlin's G-codes online: + * - http://reprap.org/wiki/G-code + * - 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) + * 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) + * 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 laser/spindle on, set spindle/laser speed/power, set rotation to clockwise + * M4 - Turn laser/spindle on, set spindle/laser speed/power, set rotation to counter-clockwise + * M5 - Turn laser/spindle off + * M17 - Enable/Power all stepper motors + * M18 - Disable all stepper motors; same as M84 + * M20 - List SD card. (Requires SDSUPPORT) + * M21 - Init SD card. (Requires SDSUPPORT) + * 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" + * 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 gcode: M42 P S. LED pin assumed if P is omitted. + * M43 - Display pin status, watch pins for changes, watch endstops & toggle LED, Z servo probe test, toggle pins + * M48 - Measure Z Probe repeatability: M48 P X Y V E L S. (Requires Z_MIN_PROBE_REPEATABILITY_TEST) + * 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 POWER_SUPPLY > 0) + * M81 - Turn off Power Supply. (Requires POWER_SUPPLY > 0) + * 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) + * M92 - Set planner.axis_steps_per_mm for one or more axes. + * M100 - Watch Free Memory (for debugging) (Requires M100_FREE_MEMORY_WATCHER) + * 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 - Sxxx Wait for extruder current temp to reach target temp. Waits only when heating + * Rxxx Wait for extruder current temp to reach target temp. Waits when heating and cooling + * If AUTOTEMP is enabled, S B F. Exit autotemp by any M109 without F + * M110 - Set the current line number. (Used by host printing) + * M111 - Set debug flags: "M111 S". See flag bits defined in enum.h. + * M112 - Emergency stop. + * M113 - Get or set the timeout interval for Host Keepalive "busy" messages. (Requires HOST_KEEPALIVE_FEATURE) + * M114 - Report current position. + * M115 - Report capabilities. (Extended capabilities requires EXTENDED_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 HAVE_TMC2130 or HAVE_TMC2208) + * 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 + * 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 P. Values 0-255. (Requires BLINKM, RGB_LED, RGBW_LED, NEOPIXEL_LED, or PCA9632). + * 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 - Save the mix as a virtual extruder. (Requires MIXING_EXTRUDER and MIXING_VIRTUAL_TOOLS) + * M165 - Set the proportions for a mixing extruder. Use parameters ABCDHI to set the mixing factors. (Requires MIXING_EXTRUDER) + * M190 - Sxxx Wait for bed current temp to reach target temp. ** Waits only when heating! ** + * Rxxx Wait for bed current temp to reach target temp. ** Waits for heating or 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) + Every normal extrude-only move will be classified as retract depending on the direction. + * M211 - Enable, Disable, and/or Report software endstops: S<0|1> (Requires MIN_SOFTWARE_ENDSTOPS or MAX_SOFTWARE_ENDSTOPS) + * 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) + * M221 - Set Flow Percentage: "M221 S" + * M226 - Wait until a pin is in a given state: "M226 P S" + * M240 - Trigger a camera to take a photograph. (Requires CHDK or PHOTOGRAPH_PIN) + * M250 - Set LCD contrast: "M250 C" (0-63). (Requires LCD support) + * M260 - i2c Send Data (Requires EXPERIMENTAL_I2CBUS) + * M261 - i2c Request Data (Requires EXPERIMENTAL_I2CBUS) + * M280 - Set servo position absolute: "M280 P S". (Requires servos) + * M290 - Babystepping (Requires 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) + * 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 extruder. (Requires EXT_SOLENOID) + * M381 - Disable all solenoids. (Requires EXT_SOLENOID) + * M400 - Finish all moves. + * M401 - Deploy and activate Z probe. (Requires a probe) + * M402 - Deactivate and stow Z probe. (Requires a probe) + * 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. + * 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 or AUTO_BED_LEVELING_UBL) + * M428 - Set the home_offset based on the current_position. Nearest edge applies. (Disabled by NO_WORKSPACE_OFFSETS or DELTA) + * 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. + * M540 - Enable/disable SD card abort on endstop hit: "M540 S". (Requires ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED) + * 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 L R S A B C I J K" (Requires DELTA) + * M666 - Set/get endstop offsets for delta (Requires DELTA) or dual endstops (Requires [XYZ]_DUAL_ENDSTOPS). + * M701 - Load filament (requires FILAMENT_LOAD_UNLOAD_GCODES) + * M702 - Unload filament (requires FILAMENT_LOAD_UNLOAD_GCODES) + * M851 - Set Z probe's Z offset in current units. (Negative = below the nozzle.) + * M852 - Set skew factors: "M852 [I] [J] [K]". (Requires SKEW_CORRECTION_GCODE, and SKEW_CORRECTION_FOR_Z for IJ) + * 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. + * M900 - Get or Set Linear Advance K-factor. (Requires LIN_ADVANCE) + * M906 - Set or get motor current in milliamps using axis codes X, Y, Z, E. Report values if no axis codes given. (Requires HAVE_TMC2130 or HAVE_TMC2208) + * M907 - Set digital trimpot motor current using axis codes. (Requires a board with digital trimpots) + * M908 - Control digital trimpot directly. (Requires DAC_STEPPER_CURRENT or DIGIPOTSS_PIN) + * M909 - Print digipot/DAC current value. (Requires DAC_STEPPER_CURRENT) + * M910 - Commit digipot/DAC value to external EEPROM via I2C. (Requires DAC_STEPPER_CURRENT) + * M911 - Report stepper driver overtemperature pre-warn condition. (Requires HAVE_TMC2130 or HAVE_TMC2208) + * M912 - Clear stepper driver overtemperature pre-warn condition flag. (Requires HAVE_TMC2130 or HAVE_TMC2208) + * M913 - Set HYBRID_THRESHOLD speed. (Requires HYBRID_THRESHOLD) + * M914 - Set SENSORLESS_HOMING sensitivity. (Requires SENSORLESS_HOMING) + * + * 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 - This can change to suit future G-code regulations + * M928 - Start SD logging: "M928 filename.gco". Stop with M29. (Requires SDSUPPORT) + * M999 - Restart after being stopped by error + * + * "T" Codes + * + * T0-T3 - Select an extruder (tool) by index: "T F" + * + */ + +#include "Marlin.h" + +#include "ultralcd.h" +#include "planner.h" +#include "stepper.h" +#include "endstops.h" +#include "temperature.h" +#include "cardreader.h" +#include "configuration_store.h" +#include "language.h" +#include "pins_arduino.h" +#include "math.h" +#include "nozzle.h" +#include "printcounter.h" +#include "duration_t.h" +#include "types.h" +#include "parser.h" + +#if ENABLED(AUTO_POWER_CONTROL) + #include "power.h" +#endif + +#if HAS_ABL + #include "vector_3.h" + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + #include "least_squares_fit.h" + #endif +#elif ENABLED(MESH_BED_LEVELING) + #include "mesh_bed_leveling.h" +#endif + +#if ENABLED(BEZIER_CURVE_SUPPORT) + #include "planner_bezier.h" +#endif + +#if ENABLED(FWRETRACT) + #include "fwretract.h" +#endif + +#if ENABLED(FILAMENT_RUNOUT_SENSOR) + #include "runout.h" +#endif + +#if HAS_BUZZER && DISABLED(LCD_USE_I2C_BUZZER) + #include "buzzer.h" +#endif + +#if ENABLED(USE_WATCHDOG) + #include "watchdog.h" +#endif + +#if ENABLED(MAX7219_DEBUG) + #include "Max7219_Debug_LEDs.h" +#endif + +#if HAS_COLOR_LEDS + #include "leds.h" +#endif + +#if HAS_SERVOS + #include "servo.h" +#endif + +#if HAS_DIGIPOTSS + #include +#endif + +#if HAS_TRINAMIC + #include "tmc_util.h" +#endif + +#if ENABLED(DAC_STEPPER_CURRENT) + #include "stepper_dac.h" +#endif + +#if ENABLED(EXPERIMENTAL_I2CBUS) + #include "twibus.h" +#endif + +#if ENABLED(I2C_POSITION_ENCODERS) + #include "I2CPositionEncoder.h" +#endif + +#if ENABLED(ENDSTOP_INTERRUPTS_FEATURE) + #include "endstop_interrupts.h" +#endif + +#if ENABLED(M100_FREE_MEMORY_WATCHER) + void gcode_M100(); + void M100_dump_routine(const char * const title, const char *start, const char *end); +#endif + +#if ENABLED(G26_MESH_VALIDATION) + bool g26_debug_flag; // =false + void gcode_G26(); +#endif + +#if ENABLED(SDSUPPORT) + CardReader card; +#endif + +#if ENABLED(EXPERIMENTAL_I2CBUS) + TWIBus i2c; +#endif + +#if ENABLED(G38_PROBE_TARGET) + bool G38_move = false, + G38_endstop_hit = false; +#endif + +#if ENABLED(AUTO_BED_LEVELING_UBL) + #include "ubl.h" +#endif + +#if ENABLED(CNC_COORDINATE_SYSTEMS) + int8_t active_coordinate_system = -1; // machine space + float coordinate_system[MAX_COORDINATE_SYSTEMS][XYZ]; +#endif + +bool Running = true; + +uint8_t marlin_debug_flags = DEBUG_NONE; + +/** + * Cartesian Current Position + * Used to track the native machine position as moves are queued. + * Used by 'buffer_line_to_current_position' to do a move after changing it. + * Used by 'SYNC_PLAN_POSITION_KINEMATIC' to update 'planner.position'. + */ +float current_position[XYZE] = { 0.0 }; + +/** + * Cartesian Destination + * The destination for a move, filled in by G-code movement commands, + * and expected by functions like 'prepare_move_to_destination'. + * Set with 'gcode_get_destination' or 'set_destination_from_current'. + */ +float destination[XYZE] = { 0.0 }; + +/** + * axis_homed + * Flags that each linear axis was homed. + * XYZ on cartesian, ABC on delta, ABZ on SCARA. + * + * axis_known_position + * Flags that the position is known in each linear axis. Set when homed. + * Cleared whenever a stepper powers off, potentially losing its position. + */ +bool axis_homed[XYZ] = { false }, axis_known_position[XYZ] = { false }; + +/** + * GCode line number handling. Hosts may opt to include line numbers when + * sending commands to Marlin, and lines will be checked for sequentiality. + * M110 N sets the current line number. + */ +static long gcode_N, gcode_LastN, Stopped_gcode_LastN = 0; + +/** + * GCode Command Queue + * A simple ring buffer of BUFSIZE command strings. + * + * Commands are copied into this buffer by the command injectors + * (immediate, serial, sd card) and they are processed sequentially by + * the main loop. The process_next_command function parses the next + * command and hands off execution to individual handler functions. + */ +uint8_t commands_in_queue = 0; // Count of commands in the queue +static uint8_t cmd_queue_index_r = 0, // Ring buffer read position + cmd_queue_index_w = 0; // Ring buffer write position +#if ENABLED(M100_FREE_MEMORY_WATCHER) + char command_queue[BUFSIZE][MAX_CMD_SIZE]; // Necessary so M100 Free Memory Dumper can show us the commands and any corruption +#else // This can be collapsed back to the way it was soon. +static char command_queue[BUFSIZE][MAX_CMD_SIZE]; +#endif + +/** + * Next Injected Command pointer. NULL if no commands are being injected. + * Used by Marlin internally to ensure that commands initiated from within + * are enqueued ahead of any pending serial or sd card commands. + */ +static const char *injected_commands_P = NULL; + +#if ENABLED(TEMPERATURE_UNITS_SUPPORT) + TempUnit input_temp_units = TEMPUNIT_C; +#endif + +/** + * Feed rates are often configured with mm/m + * but the planner and stepper like mm/s units. + */ +static const float homing_feedrate_mm_s[] PROGMEM = { + #if ENABLED(DELTA) + MMM_TO_MMS(HOMING_FEEDRATE_Z), MMM_TO_MMS(HOMING_FEEDRATE_Z), + #else + MMM_TO_MMS(HOMING_FEEDRATE_XY), MMM_TO_MMS(HOMING_FEEDRATE_XY), + #endif + MMM_TO_MMS(HOMING_FEEDRATE_Z), 0 +}; +FORCE_INLINE float homing_feedrate(const AxisEnum a) { return pgm_read_float(&homing_feedrate_mm_s[a]); } + +float feedrate_mm_s = MMM_TO_MMS(1500.0); +static float saved_feedrate_mm_s; +int16_t feedrate_percentage = 100, saved_feedrate_percentage; + +// Initialized by settings.load() +bool axis_relative_modes[] = AXIS_RELATIVE_MODES; + +#if HAS_WORKSPACE_OFFSET + #if HAS_POSITION_SHIFT + // The distance that XYZ has been offset by G92. Reset by G28. + float position_shift[XYZ] = { 0 }; + #endif + #if HAS_HOME_OFFSET + // This offset is added to the configured home position. + // Set by M206, M428, or menu item. Saved to EEPROM. + float home_offset[XYZ] = { 0 }; + #endif + #if HAS_HOME_OFFSET && HAS_POSITION_SHIFT + // The above two are combined to save on computes + float workspace_offset[XYZ] = { 0 }; + #endif +#endif + +// Software Endstops are based on the configured limits. +float soft_endstop_min[XYZ] = { X_MIN_BED, Y_MIN_BED, Z_MIN_POS }, + soft_endstop_max[XYZ] = { X_MAX_BED, Y_MAX_BED, Z_MAX_POS }; +#if HAS_SOFTWARE_ENDSTOPS + bool soft_endstops_enabled = true; + #if IS_KINEMATIC + float soft_endstop_radius, soft_endstop_radius_2; + #endif +#endif + +#if FAN_COUNT > 0 + int16_t fanSpeeds[FAN_COUNT] = { 0 }; + #if ENABLED(EXTRA_FAN_SPEED) + int16_t old_fanSpeeds[FAN_COUNT], + new_fanSpeeds[FAN_COUNT]; + #endif + #if ENABLED(PROBING_FANS_OFF) + bool fans_paused; // = false; + int16_t paused_fanSpeeds[FAN_COUNT] = { 0 }; + #endif +#endif + +#if ENABLED(USE_CONTROLLER_FAN) + int controllerFanSpeed; // = 0; +#endif + +// The active extruder (tool). Set with T command. +uint8_t active_extruder; // = 0; + +// Relative Mode. Enable with G91, disable with G90. +static bool relative_mode; // = false; + +// For M109 and M190, this flag may be cleared (by M108) to exit the wait loop +volatile bool wait_for_heatup = true; + +// For M0/M1, this flag may be cleared (by M108) to exit the wait-for-user loop +#if HAS_RESUME_CONTINUE + volatile bool wait_for_user; // = false; +#endif + +#if HAS_AUTO_REPORTING || ENABLED(HOST_KEEPALIVE_FEATURE) + bool suspend_auto_report; // = false +#endif + +const char axis_codes[XYZE] = { 'X', 'Y', 'Z', 'E' }; + +// Number of characters read in the current line of serial input +static int serial_count; // = 0; + +// Inactivity shutdown +millis_t previous_move_ms; // = 0; +static millis_t max_inactive_time; // = 0; +static millis_t stepper_inactive_time = (DEFAULT_STEPPER_DEACTIVE_TIME) * 1000UL; + +// Buzzer - I2C on the LCD or a BEEPER_PIN +#if ENABLED(LCD_USE_I2C_BUZZER) + #define BUZZ(d,f) lcd_buzz(d, f) +#elif PIN_EXISTS(BEEPER) + Buzzer buzzer; + #define BUZZ(d,f) buzzer.tone(d, f) +#else + #define BUZZ(d,f) NOOP +#endif + +uint8_t target_extruder; + +#if HAS_BED_PROBE + float zprobe_zoffset; // Initialized by settings.load() +#endif + +#if HAS_ABL + float xy_probe_feedrate_mm_s = MMM_TO_MMS(XY_PROBE_SPEED); + #define XY_PROBE_FEEDRATE_MM_S xy_probe_feedrate_mm_s +#elif defined(XY_PROBE_SPEED) + #define XY_PROBE_FEEDRATE_MM_S MMM_TO_MMS(XY_PROBE_SPEED) +#else + #define XY_PROBE_FEEDRATE_MM_S PLANNER_XY_FEEDRATE() +#endif + +#if ENABLED(AUTO_BED_LEVELING_BILINEAR) + #if ENABLED(DELTA) + #define ADJUST_DELTA(V) \ + if (planner.leveling_active) { \ + const float zadj = bilinear_z_offset(V); \ + delta[A_AXIS] += zadj; \ + delta[B_AXIS] += zadj; \ + delta[C_AXIS] += zadj; \ + } + #else + #define ADJUST_DELTA(V) if (planner.leveling_active) { delta[Z_AXIS] += bilinear_z_offset(V); } + #endif +#elif IS_KINEMATIC + #define ADJUST_DELTA(V) NOOP +#endif + +// Extruder offsets +#if HOTENDS > 1 + float hotend_offset[XYZ][HOTENDS]; // Initialized by settings.load() +#endif + +#if HAS_Z_SERVO_ENDSTOP + const int z_servo_angle[2] = Z_SERVO_ANGLES; +#endif + +#if ENABLED(BARICUDA) + uint8_t baricuda_valve_pressure = 0, + baricuda_e_to_p_pressure = 0; +#endif + +#if HAS_POWER_SWITCH + bool powersupply_on = + #if ENABLED(PS_DEFAULT_OFF) + false + #else + true + #endif + ; + #if ENABLED(AUTO_POWER_CONTROL) + #define PSU_ON() powerManager.power_on() + #define PSU_OFF() powerManager.power_off() + #else + #define PSU_ON() PSU_PIN_ON() + #define PSU_OFF() PSU_PIN_OFF() + #endif +#endif + +#if ENABLED(DELTA) + + float delta[ABC]; + + // Initialized by settings.load() + float delta_height, + delta_endstop_adj[ABC] = { 0 }, + delta_radius, + delta_tower_angle_trim[ABC], + delta_tower[ABC][2], + delta_diagonal_rod, + delta_calibration_radius, + delta_diagonal_rod_2_tower[ABC], + delta_segments_per_second, + delta_clip_start_height = Z_MAX_POS; + + float delta_safe_distance_from_top(); + +#endif + +#if ENABLED(AUTO_BED_LEVELING_BILINEAR) + int bilinear_grid_spacing[2], bilinear_start[2]; + float bilinear_grid_factor[2], + z_values[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; + #if ENABLED(ABL_BILINEAR_SUBDIVISION) + #define ABL_BG_SPACING(A) bilinear_grid_spacing_virt[A] + #define ABL_BG_FACTOR(A) bilinear_grid_factor_virt[A] + #define ABL_BG_POINTS_X ABL_GRID_POINTS_VIRT_X + #define ABL_BG_POINTS_Y ABL_GRID_POINTS_VIRT_Y + #define ABL_BG_GRID(X,Y) z_values_virt[X][Y] + #else + #define ABL_BG_SPACING(A) bilinear_grid_spacing[A] + #define ABL_BG_FACTOR(A) bilinear_grid_factor[A] + #define ABL_BG_POINTS_X GRID_MAX_POINTS_X + #define ABL_BG_POINTS_Y GRID_MAX_POINTS_Y + #define ABL_BG_GRID(X,Y) z_values[X][Y] + #endif +#endif + +#if IS_SCARA + // Float constants for SCARA calculations + const float L1 = SCARA_LINKAGE_1, L2 = SCARA_LINKAGE_2, + L1_2 = sq(float(L1)), L1_2_2 = 2.0 * L1_2, + L2_2 = sq(float(L2)); + + float delta_segments_per_second = SCARA_SEGMENTS_PER_SECOND, + delta[ABC]; +#endif + +float cartes[XYZ] = { 0 }; + +#if ENABLED(FILAMENT_WIDTH_SENSOR) + bool filament_sensor; // = false; // M405 turns on filament sensor control. M406 turns it off. + float filament_width_nominal = DEFAULT_NOMINAL_FILAMENT_DIA, // Nominal filament width. Change with M404. + filament_width_meas = DEFAULT_MEASURED_FILAMENT_DIA; // Measured filament diameter + uint8_t meas_delay_cm = MEASUREMENT_DELAY_CM; // Distance delay setting + int8_t measurement_delay[MAX_MEASUREMENT_DELAY + 1], // Ring buffer to delayed measurement. Store extruder factor after subtracting 100 + filwidth_delay_index[2] = { 0, -1 }; // Indexes into ring buffer +#endif + +#if ENABLED(ADVANCED_PAUSE_FEATURE) + AdvancedPauseMenuResponse advanced_pause_menu_response; + float filament_change_unload_length[EXTRUDERS], + filament_change_load_length[EXTRUDERS]; +#endif + +#if ENABLED(MIXING_EXTRUDER) + float mixing_factor[MIXING_STEPPERS]; // Reciprocal of mix proportion. 0.0 = off, otherwise >= 1.0. + #if MIXING_VIRTUAL_TOOLS > 1 + float mixing_virtual_tool_mix[MIXING_VIRTUAL_TOOLS][MIXING_STEPPERS]; + #endif +#endif + +static bool send_ok[BUFSIZE]; + +#if HAS_SERVOS + Servo servo[NUM_SERVOS]; + #define MOVE_SERVO(I, P) servo[I].move(P) + #if HAS_Z_SERVO_ENDSTOP + #define DEPLOY_Z_SERVO() MOVE_SERVO(Z_ENDSTOP_SERVO_NR, z_servo_angle[0]) + #define STOW_Z_SERVO() MOVE_SERVO(Z_ENDSTOP_SERVO_NR, z_servo_angle[1]) + #endif +#endif + +#ifdef CHDK + millis_t chdkHigh = 0; + bool chdkActive = false; +#endif + +#if ENABLED(PID_EXTRUSION_SCALING) + int lpq_len = 20; +#endif + +#if ENABLED(HOST_KEEPALIVE_FEATURE) + MarlinBusyState busy_state = NOT_BUSY; + static millis_t next_busy_signal_ms = 0; + uint8_t host_keepalive_interval = DEFAULT_KEEPALIVE_INTERVAL; +#else + #define host_keepalive() NOOP +#endif + +#if ENABLED(I2C_POSITION_ENCODERS) + I2CPositionEncodersMgr I2CPEM; +#endif + +#if ENABLED(CNC_WORKSPACE_PLANES) + static WorkspacePlane workspace_plane = PLANE_XY; +#endif + +FORCE_INLINE float pgm_read_any(const float *p) { return pgm_read_float_near(p); } +FORCE_INLINE signed char pgm_read_any(const signed char *p) { return pgm_read_byte_near(p); } + +#define XYZ_CONSTS_FROM_CONFIG(type, array, CONFIG) \ + static const PROGMEM type array##_P[XYZ] = { X_##CONFIG, Y_##CONFIG, Z_##CONFIG }; \ + static inline type array(const AxisEnum axis) { return pgm_read_any(&array##_P[axis]); } \ + typedef void __void_##CONFIG##__ + +XYZ_CONSTS_FROM_CONFIG(float, base_min_pos, MIN_POS); +XYZ_CONSTS_FROM_CONFIG(float, base_max_pos, MAX_POS); +XYZ_CONSTS_FROM_CONFIG(float, base_home_pos, HOME_POS); +XYZ_CONSTS_FROM_CONFIG(float, max_length, MAX_LENGTH); +XYZ_CONSTS_FROM_CONFIG(float, home_bump_mm, HOME_BUMP_MM); +XYZ_CONSTS_FROM_CONFIG(signed char, home_dir, HOME_DIR); + +/** + * *************************************************************************** + * ******************************** FUNCTIONS ******************************** + * *************************************************************************** + */ + +void stop(); + +void get_available_commands(); +void process_next_command(); +void process_parsed_command(); + +void get_cartesian_from_steppers(); +void set_current_from_steppers_for_axis(const AxisEnum axis); + +#if ENABLED(ARC_SUPPORT) + void plan_arc(const float (&cart)[XYZE], const float (&offset)[2], const bool clockwise); +#endif + +#if ENABLED(BEZIER_CURVE_SUPPORT) + void plan_cubic_move(const float (&offset)[4]); +#endif + +void tool_change(const uint8_t tmp_extruder, const float fr_mm_s=0.0, bool no_move=false); +void report_current_position(); +void report_current_position_detail(); + +#if ENABLED(DEBUG_LEVELING_FEATURE) + void print_xyz(const char* prefix, const char* suffix, const float x, const float y, const float z) { + serialprintPGM(prefix); + SERIAL_CHAR('('); + SERIAL_ECHO(x); + SERIAL_ECHOPAIR(", ", y); + SERIAL_ECHOPAIR(", ", z); + SERIAL_CHAR(')'); + if (suffix) serialprintPGM(suffix); else SERIAL_EOL(); + } + + void print_xyz(const char* prefix, const char* suffix, const float xyz[]) { + print_xyz(prefix, suffix, xyz[X_AXIS], xyz[Y_AXIS], xyz[Z_AXIS]); + } + + #if HAS_ABL + void print_xyz(const char* prefix, const char* suffix, const vector_3 &xyz) { + print_xyz(prefix, suffix, xyz.x, xyz.y, xyz.z); + } + #endif + + #define DEBUG_POS(SUFFIX,VAR) do { \ + print_xyz(PSTR(" " STRINGIFY(VAR) "="), PSTR(" : " SUFFIX "\n"), VAR); }while(0) +#endif + +/** + * sync_plan_position + * + * Set the planner/stepper positions directly from current_position with + * no kinematic translation. Used for homing axes and cartesian/core syncing. + */ +void sync_plan_position() { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("sync_plan_position", current_position); + #endif + planner.set_position_mm(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); +} +void sync_plan_position_e() { planner.set_e_position_mm(current_position[E_AXIS]); } + +#if IS_KINEMATIC + inline void sync_plan_position_kinematic() { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("sync_plan_position_kinematic", current_position); + #endif + planner.set_position_mm_kinematic(current_position); + } +#endif + +#if ENABLED(SDSUPPORT) + #include "SdFatUtil.h" + int freeMemory() { return SdFatUtil::FreeRam(); } +#else +extern "C" { + extern char __bss_end; + extern char __heap_start; + extern void* __brkval; + + int freeMemory() { + int free_memory; + if ((int)__brkval == 0) + free_memory = ((int)&free_memory) - ((int)&__bss_end); + else + free_memory = ((int)&free_memory) - ((int)__brkval); + return free_memory; + } +} +#endif // !SDSUPPORT + +#if ENABLED(DIGIPOT_I2C) + extern void digipot_i2c_set_current(uint8_t channel, float current); + extern void digipot_i2c_init(); +#endif + +/** + * Inject the next "immediate" command, when possible, onto the front of the queue. + * Return true if any immediate commands remain to inject. + */ +static bool drain_injected_commands_P() { + if (injected_commands_P != NULL) { + size_t i = 0; + char c, cmd[30]; + strncpy_P(cmd, injected_commands_P, sizeof(cmd) - 1); + cmd[sizeof(cmd) - 1] = '\0'; + while ((c = cmd[i]) && c != '\n') i++; // find the end of this gcode command + cmd[i] = '\0'; + if (enqueue_and_echo_command(cmd)) // success? + injected_commands_P = c ? injected_commands_P + i + 1 : NULL; // next command or done + } + return (injected_commands_P != NULL); // return whether any more remain +} + +/** + * Record one or many commands to run from program memory. + * Aborts the current queue, if any. + * Note: drain_injected_commands_P() must be called repeatedly to drain the commands afterwards + */ +void enqueue_and_echo_commands_P(const char * const pgcode) { + injected_commands_P = pgcode; + (void)drain_injected_commands_P(); // first command executed asap (when possible) +} + +/** + * Clear the Marlin command queue + */ +void clear_command_queue() { + cmd_queue_index_r = cmd_queue_index_w = commands_in_queue = 0; +} + +/** + * Once a new command is in the ring buffer, call this to commit it + */ +inline void _commit_command(bool say_ok) { + send_ok[cmd_queue_index_w] = say_ok; + if (++cmd_queue_index_w >= BUFSIZE) cmd_queue_index_w = 0; + commands_in_queue++; +} + +/** + * Copy a command from RAM into the main command buffer. + * Return true if the command was successfully added. + * Return false for a full buffer, or if the 'command' is a comment. + */ +inline bool _enqueuecommand(const char* cmd, bool say_ok=false) { + if (*cmd == ';' || commands_in_queue >= BUFSIZE) return false; + strcpy(command_queue[cmd_queue_index_w], cmd); + _commit_command(say_ok); + return true; +} + +/** + * Enqueue with Serial Echo + */ +bool enqueue_and_echo_command(const char* cmd, bool say_ok/*=false*/) { + if (_enqueuecommand(cmd, say_ok)) { + SERIAL_ECHO_START(); + SERIAL_ECHOPAIR(MSG_ENQUEUEING, cmd); + SERIAL_CHAR('"'); + SERIAL_EOL(); + return true; + } + return false; +} + +#if HAS_QUEUE_NOW + void enqueue_and_echo_command_now(const char* cmd, bool say_ok/*=false*/) { + while (!enqueue_and_echo_command(cmd, say_ok)) idle(); + } + #if HAS_LCD_QUEUE_NOW + void enqueue_and_echo_commands_P_now(const char * const pgcode) { + enqueue_and_echo_commands_P(pgcode); + while (drain_injected_commands_P()) idle(); + } + #endif +#endif + +void setup_killpin() { + #if HAS_KILL + SET_INPUT_PULLUP(KILL_PIN); + #endif +} + +void setup_powerhold() { + #if HAS_SUICIDE + OUT_WRITE(SUICIDE_PIN, HIGH); + #endif + #if HAS_POWER_SWITCH + #if ENABLED(PS_DEFAULT_OFF) + PSU_OFF(); + #else + PSU_ON(); + #endif + #endif +} + +void suicide() { + #if HAS_SUICIDE + OUT_WRITE(SUICIDE_PIN, LOW); + #endif +} + +void servo_init() { + #if NUM_SERVOS >= 1 && HAS_SERVO_0 + servo[0].attach(SERVO0_PIN); + servo[0].detach(); // Just set up the pin. We don't have a position yet. Don't move to a random position. + #endif + #if NUM_SERVOS >= 2 && HAS_SERVO_1 + servo[1].attach(SERVO1_PIN); + servo[1].detach(); + #endif + #if NUM_SERVOS >= 3 && HAS_SERVO_2 + servo[2].attach(SERVO2_PIN); + servo[2].detach(); + #endif + #if NUM_SERVOS >= 4 && HAS_SERVO_3 + servo[3].attach(SERVO3_PIN); + servo[3].detach(); + #endif + + #if HAS_Z_SERVO_ENDSTOP + /** + * Set position of Z Servo Endstop + * + * The servo might be deployed and positioned too low to stow + * when starting up the machine or rebooting the board. + * There's no way to know where the nozzle is positioned until + * homing has been done - no homing with z-probe without init! + * + */ + STOW_Z_SERVO(); + #endif +} + +/** + * Stepper Reset (RigidBoard, et.al.) + */ +#if HAS_STEPPER_RESET + void disableStepperDrivers() { + OUT_WRITE(STEPPER_RESET_PIN, LOW); // drive it down to hold in reset motor driver chips + } + void enableStepperDrivers() { SET_INPUT(STEPPER_RESET_PIN); } // set to input, which allows it to be pulled high by pullups +#endif + +#if ENABLED(EXPERIMENTAL_I2CBUS) && I2C_SLAVE_ADDRESS > 0 + + void i2c_on_receive(int bytes) { // just echo all bytes received to serial + i2c.receive(bytes); + } + + void i2c_on_request() { // just send dummy data for now + i2c.reply("Hello World!\n"); + } + +#endif + +void gcode_line_error(const char* err, bool doFlush = true) { + SERIAL_ERROR_START(); + serialprintPGM(err); + SERIAL_ERRORLN(gcode_LastN); + //Serial.println(gcode_N); + if (doFlush) flush_and_request_resend(); + serial_count = 0; +} + +/** + * Get all commands waiting on the serial port and queue them. + * Exit when the buffer is full or when no more characters are + * left on the serial port. + */ +inline void get_serial_commands() { + static char serial_line_buffer[MAX_CMD_SIZE]; + static bool serial_comment_mode = false; + + // If the command buffer is empty for too long, + // send "wait" to indicate Marlin is still waiting. + #if NO_TIMEOUTS > 0 + static millis_t last_command_time = 0; + const millis_t ms = millis(); + if (commands_in_queue == 0 && !MYSERIAL0.available() && ELAPSED(ms, last_command_time + NO_TIMEOUTS)) { + SERIAL_ECHOLNPGM(MSG_WAIT); + last_command_time = ms; + } + #endif + + /** + * Loop while serial characters are incoming and the queue is not full + */ + int c; + while (commands_in_queue < BUFSIZE && (c = MYSERIAL0.read()) >= 0) { + + char serial_char = c; + + /** + * If the character ends the line + */ + if (serial_char == '\n' || serial_char == '\r') { + + serial_comment_mode = false; // end of line == end of comment + + // Skip empty lines and comments + if (!serial_count) { thermalManager.manage_heater(); continue; } + + serial_line_buffer[serial_count] = 0; // Terminate string + serial_count = 0; // Reset buffer + + char* command = serial_line_buffer; + + while (*command == ' ') command++; // Skip leading spaces + char *npos = (*command == 'N') ? command : NULL; // Require the N parameter to start the line + + if (npos) { + + bool M110 = strstr_P(command, PSTR("M110")) != NULL; + + if (M110) { + char* n2pos = strchr(command + 4, 'N'); + if (n2pos) npos = n2pos; + } + + gcode_N = strtol(npos + 1, NULL, 10); + + if (gcode_N != gcode_LastN + 1 && !M110) { + gcode_line_error(PSTR(MSG_ERR_LINE_NO)); + return; + } + + char *apos = strrchr(command, '*'); + if (apos) { + uint8_t checksum = 0, count = uint8_t(apos - command); + while (count) checksum ^= command[--count]; + if (strtol(apos + 1, NULL, 10) != checksum) { + gcode_line_error(PSTR(MSG_ERR_CHECKSUM_MISMATCH)); + return; + } + } + else { + gcode_line_error(PSTR(MSG_ERR_NO_CHECKSUM)); + return; + } + + gcode_LastN = gcode_N; + } + #if ENABLED(SDSUPPORT) + else if (card.saving) { + gcode_line_error(PSTR(MSG_ERR_NO_CHECKSUM)); + return; + } + #endif + + // Movement commands alert when stopped + if (IsStopped()) { + char* gpos = strchr(command, 'G'); + if (gpos) { + const int codenum = strtol(gpos + 1, NULL, 10); + switch (codenum) { + case 0: + case 1: + case 2: + case 3: + SERIAL_ERRORLNPGM(MSG_ERR_STOPPED); + LCD_MESSAGEPGM(MSG_STOPPED); + break; + } + } + } + + #if DISABLED(EMERGENCY_PARSER) + // Process critical commands early + if (strcmp(command, "M108") == 0) { + wait_for_heatup = false; + #if ENABLED(NEWPANEL) + wait_for_user = false; + #endif + } + if (strcmp(command, "M112") == 0) kill(PSTR(MSG_KILLED)); + if (strcmp(command, "M410") == 0) quickstop_stepper(); + #endif + + #if defined(NO_TIMEOUTS) && NO_TIMEOUTS > 0 + last_command_time = ms; + #endif + + // Add the command to the queue + _enqueuecommand(serial_line_buffer, true); + } + else if (serial_count >= MAX_CMD_SIZE - 1) { + // Keep fetching, but ignore normal characters beyond the max length + // The command will be injected when EOL is reached + } + else if (serial_char == '\\') { // Handle escapes + if ((c = MYSERIAL0.read()) >= 0 && !serial_comment_mode) // if we have one more character, copy it over + serial_line_buffer[serial_count++] = (char)c; + // otherwise do nothing + } + else { // it's not a newline, carriage return or escape char + if (serial_char == ';') serial_comment_mode = true; + if (!serial_comment_mode) serial_line_buffer[serial_count++] = serial_char; + } + + } // queue has space, serial has data +} + +#if ENABLED(SDSUPPORT) + + #if ENABLED(PRINTER_EVENT_LEDS) && HAS_RESUME_CONTINUE + static bool lights_off_after_print; // = false + #endif + + /** + * Get commands from the SD Card until the command buffer is full + * or until the end of the file is reached. The special character '#' + * can also interrupt buffering. + */ + inline void get_sdcard_commands() { + static bool stop_buffering = false, + sd_comment_mode = false; + + if (!card.sdprinting) return; + + /** + * '#' stops reading from SD to the buffer prematurely, so procedural + * macro calls are possible. If it occurs, stop_buffering is triggered + * and the buffer is run dry; this character _can_ occur in serial com + * due to checksums, however, no checksums are used in SD printing. + */ + + if (commands_in_queue == 0) stop_buffering = false; + + uint16_t sd_count = 0; + bool card_eof = card.eof(); + while (commands_in_queue < BUFSIZE && !card_eof && !stop_buffering) { + const int16_t n = card.get(); + char sd_char = (char)n; + card_eof = card.eof(); + if (card_eof || n == -1 + || sd_char == '\n' || sd_char == '\r' + || ((sd_char == '#' || sd_char == ':') && !sd_comment_mode) + ) { + if (card_eof) { + + card.printingHasFinished(); + + if (card.sdprinting) + sd_count = 0; // If a sub-file was printing, continue from call point + else { + SERIAL_PROTOCOLLNPGM(MSG_FILE_PRINTED); + #if ENABLED(PRINTER_EVENT_LEDS) + LCD_MESSAGEPGM(MSG_INFO_COMPLETED_PRINTS); + leds.set_green(); + #if HAS_RESUME_CONTINUE + lights_off_after_print = true; + enqueue_and_echo_commands_P(PSTR("M0 S" + #if ENABLED(NEWPANEL) + "1800" + #else + "60" + #endif + )); + #else + safe_delay(2000); + leds.set_off(); + #endif + #endif // PRINTER_EVENT_LEDS + card.checkautostart(true); + } + } + else if (n == -1) { + SERIAL_ERROR_START(); + SERIAL_ECHOLNPGM(MSG_SD_ERR_READ); + } + if (sd_char == '#') stop_buffering = true; + + sd_comment_mode = false; // for new command + + // Skip empty lines and comments + if (!sd_count) { thermalManager.manage_heater(); continue; } + + command_queue[cmd_queue_index_w][sd_count] = '\0'; // terminate string + sd_count = 0; // clear sd line buffer + + _commit_command(false); + } + else if (sd_count >= MAX_CMD_SIZE - 1) { + /** + * Keep fetching, but ignore normal characters beyond the max length + * The command will be injected when EOL is reached + */ + } + else { + if (sd_char == ';') sd_comment_mode = true; + if (!sd_comment_mode) command_queue[cmd_queue_index_w][sd_count++] = sd_char; + } + } + } + +#endif // SDSUPPORT + +/** + * Add to the circular command queue the next command from: + * - The command-injection queue (injected_commands_P) + * - The active serial input (usually USB) + * - The SD card file being actively printed + */ +void get_available_commands() { + + // if any immediate commands remain, don't get other commands yet + if (drain_injected_commands_P()) return; + + get_serial_commands(); + + #if ENABLED(SDSUPPORT) + get_sdcard_commands(); + #endif +} + +/** + * Set target_extruder from the T parameter or the active_extruder + * + * Returns TRUE if the target is invalid + */ +bool get_target_extruder_from_command(const uint16_t code) { + if (parser.seenval('T')) { + const int8_t e = parser.value_byte(); + if (e >= EXTRUDERS) { + SERIAL_ECHO_START(); + SERIAL_CHAR('M'); + SERIAL_ECHO(code); + SERIAL_ECHOLNPAIR(" " MSG_INVALID_EXTRUDER " ", e); + return true; + } + target_extruder = e; + } + else + target_extruder = active_extruder; + + return false; +} + +#if ENABLED(DUAL_X_CARRIAGE) || ENABLED(DUAL_NOZZLE_DUPLICATION_MODE) + bool extruder_duplication_enabled = false; // Used in Dual X mode 2 +#endif + +#if ENABLED(DUAL_X_CARRIAGE) + + static DualXMode dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE; + + static float x_home_pos(const int extruder) { + if (extruder == 0) + return base_home_pos(X_AXIS); + else + /** + * In dual carriage mode the extruder offset provides an override of the + * second X-carriage position when homed - otherwise X2_HOME_POS is used. + * This allows soft recalibration of the second extruder home position + * without firmware reflash (through the M218 command). + */ + return hotend_offset[X_AXIS][1] > 0 ? hotend_offset[X_AXIS][1] : X2_HOME_POS; + } + + static int x_home_dir(const int extruder) { return extruder ? X2_HOME_DIR : X_HOME_DIR; } + + static float inactive_extruder_x_pos = X2_MAX_POS; // used in mode 0 & 1 + static bool active_extruder_parked = false; // used in mode 1 & 2 + static float raised_parked_position[XYZE]; // used in mode 1 + static millis_t delayed_move_time = 0; // used in mode 1 + static float duplicate_extruder_x_offset = DEFAULT_DUPLICATION_X_OFFSET; // used in mode 2 + static int16_t duplicate_extruder_temp_offset = 0; // used in mode 2 + +#endif // DUAL_X_CARRIAGE + +#if HAS_WORKSPACE_OFFSET || ENABLED(DUAL_X_CARRIAGE) || ENABLED(DELTA) + + /** + * Software endstops can be used to monitor the open end of + * an axis that has a hardware endstop on the other end. Or + * they can prevent axes from moving past endstops and grinding. + * + * To keep doing their job as the coordinate system changes, + * the software endstop positions must be refreshed to remain + * at the same positions relative to the machine. + */ + void update_software_endstops(const AxisEnum axis) { + #if HAS_HOME_OFFSET && HAS_POSITION_SHIFT + workspace_offset[axis] = home_offset[axis] + position_shift[axis]; + #endif + + #if ENABLED(DUAL_X_CARRIAGE) + if (axis == X_AXIS) { + + // In Dual X mode hotend_offset[X] is T1's home position + float dual_max_x = max(hotend_offset[X_AXIS][1], X2_MAX_POS); + + if (active_extruder != 0) { + // T1 can move from X2_MIN_POS to X2_MAX_POS or X2 home position (whichever is larger) + soft_endstop_min[X_AXIS] = X2_MIN_POS; + soft_endstop_max[X_AXIS] = dual_max_x; + } + else if (dual_x_carriage_mode == DXC_DUPLICATION_MODE) { + // In Duplication Mode, T0 can move as far left as X_MIN_POS + // but not so far to the right that T1 would move past the end + soft_endstop_min[X_AXIS] = base_min_pos(X_AXIS); + soft_endstop_max[X_AXIS] = min(base_max_pos(X_AXIS), dual_max_x - duplicate_extruder_x_offset); + } + else { + // In other modes, T0 can move from X_MIN_POS to X_MAX_POS + soft_endstop_min[axis] = base_min_pos(axis); + soft_endstop_max[axis] = base_max_pos(axis); + } + } + #elif ENABLED(DELTA) + soft_endstop_min[axis] = base_min_pos(axis); + soft_endstop_max[axis] = axis == Z_AXIS ? delta_height : base_max_pos(axis); + #else + soft_endstop_min[axis] = base_min_pos(axis); + soft_endstop_max[axis] = base_max_pos(axis); + #endif + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR("For ", axis_codes[axis]); + #if HAS_HOME_OFFSET + SERIAL_ECHOPAIR(" axis:\n home_offset = ", home_offset[axis]); + #endif + #if HAS_POSITION_SHIFT + SERIAL_ECHOPAIR("\n position_shift = ", position_shift[axis]); + #endif + SERIAL_ECHOPAIR("\n soft_endstop_min = ", soft_endstop_min[axis]); + SERIAL_ECHOLNPAIR("\n soft_endstop_max = ", soft_endstop_max[axis]); + } + #endif + + #if ENABLED(DELTA) + switch(axis) { + #if HAS_SOFTWARE_ENDSTOPS + case X_AXIS: + case Y_AXIS: + // Get a minimum radius for clamping + soft_endstop_radius = MIN3(FABS(max(soft_endstop_min[X_AXIS], soft_endstop_min[Y_AXIS])), soft_endstop_max[X_AXIS], soft_endstop_max[Y_AXIS]); + soft_endstop_radius_2 = sq(soft_endstop_radius); + break; + #endif + case Z_AXIS: + delta_clip_start_height = soft_endstop_max[axis] - delta_safe_distance_from_top(); + default: break; + } + #endif + } + +#endif // HAS_WORKSPACE_OFFSET || DUAL_X_CARRIAGE || DELTA + +#if HAS_M206_COMMAND + /** + * Change the home offset for an axis. + * Also refreshes the workspace offset. + */ + static void set_home_offset(const AxisEnum axis, const float v) { + home_offset[axis] = v; + update_software_endstops(axis); + } +#endif // HAS_M206_COMMAND + +/** + * Set an axis' current position to its home position (after homing). + * + * For Core and Cartesian robots this applies one-to-one when an + * individual axis has been homed. + * + * DELTA should wait until all homing is done before setting the XYZ + * current_position to home, because homing is a single operation. + * In the case where the axis positions are already known and previously + * homed, DELTA could home to X or Y individually by moving either one + * to the center. However, homing Z always homes XY and Z. + * + * SCARA should wait until all XY homing is done before setting the XY + * current_position to home, because neither X nor Y is at home until + * both are at home. Z can however be homed individually. + * + * Callers must sync the planner position after calling this! + */ +static void set_axis_is_at_home(const AxisEnum axis) { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR(">>> set_axis_is_at_home(", axis_codes[axis]); + SERIAL_CHAR(')'); + SERIAL_EOL(); + } + #endif + + axis_known_position[axis] = axis_homed[axis] = true; + + #if HAS_POSITION_SHIFT + position_shift[axis] = 0; + update_software_endstops(axis); + #endif + + #if ENABLED(DUAL_X_CARRIAGE) + if (axis == X_AXIS && (active_extruder == 1 || dual_x_carriage_mode == DXC_DUPLICATION_MODE)) { + current_position[X_AXIS] = x_home_pos(active_extruder); + return; + } + #endif + + #if ENABLED(MORGAN_SCARA) + + /** + * Morgan SCARA homes XY at the same time + */ + if (axis == X_AXIS || axis == Y_AXIS) { + + float homeposition[XYZ] = { + base_home_pos(X_AXIS), + base_home_pos(Y_AXIS), + base_home_pos(Z_AXIS) + }; + + // SERIAL_ECHOPAIR("homeposition X:", homeposition[X_AXIS]); + // SERIAL_ECHOLNPAIR(" Y:", homeposition[Y_AXIS]); + + /** + * Get Home position SCARA arm angles using inverse kinematics, + * and calculate homing offset using forward kinematics + */ + inverse_kinematics(homeposition); + forward_kinematics_SCARA(delta[A_AXIS], delta[B_AXIS]); + + // SERIAL_ECHOPAIR("Cartesian X:", cartes[X_AXIS]); + // SERIAL_ECHOLNPAIR(" Y:", cartes[Y_AXIS]); + + current_position[axis] = cartes[axis]; + + /** + * SCARA home positions are based on configuration since the actual + * limits are determined by the inverse kinematic transform. + */ + soft_endstop_min[axis] = base_min_pos(axis); // + (cartes[axis] - base_home_pos(axis)); + soft_endstop_max[axis] = base_max_pos(axis); // + (cartes[axis] - base_home_pos(axis)); + } + else + #elif ENABLED(DELTA) + if (axis == Z_AXIS) + current_position[axis] = delta_height; + else + #endif + { + current_position[axis] = base_home_pos(axis); + } + + /** + * Z Probe Z Homing? Account for the probe's Z offset. + */ + #if HAS_BED_PROBE && Z_HOME_DIR < 0 + if (axis == Z_AXIS) { + #if HOMING_Z_WITH_PROBE + + current_position[Z_AXIS] -= zprobe_zoffset; + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOLNPGM("*** Z HOMED WITH PROBE (Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN) ***"); + SERIAL_ECHOLNPAIR("> zprobe_zoffset = ", zprobe_zoffset); + } + #endif + + #elif ENABLED(DEBUG_LEVELING_FEATURE) + + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("*** Z HOMED TO ENDSTOP (Z_MIN_PROBE_ENDSTOP) ***"); + + #endif + } + #endif + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + #if HAS_HOME_OFFSET + SERIAL_ECHOPAIR("> home_offset[", axis_codes[axis]); + SERIAL_ECHOLNPAIR("] = ", home_offset[axis]); + #endif + DEBUG_POS("", current_position); + SERIAL_ECHOPAIR("<<< set_axis_is_at_home(", axis_codes[axis]); + SERIAL_CHAR(')'); + SERIAL_EOL(); + } + #endif + + #if ENABLED(I2C_POSITION_ENCODERS) + I2CPEM.homed(axis); + #endif +} + +/** + * Some planner shorthand inline functions + */ +inline float get_homing_bump_feedrate(const AxisEnum axis) { + static const uint8_t homing_bump_divisor[] PROGMEM = HOMING_BUMP_DIVISOR; + uint8_t hbd = pgm_read_byte(&homing_bump_divisor[axis]); + if (hbd < 1) { + hbd = 10; + SERIAL_ECHO_START(); + SERIAL_ECHOLNPGM("Warning: Homing Bump Divisor < 1"); + } + return homing_feedrate(axis) / hbd; +} + +/** + * Move the planner to the current position from wherever it last moved + * (or from wherever it has been told it is located). + */ +inline void buffer_line_to_current_position() { + planner.buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], feedrate_mm_s, active_extruder); +} + +/** + * Move the planner to the position stored in the destination array, which is + * used by G0/G1/G2/G3/G5 and many other functions to set a destination. + */ +inline void buffer_line_to_destination(const float &fr_mm_s) { + planner.buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], fr_mm_s, active_extruder); +} + +#if IS_KINEMATIC + /** + * Calculate delta, start a line, and set current_position to destination + */ + void prepare_uninterpolated_move_to_destination(const float fr_mm_s=0.0) { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("prepare_uninterpolated_move_to_destination", destination); + #endif + + #if UBL_SEGMENTED + // ubl segmented line will do z-only moves in single segment + ubl.prepare_segmented_line_to(destination, MMS_SCALED(fr_mm_s ? fr_mm_s : feedrate_mm_s)); + #else + if ( current_position[X_AXIS] == destination[X_AXIS] + && current_position[Y_AXIS] == destination[Y_AXIS] + && current_position[Z_AXIS] == destination[Z_AXIS] + && current_position[E_AXIS] == destination[E_AXIS] + ) return; + + planner.buffer_line_kinematic(destination, MMS_SCALED(fr_mm_s ? fr_mm_s : feedrate_mm_s), active_extruder); + #endif + + set_current_from_destination(); + } +#endif // IS_KINEMATIC + +/** + * Plan a move to (X, Y, Z) and set the current_position. + * The final current_position may not be the one that was requested + * Caution: 'destination' is modified by this function. + */ +void do_blocking_move_to(const float rx, const float ry, const float rz, const float &fr_mm_s/*=0.0*/) { + const float old_feedrate_mm_s = feedrate_mm_s; + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) print_xyz(PSTR(">>> do_blocking_move_to"), NULL, LOGICAL_X_POSITION(rx), LOGICAL_Y_POSITION(ry), LOGICAL_Z_POSITION(rz)); + #endif + + const float z_feedrate = fr_mm_s ? fr_mm_s : homing_feedrate(Z_AXIS); + + #if ENABLED(DELTA) + + if (!position_is_reachable(rx, ry)) return; + + feedrate_mm_s = fr_mm_s ? fr_mm_s : XY_PROBE_FEEDRATE_MM_S; + + set_destination_from_current(); // sync destination at the start + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("set_destination_from_current", destination); + #endif + + // when in the danger zone + if (current_position[Z_AXIS] > delta_clip_start_height) { + if (rz > delta_clip_start_height) { // staying in the danger zone + destination[X_AXIS] = rx; // move directly (uninterpolated) + destination[Y_AXIS] = ry; + destination[Z_AXIS] = rz; + prepare_uninterpolated_move_to_destination(); // set_current_from_destination + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("danger zone move", current_position); + #endif + return; + } + destination[Z_AXIS] = delta_clip_start_height; + prepare_uninterpolated_move_to_destination(); // set_current_from_destination + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("zone border move", current_position); + #endif + } + + if (rz > current_position[Z_AXIS]) { // raising? + destination[Z_AXIS] = rz; + prepare_uninterpolated_move_to_destination(z_feedrate); // set_current_from_destination + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("z raise move", current_position); + #endif + } + + destination[X_AXIS] = rx; + destination[Y_AXIS] = ry; + prepare_move_to_destination(); // set_current_from_destination + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("xy move", current_position); + #endif + + if (rz < current_position[Z_AXIS]) { // lowering? + destination[Z_AXIS] = rz; + prepare_uninterpolated_move_to_destination(z_feedrate); // set_current_from_destination + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("z lower move", current_position); + #endif + } + + #elif IS_SCARA + + if (!position_is_reachable(rx, ry)) return; + + set_destination_from_current(); + + // If Z needs to raise, do it before moving XY + if (destination[Z_AXIS] < rz) { + destination[Z_AXIS] = rz; + prepare_uninterpolated_move_to_destination(z_feedrate); + } + + destination[X_AXIS] = rx; + destination[Y_AXIS] = ry; + prepare_uninterpolated_move_to_destination(fr_mm_s ? fr_mm_s : XY_PROBE_FEEDRATE_MM_S); + + // If Z needs to lower, do it after moving XY + if (destination[Z_AXIS] > rz) { + destination[Z_AXIS] = rz; + prepare_uninterpolated_move_to_destination(z_feedrate); + } + + #else + + // If Z needs to raise, do it before moving XY + if (current_position[Z_AXIS] < rz) { + feedrate_mm_s = z_feedrate; + current_position[Z_AXIS] = rz; + buffer_line_to_current_position(); + } + + feedrate_mm_s = fr_mm_s ? fr_mm_s : XY_PROBE_FEEDRATE_MM_S; + current_position[X_AXIS] = rx; + current_position[Y_AXIS] = ry; + buffer_line_to_current_position(); + + // If Z needs to lower, do it after moving XY + if (current_position[Z_AXIS] > rz) { + feedrate_mm_s = z_feedrate; + current_position[Z_AXIS] = rz; + buffer_line_to_current_position(); + } + + #endif + + stepper.synchronize(); + + feedrate_mm_s = old_feedrate_mm_s; + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("<<< do_blocking_move_to"); + #endif +} +void do_blocking_move_to_x(const float &rx, const float &fr_mm_s/*=0.0*/) { + do_blocking_move_to(rx, current_position[Y_AXIS], current_position[Z_AXIS], fr_mm_s); +} +void do_blocking_move_to_z(const float &rz, const float &fr_mm_s/*=0.0*/) { + do_blocking_move_to(current_position[X_AXIS], current_position[Y_AXIS], rz, fr_mm_s); +} +void do_blocking_move_to_xy(const float &rx, const float &ry, const float &fr_mm_s/*=0.0*/) { + do_blocking_move_to(rx, ry, current_position[Z_AXIS], fr_mm_s); +} + +// +// Prepare to do endstop or probe moves +// with custom feedrates. +// +// - Save current feedrates +// - Reset the rate multiplier +// - Reset the command timeout +// - Enable the endstops (for endstop moves) +// +static void setup_for_endstop_or_probe_move() { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("setup_for_endstop_or_probe_move", current_position); + #endif + saved_feedrate_mm_s = feedrate_mm_s; + saved_feedrate_percentage = feedrate_percentage; + feedrate_percentage = 100; +} + +static void clean_up_after_endstop_or_probe_move() { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("clean_up_after_endstop_or_probe_move", current_position); + #endif + feedrate_mm_s = saved_feedrate_mm_s; + feedrate_percentage = saved_feedrate_percentage; +} + +#if HAS_AXIS_UNHOMED_ERR + + bool axis_unhomed_error(const bool x/*=true*/, const bool y/*=true*/, const bool z/*=true*/) { + #if ENABLED(HOME_AFTER_DEACTIVATE) + const bool xx = x && !axis_known_position[X_AXIS], + yy = y && !axis_known_position[Y_AXIS], + zz = z && !axis_known_position[Z_AXIS]; + #else + const bool xx = x && !axis_homed[X_AXIS], + yy = y && !axis_homed[Y_AXIS], + zz = z && !axis_homed[Z_AXIS]; + #endif + if (xx || yy || zz) { + SERIAL_ECHO_START(); + SERIAL_ECHOPGM(MSG_HOME " "); + if (xx) SERIAL_ECHOPGM(MSG_X); + if (yy) SERIAL_ECHOPGM(MSG_Y); + if (zz) SERIAL_ECHOPGM(MSG_Z); + SERIAL_ECHOLNPGM(" " MSG_FIRST); + + #if ENABLED(ULTRA_LCD) + lcd_status_printf_P(0, PSTR(MSG_HOME " %s%s%s " MSG_FIRST), xx ? MSG_X : "", yy ? MSG_Y : "", zz ? MSG_Z : ""); + #endif + return true; + } + return false; + } + +#endif // HAS_AXIS_UNHOMED_ERR + +#if ENABLED(Z_PROBE_SLED) + + #ifndef SLED_DOCKING_OFFSET + #define SLED_DOCKING_OFFSET 0 + #endif + + /** + * Method to dock/undock a sled designed by Charles Bell. + * + * stow[in] If false, move to MAX_X and engage the solenoid + * If true, move to MAX_X and release the solenoid + */ + static void dock_sled(bool stow) { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR("dock_sled(", stow); + SERIAL_CHAR(')'); + SERIAL_EOL(); + } + #endif + + // Dock sled a bit closer to ensure proper capturing + do_blocking_move_to_x(X_MAX_POS + SLED_DOCKING_OFFSET - ((stow) ? 1 : 0)); + + #if HAS_SOLENOID_1 && DISABLED(EXT_SOLENOID) + WRITE(SOL1_PIN, !stow); // switch solenoid + #endif + } + +#elif ENABLED(Z_PROBE_ALLEN_KEY) + + FORCE_INLINE void do_blocking_move_to(const float (&raw)[XYZ], const float &fr_mm_s) { + do_blocking_move_to(raw[X_AXIS], raw[Y_AXIS], raw[Z_AXIS], fr_mm_s); + } + + void run_deploy_moves_script() { + #if defined(Z_PROBE_ALLEN_KEY_DEPLOY_1_X) || defined(Z_PROBE_ALLEN_KEY_DEPLOY_1_Y) || defined(Z_PROBE_ALLEN_KEY_DEPLOY_1_Z) + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_1_X + #define Z_PROBE_ALLEN_KEY_DEPLOY_1_X current_position[X_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_1_Y + #define Z_PROBE_ALLEN_KEY_DEPLOY_1_Y current_position[Y_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_1_Z + #define Z_PROBE_ALLEN_KEY_DEPLOY_1_Z current_position[Z_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_1_FEEDRATE + #define Z_PROBE_ALLEN_KEY_DEPLOY_1_FEEDRATE 0.0 + #endif + const float deploy_1[] = { Z_PROBE_ALLEN_KEY_DEPLOY_1_X, Z_PROBE_ALLEN_KEY_DEPLOY_1_Y, Z_PROBE_ALLEN_KEY_DEPLOY_1_Z }; + do_blocking_move_to(deploy_1, MMM_TO_MMS(Z_PROBE_ALLEN_KEY_DEPLOY_1_FEEDRATE)); + #endif + #if defined(Z_PROBE_ALLEN_KEY_DEPLOY_2_X) || defined(Z_PROBE_ALLEN_KEY_DEPLOY_2_Y) || defined(Z_PROBE_ALLEN_KEY_DEPLOY_2_Z) + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_2_X + #define Z_PROBE_ALLEN_KEY_DEPLOY_2_X current_position[X_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_2_Y + #define Z_PROBE_ALLEN_KEY_DEPLOY_2_Y current_position[Y_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_2_Z + #define Z_PROBE_ALLEN_KEY_DEPLOY_2_Z current_position[Z_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_2_FEEDRATE + #define Z_PROBE_ALLEN_KEY_DEPLOY_2_FEEDRATE 0.0 + #endif + const float deploy_2[] = { Z_PROBE_ALLEN_KEY_DEPLOY_2_X, Z_PROBE_ALLEN_KEY_DEPLOY_2_Y, Z_PROBE_ALLEN_KEY_DEPLOY_2_Z }; + do_blocking_move_to(deploy_2, MMM_TO_MMS(Z_PROBE_ALLEN_KEY_DEPLOY_2_FEEDRATE)); + #endif + #if defined(Z_PROBE_ALLEN_KEY_DEPLOY_3_X) || defined(Z_PROBE_ALLEN_KEY_DEPLOY_3_Y) || defined(Z_PROBE_ALLEN_KEY_DEPLOY_3_Z) + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_3_X + #define Z_PROBE_ALLEN_KEY_DEPLOY_3_X current_position[X_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_3_Y + #define Z_PROBE_ALLEN_KEY_DEPLOY_3_Y current_position[Y_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_3_Z + #define Z_PROBE_ALLEN_KEY_DEPLOY_3_Z current_position[Z_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_3_FEEDRATE + #define Z_PROBE_ALLEN_KEY_DEPLOY_3_FEEDRATE 0.0 + #endif + const float deploy_3[] = { Z_PROBE_ALLEN_KEY_DEPLOY_3_X, Z_PROBE_ALLEN_KEY_DEPLOY_3_Y, Z_PROBE_ALLEN_KEY_DEPLOY_3_Z }; + do_blocking_move_to(deploy_3, MMM_TO_MMS(Z_PROBE_ALLEN_KEY_DEPLOY_3_FEEDRATE)); + #endif + #if defined(Z_PROBE_ALLEN_KEY_DEPLOY_4_X) || defined(Z_PROBE_ALLEN_KEY_DEPLOY_4_Y) || defined(Z_PROBE_ALLEN_KEY_DEPLOY_4_Z) + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_4_X + #define Z_PROBE_ALLEN_KEY_DEPLOY_4_X current_position[X_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_4_Y + #define Z_PROBE_ALLEN_KEY_DEPLOY_4_Y current_position[Y_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_4_Z + #define Z_PROBE_ALLEN_KEY_DEPLOY_4_Z current_position[Z_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_4_FEEDRATE + #define Z_PROBE_ALLEN_KEY_DEPLOY_4_FEEDRATE 0.0 + #endif + const float deploy_4[] = { Z_PROBE_ALLEN_KEY_DEPLOY_4_X, Z_PROBE_ALLEN_KEY_DEPLOY_4_Y, Z_PROBE_ALLEN_KEY_DEPLOY_4_Z }; + do_blocking_move_to(deploy_4, MMM_TO_MMS(Z_PROBE_ALLEN_KEY_DEPLOY_4_FEEDRATE)); + #endif + #if defined(Z_PROBE_ALLEN_KEY_DEPLOY_5_X) || defined(Z_PROBE_ALLEN_KEY_DEPLOY_5_Y) || defined(Z_PROBE_ALLEN_KEY_DEPLOY_5_Z) + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_5_X + #define Z_PROBE_ALLEN_KEY_DEPLOY_5_X current_position[X_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_5_Y + #define Z_PROBE_ALLEN_KEY_DEPLOY_5_Y current_position[Y_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_5_Z + #define Z_PROBE_ALLEN_KEY_DEPLOY_5_Z current_position[Z_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_DEPLOY_5_FEEDRATE + #define Z_PROBE_ALLEN_KEY_DEPLOY_5_FEEDRATE 0.0 + #endif + const float deploy_5[] = { Z_PROBE_ALLEN_KEY_DEPLOY_5_X, Z_PROBE_ALLEN_KEY_DEPLOY_5_Y, Z_PROBE_ALLEN_KEY_DEPLOY_5_Z }; + do_blocking_move_to(deploy_5, MMM_TO_MMS(Z_PROBE_ALLEN_KEY_DEPLOY_5_FEEDRATE)); + #endif + } + + void run_stow_moves_script() { + #if defined(Z_PROBE_ALLEN_KEY_STOW_1_X) || defined(Z_PROBE_ALLEN_KEY_STOW_1_Y) || defined(Z_PROBE_ALLEN_KEY_STOW_1_Z) + #ifndef Z_PROBE_ALLEN_KEY_STOW_1_X + #define Z_PROBE_ALLEN_KEY_STOW_1_X current_position[X_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_STOW_1_Y + #define Z_PROBE_ALLEN_KEY_STOW_1_Y current_position[Y_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_STOW_1_Z + #define Z_PROBE_ALLEN_KEY_STOW_1_Z current_position[Z_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_STOW_1_FEEDRATE + #define Z_PROBE_ALLEN_KEY_STOW_1_FEEDRATE 0.0 + #endif + const float stow_1[] = { Z_PROBE_ALLEN_KEY_STOW_1_X, Z_PROBE_ALLEN_KEY_STOW_1_Y, Z_PROBE_ALLEN_KEY_STOW_1_Z }; + do_blocking_move_to(stow_1, MMM_TO_MMS(Z_PROBE_ALLEN_KEY_STOW_1_FEEDRATE)); + #endif + #if defined(Z_PROBE_ALLEN_KEY_STOW_2_X) || defined(Z_PROBE_ALLEN_KEY_STOW_2_Y) || defined(Z_PROBE_ALLEN_KEY_STOW_2_Z) + #ifndef Z_PROBE_ALLEN_KEY_STOW_2_X + #define Z_PROBE_ALLEN_KEY_STOW_2_X current_position[X_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_STOW_2_Y + #define Z_PROBE_ALLEN_KEY_STOW_2_Y current_position[Y_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_STOW_2_Z + #define Z_PROBE_ALLEN_KEY_STOW_2_Z current_position[Z_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_STOW_2_FEEDRATE + #define Z_PROBE_ALLEN_KEY_STOW_2_FEEDRATE 0.0 + #endif + const float stow_2[] = { Z_PROBE_ALLEN_KEY_STOW_2_X, Z_PROBE_ALLEN_KEY_STOW_2_Y, Z_PROBE_ALLEN_KEY_STOW_2_Z }; + do_blocking_move_to(stow_2, MMM_TO_MMS(Z_PROBE_ALLEN_KEY_STOW_2_FEEDRATE)); + #endif + #if defined(Z_PROBE_ALLEN_KEY_STOW_3_X) || defined(Z_PROBE_ALLEN_KEY_STOW_3_Y) || defined(Z_PROBE_ALLEN_KEY_STOW_3_Z) + #ifndef Z_PROBE_ALLEN_KEY_STOW_3_X + #define Z_PROBE_ALLEN_KEY_STOW_3_X current_position[X_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_STOW_3_Y + #define Z_PROBE_ALLEN_KEY_STOW_3_Y current_position[Y_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_STOW_3_Z + #define Z_PROBE_ALLEN_KEY_STOW_3_Z current_position[Z_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_STOW_3_FEEDRATE + #define Z_PROBE_ALLEN_KEY_STOW_3_FEEDRATE 0.0 + #endif + const float stow_3[] = { Z_PROBE_ALLEN_KEY_STOW_3_X, Z_PROBE_ALLEN_KEY_STOW_3_Y, Z_PROBE_ALLEN_KEY_STOW_3_Z }; + do_blocking_move_to(stow_3, MMM_TO_MMS(Z_PROBE_ALLEN_KEY_STOW_3_FEEDRATE)); + #endif + #if defined(Z_PROBE_ALLEN_KEY_STOW_4_X) || defined(Z_PROBE_ALLEN_KEY_STOW_4_Y) || defined(Z_PROBE_ALLEN_KEY_STOW_4_Z) + #ifndef Z_PROBE_ALLEN_KEY_STOW_4_X + #define Z_PROBE_ALLEN_KEY_STOW_4_X current_position[X_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_STOW_4_Y + #define Z_PROBE_ALLEN_KEY_STOW_4_Y current_position[Y_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_STOW_4_Z + #define Z_PROBE_ALLEN_KEY_STOW_4_Z current_position[Z_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_STOW_4_FEEDRATE + #define Z_PROBE_ALLEN_KEY_STOW_4_FEEDRATE 0.0 + #endif + const float stow_4[] = { Z_PROBE_ALLEN_KEY_STOW_4_X, Z_PROBE_ALLEN_KEY_STOW_4_Y, Z_PROBE_ALLEN_KEY_STOW_4_Z }; + do_blocking_move_to(stow_4, MMM_TO_MMS(Z_PROBE_ALLEN_KEY_STOW_4_FEEDRATE)); + #endif + #if defined(Z_PROBE_ALLEN_KEY_STOW_5_X) || defined(Z_PROBE_ALLEN_KEY_STOW_5_Y) || defined(Z_PROBE_ALLEN_KEY_STOW_5_Z) + #ifndef Z_PROBE_ALLEN_KEY_STOW_5_X + #define Z_PROBE_ALLEN_KEY_STOW_5_X current_position[X_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_STOW_5_Y + #define Z_PROBE_ALLEN_KEY_STOW_5_Y current_position[Y_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_STOW_5_Z + #define Z_PROBE_ALLEN_KEY_STOW_5_Z current_position[Z_AXIS] + #endif + #ifndef Z_PROBE_ALLEN_KEY_STOW_5_FEEDRATE + #define Z_PROBE_ALLEN_KEY_STOW_5_FEEDRATE 0.0 + #endif + const float stow_5[] = { Z_PROBE_ALLEN_KEY_STOW_5_X, Z_PROBE_ALLEN_KEY_STOW_5_Y, Z_PROBE_ALLEN_KEY_STOW_5_Z }; + do_blocking_move_to(stow_5, MMM_TO_MMS(Z_PROBE_ALLEN_KEY_STOW_5_FEEDRATE)); + #endif + } + +#endif // Z_PROBE_ALLEN_KEY + +#if ENABLED(PROBING_FANS_OFF) + + void fans_pause(const bool p) { + if (p != fans_paused) { + fans_paused = p; + if (p) + for (uint8_t x = 0; x < FAN_COUNT; x++) { + paused_fanSpeeds[x] = fanSpeeds[x]; + fanSpeeds[x] = 0; + } + else + for (uint8_t x = 0; x < FAN_COUNT; x++) + fanSpeeds[x] = paused_fanSpeeds[x]; + } + } + +#endif // PROBING_FANS_OFF + +#if HAS_BED_PROBE + + // TRIGGERED_WHEN_STOWED_TEST can easily be extended to servo probes, ... if needed. + #if ENABLED(PROBE_IS_TRIGGERED_WHEN_STOWED_TEST) + #if ENABLED(Z_MIN_PROBE_ENDSTOP) + #define _TRIGGERED_WHEN_STOWED_TEST (READ(Z_MIN_PROBE_PIN) != Z_MIN_PROBE_ENDSTOP_INVERTING) + #else + #define _TRIGGERED_WHEN_STOWED_TEST (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING) + #endif + #endif + + #if QUIET_PROBING + void probing_pause(const bool p) { + #if ENABLED(PROBING_HEATERS_OFF) + thermalManager.pause(p); + #endif + #if ENABLED(PROBING_FANS_OFF) + fans_pause(p); + #endif + if (p) safe_delay( + #if DELAY_BEFORE_PROBING > 25 + DELAY_BEFORE_PROBING + #else + 25 + #endif + ); + } + #endif // QUIET_PROBING + + #if ENABLED(BLTOUCH) + + void bltouch_command(int angle) { + MOVE_SERVO(Z_ENDSTOP_SERVO_NR, angle); // Give the BL-Touch the command and wait + safe_delay(BLTOUCH_DELAY); + } + + bool set_bltouch_deployed(const bool deploy) { + if (deploy && TEST_BLTOUCH()) { // If BL-Touch says it's triggered + bltouch_command(BLTOUCH_RESET); // try to reset it. + bltouch_command(BLTOUCH_DEPLOY); // Also needs to deploy and stow to + bltouch_command(BLTOUCH_STOW); // clear the triggered condition. + safe_delay(1500); // Wait for internal self-test to complete. + // (Measured completion time was 0.65 seconds + // after reset, deploy, and stow sequence) + if (TEST_BLTOUCH()) { // If it still claims to be triggered... + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_STOP_BLTOUCH); + stop(); // punt! + return true; + } + } + + bltouch_command(deploy ? BLTOUCH_DEPLOY : BLTOUCH_STOW); + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR("set_bltouch_deployed(", deploy); + SERIAL_CHAR(')'); + SERIAL_EOL(); + } + #endif + + return false; + } + + #endif // BLTOUCH + + /** + * Raise Z to a minimum height to make room for a probe to move + */ + inline void do_probe_raise(const float z_raise) { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR("do_probe_raise(", z_raise); + SERIAL_CHAR(')'); + SERIAL_EOL(); + } + #endif + + float z_dest = z_raise; + if (zprobe_zoffset < 0) z_dest -= zprobe_zoffset; + + NOMORE(z_dest, Z_MAX_POS); + + if (z_dest > current_position[Z_AXIS]) + do_blocking_move_to_z(z_dest); + } + + // returns false for ok and true for failure + bool set_probe_deployed(const bool deploy) { + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + DEBUG_POS("set_probe_deployed", current_position); + SERIAL_ECHOLNPAIR("deploy: ", deploy); + } + #endif + + if (endstops.z_probe_enabled == deploy) return false; + + // Make room for probe to deploy (or stow) + // Fix-mounted probe should only raise for deploy + #if ENABLED(FIX_MOUNTED_PROBE) + const bool deploy_stow_condition = deploy; + #else + constexpr bool deploy_stow_condition = true; + #endif + + // For beds that fall when Z is powered off only raise for trusted Z + #if ENABLED(UNKNOWN_Z_NO_RAISE) + const bool unknown_condition = axis_known_position[Z_AXIS]; + #else + constexpr float unknown_condition = true; + #endif + + if (deploy_stow_condition && unknown_condition) + do_probe_raise(max(Z_CLEARANCE_BETWEEN_PROBES, Z_CLEARANCE_DEPLOY_PROBE)); + + #if ENABLED(Z_PROBE_SLED) || ENABLED(Z_PROBE_ALLEN_KEY) + #if ENABLED(Z_PROBE_SLED) + #define _AUE_ARGS true, false, false + #else + #define _AUE_ARGS + #endif + if (axis_unhomed_error(_AUE_ARGS)) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_STOP_UNHOMED); + stop(); + return true; + } + #endif + + const float oldXpos = current_position[X_AXIS], + oldYpos = current_position[Y_AXIS]; + + #ifdef _TRIGGERED_WHEN_STOWED_TEST + + // If endstop is already false, the Z probe is deployed + if (_TRIGGERED_WHEN_STOWED_TEST == deploy) { // closed after the probe specific actions. + // Would a goto be less ugly? + //while (!_TRIGGERED_WHEN_STOWED_TEST) idle(); // would offer the opportunity + // for a triggered when stowed manual probe. + + if (!deploy) endstops.enable_z_probe(false); // Switch off triggered when stowed probes early + // otherwise an Allen-Key probe can't be stowed. + #endif + + #if ENABLED(SOLENOID_PROBE) + + #if HAS_SOLENOID_1 + WRITE(SOL1_PIN, deploy); + #endif + + #elif ENABLED(Z_PROBE_SLED) + + dock_sled(!deploy); + + #elif HAS_Z_SERVO_ENDSTOP && DISABLED(BLTOUCH) + + MOVE_SERVO(Z_ENDSTOP_SERVO_NR, z_servo_angle[deploy ? 0 : 1]); + + #elif ENABLED(Z_PROBE_ALLEN_KEY) + + deploy ? run_deploy_moves_script() : run_stow_moves_script(); + + #endif + + #ifdef _TRIGGERED_WHEN_STOWED_TEST + } // _TRIGGERED_WHEN_STOWED_TEST == deploy + + if (_TRIGGERED_WHEN_STOWED_TEST == deploy) { // State hasn't changed? + + if (IsRunning()) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM("Z-Probe failed"); + LCD_ALERTMESSAGEPGM("Err: ZPROBE"); + } + stop(); + return true; + + } // _TRIGGERED_WHEN_STOWED_TEST == deploy + + #endif + + do_blocking_move_to(oldXpos, oldYpos, current_position[Z_AXIS]); // return to position before deploy + endstops.enable_z_probe(deploy); + return false; + } + + /** + * @brief Used by run_z_probe to do a single Z probe move. + * + * @param z Z destination + * @param fr_mm_s Feedrate in mm/s + * @return true to indicate an error + */ + static bool do_probe_move(const float z, const float fr_mm_m) { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS(">>> do_probe_move", current_position); + #endif + + // Deploy BLTouch at the start of any probe + #if ENABLED(BLTOUCH) + if (set_bltouch_deployed(true)) return true; + #endif + + #if QUIET_PROBING + probing_pause(true); + #endif + + // Move down until probe triggered + do_blocking_move_to_z(z, MMM_TO_MMS(fr_mm_m)); + + // Check to see if the probe was triggered + const bool probe_triggered = TEST(Endstops::endstop_hit_bits, + #if ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN) + Z_MIN + #else + Z_MIN_PROBE + #endif + ); + + #if QUIET_PROBING + probing_pause(false); + #endif + + // Retract BLTouch immediately after a probe if it was triggered + #if ENABLED(BLTOUCH) + if (probe_triggered && set_bltouch_deployed(false)) return true; + #endif + + // Clear endstop flags + endstops.hit_on_purpose(); + + // Get Z where the steppers were interrupted + set_current_from_steppers_for_axis(Z_AXIS); + + // Tell the planner where we actually are + SYNC_PLAN_POSITION_KINEMATIC(); + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("<<< do_probe_move", current_position); + #endif + + return !probe_triggered; + } + + /** + * @details Used by probe_pt to do a single Z probe at the current position. + * Leaves current_position[Z_AXIS] at the height where the probe triggered. + * + * @return The raw Z position where the probe was triggered + */ + static float run_z_probe() { + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS(">>> run_z_probe", current_position); + #endif + + // Double-probing does a fast probe followed by a slow probe + #if MULTIPLE_PROBING == 2 + + // Do a first probe at the fast speed + if (do_probe_move(-10, Z_PROBE_SPEED_FAST)) return NAN; + + float first_probe_z = current_position[Z_AXIS]; + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPAIR("1st Probe Z:", first_probe_z); + #endif + + // move up to make clearance for the probe + do_blocking_move_to_z(current_position[Z_AXIS] + Z_CLEARANCE_BETWEEN_PROBES, MMM_TO_MMS(Z_PROBE_SPEED_FAST)); + + #else + + // If the nozzle is well over the travel height then + // move down quickly before doing the slow probe + float z = Z_CLEARANCE_DEPLOY_PROBE + 5.0; + if (zprobe_zoffset < 0) z -= zprobe_zoffset; + + if (current_position[Z_AXIS] > z) { + // If we don't make it to the z position (i.e. the probe triggered), move up to make clearance for the probe + if (!do_probe_move(z, Z_PROBE_SPEED_FAST)) + do_blocking_move_to_z(current_position[Z_AXIS] + Z_CLEARANCE_BETWEEN_PROBES, MMM_TO_MMS(Z_PROBE_SPEED_FAST)); + } + #endif + + #if MULTIPLE_PROBING > 2 + float probes_total = 0; + for (uint8_t p = MULTIPLE_PROBING + 1; --p;) { + #endif + + // move down slowly to find bed + if (do_probe_move(-10, Z_PROBE_SPEED_SLOW)) return NAN; + + #if MULTIPLE_PROBING > 2 + probes_total += current_position[Z_AXIS]; + if (p > 1) do_blocking_move_to_z(current_position[Z_AXIS] + Z_CLEARANCE_BETWEEN_PROBES, MMM_TO_MMS(Z_PROBE_SPEED_FAST)); + } + #endif + + #if MULTIPLE_PROBING > 2 + + // Return the average value of all probes + return probes_total * (1.0 / (MULTIPLE_PROBING)); + + #elif MULTIPLE_PROBING == 2 + + const float z2 = current_position[Z_AXIS]; + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR("2nd Probe Z:", z2); + SERIAL_ECHOLNPAIR(" Discrepancy:", first_probe_z - z2); + } + #endif + + // Return a weighted average of the fast and slow probes + return (z2 * 3.0 + first_probe_z * 2.0) * 0.2; + + #else + + // Return the single probe result + return current_position[Z_AXIS]; + + #endif + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("<<< run_z_probe", current_position); + #endif + } + + /** + * - Move to the given XY + * - Deploy the probe, if not already deployed + * - Probe the bed, get the Z position + * - Depending on the 'stow' flag + * - Stow the probe, or + * - Raise to the BETWEEN height + * - Return the probed Z position + */ + float probe_pt(const float &rx, const float &ry, const ProbePtRaise raise_after/*=PROBE_PT_NONE*/, const uint8_t verbose_level/*=0*/, const bool probe_relative/*=true*/) { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR(">>> probe_pt(", LOGICAL_X_POSITION(rx)); + SERIAL_ECHOPAIR(", ", LOGICAL_Y_POSITION(ry)); + SERIAL_ECHOPAIR(", ", raise_after == PROBE_PT_RAISE ? "raise" : raise_after == PROBE_PT_STOW ? "stow" : "none"); + SERIAL_ECHOPAIR(", ", int(verbose_level)); + SERIAL_ECHOPAIR(", ", probe_relative ? "probe" : "nozzle"); + SERIAL_ECHOLNPGM("_relative)"); + DEBUG_POS("", current_position); + } + #endif + + // TODO: Adapt for SCARA, where the offset rotates + float nx = rx, ny = ry; + if (probe_relative) { + if (!position_is_reachable_by_probe(rx, ry)) return NAN; // The given position is in terms of the probe + nx -= (X_PROBE_OFFSET_FROM_EXTRUDER); // Get the nozzle position + ny -= (Y_PROBE_OFFSET_FROM_EXTRUDER); + } + else if (!position_is_reachable(nx, ny)) return NAN; // The given position is in terms of the nozzle + + const float nz = + #if ENABLED(DELTA) + // Move below clip height or xy move will be aborted by do_blocking_move_to + min(current_position[Z_AXIS], delta_clip_start_height) + #else + current_position[Z_AXIS] + #endif + ; + + const float old_feedrate_mm_s = feedrate_mm_s; + feedrate_mm_s = XY_PROBE_FEEDRATE_MM_S; + + // Move the probe to the starting XYZ + do_blocking_move_to(nx, ny, nz); + + float measured_z = NAN; + if (!DEPLOY_PROBE()) { + measured_z = run_z_probe() + zprobe_zoffset; + + if (raise_after == PROBE_PT_RAISE) + do_blocking_move_to_z(current_position[Z_AXIS] + Z_CLEARANCE_BETWEEN_PROBES, MMM_TO_MMS(Z_PROBE_SPEED_FAST)); + else if (raise_after == PROBE_PT_STOW) + if (STOW_PROBE()) measured_z = NAN; + } + + if (verbose_level > 2) { + SERIAL_PROTOCOLPGM("Bed X: "); + SERIAL_PROTOCOL_F(LOGICAL_X_POSITION(rx), 3); + SERIAL_PROTOCOLPGM(" Y: "); + SERIAL_PROTOCOL_F(LOGICAL_Y_POSITION(ry), 3); + SERIAL_PROTOCOLPGM(" Z: "); + SERIAL_PROTOCOL_F(measured_z, 3); + SERIAL_EOL(); + } + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("<<< probe_pt"); + #endif + + feedrate_mm_s = old_feedrate_mm_s; + + if (isnan(measured_z)) { + LCD_MESSAGEPGM(MSG_ERR_PROBING_FAILED); + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_PROBING_FAILED); + } + + return measured_z; + } + +#endif // HAS_BED_PROBE + +#if HAS_LEVELING + + bool leveling_is_valid() { + return + #if ENABLED(MESH_BED_LEVELING) + mbl.has_mesh() + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + !!bilinear_grid_spacing[X_AXIS] + #elif ENABLED(AUTO_BED_LEVELING_UBL) + true + #else // 3POINT, LINEAR + true + #endif + ; + } + + /** + * Turn bed leveling on or off, fixing the current + * position as-needed. + * + * Disable: Current position = physical position + * Enable: Current position = "unleveled" physical position + */ + void set_bed_leveling_enabled(const bool enable/*=true*/) { + + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + const bool can_change = (!enable || leveling_is_valid()); + #else + constexpr bool can_change = true; + #endif + + if (can_change && enable != planner.leveling_active) { + + #if ENABLED(MESH_BED_LEVELING) + + if (!enable) + planner.apply_leveling(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS]); + + const bool enabling = enable && leveling_is_valid(); + planner.leveling_active = enabling; + if (enabling) planner.unapply_leveling(current_position); + + #elif ENABLED(AUTO_BED_LEVELING_UBL) + #if PLANNER_LEVELING + if (planner.leveling_active) { // leveling from on to off + // change unleveled current_position to physical current_position without moving steppers. + planner.apply_leveling(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS]); + planner.leveling_active = false; // disable only AFTER calling apply_leveling + } + else { // leveling from off to on + planner.leveling_active = true; // enable BEFORE calling unapply_leveling, otherwise ignored + // change physical current_position to unleveled current_position without moving steppers. + planner.unapply_leveling(current_position); + } + #else + planner.leveling_active = enable; // just flip the bit, current_position will be wrong until next move. + #endif + + #else // ABL + + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + // Force bilinear_z_offset to re-calculate next time + const float reset[XYZ] = { -9999.999, -9999.999, 0 }; + (void)bilinear_z_offset(reset); + #endif + + // Enable or disable leveling compensation in the planner + planner.leveling_active = enable; + + if (!enable) + // When disabling just get the current position from the steppers. + // This will yield the smallest error when first converted back to steps. + set_current_from_steppers_for_axis( + #if ABL_PLANAR + ALL_AXES + #else + Z_AXIS + #endif + ); + else + // When enabling, remove compensation from the current position, + // so compensation will give the right stepper counts. + planner.unapply_leveling(current_position); + + SYNC_PLAN_POSITION_KINEMATIC(); + + #endif // ABL + } + } + + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + + void set_z_fade_height(const float zfh, const bool do_report/*=true*/) { + + if (planner.z_fade_height == zfh) return; // do nothing if no change + + const bool level_active = planner.leveling_active; + + #if ENABLED(AUTO_BED_LEVELING_UBL) + if (level_active) set_bed_leveling_enabled(false); // turn off before changing fade height for proper apply/unapply leveling to maintain current_position + #endif + + planner.set_z_fade_height(zfh); + + if (level_active) { + const float oldpos[] = { current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] }; + #if ENABLED(AUTO_BED_LEVELING_UBL) + set_bed_leveling_enabled(true); // turn back on after changing fade height + #else + set_current_from_steppers_for_axis( + #if ABL_PLANAR + ALL_AXES + #else + Z_AXIS + #endif + ); + SYNC_PLAN_POSITION_KINEMATIC(); + #endif + if (do_report && memcmp(oldpos, current_position, sizeof(oldpos))) + report_current_position(); + } + } + + #endif // LEVELING_FADE_HEIGHT + + /** + * Reset calibration results to zero. + */ + void reset_bed_level() { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("reset_bed_level"); + #endif + set_bed_leveling_enabled(false); + #if ENABLED(MESH_BED_LEVELING) + mbl.reset(); + #elif ENABLED(AUTO_BED_LEVELING_UBL) + ubl.reset(); + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + bilinear_start[X_AXIS] = bilinear_start[Y_AXIS] = + bilinear_grid_spacing[X_AXIS] = bilinear_grid_spacing[Y_AXIS] = 0; + for (uint8_t x = 0; x < GRID_MAX_POINTS_X; x++) + for (uint8_t y = 0; y < GRID_MAX_POINTS_Y; y++) + z_values[x][y] = NAN; + #elif ABL_PLANAR + planner.bed_level_matrix.set_to_identity(); + #endif + } + +#endif // HAS_LEVELING + +#if ENABLED(AUTO_BED_LEVELING_BILINEAR) || ENABLED(MESH_BED_LEVELING) + + /** + * Enable to produce output in JSON format suitable + * for SCAD or JavaScript mesh visualizers. + * + * Visualize meshes in OpenSCAD using the included script. + * + * buildroot/shared/scripts/MarlinMesh.scad + */ + //#define SCAD_MESH_OUTPUT + + /** + * Print calibration results for plotting or manual frame adjustment. + */ + void print_2d_array(const uint8_t sx, const uint8_t sy, const uint8_t precision, const element_2d_fn fn) { + #ifndef SCAD_MESH_OUTPUT + for (uint8_t x = 0; x < sx; x++) { + for (uint8_t i = 0; i < precision + 2 + (x < 10 ? 1 : 0); i++) + SERIAL_PROTOCOLCHAR(' '); + SERIAL_PROTOCOL((int)x); + } + SERIAL_EOL(); + #endif + #ifdef SCAD_MESH_OUTPUT + SERIAL_PROTOCOLLNPGM("measured_z = ["); // open 2D array + #endif + for (uint8_t y = 0; y < sy; y++) { + #ifdef SCAD_MESH_OUTPUT + SERIAL_PROTOCOLPGM(" ["); // open sub-array + #else + if (y < 10) SERIAL_PROTOCOLCHAR(' '); + SERIAL_PROTOCOL((int)y); + #endif + for (uint8_t x = 0; x < sx; x++) { + SERIAL_PROTOCOLCHAR(' '); + const float offset = fn(x, y); + if (!isnan(offset)) { + if (offset >= 0) SERIAL_PROTOCOLCHAR('+'); + SERIAL_PROTOCOL_F(offset, precision); + } + else { + #ifdef SCAD_MESH_OUTPUT + for (uint8_t i = 3; i < precision + 3; i++) + SERIAL_PROTOCOLCHAR(' '); + SERIAL_PROTOCOLPGM("NAN"); + #else + for (uint8_t i = 0; i < precision + 3; i++) + SERIAL_PROTOCOLCHAR(i ? '=' : ' '); + #endif + } + #ifdef SCAD_MESH_OUTPUT + if (x < sx - 1) SERIAL_PROTOCOLCHAR(','); + #endif + } + #ifdef SCAD_MESH_OUTPUT + SERIAL_PROTOCOLCHAR(' '); + SERIAL_PROTOCOLCHAR(']'); // close sub-array + if (y < sy - 1) SERIAL_PROTOCOLCHAR(','); + #endif + SERIAL_EOL(); + } + #ifdef SCAD_MESH_OUTPUT + SERIAL_PROTOCOLPGM("];"); // close 2D array + #endif + SERIAL_EOL(); + } + +#endif + +#if ENABLED(AUTO_BED_LEVELING_BILINEAR) + + /** + * Extrapolate a single point from its neighbors + */ + static void extrapolate_one_point(const uint8_t x, const uint8_t y, const int8_t xdir, const int8_t ydir) { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPGM("Extrapolate ["); + if (x < 10) SERIAL_CHAR(' '); + SERIAL_ECHO((int)x); + SERIAL_CHAR(xdir ? (xdir > 0 ? '+' : '-') : ' '); + SERIAL_CHAR(' '); + if (y < 10) SERIAL_CHAR(' '); + SERIAL_ECHO((int)y); + SERIAL_CHAR(ydir ? (ydir > 0 ? '+' : '-') : ' '); + SERIAL_CHAR(']'); + } + #endif + if (!isnan(z_values[x][y])) { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM(" (done)"); + #endif + return; // Don't overwrite good values. + } + SERIAL_EOL(); + + // Get X neighbors, Y neighbors, and XY neighbors + const uint8_t x1 = x + xdir, y1 = y + ydir, x2 = x1 + xdir, y2 = y1 + ydir; + float a1 = z_values[x1][y ], a2 = z_values[x2][y ], + b1 = z_values[x ][y1], b2 = z_values[x ][y2], + c1 = z_values[x1][y1], c2 = z_values[x2][y2]; + + // Treat far unprobed points as zero, near as equal to far + if (isnan(a2)) a2 = 0.0; if (isnan(a1)) a1 = a2; + if (isnan(b2)) b2 = 0.0; if (isnan(b1)) b1 = b2; + if (isnan(c2)) c2 = 0.0; if (isnan(c1)) c1 = c2; + + const float a = 2 * a1 - a2, b = 2 * b1 - b2, c = 2 * c1 - c2; + + // Take the average instead of the median + z_values[x][y] = (a + b + c) / 3.0; + + // Median is robust (ignores outliers). + // z_values[x][y] = (a < b) ? ((b < c) ? b : (c < a) ? a : c) + // : ((c < b) ? b : (a < c) ? a : c); + } + + //Enable this if your SCARA uses 180° of total area + //#define EXTRAPOLATE_FROM_EDGE + + #if ENABLED(EXTRAPOLATE_FROM_EDGE) + #if GRID_MAX_POINTS_X < GRID_MAX_POINTS_Y + #define HALF_IN_X + #elif GRID_MAX_POINTS_Y < GRID_MAX_POINTS_X + #define HALF_IN_Y + #endif + #endif + + /** + * Fill in the unprobed points (corners of circular print surface) + * using linear extrapolation, away from the center. + */ + static void extrapolate_unprobed_bed_level() { + #ifdef HALF_IN_X + constexpr uint8_t ctrx2 = 0, xlen = GRID_MAX_POINTS_X - 1; + #else + constexpr uint8_t ctrx1 = (GRID_MAX_POINTS_X - 1) / 2, // left-of-center + ctrx2 = (GRID_MAX_POINTS_X) / 2, // right-of-center + xlen = ctrx1; + #endif + + #ifdef HALF_IN_Y + constexpr uint8_t ctry2 = 0, ylen = GRID_MAX_POINTS_Y - 1; + #else + constexpr uint8_t ctry1 = (GRID_MAX_POINTS_Y - 1) / 2, // top-of-center + ctry2 = (GRID_MAX_POINTS_Y) / 2, // bottom-of-center + ylen = ctry1; + #endif + + for (uint8_t xo = 0; xo <= xlen; xo++) + for (uint8_t yo = 0; yo <= ylen; yo++) { + uint8_t x2 = ctrx2 + xo, y2 = ctry2 + yo; + #ifndef HALF_IN_X + const uint8_t x1 = ctrx1 - xo; + #endif + #ifndef HALF_IN_Y + const uint8_t y1 = ctry1 - yo; + #ifndef HALF_IN_X + extrapolate_one_point(x1, y1, +1, +1); // left-below + + + #endif + extrapolate_one_point(x2, y1, -1, +1); // right-below - + + #endif + #ifndef HALF_IN_X + extrapolate_one_point(x1, y2, +1, -1); // left-above + - + #endif + extrapolate_one_point(x2, y2, -1, -1); // right-above - - + } + + } + + static void print_bilinear_leveling_grid() { + SERIAL_ECHOLNPGM("Bilinear Leveling Grid:"); + print_2d_array(GRID_MAX_POINTS_X, GRID_MAX_POINTS_Y, 3, + [](const uint8_t ix, const uint8_t iy) { return z_values[ix][iy]; } + ); + } + + #if ENABLED(ABL_BILINEAR_SUBDIVISION) + + #define ABL_GRID_POINTS_VIRT_X (GRID_MAX_POINTS_X - 1) * (BILINEAR_SUBDIVISIONS) + 1 + #define ABL_GRID_POINTS_VIRT_Y (GRID_MAX_POINTS_Y - 1) * (BILINEAR_SUBDIVISIONS) + 1 + #define ABL_TEMP_POINTS_X (GRID_MAX_POINTS_X + 2) + #define ABL_TEMP_POINTS_Y (GRID_MAX_POINTS_Y + 2) + float z_values_virt[ABL_GRID_POINTS_VIRT_X][ABL_GRID_POINTS_VIRT_Y]; + int bilinear_grid_spacing_virt[2] = { 0 }; + float bilinear_grid_factor_virt[2] = { 0 }; + + static void print_bilinear_leveling_grid_virt() { + SERIAL_ECHOLNPGM("Subdivided with CATMULL ROM Leveling Grid:"); + print_2d_array(ABL_GRID_POINTS_VIRT_X, ABL_GRID_POINTS_VIRT_Y, 5, + [](const uint8_t ix, const uint8_t iy) { return z_values_virt[ix][iy]; } + ); + } + + #define LINEAR_EXTRAPOLATION(E, I) ((E) * 2 - (I)) + float bed_level_virt_coord(const uint8_t x, const uint8_t y) { + uint8_t ep = 0, ip = 1; + if (!x || x == ABL_TEMP_POINTS_X - 1) { + if (x) { + ep = GRID_MAX_POINTS_X - 1; + ip = GRID_MAX_POINTS_X - 2; + } + if (WITHIN(y, 1, ABL_TEMP_POINTS_Y - 2)) + return LINEAR_EXTRAPOLATION( + z_values[ep][y - 1], + z_values[ip][y - 1] + ); + else + return LINEAR_EXTRAPOLATION( + bed_level_virt_coord(ep + 1, y), + bed_level_virt_coord(ip + 1, y) + ); + } + if (!y || y == ABL_TEMP_POINTS_Y - 1) { + if (y) { + ep = GRID_MAX_POINTS_Y - 1; + ip = GRID_MAX_POINTS_Y - 2; + } + if (WITHIN(x, 1, ABL_TEMP_POINTS_X - 2)) + return LINEAR_EXTRAPOLATION( + z_values[x - 1][ep], + z_values[x - 1][ip] + ); + else + return LINEAR_EXTRAPOLATION( + bed_level_virt_coord(x, ep + 1), + bed_level_virt_coord(x, ip + 1) + ); + } + return z_values[x - 1][y - 1]; + } + + static float bed_level_virt_cmr(const float p[4], const uint8_t i, const float t) { + return ( + p[i-1] * -t * sq(1 - t) + + p[i] * (2 - 5 * sq(t) + 3 * t * sq(t)) + + p[i+1] * t * (1 + 4 * t - 3 * sq(t)) + - p[i+2] * sq(t) * (1 - t) + ) * 0.5; + } + + static float bed_level_virt_2cmr(const uint8_t x, const uint8_t y, const float &tx, const float &ty) { + float row[4], column[4]; + for (uint8_t i = 0; i < 4; i++) { + for (uint8_t j = 0; j < 4; j++) { + column[j] = bed_level_virt_coord(i + x - 1, j + y - 1); + } + row[i] = bed_level_virt_cmr(column, 1, ty); + } + return bed_level_virt_cmr(row, 1, tx); + } + + void bed_level_virt_interpolate() { + bilinear_grid_spacing_virt[X_AXIS] = bilinear_grid_spacing[X_AXIS] / (BILINEAR_SUBDIVISIONS); + bilinear_grid_spacing_virt[Y_AXIS] = bilinear_grid_spacing[Y_AXIS] / (BILINEAR_SUBDIVISIONS); + bilinear_grid_factor_virt[X_AXIS] = RECIPROCAL(bilinear_grid_spacing_virt[X_AXIS]); + bilinear_grid_factor_virt[Y_AXIS] = RECIPROCAL(bilinear_grid_spacing_virt[Y_AXIS]); + for (uint8_t y = 0; y < GRID_MAX_POINTS_Y; y++) + for (uint8_t x = 0; x < GRID_MAX_POINTS_X; x++) + for (uint8_t ty = 0; ty < BILINEAR_SUBDIVISIONS; ty++) + for (uint8_t tx = 0; tx < BILINEAR_SUBDIVISIONS; tx++) { + if ((ty && y == GRID_MAX_POINTS_Y - 1) || (tx && x == GRID_MAX_POINTS_X - 1)) + continue; + z_values_virt[x * (BILINEAR_SUBDIVISIONS) + tx][y * (BILINEAR_SUBDIVISIONS) + ty] = + bed_level_virt_2cmr( + x + 1, + y + 1, + (float)tx / (BILINEAR_SUBDIVISIONS), + (float)ty / (BILINEAR_SUBDIVISIONS) + ); + } + } + #endif // ABL_BILINEAR_SUBDIVISION + + // Refresh after other values have been updated + void refresh_bed_level() { + bilinear_grid_factor[X_AXIS] = RECIPROCAL(bilinear_grid_spacing[X_AXIS]); + bilinear_grid_factor[Y_AXIS] = RECIPROCAL(bilinear_grid_spacing[Y_AXIS]); + #if ENABLED(ABL_BILINEAR_SUBDIVISION) + bed_level_virt_interpolate(); + #endif + } + +#endif // AUTO_BED_LEVELING_BILINEAR + +#if ENABLED(SENSORLESS_HOMING) + + /** + * Set sensorless homing if the axis has it, accounting for Core Kinematics. + */ + void sensorless_homing_per_axis(const AxisEnum axis, const bool enable=true) { + switch (axis) { + #if X_SENSORLESS + case X_AXIS: + tmc_sensorless_homing(stepperX, enable); + #if CORE_IS_XY && Y_SENSORLESS + tmc_sensorless_homing(stepperY, enable); + #elif CORE_IS_XZ && Z_SENSORLESS + tmc_sensorless_homing(stepperZ, enable); + #endif + break; + #endif + #if Y_SENSORLESS + case Y_AXIS: + tmc_sensorless_homing(stepperY, enable); + #if CORE_IS_XY && X_SENSORLESS + tmc_sensorless_homing(stepperX, enable); + #elif CORE_IS_YZ && Z_SENSORLESS + tmc_sensorless_homing(stepperZ, enable); + #endif + break; + #endif + #if Z_SENSORLESS + case Z_AXIS: + tmc_sensorless_homing(stepperZ, enable); + #if CORE_IS_XZ && X_SENSORLESS + tmc_sensorless_homing(stepperX, enable); + #elif CORE_IS_YZ && Y_SENSORLESS + tmc_sensorless_homing(stepperY, enable); + #endif + break; + #endif + default: break; + } + } + +#endif // SENSORLESS_HOMING + +/** + * Home an individual linear axis + */ +static void do_homing_move(const AxisEnum axis, const float distance, const float fr_mm_s=0.0) { + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR(">>> do_homing_move(", axis_codes[axis]); + SERIAL_ECHOPAIR(", ", distance); + SERIAL_ECHOPAIR(", ", fr_mm_s); + SERIAL_CHAR(')'); + SERIAL_EOL(); + } + #endif + + // Only do some things when moving towards an endstop + const int8_t axis_home_dir = + #if ENABLED(DUAL_X_CARRIAGE) + (axis == X_AXIS) ? x_home_dir(active_extruder) : + #endif + home_dir(axis); + const bool is_home_dir = (axis_home_dir > 0) == (distance > 0); + + if (is_home_dir) { + + if (axis == Z_AXIS) { + #if HOMING_Z_WITH_PROBE + #if ENABLED(BLTOUCH) + set_bltouch_deployed(true); + #endif + #if QUIET_PROBING + probing_pause(true); + #endif + #endif + } + + // Disable stealthChop if used. Enable diag1 pin on driver. + #if ENABLED(SENSORLESS_HOMING) + sensorless_homing_per_axis(axis); + #endif + } + + // Tell the planner the axis is at 0 + current_position[axis] = 0; + + #if IS_SCARA + SYNC_PLAN_POSITION_KINEMATIC(); + current_position[axis] = distance; + inverse_kinematics(current_position); + planner.buffer_line(delta[A_AXIS], delta[B_AXIS], delta[C_AXIS], current_position[E_AXIS], fr_mm_s ? fr_mm_s : homing_feedrate(axis), active_extruder); + #else + sync_plan_position(); + current_position[axis] = distance; + planner.buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], fr_mm_s ? fr_mm_s : homing_feedrate(axis), active_extruder); + #endif + + stepper.synchronize(); + + if (is_home_dir) { + + if (axis == Z_AXIS) { + #if HOMING_Z_WITH_PROBE + #if QUIET_PROBING + probing_pause(false); + #endif + #if ENABLED(BLTOUCH) + set_bltouch_deployed(false); + #endif + #endif + } + + endstops.hit_on_purpose(); + + // Re-enable stealthChop if used. Disable diag1 pin on driver. + #if ENABLED(SENSORLESS_HOMING) + sensorless_homing_per_axis(axis, false); + #endif + } + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR("<<< do_homing_move(", axis_codes[axis]); + SERIAL_CHAR(')'); + SERIAL_EOL(); + } + #endif +} + +/** + * Home an individual "raw axis" to its endstop. + * This applies to XYZ on Cartesian and Core robots, and + * to the individual ABC steppers on DELTA and SCARA. + * + * At the end of the procedure the axis is marked as + * homed and the current position of that axis is updated. + * Kinematic robots should wait till all axes are homed + * before updating the current position. + */ + +#define HOMEAXIS(LETTER) homeaxis(LETTER##_AXIS) + +static void homeaxis(const AxisEnum axis) { + + #if IS_SCARA + // Only Z homing (with probe) is permitted + if (axis != Z_AXIS) { BUZZ(100, 880); return; } + #else + #define CAN_HOME(A) \ + (axis == A##_AXIS && ((A##_MIN_PIN > -1 && A##_HOME_DIR < 0) || (A##_MAX_PIN > -1 && A##_HOME_DIR > 0))) + if (!CAN_HOME(X) && !CAN_HOME(Y) && !CAN_HOME(Z)) return; + #endif + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR(">>> homeaxis(", axis_codes[axis]); + SERIAL_CHAR(')'); + SERIAL_EOL(); + } + #endif + + const int axis_home_dir = + #if ENABLED(DUAL_X_CARRIAGE) + (axis == X_AXIS) ? x_home_dir(active_extruder) : + #endif + home_dir(axis); + + // Homing Z towards the bed? Deploy the Z probe or endstop. + #if HOMING_Z_WITH_PROBE + if (axis == Z_AXIS && DEPLOY_PROBE()) return; + #endif + + // Set flags for X, Y, Z motor locking + #if ENABLED(X_DUAL_ENDSTOPS) + if (axis == X_AXIS) stepper.set_homing_flag_x(true); + #endif + #if ENABLED(Y_DUAL_ENDSTOPS) + if (axis == Y_AXIS) stepper.set_homing_flag_y(true); + #endif + #if ENABLED(Z_DUAL_ENDSTOPS) + if (axis == Z_AXIS) stepper.set_homing_flag_z(true); + #endif + + // Fast move towards endstop until triggered + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("Home 1 Fast:"); + #endif + do_homing_move(axis, 1.5 * max_length(axis) * axis_home_dir); + + // When homing Z with probe respect probe clearance + const float bump = axis_home_dir * ( + #if HOMING_Z_WITH_PROBE + (axis == Z_AXIS) ? max(Z_CLEARANCE_BETWEEN_PROBES, home_bump_mm(Z_AXIS)) : + #endif + home_bump_mm(axis) + ); + + // If a second homing move is configured... + if (bump) { + // Move away from the endstop by the axis HOME_BUMP_MM + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("Move Away:"); + #endif + do_homing_move(axis, -bump); + + // Slow move towards endstop until triggered + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("Home 2 Slow:"); + #endif + do_homing_move(axis, 2 * bump, get_homing_bump_feedrate(axis)); + } + + /** + * Home axes that have dual endstops... differently + */ + #if ENABLED(X_DUAL_ENDSTOPS) || ENABLED(Y_DUAL_ENDSTOPS) || ENABLED(Z_DUAL_ENDSTOPS) + const bool pos_dir = axis_home_dir > 0; + #if ENABLED(X_DUAL_ENDSTOPS) + if (axis == X_AXIS) { + const bool lock_x1 = pos_dir ? (endstops.x_endstop_adj > 0) : (endstops.x_endstop_adj < 0); + const float adj = FABS(endstops.x_endstop_adj); + if (lock_x1) stepper.set_x_lock(true); else stepper.set_x2_lock(true); + do_homing_move(axis, pos_dir ? -adj : adj); + if (lock_x1) stepper.set_x_lock(false); else stepper.set_x2_lock(false); + stepper.set_homing_flag_x(false); + } + #endif + #if ENABLED(Y_DUAL_ENDSTOPS) + if (axis == Y_AXIS) { + const bool lock_y1 = pos_dir ? (endstops.y_endstop_adj > 0) : (endstops.y_endstop_adj < 0); + const float adj = FABS(endstops.y_endstop_adj); + if (lock_y1) stepper.set_y_lock(true); else stepper.set_y2_lock(true); + do_homing_move(axis, pos_dir ? -adj : adj); + if (lock_y1) stepper.set_y_lock(false); else stepper.set_y2_lock(false); + stepper.set_homing_flag_y(false); + } + #endif + #if ENABLED(Z_DUAL_ENDSTOPS) + if (axis == Z_AXIS) { + const bool lock_z1 = pos_dir ? (endstops.z_endstop_adj > 0) : (endstops.z_endstop_adj < 0); + const float adj = FABS(endstops.z_endstop_adj); + if (lock_z1) stepper.set_z_lock(true); else stepper.set_z2_lock(true); + do_homing_move(axis, pos_dir ? -adj : adj); + if (lock_z1) stepper.set_z_lock(false); else stepper.set_z2_lock(false); + stepper.set_homing_flag_z(false); + } + #endif + #endif + + #if IS_SCARA + + set_axis_is_at_home(axis); + SYNC_PLAN_POSITION_KINEMATIC(); + + #elif ENABLED(DELTA) + + // Delta has already moved all three towers up in G28 + // so here it re-homes each tower in turn. + // Delta homing treats the axes as normal linear axes. + + // retrace by the amount specified in delta_endstop_adj + additional 0.1mm in order to have minimum steps + if (delta_endstop_adj[axis] * Z_HOME_DIR <= 0) { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("delta_endstop_adj:"); + #endif + do_homing_move(axis, delta_endstop_adj[axis] - 0.1 * Z_HOME_DIR); + } + + #else + + // For cartesian/core machines, + // set the axis to its home position + set_axis_is_at_home(axis); + sync_plan_position(); + + destination[axis] = current_position[axis]; + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("> AFTER set_axis_is_at_home", current_position); + #endif + + #endif + + // Put away the Z probe + #if HOMING_Z_WITH_PROBE + if (axis == Z_AXIS && STOW_PROBE()) return; + #endif + + // Clear retracted status if homing the Z axis + #if ENABLED(FWRETRACT) + if (axis == Z_AXIS) + fwretract.hop_amount = 0.0; + #endif + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR("<<< homeaxis(", axis_codes[axis]); + SERIAL_CHAR(')'); + SERIAL_EOL(); + } + #endif +} // homeaxis() + +#if ENABLED(MIXING_EXTRUDER) + + void normalize_mix() { + float mix_total = 0.0; + for (uint8_t i = 0; i < MIXING_STEPPERS; i++) mix_total += RECIPROCAL(mixing_factor[i]); + // Scale all values if they don't add up to ~1.0 + if (!NEAR(mix_total, 1.0)) { + SERIAL_PROTOCOLLNPGM("Warning: Mix factors must add up to 1.0. Scaling."); + for (uint8_t i = 0; i < MIXING_STEPPERS; i++) mixing_factor[i] *= mix_total; + } + } + + #if ENABLED(DIRECT_MIXING_IN_G1) + // Get mixing parameters from the GCode + // The total "must" be 1.0 (but it will be normalized) + // If no mix factors are given, the old mix is preserved + void gcode_get_mix() { + const char* mixing_codes = "ABCDHI"; + byte mix_bits = 0; + for (uint8_t i = 0; i < MIXING_STEPPERS; i++) { + if (parser.seenval(mixing_codes[i])) { + SBI(mix_bits, i); + float v = parser.value_float(); + NOLESS(v, 0.0); + mixing_factor[i] = RECIPROCAL(v); + } + } + // If any mixing factors were included, clear the rest + // If none were included, preserve the last mix + if (mix_bits) { + for (uint8_t i = 0; i < MIXING_STEPPERS; i++) + if (!TEST(mix_bits, i)) mixing_factor[i] = 0.0; + normalize_mix(); + } + } + #endif + +#endif + +/** + * *************************************************************************** + * ***************************** G-CODE HANDLING ***************************** + * *************************************************************************** + */ + +/** + * Set XYZE destination and feedrate from the current GCode command + * + * - Set destination from included axis codes + * - Set to current for missing axis codes + * - Set the feedrate, if included + */ +void gcode_get_destination() { + LOOP_XYZE(i) { + if (parser.seen(axis_codes[i])) { + const float v = parser.value_axis_units((AxisEnum)i); + destination[i] = (axis_relative_modes[i] || relative_mode) + ? current_position[i] + v + : (i == E_AXIS) ? v : LOGICAL_TO_NATIVE(v, i); + } + else + destination[i] = current_position[i]; + } + + if (parser.linearval('F') > 0.0) + feedrate_mm_s = MMM_TO_MMS(parser.value_feedrate()); + + #if ENABLED(PRINTCOUNTER) + if (!DEBUGGING(DRYRUN)) + print_job_timer.incFilamentUsed(destination[E_AXIS] - current_position[E_AXIS]); + #endif + + // Get ABCDHI mixing factors + #if ENABLED(MIXING_EXTRUDER) && ENABLED(DIRECT_MIXING_IN_G1) + gcode_get_mix(); + #endif +} + +#if ENABLED(HOST_KEEPALIVE_FEATURE) + + /** + * Output a "busy" message at regular intervals + * while the machine is not accepting commands. + */ + void host_keepalive() { + const millis_t ms = millis(); + if (!suspend_auto_report && host_keepalive_interval && busy_state != NOT_BUSY) { + if (PENDING(ms, next_busy_signal_ms)) return; + switch (busy_state) { + case IN_HANDLER: + case IN_PROCESS: + SERIAL_ECHO_START(); + SERIAL_ECHOLNPGM(MSG_BUSY_PROCESSING); + break; + case PAUSED_FOR_USER: + SERIAL_ECHO_START(); + SERIAL_ECHOLNPGM(MSG_BUSY_PAUSED_FOR_USER); + break; + case PAUSED_FOR_INPUT: + SERIAL_ECHO_START(); + SERIAL_ECHOLNPGM(MSG_BUSY_PAUSED_FOR_INPUT); + break; + default: + break; + } + } + next_busy_signal_ms = ms + host_keepalive_interval * 1000UL; + } + +#endif // HOST_KEEPALIVE_FEATURE + + +/************************************************** + ***************** GCode Handlers ***************** + **************************************************/ + +#if ENABLED(NO_MOTION_BEFORE_HOMING) + #define G0_G1_CONDITION !axis_unhomed_error(parser.seen('X'), parser.seen('Y'), parser.seen('Z')) +#else + #define G0_G1_CONDITION true +#endif + +/** + * G0, G1: Coordinated movement of X Y Z E axes + */ +inline void gcode_G0_G1( + #if IS_SCARA + bool fast_move=false + #endif +) { + if (IsRunning() && G0_G1_CONDITION) { + gcode_get_destination(); // For X Y Z E F + + #if ENABLED(FWRETRACT) + if (MIN_AUTORETRACT <= MAX_AUTORETRACT) { + // When M209 Autoretract is enabled, convert E-only moves to firmware retract/prime moves + if (fwretract.autoretract_enabled && parser.seen('E') && !(parser.seen('X') || parser.seen('Y') || parser.seen('Z'))) { + const float echange = destination[E_AXIS] - current_position[E_AXIS]; + // Is this a retract or prime move? + if (WITHIN(FABS(echange), MIN_AUTORETRACT, MAX_AUTORETRACT) && fwretract.retracted[active_extruder] == (echange > 0.0)) { + current_position[E_AXIS] = destination[E_AXIS]; // Hide a G1-based retract/prime from calculations + sync_plan_position_e(); // AND from the planner + return fwretract.retract(echange < 0.0); // Firmware-based retract/prime (double-retract ignored) + } + } + } + #endif // FWRETRACT + + #if IS_SCARA + fast_move ? prepare_uninterpolated_move_to_destination() : prepare_move_to_destination(); + #else + prepare_move_to_destination(); + #endif + + #if ENABLED(NANODLP_Z_SYNC) + #if ENABLED(NANODLP_ALL_AXIS) + #define _MOVE_SYNC parser.seenval('X') || parser.seenval('Y') || parser.seenval('Z') // For any move wait and output sync message + #else + #define _MOVE_SYNC parser.seenval('Z') // Only for Z move + #endif + if (_MOVE_SYNC) { + stepper.synchronize(); + SERIAL_ECHOLNPGM(MSG_Z_MOVE_COMP); + } + #endif + } +} + +/** + * G2: Clockwise Arc + * G3: Counterclockwise Arc + * + * This command has two forms: IJ-form and R-form. + * + * - I specifies an X offset. J specifies a Y offset. + * At least one of the IJ parameters is required. + * X and Y can be omitted to do a complete circle. + * The given XY is not error-checked. The arc ends + * based on the angle of the destination. + * Mixing I or J with R will throw an error. + * + * - R specifies the radius. X or Y is required. + * Omitting both X and Y will throw an error. + * X or Y must differ from the current XY. + * Mixing R with I or J will throw an error. + * + * - P specifies the number of full circles to do + * before the specified arc move. + * + * Examples: + * + * G2 I10 ; CW circle centered at X+10 + * G3 X20 Y12 R14 ; CCW circle with r=14 ending at X20 Y12 + */ +#if ENABLED(ARC_SUPPORT) + + inline void gcode_G2_G3(const bool clockwise) { + #if ENABLED(NO_MOTION_BEFORE_HOMING) + if (axis_unhomed_error()) return; + #endif + + if (IsRunning()) { + + #if ENABLED(SF_ARC_FIX) + const bool relative_mode_backup = relative_mode; + relative_mode = true; + #endif + + gcode_get_destination(); + + #if ENABLED(SF_ARC_FIX) + relative_mode = relative_mode_backup; + #endif + + float arc_offset[2] = { 0.0, 0.0 }; + if (parser.seenval('R')) { + const float r = parser.value_linear_units(), + p1 = current_position[X_AXIS], q1 = current_position[Y_AXIS], + p2 = destination[X_AXIS], q2 = destination[Y_AXIS]; + if (r && (p2 != p1 || q2 != q1)) { + const float e = clockwise ^ (r < 0) ? -1 : 1, // clockwise -1/1, counterclockwise 1/-1 + dx = p2 - p1, dy = q2 - q1, // X and Y differences + d = HYPOT(dx, dy), // Linear distance between the points + h = SQRT(sq(r) - sq(d * 0.5)), // Distance to the arc pivot-point + mx = (p1 + p2) * 0.5, my = (q1 + q2) * 0.5, // Point between the two points + sx = -dy / d, sy = dx / d, // Slope of the perpendicular bisector + cx = mx + e * h * sx, cy = my + e * h * sy; // Pivot-point of the arc + arc_offset[0] = cx - p1; + arc_offset[1] = cy - q1; + } + } + else { + if (parser.seenval('I')) arc_offset[0] = parser.value_linear_units(); + if (parser.seenval('J')) arc_offset[1] = parser.value_linear_units(); + } + + if (arc_offset[0] || arc_offset[1]) { + + #if ENABLED(ARC_P_CIRCLES) + // P indicates number of circles to do + int8_t circles_to_do = parser.byteval('P'); + if (!WITHIN(circles_to_do, 0, 100)) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_ARC_ARGS); + } + while (circles_to_do--) + plan_arc(current_position, arc_offset, clockwise); + #endif + + // Send the arc to the planner + plan_arc(destination, arc_offset, clockwise); + } + else { + // Bad arguments + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_ARC_ARGS); + } + } + } + +#endif // ARC_SUPPORT + +void dwell(millis_t time) { + time += millis(); + while (PENDING(millis(), time)) idle(); +} + +/** + * G4: Dwell S or P + */ +inline void gcode_G4() { + millis_t dwell_ms = 0; + + if (parser.seenval('P')) dwell_ms = parser.value_millis(); // milliseconds to wait + if (parser.seenval('S')) dwell_ms = parser.value_millis_from_seconds(); // seconds to wait + + stepper.synchronize(); + #if ENABLED(NANODLP_Z_SYNC) + SERIAL_ECHOLNPGM(MSG_Z_MOVE_COMP); + #endif + + if (!lcd_hasstatus()) LCD_MESSAGEPGM(MSG_DWELL); + + dwell(dwell_ms); +} + +#if ENABLED(BEZIER_CURVE_SUPPORT) + + /** + * Parameters interpreted according to: + * http://linuxcnc.org/docs/2.6/html/gcode/gcode.html#sec:G5-Cubic-Spline + * However I, J omission is not supported at this point; all + * parameters can be omitted and default to zero. + */ + + /** + * G5: Cubic B-spline + */ + inline void gcode_G5() { + #if ENABLED(NO_MOTION_BEFORE_HOMING) + if (axis_unhomed_error()) return; + #endif + + if (IsRunning()) { + + #if ENABLED(CNC_WORKSPACE_PLANES) + if (workspace_plane != PLANE_XY) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_BAD_PLANE_MODE); + return; + } + #endif + + gcode_get_destination(); + + const float offset[] = { + parser.linearval('I'), + parser.linearval('J'), + parser.linearval('P'), + parser.linearval('Q') + }; + + plan_cubic_move(offset); + } + } + +#endif // BEZIER_CURVE_SUPPORT + +#if ENABLED(FWRETRACT) + + /** + * G10 - Retract filament according to settings of M207 + */ + inline void gcode_G10() { + #if EXTRUDERS > 1 + const bool rs = parser.boolval('S'); + #endif + fwretract.retract(true + #if EXTRUDERS > 1 + , rs + #endif + ); + } + + /** + * G11 - Recover filament according to settings of M208 + */ + inline void gcode_G11() { fwretract.retract(false); } + +#endif // FWRETRACT + +#if ENABLED(NOZZLE_CLEAN_FEATURE) + /** + * G12: Clean the nozzle + */ + inline void gcode_G12() { + // Don't allow nozzle cleaning without homing first + if (axis_unhomed_error()) return; + + const uint8_t pattern = parser.ushortval('P', 0), + strokes = parser.ushortval('S', NOZZLE_CLEAN_STROKES), + objects = parser.ushortval('T', NOZZLE_CLEAN_TRIANGLES); + const float radius = parser.floatval('R', NOZZLE_CLEAN_CIRCLE_RADIUS); + + Nozzle::clean(pattern, strokes, radius, objects); + } +#endif + +#if ENABLED(CNC_WORKSPACE_PLANES) + + inline void report_workspace_plane() { + SERIAL_ECHO_START(); + SERIAL_ECHOPGM("Workspace Plane "); + serialprintPGM( + workspace_plane == PLANE_YZ ? PSTR("YZ\n") : + workspace_plane == PLANE_ZX ? PSTR("ZX\n") : + PSTR("XY\n") + ); + } + + inline void set_workspace_plane(const WorkspacePlane plane) { + workspace_plane = plane; + if (DEBUGGING(INFO)) report_workspace_plane(); + } + + /** + * G17: Select Plane XY + * G18: Select Plane ZX + * G19: Select Plane YZ + */ + inline void gcode_G17() { set_workspace_plane(PLANE_XY); } + inline void gcode_G18() { set_workspace_plane(PLANE_ZX); } + inline void gcode_G19() { set_workspace_plane(PLANE_YZ); } + +#endif // CNC_WORKSPACE_PLANES + +#if ENABLED(CNC_COORDINATE_SYSTEMS) + + /** + * Select a coordinate system and update the workspace offset. + * System index -1 is used to specify machine-native. + */ + bool select_coordinate_system(const int8_t _new) { + if (active_coordinate_system == _new) return false; + float old_offset[XYZ] = { 0 }, new_offset[XYZ] = { 0 }; + if (WITHIN(active_coordinate_system, 0, MAX_COORDINATE_SYSTEMS - 1)) + COPY(old_offset, coordinate_system[active_coordinate_system]); + if (WITHIN(_new, 0, MAX_COORDINATE_SYSTEMS - 1)) + COPY(new_offset, coordinate_system[_new]); + active_coordinate_system = _new; + LOOP_XYZ(i) { + const float diff = new_offset[i] - old_offset[i]; + if (diff) { + position_shift[i] += diff; + update_software_endstops((AxisEnum)i); + } + } + return true; + } + + /** + * G53: Apply native workspace to the current move + * + * In CNC G-code G53 is a modifier. + * It precedes a movement command (or other modifiers) on the same line. + * This is the first command to use parser.chain() to make this possible. + * + * Marlin also uses G53 on a line by itself to go back to native space. + */ + inline void gcode_G53() { + const int8_t _system = active_coordinate_system; + active_coordinate_system = -1; + if (parser.chain()) { // If this command has more following... + process_parsed_command(); + active_coordinate_system = _system; + } + } + + /** + * G54-G59.3: Select a new workspace + * + * A workspace is an XYZ offset to the machine native space. + * All workspaces default to 0,0,0 at start, or with EEPROM + * support they may be restored from a previous session. + * + * G92 is used to set the current workspace's offset. + */ + inline void gcode_G54_59(uint8_t subcode=0) { + const int8_t _space = parser.codenum - 54 + subcode; + if (select_coordinate_system(_space)) { + SERIAL_PROTOCOLLNPAIR("Select workspace ", _space); + report_current_position(); + } + } + FORCE_INLINE void gcode_G54() { gcode_G54_59(); } + FORCE_INLINE void gcode_G55() { gcode_G54_59(); } + FORCE_INLINE void gcode_G56() { gcode_G54_59(); } + FORCE_INLINE void gcode_G57() { gcode_G54_59(); } + FORCE_INLINE void gcode_G58() { gcode_G54_59(); } + FORCE_INLINE void gcode_G59() { gcode_G54_59(parser.subcode); } + +#endif + +#if ENABLED(INCH_MODE_SUPPORT) + /** + * G20: Set input mode to inches + */ + inline void gcode_G20() { parser.set_input_linear_units(LINEARUNIT_INCH); } + + /** + * G21: Set input mode to millimeters + */ + inline void gcode_G21() { parser.set_input_linear_units(LINEARUNIT_MM); } +#endif + +#if ENABLED(NOZZLE_PARK_FEATURE) + /** + * G27: Park the nozzle + */ + inline void gcode_G27() { + // Don't allow nozzle parking without homing first + if (axis_unhomed_error()) return; + Nozzle::park(parser.ushortval('P')); + } +#endif // NOZZLE_PARK_FEATURE + +#if ENABLED(QUICK_HOME) + + static void quick_home_xy() { + + // Pretend the current position is 0,0 + current_position[X_AXIS] = current_position[Y_AXIS] = 0.0; + sync_plan_position(); + + const int x_axis_home_dir = + #if ENABLED(DUAL_X_CARRIAGE) + x_home_dir(active_extruder) + #else + home_dir(X_AXIS) + #endif + ; + + const float mlx = max_length(X_AXIS), + mly = max_length(Y_AXIS), + mlratio = mlx > mly ? mly / mlx : mlx / mly, + fr_mm_s = min(homing_feedrate(X_AXIS), homing_feedrate(Y_AXIS)) * SQRT(sq(mlratio) + 1.0); + + #if ENABLED(SENSORLESS_HOMING) + sensorless_homing_per_axis(X_AXIS); + sensorless_homing_per_axis(Y_AXIS); + #endif + + do_blocking_move_to_xy(1.5 * mlx * x_axis_home_dir, 1.5 * mly * home_dir(Y_AXIS), fr_mm_s); + endstops.hit_on_purpose(); // clear endstop hit flags + current_position[X_AXIS] = current_position[Y_AXIS] = 0.0; + + #if ENABLED(SENSORLESS_HOMING) + sensorless_homing_per_axis(X_AXIS, false); + sensorless_homing_per_axis(Y_AXIS, false); + #endif + } + +#endif // QUICK_HOME + +#if ENABLED(DEBUG_LEVELING_FEATURE) + + void log_machine_info() { + SERIAL_ECHOPGM("Machine Type: "); + #if ENABLED(DELTA) + SERIAL_ECHOLNPGM("Delta"); + #elif IS_SCARA + SERIAL_ECHOLNPGM("SCARA"); + #elif IS_CORE + SERIAL_ECHOLNPGM("Core"); + #else + SERIAL_ECHOLNPGM("Cartesian"); + #endif + + SERIAL_ECHOPGM("Probe: "); + #if ENABLED(PROBE_MANUALLY) + SERIAL_ECHOLNPGM("PROBE_MANUALLY"); + #elif ENABLED(FIX_MOUNTED_PROBE) + SERIAL_ECHOLNPGM("FIX_MOUNTED_PROBE"); + #elif ENABLED(BLTOUCH) + SERIAL_ECHOLNPGM("BLTOUCH"); + #elif HAS_Z_SERVO_ENDSTOP + SERIAL_ECHOLNPGM("SERVO PROBE"); + #elif ENABLED(Z_PROBE_SLED) + SERIAL_ECHOLNPGM("Z_PROBE_SLED"); + #elif ENABLED(Z_PROBE_ALLEN_KEY) + SERIAL_ECHOLNPGM("Z_PROBE_ALLEN_KEY"); + #else + SERIAL_ECHOLNPGM("NONE"); + #endif + + #if HAS_BED_PROBE + SERIAL_ECHOPAIR("Probe Offset X:", X_PROBE_OFFSET_FROM_EXTRUDER); + SERIAL_ECHOPAIR(" Y:", Y_PROBE_OFFSET_FROM_EXTRUDER); + SERIAL_ECHOPAIR(" Z:", zprobe_zoffset); + #if X_PROBE_OFFSET_FROM_EXTRUDER > 0 + SERIAL_ECHOPGM(" (Right"); + #elif X_PROBE_OFFSET_FROM_EXTRUDER < 0 + SERIAL_ECHOPGM(" (Left"); + #elif Y_PROBE_OFFSET_FROM_EXTRUDER != 0 + SERIAL_ECHOPGM(" (Middle"); + #else + SERIAL_ECHOPGM(" (Aligned With"); + #endif + #if Y_PROBE_OFFSET_FROM_EXTRUDER > 0 + SERIAL_ECHOPGM("-Back"); + #elif Y_PROBE_OFFSET_FROM_EXTRUDER < 0 + SERIAL_ECHOPGM("-Front"); + #elif X_PROBE_OFFSET_FROM_EXTRUDER != 0 + SERIAL_ECHOPGM("-Center"); + #endif + if (zprobe_zoffset < 0) + SERIAL_ECHOPGM(" & Below"); + else if (zprobe_zoffset > 0) + SERIAL_ECHOPGM(" & Above"); + else + SERIAL_ECHOPGM(" & Same Z as"); + SERIAL_ECHOLNPGM(" Nozzle)"); + #endif + + #if HAS_ABL + SERIAL_ECHOPGM("Auto Bed Leveling: "); + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + SERIAL_ECHOPGM("LINEAR"); + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + SERIAL_ECHOPGM("BILINEAR"); + #elif ENABLED(AUTO_BED_LEVELING_3POINT) + SERIAL_ECHOPGM("3POINT"); + #elif ENABLED(AUTO_BED_LEVELING_UBL) + SERIAL_ECHOPGM("UBL"); + #endif + if (planner.leveling_active) { + SERIAL_ECHOLNPGM(" (enabled)"); + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + if (planner.z_fade_height) + SERIAL_ECHOLNPAIR("Z Fade: ", planner.z_fade_height); + #endif + #if ABL_PLANAR + const float diff[XYZ] = { + stepper.get_axis_position_mm(X_AXIS) - current_position[X_AXIS], + stepper.get_axis_position_mm(Y_AXIS) - current_position[Y_AXIS], + stepper.get_axis_position_mm(Z_AXIS) - current_position[Z_AXIS] + }; + SERIAL_ECHOPGM("ABL Adjustment X"); + if (diff[X_AXIS] > 0) SERIAL_CHAR('+'); + SERIAL_ECHO(diff[X_AXIS]); + SERIAL_ECHOPGM(" Y"); + if (diff[Y_AXIS] > 0) SERIAL_CHAR('+'); + SERIAL_ECHO(diff[Y_AXIS]); + SERIAL_ECHOPGM(" Z"); + if (diff[Z_AXIS] > 0) SERIAL_CHAR('+'); + SERIAL_ECHO(diff[Z_AXIS]); + #else + #if ENABLED(AUTO_BED_LEVELING_UBL) + SERIAL_ECHOPGM("UBL Adjustment Z"); + const float rz = ubl.get_z_correction(current_position[X_AXIS], current_position[Y_AXIS]); + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + SERIAL_ECHOPAIR("Bilinear Grid X", bilinear_start[X_AXIS]); + SERIAL_ECHOPAIR(" Y", bilinear_start[Y_AXIS]); + SERIAL_ECHOPAIR(" W", ABL_BG_SPACING(X_AXIS)); + SERIAL_ECHOLNPAIR(" H", ABL_BG_SPACING(Y_AXIS)); + SERIAL_ECHOPGM("ABL Adjustment Z"); + const float rz = bilinear_z_offset(current_position); + #endif + SERIAL_ECHO(ftostr43sign(rz, '+')); + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + if (planner.z_fade_height) { + SERIAL_ECHOPAIR(" (", ftostr43sign(rz * planner.fade_scaling_factor_for_z(current_position[Z_AXIS]), '+')); + SERIAL_CHAR(')'); + } + #endif + #endif + } + else + SERIAL_ECHOLNPGM(" (disabled)"); + + SERIAL_EOL(); + + #elif ENABLED(MESH_BED_LEVELING) + + SERIAL_ECHOPGM("Mesh Bed Leveling"); + if (planner.leveling_active) { + SERIAL_ECHOLNPGM(" (enabled)"); + SERIAL_ECHOPAIR("MBL Adjustment Z", ftostr43sign(mbl.get_z(current_position[X_AXIS], current_position[Y_AXIS], 1.0), '+')); + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + if (planner.z_fade_height) { + SERIAL_ECHOPAIR(" (", ftostr43sign( + mbl.get_z(current_position[X_AXIS], current_position[Y_AXIS], planner.fade_scaling_factor_for_z(current_position[Z_AXIS])), '+' + )); + SERIAL_CHAR(')'); + } + #endif + } + else + SERIAL_ECHOPGM(" (disabled)"); + + SERIAL_EOL(); + + #endif // MESH_BED_LEVELING + } + +#endif // DEBUG_LEVELING_FEATURE + +#if ENABLED(DELTA) + + #if ENABLED(SENSORLESS_HOMING) + inline void delta_sensorless_homing(const bool on=true) { + sensorless_homing_per_axis(A_AXIS, on); + sensorless_homing_per_axis(B_AXIS, on); + sensorless_homing_per_axis(C_AXIS, on); + } + #endif + + /** + * A delta can only safely home all axes at the same time + * This is like quick_home_xy() but for 3 towers. + */ + inline bool home_delta() { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS(">>> home_delta", current_position); + #endif + // Init the current position of all carriages to 0,0,0 + ZERO(current_position); + sync_plan_position(); + + // Disable stealthChop if used. Enable diag1 pin on driver. + #if ENABLED(SENSORLESS_HOMING) + delta_sensorless_homing(); + #endif + + // Move all carriages together linearly until an endstop is hit. + current_position[X_AXIS] = current_position[Y_AXIS] = current_position[Z_AXIS] = (delta_height + 10); + feedrate_mm_s = homing_feedrate(X_AXIS); + buffer_line_to_current_position(); + stepper.synchronize(); + + // Re-enable stealthChop if used. Disable diag1 pin on driver. + #if ENABLED(SENSORLESS_HOMING) + delta_sensorless_homing(false); + #endif + + // If an endstop was not hit, then damage can occur if homing is continued. + // This can occur if the delta height not set correctly. + if (!(Endstops::endstop_hit_bits & (_BV(X_MAX) | _BV(Y_MAX) | _BV(Z_MAX)))) { + LCD_MESSAGEPGM(MSG_ERR_HOMING_FAILED); + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_HOMING_FAILED); + return false; + } + + endstops.hit_on_purpose(); // clear endstop hit flags + + // At least one carriage has reached the top. + // Now re-home each carriage separately. + HOMEAXIS(A); + HOMEAXIS(B); + HOMEAXIS(C); + + // Set all carriages to their home positions + // Do this here all at once for Delta, because + // XYZ isn't ABC. Applying this per-tower would + // give the impression that they are the same. + LOOP_XYZ(i) set_axis_is_at_home((AxisEnum)i); + + SYNC_PLAN_POSITION_KINEMATIC(); + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("<<< home_delta", current_position); + #endif + + return true; + } + +#endif // DELTA + +#if Z_AFTER_PROBING + void move_z_after_probing() { + if (current_position[Z_AXIS] != Z_AFTER_PROBING) { + do_blocking_move_to_z(Z_AFTER_PROBING); + current_position[Z_AXIS] = Z_AFTER_PROBING; + } + } +#endif + +#if ENABLED(Z_SAFE_HOMING) + + inline void home_z_safely() { + + // Disallow Z homing if X or Y are unknown + if (!axis_known_position[X_AXIS] || !axis_known_position[Y_AXIS]) { + LCD_MESSAGEPGM(MSG_ERR_Z_HOMING); + SERIAL_ECHO_START(); + SERIAL_ECHOLNPGM(MSG_ERR_Z_HOMING); + return; + } + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("Z_SAFE_HOMING >>>"); + #endif + + SYNC_PLAN_POSITION_KINEMATIC(); + + /** + * Move the Z probe (or just the nozzle) to the safe homing point + */ + destination[X_AXIS] = Z_SAFE_HOMING_X_POINT; + destination[Y_AXIS] = Z_SAFE_HOMING_Y_POINT; + destination[Z_AXIS] = current_position[Z_AXIS]; // Z is already at the right height + + #if HOMING_Z_WITH_PROBE + destination[X_AXIS] -= X_PROBE_OFFSET_FROM_EXTRUDER; + destination[Y_AXIS] -= Y_PROBE_OFFSET_FROM_EXTRUDER; + #endif + + if (position_is_reachable(destination[X_AXIS], destination[Y_AXIS])) { + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("Z_SAFE_HOMING", destination); + #endif + + // This causes the carriage on Dual X to unpark + #if ENABLED(DUAL_X_CARRIAGE) + active_extruder_parked = false; + #endif + + #if ENABLED(SENSORLESS_HOMING) + safe_delay(500); // Short delay needed to settle + #endif + + do_blocking_move_to_xy(destination[X_AXIS], destination[Y_AXIS]); + HOMEAXIS(Z); + } + else { + LCD_MESSAGEPGM(MSG_ZPROBE_OUT); + SERIAL_ECHO_START(); + SERIAL_ECHOLNPGM(MSG_ZPROBE_OUT); + } + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("<<< Z_SAFE_HOMING"); + #endif + } + +#endif // Z_SAFE_HOMING + +#if ENABLED(PROBE_MANUALLY) + bool g29_in_progress = false; +#else + constexpr bool g29_in_progress = false; +#endif + +/** + * G28: Home all axes according to settings + * + * Parameters + * + * None Home to all axes with no parameters. + * With QUICK_HOME enabled XY will home together, then Z. + * + * Cartesian parameters + * + * X Home to the X endstop + * Y Home to the Y endstop + * Z Home to the Z endstop + * + */ +inline void gcode_G28(const bool always_home_all) { + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOLNPGM(">>> gcode_G28"); + log_machine_info(); + } + #endif + + // Wait for planner moves to finish! + stepper.synchronize(); + + // Cancel the active G29 session + #if ENABLED(PROBE_MANUALLY) + g29_in_progress = false; + #endif + + // Disable the leveling matrix before homing + #if HAS_LEVELING + #if ENABLED(RESTORE_LEVELING_AFTER_G28) + const bool leveling_state_at_entry = planner.leveling_active; + #endif + set_bed_leveling_enabled(false); + #endif + + #if ENABLED(CNC_WORKSPACE_PLANES) + workspace_plane = PLANE_XY; + #endif + + // Always home with tool 0 active + #if HOTENDS > 1 + #if DISABLED(DELTA) || ENABLED(DELTA_HOME_TO_SAFE_ZONE) + const uint8_t old_tool_index = active_extruder; + #endif + tool_change(0, 0, true); + #endif + + #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(DUAL_NOZZLE_DUPLICATION_MODE) + extruder_duplication_enabled = false; + #endif + + setup_for_endstop_or_probe_move(); + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("> endstops.enable(true)"); + #endif + endstops.enable(true); // Enable endstops for next homing move + + #if ENABLED(DELTA) + + home_delta(); + UNUSED(always_home_all); + + #else // NOT DELTA + + const bool homeX = always_home_all || parser.seen('X'), + homeY = always_home_all || parser.seen('Y'), + homeZ = always_home_all || parser.seen('Z'), + home_all = (!homeX && !homeY && !homeZ) || (homeX && homeY && homeZ); + + set_destination_from_current(); + + #if Z_HOME_DIR > 0 // If homing away from BED do Z first + + if (home_all || homeZ) HOMEAXIS(Z); + + #endif + + #if ENABLED(UNKNOWN_Z_NO_RAISE) + const float z_homing_height = axis_known_position[Z_AXIS] ? Z_HOMING_HEIGHT : 0; + #else + constexpr float z_homing_height = Z_HOMING_HEIGHT; + #endif + + if (z_homing_height && (home_all || homeX || homeY)) { + // Raise Z before homing any other axes and z is not already high enough (never lower z) + destination[Z_AXIS] = z_homing_height; + if (destination[Z_AXIS] > current_position[Z_AXIS]) { + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) + SERIAL_ECHOLNPAIR("Raise Z (before homing) to ", destination[Z_AXIS]); + #endif + + do_blocking_move_to_z(destination[Z_AXIS]); + } + } + + #if ENABLED(QUICK_HOME) + + if (home_all || (homeX && homeY)) quick_home_xy(); + + #endif + + // Home Y (before X) + #if ENABLED(HOME_Y_BEFORE_X) + + if (home_all || homeY + #if ENABLED(CODEPENDENT_XY_HOMING) + || homeX + #endif + ) HOMEAXIS(Y); + + #endif + + // Home X + if (home_all || homeX + #if ENABLED(CODEPENDENT_XY_HOMING) && DISABLED(HOME_Y_BEFORE_X) + || homeY + #endif + ) { + + #if ENABLED(DUAL_X_CARRIAGE) + + // Always home the 2nd (right) extruder first + active_extruder = 1; + HOMEAXIS(X); + + // Remember this extruder's position for later tool change + inactive_extruder_x_pos = current_position[X_AXIS]; + + // Home the 1st (left) extruder + active_extruder = 0; + HOMEAXIS(X); + + // Consider the active extruder to be parked + COPY(raised_parked_position, current_position); + delayed_move_time = 0; + active_extruder_parked = true; + + #else + + HOMEAXIS(X); + + #endif + } + + // Home Y (after X) + #if DISABLED(HOME_Y_BEFORE_X) + if (home_all || homeY) HOMEAXIS(Y); + #endif + + // Home Z last if homing towards the bed + #if Z_HOME_DIR < 0 + if (home_all || homeZ) { + #if ENABLED(Z_SAFE_HOMING) + home_z_safely(); + #else + HOMEAXIS(Z); + #endif + + #if HOMING_Z_WITH_PROBE && Z_AFTER_PROBING + move_z_after_probing(); + #endif + + } // home_all || homeZ + #endif // Z_HOME_DIR < 0 + + SYNC_PLAN_POSITION_KINEMATIC(); + + #endif // !DELTA (gcode_G28) + + endstops.not_homing(); + + #if ENABLED(DELTA) && ENABLED(DELTA_HOME_TO_SAFE_ZONE) + // move to a height where we can use the full xy-area + do_blocking_move_to_z(delta_clip_start_height); + #endif + + #if ENABLED(RESTORE_LEVELING_AFTER_G28) + set_bed_leveling_enabled(leveling_state_at_entry); + #endif + + clean_up_after_endstop_or_probe_move(); + + // Restore the active tool after homing + #if HOTENDS > 1 && (DISABLED(DELTA) || ENABLED(DELTA_HOME_TO_SAFE_ZONE)) + #if ENABLED(PARKING_EXTRUDER) + #define NO_FETCH false // fetch the previous toolhead + #else + #define NO_FETCH true + #endif + tool_change(old_tool_index, 0, NO_FETCH); + #endif + + lcd_refresh(); + + report_current_position(); + + #if ENABLED(NANODLP_Z_SYNC) + #if ENABLED(NANODLP_ALL_AXIS) + #define _HOME_SYNC true // For any axis, output sync text. + #else + #define _HOME_SYNC (home_all || homeZ) // Only for Z-axis + #endif + if (_HOME_SYNC) + SERIAL_ECHOLNPGM(MSG_Z_MOVE_COMP); + #endif + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("<<< gcode_G28"); + #endif +} // G28 + +void home_all_axes() { gcode_G28(true); } + +#if ENABLED(MESH_BED_LEVELING) || ENABLED(PROBE_MANUALLY) + + inline void _manual_goto_xy(const float &rx, const float &ry) { + + #if MANUAL_PROBE_HEIGHT > 0 + const float prev_z = current_position[Z_AXIS]; + do_blocking_move_to(rx, ry, MANUAL_PROBE_HEIGHT); + do_blocking_move_to_z(prev_z); + #else + do_blocking_move_to_xy(rx, ry); + #endif + + current_position[X_AXIS] = rx; + current_position[Y_AXIS] = ry; + + #if ENABLED(LCD_BED_LEVELING) + lcd_wait_for_move = false; + #endif + } + +#endif + +#if ENABLED(MESH_BED_LEVELING) + + // Save 130 bytes with non-duplication of PSTR + void echo_not_entered() { SERIAL_PROTOCOLLNPGM(" not entered."); } + + /** + * G29: Mesh-based Z probe, probes a grid and produces a + * mesh to compensate for variable bed height + * + * Parameters With MESH_BED_LEVELING: + * + * S0 Produce a mesh report + * S1 Start probing mesh points + * S2 Probe the next mesh point + * S3 Xn Yn Zn.nn Manually modify a single point + * S4 Zn.nn Set z offset. Positive away from bed, negative closer to bed. + * S5 Reset and disable mesh + * + * The S0 report the points as below + * + * +----> X-axis 1-n + * | + * | + * v Y-axis 1-n + * + */ + inline void gcode_G29() { + + static int mbl_probe_index = -1; + #if HAS_SOFTWARE_ENDSTOPS + static bool enable_soft_endstops; + #endif + + const MeshLevelingState state = (MeshLevelingState)parser.byteval('S', (int8_t)MeshReport); + if (!WITHIN(state, 0, 5)) { + SERIAL_PROTOCOLLNPGM("S out of range (0-5)."); + return; + } + + int8_t px, py; + + switch (state) { + case MeshReport: + if (leveling_is_valid()) { + SERIAL_PROTOCOLLNPAIR("State: ", planner.leveling_active ? MSG_ON : MSG_OFF); + mbl.report_mesh(); + } + else + SERIAL_PROTOCOLLNPGM("Mesh bed leveling has no data."); + break; + + case MeshStart: + mbl.reset(); + mbl_probe_index = 0; + enqueue_and_echo_commands_P(lcd_wait_for_move ? PSTR("G29 S2") : PSTR("G28\nG29 S2")); + break; + + case MeshNext: + if (mbl_probe_index < 0) { + SERIAL_PROTOCOLLNPGM("Start mesh probing with \"G29 S1\" first."); + return; + } + // For each G29 S2... + if (mbl_probe_index == 0) { + #if HAS_SOFTWARE_ENDSTOPS + // For the initial G29 S2 save software endstop state + enable_soft_endstops = soft_endstops_enabled; + #endif + } + else { + // For G29 S2 after adjusting Z. + mbl.set_zigzag_z(mbl_probe_index - 1, current_position[Z_AXIS]); + #if HAS_SOFTWARE_ENDSTOPS + soft_endstops_enabled = enable_soft_endstops; + #endif + } + // If there's another point to sample, move there with optional lift. + if (mbl_probe_index < GRID_MAX_POINTS) { + mbl.zigzag(mbl_probe_index, px, py); + _manual_goto_xy(mbl.index_to_xpos[px], mbl.index_to_ypos[py]); + + #if HAS_SOFTWARE_ENDSTOPS + // Disable software endstops to allow manual adjustment + // If G29 is not completed, they will not be re-enabled + soft_endstops_enabled = false; + #endif + + mbl_probe_index++; + } + else { + // One last "return to the bed" (as originally coded) at completion + current_position[Z_AXIS] = Z_MIN_POS + MANUAL_PROBE_HEIGHT; + buffer_line_to_current_position(); + stepper.synchronize(); + + // After recording the last point, activate home and activate + mbl_probe_index = -1; + SERIAL_PROTOCOLLNPGM("Mesh probing done."); + BUZZ(100, 659); + BUZZ(100, 698); + + home_all_axes(); + set_bed_leveling_enabled(true); + + #if ENABLED(MESH_G28_REST_ORIGIN) + current_position[Z_AXIS] = Z_MIN_POS; + set_destination_from_current(); + buffer_line_to_destination(homing_feedrate(Z_AXIS)); + stepper.synchronize(); + #endif + + #if ENABLED(LCD_BED_LEVELING) + lcd_wait_for_move = false; + #endif + } + break; + + case MeshSet: + if (parser.seenval('X')) { + px = parser.value_int() - 1; + if (!WITHIN(px, 0, GRID_MAX_POINTS_X - 1)) { + SERIAL_PROTOCOLLNPGM("X out of range (1-" STRINGIFY(GRID_MAX_POINTS_X) ")."); + return; + } + } + else { + SERIAL_CHAR('X'); echo_not_entered(); + return; + } + + if (parser.seenval('Y')) { + py = parser.value_int() - 1; + if (!WITHIN(py, 0, GRID_MAX_POINTS_Y - 1)) { + SERIAL_PROTOCOLLNPGM("Y out of range (1-" STRINGIFY(GRID_MAX_POINTS_Y) ")."); + return; + } + } + else { + SERIAL_CHAR('Y'); echo_not_entered(); + return; + } + + if (parser.seenval('Z')) + mbl.z_values[px][py] = parser.value_linear_units(); + else { + SERIAL_CHAR('Z'); echo_not_entered(); + return; + } + break; + + case MeshSetZOffset: + if (parser.seenval('Z')) + mbl.z_offset = parser.value_linear_units(); + else { + SERIAL_CHAR('Z'); echo_not_entered(); + return; + } + break; + + case MeshReset: + reset_bed_level(); + break; + + } // switch(state) + + if (state == MeshStart || state == MeshNext) { + SERIAL_PROTOCOLPAIR("MBL G29 point ", min(mbl_probe_index, GRID_MAX_POINTS)); + SERIAL_PROTOCOLLNPAIR(" of ", int(GRID_MAX_POINTS)); + } + + report_current_position(); + } + +#elif OLDSCHOOL_ABL + + #if ABL_GRID + #if ENABLED(PROBE_Y_FIRST) + #define PR_OUTER_VAR xCount + #define PR_OUTER_END abl_grid_points_x + #define PR_INNER_VAR yCount + #define PR_INNER_END abl_grid_points_y + #else + #define PR_OUTER_VAR yCount + #define PR_OUTER_END abl_grid_points_y + #define PR_INNER_VAR xCount + #define PR_INNER_END abl_grid_points_x + #endif + #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 + * + * 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) + * + * 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. + * + */ + inline void gcode_G29() { + + #if ENABLED(DEBUG_LEVELING_FEATURE) || ENABLED(PROBE_MANUALLY) + const bool seenQ = parser.seen('Q'); + #else + constexpr bool seenQ = false; + #endif + + // G29 Q is also available if debugging + #if ENABLED(DEBUG_LEVELING_FEATURE) + const uint8_t old_debug_flags = marlin_debug_flags; + if (seenQ) marlin_debug_flags |= DEBUG_LEVELING; + if (DEBUGGING(LEVELING)) { + DEBUG_POS(">>> G29", current_position); + log_machine_info(); + } + marlin_debug_flags = old_debug_flags; + #if DISABLED(PROBE_MANUALLY) + if (seenQ) return; + #endif + #endif + + #if ENABLED(PROBE_MANUALLY) + const bool seenA = parser.seen('A'); + #else + constexpr bool seenA = false; + #endif + + const bool no_action = seenA || seenQ, + faux = + #if ENABLED(DEBUG_LEVELING_FEATURE) && DISABLED(PROBE_MANUALLY) + parser.boolval('C') + #else + no_action + #endif + ; + + // Don't allow auto-leveling without homing first + if (axis_unhomed_error()) return; + + // Define local vars 'static' for manual probing, 'auto' otherwise + #if ENABLED(PROBE_MANUALLY) + #define ABL_VAR static + #else + #define ABL_VAR + #endif + + ABL_VAR int verbose_level; + ABL_VAR float xProbe, yProbe, measured_z; + ABL_VAR bool dryrun, abl_should_enable; + + #if ENABLED(PROBE_MANUALLY) || ENABLED(AUTO_BED_LEVELING_LINEAR) + ABL_VAR int16_t abl_probe_index; + #endif + + #if HAS_SOFTWARE_ENDSTOPS && ENABLED(PROBE_MANUALLY) + ABL_VAR bool enable_soft_endstops = true; + #endif + + #if ABL_GRID + + #if ENABLED(PROBE_MANUALLY) + ABL_VAR uint8_t PR_OUTER_VAR; + ABL_VAR int8_t PR_INNER_VAR; + #endif + + ABL_VAR int left_probe_bed_position, right_probe_bed_position, front_probe_bed_position, back_probe_bed_position; + ABL_VAR float xGridSpacing = 0, yGridSpacing = 0; + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + ABL_VAR uint8_t abl_grid_points_x = GRID_MAX_POINTS_X, + abl_grid_points_y = GRID_MAX_POINTS_Y; + ABL_VAR bool do_topography_map; + #else // Bilinear + uint8_t constexpr abl_grid_points_x = GRID_MAX_POINTS_X, + abl_grid_points_y = GRID_MAX_POINTS_Y; + #endif + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + ABL_VAR int16_t abl_points; + #elif ENABLED(PROBE_MANUALLY) // Bilinear + int16_t constexpr abl_points = GRID_MAX_POINTS; + #endif + + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + + ABL_VAR float zoffset; + + #elif ENABLED(AUTO_BED_LEVELING_LINEAR) + + ABL_VAR int indexIntoAB[GRID_MAX_POINTS_X][GRID_MAX_POINTS_Y]; + + ABL_VAR 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 + + #elif ENABLED(AUTO_BED_LEVELING_3POINT) + + #if ENABLED(PROBE_MANUALLY) + int8_t constexpr abl_points = 3; // used to show total points + #endif + + // Probe at 3 arbitrary points + ABL_VAR vector_3 points[3] = { + vector_3(PROBE_PT_1_X, PROBE_PT_1_Y, 0), + vector_3(PROBE_PT_2_X, PROBE_PT_2_Y, 0), + vector_3(PROBE_PT_3_X, PROBE_PT_3_Y, 0) + }; + + #endif // AUTO_BED_LEVELING_3POINT + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + struct linear_fit_data lsf_results; + incremental_LSF_reset(&lsf_results); + #endif + + /** + * On the initial G29 fetch command parameters. + */ + if (!g29_in_progress) { + + #if ENABLED(PROBE_MANUALLY) || ENABLED(AUTO_BED_LEVELING_LINEAR) + abl_probe_index = -1; + #endif + + abl_should_enable = planner.leveling_active; + + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + + const bool seen_w = parser.seen('W'); + if (seen_w) { + if (!leveling_is_valid()) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM("No bilinear grid"); + return; + } + + const float rz = parser.seenval('Z') ? RAW_Z_POSITION(parser.value_linear_units()) : current_position[Z_AXIS]; + if (!WITHIN(rz, -10, 10)) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM("Bad Z value"); + return; + } + + 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); + + if (!isnan(rx) && !isnan(ry)) { + // Get nearest i / j from rx / ry + i = (rx - bilinear_start[X_AXIS] + 0.5 * xGridSpacing) / xGridSpacing; + j = (ry - bilinear_start[Y_AXIS] + 0.5 * yGridSpacing) / yGridSpacing; + i = constrain(i, 0, GRID_MAX_POINTS_X - 1); + j = constrain(j, 0, GRID_MAX_POINTS_Y - 1); + } + if (WITHIN(i, 0, GRID_MAX_POINTS_X - 1) && WITHIN(j, 0, GRID_MAX_POINTS_Y)) { + set_bed_leveling_enabled(false); + z_values[i][j] = rz; + #if ENABLED(ABL_BILINEAR_SUBDIVISION) + bed_level_virt_interpolate(); + #endif + set_bed_leveling_enabled(abl_should_enable); + if (abl_should_enable) report_current_position(); + } + return; + } // parser.seen('W') + + #else + + constexpr bool seen_w = false; + + #endif + + // Jettison bed leveling data + if (!seen_w && parser.seen('J')) { + reset_bed_level(); + return; + } + + verbose_level = parser.intval('V'); + if (!WITHIN(verbose_level, 0, 4)) { + SERIAL_PROTOCOLLNPGM("?(V)erbose level is implausible (0-4)."); + return; + } + + dryrun = parser.boolval('D') + #if ENABLED(PROBE_MANUALLY) + || no_action + #endif + ; + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + + do_topography_map = 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_x = parser.intval('X', GRID_MAX_POINTS_X); + abl_grid_points_y = parser.intval('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_PROTOCOLLNPGM("?Probe points (X) is implausible (2-" STRINGIFY(GRID_MAX_POINTS_X) ")."); + return; + } + if (!WITHIN(abl_grid_points_y, 2, GRID_MAX_POINTS_Y)) { + SERIAL_PROTOCOLLNPGM("?Probe points (Y) is implausible (2-" STRINGIFY(GRID_MAX_POINTS_Y) ")."); + return; + } + + abl_points = abl_grid_points_x * abl_grid_points_y; + mean = 0; + + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + + zoffset = parser.linearval('Z'); + + #endif + + #if ABL_GRID + + xy_probe_feedrate_mm_s = MMM_TO_MMS(parser.linearval('S', XY_PROBE_SPEED)); + + left_probe_bed_position = parser.seenval('L') ? (int)RAW_X_POSITION(parser.value_linear_units()) : LEFT_PROBE_BED_POSITION; + right_probe_bed_position = parser.seenval('R') ? (int)RAW_X_POSITION(parser.value_linear_units()) : RIGHT_PROBE_BED_POSITION; + front_probe_bed_position = parser.seenval('F') ? (int)RAW_Y_POSITION(parser.value_linear_units()) : FRONT_PROBE_BED_POSITION; + back_probe_bed_position = parser.seenval('B') ? (int)RAW_Y_POSITION(parser.value_linear_units()) : BACK_PROBE_BED_POSITION; + + if ( !position_is_reachable_by_probe(left_probe_bed_position, front_probe_bed_position) + || !position_is_reachable_by_probe(right_probe_bed_position, back_probe_bed_position)) { + SERIAL_PROTOCOLLNPGM("? (L,R,F,B) out of bounds."); + return; + } + + // probe at the points of a lattice grid + xGridSpacing = (right_probe_bed_position - left_probe_bed_position) / (abl_grid_points_x - 1); + yGridSpacing = (back_probe_bed_position - front_probe_bed_position) / (abl_grid_points_y - 1); + + #endif // ABL_GRID + + if (verbose_level > 0) { + SERIAL_PROTOCOLPGM("G29 Auto Bed Leveling"); + if (dryrun) SERIAL_PROTOCOLPGM(" (DRYRUN)"); + SERIAL_EOL(); + } + + stepper.synchronize(); + + // Disable auto bed leveling during G29. + // Be formal so G29 can be done successively without G28. + if (!no_action) set_bed_leveling_enabled(false); + + #if HAS_BED_PROBE + // Deploy the probe. Probe will raise if needed. + if (DEPLOY_PROBE()) { + set_bed_leveling_enabled(abl_should_enable); + return; + } + #endif + + if (!faux) setup_for_endstop_or_probe_move(); + + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + + #if ENABLED(PROBE_MANUALLY) + if (!no_action) + #endif + if ( xGridSpacing != bilinear_grid_spacing[X_AXIS] + || yGridSpacing != bilinear_grid_spacing[Y_AXIS] + || left_probe_bed_position != bilinear_start[X_AXIS] + || front_probe_bed_position != bilinear_start[Y_AXIS] + ) { + // Reset grid to 0.0 or "not probed". (Also disables ABL) + reset_bed_level(); + + // Initialize a grid with the given dimensions + bilinear_grid_spacing[X_AXIS] = xGridSpacing; + bilinear_grid_spacing[Y_AXIS] = yGridSpacing; + bilinear_start[X_AXIS] = left_probe_bed_position; + bilinear_start[Y_AXIS] = front_probe_bed_position; + + // Can't re-enable (on error) until the new grid is written + abl_should_enable = false; + } + + #endif // AUTO_BED_LEVELING_BILINEAR + + #if ENABLED(AUTO_BED_LEVELING_3POINT) + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("> 3-point Leveling"); + #endif + + // Probe at 3 arbitrary points + points[0].z = points[1].z = points[2].z = 0; + + #endif // AUTO_BED_LEVELING_3POINT + + } // !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_probe_index; + g29_in_progress = true; + } + + // Abort current G29 procedure, go back to idle state + if (seenA && g29_in_progress) { + SERIAL_PROTOCOLLNPGM("Manual G29 aborted"); + #if HAS_SOFTWARE_ENDSTOPS + soft_endstops_enabled = enable_soft_endstops; + #endif + set_bed_leveling_enabled(abl_should_enable); + g29_in_progress = false; + #if ENABLED(LCD_BED_LEVELING) + lcd_wait_for_move = false; + #endif + } + + // Query G29 status + if (verbose_level || seenQ) { + SERIAL_PROTOCOLPGM("Manual G29 "); + if (g29_in_progress) { + SERIAL_PROTOCOLPAIR("point ", min(abl_probe_index + 1, abl_points)); + SERIAL_PROTOCOLLNPAIR(" of ", abl_points); + } + else + SERIAL_PROTOCOLLNPGM("idle"); + } + + if (no_action) return; + + if (abl_probe_index == 0) { + // For the initial G29 save software endstop state + #if HAS_SOFTWARE_ENDSTOPS + enable_soft_endstops = soft_endstops_enabled; + #endif + } + else { + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) || ENABLED(AUTO_BED_LEVELING_3POINT) + const uint16_t index = abl_probe_index - 1; + #endif + + // For G29 after adjusting Z. + // Save the previous Z before going to the next point + measured_z = current_position[Z_AXIS]; + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + + mean += measured_z; + eqnBVector[index] = measured_z; + eqnAMatrix[index + 0 * abl_points] = xProbe; + eqnAMatrix[index + 1 * abl_points] = yProbe; + eqnAMatrix[index + 2 * abl_points] = 1; + + incremental_LSF(&lsf_results, xProbe, yProbe, measured_z); + + #elif ENABLED(AUTO_BED_LEVELING_3POINT) + + points[index].z = measured_z; + + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + + z_values[xCount][yCount] = measured_z + zoffset; + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_PROTOCOLPAIR("Save X", xCount); + SERIAL_PROTOCOLPAIR(" Y", yCount); + SERIAL_PROTOCOLLNPAIR(" Z", measured_z + zoffset); + } + #endif + + #endif + } + + // + // If there's another point to sample, move there with optional lift. + // + + #if ABL_GRID + + // Skip any unreachable points + while (abl_probe_index < abl_points) { + + // Set xCount, yCount based on abl_probe_index, with zig-zag + PR_OUTER_VAR = abl_probe_index / PR_INNER_END; + PR_INNER_VAR = abl_probe_index - (PR_OUTER_VAR * PR_INNER_END); + + // Probe in reverse order for every other row/column + bool zig = (PR_OUTER_VAR & 1); // != ((PR_OUTER_END) & 1); + + if (zig) PR_INNER_VAR = (PR_INNER_END - 1) - PR_INNER_VAR; + + const float xBase = xCount * xGridSpacing + left_probe_bed_position, + yBase = yCount * yGridSpacing + front_probe_bed_position; + + xProbe = FLOOR(xBase + (xBase < 0 ? 0 : 0.5)); + yProbe = FLOOR(yBase + (yBase < 0 ? 0 : 0.5)); + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + indexIntoAB[xCount][yCount] = abl_probe_index; + #endif + + // Keep looping till a reachable point is found + if (position_is_reachable(xProbe, yProbe)) break; + ++abl_probe_index; + } + + // Is there a next point to move to? + if (abl_probe_index < abl_points) { + _manual_goto_xy(xProbe, yProbe); // Can be used here too! + #if HAS_SOFTWARE_ENDSTOPS + // Disable software endstops to allow manual adjustment + // If G29 is not completed, they will not be re-enabled + soft_endstops_enabled = false; + #endif + return; + } + else { + + // Leveling done! Fall through to G29 finishing code below + + SERIAL_PROTOCOLLNPGM("Grid probing done."); + + // Re-enable software endstops, if needed + #if HAS_SOFTWARE_ENDSTOPS + soft_endstops_enabled = enable_soft_endstops; + #endif + } + + #elif ENABLED(AUTO_BED_LEVELING_3POINT) + + // Probe at 3 arbitrary points + if (abl_probe_index < abl_points) { + xProbe = points[abl_probe_index].x; + yProbe = points[abl_probe_index].y; + _manual_goto_xy(xProbe, yProbe); + #if HAS_SOFTWARE_ENDSTOPS + // Disable software endstops to allow manual adjustment + // If G29 is not completed, they will not be re-enabled + soft_endstops_enabled = false; + #endif + return; + } + else { + + SERIAL_PROTOCOLLNPGM("3-point probing done."); + + // Re-enable software endstops, if needed + #if HAS_SOFTWARE_ENDSTOPS + soft_endstops_enabled = enable_soft_endstops; + #endif + + if (!dryrun) { + vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal(); + if (planeNormal.z < 0) { + planeNormal.x *= -1; + planeNormal.y *= -1; + planeNormal.z *= -1; + } + planner.bed_level_matrix = matrix_3x3::create_look_at(planeNormal); + + // Can't re-enable (on error) until the new grid is written + abl_should_enable = false; + } + + } + + #endif // AUTO_BED_LEVELING_3POINT + + #else // !PROBE_MANUALLY + { + const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE; + + measured_z = 0; + + #if ABL_GRID + + bool zig = PR_OUTER_END & 1; // Always end at RIGHT and BACK_PROBE_BED_POSITION + + measured_z = 0; + + // Outer loop is Y with PROBE_Y_FIRST disabled + for (uint8_t PR_OUTER_VAR = 0; PR_OUTER_VAR < PR_OUTER_END && !isnan(measured_z); PR_OUTER_VAR++) { + + int8_t inStart, inStop, inInc; + + if (zig) { // away from origin + inStart = 0; + inStop = PR_INNER_END; + inInc = 1; + } + else { // towards origin + inStart = PR_INNER_END - 1; + inStop = -1; + inInc = -1; + } + + zig ^= true; // zag + + // Inner loop is Y with PROBE_Y_FIRST enabled + for (int8_t PR_INNER_VAR = inStart; PR_INNER_VAR != inStop; PR_INNER_VAR += inInc) { + + float xBase = left_probe_bed_position + xGridSpacing * xCount, + yBase = front_probe_bed_position + yGridSpacing * yCount; + + xProbe = FLOOR(xBase + (xBase < 0 ? 0 : 0.5)); + yProbe = FLOOR(yBase + (yBase < 0 ? 0 : 0.5)); + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + indexIntoAB[xCount][yCount] = ++abl_probe_index; // 0... + #endif + + #if IS_KINEMATIC + // Avoid probing outside the round or hexagonal area + if (!position_is_reachable_by_probe(xProbe, yProbe)) continue; + #endif + + measured_z = faux ? 0.001 * random(-100, 101) : probe_pt(xProbe, yProbe, raise_after, verbose_level); + + if (isnan(measured_z)) { + set_bed_leveling_enabled(abl_should_enable); + break; + } + + #if ENABLED(AUTO_BED_LEVELING_LINEAR) + + mean += measured_z; + eqnBVector[abl_probe_index] = measured_z; + eqnAMatrix[abl_probe_index + 0 * abl_points] = xProbe; + eqnAMatrix[abl_probe_index + 1 * abl_points] = yProbe; + eqnAMatrix[abl_probe_index + 2 * abl_points] = 1; + + incremental_LSF(&lsf_results, xProbe, yProbe, measured_z); + + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + + z_values[xCount][yCount] = measured_z + zoffset; + + #endif + + abl_should_enable = false; + idle(); + + } // inner + } // outer + + #elif ENABLED(AUTO_BED_LEVELING_3POINT) + + // Probe at 3 arbitrary points + + for (uint8_t i = 0; i < 3; ++i) { + // Retain the last probe position + xProbe = points[i].x; + yProbe = points[i].y; + measured_z = faux ? 0.001 * random(-100, 101) : probe_pt(xProbe, yProbe, raise_after, verbose_level); + if (isnan(measured_z)) { + set_bed_leveling_enabled(abl_should_enable); + break; + } + points[i].z = measured_z; + } + + if (!dryrun && !isnan(measured_z)) { + vector_3 planeNormal = vector_3::cross(points[0] - points[1], points[2] - points[1]).get_normal(); + if (planeNormal.z < 0) { + planeNormal.x *= -1; + planeNormal.y *= -1; + planeNormal.z *= -1; + } + planner.bed_level_matrix = matrix_3x3::create_look_at(planeNormal); + + // Can't re-enable (on error) until the new grid is written + abl_should_enable = false; + } + + #endif // AUTO_BED_LEVELING_3POINT + + // Stow the probe. No raise for FIX_MOUNTED_PROBE. + if (STOW_PROBE()) { + set_bed_leveling_enabled(abl_should_enable); + 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 ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("> probing complete", current_position); + #endif + + #if ENABLED(PROBE_MANUALLY) + g29_in_progress = false; + #if ENABLED(LCD_BED_LEVELING) + lcd_wait_for_move = false; + #endif + #endif + + // Calculate leveling, print reports, correct the position + if (!isnan(measured_z)) { + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + + if (!dryrun) extrapolate_unprobed_bed_level(); + print_bilinear_leveling_grid(); + + refresh_bed_level(); + + #if ENABLED(ABL_BILINEAR_SUBDIVISION) + print_bilinear_leveling_grid_virt(); + #endif + + #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 + */ + float plane_equation_coefficients[3]; + + finish_incremental_LSF(&lsf_results); + plane_equation_coefficients[0] = -lsf_results.A; // We should be able to eliminate the '-' on these three lines and down below + plane_equation_coefficients[1] = -lsf_results.B; // but that is not yet tested. + plane_equation_coefficients[2] = -lsf_results.D; + + mean /= abl_points; + + if (verbose_level) { + SERIAL_PROTOCOLPGM("Eqn coefficients: a: "); + SERIAL_PROTOCOL_F(plane_equation_coefficients[0], 8); + SERIAL_PROTOCOLPGM(" b: "); + SERIAL_PROTOCOL_F(plane_equation_coefficients[1], 8); + SERIAL_PROTOCOLPGM(" d: "); + SERIAL_PROTOCOL_F(plane_equation_coefficients[2], 8); + SERIAL_EOL(); + if (verbose_level > 2) { + SERIAL_PROTOCOLPGM("Mean of sampled points: "); + SERIAL_PROTOCOL_F(mean, 8); + SERIAL_EOL(); + } + } + + // Create the matrix but don't correct the position yet + if (!dryrun) + planner.bed_level_matrix = matrix_3x3::create_look_at( + vector_3(-plane_equation_coefficients[0], -plane_equation_coefficients[1], 1) // We can eliminate the '-' here and up above + ); + + // Show the Topography map if enabled + if (do_topography_map) { + + SERIAL_PROTOCOLLNPGM("\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)"); + + float min_diff = 999; + + for (int8_t yy = abl_grid_points_y - 1; yy >= 0; yy--) { + for (uint8_t xx = 0; xx < abl_grid_points_x; xx++) { + int ind = indexIntoAB[xx][yy]; + float diff = eqnBVector[ind] - mean, + x_tmp = eqnAMatrix[ind + 0 * abl_points], + y_tmp = eqnAMatrix[ind + 1 * abl_points], + z_tmp = 0; + + apply_rotation_xyz(planner.bed_level_matrix, x_tmp, y_tmp, z_tmp); + + NOMORE(min_diff, eqnBVector[ind] - z_tmp); + + if (diff >= 0.0) + SERIAL_PROTOCOLPGM(" +"); // Include + for column alignment + else + SERIAL_PROTOCOLCHAR(' '); + SERIAL_PROTOCOL_F(diff, 5); + } // xx + SERIAL_EOL(); + } // yy + SERIAL_EOL(); + + if (verbose_level > 3) { + SERIAL_PROTOCOLLNPGM("\nCorrected Bed Height vs. Bed Topology:"); + + for (int8_t yy = abl_grid_points_y - 1; yy >= 0; yy--) { + for (uint8_t xx = 0; xx < abl_grid_points_x; xx++) { + int ind = indexIntoAB[xx][yy]; + float x_tmp = eqnAMatrix[ind + 0 * abl_points], + y_tmp = eqnAMatrix[ind + 1 * abl_points], + z_tmp = 0; + + apply_rotation_xyz(planner.bed_level_matrix, x_tmp, y_tmp, z_tmp); + + float diff = eqnBVector[ind] - z_tmp - min_diff; + if (diff >= 0.0) + SERIAL_PROTOCOLPGM(" +"); + // Include + for column alignment + else + SERIAL_PROTOCOLCHAR(' '); + SERIAL_PROTOCOL_F(diff, 5); + } // xx + SERIAL_EOL(); + } // yy + SERIAL_EOL(); + } + } //do_topography_map + + #endif // AUTO_BED_LEVELING_LINEAR + + #if ABL_PLANAR + + // For LINEAR and 3POINT leveling correct the current position + + if (verbose_level > 0) + planner.bed_level_matrix.debug(PSTR("\n\nBed Level Correction Matrix:")); + + if (!dryrun) { + // + // Correct the current XYZ position based on the tilted plane. + // + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("G29 uncorrected XYZ", current_position); + #endif + + float converted[XYZ]; + COPY(converted, current_position); + + planner.leveling_active = true; + planner.unapply_leveling(converted); // use conversion machinery + planner.leveling_active = false; + + // Use the last measured distance to the bed, if possible + if ( NEAR(current_position[X_AXIS], xProbe - (X_PROBE_OFFSET_FROM_EXTRUDER)) + && NEAR(current_position[Y_AXIS], yProbe - (Y_PROBE_OFFSET_FROM_EXTRUDER)) + ) { + const float simple_z = current_position[Z_AXIS] - measured_z; + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR("Z from Probe:", simple_z); + SERIAL_ECHOPAIR(" Matrix:", converted[Z_AXIS]); + SERIAL_ECHOLNPAIR(" Discrepancy:", simple_z - converted[Z_AXIS]); + } + #endif + converted[Z_AXIS] = simple_z; + } + + // The rotated XY and corrected Z are now current_position + COPY(current_position, converted); + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("G29 corrected XYZ", current_position); + #endif + } + + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + + if (!dryrun) { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPAIR("G29 uncorrected Z:", current_position[Z_AXIS]); + #endif + + // Unapply the offset because it is going to be immediately applied + // and cause compensation movement in Z + current_position[Z_AXIS] -= bilinear_z_offset(current_position); + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPAIR(" corrected Z:", current_position[Z_AXIS]); + #endif + } + + #endif // ABL_PLANAR + + #ifdef Z_PROBE_END_SCRIPT + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPAIR("Z Probe End Script: ", Z_PROBE_END_SCRIPT); + #endif + enqueue_and_echo_commands_P(PSTR(Z_PROBE_END_SCRIPT)); + stepper.synchronize(); + #endif + + // Auto Bed Leveling is complete! Enable if possible. + planner.leveling_active = dryrun ? abl_should_enable : true; + } // !isnan(measured_z) + + // Restore state after probing + if (!faux) clean_up_after_endstop_or_probe_move(); + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("<<< G29"); + #endif + + KEEPALIVE_STATE(IN_HANDLER); + + if (planner.leveling_active) + SYNC_PLAN_POSITION_KINEMATIC(); + + #if HAS_BED_PROBE && Z_AFTER_PROBING + move_z_after_probing(); + #endif + + report_current_position(); + } + +#endif // OLDSCHOOL_ABL + +#if HAS_BED_PROBE + + /** + * G30: Do a single Z probe at the current XY + * + * Parameters: + * + * X Probe X position (default current X) + * Y Probe Y position (default current Y) + * E Engage the probe for each probe + */ + inline void gcode_G30() { + const float xpos = parser.linearval('X', current_position[X_AXIS] + X_PROBE_OFFSET_FROM_EXTRUDER), + ypos = parser.linearval('Y', current_position[Y_AXIS] + Y_PROBE_OFFSET_FROM_EXTRUDER); + + if (!position_is_reachable_by_probe(xpos, ypos)) return; + + // Disable leveling so the planner won't mess with us + #if HAS_LEVELING + set_bed_leveling_enabled(false); + #endif + + setup_for_endstop_or_probe_move(); + + const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_NONE; + const float measured_z = probe_pt(xpos, ypos, raise_after, parser.intval('V', 1)); + + if (!isnan(measured_z)) { + SERIAL_PROTOCOLPAIR("Bed X: ", FIXFLOAT(xpos)); + SERIAL_PROTOCOLPAIR(" Y: ", FIXFLOAT(ypos)); + SERIAL_PROTOCOLLNPAIR(" Z: ", FIXFLOAT(measured_z)); + } + + clean_up_after_endstop_or_probe_move(); + + #if Z_AFTER_PROBING + if (raise_after == PROBE_PT_STOW) move_z_after_probing(); + #endif + + report_current_position(); + } + + #if ENABLED(Z_PROBE_SLED) + + /** + * G31: Deploy the Z probe + */ + inline void gcode_G31() { DEPLOY_PROBE(); } + + /** + * G32: Stow the Z probe + */ + inline void gcode_G32() { STOW_PROBE(); } + + #endif // Z_PROBE_SLED + +#endif // HAS_BED_PROBE + +#if ENABLED(DELTA_AUTO_CALIBRATION) + + constexpr uint8_t _7P_STEP = 1, // 7-point step - to change number of calibration points + _4P_STEP = _7P_STEP * 2, // 4-point step + NPP = _7P_STEP * 6; // number of calibration points on the radius + enum CalEnum : char { // the 7 main calibration points - add definitions if needed + CEN = 0, + __A = 1, + _AB = __A + _7P_STEP, + __B = _AB + _7P_STEP, + _BC = __B + _7P_STEP, + __C = _BC + _7P_STEP, + _CA = __C + _7P_STEP, + }; + + #define LOOP_CAL_PT(VAR, S, N) for (uint8_t VAR=S; VAR<=NPP; VAR+=N) + #define F_LOOP_CAL_PT(VAR, S, N) for (float VAR=S; VARCEN+0.9999; VAR-=N) + #define LOOP_CAL_ALL(VAR) LOOP_CAL_PT(VAR, CEN, 1) + #define LOOP_CAL_RAD(VAR) LOOP_CAL_PT(VAR, __A, _7P_STEP) + #define LOOP_CAL_ACT(VAR, _4P, _OP) LOOP_CAL_PT(VAR, _OP ? _AB : __A, _4P ? _4P_STEP : _7P_STEP) + + static void print_signed_float(const char * const prefix, const float &f) { + SERIAL_PROTOCOLPGM(" "); + serialprintPGM(prefix); + SERIAL_PROTOCOLCHAR(':'); + if (f >= 0) SERIAL_CHAR('+'); + SERIAL_PROTOCOL_F(f, 2); + } + + static void print_G33_settings(const bool end_stops, const bool tower_angles) { + SERIAL_PROTOCOLPAIR(".Height:", delta_height); + if (end_stops) { + print_signed_float(PSTR("Ex"), delta_endstop_adj[A_AXIS]); + print_signed_float(PSTR("Ey"), delta_endstop_adj[B_AXIS]); + print_signed_float(PSTR("Ez"), delta_endstop_adj[C_AXIS]); + } + if (end_stops && tower_angles) { + SERIAL_PROTOCOLPAIR(" Radius:", delta_radius); + SERIAL_EOL(); + SERIAL_CHAR('.'); + SERIAL_PROTOCOL_SP(13); + } + if (tower_angles) { + print_signed_float(PSTR("Tx"), delta_tower_angle_trim[A_AXIS]); + print_signed_float(PSTR("Ty"), delta_tower_angle_trim[B_AXIS]); + print_signed_float(PSTR("Tz"), delta_tower_angle_trim[C_AXIS]); + } + if ((!end_stops && tower_angles) || (end_stops && !tower_angles)) { // XOR + SERIAL_PROTOCOLPAIR(" Radius:", delta_radius); + } + SERIAL_EOL(); + } + + static void print_G33_results(const float z_at_pt[NPP + 1], const bool tower_points, const bool opposite_points) { + SERIAL_PROTOCOLPGM(". "); + print_signed_float(PSTR("c"), z_at_pt[CEN]); + if (tower_points) { + print_signed_float(PSTR(" x"), z_at_pt[__A]); + print_signed_float(PSTR(" y"), z_at_pt[__B]); + print_signed_float(PSTR(" z"), z_at_pt[__C]); + } + if (tower_points && opposite_points) { + SERIAL_EOL(); + SERIAL_CHAR('.'); + SERIAL_PROTOCOL_SP(13); + } + if (opposite_points) { + print_signed_float(PSTR("yz"), z_at_pt[_BC]); + print_signed_float(PSTR("zx"), z_at_pt[_CA]); + print_signed_float(PSTR("xy"), z_at_pt[_AB]); + } + SERIAL_EOL(); + } + + /** + * After G33: + * - Move to the print ceiling (DELTA_HOME_TO_SAFE_ZONE only) + * - Stow the probe + * - Restore endstops state + * - Select the old tool, if needed + */ + static void G33_cleanup( + #if HOTENDS > 1 + const uint8_t old_tool_index + #endif + ) { + #if ENABLED(DELTA_HOME_TO_SAFE_ZONE) + do_blocking_move_to_z(delta_clip_start_height); + #endif + STOW_PROBE(); + clean_up_after_endstop_or_probe_move(); + #if HOTENDS > 1 + tool_change(old_tool_index, 0, true); + #endif + } + + inline float calibration_probe(const float nx, const float ny, const bool stow) { + #if HAS_BED_PROBE + return probe_pt(nx, ny, stow ? PROBE_PT_STOW : PROBE_PT_RAISE, 0, false); + #else + UNUSED(stow); + return lcd_probe_pt(nx, ny); + #endif + } + + static float probe_G33_points(float z_at_pt[NPP + 1], const int8_t probe_points, const bool towers_set, const bool stow_after_each) { + const bool _0p_calibration = probe_points == 0, + _1p_calibration = probe_points == 1, + _4p_calibration = probe_points == 2, + _4p_opposite_points = _4p_calibration && !towers_set, + _7p_calibration = probe_points >= 3 || probe_points == 0, + _7p_no_intermediates = probe_points == 3, + _7p_1_intermediates = probe_points == 4, + _7p_2_intermediates = probe_points == 5, + _7p_4_intermediates = probe_points == 6, + _7p_6_intermediates = probe_points == 7, + _7p_8_intermediates = probe_points == 8, + _7p_11_intermediates = probe_points == 9, + _7p_14_intermediates = probe_points == 10, + _7p_intermed_points = probe_points >= 4, + _7p_6_centre = probe_points >= 5 && probe_points <= 7, + _7p_9_centre = probe_points >= 8; + + LOOP_CAL_ALL(axis) z_at_pt[axis] = 0.0; + + if (!_0p_calibration) { + + if (!_7p_no_intermediates && !_7p_4_intermediates && !_7p_11_intermediates) { // probe the center + z_at_pt[CEN] += calibration_probe(0, 0, stow_after_each); + if (isnan(z_at_pt[CEN])) return NAN; + } + + if (_7p_calibration) { // probe extra center points + const float start = _7p_9_centre ? _CA + _7P_STEP / 3.0 : _7p_6_centre ? _CA : __C, + steps = _7p_9_centre ? _4P_STEP / 3.0 : _7p_6_centre ? _7P_STEP : _4P_STEP; + I_LOOP_CAL_PT(axis, start, steps) { + const float a = RADIANS(210 + (360 / NPP) * (axis - 1)), + r = delta_calibration_radius * 0.1; + z_at_pt[CEN] += calibration_probe(cos(a) * r, sin(a) * r, stow_after_each); + if (isnan(z_at_pt[CEN])) return NAN; + } + z_at_pt[CEN] /= float(_7p_2_intermediates ? 7 : probe_points); + } + + if (!_1p_calibration) { // probe the radius + const CalEnum start = _4p_opposite_points ? _AB : __A; + const float steps = _7p_14_intermediates ? _7P_STEP / 15.0 : // 15r * 6 + 10c = 100 + _7p_11_intermediates ? _7P_STEP / 12.0 : // 12r * 6 + 9c = 81 + _7p_8_intermediates ? _7P_STEP / 9.0 : // 9r * 6 + 10c = 64 + _7p_6_intermediates ? _7P_STEP / 7.0 : // 7r * 6 + 7c = 49 + _7p_4_intermediates ? _7P_STEP / 5.0 : // 5r * 6 + 6c = 36 + _7p_2_intermediates ? _7P_STEP / 3.0 : // 3r * 6 + 7c = 25 + _7p_1_intermediates ? _7P_STEP / 2.0 : // 2r * 6 + 4c = 16 + _7p_no_intermediates ? _7P_STEP : // 1r * 6 + 3c = 9 + _4P_STEP; // .5r * 6 + 1c = 4 + bool zig_zag = true; + F_LOOP_CAL_PT(axis, start, _7p_9_centre ? steps * 3 : steps) { + const int8_t offset = _7p_9_centre ? 1 : 0; + for (int8_t circle = -offset; circle <= offset; circle++) { + const float a = RADIANS(210 + (360 / NPP) * (axis - 1)), + r = delta_calibration_radius * (1 + 0.1 * (zig_zag ? circle : - circle)), + interpol = fmod(axis, 1); + const float z_temp = calibration_probe(cos(a) * r, sin(a) * r, stow_after_each); + if (isnan(z_temp)) return NAN; + // split probe point to neighbouring calibration points + z_at_pt[uint8_t(round(axis - interpol + NPP - 1)) % NPP + 1] += z_temp * sq(cos(RADIANS(interpol * 90))); + z_at_pt[uint8_t(round(axis - interpol)) % NPP + 1] += z_temp * sq(sin(RADIANS(interpol * 90))); + } + zig_zag = !zig_zag; + } + if (_7p_intermed_points) + LOOP_CAL_RAD(axis) + z_at_pt[axis] /= _7P_STEP / steps; + } + + float S1 = z_at_pt[CEN], + S2 = sq(z_at_pt[CEN]); + int16_t N = 1; + if (!_1p_calibration) { // std dev from zero plane + LOOP_CAL_ACT(axis, _4p_calibration, _4p_opposite_points) { + S1 += z_at_pt[axis]; + S2 += sq(z_at_pt[axis]); + N++; + } + return round(SQRT(S2 / N) * 1000.0) / 1000.0 + 0.00001; + } + } + + return 0.00001; + } + + #if HAS_BED_PROBE + + static bool G33_auto_tune() { + float z_at_pt[NPP + 1] = { 0.0 }, + z_at_pt_base[NPP + 1] = { 0.0 }, + z_temp, h_fac = 0.0, r_fac = 0.0, a_fac = 0.0, norm = 0.8; + + #define ZP(N,I) ((N) * z_at_pt[I]) + #define Z06(I) ZP(6, I) + #define Z03(I) ZP(3, I) + #define Z02(I) ZP(2, I) + #define Z01(I) ZP(1, I) + #define Z32(I) ZP(3/2, I) + + SERIAL_PROTOCOLPGM("AUTO TUNE baseline"); + SERIAL_EOL(); + if (isnan(probe_G33_points(z_at_pt_base, 3, true, false))) return false; + print_G33_results(z_at_pt_base, true, true); + + LOOP_XYZ(axis) { + delta_endstop_adj[axis] -= 1.0; + recalc_delta_settings(); + + endstops.enable(true); + if (!home_delta()) return false; + endstops.not_homing(); + + SERIAL_PROTOCOLPGM("Tuning E"); + SERIAL_CHAR(tolower(axis_codes[axis])); + SERIAL_EOL(); + + if (isnan(probe_G33_points(z_at_pt, 3, true, false))) return false; + LOOP_CAL_ALL(axis) z_at_pt[axis] -= z_at_pt_base[axis]; + print_G33_results(z_at_pt, true, true); + delta_endstop_adj[axis] += 1.0; + recalc_delta_settings(); + switch (axis) { + case A_AXIS : + h_fac += 4.0 / (Z03(CEN) +Z01(__A) +Z32(_CA) +Z32(_AB)); // Offset by X-tower end-stop + break; + case B_AXIS : + h_fac += 4.0 / (Z03(CEN) +Z01(__B) +Z32(_BC) +Z32(_AB)); // Offset by Y-tower end-stop + break; + case C_AXIS : + h_fac += 4.0 / (Z03(CEN) +Z01(__C) +Z32(_BC) +Z32(_CA) ); // Offset by Z-tower end-stop + break; + } + } + h_fac /= 3.0; + h_fac *= norm; // Normalize to 1.02 for Kossel mini + + for (int8_t zig_zag = -1; zig_zag < 2; zig_zag += 2) { + delta_radius += 1.0 * zig_zag; + recalc_delta_settings(); + + endstops.enable(true); + if (!home_delta()) return false; + endstops.not_homing(); + + SERIAL_PROTOCOLPGM("Tuning R"); + SERIAL_PROTOCOL(zig_zag == -1 ? "-" : "+"); + SERIAL_EOL(); + if (isnan(probe_G33_points(z_at_pt, 3, true, false))) return false; + LOOP_CAL_ALL(axis) z_at_pt[axis] -= z_at_pt_base[axis]; + print_G33_results(z_at_pt, true, true); + delta_radius -= 1.0 * zig_zag; + recalc_delta_settings(); + r_fac -= zig_zag * 6.0 / (Z03(__A) +Z03(__B) +Z03(__C) +Z03(_BC) +Z03(_CA) +Z03(_AB)); // Offset by delta radius + } + r_fac /= 2.0; + r_fac *= 3 * norm; // Normalize to 2.25 for Kossel mini + + LOOP_XYZ(axis) { + delta_tower_angle_trim[axis] += 1.0; + delta_endstop_adj[(axis + 1) % 3] -= 1.0 / 4.5; + delta_endstop_adj[(axis + 2) % 3] += 1.0 / 4.5; + z_temp = MAX3(delta_endstop_adj[A_AXIS], delta_endstop_adj[B_AXIS], delta_endstop_adj[C_AXIS]); + delta_height -= z_temp; + LOOP_XYZ(axis) delta_endstop_adj[axis] -= z_temp; + recalc_delta_settings(); + + endstops.enable(true); + if (!home_delta()) return false; + endstops.not_homing(); + + SERIAL_PROTOCOLPGM("Tuning T"); + SERIAL_CHAR(tolower(axis_codes[axis])); + SERIAL_EOL(); + + if (isnan(probe_G33_points(z_at_pt, 3, true, false))) return false; + LOOP_CAL_ALL(axis) z_at_pt[axis] -= z_at_pt_base[axis]; + print_G33_results(z_at_pt, true, true); + + delta_tower_angle_trim[axis] -= 1.0; + delta_endstop_adj[(axis+1) % 3] += 1.0/4.5; + delta_endstop_adj[(axis+2) % 3] -= 1.0/4.5; + z_temp = MAX3(delta_endstop_adj[A_AXIS], delta_endstop_adj[B_AXIS], delta_endstop_adj[C_AXIS]); + delta_height -= z_temp; + LOOP_XYZ(axis) delta_endstop_adj[axis] -= z_temp; + recalc_delta_settings(); + switch (axis) { + case A_AXIS : + a_fac += 4.0 / ( Z06(__B) -Z06(__C) +Z06(_CA) -Z06(_AB)); // Offset by alpha tower angle + break; + case B_AXIS : + a_fac += 4.0 / (-Z06(__A) +Z06(__C) -Z06(_BC) +Z06(_AB)); // Offset by beta tower angle + break; + case C_AXIS : + a_fac += 4.0 / (Z06(__A) -Z06(__B) +Z06(_BC) -Z06(_CA) ); // Offset by gamma tower angle + break; + } + } + a_fac /= 3.0; + a_fac *= norm; // Normalize to 0.83 for Kossel mini + + endstops.enable(true); + if (!home_delta()) return false; + endstops.not_homing(); + print_signed_float(PSTR( "H_FACTOR: "), h_fac); + print_signed_float(PSTR(" R_FACTOR: "), r_fac); + print_signed_float(PSTR(" A_FACTOR: "), a_fac); + SERIAL_EOL(); + SERIAL_PROTOCOLPGM("Copy these values to Configuration.h"); + SERIAL_EOL(); + return true; + } + + #endif // HAS_BED_PROBE + + /** + * G33 - Delta '1-4-7-point' Auto-Calibration + * Calibrate height, endstops, delta radius, and tower angles. + * + * Parameters: + * + * Pn Number of probe points: + * P0 No probe. Normalize only. + * P1 Probe center and set height only. + * P2 Probe center and towers. Set height, endstops and delta radius. + * P3 Probe all positions: center, towers and opposite towers. Set all. + * P4-P10 Probe all positions + at different intermediate locations and average them. + * + * T Don't calibrate tower angle corrections + * + * Cn.nn Calibration precision; when omitted calibrates to maximum precision + * + * Fn Force to run at least n iterations and take the best result + * + * A Auto-tune calibration factors (set in Configuration.h) + * + * Vn Verbose level: + * V0 Dry-run mode. Report settings and probe results. No calibration. + * V1 Report start and end settings only + * V2 Report settings at each iteration + * V3 Report settings and probe results + * + * E Engage the probe for each point + */ + inline void gcode_G33() { + + const int8_t probe_points = parser.intval('P', DELTA_CALIBRATION_DEFAULT_POINTS); + if (!WITHIN(probe_points, 0, 10)) { + SERIAL_PROTOCOLLNPGM("?(P)oints is implausible (0-10)."); + return; + } + + const int8_t verbose_level = parser.byteval('V', 1); + if (!WITHIN(verbose_level, 0, 3)) { + SERIAL_PROTOCOLLNPGM("?(V)erbose level is implausible (0-3)."); + return; + } + + const float calibration_precision = parser.floatval('C', 0.0); + if (calibration_precision < 0) { + SERIAL_PROTOCOLLNPGM("?(C)alibration precision is implausible (>=0)."); + return; + } + + const int8_t force_iterations = parser.intval('F', 0); + if (!WITHIN(force_iterations, 0, 30)) { + SERIAL_PROTOCOLLNPGM("?(F)orce iteration is implausible (0-30)."); + return; + } + + const bool towers_set = !parser.boolval('T'), + auto_tune = parser.boolval('A'), + stow_after_each = parser.boolval('E'), + _0p_calibration = probe_points == 0, + _1p_calibration = probe_points == 1, + _4p_calibration = probe_points == 2, + _7p_9_centre = probe_points >= 8, + _tower_results = (_4p_calibration && towers_set) + || probe_points >= 3 || probe_points == 0, + _opposite_results = (_4p_calibration && !towers_set) + || probe_points >= 3 || probe_points == 0, + _endstop_results = probe_points != 1, + _angle_results = (probe_points >= 3 || probe_points == 0) && towers_set; + const static char save_message[] PROGMEM = "Save with M500 and/or copy to Configuration.h"; + int8_t iterations = 0; + float test_precision, + zero_std_dev = (verbose_level ? 999.0 : 0.0), // 0.0 in dry-run mode : forced end + zero_std_dev_min = zero_std_dev, + e_old[ABC] = { + delta_endstop_adj[A_AXIS], + delta_endstop_adj[B_AXIS], + delta_endstop_adj[C_AXIS] + }, + dr_old = delta_radius, + zh_old = delta_height, + ta_old[ABC] = { + delta_tower_angle_trim[A_AXIS], + delta_tower_angle_trim[B_AXIS], + delta_tower_angle_trim[C_AXIS] + }; + + SERIAL_PROTOCOLLNPGM("G33 Auto Calibrate"); + + if (!_1p_calibration && !_0p_calibration) { // test if the outer radius is reachable + LOOP_CAL_RAD(axis) { + const float a = RADIANS(210 + (360 / NPP) * (axis - 1)), + r = delta_calibration_radius * (1 + (_7p_9_centre ? 0.1 : 0.0)); + if (!position_is_reachable(cos(a) * r, sin(a) * r)) { + SERIAL_PROTOCOLLNPGM("?(M665 B)ed radius is implausible."); + return; + } + } + } + + stepper.synchronize(); + #if HAS_LEVELING + reset_bed_level(); // After calibration bed-level data is no longer valid + #endif + + #if HOTENDS > 1 + const uint8_t old_tool_index = active_extruder; + tool_change(0, 0, true); + #define G33_CLEANUP() G33_cleanup(old_tool_index) + #else + #define G33_CLEANUP() G33_cleanup() + #endif + + setup_for_endstop_or_probe_move(); + endstops.enable(true); + if (!_0p_calibration) { + if (!home_delta()) + return; + endstops.not_homing(); + } + + if (auto_tune) { + #if HAS_BED_PROBE + G33_auto_tune(); + #else + SERIAL_PROTOCOLLNPGM("A probe is needed for auto-tune"); + #endif + G33_CLEANUP(); + return; + } + + // Report settings + + PGM_P checkingac = PSTR("Checking... AC"); // TODO: Make translatable string + serialprintPGM(checkingac); + if (verbose_level == 0) SERIAL_PROTOCOLPGM(" (DRY-RUN)"); + SERIAL_EOL(); + lcd_setstatusPGM(checkingac); + + print_G33_settings(_endstop_results, _angle_results); + + do { + + float z_at_pt[NPP + 1] = { 0.0 }; + + test_precision = zero_std_dev; + + iterations++; + + // Probe the points + + zero_std_dev = probe_G33_points(z_at_pt, probe_points, towers_set, stow_after_each); + if (isnan(zero_std_dev)) { + SERIAL_PROTOCOLPGM("Correct delta_radius with M665 R or end-stops with M666 X Y Z"); + SERIAL_EOL(); + return G33_CLEANUP(); + } + + // Solve matrices + + if ((zero_std_dev < test_precision || iterations <= force_iterations) && zero_std_dev > calibration_precision) { + if (zero_std_dev < zero_std_dev_min) { + COPY(e_old, delta_endstop_adj); + dr_old = delta_radius; + zh_old = delta_height; + COPY(ta_old, delta_tower_angle_trim); + } + + float e_delta[ABC] = { 0.0 }, r_delta = 0.0, t_delta[ABC] = { 0.0 }; + const float r_diff = delta_radius - delta_calibration_radius, + h_factor = 1 / 6.0 * + #ifdef H_FACTOR + (H_FACTOR), // Set in Configuration.h + #else + (1.00 + r_diff * 0.001), // 1.02 for r_diff = 20mm + #endif + r_factor = 1 / 6.0 * + #ifdef R_FACTOR + -(R_FACTOR), // Set in Configuration.h + #else + -(1.75 + 0.005 * r_diff + 0.001 * sq(r_diff)), // 2.25 for r_diff = 20mm + #endif + a_factor = 1 / 6.0 * + #ifdef A_FACTOR + (A_FACTOR); // Set in Configuration.h + #else + (66.66 / delta_calibration_radius); // 0.83 for cal_rd = 80mm + #endif + + #define ZP(N,I) ((N) * z_at_pt[I]) + #define Z6(I) ZP(6, I) + #define Z4(I) ZP(4, I) + #define Z2(I) ZP(2, I) + #define Z1(I) ZP(1, I) + + #if !HAS_BED_PROBE + test_precision = 0.00; // forced end + #endif + + switch (probe_points) { + case 0: + test_precision = 0.00; // forced end + break; + + case 1: + test_precision = 0.00; // forced end + LOOP_XYZ(axis) e_delta[axis] = Z1(CEN); + break; + + case 2: + if (towers_set) { + e_delta[A_AXIS] = (Z6(CEN) +Z4(__A) -Z2(__B) -Z2(__C)) * h_factor; + e_delta[B_AXIS] = (Z6(CEN) -Z2(__A) +Z4(__B) -Z2(__C)) * h_factor; + e_delta[C_AXIS] = (Z6(CEN) -Z2(__A) -Z2(__B) +Z4(__C)) * h_factor; + r_delta = (Z6(CEN) -Z2(__A) -Z2(__B) -Z2(__C)) * r_factor; + } + else { + e_delta[A_AXIS] = (Z6(CEN) -Z4(_BC) +Z2(_CA) +Z2(_AB)) * h_factor; + e_delta[B_AXIS] = (Z6(CEN) +Z2(_BC) -Z4(_CA) +Z2(_AB)) * h_factor; + e_delta[C_AXIS] = (Z6(CEN) +Z2(_BC) +Z2(_CA) -Z4(_AB)) * h_factor; + r_delta = (Z6(CEN) -Z2(_BC) -Z2(_CA) -Z2(_AB)) * r_factor; + } + break; + + default: + e_delta[A_AXIS] = (Z6(CEN) +Z2(__A) -Z1(__B) -Z1(__C) -Z2(_BC) +Z1(_CA) +Z1(_AB)) * h_factor; + e_delta[B_AXIS] = (Z6(CEN) -Z1(__A) +Z2(__B) -Z1(__C) +Z1(_BC) -Z2(_CA) +Z1(_AB)) * h_factor; + e_delta[C_AXIS] = (Z6(CEN) -Z1(__A) -Z1(__B) +Z2(__C) +Z1(_BC) +Z1(_CA) -Z2(_AB)) * h_factor; + r_delta = (Z6(CEN) -Z1(__A) -Z1(__B) -Z1(__C) -Z1(_BC) -Z1(_CA) -Z1(_AB)) * r_factor; + + if (towers_set) { + t_delta[A_AXIS] = ( -Z4(__B) +Z4(__C) -Z4(_CA) +Z4(_AB)) * a_factor; + t_delta[B_AXIS] = ( Z4(__A) -Z4(__C) +Z4(_BC) -Z4(_AB)) * a_factor; + t_delta[C_AXIS] = (-Z4(__A) +Z4(__B) -Z4(_BC) +Z4(_CA) ) * a_factor; + e_delta[A_AXIS] += (t_delta[B_AXIS] - t_delta[C_AXIS]) / 4.5; + e_delta[B_AXIS] += (t_delta[C_AXIS] - t_delta[A_AXIS]) / 4.5; + e_delta[C_AXIS] += (t_delta[A_AXIS] - t_delta[B_AXIS]) / 4.5; + } + break; + } + + LOOP_XYZ(axis) delta_endstop_adj[axis] += e_delta[axis]; + delta_radius += r_delta; + LOOP_XYZ(axis) delta_tower_angle_trim[axis] += t_delta[axis]; + } + else if (zero_std_dev >= test_precision) { // step one back + COPY(delta_endstop_adj, e_old); + delta_radius = dr_old; + delta_height = zh_old; + COPY(delta_tower_angle_trim, ta_old); + } + + if (verbose_level != 0) { // !dry run + // normalise angles to least squares + if (_angle_results) { + float a_sum = 0.0; + LOOP_XYZ(axis) a_sum += delta_tower_angle_trim[axis]; + LOOP_XYZ(axis) delta_tower_angle_trim[axis] -= a_sum / 3.0; + } + + // adjust delta_height and endstops by the max amount + const float z_temp = MAX3(delta_endstop_adj[A_AXIS], delta_endstop_adj[B_AXIS], delta_endstop_adj[C_AXIS]); + delta_height -= z_temp; + LOOP_XYZ(axis) delta_endstop_adj[axis] -= z_temp; + } + recalc_delta_settings(); + NOMORE(zero_std_dev_min, zero_std_dev); + + // print report + + if (verbose_level > 2) + print_G33_results(z_at_pt, _tower_results, _opposite_results); + + if (verbose_level != 0) { // !dry run + if ((zero_std_dev >= test_precision && iterations > force_iterations) || zero_std_dev <= calibration_precision) { // end iterations + SERIAL_PROTOCOLPGM("Calibration OK"); + SERIAL_PROTOCOL_SP(32); + #if HAS_BED_PROBE + if (zero_std_dev >= test_precision && !_1p_calibration) + SERIAL_PROTOCOLPGM("rolling back."); + else + #endif + { + SERIAL_PROTOCOLPGM("std dev:"); + SERIAL_PROTOCOL_F(zero_std_dev_min, 3); + } + SERIAL_EOL(); + char mess[21]; + strcpy_P(mess, PSTR("Calibration sd:")); + if (zero_std_dev_min < 1) + sprintf_P(&mess[15], PSTR("0.%03i"), (int)round(zero_std_dev_min * 1000.0)); + else + sprintf_P(&mess[15], PSTR("%03i.x"), (int)round(zero_std_dev_min)); + lcd_setstatus(mess); + print_G33_settings(_endstop_results, _angle_results); + serialprintPGM(save_message); + SERIAL_EOL(); + } + else { // !end iterations + char mess[15]; + if (iterations < 31) + sprintf_P(mess, PSTR("Iteration : %02i"), (int)iterations); + else + strcpy_P(mess, PSTR("No convergence")); + SERIAL_PROTOCOL(mess); + SERIAL_PROTOCOL_SP(32); + SERIAL_PROTOCOLPGM("std dev:"); + SERIAL_PROTOCOL_F(zero_std_dev, 3); + SERIAL_EOL(); + lcd_setstatus(mess); + if (verbose_level > 1) + print_G33_settings(_endstop_results, _angle_results); + } + } + else { // dry run + PGM_P enddryrun = PSTR("End DRY-RUN"); + serialprintPGM(enddryrun); + SERIAL_PROTOCOL_SP(35); + SERIAL_PROTOCOLPGM("std dev:"); + SERIAL_PROTOCOL_F(zero_std_dev, 3); + SERIAL_EOL(); + + char mess[21]; + strcpy_P(mess, enddryrun); + strcpy_P(&mess[11], PSTR(" sd:")); + if (zero_std_dev < 1) + sprintf_P(&mess[15], PSTR("0.%03i"), (int)round(zero_std_dev * 1000.0)); + else + sprintf_P(&mess[15], PSTR("%03i.x"), (int)round(zero_std_dev)); + lcd_setstatus(mess); + } + + endstops.enable(true); + if (!home_delta()) + return; + endstops.not_homing(); + + } + while (((zero_std_dev < test_precision && iterations < 31) || iterations <= force_iterations) && zero_std_dev > calibration_precision); + + G33_CLEANUP(); + } + +#endif // DELTA_AUTO_CALIBRATION + +#if ENABLED(G38_PROBE_TARGET) + + static bool G38_run_probe() { + + bool G38_pass_fail = false; + + #if MULTIPLE_PROBING > 1 + // Get direction of move and retract + float retract_mm[XYZ]; + LOOP_XYZ(i) { + float dist = destination[i] - current_position[i]; + retract_mm[i] = FABS(dist) < G38_MINIMUM_MOVE ? 0 : home_bump_mm((AxisEnum)i) * (dist > 0 ? -1 : 1); + } + #endif + + stepper.synchronize(); // wait until the machine is idle + + // Move until destination reached or target hit + endstops.enable(true); + G38_move = true; + G38_endstop_hit = false; + prepare_move_to_destination(); + stepper.synchronize(); + G38_move = false; + + endstops.hit_on_purpose(); + set_current_from_steppers_for_axis(ALL_AXES); + SYNC_PLAN_POSITION_KINEMATIC(); + + if (G38_endstop_hit) { + + G38_pass_fail = true; + + #if MULTIPLE_PROBING > 1 + // Move away by the retract distance + set_destination_from_current(); + LOOP_XYZ(i) destination[i] += retract_mm[i]; + endstops.enable(false); + prepare_move_to_destination(); + stepper.synchronize(); + + feedrate_mm_s /= 4; + + // Bump the target more slowly + LOOP_XYZ(i) destination[i] -= retract_mm[i] * 2; + + endstops.enable(true); + G38_move = true; + prepare_move_to_destination(); + stepper.synchronize(); + G38_move = false; + + set_current_from_steppers_for_axis(ALL_AXES); + SYNC_PLAN_POSITION_KINEMATIC(); + #endif + } + + endstops.hit_on_purpose(); + endstops.not_homing(); + return G38_pass_fail; + } + + /** + * G38.2 - probe toward workpiece, stop on contact, signal error if failure + * G38.3 - probe toward workpiece, stop on contact + * + * Like G28 except uses Z min probe for all axes + */ + inline void gcode_G38(bool is_38_2) { + // Get X Y Z E F + gcode_get_destination(); + + setup_for_endstop_or_probe_move(); + + // If any axis has enough movement, do the move + LOOP_XYZ(i) + if (FABS(destination[i] - current_position[i]) >= G38_MINIMUM_MOVE) { + if (!parser.seenval('F')) feedrate_mm_s = homing_feedrate((AxisEnum)i); + // If G38.2 fails throw an error + if (!G38_run_probe() && is_38_2) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM("Failed to reach target"); + } + break; + } + + clean_up_after_endstop_or_probe_move(); + } + +#endif // G38_PROBE_TARGET + +#if HAS_MESH + + /** + * G42: Move X & Y axes to mesh coordinates (I & J) + */ + inline void gcode_G42() { + #if ENABLED(NO_MOTION_BEFORE_HOMING) + if (axis_unhomed_error()) return; + #endif + + if (IsRunning()) { + const bool hasI = parser.seenval('I'); + const int8_t ix = hasI ? parser.value_int() : 0; + const bool hasJ = parser.seenval('J'); + const int8_t iy = hasJ ? parser.value_int() : 0; + + if ((hasI && !WITHIN(ix, 0, GRID_MAX_POINTS_X - 1)) || (hasJ && !WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1))) { + SERIAL_ECHOLNPGM(MSG_ERR_MESH_XY); + return; + } + + set_destination_from_current(); + if (hasI) destination[X_AXIS] = _GET_MESH_X(ix); + if (hasJ) destination[Y_AXIS] = _GET_MESH_Y(iy); + if (parser.boolval('P')) { + if (hasI) destination[X_AXIS] -= X_PROBE_OFFSET_FROM_EXTRUDER; + if (hasJ) destination[Y_AXIS] -= Y_PROBE_OFFSET_FROM_EXTRUDER; + } + + const float fval = parser.linearval('F'); + if (fval > 0.0) feedrate_mm_s = MMM_TO_MMS(fval); + + // SCARA kinematic has "safe" XY raw moves + #if IS_SCARA + prepare_uninterpolated_move_to_destination(); + #else + prepare_move_to_destination(); + #endif + } + } + +#endif // HAS_MESH + +/** + * G92: Set current position to given X Y Z E + */ +inline void gcode_G92() { + + stepper.synchronize(); + + #if ENABLED(CNC_COORDINATE_SYSTEMS) + switch (parser.subcode) { + case 1: + // Zero the G92 values and restore current position + #if !IS_SCARA + LOOP_XYZ(i) { + const float v = position_shift[i]; + if (v) { + position_shift[i] = 0; + update_software_endstops((AxisEnum)i); + } + } + #endif // Not SCARA + return; + } + #endif + + #if ENABLED(CNC_COORDINATE_SYSTEMS) + #define IS_G92_0 (parser.subcode == 0) + #else + #define IS_G92_0 true + #endif + + bool didE = false; + #if IS_SCARA || !HAS_POSITION_SHIFT + bool didXYZ = false; + #else + constexpr bool didXYZ = false; + #endif + + if (IS_G92_0) LOOP_XYZE(i) { + if (parser.seenval(axis_codes[i])) { + const float l = parser.value_axis_units((AxisEnum)i), + v = i == E_AXIS ? l : LOGICAL_TO_NATIVE(l, i), + d = v - current_position[i]; + if (!NEAR_ZERO(d)) { + #if IS_SCARA || !HAS_POSITION_SHIFT + if (i == E_AXIS) didE = true; else didXYZ = true; + current_position[i] = v; // Without workspaces revert to Marlin 1.0 behavior + #elif HAS_POSITION_SHIFT + if (i == E_AXIS) { + didE = true; + current_position[E_AXIS] = v; // When using coordinate spaces, only E is set directly + } + else { + position_shift[i] += d; // Other axes simply offset the coordinate space + update_software_endstops((AxisEnum)i); + } + #endif + } + } + } + + #if ENABLED(CNC_COORDINATE_SYSTEMS) + // Apply workspace offset to the active coordinate system + if (WITHIN(active_coordinate_system, 0, MAX_COORDINATE_SYSTEMS - 1)) + COPY(coordinate_system[active_coordinate_system], position_shift); + #endif + + if (didXYZ) + SYNC_PLAN_POSITION_KINEMATIC(); + else if (didE) + sync_plan_position_e(); + + report_current_position(); +} + +#if HAS_RESUME_CONTINUE + + /** + * M0: Unconditional stop - Wait for user button press on LCD + * M1: Conditional stop - Wait for user button press on LCD + */ + inline void gcode_M0_M1() { + const char * const args = parser.string_arg; + + millis_t ms = 0; + bool hasP = false, hasS = false; + if (parser.seenval('P')) { + ms = parser.value_millis(); // milliseconds to wait + hasP = ms > 0; + } + if (parser.seenval('S')) { + ms = parser.value_millis_from_seconds(); // seconds to wait + hasS = ms > 0; + } + + const bool has_message = !hasP && !hasS && args && *args; + + #if ENABLED(ULTIPANEL) + + if (has_message) + lcd_setstatus(args, true); + else { + LCD_MESSAGEPGM(MSG_USERWAIT); + #if ENABLED(LCD_PROGRESS_BAR) && PROGRESS_MSG_EXPIRE > 0 + dontExpireStatus(); + #endif + } + + #else + + if (has_message) { + SERIAL_ECHO_START(); + SERIAL_ECHOLN(args); + } + + #endif + + KEEPALIVE_STATE(PAUSED_FOR_USER); + wait_for_user = true; + + stepper.synchronize(); + + if (ms > 0) { + ms += millis(); // wait until this time for a click + while (PENDING(millis(), ms) && wait_for_user) idle(); + } + else { + #if ENABLED(ULTIPANEL) + if (lcd_detected()) + #endif + while (wait_for_user) idle(); + } + + #if ENABLED(PRINTER_EVENT_LEDS) && ENABLED(SDSUPPORT) + if (lights_off_after_print) { + leds.set_off(); + lights_off_after_print = false; + } + #endif + + #if ENABLED(ULTIPANEL) + if (lcd_detected()) { + print_job_timer.isPaused() ? LCD_MESSAGEPGM(WELCOME_MSG) : LCD_MESSAGEPGM(MSG_RESUMING); + } + #endif + + wait_for_user = false; + KEEPALIVE_STATE(IN_HANDLER); + } + +#endif // HAS_RESUME_CONTINUE + +#if ENABLED(SPINDLE_LASER_ENABLE) + /** + * M3: Spindle Clockwise + * M4: Spindle Counter-clockwise + * + * S0 turns off spindle. + * + * If no speed PWM output is defined then M3/M4 just turns it on. + * + * At least 12.8KHz (50Hz * 256) is needed for spindle PWM. + * Hardware PWM is required. ISRs are too slow. + * + * NOTE: WGM for timers 3, 4, and 5 must be either Mode 1 or Mode 5. + * No other settings give a PWM signal that goes from 0 to 5 volts. + * + * The system automatically sets WGM to Mode 1, so no special + * initialization is needed. + * + * WGM bits for timer 2 are automatically set by the system to + * Mode 1. This produces an acceptable 0 to 5 volt signal. + * No special initialization is needed. + * + * NOTE: A minimum PWM frequency of 50 Hz is needed. All prescaler + * factors for timers 2, 3, 4, and 5 are acceptable. + * + * SPINDLE_LASER_ENABLE_PIN needs an external pullup or it may power on + * the spindle/laser during power-up or when connecting to the host + * (usually goes through a reset which sets all I/O pins to tri-state) + * + * PWM duty cycle goes from 0 (off) to 255 (always on). + */ + + // Wait for spindle to come up to speed + inline void delay_for_power_up() { dwell(SPINDLE_LASER_POWERUP_DELAY); } + + // Wait for spindle to stop turning + inline void delay_for_power_down() { dwell(SPINDLE_LASER_POWERDOWN_DELAY); } + + /** + * ocr_val_mode() is used for debugging and to get the points needed to compute the RPM vs ocr_val line + * + * it accepts inputs of 0-255 + */ + + inline void ocr_val_mode() { + uint8_t spindle_laser_power = parser.value_byte(); + WRITE(SPINDLE_LASER_ENABLE_PIN, SPINDLE_LASER_ENABLE_INVERT); // turn spindle on (active low) + if (SPINDLE_LASER_PWM_INVERT) spindle_laser_power = 255 - spindle_laser_power; + analogWrite(SPINDLE_LASER_PWM_PIN, spindle_laser_power); + } + + inline void gcode_M3_M4(bool is_M3) { + + stepper.synchronize(); // wait until previous movement commands (G0/G0/G2/G3) have completed before playing with the spindle + #if SPINDLE_DIR_CHANGE + const bool rotation_dir = (is_M3 && !SPINDLE_INVERT_DIR || !is_M3 && SPINDLE_INVERT_DIR) ? HIGH : LOW; + if (SPINDLE_STOP_ON_DIR_CHANGE \ + && READ(SPINDLE_LASER_ENABLE_PIN) == SPINDLE_LASER_ENABLE_INVERT \ + && READ(SPINDLE_DIR_PIN) != rotation_dir + ) { + WRITE(SPINDLE_LASER_ENABLE_PIN, !SPINDLE_LASER_ENABLE_INVERT); // turn spindle off + delay_for_power_down(); + } + WRITE(SPINDLE_DIR_PIN, rotation_dir); + #endif + + /** + * Our final value for ocr_val is an unsigned 8 bit value between 0 and 255 which usually means uint8_t. + * Went to uint16_t because some of the uint8_t calculations would sometimes give 1000 0000 rather than 1111 1111. + * Then needed to AND the uint16_t result with 0x00FF to make sure we only wrote the byte of interest. + */ + #if ENABLED(SPINDLE_LASER_PWM) + if (parser.seen('O')) ocr_val_mode(); + else { + const float spindle_laser_power = parser.floatval('S'); + if (spindle_laser_power == 0) { + WRITE(SPINDLE_LASER_ENABLE_PIN, !SPINDLE_LASER_ENABLE_INVERT); // turn spindle off (active low) + analogWrite(SPINDLE_LASER_PWM_PIN, SPINDLE_LASER_PWM_INVERT ? 255 : 0); // only write low byte + delay_for_power_down(); + } + else { + int16_t ocr_val = (spindle_laser_power - (SPEED_POWER_INTERCEPT)) * (1.0 / (SPEED_POWER_SLOPE)); // convert RPM to PWM duty cycle + NOMORE(ocr_val, 255); // limit to max the Atmel PWM will support + if (spindle_laser_power <= SPEED_POWER_MIN) + ocr_val = (SPEED_POWER_MIN - (SPEED_POWER_INTERCEPT)) * (1.0 / (SPEED_POWER_SLOPE)); // minimum setting + if (spindle_laser_power >= SPEED_POWER_MAX) + ocr_val = (SPEED_POWER_MAX - (SPEED_POWER_INTERCEPT)) * (1.0 / (SPEED_POWER_SLOPE)); // limit to max RPM + if (SPINDLE_LASER_PWM_INVERT) ocr_val = 255 - ocr_val; + WRITE(SPINDLE_LASER_ENABLE_PIN, SPINDLE_LASER_ENABLE_INVERT); // turn spindle on (active low) + analogWrite(SPINDLE_LASER_PWM_PIN, ocr_val & 0xFF); // only write low byte + delay_for_power_up(); + } + } + #else + WRITE(SPINDLE_LASER_ENABLE_PIN, SPINDLE_LASER_ENABLE_INVERT); // turn spindle on (active low) if spindle speed option not enabled + delay_for_power_up(); + #endif + } + + /** + * M5 turn off spindle + */ + inline void gcode_M5() { + stepper.synchronize(); + WRITE(SPINDLE_LASER_ENABLE_PIN, !SPINDLE_LASER_ENABLE_INVERT); + #if ENABLED(SPINDLE_LASER_PWM) + analogWrite(SPINDLE_LASER_PWM_PIN, SPINDLE_LASER_PWM_INVERT ? 255 : 0); + #endif + delay_for_power_down(); + } + +#endif // SPINDLE_LASER_ENABLE + +#if ENABLED(FAN_AS_LASER) + /** + * M3: Laser On + */ + + + inline void gcode_M3() { + const uint16_t s = parser.seen('S') ? parser.ushortval('S', 255) : 255; + fanSpeeds[FAN_NUM_AS_LASER] = min(s, 255); + + } + + /** + * M5: Laser Off + */ + inline void gcode_M5() { + fanSpeeds[FAN_NUM_AS_LASER] = 0; + } + +#endif +/** + * M17: Enable power on all stepper motors + */ +inline void gcode_M17() { + LCD_MESSAGEPGM(MSG_NO_MOVE); + enable_all_steppers(); +} + +#if ENABLED(ADVANCED_PAUSE_FEATURE) + + void do_pause_e_move(const float &length, const float &fr) { + set_destination_from_current(); + destination[E_AXIS] += length / planner.e_factor[active_extruder]; + planner.buffer_line_kinematic(destination, fr, active_extruder); + stepper.synchronize(); + set_current_from_destination(); + } + + static float resume_position[XYZE]; + int8_t did_pause_print = 0; + + #if HAS_BUZZER + static void filament_change_beep(const int8_t max_beep_count, const bool init=false) { + static millis_t next_buzz = 0; + static int8_t runout_beep = 0; + + if (init) next_buzz = runout_beep = 0; + + const millis_t ms = millis(); + if (ELAPSED(ms, next_buzz)) { + if (max_beep_count < 0 || runout_beep < max_beep_count + 5) { // Only beep as long as we're supposed to + next_buzz = ms + ((max_beep_count < 0 || runout_beep < max_beep_count) ? 1000 : 500); + BUZZ(50, 880 - (runout_beep & 1) * 220); + runout_beep++; + } + } + } + #endif + + /** + * Ensure a safe temperature for extrusion + * + * - Fail if the TARGET temperature is too low + * - Display LCD placard with temperature status + * - Return when heating is done or aborted + * + * Returns 'true' if heating was completed, 'false' for abort + */ + static bool ensure_safe_temperature(const AdvancedPauseMode mode=ADVANCED_PAUSE_MODE_PAUSE_PRINT) { + + #if ENABLED(PREVENT_COLD_EXTRUSION) + if (!DEBUGGING(DRYRUN) && thermalManager.targetTooColdToExtrude(active_extruder)) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_HOTEND_TOO_COLD); + return false; + } + #endif + + #if ENABLED(ULTIPANEL) + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_WAIT_FOR_NOZZLES_TO_HEAT, mode); + #else + UNUSED(mode); + #endif + + wait_for_heatup = true; // M108 will clear this + while (wait_for_heatup && thermalManager.wait_for_heating(active_extruder)) idle(); + const bool status = wait_for_heatup; + wait_for_heatup = false; + + return status; + } + + /** + * Load filament into the hotend + * + * - Fail if the a safe temperature was not reached + * - If pausing for confirmation, wait for a click or M108 + * - Show "wait for load" placard + * - Load and purge filament + * - Show "Purge more" / "Continue" menu + * - Return when "Continue" is selected + * + * Returns 'true' if load was completed, 'false' for abort + */ + static bool load_filament(const float &load_length=0, const float &purge_length=0, const int8_t max_beep_count=0, + const bool show_lcd=false, const bool pause_for_user=false, + const AdvancedPauseMode mode=ADVANCED_PAUSE_MODE_PAUSE_PRINT + ) { + #if DISABLED(ULTIPANEL) + UNUSED(show_lcd); + #endif + + if (!ensure_safe_temperature(mode)) { + #if ENABLED(ULTIPANEL) + if (show_lcd) // Show status screen + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_STATUS); + #endif + + return false; + } + + if (pause_for_user) { + #if ENABLED(ULTIPANEL) + if (show_lcd) // Show "insert filament" + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_INSERT, mode); + #endif + SERIAL_ECHO_START(); + SERIAL_ECHOLNPGM(MSG_FILAMENT_CHANGE_INSERT); + + #if HAS_BUZZER + filament_change_beep(max_beep_count, true); + #else + UNUSED(max_beep_count); + #endif + + KEEPALIVE_STATE(PAUSED_FOR_USER); + wait_for_user = true; // LCD click or M108 will clear this + while (wait_for_user) { + #if HAS_BUZZER + filament_change_beep(max_beep_count); + #endif + idle(true); + } + KEEPALIVE_STATE(IN_HANDLER); + } + + #if ENABLED(ULTIPANEL) + if (show_lcd) // Show "wait for load" message + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_LOAD, mode); + #endif + + // Load filament + if (load_length) do_pause_e_move(load_length, FILAMENT_CHANGE_LOAD_FEEDRATE); + + do { + if (purge_length > 0) { + // "Wait for filament purge" + #if ENABLED(ULTIPANEL) + if (show_lcd) + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_PURGE, mode); + #endif + + // Extrude filament to get into hotend + do_pause_e_move(purge_length, ADVANCED_PAUSE_EXTRUDE_FEEDRATE); + } + + // Show "Purge More" / "Resume" menu and wait for reply + #if ENABLED(ULTIPANEL) + if (show_lcd) { + KEEPALIVE_STATE(PAUSED_FOR_USER); + wait_for_user = false; + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_OPTION, mode); + while (advanced_pause_menu_response == ADVANCED_PAUSE_RESPONSE_WAIT_FOR) idle(true); + KEEPALIVE_STATE(IN_HANDLER); + } + #endif + + // Keep looping if "Purge More" was selected + } while ( + #if ENABLED(ULTIPANEL) + show_lcd && advanced_pause_menu_response == ADVANCED_PAUSE_RESPONSE_EXTRUDE_MORE + #else + 0 + #endif + ); + + return true; + } + + /** + * Unload filament from the hotend + * + * - Fail if the a safe temperature was not reached + * - Show "wait for unload" placard + * - Retract, pause, then unload filament + * - Disable E stepper (on most machines) + * + * Returns 'true' if unload was completed, 'false' for abort + */ + static bool unload_filament(const float &unload_length, const bool show_lcd=false, + const AdvancedPauseMode mode=ADVANCED_PAUSE_MODE_PAUSE_PRINT + ) { + if (!ensure_safe_temperature(mode)) { + #if ENABLED(ULTIPANEL) + if (show_lcd) // Show status screen + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_STATUS); + #endif + + return false; + } + + #if DISABLED(ULTIPANEL) + UNUSED(show_lcd); + #else + if (show_lcd) + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_UNLOAD, mode); + #endif + + // Retract filament + do_pause_e_move(-FILAMENT_UNLOAD_RETRACT_LENGTH, PAUSE_PARK_RETRACT_FEEDRATE); + + // Wait for filament to cool + safe_delay(FILAMENT_UNLOAD_DELAY); + + // Quickly purge + do_pause_e_move(FILAMENT_UNLOAD_RETRACT_LENGTH + FILAMENT_UNLOAD_PURGE_LENGTH, planner.max_feedrate_mm_s[E_AXIS]); + + // Unload filament + do_pause_e_move(unload_length, FILAMENT_CHANGE_UNLOAD_FEEDRATE); + + // Disable extruders steppers for manual filament changing (only on boards that have separate ENABLE_PINS) + #if E0_ENABLE_PIN != X_ENABLE_PIN && E1_ENABLE_PIN != Y_ENABLE_PIN + disable_e_stepper(active_extruder); + safe_delay(100); + #endif + + return true; + } + + /** + * Pause procedure + * + * - Abort if already paused + * - Send host action for pause, if configured + * - Abort if TARGET temperature is too low + * - Display "wait for start of filament change" (if a length was specified) + * - Initial retract, if current temperature is hot enough + * - Park the nozzle at the given position + * - Call unload_filament (if a length was specified) + * + * Returns 'true' if pause was completed, 'false' for abort + */ + static bool pause_print(const float &retract, const point_t &park_point, const float &unload_length=0, const bool show_lcd=false) { + if (did_pause_print) return false; // already paused + + #ifdef ACTION_ON_PAUSE + SERIAL_ECHOLNPGM("//action:" ACTION_ON_PAUSE); + #endif + + if (!DEBUGGING(DRYRUN) && unload_length && thermalManager.targetTooColdToExtrude(active_extruder)) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_HOTEND_TOO_COLD); + + #if ENABLED(ULTIPANEL) + if (show_lcd) // Show status screen + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_STATUS); + LCD_MESSAGEPGM(MSG_M600_TOO_COLD); + #endif + + return false; // unable to reach safe temperature + } + + // Indicate that the printer is paused + ++did_pause_print; + + // Pause the print job and timer + #if ENABLED(SDSUPPORT) + if (card.sdprinting) { + card.pauseSDPrint(); + ++did_pause_print; // Indicate SD pause also + } + #endif + print_job_timer.pause(); + + // Wait for synchronize steppers + stepper.synchronize(); + + // Save current position + COPY(resume_position, current_position); + + // Initial retract before move to filament change position + if (retract && thermalManager.hotEnoughToExtrude(active_extruder)) + do_pause_e_move(retract, PAUSE_PARK_RETRACT_FEEDRATE); + + #if ENABLED(NO_MOTION_BEFORE_HOMING) + if (!axis_unhomed_error()) + #endif + // Park the nozzle by moving up by z_lift and then moving to (x_pos, y_pos) + Nozzle::park(2, park_point); + + // Unload the filament + if (unload_length) + unload_filament(unload_length, show_lcd); + + return true; + } + + /** + * - Show "Insert filament and press button to continue" + * - Wait for a click before returning + * - Heaters can time out, reheated before accepting a click + * + * Used by M125 and M600 + */ + static void wait_for_filament_reload(const int8_t max_beep_count=0) { + bool nozzle_timed_out = false; + + #if ENABLED(ULTIPANEL) + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_INSERT); + #endif + SERIAL_ECHO_START(); + SERIAL_ERRORLNPGM(MSG_FILAMENT_CHANGE_INSERT); + + #if HAS_BUZZER + filament_change_beep(max_beep_count, true); + #endif + + // Start the heater idle timers + const millis_t nozzle_timeout = (millis_t)(PAUSE_PARK_NOZZLE_TIMEOUT) * 1000UL; + + HOTEND_LOOP() + thermalManager.start_heater_idle_timer(e, nozzle_timeout); + + // Wait for filament insert by user and press button + KEEPALIVE_STATE(PAUSED_FOR_USER); + wait_for_user = true; // LCD click or M108 will clear this + while (wait_for_user) { + #if HAS_BUZZER + filament_change_beep(max_beep_count); + #endif + + // If the nozzle has timed out, wait for the user to press the button to re-heat the nozzle, then + // re-heat the nozzle, re-show the insert screen, restart the idle timers, and start over + if (!nozzle_timed_out) + HOTEND_LOOP() + nozzle_timed_out |= thermalManager.is_heater_idle(e); + + if (nozzle_timed_out) { + #if ENABLED(ULTIPANEL) + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_CLICK_TO_HEAT_NOZZLE); + #endif + SERIAL_ECHO_START(); + #if ENABLED(ULTIPANEL) && ENABLED(EMERGENCY_PARSER) + SERIAL_ERRORLNPGM(MSG_FILAMENT_CHANGE_HEAT); + #elif ENABLED(EMERGENCY_PARSER) + SERIAL_ERRORLNPGM(MSG_FILAMENT_CHANGE_HEAT_M108); + #else + SERIAL_ERRORLNPGM(MSG_FILAMENT_CHANGE_HEAT_LCD); + #endif + + // Wait for LCD click or M108 + while (wait_for_user) idle(true); + + // Re-enable the heaters if they timed out + HOTEND_LOOP() thermalManager.reset_heater_idle_timer(e); + + // Wait for the heaters to reach the target temperatures + ensure_safe_temperature(); + + #if ENABLED(ULTIPANEL) + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_INSERT); + #endif + SERIAL_ECHO_START(); + #if ENABLED(ULTIPANEL) && ENABLED(EMERGENCY_PARSER) + SERIAL_ERRORLNPGM(MSG_FILAMENT_CHANGE_INSERT); + #elif ENABLED(EMERGENCY_PARSER) + SERIAL_ERRORLNPGM(MSG_FILAMENT_CHANGE_INSERT_M108); + #else + SERIAL_ERRORLNPGM(MSG_FILAMENT_CHANGE_INSERT_LCD); + #endif + + // Start the heater idle timers + const millis_t nozzle_timeout = (millis_t)(PAUSE_PARK_NOZZLE_TIMEOUT) * 1000UL; + + HOTEND_LOOP() + thermalManager.start_heater_idle_timer(e, nozzle_timeout); + + wait_for_user = true; // Wait for user to load filament + nozzle_timed_out = false; + + #if HAS_BUZZER + filament_change_beep(max_beep_count, true); + #endif + } + + idle(true); + } + KEEPALIVE_STATE(IN_HANDLER); + } + + /** + * Resume or Start print procedure + * + * - Abort if not paused + * - Reset heater idle timers + * - Load filament if specified, but only if: + * - a nozzle timed out, or + * - the nozzle is already heated. + * - Display "wait for print to resume" + * - Re-prime the nozzle... + * - FWRETRACT: Recover/prime from the prior G10. + * - !FWRETRACT: Retract by resume_position[E], if negative. + * Not sure how this logic comes into use. + * - Move the nozzle back to resume_position + * - Sync the planner E to resume_position[E] + * - Send host action for resume, if configured + * - Resume the current SD print job, if any + */ + static void resume_print(const float &load_length=0, const float &purge_length=ADVANCED_PAUSE_EXTRUDE_LENGTH, const int8_t max_beep_count=0) { + if (!did_pause_print) return; + + // Re-enable the heaters if they timed out + bool nozzle_timed_out = false; + HOTEND_LOOP() { + nozzle_timed_out |= thermalManager.is_heater_idle(e); + thermalManager.reset_heater_idle_timer(e); + } + + if (nozzle_timed_out || thermalManager.hotEnoughToExtrude(active_extruder)) { + // Load the new filament + load_filament(load_length, purge_length, max_beep_count, true, nozzle_timed_out); + } + + #if ENABLED(ULTIPANEL) + // "Wait for print to resume" + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_RESUME); + #endif + + // Intelligent resuming + #if ENABLED(FWRETRACT) + // If retracted before goto pause + if (fwretract.retracted[active_extruder]) + do_pause_e_move(-fwretract.retract_length, fwretract.retract_feedrate_mm_s); + #endif + + // If resume_position is negative + if (resume_position[E_AXIS] < 0) do_pause_e_move(resume_position[E_AXIS], PAUSE_PARK_RETRACT_FEEDRATE); + + // Move XY to starting position, then Z + do_blocking_move_to_xy(resume_position[X_AXIS], resume_position[Y_AXIS], NOZZLE_PARK_XY_FEEDRATE); + + // Set Z_AXIS to saved position + do_blocking_move_to_z(resume_position[Z_AXIS], NOZZLE_PARK_Z_FEEDRATE); + + // Now all extrusion positions are resumed and ready to be confirmed + // Set extruder to saved position + planner.set_e_position_mm((destination[E_AXIS] = current_position[E_AXIS] = resume_position[E_AXIS])); + + #if ENABLED(FILAMENT_RUNOUT_SENSOR) + runout.reset(); + #endif + + #if ENABLED(ULTIPANEL) + // Show status screen + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_STATUS); + #endif + + #ifdef ACTION_ON_RESUME + SERIAL_ECHOLNPGM("//action:" ACTION_ON_RESUME); + #endif + + --did_pause_print; + + #if ENABLED(SDSUPPORT) + if (did_pause_print) { + card.startFileprint(); + --did_pause_print; + } + #endif + } + +#endif // ADVANCED_PAUSE_FEATURE + +#if ENABLED(SDSUPPORT) + + /** + * M20: List SD card to serial output + */ + inline void gcode_M20() { + SERIAL_PROTOCOLLNPGM(MSG_BEGIN_FILE_LIST); + card.ls(); + SERIAL_PROTOCOLLNPGM(MSG_END_FILE_LIST); + } + + /** + * M21: Init SD Card + */ + inline void gcode_M21() { card.initsd(); } + + /** + * M22: Release SD Card + */ + inline void gcode_M22() { card.release(); } + + /** + * M23: Open a file + */ + inline void gcode_M23() { + // Simplify3D includes the size, so zero out all spaces (#7227) + for (char *fn = parser.string_arg; *fn; ++fn) if (*fn == ' ') *fn = '\0'; + card.openFile(parser.string_arg, true); + } + + /** + * M24: Start or Resume SD Print + */ + inline void gcode_M24() { + #if ENABLED(PARK_HEAD_ON_PAUSE) + resume_print(); + #endif + + card.startFileprint(); + print_job_timer.start(); + } + + /** + * M25: Pause SD Print + */ + inline void gcode_M25() { + card.pauseSDPrint(); + print_job_timer.pause(); + + #if ENABLED(PARK_HEAD_ON_PAUSE) + enqueue_and_echo_commands_P(PSTR("M125")); // Must be enqueued with pauseSDPrint set to be last in the buffer + #endif + } + + /** + * M26: Set SD Card file index + */ + inline void gcode_M26() { + if (card.cardOK && parser.seenval('S')) + card.setIndex(parser.value_long()); + } + + /** + * M27: Get SD Card status + * OR, with 'S' set the SD status auto-report interval. (Requires AUTO_REPORT_SD_STATUS) + * OR, with 'C' get the current filename. + */ + inline void gcode_M27() { + if (parser.seen('C')) { + SERIAL_ECHOPGM("Current file: "); + card.printFilename(); + } + + #if ENABLED(AUTO_REPORT_SD_STATUS) + else if (parser.seenval('S')) + card.set_auto_report_interval(parser.value_byte()); + #endif + + else + card.getStatus(); + } + + /** + * M28: Start SD Write + */ + inline void gcode_M28() { card.openFile(parser.string_arg, false); } + + /** + * M29: Stop SD Write + * Processed in write to file routine above + */ + inline void gcode_M29() { + // card.saving = false; + } + + /** + * M30 : Delete SD Card file + */ + inline void gcode_M30() { + if (card.cardOK) { + card.closefile(); + card.removeFile(parser.string_arg); + } + } + +#endif // SDSUPPORT + +/** + * M31: Get the time since the start of SD Print (or last M109) + */ +inline void gcode_M31() { + char buffer[21]; + duration_t elapsed = print_job_timer.duration(); + elapsed.toString(buffer); + lcd_setstatus(buffer); + + SERIAL_ECHO_START(); + SERIAL_ECHOLNPAIR("Print time: ", buffer); +} + +#if ENABLED(SDSUPPORT) + + /** + * M32: Select file and start SD Print + * + * Examples: + * + * M32 !PATH/TO/FILE.GCO# ; Start FILE.GCO + * M32 P !PATH/TO/FILE.GCO# ; Start FILE.GCO as a procedure + * M32 S60 !PATH/TO/FILE.GCO# ; Start FILE.GCO at byte 60 + * + */ + inline void gcode_M32() { + if (card.sdprinting) stepper.synchronize(); + + if (card.cardOK) { + const bool call_procedure = parser.boolval('P'); + + card.openFile(parser.string_arg, true, call_procedure); + + if (parser.seenval('S')) card.setIndex(parser.value_long()); + + card.startFileprint(); + + // Procedure calls count as normal print time. + if (!call_procedure) print_job_timer.start(); + } + } + + #if ENABLED(LONG_FILENAME_HOST_SUPPORT) + + /** + * M33: Get the long full path of a file or folder + * + * Parameters: + * Case-insensitive DOS-style path to a file or folder + * + * Example: + * M33 miscel~1/armchair/armcha~1.gco + * + * Output: + * /Miscellaneous/Armchair/Armchair.gcode + */ + inline void gcode_M33() { + card.printLongPath(parser.string_arg); + } + + #endif + + #if ENABLED(SDCARD_SORT_ALPHA) && ENABLED(SDSORT_GCODE) + /** + * M34: Set SD Card Sorting Options + */ + inline void gcode_M34() { + if (parser.seen('S')) card.setSortOn(parser.value_bool()); + if (parser.seenval('F')) { + const int v = parser.value_long(); + card.setSortFolders(v < 0 ? -1 : v > 0 ? 1 : 0); + } + //if (parser.seen('R')) card.setSortReverse(parser.value_bool()); + } + #endif // SDCARD_SORT_ALPHA && SDSORT_GCODE + + /** + * M928: Start SD Write + */ + inline void gcode_M928() { + card.openLogFile(parser.string_arg); + } + +#endif // SDSUPPORT + +/** + * Sensitive pin test for M42, M226 + */ +static bool pin_is_protected(const pin_t pin) { + static const pin_t sensitive_pins[] PROGMEM = SENSITIVE_PINS; + for (uint8_t i = 0; i < COUNT(sensitive_pins); i++) + if (pin == (pin_t)pgm_read_byte(&sensitive_pins[i])) return true; + return false; +} + +/** + * M42: Change pin status via GCode + * + * P Pin number (LED if omitted) + * S Pin status from 0 - 255 + */ +inline void gcode_M42() { + if (!parser.seenval('S')) return; + const byte pin_status = parser.value_byte(); + + const pin_t pin_number = parser.byteval('P', LED_PIN); + if (pin_number < 0) return; + + if (pin_is_protected(pin_number)) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_PROTECTED_PIN); + return; + } + + pinMode(pin_number, OUTPUT); + digitalWrite(pin_number, pin_status); + analogWrite(pin_number, pin_status); + + #if FAN_COUNT > 0 + switch (pin_number) { + #if HAS_FAN0 + case FAN_PIN: fanSpeeds[0] = pin_status; break; + #endif + #if HAS_FAN1 + case FAN1_PIN: fanSpeeds[1] = pin_status; break; + #endif + #if HAS_FAN2 + case FAN2_PIN: fanSpeeds[2] = pin_status; break; + #endif + } + #endif +} + +#if ENABLED(PINS_DEBUGGING) + + #include "pinsDebug.h" + + inline void toggle_pins() { + const bool I_flag = parser.boolval('I'); + const int repeat = parser.intval('R', 1), + start = parser.intval('S'), + end = parser.intval('L', NUM_DIGITAL_PINS - 1), + wait = parser.intval('W', 500); + + for (uint8_t pin = start; pin <= end; pin++) { + //report_pin_state_extended(pin, I_flag, false); + + if (!I_flag && pin_is_protected(pin)) { + report_pin_state_extended(pin, I_flag, true, "Untouched "); + SERIAL_EOL(); + } + else { + report_pin_state_extended(pin, I_flag, true, "Pulsing "); + #if AVR_AT90USB1286_FAMILY // Teensy IDEs don't know about these pins so must use FASTIO + if (pin == TEENSY_E2) { + SET_OUTPUT(TEENSY_E2); + for (int16_t j = 0; j < repeat; j++) { + WRITE(TEENSY_E2, LOW); safe_delay(wait); + WRITE(TEENSY_E2, HIGH); safe_delay(wait); + WRITE(TEENSY_E2, LOW); safe_delay(wait); + } + } + else if (pin == TEENSY_E3) { + SET_OUTPUT(TEENSY_E3); + for (int16_t j = 0; j < repeat; j++) { + WRITE(TEENSY_E3, LOW); safe_delay(wait); + WRITE(TEENSY_E3, HIGH); safe_delay(wait); + WRITE(TEENSY_E3, LOW); safe_delay(wait); + } + } + else + #endif + { + pinMode(pin, OUTPUT); + for (int16_t j = 0; j < repeat; j++) { + digitalWrite(pin, 0); safe_delay(wait); + digitalWrite(pin, 1); safe_delay(wait); + digitalWrite(pin, 0); safe_delay(wait); + } + } + + } + SERIAL_EOL(); + } + SERIAL_ECHOLNPGM("Done."); + + } // toggle_pins + + inline void servo_probe_test() { + #if !(NUM_SERVOS > 0 && HAS_SERVO_0) + + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM("SERVO not setup"); + + #elif !HAS_Z_SERVO_ENDSTOP + + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM("Z_ENDSTOP_SERVO_NR not setup"); + + #else // HAS_Z_SERVO_ENDSTOP + + const uint8_t probe_index = parser.byteval('P', Z_ENDSTOP_SERVO_NR); + + SERIAL_PROTOCOLLNPGM("Servo probe test"); + SERIAL_PROTOCOLLNPAIR(". using index: ", probe_index); + SERIAL_PROTOCOLLNPAIR(". deploy angle: ", z_servo_angle[0]); + SERIAL_PROTOCOLLNPAIR(". stow angle: ", z_servo_angle[1]); + + bool probe_inverting; + + #if ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN) + + #define PROBE_TEST_PIN Z_MIN_PIN + + SERIAL_PROTOCOLLNPAIR(". probe uses Z_MIN pin: ", PROBE_TEST_PIN); + SERIAL_PROTOCOLLNPGM(". uses Z_MIN_ENDSTOP_INVERTING (ignores Z_MIN_PROBE_ENDSTOP_INVERTING)"); + SERIAL_PROTOCOLPGM(". Z_MIN_ENDSTOP_INVERTING: "); + + #if Z_MIN_ENDSTOP_INVERTING + SERIAL_PROTOCOLLNPGM("true"); + #else + SERIAL_PROTOCOLLNPGM("false"); + #endif + + probe_inverting = Z_MIN_ENDSTOP_INVERTING; + + #elif ENABLED(Z_MIN_PROBE_ENDSTOP) + + #define PROBE_TEST_PIN Z_MIN_PROBE_PIN + SERIAL_PROTOCOLLNPAIR(". probe uses Z_MIN_PROBE_PIN: ", PROBE_TEST_PIN); + SERIAL_PROTOCOLLNPGM(". uses Z_MIN_PROBE_ENDSTOP_INVERTING (ignores Z_MIN_ENDSTOP_INVERTING)"); + SERIAL_PROTOCOLPGM(". Z_MIN_PROBE_ENDSTOP_INVERTING: "); + + #if Z_MIN_PROBE_ENDSTOP_INVERTING + SERIAL_PROTOCOLLNPGM("true"); + #else + SERIAL_PROTOCOLLNPGM("false"); + #endif + + probe_inverting = Z_MIN_PROBE_ENDSTOP_INVERTING; + + #endif + + SERIAL_PROTOCOLLNPGM(". deploy & stow 4 times"); + SET_INPUT_PULLUP(PROBE_TEST_PIN); + bool deploy_state, stow_state; + for (uint8_t i = 0; i < 4; i++) { + MOVE_SERVO(probe_index, z_servo_angle[0]); //deploy + safe_delay(500); + deploy_state = READ(PROBE_TEST_PIN); + MOVE_SERVO(probe_index, z_servo_angle[1]); //stow + safe_delay(500); + stow_state = READ(PROBE_TEST_PIN); + } + if (probe_inverting != deploy_state) SERIAL_PROTOCOLLNPGM("WARNING - INVERTING setting probably backwards"); + + if (deploy_state != stow_state) { + SERIAL_PROTOCOLLNPGM("BLTouch clone detected"); + if (deploy_state) { + SERIAL_PROTOCOLLNPGM(". DEPLOYED state: HIGH (logic 1)"); + SERIAL_PROTOCOLLNPGM(". STOWED (triggered) state: LOW (logic 0)"); + } + else { + SERIAL_PROTOCOLLNPGM(". DEPLOYED state: LOW (logic 0)"); + SERIAL_PROTOCOLLNPGM(". STOWED (triggered) state: HIGH (logic 1)"); + } + #if ENABLED(BLTOUCH) + SERIAL_PROTOCOLLNPGM("ERROR: BLTOUCH enabled - set this device up as a Z Servo Probe with inverting as true."); + #endif + + } + else { // measure active signal length + MOVE_SERVO(probe_index, z_servo_angle[0]); // deploy + safe_delay(500); + SERIAL_PROTOCOLLNPGM("please trigger probe"); + uint16_t probe_counter = 0; + + // Allow 30 seconds max for operator to trigger probe + for (uint16_t j = 0; j < 500 * 30 && probe_counter == 0 ; j++) { + + safe_delay(2); + + if (0 == j % (500 * 1)) reset_stepper_timeout(); // Keep steppers powered + + if (deploy_state != READ(PROBE_TEST_PIN)) { // probe triggered + + for (probe_counter = 1; probe_counter < 50 && deploy_state != READ(PROBE_TEST_PIN); ++probe_counter) + safe_delay(2); + + if (probe_counter == 50) + SERIAL_PROTOCOLLNPGM("Z Servo Probe detected"); // >= 100mS active time + else if (probe_counter >= 2) + SERIAL_PROTOCOLLNPAIR("BLTouch compatible probe detected - pulse width (+/- 4mS): ", probe_counter * 2); // allow 4 - 100mS pulse + else + SERIAL_PROTOCOLLNPGM("noise detected - please re-run test"); // less than 2mS pulse + + MOVE_SERVO(probe_index, z_servo_angle[1]); //stow + + } // pulse detected + + } // for loop waiting for trigger + + if (probe_counter == 0) SERIAL_PROTOCOLLNPGM("trigger not detected"); + + } // measure active signal length + + #endif + + } // servo_probe_test + + /** + * M43: Pin debug - report pin state, watch pins, toggle pins and servo probe test/report + * + * M43 - report name and state of pin(s) + * P Pin to read or watch. If omitted, reads all pins. + * I Flag to ignore Marlin's pin protection. + * + * M43 W - Watch pins -reporting changes- until reset, click, or M108. + * P Pin to read or watch. If omitted, read/watch all pins. + * I Flag to ignore Marlin's pin protection. + * + * M43 E - Enable / disable background endstop monitoring + * - Machine continues to operate + * - Reports changes to endstops + * - Toggles LED_PIN when an endstop changes + * - Can not reliably catch the 5mS pulse from BLTouch type probes + * + * M43 T - Toggle pin(s) and report which pin is being toggled + * S - Start Pin number. If not given, will default to 0 + * L - End Pin number. If not given, will default to last pin defined for this board + * I - Flag to ignore Marlin's pin protection. Use with caution!!!! + * R - Repeat pulses on each pin this number of times before continueing to next pin + * W - Wait time (in miliseconds) between pulses. If not given will default to 500 + * + * M43 S - Servo probe test + * P - Probe index (optional - defaults to 0 + */ + inline void gcode_M43() { + + if (parser.seen('T')) { // must be first or else its "S" and "E" parameters will execute endstop or servo test + toggle_pins(); + return; + } + + // Enable or disable endstop monitoring + if (parser.seen('E')) { + endstop_monitor_flag = parser.value_bool(); + SERIAL_PROTOCOLPGM("endstop monitor "); + serialprintPGM(endstop_monitor_flag ? PSTR("en") : PSTR("dis")); + SERIAL_PROTOCOLLNPGM("abled"); + return; + } + + if (parser.seen('S')) { + servo_probe_test(); + return; + } + + // Get the range of pins to test or watch + const pin_t first_pin = parser.byteval('P'), + last_pin = parser.seenval('P') ? first_pin : NUM_DIGITAL_PINS - 1; + + if (first_pin > last_pin) return; + + const bool ignore_protection = parser.boolval('I'); + + // Watch until click, M108, or reset + if (parser.boolval('W')) { + SERIAL_PROTOCOLLNPGM("Watching pins"); + byte pin_state[last_pin - first_pin + 1]; + for (pin_t pin = first_pin; pin <= last_pin; pin++) { + if (pin_is_protected(pin) && !ignore_protection) continue; + pinMode(pin, INPUT_PULLUP); + delay(1); + /* + if (IS_ANALOG(pin)) + pin_state[pin - first_pin] = analogRead(pin - analogInputToDigitalPin(0)); // int16_t pin_state[...] + else + //*/ + pin_state[pin - first_pin] = digitalRead(pin); + } + + #if HAS_RESUME_CONTINUE + wait_for_user = true; + KEEPALIVE_STATE(PAUSED_FOR_USER); + #endif + + for (;;) { + for (pin_t pin = first_pin; pin <= last_pin; pin++) { + if (pin_is_protected(pin) && !ignore_protection) continue; + const byte val = + /* + IS_ANALOG(pin) + ? analogRead(pin - analogInputToDigitalPin(0)) : // int16_t val + : + //*/ + digitalRead(pin); + if (val != pin_state[pin - first_pin]) { + report_pin_state_extended(pin, ignore_protection, false); + pin_state[pin - first_pin] = val; + } + } + + #if HAS_RESUME_CONTINUE + if (!wait_for_user) { + KEEPALIVE_STATE(IN_HANDLER); + break; + } + #endif + + safe_delay(200); + } + return; + } + + // Report current state of selected pin(s) + for (pin_t pin = first_pin; pin <= last_pin; pin++) + report_pin_state_extended(pin, ignore_protection, true); + } + +#endif // PINS_DEBUGGING + +#if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST) + + /** + * M48: Z probe repeatability measurement function. + * + * Usage: + * M48 + * P = Number of sampled points (4-50, default 10) + * X = Sample X position + * Y = Sample Y position + * V = Verbose level (0-4, default=1) + * E = Engage Z probe for each reading + * L = Number of legs of movement before probe + * S = Schizoid (Or Star if you prefer) + * + * This function requires the machine to be homed before invocation. + */ + inline void gcode_M48() { + + if (axis_unhomed_error()) return; + + const int8_t verbose_level = parser.byteval('V', 1); + if (!WITHIN(verbose_level, 0, 4)) { + SERIAL_PROTOCOLLNPGM("?(V)erbose level is implausible (0-4)."); + return; + } + + if (verbose_level > 0) + SERIAL_PROTOCOLLNPGM("M48 Z-Probe Repeatability Test"); + + const int8_t n_samples = parser.byteval('P', 10); + if (!WITHIN(n_samples, 4, 50)) { + SERIAL_PROTOCOLLNPGM("?Sample size not plausible (4-50)."); + return; + } + + const ProbePtRaise raise_after = parser.boolval('E') ? PROBE_PT_STOW : PROBE_PT_RAISE; + + float X_current = current_position[X_AXIS], + Y_current = current_position[Y_AXIS]; + + const float X_probe_location = parser.linearval('X', X_current + X_PROBE_OFFSET_FROM_EXTRUDER), + Y_probe_location = parser.linearval('Y', Y_current + Y_PROBE_OFFSET_FROM_EXTRUDER); + + if (!position_is_reachable_by_probe(X_probe_location, Y_probe_location)) { + SERIAL_PROTOCOLLNPGM("? (X,Y) out of bounds."); + return; + } + + bool seen_L = parser.seen('L'); + uint8_t n_legs = seen_L ? parser.value_byte() : 0; + if (n_legs > 15) { + SERIAL_PROTOCOLLNPGM("?Number of legs in movement not plausible (0-15)."); + return; + } + if (n_legs == 1) n_legs = 2; + + const bool schizoid_flag = parser.boolval('S'); + if (schizoid_flag && !seen_L) n_legs = 7; + + /** + * Now get everything to the specified probe point So we can safely do a + * probe to get us close to the bed. If the Z-Axis is far from the bed, + * we don't want to use that as a starting point for each probe. + */ + if (verbose_level > 2) + SERIAL_PROTOCOLLNPGM("Positioning the probe..."); + + // Disable bed level correction in M48 because we want the raw data when we probe + + #if HAS_LEVELING + const bool was_enabled = planner.leveling_active; + set_bed_leveling_enabled(false); + #endif + + setup_for_endstop_or_probe_move(); + + double mean = 0.0, sigma = 0.0, min = 99999.9, max = -99999.9, sample_set[n_samples]; + + // Move to the first point, deploy, and probe + const float t = probe_pt(X_probe_location, Y_probe_location, raise_after, verbose_level); + bool probing_good = !isnan(t); + + if (probing_good) { + randomSeed(millis()); + + for (uint8_t n = 0; n < n_samples; n++) { + if (n_legs) { + const int dir = (random(0, 10) > 5.0) ? -1 : 1; // clockwise or counter clockwise + float angle = random(0.0, 360.0); + const float radius = random( + #if ENABLED(DELTA) + 0.1250000000 * (DELTA_PRINTABLE_RADIUS), + 0.3333333333 * (DELTA_PRINTABLE_RADIUS) + #else + 5.0, 0.125 * min(X_BED_SIZE, Y_BED_SIZE) + #endif + ); + + if (verbose_level > 3) { + SERIAL_ECHOPAIR("Starting radius: ", radius); + SERIAL_ECHOPAIR(" angle: ", angle); + SERIAL_ECHOPGM(" Direction: "); + if (dir > 0) SERIAL_ECHOPGM("Counter-"); + SERIAL_ECHOLNPGM("Clockwise"); + } + + for (uint8_t l = 0; l < n_legs - 1; l++) { + double delta_angle; + + if (schizoid_flag) + // The points of a 5 point star are 72 degrees apart. We need to + // skip a point and go to the next one on the star. + delta_angle = dir * 2.0 * 72.0; + + else + // If we do this line, we are just trying to move further + // around the circle. + delta_angle = dir * (float) random(25, 45); + + angle += delta_angle; + + while (angle > 360.0) // We probably do not need to keep the angle between 0 and 2*PI, but the + angle -= 360.0; // Arduino documentation says the trig functions should not be given values + while (angle < 0.0) // outside of this range. It looks like they behave correctly with + angle += 360.0; // numbers outside of the range, but just to be safe we clamp them. + + X_current = X_probe_location - (X_PROBE_OFFSET_FROM_EXTRUDER) + cos(RADIANS(angle)) * radius; + Y_current = Y_probe_location - (Y_PROBE_OFFSET_FROM_EXTRUDER) + sin(RADIANS(angle)) * radius; + + #if DISABLED(DELTA) + X_current = constrain(X_current, X_MIN_POS, X_MAX_POS); + Y_current = constrain(Y_current, Y_MIN_POS, Y_MAX_POS); + #else + // If we have gone out too far, we can do a simple fix and scale the numbers + // back in closer to the origin. + while (!position_is_reachable_by_probe(X_current, Y_current)) { + X_current *= 0.8; + Y_current *= 0.8; + if (verbose_level > 3) { + SERIAL_ECHOPAIR("Pulling point towards center:", X_current); + SERIAL_ECHOLNPAIR(", ", Y_current); + } + } + #endif + if (verbose_level > 3) { + SERIAL_PROTOCOLPGM("Going to:"); + SERIAL_ECHOPAIR(" X", X_current); + SERIAL_ECHOPAIR(" Y", Y_current); + SERIAL_ECHOLNPAIR(" Z", current_position[Z_AXIS]); + } + do_blocking_move_to_xy(X_current, Y_current); + } // n_legs loop + } // n_legs + + // Probe a single point + sample_set[n] = probe_pt(X_probe_location, Y_probe_location, raise_after); + + // Break the loop if the probe fails + probing_good = !isnan(sample_set[n]); + if (!probing_good) break; + + /** + * Get the current mean for the data points we have so far + */ + double sum = 0.0; + for (uint8_t j = 0; j <= n; j++) sum += sample_set[j]; + mean = sum / (n + 1); + + NOMORE(min, sample_set[n]); + NOLESS(max, sample_set[n]); + + /** + * Now, use that mean to calculate the standard deviation for the + * data points we have so far + */ + sum = 0.0; + for (uint8_t j = 0; j <= n; j++) + sum += sq(sample_set[j] - mean); + + sigma = SQRT(sum / (n + 1)); + if (verbose_level > 0) { + if (verbose_level > 1) { + SERIAL_PROTOCOL(n + 1); + SERIAL_PROTOCOLPGM(" of "); + SERIAL_PROTOCOL((int)n_samples); + SERIAL_PROTOCOLPGM(": z: "); + SERIAL_PROTOCOL_F(sample_set[n], 3); + if (verbose_level > 2) { + SERIAL_PROTOCOLPGM(" mean: "); + SERIAL_PROTOCOL_F(mean, 4); + SERIAL_PROTOCOLPGM(" sigma: "); + SERIAL_PROTOCOL_F(sigma, 6); + SERIAL_PROTOCOLPGM(" min: "); + SERIAL_PROTOCOL_F(min, 3); + SERIAL_PROTOCOLPGM(" max: "); + SERIAL_PROTOCOL_F(max, 3); + SERIAL_PROTOCOLPGM(" range: "); + SERIAL_PROTOCOL_F(max-min, 3); + } + SERIAL_EOL(); + } + } + + } // n_samples loop + } + + STOW_PROBE(); + + if (probing_good) { + SERIAL_PROTOCOLLNPGM("Finished!"); + + if (verbose_level > 0) { + SERIAL_PROTOCOLPGM("Mean: "); + SERIAL_PROTOCOL_F(mean, 6); + SERIAL_PROTOCOLPGM(" Min: "); + SERIAL_PROTOCOL_F(min, 3); + SERIAL_PROTOCOLPGM(" Max: "); + SERIAL_PROTOCOL_F(max, 3); + SERIAL_PROTOCOLPGM(" Range: "); + SERIAL_PROTOCOL_F(max-min, 3); + SERIAL_EOL(); + } + + SERIAL_PROTOCOLPGM("Standard Deviation: "); + SERIAL_PROTOCOL_F(sigma, 6); + SERIAL_EOL(); + SERIAL_EOL(); + } + + clean_up_after_endstop_or_probe_move(); + + // Re-enable bed level correction if it had been on + #if HAS_LEVELING + set_bed_leveling_enabled(was_enabled); + #endif + + #if Z_AFTER_PROBING + move_z_after_probing(); + #endif + + report_current_position(); + } + +#endif // Z_MIN_PROBE_REPEATABILITY_TEST + +#if ENABLED(G26_MESH_VALIDATION) + + inline void gcode_M49() { + g26_debug_flag ^= true; + SERIAL_PROTOCOLPGM("G26 Debug "); + serialprintPGM(g26_debug_flag ? PSTR("on.\n") : PSTR("off.\n")); + } + +#endif // G26_MESH_VALIDATION + +#if ENABLED(ULTRA_LCD) && ENABLED(LCD_SET_PROGRESS_MANUALLY) + /** + * M73: Set percentage complete (for display on LCD) + * + * Example: + * M73 P25 ; Set progress to 25% + * + * Notes: + * This has no effect during an SD print job + */ + inline void gcode_M73() { + if (!IS_SD_PRINTING && parser.seen('P')) { + progress_bar_percent = parser.value_byte(); + NOMORE(progress_bar_percent, 100); + } + } +#endif // ULTRA_LCD && LCD_SET_PROGRESS_MANUALLY + +/** + * M75: Start print timer + */ +inline void gcode_M75() { print_job_timer.start(); } + +/** + * M76: Pause print timer + */ +inline void gcode_M76() { print_job_timer.pause(); } + +/** + * M77: Stop print timer + */ +inline void gcode_M77() { print_job_timer.stop(); } + +#if ENABLED(PRINTCOUNTER) + /** + * M78: Show print statistics + */ + inline void gcode_M78() { + // "M78 S78" will reset the statistics + if (parser.intval('S') == 78) + print_job_timer.initStats(); + else + print_job_timer.showStats(); + } +#endif + +/** + * M104: Set hot end temperature + */ +inline void gcode_M104() { + if (get_target_extruder_from_command(104)) return; + if (DEBUGGING(DRYRUN)) return; + + #if ENABLED(SINGLENOZZLE) + if (target_extruder != active_extruder) return; + #endif + + if (parser.seenval('S')) { + const int16_t temp = parser.value_celsius(); + thermalManager.setTargetHotend(temp, target_extruder); + + #if ENABLED(DUAL_X_CARRIAGE) + if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && target_extruder == 0) + thermalManager.setTargetHotend(temp ? temp + duplicate_extruder_temp_offset : 0, 1); + #endif + + #if ENABLED(PRINTJOB_TIMER_AUTOSTART) + /** + * Stop the timer at the end of print. Start is managed by 'heat and wait' M109. + * We use half EXTRUDE_MINTEMP here to allow nozzles to be put into hot + * standby mode, for instance in a dual extruder setup, without affecting + * the running print timer. + */ + if (parser.value_celsius() <= (EXTRUDE_MINTEMP) / 2) { + print_job_timer.stop(); + LCD_MESSAGEPGM(WELCOME_MSG); + } + #endif + + #if ENABLED(ULTRA_LCD) + if (parser.value_celsius() > thermalManager.degHotend(target_extruder)) + lcd_status_printf_P(0, PSTR("E%i %s"), target_extruder + 1, MSG_HEATING); + #endif + } + + #if ENABLED(AUTOTEMP) + planner.autotemp_M104_M109(); + #endif +} + +/** + * M105: Read hot end and bed temperature + */ +inline void gcode_M105() { + if (get_target_extruder_from_command(105)) return; + + #if HAS_TEMP_SENSOR + SERIAL_PROTOCOLPGM(MSG_OK); + thermalManager.print_heaterstates(); + #else // !HAS_TEMP_HOTEND && !HAS_TEMP_BED + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_NO_THERMISTORS); + #endif + + SERIAL_EOL(); +} + +#if ENABLED(AUTO_REPORT_TEMPERATURES) + + /** + * M155: Set temperature auto-report interval. M155 S + */ + inline void gcode_M155() { + if (parser.seenval('S')) + thermalManager.set_auto_report_interval(parser.value_byte()); + } + +#endif // AUTO_REPORT_TEMPERATURES + +#if FAN_COUNT > 0 + + /** + * M106: Set Fan Speed + * + * S Speed between 0-255 + * P Fan index, if more than one fan + * + * With EXTRA_FAN_SPEED enabled: + * + * T Restore/Use/Set Temporary Speed: + * 1 = Restore previous speed after T2 + * 2 = Use temporary speed set with T3-255 + * 3-255 = Set the speed for use with T2 + */ + inline void gcode_M106() { + const uint8_t p = parser.byteval('P'); + if (p < FAN_COUNT) { + #if ENABLED(EXTRA_FAN_SPEED) + const int16_t t = parser.intval('T'); + if (t > 0) { + switch (t) { + case 1: + fanSpeeds[p] = old_fanSpeeds[p]; + break; + case 2: + old_fanSpeeds[p] = fanSpeeds[p]; + fanSpeeds[p] = new_fanSpeeds[p]; + break; + default: + new_fanSpeeds[p] = min(t, 255); + break; + } + return; + } + #endif // EXTRA_FAN_SPEED + const uint16_t s = parser.ushortval('S', 255); + fanSpeeds[p] = min(s, 255); + } + } + + /** + * M107: Fan Off + */ + inline void gcode_M107() { + const uint16_t p = parser.ushortval('P'); + if (p < FAN_COUNT) fanSpeeds[p] = 0; + } + +#endif // FAN_COUNT > 0 + +#if DISABLED(EMERGENCY_PARSER) + + /** + * M108: Stop the waiting for heaters in M109, M190, M303. Does not affect the target temperature. + */ + inline void gcode_M108() { wait_for_heatup = false; } + + + /** + * M112: Emergency Stop + */ + inline void gcode_M112() { kill(PSTR(MSG_KILLED)); } + + + /** + * M410: Quickstop - Abort all planned moves + * + * This will stop the carriages mid-move, so most likely they + * will be out of sync with the stepper position after this. + */ + inline void gcode_M410() { quickstop_stepper(); } + +#endif + +/** + * M109: Sxxx Wait for extruder(s) to reach temperature. Waits only when heating. + * Rxxx Wait for extruder(s) to reach temperature. Waits when heating and cooling. + */ + +#ifndef MIN_COOLING_SLOPE_DEG + #define MIN_COOLING_SLOPE_DEG 1.50 +#endif +#ifndef MIN_COOLING_SLOPE_TIME + #define MIN_COOLING_SLOPE_TIME 60 +#endif + +inline void gcode_M109() { + + if (get_target_extruder_from_command(109)) return; + if (DEBUGGING(DRYRUN)) return; + + #if ENABLED(SINGLENOZZLE) + if (target_extruder != active_extruder) return; + #endif + + const bool no_wait_for_cooling = parser.seenval('S'); + if (no_wait_for_cooling || parser.seenval('R')) { + const int16_t temp = parser.value_celsius(); + thermalManager.setTargetHotend(temp, target_extruder); + + #if ENABLED(DUAL_X_CARRIAGE) + if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && target_extruder == 0) + thermalManager.setTargetHotend(temp ? temp + duplicate_extruder_temp_offset : 0, 1); + #endif + + #if ENABLED(PRINTJOB_TIMER_AUTOSTART) + /** + * Use half EXTRUDE_MINTEMP to allow nozzles to be put into hot + * standby mode, (e.g., in a dual extruder setup) without affecting + * the running print timer. + */ + if (parser.value_celsius() <= (EXTRUDE_MINTEMP) / 2) { + print_job_timer.stop(); + LCD_MESSAGEPGM(WELCOME_MSG); + } + else + print_job_timer.start(); + #endif + + #if ENABLED(ULTRA_LCD) + if (thermalManager.isHeatingHotend(target_extruder)) + lcd_status_printf_P(0, PSTR("E%i %s"), target_extruder + 1, MSG_HEATING); + #endif + } + else return; + + #if ENABLED(AUTOTEMP) + planner.autotemp_M104_M109(); + #endif + + #if TEMP_RESIDENCY_TIME > 0 + millis_t residency_start_ms = 0; + // Loop until the temperature has stabilized + #define TEMP_CONDITIONS (!residency_start_ms || PENDING(now, residency_start_ms + (TEMP_RESIDENCY_TIME) * 1000UL)) + #else + // Loop until the temperature is very close target + #define TEMP_CONDITIONS (wants_to_cool ? thermalManager.isCoolingHotend(target_extruder) : thermalManager.isHeatingHotend(target_extruder)) + #endif + + float target_temp = -1.0, old_temp = 9999.0; + bool wants_to_cool = false; + wait_for_heatup = true; + millis_t now, next_temp_ms = 0, next_cool_check_ms = 0; + + #if DISABLED(BUSY_WHILE_HEATING) + KEEPALIVE_STATE(NOT_BUSY); + #endif + + #if ENABLED(PRINTER_EVENT_LEDS) + const float start_temp = thermalManager.degHotend(target_extruder); + uint8_t old_blue = 0; + #endif + + do { + // Target temperature might be changed during the loop + if (target_temp != thermalManager.degTargetHotend(target_extruder)) { + wants_to_cool = thermalManager.isCoolingHotend(target_extruder); + target_temp = thermalManager.degTargetHotend(target_extruder); + + // Exit if S, continue if S, R, or R + if (no_wait_for_cooling && wants_to_cool) break; + } + + now = millis(); + if (ELAPSED(now, next_temp_ms)) { //Print temp & remaining time every 1s while waiting + next_temp_ms = now + 1000UL; + thermalManager.print_heaterstates(); + #if TEMP_RESIDENCY_TIME > 0 + SERIAL_PROTOCOLPGM(" W:"); + if (residency_start_ms) + SERIAL_PROTOCOL(long((((TEMP_RESIDENCY_TIME) * 1000UL) - (now - residency_start_ms)) / 1000UL)); + else + SERIAL_PROTOCOLCHAR('?'); + #endif + SERIAL_EOL(); + } + + idle(); + reset_stepper_timeout(); // Keep steppers powered + + const float temp = thermalManager.degHotend(target_extruder); + + #if ENABLED(PRINTER_EVENT_LEDS) + // Gradually change LED strip from violet to red as nozzle heats up + if (!wants_to_cool) { + const uint8_t blue = map(constrain(temp, start_temp, target_temp), start_temp, target_temp, 255, 0); + if (blue != old_blue) { + old_blue = blue; + leds.set_color( + MakeLEDColor(255, 0, blue, 0, pixels.getBrightness()) + #if ENABLED(NEOPIXEL_IS_SEQUENTIAL) + , true + #endif + ); + } + } + #endif + + #if TEMP_RESIDENCY_TIME > 0 + + const float temp_diff = FABS(target_temp - temp); + + if (!residency_start_ms) { + // Start the TEMP_RESIDENCY_TIME timer when we reach target temp for the first time. + if (temp_diff < TEMP_WINDOW) residency_start_ms = now; + } + else if (temp_diff > TEMP_HYSTERESIS) { + // Restart the timer whenever the temperature falls outside the hysteresis. + residency_start_ms = now; + } + + #endif + + // Prevent a wait-forever situation if R is misused i.e. M109 R0 + if (wants_to_cool) { + // break after MIN_COOLING_SLOPE_TIME seconds + // if the temperature did not drop at least MIN_COOLING_SLOPE_DEG + if (!next_cool_check_ms || ELAPSED(now, next_cool_check_ms)) { + if (old_temp - temp < MIN_COOLING_SLOPE_DEG) break; + next_cool_check_ms = now + 1000UL * MIN_COOLING_SLOPE_TIME; + old_temp = temp; + } + } + + } while (wait_for_heatup && TEMP_CONDITIONS); + + if (wait_for_heatup) { + LCD_MESSAGEPGM(MSG_HEATING_COMPLETE); + #if ENABLED(PRINTER_EVENT_LEDS) + leds.set_white(); + #endif + } + + #if DISABLED(BUSY_WHILE_HEATING) + KEEPALIVE_STATE(IN_HANDLER); + #endif +} + +#if HAS_TEMP_BED + + #ifndef MIN_COOLING_SLOPE_DEG_BED + #define MIN_COOLING_SLOPE_DEG_BED 1.50 + #endif + #ifndef MIN_COOLING_SLOPE_TIME_BED + #define MIN_COOLING_SLOPE_TIME_BED 60 + #endif + + /** + * M190: Sxxx Wait for bed current temp to reach target temp. Waits only when heating + * Rxxx Wait for bed current temp to reach target temp. Waits when heating and cooling + */ + inline void gcode_M190() { + if (DEBUGGING(DRYRUN)) return; + + LCD_MESSAGEPGM(MSG_BED_HEATING); + const bool no_wait_for_cooling = parser.seenval('S'); + if (no_wait_for_cooling || parser.seenval('R')) { + thermalManager.setTargetBed(parser.value_celsius()); + #if ENABLED(PRINTJOB_TIMER_AUTOSTART) + if (parser.value_celsius() > BED_MINTEMP) + print_job_timer.start(); + #endif + } + else return; + + #if TEMP_BED_RESIDENCY_TIME > 0 + millis_t residency_start_ms = 0; + // Loop until the temperature has stabilized + #define TEMP_BED_CONDITIONS (!residency_start_ms || PENDING(now, residency_start_ms + (TEMP_BED_RESIDENCY_TIME) * 1000UL)) + #else + // Loop until the temperature is very close target + #define TEMP_BED_CONDITIONS (wants_to_cool ? thermalManager.isCoolingBed() : thermalManager.isHeatingBed()) + #endif + + float target_temp = -1.0, old_temp = 9999.0; + bool wants_to_cool = false; + wait_for_heatup = true; + millis_t now, next_temp_ms = 0, next_cool_check_ms = 0; + + #if DISABLED(BUSY_WHILE_HEATING) + KEEPALIVE_STATE(NOT_BUSY); + #endif + + target_extruder = active_extruder; // for print_heaterstates + + #if ENABLED(PRINTER_EVENT_LEDS) + const float start_temp = thermalManager.degBed(); + uint8_t old_red = 127; + #endif + + do { + // Target temperature might be changed during the loop + if (target_temp != thermalManager.degTargetBed()) { + wants_to_cool = thermalManager.isCoolingBed(); + target_temp = thermalManager.degTargetBed(); + + // Exit if S, continue if S, R, or R + if (no_wait_for_cooling && wants_to_cool) break; + } + + now = millis(); + if (ELAPSED(now, next_temp_ms)) { //Print Temp Reading every 1 second while heating up. + next_temp_ms = now + 1000UL; + thermalManager.print_heaterstates(); + #if TEMP_BED_RESIDENCY_TIME > 0 + SERIAL_PROTOCOLPGM(" W:"); + if (residency_start_ms) + SERIAL_PROTOCOL(long((((TEMP_BED_RESIDENCY_TIME) * 1000UL) - (now - residency_start_ms)) / 1000UL)); + else + SERIAL_PROTOCOLCHAR('?'); + #endif + SERIAL_EOL(); + } + + idle(); + reset_stepper_timeout(); // Keep steppers powered + + const float temp = thermalManager.degBed(); + + #if ENABLED(PRINTER_EVENT_LEDS) + // Gradually change LED strip from blue to violet as bed heats up + if (!wants_to_cool) { + const uint8_t red = map(constrain(temp, start_temp, target_temp), start_temp, target_temp, 0, 255); + if (red != old_red) { + old_red = red; + leds.set_color( + MakeLEDColor(red, 0, 255, 0, pixels.getBrightness()) + #if ENABLED(NEOPIXEL_IS_SEQUENTIAL) + , true + #endif + ); + } + } + #endif + + #if TEMP_BED_RESIDENCY_TIME > 0 + + const float temp_diff = FABS(target_temp - temp); + + if (!residency_start_ms) { + // Start the TEMP_BED_RESIDENCY_TIME timer when we reach target temp for the first time. + if (temp_diff < TEMP_BED_WINDOW) residency_start_ms = now; + } + else if (temp_diff > TEMP_BED_HYSTERESIS) { + // Restart the timer whenever the temperature falls outside the hysteresis. + residency_start_ms = now; + } + + #endif // TEMP_BED_RESIDENCY_TIME > 0 + + // Prevent a wait-forever situation if R is misused i.e. M190 R0 + if (wants_to_cool) { + // Break after MIN_COOLING_SLOPE_TIME_BED seconds + // if the temperature did not drop at least MIN_COOLING_SLOPE_DEG_BED + if (!next_cool_check_ms || ELAPSED(now, next_cool_check_ms)) { + if (old_temp - temp < MIN_COOLING_SLOPE_DEG_BED) break; + next_cool_check_ms = now + 1000UL * MIN_COOLING_SLOPE_TIME_BED; + old_temp = temp; + } + } + + } while (wait_for_heatup && TEMP_BED_CONDITIONS); + + if (wait_for_heatup) LCD_MESSAGEPGM(MSG_BED_DONE); + #if DISABLED(BUSY_WHILE_HEATING) + KEEPALIVE_STATE(IN_HANDLER); + #endif + } + +#endif // HAS_TEMP_BED + +/** + * M110: Set Current Line Number + */ +inline void gcode_M110() { + if (parser.seenval('N')) gcode_LastN = parser.value_long(); +} + +/** + * M111: Set the debug level + */ +inline void gcode_M111() { + if (parser.seen('S')) marlin_debug_flags = parser.byteval('S'); + + const static char str_debug_1[] PROGMEM = MSG_DEBUG_ECHO, + str_debug_2[] PROGMEM = MSG_DEBUG_INFO, + str_debug_4[] PROGMEM = MSG_DEBUG_ERRORS, + str_debug_8[] PROGMEM = MSG_DEBUG_DRYRUN, + str_debug_16[] PROGMEM = MSG_DEBUG_COMMUNICATION + #if ENABLED(DEBUG_LEVELING_FEATURE) + , str_debug_32[] PROGMEM = MSG_DEBUG_LEVELING + #endif + ; + + const static char* const debug_strings[] PROGMEM = { + str_debug_1, str_debug_2, str_debug_4, str_debug_8, str_debug_16 + #if ENABLED(DEBUG_LEVELING_FEATURE) + , str_debug_32 + #endif + }; + + SERIAL_ECHO_START(); + SERIAL_ECHOPGM(MSG_DEBUG_PREFIX); + if (marlin_debug_flags) { + uint8_t comma = 0; + for (uint8_t i = 0; i < COUNT(debug_strings); i++) { + if (TEST(marlin_debug_flags, i)) { + if (comma++) SERIAL_CHAR(','); + serialprintPGM((char*)pgm_read_ptr(&debug_strings[i])); + } + } + } + else { + SERIAL_ECHOPGM(MSG_DEBUG_OFF); + } + SERIAL_EOL(); +} + +#if ENABLED(HOST_KEEPALIVE_FEATURE) + + /** + * M113: Get or set Host Keepalive interval (0 to disable) + * + * S Optional. Set the keepalive interval. + */ + inline void gcode_M113() { + if (parser.seenval('S')) { + host_keepalive_interval = parser.value_byte(); + NOMORE(host_keepalive_interval, 60); + } + else { + SERIAL_ECHO_START(); + SERIAL_ECHOLNPAIR("M113 S", (unsigned long)host_keepalive_interval); + } + } + +#endif + +#if ENABLED(BARICUDA) + + #if HAS_HEATER_1 + /** + * M126: Heater 1 valve open + */ + inline void gcode_M126() { baricuda_valve_pressure = parser.byteval('S', 255); } + /** + * M127: Heater 1 valve close + */ + inline void gcode_M127() { baricuda_valve_pressure = 0; } + #endif + + #if HAS_HEATER_2 + /** + * M128: Heater 2 valve open + */ + inline void gcode_M128() { baricuda_e_to_p_pressure = parser.byteval('S', 255); } + /** + * M129: Heater 2 valve close + */ + inline void gcode_M129() { baricuda_e_to_p_pressure = 0; } + #endif + +#endif // BARICUDA + +/** + * M140: Set bed temperature + */ +inline void gcode_M140() { + if (DEBUGGING(DRYRUN)) return; + if (parser.seenval('S')) thermalManager.setTargetBed(parser.value_celsius()); +} + +#if ENABLED(ULTIPANEL) + + /** + * M145: Set the heatup state for a material in the LCD menu + * + * S (0=PLA, 1=ABS) + * H + * B + * F + */ + inline void gcode_M145() { + const uint8_t material = (uint8_t)parser.intval('S'); + if (material >= COUNT(lcd_preheat_hotend_temp)) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_MATERIAL_INDEX); + } + else { + int v; + if (parser.seenval('H')) { + v = parser.value_int(); + lcd_preheat_hotend_temp[material] = constrain(v, EXTRUDE_MINTEMP, HEATER_0_MAXTEMP - 15); + } + if (parser.seenval('F')) { + v = parser.value_int(); + lcd_preheat_fan_speed[material] = constrain(v, 0, 255); + } + #if TEMP_SENSOR_BED != 0 + if (parser.seenval('B')) { + v = parser.value_int(); + lcd_preheat_bed_temp[material] = constrain(v, BED_MINTEMP, BED_MAXTEMP - 15); + } + #endif + } + } + +#endif // ULTIPANEL + +#if ENABLED(TEMPERATURE_UNITS_SUPPORT) + /** + * M149: Set temperature units + */ + inline void gcode_M149() { + if (parser.seenval('C')) parser.set_input_temp_units(TEMPUNIT_C); + else if (parser.seenval('K')) parser.set_input_temp_units(TEMPUNIT_K); + else if (parser.seenval('F')) parser.set_input_temp_units(TEMPUNIT_F); + } +#endif + +#if HAS_POWER_SWITCH + + /** + * M80 : Turn on the Power Supply + * M80 S : Report the current state and exit + */ + inline void gcode_M80() { + + // S: Report the current power supply state and exit + if (parser.seen('S')) { + serialprintPGM(powersupply_on ? PSTR("PS:1\n") : PSTR("PS:0\n")); + return; + } + + PSU_ON(); + + /** + * If you have a switch on suicide pin, this is useful + * if you want to start another print with suicide feature after + * a print without suicide... + */ + #if HAS_SUICIDE + OUT_WRITE(SUICIDE_PIN, HIGH); + #endif + + #if DISABLED(AUTO_POWER_CONTROL) + delay(100); // Wait for power to settle + restore_stepper_drivers(); + #endif + + #if ENABLED(ULTIPANEL) + LCD_MESSAGEPGM(WELCOME_MSG); + #endif + } + +#endif // HAS_POWER_SWITCH + +/** + * M81: Turn off Power, including Power Supply, if there is one. + * + * This code should ALWAYS be available for EMERGENCY SHUTDOWN! + */ +inline void gcode_M81() { + thermalManager.disable_all_heaters(); + stepper.finish_and_disable(); + + #if FAN_COUNT > 0 + for (uint8_t i = 0; i < FAN_COUNT; i++) fanSpeeds[i] = 0; + #if ENABLED(PROBING_FANS_OFF) + fans_paused = false; + ZERO(paused_fanSpeeds); + #endif + #endif + + safe_delay(1000); // Wait 1 second before switching off + + #if HAS_SUICIDE + stepper.synchronize(); + suicide(); + #elif HAS_POWER_SWITCH + PSU_OFF(); + #endif + + #if ENABLED(ULTIPANEL) + LCD_MESSAGEPGM(MACHINE_NAME " " MSG_OFF "."); + #endif +} + +/** + * M82: Set E codes absolute (default) + */ +inline void gcode_M82() { axis_relative_modes[E_AXIS] = false; } + +/** + * M83: Set E codes relative while in Absolute Coordinates (G90) mode + */ +inline void gcode_M83() { axis_relative_modes[E_AXIS] = true; } + +/** + * M18, M84: Disable stepper motors + */ +inline void gcode_M18_M84() { + if (parser.seenval('S')) { + stepper_inactive_time = parser.value_millis_from_seconds(); + } + else { + bool all_axis = !(parser.seen('X') || parser.seen('Y') || parser.seen('Z') || parser.seen('E')); + if (all_axis) { + stepper.finish_and_disable(); + } + else { + stepper.synchronize(); + if (parser.seen('X')) disable_X(); + if (parser.seen('Y')) disable_Y(); + if (parser.seen('Z')) disable_Z(); + #if E0_ENABLE_PIN != X_ENABLE_PIN && E1_ENABLE_PIN != Y_ENABLE_PIN // Only disable on boards that have separate ENABLE_PINS + if (parser.seen('E')) disable_e_steppers(); + #endif + } + + #if ENABLED(AUTO_BED_LEVELING_UBL) && ENABLED(ULTIPANEL) // Only needed with an LCD + if (ubl.lcd_map_control) ubl.lcd_map_control = defer_return_to_status = false; + #endif + } +} + +/** + * M85: Set inactivity shutdown timer with parameter S. To disable set zero (default) + */ +inline void gcode_M85() { + if (parser.seen('S')) max_inactive_time = parser.value_millis_from_seconds(); +} + +/** + * Multi-stepper support for M92, M201, M203 + */ +#if ENABLED(DISTINCT_E_FACTORS) + #define GET_TARGET_EXTRUDER(CMD) if (get_target_extruder_from_command(CMD)) return + #define TARGET_EXTRUDER target_extruder +#else + #define GET_TARGET_EXTRUDER(CMD) NOOP + #define TARGET_EXTRUDER 0 +#endif + +/** + * M92: Set axis steps-per-unit for one or more axes, X, Y, Z, and E. + * (Follows the same syntax as G92) + * + * With multiple extruders use T to specify which one. + */ +inline void gcode_M92() { + + GET_TARGET_EXTRUDER(92); + + LOOP_XYZE(i) { + if (parser.seen(axis_codes[i])) { + if (i == E_AXIS) { + const float value = parser.value_per_axis_unit((AxisEnum)(E_AXIS + TARGET_EXTRUDER)); + if (value < 20.0) { + float factor = planner.axis_steps_per_mm[E_AXIS + TARGET_EXTRUDER] / value; // increase e constants if M92 E14 is given for netfab. + planner.max_jerk[E_AXIS] *= factor; + planner.max_feedrate_mm_s[E_AXIS + TARGET_EXTRUDER] *= factor; + planner.max_acceleration_steps_per_s2[E_AXIS + TARGET_EXTRUDER] *= factor; + } + planner.axis_steps_per_mm[E_AXIS + TARGET_EXTRUDER] = value; + } + else { + planner.axis_steps_per_mm[i] = parser.value_per_axis_unit((AxisEnum)i); + } + } + } + planner.refresh_positioning(); +} + +/** + * Output the current position to serial + */ +void report_current_position() { + SERIAL_PROTOCOLPGM("X:"); + SERIAL_PROTOCOL(LOGICAL_X_POSITION(current_position[X_AXIS])); + SERIAL_PROTOCOLPGM(" Y:"); + SERIAL_PROTOCOL(LOGICAL_Y_POSITION(current_position[Y_AXIS])); + SERIAL_PROTOCOLPGM(" Z:"); + SERIAL_PROTOCOL(LOGICAL_Z_POSITION(current_position[Z_AXIS])); + SERIAL_PROTOCOLPGM(" E:"); + SERIAL_PROTOCOL(current_position[E_AXIS]); + + stepper.report_positions(); + + #if IS_SCARA + SERIAL_PROTOCOLPAIR("SCARA Theta:", stepper.get_axis_position_degrees(A_AXIS)); + SERIAL_PROTOCOLLNPAIR(" Psi+Theta:", stepper.get_axis_position_degrees(B_AXIS)); + SERIAL_EOL(); + #endif +} + +#ifdef M114_DETAIL + + void report_xyze(const float pos[], const uint8_t n = 4, const uint8_t precision = 3) { + char str[12]; + for (uint8_t i = 0; i < n; i++) { + SERIAL_CHAR(' '); + SERIAL_CHAR(axis_codes[i]); + SERIAL_CHAR(':'); + SERIAL_PROTOCOL(dtostrf(pos[i], 8, precision, str)); + } + SERIAL_EOL(); + } + + inline void report_xyz(const float pos[]) { report_xyze(pos, 3); } + + void report_current_position_detail() { + + stepper.synchronize(); + + SERIAL_PROTOCOLPGM("\nLogical:"); + const float logical[XYZ] = { + LOGICAL_X_POSITION(current_position[X_AXIS]), + LOGICAL_Y_POSITION(current_position[Y_AXIS]), + LOGICAL_Z_POSITION(current_position[Z_AXIS]) + }; + report_xyz(logical); + + SERIAL_PROTOCOLPGM("Raw: "); + report_xyz(current_position); + + float leveled[XYZ] = { current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] }; + + #if PLANNER_LEVELING + SERIAL_PROTOCOLPGM("Leveled:"); + planner.apply_leveling(leveled); + report_xyz(leveled); + + SERIAL_PROTOCOLPGM("UnLevel:"); + float unleveled[XYZ] = { leveled[X_AXIS], leveled[Y_AXIS], leveled[Z_AXIS] }; + planner.unapply_leveling(unleveled); + report_xyz(unleveled); + #endif + + #if IS_KINEMATIC + #if IS_SCARA + SERIAL_PROTOCOLPGM("ScaraK: "); + #else + SERIAL_PROTOCOLPGM("DeltaK: "); + #endif + inverse_kinematics(leveled); // writes delta[] + report_xyz(delta); + #endif + + SERIAL_PROTOCOLPGM("Stepper:"); + LOOP_XYZE(i) { + SERIAL_CHAR(' '); + SERIAL_CHAR(axis_codes[i]); + SERIAL_CHAR(':'); + SERIAL_PROTOCOL(stepper.position((AxisEnum)i)); + } + SERIAL_EOL(); + + #if IS_SCARA + const float deg[XYZ] = { + stepper.get_axis_position_degrees(A_AXIS), + stepper.get_axis_position_degrees(B_AXIS) + }; + SERIAL_PROTOCOLPGM("Degrees:"); + report_xyze(deg, 2); + #endif + + SERIAL_PROTOCOLPGM("FromStp:"); + get_cartesian_from_steppers(); // writes cartes[XYZ] (with forward kinematics) + const float from_steppers[XYZE] = { cartes[X_AXIS], cartes[Y_AXIS], cartes[Z_AXIS], stepper.get_axis_position_mm(E_AXIS) }; + report_xyze(from_steppers); + + const float diff[XYZE] = { + from_steppers[X_AXIS] - leveled[X_AXIS], + from_steppers[Y_AXIS] - leveled[Y_AXIS], + from_steppers[Z_AXIS] - leveled[Z_AXIS], + from_steppers[E_AXIS] - current_position[E_AXIS] + }; + SERIAL_PROTOCOLPGM("Differ: "); + report_xyze(diff); + } +#endif // M114_DETAIL + +/** + * M114: Report current position to host + */ +inline void gcode_M114() { + + #ifdef M114_DETAIL + if (parser.seen('D')) { + report_current_position_detail(); + return; + } + #endif + + stepper.synchronize(); + report_current_position(); +} + +/** + * M115: Capabilities string + */ + +#if ENABLED(EXTENDED_CAPABILITIES_REPORT) + static void cap_line(const char * const name, bool ena=false) { + SERIAL_PROTOCOLPGM("Cap:"); + serialprintPGM(name); + SERIAL_PROTOCOLPGM(":"); + SERIAL_PROTOCOLLN(int(ena ? 1 : 0)); + } +#endif + +inline void gcode_M115() { + SERIAL_PROTOCOLLNPGM(MSG_M115_REPORT); + + #if ENABLED(EXTENDED_CAPABILITIES_REPORT) + + // SERIAL_XON_XOFF + cap_line(PSTR("SERIAL_XON_XOFF") + #if ENABLED(SERIAL_XON_XOFF) + , true + #endif + ); + + // EEPROM (M500, M501) + cap_line(PSTR("EEPROM") + #if ENABLED(EEPROM_SETTINGS) + , true + #endif + ); + + // Volumetric Extrusion (M200) + cap_line(PSTR("VOLUMETRIC") + #if DISABLED(NO_VOLUMETRICS) + , true + #endif + ); + + // AUTOREPORT_TEMP (M155) + cap_line(PSTR("AUTOREPORT_TEMP") + #if ENABLED(AUTO_REPORT_TEMPERATURES) + , true + #endif + ); + + // PROGRESS (M530 S L, M531 , M532 X L) + cap_line(PSTR("PROGRESS")); + + // Print Job timer M75, M76, M77 + cap_line(PSTR("PRINT_JOB"), true); + + // AUTOLEVEL (G29) + cap_line(PSTR("AUTOLEVEL") + #if HAS_AUTOLEVEL + , true + #endif + ); + + // Z_PROBE (G30) + cap_line(PSTR("Z_PROBE") + #if HAS_BED_PROBE + , true + #endif + ); + + // MESH_REPORT (M420 V) + cap_line(PSTR("LEVELING_DATA") + #if HAS_LEVELING + , true + #endif + ); + + // BUILD_PERCENT (M73) + cap_line(PSTR("BUILD_PERCENT") + #if ENABLED(LCD_SET_PROGRESS_MANUALLY) + , true + #endif + ); + + // SOFTWARE_POWER (M80, M81) + cap_line(PSTR("SOFTWARE_POWER") + #if HAS_POWER_SWITCH + , true + #endif + ); + + // CASE LIGHTS (M355) + cap_line(PSTR("TOGGLE_LIGHTS") + #if HAS_CASE_LIGHT + , true + #endif + ); + cap_line(PSTR("CASE_LIGHT_BRIGHTNESS") + #if HAS_CASE_LIGHT + , USEABLE_HARDWARE_PWM(CASE_LIGHT_PIN) + #endif + ); + + // EMERGENCY_PARSER (M108, M112, M410) + cap_line(PSTR("EMERGENCY_PARSER") + #if ENABLED(EMERGENCY_PARSER) + , true + #endif + ); + + // AUTOREPORT_SD_STATUS (M27 extension) + cap_line(PSTR("AUTOREPORT_SD_STATUS") + #if ENABLED(AUTO_REPORT_SD_STATUS) + , true + #endif + ); + + #endif // EXTENDED_CAPABILITIES_REPORT +} + +/** + * M117: Set LCD Status Message + */ +inline void gcode_M117() { lcd_setstatus(parser.string_arg); } + +/** + * M118: Display a message in the host console. + * + * A1 Append '// ' for an action command, as in OctoPrint + * E1 Have the host 'echo:' the text + */ +inline void gcode_M118() { + if (parser.seenval('E') && parser.value_bool()) SERIAL_ECHO_START(); + if (parser.seenval('A') && parser.value_bool()) SERIAL_ECHOPGM("// "); + SERIAL_ECHOLN(parser.string_arg); +} + +/** + * M119: Output endstop states to serial output + */ +inline void gcode_M119() { endstops.M119(); } + +/** + * M120: Enable endstops and set non-homing endstop state to "enabled" + */ +inline void gcode_M120() { endstops.enable_globally(true); } + +/** + * M121: Disable endstops and set non-homing endstop state to "disabled" + */ +inline void gcode_M121() { endstops.enable_globally(false); } + +#if ENABLED(PARK_HEAD_ON_PAUSE) + + /** + * M125: Store current position and move to filament change position. + * Called on pause (by M25) to prevent material leaking onto the + * object. On resume (M24) the head will be moved back and the + * print will resume. + * + * If Marlin is compiled without SD Card support, M125 can be + * used directly to pause the print and move to park position, + * resuming with a button click or M108. + * + * L = override retract length + * X = override X + * Y = override Y + * Z = override Z raise + */ + inline void gcode_M125() { + + // Initial retract before move to filament change position + const float retract = -FABS(parser.seen('L') ? parser.value_axis_units(E_AXIS) : 0 + #ifdef PAUSE_PARK_RETRACT_LENGTH + + (PAUSE_PARK_RETRACT_LENGTH) + #endif + ); + + point_t park_point = NOZZLE_PARK_POINT; + + // Move XY axes to filament change position or given position + if (parser.seenval('X')) park_point.x = parser.linearval('X'); + if (parser.seenval('Y')) park_point.y = parser.linearval('Y'); + + // Lift Z axis + if (parser.seenval('Z')) park_point.z = parser.linearval('Z'); + + #if HOTENDS > 1 && DISABLED(DUAL_X_CARRIAGE) && DISABLED(DELTA) + park_point.x += (active_extruder ? hotend_offset[X_AXIS][active_extruder] : 0); + park_point.y += (active_extruder ? hotend_offset[Y_AXIS][active_extruder] : 0); + #endif + + #if DISABLED(SDSUPPORT) + const bool job_running = print_job_timer.isRunning(); + #endif + + if (pause_print(retract, park_point)) { + #if DISABLED(SDSUPPORT) + // Wait for lcd click or M108 + wait_for_filament_reload(); + + // Return to print position and continue + resume_print(); + + if (job_running) print_job_timer.start(); + #endif + } + } + +#endif // PARK_HEAD_ON_PAUSE + +#if HAS_COLOR_LEDS + + /** + * M150: Set Status LED Color - Use R-U-B-W for R-G-B-W + * and Brightness - Use P (for NEOPIXEL only) + * + * Always sets all 3 or 4 components. If a component is left out, set to 0. + * If brightness is left out, no value changed + * + * Examples: + * + * M150 R255 ; Turn LED red + * M150 R255 U127 ; Turn LED orange (PWM only) + * M150 ; Turn LED off + * M150 R U B ; Turn LED white + * M150 W ; Turn LED white using a white LED + * M150 P127 ; Set LED 50% brightness + * M150 P ; Set LED full brightness + */ + inline void gcode_M150() { + leds.set_color(MakeLEDColor( + parser.seen('R') ? (parser.has_value() ? parser.value_byte() : 255) : 0, + parser.seen('U') ? (parser.has_value() ? parser.value_byte() : 255) : 0, + parser.seen('B') ? (parser.has_value() ? parser.value_byte() : 255) : 0, + parser.seen('W') ? (parser.has_value() ? parser.value_byte() : 255) : 0, + parser.seen('P') ? (parser.has_value() ? parser.value_byte() : 255) : pixels.getBrightness() + )); + } + +#endif // HAS_COLOR_LEDS + +#if DISABLED(NO_VOLUMETRICS) + + /** + * M200: Set filament diameter and set E axis units to cubic units + * + * T - Optional extruder number. Current extruder if omitted. + * D - Diameter of the filament. Use "D0" to switch back to linear units on the E axis. + */ + inline void gcode_M200() { + + if (get_target_extruder_from_command(200)) return; + + if (parser.seen('D')) { + // setting any extruder filament size disables volumetric on the assumption that + // slicers either generate in extruder values as cubic mm or as as filament feeds + // for all extruders + if ( (parser.volumetric_enabled = (parser.value_linear_units() != 0.0)) ) + planner.set_filament_size(target_extruder, parser.value_linear_units()); + } + planner.calculate_volumetric_multipliers(); + } + +#endif // !NO_VOLUMETRICS + +/** + * M201: Set max acceleration in units/s^2 for print moves (M201 X1000 Y1000) + * + * With multiple extruders use T to specify which one. + */ +inline void gcode_M201() { + + GET_TARGET_EXTRUDER(201); + + LOOP_XYZE(i) { + if (parser.seen(axis_codes[i])) { + const uint8_t a = i + (i == E_AXIS ? TARGET_EXTRUDER : 0); + planner.max_acceleration_mm_per_s2[a] = parser.value_axis_units((AxisEnum)a); + } + } + // steps per sq second need to be updated to agree with the units per sq second (as they are what is used in the planner) + planner.reset_acceleration_rates(); +} + +#if 0 // Not used for Sprinter/grbl gen6 + inline void gcode_M202() { + LOOP_XYZE(i) { + if (parser.seen(axis_codes[i])) axis_travel_steps_per_sqr_second[i] = parser.value_axis_units((AxisEnum)i) * planner.axis_steps_per_mm[i]; + } + } +#endif + + +/** + * M203: Set maximum feedrate that your machine can sustain (M203 X200 Y200 Z300 E10000) in units/sec + * + * With multiple extruders use T to specify which one. + */ +inline void gcode_M203() { + + GET_TARGET_EXTRUDER(203); + + LOOP_XYZE(i) + if (parser.seen(axis_codes[i])) { + const uint8_t a = i + (i == E_AXIS ? TARGET_EXTRUDER : 0); + planner.max_feedrate_mm_s[a] = parser.value_axis_units((AxisEnum)a); + } +} + +/** + * M204: Set Accelerations in units/sec^2 (M204 P1200 R3000 T3000) + * + * P = Printing moves + * R = Retract only (no X, Y, Z) moves + * T = Travel (non printing) moves + */ +inline void gcode_M204() { + bool report = true; + if (parser.seenval('S')) { // Kept for legacy compatibility. Should NOT BE USED for new developments. + planner.travel_acceleration = planner.acceleration = parser.value_linear_units(); + report = false; + } + if (parser.seenval('P')) { + planner.acceleration = parser.value_linear_units(); + report = false; + } + if (parser.seenval('R')) { + planner.retract_acceleration = parser.value_linear_units(); + report = false; + } + if (parser.seenval('T')) { + planner.travel_acceleration = parser.value_linear_units(); + report = false; + } + if (report) { + SERIAL_ECHOPAIR("Acceleration: P", planner.acceleration); + SERIAL_ECHOPAIR(" R", planner.retract_acceleration); + SERIAL_ECHOLNPAIR(" T", planner.travel_acceleration); + } +} + +/** + * M205: Set Advanced Settings + * + * S = Min Feed Rate (units/s) + * T = Min Travel Feed Rate (units/s) + * B = Min Segment Time (µs) + * X = Max X Jerk (units/sec^2) + * Y = Max Y Jerk (units/sec^2) + * Z = Max Z Jerk (units/sec^2) + * E = Max E Jerk (units/sec^2) + */ +inline void gcode_M205() { + if (parser.seen('S')) planner.min_feedrate_mm_s = parser.value_linear_units(); + if (parser.seen('T')) planner.min_travel_feedrate_mm_s = parser.value_linear_units(); + if (parser.seen('B')) planner.min_segment_time_us = parser.value_ulong(); + if (parser.seen('X')) planner.max_jerk[X_AXIS] = parser.value_linear_units(); + if (parser.seen('Y')) planner.max_jerk[Y_AXIS] = parser.value_linear_units(); + if (parser.seen('Z')) { + planner.max_jerk[Z_AXIS] = parser.value_linear_units(); + #if HAS_MESH + if (planner.max_jerk[Z_AXIS] <= 0.1) + SERIAL_ECHOLNPGM("WARNING! Low Z Jerk may lead to unwanted pauses."); + #endif + } + if (parser.seen('E')) planner.max_jerk[E_AXIS] = parser.value_linear_units(); +} + +#if HAS_M206_COMMAND + + /** + * M206: Set Additional Homing Offset (X Y Z). SCARA aliases T=X, P=Y + * + * *** @thinkyhead: I recommend deprecating M206 for SCARA in favor of M665. + * *** M206 for SCARA will remain enabled in 1.1.x for compatibility. + * *** In the next 1.2 release, it will simply be disabled by default. + */ + inline void gcode_M206() { + LOOP_XYZ(i) + if (parser.seen(axis_codes[i])) + set_home_offset((AxisEnum)i, parser.value_linear_units()); + + #if ENABLED(MORGAN_SCARA) + if (parser.seen('T')) set_home_offset(A_AXIS, parser.value_float()); // Theta + if (parser.seen('P')) set_home_offset(B_AXIS, parser.value_float()); // Psi + #endif + + report_current_position(); + } + +#endif // HAS_M206_COMMAND + +#if ENABLED(DELTA) + /** + * M665: Set delta configurations + * + * H = delta height + * L = diagonal rod + * R = delta radius + * S = segments per second + * B = delta calibration radius + * X = Alpha (Tower 1) angle trim + * Y = Beta (Tower 2) angle trim + * Z = Rotate A and B by this angle + */ + inline void gcode_M665() { + if (parser.seen('H')) delta_height = parser.value_linear_units(); + if (parser.seen('L')) delta_diagonal_rod = parser.value_linear_units(); + if (parser.seen('R')) delta_radius = parser.value_linear_units(); + if (parser.seen('S')) delta_segments_per_second = parser.value_float(); + if (parser.seen('B')) delta_calibration_radius = parser.value_float(); + if (parser.seen('X')) delta_tower_angle_trim[A_AXIS] = parser.value_float(); + if (parser.seen('Y')) delta_tower_angle_trim[B_AXIS] = parser.value_float(); + if (parser.seen('Z')) delta_tower_angle_trim[C_AXIS] = parser.value_float(); + recalc_delta_settings(); + } + /** + * M666: Set delta endstop adjustment + */ + inline void gcode_M666() { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOLNPGM(">>> gcode_M666"); + } + #endif + LOOP_XYZ(i) { + if (parser.seen(axis_codes[i])) { + if (parser.value_linear_units() * Z_HOME_DIR <= 0) + delta_endstop_adj[i] = parser.value_linear_units(); + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR("delta_endstop_adj[", axis_codes[i]); + SERIAL_ECHOLNPAIR("] = ", delta_endstop_adj[i]); + } + #endif + } + } + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOLNPGM("<<< gcode_M666"); + } + #endif + } + +#elif IS_SCARA + + /** + * M665: Set SCARA settings + * + * Parameters: + * + * S[segments-per-second] - Segments-per-second + * P[theta-psi-offset] - Theta-Psi offset, added to the shoulder (A/X) angle + * T[theta-offset] - Theta offset, added to the elbow (B/Y) angle + * + * A, P, and X are all aliases for the shoulder angle + * B, T, and Y are all aliases for the elbow angle + */ + inline void gcode_M665() { + if (parser.seen('S')) delta_segments_per_second = parser.value_float(); + + const bool hasA = parser.seen('A'), hasP = parser.seen('P'), hasX = parser.seen('X'); + const uint8_t sumAPX = hasA + hasP + hasX; + if (sumAPX == 1) + home_offset[A_AXIS] = parser.value_float(); + else if (sumAPX > 1) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM("Only one of A, P, or X is allowed."); + return; + } + + const bool hasB = parser.seen('B'), hasT = parser.seen('T'), hasY = parser.seen('Y'); + const uint8_t sumBTY = hasB + hasT + hasY; + if (sumBTY == 1) + home_offset[B_AXIS] = parser.value_float(); + else if (sumBTY > 1) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM("Only one of B, T, or Y is allowed."); + return; + } + } + +#elif ENABLED(X_DUAL_ENDSTOPS) || ENABLED(Y_DUAL_ENDSTOPS) || ENABLED(Z_DUAL_ENDSTOPS) + + /** + * M666: Set Dual Endstops offsets for X, Y, and/or Z. + * With no parameters report current offsets. + */ + inline void gcode_M666() { + bool report = true; + #if ENABLED(X_DUAL_ENDSTOPS) + if (parser.seenval('X')) { + endstops.x_endstop_adj = parser.value_linear_units(); + report = false; + } + #endif + #if ENABLED(Y_DUAL_ENDSTOPS) + if (parser.seenval('Y')) { + endstops.y_endstop_adj = parser.value_linear_units(); + report = false; + } + #endif + #if ENABLED(Z_DUAL_ENDSTOPS) + if (parser.seenval('Z')) { + endstops.z_endstop_adj = parser.value_linear_units(); + report = false; + } + #endif + if (report) { + SERIAL_ECHOPGM("Dual Endstop Adjustment (mm): "); + #if ENABLED(X_DUAL_ENDSTOPS) + SERIAL_ECHOPAIR(" X", endstops.x_endstop_adj); + #endif + #if ENABLED(Y_DUAL_ENDSTOPS) + SERIAL_ECHOPAIR(" Y", endstops.y_endstop_adj); + #endif + #if ENABLED(Z_DUAL_ENDSTOPS) + SERIAL_ECHOPAIR(" Z", endstops.z_endstop_adj); + #endif + SERIAL_EOL(); + } + } + +#endif // X_DUAL_ENDSTOPS || Y_DUAL_ENDSTOPS || Z_DUAL_ENDSTOPS + +#if ENABLED(FWRETRACT) + + /** + * M207: Set firmware retraction values + * + * S[+units] retract_length + * W[+units] swap_retract_length (multi-extruder) + * F[units/min] retract_feedrate_mm_s + * Z[units] retract_zlift + */ + inline void gcode_M207() { + if (parser.seen('S')) fwretract.retract_length = parser.value_axis_units(E_AXIS); + if (parser.seen('F')) fwretract.retract_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS)); + if (parser.seen('Z')) fwretract.retract_zlift = parser.value_linear_units(); + if (parser.seen('W')) fwretract.swap_retract_length = parser.value_axis_units(E_AXIS); + } + + /** + * M208: Set firmware un-retraction values + * + * S[+units] retract_recover_length (in addition to M207 S*) + * W[+units] swap_retract_recover_length (multi-extruder) + * F[units/min] retract_recover_feedrate_mm_s + * R[units/min] swap_retract_recover_feedrate_mm_s + */ + inline void gcode_M208() { + if (parser.seen('S')) fwretract.retract_recover_length = parser.value_axis_units(E_AXIS); + if (parser.seen('F')) fwretract.retract_recover_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS)); + if (parser.seen('R')) fwretract.swap_retract_recover_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS)); + if (parser.seen('W')) fwretract.swap_retract_recover_length = parser.value_axis_units(E_AXIS); + } + + /** + * M209: Enable automatic retract (M209 S1) + * For slicers that don't support G10/11, reversed extrude-only + * moves will be classified as retraction. + */ + inline void gcode_M209() { + if (MIN_AUTORETRACT <= MAX_AUTORETRACT) { + if (parser.seen('S')) { + fwretract.autoretract_enabled = parser.value_bool(); + for (uint8_t i = 0; i < EXTRUDERS; i++) fwretract.retracted[i] = false; + } + } + } + +#endif // FWRETRACT + +/** + * M211: Enable, Disable, and/or Report software endstops + * + * Usage: M211 S1 to enable, M211 S0 to disable, M211 alone for report + */ +inline void gcode_M211() { + SERIAL_ECHO_START(); + #if HAS_SOFTWARE_ENDSTOPS + if (parser.seen('S')) soft_endstops_enabled = parser.value_bool(); + SERIAL_ECHOPGM(MSG_SOFT_ENDSTOPS); + serialprintPGM(soft_endstops_enabled ? PSTR(MSG_ON) : PSTR(MSG_OFF)); + #else + SERIAL_ECHOPGM(MSG_SOFT_ENDSTOPS); + SERIAL_ECHOPGM(MSG_OFF); + #endif + SERIAL_ECHOPGM(MSG_SOFT_MIN); + SERIAL_ECHOPAIR( MSG_X, LOGICAL_X_POSITION(soft_endstop_min[X_AXIS])); + SERIAL_ECHOPAIR(" " MSG_Y, LOGICAL_Y_POSITION(soft_endstop_min[Y_AXIS])); + SERIAL_ECHOPAIR(" " MSG_Z, LOGICAL_Z_POSITION(soft_endstop_min[Z_AXIS])); + SERIAL_ECHOPGM(MSG_SOFT_MAX); + SERIAL_ECHOPAIR( MSG_X, LOGICAL_X_POSITION(soft_endstop_max[X_AXIS])); + SERIAL_ECHOPAIR(" " MSG_Y, LOGICAL_Y_POSITION(soft_endstop_max[Y_AXIS])); + SERIAL_ECHOLNPAIR(" " MSG_Z, LOGICAL_Z_POSITION(soft_endstop_max[Z_AXIS])); +} + +#if HOTENDS > 1 + + /** + * M218 - Set/get hotend offset (in linear units) + * + * T + * X + * Y + * Z - Available with DUAL_X_CARRIAGE and SWITCHING_NOZZLE + */ + inline void gcode_M218() { + if (get_target_extruder_from_command(218) || target_extruder == 0) return; + + bool report = true; + if (parser.seenval('X')) { + hotend_offset[X_AXIS][target_extruder] = parser.value_linear_units(); + report = false; + } + if (parser.seenval('Y')) { + hotend_offset[Y_AXIS][target_extruder] = parser.value_linear_units(); + report = false; + } + + #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(SWITCHING_NOZZLE) || ENABLED(PARKING_EXTRUDER) + if (parser.seenval('Z')) { + hotend_offset[Z_AXIS][target_extruder] = parser.value_linear_units(); + report = false; + } + #endif + + if (report) { + SERIAL_ECHO_START(); + SERIAL_ECHOPGM(MSG_HOTEND_OFFSET); + HOTEND_LOOP() { + SERIAL_CHAR(' '); + SERIAL_ECHO(hotend_offset[X_AXIS][e]); + SERIAL_CHAR(','); + SERIAL_ECHO(hotend_offset[Y_AXIS][e]); + #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(SWITCHING_NOZZLE) || ENABLED(PARKING_EXTRUDER) + SERIAL_CHAR(','); + SERIAL_ECHO(hotend_offset[Z_AXIS][e]); + #endif + } + SERIAL_EOL(); + } + + #if ENABLED(DELTA) + if (target_extruder == active_extruder) + do_blocking_move_to_xy(current_position[X_AXIS], current_position[Y_AXIS], planner.max_feedrate_mm_s[X_AXIS]); + #endif + } + +#endif // HOTENDS > 1 + +/** + * M220: Set speed percentage factor, aka "Feed Rate" (M220 S95) + */ +inline void gcode_M220() { + if (parser.seenval('S')) feedrate_percentage = parser.value_int(); +} + +/** + * M221: Set extrusion percentage (M221 T0 S95) + */ +inline void gcode_M221() { + if (get_target_extruder_from_command(221)) return; + if (parser.seenval('S')) { + planner.flow_percentage[target_extruder] = parser.value_int(); + planner.refresh_e_factor(target_extruder); + } +} + +/** + * M226: Wait until the specified pin reaches the state required (M226 P S) + */ +inline void gcode_M226() { + if (parser.seen('P')) { + const int pin = parser.value_int(), + pin_state = parser.intval('S', -1); // required pin state - default is inverted + + if (WITHIN(pin_state, -1, 1) && pin > -1 && !pin_is_protected(pin)) { + + int target = LOW; + + stepper.synchronize(); + + pinMode(pin, INPUT); + switch (pin_state) { + case 1: + target = HIGH; + break; + case 0: + target = LOW; + break; + case -1: + target = !digitalRead(pin); + break; + } + + while (digitalRead(pin) != target) idle(); + + } // pin_state -1 0 1 && pin > -1 + } // parser.seen('P') +} + +#if ENABLED(EXPERIMENTAL_I2CBUS) + + /** + * M260: Send data to a I2C slave device + * + * This is a PoC, the formating and arguments for the GCODE will + * change to be more compatible, the current proposal is: + * + * M260 A ; Sets the I2C slave address the data will be sent to + * + * M260 B + * M260 B + * M260 B + * + * M260 S1 ; Send the buffered data and reset the buffer + * M260 R1 ; Reset the buffer without sending data + * + */ + inline void gcode_M260() { + // Set the target address + if (parser.seen('A')) i2c.address(parser.value_byte()); + + // Add a new byte to the buffer + if (parser.seen('B')) i2c.addbyte(parser.value_byte()); + + // Flush the buffer to the bus + if (parser.seen('S')) i2c.send(); + + // Reset and rewind the buffer + else if (parser.seen('R')) i2c.reset(); + } + + /** + * M261: Request X bytes from I2C slave device + * + * Usage: M261 A B + */ + inline void gcode_M261() { + if (parser.seen('A')) i2c.address(parser.value_byte()); + + uint8_t bytes = parser.byteval('B', 1); + + if (i2c.addr && bytes && bytes <= TWIBUS_BUFFER_SIZE) { + i2c.relay(bytes); + } + else { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM("Bad i2c request"); + } + } + +#endif // EXPERIMENTAL_I2CBUS + +#if HAS_SERVOS + + /** + * M280: Get or set servo position. P [S] + */ + inline void gcode_M280() { + if (!parser.seen('P')) return; + const int servo_index = parser.value_int(); + if (WITHIN(servo_index, 0, NUM_SERVOS - 1)) { + if (parser.seen('S')) + MOVE_SERVO(servo_index, parser.value_int()); + else { + SERIAL_ECHO_START(); + SERIAL_ECHOPAIR(" Servo ", servo_index); + SERIAL_ECHOLNPAIR(": ", servo[servo_index].read()); + } + } + else { + SERIAL_ERROR_START(); + SERIAL_ECHOPAIR("Servo ", servo_index); + SERIAL_ECHOLNPGM(" out of range"); + } + } + +#endif // HAS_SERVOS + +#if ENABLED(BABYSTEPPING) + + #if ENABLED(BABYSTEP_ZPROBE_OFFSET) + FORCE_INLINE void mod_zprobe_zoffset(const float &offs) { + zprobe_zoffset += offs; + SERIAL_ECHO_START(); + SERIAL_ECHOLNPAIR(MSG_PROBE_Z_OFFSET ": ", zprobe_zoffset); + } + #endif + + /** + * M290: Babystepping + */ + inline void gcode_M290() { + #if ENABLED(BABYSTEP_XY) + for (uint8_t a = X_AXIS; a <= Z_AXIS; a++) + if (parser.seenval(axis_codes[a]) || (a == Z_AXIS && parser.seenval('S'))) { + const float offs = constrain(parser.value_axis_units((AxisEnum)a), -2, 2); + thermalManager.babystep_axis((AxisEnum)a, offs * planner.axis_steps_per_mm[a]); + #if ENABLED(BABYSTEP_ZPROBE_OFFSET) + if (a == Z_AXIS && (!parser.seen('P') || parser.value_bool())) mod_zprobe_zoffset(offs); + #endif + } + #else + if (parser.seenval('Z') || parser.seenval('S')) { + const float offs = constrain(parser.value_axis_units(Z_AXIS), -2, 2); + thermalManager.babystep_axis(Z_AXIS, offs * planner.axis_steps_per_mm[Z_AXIS]); + #if ENABLED(BABYSTEP_ZPROBE_OFFSET) + if (!parser.seen('P') || parser.value_bool()) mod_zprobe_zoffset(offs); + #endif + } + #endif + } + +#endif // BABYSTEPPING + +#if HAS_BUZZER + + /** + * M300: Play beep sound S P + */ + inline void gcode_M300() { + uint16_t const frequency = parser.ushortval('S', 260); + uint16_t duration = parser.ushortval('P', 1000); + + // Limits the tone duration to 0-5 seconds. + NOMORE(duration, 5000); + + BUZZ(duration, frequency); + } + +#endif // HAS_BUZZER + +#if ENABLED(PIDTEMP) + + /** + * M301: Set PID parameters P I D (and optionally C, L) + * + * P[float] Kp term + * I[float] Ki term (unscaled) + * D[float] Kd term (unscaled) + * + * With PID_EXTRUSION_SCALING: + * + * C[float] Kc term + * L[float] LPQ length + */ + inline void gcode_M301() { + + // multi-extruder PID patch: M301 updates or prints a single extruder's PID values + // default behaviour (omitting E parameter) is to update for extruder 0 only + const uint8_t e = parser.byteval('E'); // extruder being updated + + if (e < HOTENDS) { // catch bad input value + if (parser.seen('P')) PID_PARAM(Kp, e) = parser.value_float(); + if (parser.seen('I')) PID_PARAM(Ki, e) = scalePID_i(parser.value_float()); + if (parser.seen('D')) PID_PARAM(Kd, e) = scalePID_d(parser.value_float()); + #if ENABLED(PID_EXTRUSION_SCALING) + if (parser.seen('C')) PID_PARAM(Kc, e) = parser.value_float(); + if (parser.seen('L')) lpq_len = parser.value_float(); + NOMORE(lpq_len, LPQ_MAX_LEN); + #endif + + thermalManager.updatePID(); + SERIAL_ECHO_START(); + #if ENABLED(PID_PARAMS_PER_HOTEND) + SERIAL_ECHOPAIR(" e:", e); // specify extruder in serial output + #endif // PID_PARAMS_PER_HOTEND + SERIAL_ECHOPAIR(" p:", PID_PARAM(Kp, e)); + SERIAL_ECHOPAIR(" i:", unscalePID_i(PID_PARAM(Ki, e))); + SERIAL_ECHOPAIR(" d:", unscalePID_d(PID_PARAM(Kd, e))); + #if ENABLED(PID_EXTRUSION_SCALING) + //Kc does not have scaling applied above, or in resetting defaults + SERIAL_ECHOPAIR(" c:", PID_PARAM(Kc, e)); + #endif + SERIAL_EOL(); + } + else { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_INVALID_EXTRUDER); + } + } + +#endif // PIDTEMP + +#if ENABLED(PIDTEMPBED) + + inline void gcode_M304() { + if (parser.seen('P')) thermalManager.bedKp = parser.value_float(); + if (parser.seen('I')) thermalManager.bedKi = scalePID_i(parser.value_float()); + if (parser.seen('D')) thermalManager.bedKd = scalePID_d(parser.value_float()); + + SERIAL_ECHO_START(); + SERIAL_ECHOPAIR(" p:", thermalManager.bedKp); + SERIAL_ECHOPAIR(" i:", unscalePID_i(thermalManager.bedKi)); + SERIAL_ECHOLNPAIR(" d:", unscalePID_d(thermalManager.bedKd)); + } + +#endif // PIDTEMPBED + +#if defined(CHDK) || HAS_PHOTOGRAPH + + /** + * M240: Trigger a camera by emulating a Canon RC-1 + * See http://www.doc-diy.net/photo/rc-1_hacked/ + */ + inline void gcode_M240() { + #ifdef CHDK + + OUT_WRITE(CHDK, HIGH); + chdkHigh = millis(); + chdkActive = true; + + #elif HAS_PHOTOGRAPH + + const uint8_t NUM_PULSES = 16; + const float PULSE_LENGTH = 0.01524; + for (int i = 0; i < NUM_PULSES; i++) { + WRITE(PHOTOGRAPH_PIN, HIGH); + _delay_ms(PULSE_LENGTH); + WRITE(PHOTOGRAPH_PIN, LOW); + _delay_ms(PULSE_LENGTH); + } + delay(7.33); + for (int i = 0; i < NUM_PULSES; i++) { + WRITE(PHOTOGRAPH_PIN, HIGH); + _delay_ms(PULSE_LENGTH); + WRITE(PHOTOGRAPH_PIN, LOW); + _delay_ms(PULSE_LENGTH); + } + + #endif // !CHDK && HAS_PHOTOGRAPH + } + +#endif // CHDK || PHOTOGRAPH_PIN + +#if HAS_LCD_CONTRAST + + /** + * M250: Read and optionally set the LCD contrast + */ + inline void gcode_M250() { + if (parser.seen('C')) set_lcd_contrast(parser.value_int()); + SERIAL_PROTOCOLPGM("lcd contrast value: "); + SERIAL_PROTOCOL(lcd_contrast); + SERIAL_EOL(); + } + +#endif // HAS_LCD_CONTRAST + +#if ENABLED(PREVENT_COLD_EXTRUSION) + + /** + * M302: Allow cold extrudes, or set the minimum extrude temperature + * + * S sets the minimum extrude temperature + * P enables (1) or disables (0) cold extrusion + * + * Examples: + * + * M302 ; report current cold extrusion state + * M302 P0 ; enable cold extrusion checking + * M302 P1 ; disables cold extrusion checking + * M302 S0 ; always allow extrusion (disables checking) + * M302 S170 ; only allow extrusion above 170 + * M302 S170 P1 ; set min extrude temp to 170 but leave disabled + */ + inline void gcode_M302() { + const bool seen_S = parser.seen('S'); + if (seen_S) { + thermalManager.extrude_min_temp = parser.value_celsius(); + thermalManager.allow_cold_extrude = (thermalManager.extrude_min_temp == 0); + } + + if (parser.seen('P')) + thermalManager.allow_cold_extrude = (thermalManager.extrude_min_temp == 0) || parser.value_bool(); + else if (!seen_S) { + // Report current state + SERIAL_ECHO_START(); + SERIAL_ECHOPAIR("Cold extrudes are ", (thermalManager.allow_cold_extrude ? "en" : "dis")); + SERIAL_ECHOPAIR("abled (min temp ", thermalManager.extrude_min_temp); + SERIAL_ECHOLNPGM("C)"); + } + } + +#endif // PREVENT_COLD_EXTRUSION + +/** + * M303: PID relay autotune + * + * S sets the target temperature. (default 150C / 70C) + * E (-1 for the bed) (default 0) + * C + * U with a non-zero value will apply the result to current settings + */ +inline void gcode_M303() { + #if HAS_PID_HEATING + const int e = parser.intval('E'), c = parser.intval('C', 5); + const bool u = parser.boolval('U'); + + int16_t temp = parser.celsiusval('S', e < 0 ? 70 : 150); + + if (WITHIN(e, 0, HOTENDS - 1)) + target_extruder = e; + + #if DISABLED(BUSY_WHILE_HEATING) + KEEPALIVE_STATE(NOT_BUSY); + #endif + + thermalManager.PID_autotune(temp, e, c, u); + + #if DISABLED(BUSY_WHILE_HEATING) + KEEPALIVE_STATE(IN_HANDLER); + #endif + #else + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_M303_DISABLED); + #endif +} + +#if ENABLED(MORGAN_SCARA) + + bool SCARA_move_to_cal(const uint8_t delta_a, const uint8_t delta_b) { + if (IsRunning()) { + forward_kinematics_SCARA(delta_a, delta_b); + destination[X_AXIS] = cartes[X_AXIS]; + destination[Y_AXIS] = cartes[Y_AXIS]; + destination[Z_AXIS] = current_position[Z_AXIS]; + prepare_move_to_destination(); + return true; + } + return false; + } + + /** + * M360: SCARA calibration: Move to cal-position ThetaA (0 deg calibration) + */ + inline bool gcode_M360() { + SERIAL_ECHOLNPGM(" Cal: Theta 0"); + return SCARA_move_to_cal(0, 120); + } + + /** + * M361: SCARA calibration: Move to cal-position ThetaB (90 deg calibration - steps per degree) + */ + inline bool gcode_M361() { + SERIAL_ECHOLNPGM(" Cal: Theta 90"); + return SCARA_move_to_cal(90, 130); + } + + /** + * M362: SCARA calibration: Move to cal-position PsiA (0 deg calibration) + */ + inline bool gcode_M362() { + SERIAL_ECHOLNPGM(" Cal: Psi 0"); + return SCARA_move_to_cal(60, 180); + } + + /** + * M363: SCARA calibration: Move to cal-position PsiB (90 deg calibration - steps per degree) + */ + inline bool gcode_M363() { + SERIAL_ECHOLNPGM(" Cal: Psi 90"); + return SCARA_move_to_cal(50, 90); + } + + /** + * M364: SCARA calibration: Move to cal-position PsiC (90 deg to Theta calibration position) + */ + inline bool gcode_M364() { + SERIAL_ECHOLNPGM(" Cal: Theta-Psi 90"); + return SCARA_move_to_cal(45, 135); + } + +#endif // SCARA + +#if ENABLED(EXT_SOLENOID) + + void enable_solenoid(const uint8_t num) { + switch (num) { + case 0: + OUT_WRITE(SOL0_PIN, HIGH); + break; + #if HAS_SOLENOID_1 && EXTRUDERS > 1 + case 1: + OUT_WRITE(SOL1_PIN, HIGH); + break; + #endif + #if HAS_SOLENOID_2 && EXTRUDERS > 2 + case 2: + OUT_WRITE(SOL2_PIN, HIGH); + break; + #endif + #if HAS_SOLENOID_3 && EXTRUDERS > 3 + case 3: + OUT_WRITE(SOL3_PIN, HIGH); + break; + #endif + #if HAS_SOLENOID_4 && EXTRUDERS > 4 + case 4: + OUT_WRITE(SOL4_PIN, HIGH); + break; + #endif + default: + SERIAL_ECHO_START(); + SERIAL_ECHOLNPGM(MSG_INVALID_SOLENOID); + break; + } + } + + void enable_solenoid_on_active_extruder() { enable_solenoid(active_extruder); } + + void disable_all_solenoids() { + OUT_WRITE(SOL0_PIN, LOW); + #if HAS_SOLENOID_1 && EXTRUDERS > 1 + OUT_WRITE(SOL1_PIN, LOW); + #endif + #if HAS_SOLENOID_2 && EXTRUDERS > 2 + OUT_WRITE(SOL2_PIN, LOW); + #endif + #if HAS_SOLENOID_3 && EXTRUDERS > 3 + OUT_WRITE(SOL3_PIN, LOW); + #endif + #if HAS_SOLENOID_4 && EXTRUDERS > 4 + OUT_WRITE(SOL4_PIN, LOW); + #endif + } + + /** + * M380: Enable solenoid on the active extruder + */ + inline void gcode_M380() { enable_solenoid_on_active_extruder(); } + + /** + * M381: Disable all solenoids + */ + inline void gcode_M381() { disable_all_solenoids(); } + +#endif // EXT_SOLENOID + +/** + * M400: Finish all moves + */ +inline void gcode_M400() { stepper.synchronize(); } + +#if HAS_BED_PROBE + + /** + * M401: Deploy and activate the Z probe + */ + inline void gcode_M401() { + DEPLOY_PROBE(); + report_current_position(); + } + + /** + * M402: Deactivate and stow the Z probe + */ + inline void gcode_M402() { + STOW_PROBE(); + #if Z_AFTER_PROBING + move_z_after_probing(); + #endif + report_current_position(); + } + +#endif // HAS_BED_PROBE + +#if ENABLED(FILAMENT_WIDTH_SENSOR) + + /** + * M404: Display or set (in current units) the nominal filament width (3mm, 1.75mm ) W<3.0> + */ + inline void gcode_M404() { + if (parser.seen('W')) { + filament_width_nominal = parser.value_linear_units(); + planner.volumetric_area_nominal = CIRCLE_AREA(filament_width_nominal * 0.5); + } + else { + SERIAL_PROTOCOLPGM("Filament dia (nominal mm):"); + SERIAL_PROTOCOLLN(filament_width_nominal); + } + } + + /** + * M405: Turn on filament sensor for control + */ + inline void gcode_M405() { + // This is technically a linear measurement, but since it's quantized to centimeters and is a different + // unit than everything else, it uses parser.value_byte() instead of parser.value_linear_units(). + if (parser.seen('D')) { + meas_delay_cm = parser.value_byte(); + NOMORE(meas_delay_cm, MAX_MEASUREMENT_DELAY); + } + + if (filwidth_delay_index[1] == -1) { // Initialize the ring buffer if not done since startup + const int8_t temp_ratio = thermalManager.widthFil_to_size_ratio(); + + for (uint8_t i = 0; i < COUNT(measurement_delay); ++i) + measurement_delay[i] = temp_ratio; + + filwidth_delay_index[0] = filwidth_delay_index[1] = 0; + } + + filament_sensor = true; + } + + /** + * M406: Turn off filament sensor for control + */ + inline void gcode_M406() { + filament_sensor = false; + planner.calculate_volumetric_multipliers(); // Restore correct 'volumetric_multiplier' value + } + + /** + * M407: Get measured filament diameter on serial output + */ + inline void gcode_M407() { + SERIAL_PROTOCOLPGM("Filament dia (measured mm):"); + SERIAL_PROTOCOLLN(filament_width_meas); + } + +#endif // FILAMENT_WIDTH_SENSOR + +void quickstop_stepper() { + stepper.quick_stop(); + stepper.synchronize(); + set_current_from_steppers_for_axis(ALL_AXES); + SYNC_PLAN_POSITION_KINEMATIC(); +} + +#if HAS_LEVELING + /** + * M420: Enable/Disable Bed Leveling and/or set the Z fade height. + * + * S[bool] Turns leveling on or off + * Z[height] Sets the Z fade height (0 or none to disable) + * V[bool] Verbose - Print the leveling grid + * + * With AUTO_BED_LEVELING_UBL only: + * + * L[index] Load UBL mesh from index (0 is default) + */ + inline void gcode_M420() { + + const float oldpos[] = { current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] }; + + #if ENABLED(AUTO_BED_LEVELING_UBL) + + // L to load a mesh from the EEPROM + if (parser.seen('L')) { + + #if ENABLED(EEPROM_SETTINGS) + const int8_t storage_slot = parser.has_value() ? parser.value_int() : ubl.storage_slot; + const int16_t a = settings.calc_num_meshes(); + + if (!a) { + SERIAL_PROTOCOLLNPGM("?EEPROM storage not available."); + return; + } + + if (!WITHIN(storage_slot, 0, a - 1)) { + SERIAL_PROTOCOLLNPGM("?Invalid storage slot."); + SERIAL_PROTOCOLLNPAIR("?Use 0 to ", a - 1); + return; + } + + settings.load_mesh(storage_slot); + ubl.storage_slot = storage_slot; + + #else + + SERIAL_PROTOCOLLNPGM("?EEPROM storage not available."); + return; + + #endif + } + + // L to load a mesh from the EEPROM + if (parser.seen('L') || parser.seen('V')) { + ubl.display_map(0); // Currently only supports one map type + SERIAL_ECHOLNPAIR("ubl.mesh_is_valid = ", ubl.mesh_is_valid()); + SERIAL_ECHOLNPAIR("ubl.storage_slot = ", ubl.storage_slot); + } + + #endif // AUTO_BED_LEVELING_UBL + + // V to print the matrix or mesh + if (parser.seen('V')) { + #if ABL_PLANAR + planner.bed_level_matrix.debug(PSTR("Bed Level Correction Matrix:")); + #else + if (leveling_is_valid()) { + #if ENABLED(AUTO_BED_LEVELING_BILINEAR) + print_bilinear_leveling_grid(); + #if ENABLED(ABL_BILINEAR_SUBDIVISION) + print_bilinear_leveling_grid_virt(); + #endif + #elif ENABLED(MESH_BED_LEVELING) + SERIAL_ECHOLNPGM("Mesh Bed Level data:"); + mbl.report_mesh(); + #endif + } + #endif + } + + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + if (parser.seen('Z')) set_z_fade_height(parser.value_linear_units(), false); + #endif + + bool to_enable = false; + if (parser.seen('S')) { + to_enable = parser.value_bool(); + set_bed_leveling_enabled(to_enable); + } + + const bool new_status = planner.leveling_active; + + if (to_enable && !new_status) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_M420_FAILED); + } + + SERIAL_ECHO_START(); + SERIAL_ECHOLNPAIR("Bed Leveling ", new_status ? MSG_ON : MSG_OFF); + + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + SERIAL_ECHO_START(); + SERIAL_ECHOPGM("Fade Height "); + if (planner.z_fade_height > 0.0) + SERIAL_ECHOLN(planner.z_fade_height); + else + SERIAL_ECHOLNPGM(MSG_OFF); + #endif + + // Report change in position + if (memcmp(oldpos, current_position, sizeof(oldpos))) + report_current_position(); + } +#endif + +#if ENABLED(MESH_BED_LEVELING) + + /** + * M421: Set a single Mesh Bed Leveling Z coordinate + * + * Usage: + * M421 X Y Z + * M421 X Y Q + * M421 I J Z + * M421 I J Q + */ + inline void gcode_M421() { + const bool hasX = parser.seen('X'), hasI = parser.seen('I'); + const int8_t ix = hasI ? parser.value_int() : hasX ? mbl.probe_index_x(parser.value_linear_units()) : -1; + const bool hasY = parser.seen('Y'), hasJ = parser.seen('J'); + const int8_t iy = hasJ ? parser.value_int() : hasY ? mbl.probe_index_y(parser.value_linear_units()) : -1; + const bool hasZ = parser.seen('Z'), hasQ = !hasZ && parser.seen('Q'); + + if (int(hasI && hasJ) + int(hasX && hasY) != 1 || !(hasZ || hasQ)) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_M421_PARAMETERS); + } + else if (ix < 0 || iy < 0) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_MESH_XY); + } + else + mbl.set_z(ix, iy, parser.value_linear_units() + (hasQ ? mbl.z_values[ix][iy] : 0)); + } + +#elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + + /** + * M421: Set a single Mesh Bed Leveling Z coordinate + * + * Usage: + * M421 I J Z + * M421 I J Q + */ + inline void gcode_M421() { + int8_t ix = parser.intval('I', -1), iy = parser.intval('J', -1); + const bool hasI = ix >= 0, + hasJ = iy >= 0, + hasZ = parser.seen('Z'), + hasQ = !hasZ && parser.seen('Q'); + + if (!hasI || !hasJ || !(hasZ || hasQ)) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_M421_PARAMETERS); + } + else if (!WITHIN(ix, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1)) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_MESH_XY); + } + else { + z_values[ix][iy] = parser.value_linear_units() + (hasQ ? z_values[ix][iy] : 0); + #if ENABLED(ABL_BILINEAR_SUBDIVISION) + bed_level_virt_interpolate(); + #endif + } + } + +#elif ENABLED(AUTO_BED_LEVELING_UBL) + + /** + * M421: Set a single Mesh Bed Leveling Z coordinate + * + * Usage: + * M421 I J Z + * M421 I J Q + * M421 C Z + * M421 C Q + */ + inline void gcode_M421() { + int8_t ix = parser.intval('I', -1), iy = parser.intval('J', -1); + const bool hasI = ix >= 0, + hasJ = iy >= 0, + hasC = parser.seen('C'), + hasZ = parser.seen('Z'), + hasQ = !hasZ && parser.seen('Q'); + + if (hasC) { + const mesh_index_pair location = ubl.find_closest_mesh_point_of_type(REAL, current_position[X_AXIS], current_position[Y_AXIS], USE_NOZZLE_AS_REFERENCE, NULL); + ix = location.x_index; + iy = location.y_index; + } + + if (int(hasC) + int(hasI && hasJ) != 1 || !(hasZ || hasQ)) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_M421_PARAMETERS); + } + else if (!WITHIN(ix, 0, GRID_MAX_POINTS_X - 1) || !WITHIN(iy, 0, GRID_MAX_POINTS_Y - 1)) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_MESH_XY); + } + else + ubl.z_values[ix][iy] = parser.value_linear_units() + (hasQ ? ubl.z_values[ix][iy] : 0); + } + +#endif // AUTO_BED_LEVELING_UBL + +#if HAS_M206_COMMAND + + /** + * M428: Set home_offset based on the distance between the + * current_position and the nearest "reference point." + * If an axis is past center its endstop position + * is the reference-point. Otherwise it uses 0. This allows + * the Z offset to be set near the bed when using a max endstop. + * + * M428 can't be used more than 2cm away from 0 or an endstop. + * + * Use M206 to set these values directly. + */ + inline void gcode_M428() { + if (axis_unhomed_error()) return; + + float diff[XYZ]; + LOOP_XYZ(i) { + diff[i] = base_home_pos((AxisEnum)i) - current_position[i]; + if (!WITHIN(diff[i], -20, 20) && home_dir((AxisEnum)i) > 0) + diff[i] = -current_position[i]; + if (!WITHIN(diff[i], -20, 20)) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_M428_TOO_FAR); + LCD_ALERTMESSAGEPGM("Err: Too far!"); + BUZZ(200, 40); + return; + } + } + + LOOP_XYZ(i) set_home_offset((AxisEnum)i, diff[i]); + report_current_position(); + LCD_MESSAGEPGM(MSG_HOME_OFFSETS_APPLIED); + BUZZ(100, 659); + BUZZ(100, 698); + } + +#endif // HAS_M206_COMMAND + +/** + * M500: Store settings in EEPROM + */ +inline void gcode_M500() { + (void)settings.save(); +} + +/** + * M501: Read settings from EEPROM + */ +inline void gcode_M501() { + (void)settings.load(); +} + +/** + * M502: Revert to default settings + */ +inline void gcode_M502() { + (void)settings.reset(); +} + +#if DISABLED(DISABLE_M503) + /** + * M503: print settings currently in memory + */ + inline void gcode_M503() { + (void)settings.report(parser.seen('S') && !parser.value_bool()); + } +#endif + +#if ENABLED(EEPROM_SETTINGS) + /** + * M504: Validate EEPROM Contents + */ + inline void gcode_M504() { + if (settings.validate()) { + SERIAL_ECHO_START(); + SERIAL_ECHOLNPGM("EEPROM OK"); + } + } +#endif + +#if ENABLED(ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED) + + /** + * M540: Set whether SD card print should abort on endstop hit (M540 S<0|1>) + */ + inline void gcode_M540() { + if (parser.seen('S')) stepper.abort_on_endstop_hit = parser.value_bool(); + } + +#endif // ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED + +#if HAS_BED_PROBE + + inline void gcode_M851() { + if (parser.seenval('Z')) { + const float value = parser.value_linear_units(); + if (WITHIN(value, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX)) + zprobe_zoffset = value; + else { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM("?Z out of range (" STRINGIFY(Z_PROBE_OFFSET_RANGE_MIN) " to " STRINGIFY(Z_PROBE_OFFSET_RANGE_MAX) ")"); + } + return; + } + SERIAL_ECHO_START(); + SERIAL_ECHOPGM(MSG_PROBE_Z_OFFSET); + SERIAL_ECHOLNPAIR(": ", zprobe_zoffset); + } + +#endif // HAS_BED_PROBE + +#if ENABLED(SKEW_CORRECTION_GCODE) + + /** + * M852: Get or set the machine skew factors. Reports current values with no arguments. + * + * S[xy_factor] - Alias for 'I' + * I[xy_factor] - New XY skew factor + * J[xz_factor] - New XZ skew factor + * K[yz_factor] - New YZ skew factor + */ + inline void gcode_M852() { + uint8_t ijk = 0, badval = 0, setval = 0; + + if (parser.seen('I') || parser.seen('S')) { + ++ijk; + const float value = parser.value_linear_units(); + if (WITHIN(value, SKEW_FACTOR_MIN, SKEW_FACTOR_MAX)) { + if (planner.xy_skew_factor != value) { + planner.xy_skew_factor = value; + ++setval; + } + } + else + ++badval; + } + + #if ENABLED(SKEW_CORRECTION_FOR_Z) + + if (parser.seen('J')) { + ++ijk; + const float value = parser.value_linear_units(); + if (WITHIN(value, SKEW_FACTOR_MIN, SKEW_FACTOR_MAX)) { + if (planner.xz_skew_factor != value) { + planner.xz_skew_factor = value; + ++setval; + } + } + else + ++badval; + } + + if (parser.seen('K')) { + ++ijk; + const float value = parser.value_linear_units(); + if (WITHIN(value, SKEW_FACTOR_MIN, SKEW_FACTOR_MAX)) { + if (planner.yz_skew_factor != value) { + planner.yz_skew_factor = value; + ++setval; + } + } + else + ++badval; + } + + #endif + + if (badval) + SERIAL_ECHOLNPGM(MSG_SKEW_MIN " " STRINGIFY(SKEW_FACTOR_MIN) " " MSG_SKEW_MAX " " STRINGIFY(SKEW_FACTOR_MAX)); + + // When skew is changed the current position changes + if (setval) { + set_current_from_steppers_for_axis(ALL_AXES); + SYNC_PLAN_POSITION_KINEMATIC(); + report_current_position(); + } + + if (!ijk) { + SERIAL_ECHO_START(); + SERIAL_ECHOPGM(MSG_SKEW_FACTOR " XY: "); + SERIAL_ECHO_F(planner.xy_skew_factor, 6); + SERIAL_EOL(); + #if ENABLED(SKEW_CORRECTION_FOR_Z) + SERIAL_ECHOPAIR(" XZ: ", planner.xz_skew_factor); + SERIAL_ECHOLNPAIR(" YZ: ", planner.yz_skew_factor); + #else + SERIAL_EOL(); + #endif + } + } + +#endif // SKEW_CORRECTION_GCODE + +#if ENABLED(ADVANCED_PAUSE_FEATURE) + + /** + * M600: Pause for filament change + * + * E[distance] - Retract the filament this far + * Z[distance] - Move the Z axis by this distance + * X[position] - Move to this X position, with Y + * Y[position] - Move to this Y position, with X + * U[distance] - Retract distance for removal (manual reload) + * L[distance] - Extrude distance for insertion (manual reload) + * B[count] - Number of times to beep, -1 for indefinite (if equipped with a buzzer) + * T[toolhead] - Select extruder for filament change + * + * Default values are used for omitted arguments. + */ + inline void gcode_M600() { + point_t park_point = NOZZLE_PARK_POINT; + + if (get_target_extruder_from_command(600)) return; + + // Show initial message + #if ENABLED(ULTIPANEL) + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_INIT, ADVANCED_PAUSE_MODE_PAUSE_PRINT, target_extruder); + #endif + + #if ENABLED(HOME_BEFORE_FILAMENT_CHANGE) + // Don't allow filament change without homing first + if (axis_unhomed_error()) home_all_axes(); + #endif + + #if EXTRUDERS > 1 + // Change toolhead if specified + uint8_t active_extruder_before_filament_change = active_extruder; + if (active_extruder != target_extruder) + tool_change(target_extruder, 0, true); + #endif + + // Initial retract before move to filament change position + const float retract = -FABS(parser.seen('E') ? parser.value_axis_units(E_AXIS) : 0 + #ifdef PAUSE_PARK_RETRACT_LENGTH + + (PAUSE_PARK_RETRACT_LENGTH) + #endif + ); + + // Lift Z axis + if (parser.seenval('Z')) park_point.z = parser.linearval('Z'); + + // Move XY axes to filament change position or given position + if (parser.seenval('X')) park_point.x = parser.linearval('X'); + if (parser.seenval('Y')) park_point.y = parser.linearval('Y'); + + #if HOTENDS > 1 && DISABLED(DUAL_X_CARRIAGE) && DISABLED(DELTA) + park_point.x += (active_extruder ? hotend_offset[X_AXIS][active_extruder] : 0); + park_point.y += (active_extruder ? hotend_offset[Y_AXIS][active_extruder] : 0); + #endif + + // Unload filament + const float unload_length = -FABS(parser.seen('U') ? parser.value_axis_units(E_AXIS) : + filament_change_unload_length[active_extruder]); + + // Load filament + const float load_length = FABS(parser.seen('L') ? parser.value_axis_units(E_AXIS) : + filament_change_load_length[active_extruder]); + + const int beep_count = parser.intval('B', + #ifdef FILAMENT_CHANGE_ALERT_BEEPS + FILAMENT_CHANGE_ALERT_BEEPS + #else + -1 + #endif + ); + + const bool job_running = print_job_timer.isRunning(); + + if (pause_print(retract, park_point, unload_length, true)) { + wait_for_filament_reload(beep_count); + resume_print(load_length, ADVANCED_PAUSE_EXTRUDE_LENGTH, beep_count); + } + + #if EXTRUDERS > 1 + // Restore toolhead if it was changed + if (active_extruder_before_filament_change != active_extruder) + tool_change(active_extruder_before_filament_change, 0, true); + #endif + + // Resume the print job timer if it was running + if (job_running) print_job_timer.start(); + } + + /** + * M603: Configure filament change + * + * T[toolhead] - Select extruder to configure, active extruder if not specified + * U[distance] - Retract distance for removal, for the specified extruder + * L[distance] - Extrude distance for insertion, for the specified extruder + * + */ + inline void gcode_M603() { + + if (get_target_extruder_from_command(603)) return; + + // Unload length + if (parser.seen('U')) { + filament_change_unload_length[target_extruder] = FABS(parser.value_axis_units(E_AXIS)); + #if ENABLED(PREVENT_LENGTHY_EXTRUDE) + NOMORE(filament_change_unload_length[target_extruder], EXTRUDE_MAXLENGTH); + #endif + } + + // Load length + if (parser.seen('L')) { + filament_change_load_length[target_extruder] = FABS(parser.value_axis_units(E_AXIS)); + #if ENABLED(PREVENT_LENGTHY_EXTRUDE) + NOMORE(filament_change_load_length[target_extruder], EXTRUDE_MAXLENGTH); + #endif + } + } + +#endif // ADVANCED_PAUSE_FEATURE + +#if ENABLED(MK2_MULTIPLEXER) + + inline void select_multiplexed_stepper(const uint8_t e) { + stepper.synchronize(); + disable_e_steppers(); + WRITE(E_MUX0_PIN, TEST(e, 0) ? HIGH : LOW); + WRITE(E_MUX1_PIN, TEST(e, 1) ? HIGH : LOW); + WRITE(E_MUX2_PIN, TEST(e, 2) ? HIGH : LOW); + safe_delay(100); + } + +#endif // MK2_MULTIPLEXER + +#if ENABLED(DUAL_X_CARRIAGE) + + /** + * M605: Set dual x-carriage movement mode + * + * M605 S0: Full control mode. The slicer has full control over x-carriage movement + * M605 S1: Auto-park mode. The inactive head will auto park/unpark without slicer involvement + * M605 S2 [Xnnn] [Rmmm]: Duplication mode. The second extruder will duplicate the first with nnn + * units x-offset and an optional differential hotend temperature of + * mmm degrees. E.g., with "M605 S2 X100 R2" the second extruder will duplicate + * the first with a spacing of 100mm in the x direction and 2 degrees hotter. + * + * Note: the X axis should be homed after changing dual x-carriage mode. + */ + inline void gcode_M605() { + stepper.synchronize(); + if (parser.seen('S')) dual_x_carriage_mode = (DualXMode)parser.value_byte(); + switch (dual_x_carriage_mode) { + case DXC_FULL_CONTROL_MODE: + case DXC_AUTO_PARK_MODE: + break; + case DXC_DUPLICATION_MODE: + if (parser.seen('X')) duplicate_extruder_x_offset = max(parser.value_linear_units(), X2_MIN_POS - x_home_pos(0)); + if (parser.seen('R')) duplicate_extruder_temp_offset = parser.value_celsius_diff(); + SERIAL_ECHO_START(); + SERIAL_ECHOPGM(MSG_HOTEND_OFFSET); + SERIAL_CHAR(' '); + SERIAL_ECHO(hotend_offset[X_AXIS][0]); + SERIAL_CHAR(','); + SERIAL_ECHO(hotend_offset[Y_AXIS][0]); + SERIAL_CHAR(' '); + SERIAL_ECHO(duplicate_extruder_x_offset); + SERIAL_CHAR(','); + SERIAL_ECHOLN(hotend_offset[Y_AXIS][1]); + break; + default: + dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE; + break; + } + active_extruder_parked = false; + extruder_duplication_enabled = false; + delayed_move_time = 0; + } + +#elif ENABLED(DUAL_NOZZLE_DUPLICATION_MODE) + + inline void gcode_M605() { + stepper.synchronize(); + extruder_duplication_enabled = parser.intval('S') == (int)DXC_DUPLICATION_MODE; + SERIAL_ECHO_START(); + SERIAL_ECHOLNPAIR(MSG_DUPLICATION_MODE, extruder_duplication_enabled ? MSG_ON : MSG_OFF); + } + +#endif // DUAL_NOZZLE_DUPLICATION_MODE + +#if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES) + + /** + * M701: Load filament + * + * T[extruder] - Optional extruder number. Current extruder if omitted. + * Z[distance] - Move the Z axis by this distance + * L[distance] - Extrude distance for insertion (positive value) (manual reload) + * + * Default values are used for omitted arguments. + */ + inline void gcode_M701() { + point_t park_point = NOZZLE_PARK_POINT; + + #if ENABLED(NO_MOTION_BEFORE_HOMING) + // Only raise Z if the machine is homed + if (axis_unhomed_error()) park_point.z = 0; + #endif + + if (get_target_extruder_from_command(701)) return; + + // Z axis lift + if (parser.seenval('Z')) park_point.z = parser.linearval('Z'); + + // Load filament + const float load_length = FABS(parser.seen('L') ? parser.value_axis_units(E_AXIS) : + filament_change_load_length[target_extruder]); + + // Show initial "wait for load" message + #if ENABLED(ULTIPANEL) + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_LOAD, ADVANCED_PAUSE_MODE_LOAD_FILAMENT, target_extruder); + #endif + + #if EXTRUDERS > 1 + // Change toolhead if specified + uint8_t active_extruder_before_filament_change = active_extruder; + if (active_extruder != target_extruder) + tool_change(target_extruder, 0, true); + #endif + + // Lift Z axis + if (park_point.z > 0) + do_blocking_move_to_z(min(current_position[Z_AXIS] + park_point.z, Z_MAX_POS), NOZZLE_PARK_Z_FEEDRATE); + + load_filament(load_length, ADVANCED_PAUSE_EXTRUDE_LENGTH, FILAMENT_CHANGE_ALERT_BEEPS, true, + thermalManager.wait_for_heating(target_extruder), ADVANCED_PAUSE_MODE_LOAD_FILAMENT); + + // Restore Z axis + if (park_point.z > 0) + do_blocking_move_to_z(max(current_position[Z_AXIS] - park_point.z, Z_MIN_POS), NOZZLE_PARK_Z_FEEDRATE); + + #if EXTRUDERS > 1 + // Restore toolhead if it was changed + if (active_extruder_before_filament_change != active_extruder) + tool_change(active_extruder_before_filament_change, 0, true); + #endif + + // Show status screen + #if ENABLED(ULTIPANEL) + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_STATUS); + #endif + } + + /** + * M702: Unload filament + * + * T[extruder] - Optional extruder number. If omitted, current extruder + * (or ALL extruders with FILAMENT_UNLOAD_ALL_EXTRUDERS). + * Z[distance] - Move the Z axis by this distance + * U[distance] - Retract distance for removal (manual reload) + * + * Default values are used for omitted arguments. + */ + inline void gcode_M702() { + point_t park_point = NOZZLE_PARK_POINT; + + #if ENABLED(NO_MOTION_BEFORE_HOMING) + // Only raise Z if the machine is homed + if (axis_unhomed_error()) park_point.z = 0; + #endif + + if (get_target_extruder_from_command(702)) return; + + // Z axis lift + if (parser.seenval('Z')) park_point.z = parser.linearval('Z'); + + // Show initial message + #if ENABLED(ULTIPANEL) + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_UNLOAD, ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT, target_extruder); + #endif + + #if EXTRUDERS > 1 + // Change toolhead if specified + uint8_t active_extruder_before_filament_change = active_extruder; + if (active_extruder != target_extruder) + tool_change(target_extruder, 0, true); + #endif + + // Lift Z axis + if (park_point.z > 0) + do_blocking_move_to_z(min(current_position[Z_AXIS] + park_point.z, Z_MAX_POS), NOZZLE_PARK_Z_FEEDRATE); + + // Unload filament + #if EXTRUDERS > 1 && ENABLED(FILAMENT_UNLOAD_ALL_EXTRUDERS) + if (!parser.seenval('T')) { + HOTEND_LOOP() { + if (e != active_extruder) tool_change(e, 0, true); + unload_filament(-filament_change_unload_length[e], true, ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT); + } + } + else + #endif + { + // Unload length + const float unload_length = -FABS(parser.seen('U') ? parser.value_axis_units(E_AXIS) : + filament_change_unload_length[target_extruder]); + + unload_filament(unload_length, true, ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT); + } + + // Restore Z axis + if (park_point.z > 0) + do_blocking_move_to_z(max(current_position[Z_AXIS] - park_point.z, Z_MIN_POS), NOZZLE_PARK_Z_FEEDRATE); + + #if EXTRUDERS > 1 + // Restore toolhead if it was changed + if (active_extruder_before_filament_change != active_extruder) + tool_change(active_extruder_before_filament_change, 0, true); + #endif + + // Show status screen + #if ENABLED(ULTIPANEL) + lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_STATUS); + #endif + } + +#endif // FILAMENT_LOAD_UNLOAD_GCODES + +#if ENABLED(LIN_ADVANCE) + /** + * M900: Get or Set Linear Advance K-factor + * + * K Set advance K factor + */ + inline void gcode_M900() { + if (parser.seenval('K')) { + const float newK = parser.floatval('K'); + if (WITHIN(newK, 0, 10)) { + stepper.synchronize(); + planner.extruder_advance_K = newK; + } + else + SERIAL_PROTOCOLLNPGM("?K value out of range (0-10)."); + } + else { + SERIAL_ECHO_START(); + SERIAL_ECHOLNPAIR("Advance K=", planner.extruder_advance_K); + } + } +#endif // LIN_ADVANCE + +#if HAS_TRINAMIC + #if ENABLED(TMC_DEBUG) + inline void gcode_M122() { + if (parser.seen('S')) + tmc_set_report_status(parser.value_bool()); + else + tmc_report_all(); + } + #endif // TMC_DEBUG + + /** + * M906: Set motor current in milliamps using axis codes X, Y, Z, E + * Report driver currents when no axis specified + */ + inline void gcode_M906() { + #define TMC_SAY_CURRENT(Q) tmc_get_current(stepper##Q, TMC_##Q) + #define TMC_SET_CURRENT(Q) tmc_set_current(stepper##Q, TMC_##Q, value) + + bool report = true; + const uint8_t index = parser.byteval('I'); + LOOP_XYZE(i) if (uint16_t value = parser.intval(axis_codes[i])) { + report = false; + switch (i) { + case X_AXIS: + #if X_IS_TRINAMIC + if (index == 0) TMC_SET_CURRENT(X); + #endif + #if X2_IS_TRINAMIC + if (index == 1) TMC_SET_CURRENT(X2); + #endif + break; + case Y_AXIS: + #if Y_IS_TRINAMIC + if (index == 0) TMC_SET_CURRENT(Y); + #endif + #if Y2_IS_TRINAMIC + if (index == 1) TMC_SET_CURRENT(Y2); + #endif + break; + case Z_AXIS: + #if Z_IS_TRINAMIC + if (index == 0) TMC_SET_CURRENT(Z); + #endif + #if Z2_IS_TRINAMIC + if (index == 1) TMC_SET_CURRENT(Z2); + #endif + break; + case E_AXIS: { + if (get_target_extruder_from_command(906)) return; + switch (target_extruder) { + #if E0_IS_TRINAMIC + case 0: TMC_SET_CURRENT(E0); break; + #endif + #if E1_IS_TRINAMIC + case 1: TMC_SET_CURRENT(E1); break; + #endif + #if E2_IS_TRINAMIC + case 2: TMC_SET_CURRENT(E2); break; + #endif + #if E3_IS_TRINAMIC + case 3: TMC_SET_CURRENT(E3); break; + #endif + #if E4_IS_TRINAMIC + case 4: TMC_SET_CURRENT(E4); break; + #endif + } + } break; + } + } + + if (report) LOOP_XYZE(i) switch (i) { + case X_AXIS: + #if X_IS_TRINAMIC + TMC_SAY_CURRENT(X); + #endif + #if X2_IS_TRINAMIC + TMC_SAY_CURRENT(X2); + #endif + break; + case Y_AXIS: + #if Y_IS_TRINAMIC + TMC_SAY_CURRENT(Y); + #endif + #if Y2_IS_TRINAMIC + TMC_SAY_CURRENT(Y2); + #endif + break; + case Z_AXIS: + #if Z_IS_TRINAMIC + TMC_SAY_CURRENT(Z); + #endif + #if Z2_IS_TRINAMIC + TMC_SAY_CURRENT(Z2); + #endif + break; + case E_AXIS: + #if E0_IS_TRINAMIC + TMC_SAY_CURRENT(E0); + #endif + #if E1_IS_TRINAMIC + TMC_SAY_CURRENT(E1); + #endif + #if E2_IS_TRINAMIC + TMC_SAY_CURRENT(E2); + #endif + #if E3_IS_TRINAMIC + TMC_SAY_CURRENT(E3); + #endif + #if E4_IS_TRINAMIC + TMC_SAY_CURRENT(E4); + #endif + break; + } + } + + /** + * M911: Report TMC stepper driver overtemperature pre-warn flag + * The flag is held by the library and persist until manually cleared by M912 + */ + inline void gcode_M911() { + #if ENABLED(X_IS_TMC2130) || (ENABLED(X_IS_TMC2208) && PIN_EXISTS(X_SERIAL_RX)) || ENABLED(IS_TRAMS) + tmc_report_otpw(stepperX, TMC_X); + #endif + #if ENABLED(Y_IS_TMC2130) || (ENABLED(Y_IS_TMC2208) && PIN_EXISTS(Y_SERIAL_RX)) || ENABLED(IS_TRAMS) + tmc_report_otpw(stepperY, TMC_Y); + #endif + #if ENABLED(Z_IS_TMC2130) || (ENABLED(Z_IS_TMC2208) && PIN_EXISTS(Z_SERIAL_RX)) || ENABLED(IS_TRAMS) + tmc_report_otpw(stepperZ, TMC_Z); + #endif + #if ENABLED(E0_IS_TMC2130) || (ENABLED(E0_IS_TMC2208) && PIN_EXISTS(E0_SERIAL_RX)) || ENABLED(IS_TRAMS) + tmc_report_otpw(stepperE0, TMC_E0); + #endif + } + + /** + * M912: Clear TMC stepper driver overtemperature pre-warn flag held by the library + */ + inline void gcode_M912() { + const bool clearX = parser.seen(axis_codes[X_AXIS]), clearY = parser.seen(axis_codes[Y_AXIS]), clearZ = parser.seen(axis_codes[Z_AXIS]), clearE = parser.seen(axis_codes[E_AXIS]), + clearAll = (!clearX && !clearY && !clearZ && !clearE) || (clearX && clearY && clearZ && clearE); + #if ENABLED(X_IS_TMC2130) || ENABLED(IS_TRAMS) || (ENABLED(X_IS_TMC2208) && PIN_EXISTS(X_SERIAL_RX)) + if (clearX || clearAll) tmc_clear_otpw(stepperX, TMC_X); + #endif + #if ENABLED(X2_IS_TMC2130) || (ENABLED(X2_IS_TMC2208) && PIN_EXISTS(X_SERIAL_RX)) + if (clearX || clearAll) tmc_clear_otpw(stepperX, TMC_X); + #endif + + #if ENABLED(Y_IS_TMC2130) || (ENABLED(Y_IS_TMC2208) && PIN_EXISTS(Y_SERIAL_RX)) + if (clearY || clearAll) tmc_clear_otpw(stepperY, TMC_Y); + #endif + + #if ENABLED(Z_IS_TMC2130) || (ENABLED(Z_IS_TMC2208) && PIN_EXISTS(Z_SERIAL_RX)) + if (clearZ || clearAll) tmc_clear_otpw(stepperZ, TMC_Z); + #endif + + #if ENABLED(E0_IS_TMC2130) || (ENABLED(E0_IS_TMC2208) && PIN_EXISTS(E0_SERIAL_RX)) + if (clearE || clearAll) tmc_clear_otpw(stepperE0, TMC_E0); + #endif + } + + /** + * M913: Set HYBRID_THRESHOLD speed. + */ + #if ENABLED(HYBRID_THRESHOLD) + inline void gcode_M913() { + #define TMC_SAY_PWMTHRS(P,Q) tmc_get_pwmthrs(stepper##Q, TMC_##Q, planner.axis_steps_per_mm[P##_AXIS]) + #define TMC_SET_PWMTHRS(P,Q) tmc_set_pwmthrs(stepper##Q, TMC_##Q, value, planner.axis_steps_per_mm[P##_AXIS]) + #define TMC_SAY_PWMTHRS_E(E) do{ const uint8_t extruder = E; tmc_get_pwmthrs(stepperE##E, TMC_E##E, planner.axis_steps_per_mm[E_AXIS_N]); }while(0) + #define TMC_SET_PWMTHRS_E(E) do{ const uint8_t extruder = E; tmc_set_pwmthrs(stepperE##E, TMC_E##E, value, planner.axis_steps_per_mm[E_AXIS_N]); }while(0) + + bool report = true; + const uint8_t index = parser.byteval('I'); + LOOP_XYZE(i) if (int32_t value = parser.longval(axis_codes[i])) { + report = false; + switch (i) { + case X_AXIS: + #if X_IS_TRINAMIC + if (index == 0) TMC_SET_PWMTHRS(X,X); + #endif + #if X2_IS_TRINAMIC + if (index == 1) TMC_SET_PWMTHRS(X,X2); + #endif + break; + case Y_AXIS: + #if Y_IS_TRINAMIC + if (index == 0) TMC_SET_PWMTHRS(Y,Y); + #endif + #if Y2_IS_TRINAMIC + if (index == 1) TMC_SET_PWMTHRS(Y,Y2); + #endif + break; + case Z_AXIS: + #if Z_IS_TRINAMIC + if (index == 0) TMC_SET_PWMTHRS(Z,Z); + #endif + #if Z2_IS_TRINAMIC + if (index == 1) TMC_SET_PWMTHRS(Z,Z2); + #endif + break; + case E_AXIS: { + if (get_target_extruder_from_command(913)) return; + switch (target_extruder) { + #if E0_IS_TRINAMIC + case 0: TMC_SET_PWMTHRS_E(0); break; + #endif + #if E_STEPPERS > 1 && E1_IS_TRINAMIC + case 1: TMC_SET_PWMTHRS_E(1); break; + #endif + #if E_STEPPERS > 2 && E2_IS_TRINAMIC + case 2: TMC_SET_PWMTHRS_E(2); break; + #endif + #if E_STEPPERS > 3 && E3_IS_TRINAMIC + case 3: TMC_SET_PWMTHRS_E(3); break; + #endif + #if E_STEPPERS > 4 && E4_IS_TRINAMIC + case 4: TMC_SET_PWMTHRS_E(4); break; + #endif + } + } break; + } + } + + if (report) LOOP_XYZE(i) switch (i) { + case X_AXIS: + #if X_IS_TRINAMIC + TMC_SAY_PWMTHRS(X,X); + #endif + #if X2_IS_TRINAMIC + TMC_SAY_PWMTHRS(X,X2); + #endif + break; + case Y_AXIS: + #if Y_IS_TRINAMIC + TMC_SAY_PWMTHRS(Y,Y); + #endif + #if Y2_IS_TRINAMIC + TMC_SAY_PWMTHRS(Y,Y2); + #endif + break; + case Z_AXIS: + #if Z_IS_TRINAMIC + TMC_SAY_PWMTHRS(Z,Z); + #endif + #if Z2_IS_TRINAMIC + TMC_SAY_PWMTHRS(Z,Z2); + #endif + break; + case E_AXIS: + #if E0_IS_TRINAMIC + TMC_SAY_PWMTHRS_E(0); + #endif + #if E_STEPPERS > 1 && E1_IS_TRINAMIC + TMC_SAY_PWMTHRS_E(1); + #endif + #if E_STEPPERS > 2 && E2_IS_TRINAMIC + TMC_SAY_PWMTHRS_E(2); + #endif + #if E_STEPPERS > 3 && E3_IS_TRINAMIC + TMC_SAY_PWMTHRS_E(3); + #endif + #if E_STEPPERS > 4 && E4_IS_TRINAMIC + TMC_SAY_PWMTHRS_E(4); + #endif + break; + } + } + #endif // HYBRID_THRESHOLD + + /** + * M914: Set SENSORLESS_HOMING sensitivity. + */ + #if ENABLED(SENSORLESS_HOMING) + inline void gcode_M914() { + #define TMC_SAY_SGT(Q) tmc_get_sgt(stepper##Q, TMC_##Q) + #define TMC_SET_SGT(Q) tmc_set_sgt(stepper##Q, TMC_##Q, value) + + bool report = true; + const uint8_t index = parser.byteval('I'); + LOOP_XYZ(i) if (parser.seen(axis_codes[i])) { + const int8_t value = (int8_t)constrain(parser.value_int(), -63, 64); + report = false; + switch (i) { + case X_AXIS: + #if ENABLED(X_IS_TMC2130) || ENABLED(IS_TRAMS) + if (index == 0) TMC_SET_SGT(X); + #endif + #if ENABLED(X2_IS_TMC2130) + if (index == 1) TMC_SET_SGT(X2); + #endif + break; + case Y_AXIS: + #if ENABLED(Y_IS_TMC2130) || ENABLED(IS_TRAMS) + if (index == 0) TMC_SET_SGT(Y); + #endif + #if ENABLED(Y2_IS_TMC2130) + if (index == 1) TMC_SET_SGT(Y2); + #endif + break; + case Z_AXIS: + #if ENABLED(Z_IS_TMC2130) || ENABLED(IS_TRAMS) + if (index == 0) TMC_SET_SGT(Z); + #endif + #if ENABLED(Z2_IS_TMC2130) + if (index == 1) TMC_SET_SGT(Z2); + #endif + break; + } + } + + if (report) LOOP_XYZ(i) switch (i) { + case X_AXIS: + #if ENABLED(X_IS_TMC2130) || ENABLED(IS_TRAMS) + TMC_SAY_SGT(X); + #endif + #if ENABLED(X2_IS_TMC2130) + TMC_SAY_SGT(X2); + #endif + break; + case Y_AXIS: + #if ENABLED(Y_IS_TMC2130) || ENABLED(IS_TRAMS) + TMC_SAY_SGT(Y); + #endif + #if ENABLED(Y2_IS_TMC2130) + TMC_SAY_SGT(Y2); + #endif + break; + case Z_AXIS: + #if ENABLED(Z_IS_TMC2130) || ENABLED(IS_TRAMS) + TMC_SAY_SGT(Z); + #endif + #if ENABLED(Z2_IS_TMC2130) + TMC_SAY_SGT(Z2); + #endif + break; + } + } + #endif // SENSORLESS_HOMING + + /** + * TMC Z axis calibration routine + */ + #if ENABLED(TMC_Z_CALIBRATION) + inline void gcode_M915() { + const uint16_t _rms = parser.seenval('S') ? parser.value_int() : CALIBRATION_CURRENT, + _z = parser.seenval('Z') ? parser.value_linear_units() : CALIBRATION_EXTRA_HEIGHT; + + if (!axis_known_position[Z_AXIS]) { + SERIAL_ECHOLNPGM("\nPlease home Z axis first"); + return; + } + + #if Z_IS_TRINAMIC + const uint16_t Z_current_1 = stepperZ.getCurrent(); + stepperZ.setCurrent(_rms, R_SENSE, HOLD_MULTIPLIER); + #endif + #if Z2_IS_TRINAMIC + const uint16_t Z2_current_1 = stepperZ2.getCurrent(); + stepperZ2.setCurrent(_rms, R_SENSE, HOLD_MULTIPLIER); + #endif + + SERIAL_ECHOPAIR("\nCalibration current: Z", _rms); + + soft_endstops_enabled = false; + + do_blocking_move_to_z(Z_MAX_POS+_z); + + #if Z_IS_TRINAMIC + stepperZ.setCurrent(Z_current_1, R_SENSE, HOLD_MULTIPLIER); + #endif + #if Z2_IS_TRINAMIC + stepperZ2.setCurrent(Z2_current_1, R_SENSE, HOLD_MULTIPLIER); + #endif + + do_blocking_move_to_z(Z_MAX_POS); + soft_endstops_enabled = true; + + SERIAL_ECHOLNPGM("\nHoming Z due to lost steps"); + enqueue_and_echo_commands_P(PSTR("G28 Z")); + } + #endif + +#endif // HAS_TRINAMIC + +/** + * M907: Set digital trimpot motor current using axis codes X, Y, Z, E, B, S + */ +inline void gcode_M907() { + #if HAS_DIGIPOTSS + + LOOP_XYZE(i) if (parser.seen(axis_codes[i])) stepper.digipot_current(i, parser.value_int()); + if (parser.seen('B')) stepper.digipot_current(4, parser.value_int()); + if (parser.seen('S')) for (uint8_t i = 0; i <= 4; i++) stepper.digipot_current(i, parser.value_int()); + + #elif HAS_MOTOR_CURRENT_PWM + + #if PIN_EXISTS(MOTOR_CURRENT_PWM_XY) + if (parser.seen('X')) stepper.digipot_current(0, parser.value_int()); + #endif + #if PIN_EXISTS(MOTOR_CURRENT_PWM_Z) + if (parser.seen('Z')) stepper.digipot_current(1, parser.value_int()); + #endif + #if PIN_EXISTS(MOTOR_CURRENT_PWM_E) + if (parser.seen('E')) stepper.digipot_current(2, parser.value_int()); + #endif + + #endif + + #if ENABLED(DIGIPOT_I2C) + // this one uses actual amps in floating point + LOOP_XYZE(i) if (parser.seen(axis_codes[i])) digipot_i2c_set_current(i, parser.value_float()); + // for each additional extruder (named B,C,D,E..., channels 4,5,6,7...) + for (uint8_t i = NUM_AXIS; i < DIGIPOT_I2C_NUM_CHANNELS; i++) if (parser.seen('B' + i - (NUM_AXIS))) digipot_i2c_set_current(i, parser.value_float()); + #endif + + #if ENABLED(DAC_STEPPER_CURRENT) + if (parser.seen('S')) { + const float dac_percent = parser.value_float(); + for (uint8_t i = 0; i <= 4; i++) dac_current_percent(i, dac_percent); + } + LOOP_XYZE(i) if (parser.seen(axis_codes[i])) dac_current_percent(i, parser.value_float()); + #endif +} + +#if HAS_DIGIPOTSS || ENABLED(DAC_STEPPER_CURRENT) + + /** + * M908: Control digital trimpot directly (M908 P S) + */ + inline void gcode_M908() { + #if HAS_DIGIPOTSS + stepper.digitalPotWrite( + parser.intval('P'), + parser.intval('S') + ); + #endif + #ifdef DAC_STEPPER_CURRENT + dac_current_raw( + parser.byteval('P', -1), + parser.ushortval('S', 0) + ); + #endif + } + + #if ENABLED(DAC_STEPPER_CURRENT) // As with Printrbot RevF + + inline void gcode_M909() { dac_print_values(); } + + inline void gcode_M910() { dac_commit_eeprom(); } + + #endif + +#endif // HAS_DIGIPOTSS || DAC_STEPPER_CURRENT + +#if HAS_MICROSTEPS + + // M350 Set microstepping mode. Warning: Steps per unit remains unchanged. S code sets stepping mode for all drivers. + inline void gcode_M350() { + if (parser.seen('S')) for (int i = 0; i <= 4; i++) stepper.microstep_mode(i, parser.value_byte()); + LOOP_XYZE(i) if (parser.seen(axis_codes[i])) stepper.microstep_mode(i, parser.value_byte()); + if (parser.seen('B')) stepper.microstep_mode(4, parser.value_byte()); + stepper.microstep_readings(); + } + + /** + * M351: Toggle MS1 MS2 pins directly with axis codes X Y Z E B + * S# determines MS1 or MS2, X# sets the pin high/low. + */ + inline void gcode_M351() { + if (parser.seenval('S')) switch (parser.value_byte()) { + case 1: + LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) stepper.microstep_ms(i, parser.value_byte(), -1); + if (parser.seenval('B')) stepper.microstep_ms(4, parser.value_byte(), -1); + break; + case 2: + LOOP_XYZE(i) if (parser.seenval(axis_codes[i])) stepper.microstep_ms(i, -1, parser.value_byte()); + if (parser.seenval('B')) stepper.microstep_ms(4, -1, parser.value_byte()); + break; + } + stepper.microstep_readings(); + } + +#endif // HAS_MICROSTEPS + +#if HAS_CASE_LIGHT + + #ifndef INVERT_CASE_LIGHT + #define INVERT_CASE_LIGHT false + #endif + uint8_t case_light_brightness; // LCD routine wants INT + bool case_light_on; + + #if ENABLED(CASE_LIGHT_USE_NEOPIXEL) + LEDColor case_light_color = + #ifdef CASE_LIGHT_NEOPIXEL_COLOR + CASE_LIGHT_NEOPIXEL_COLOR + #else + { 255, 255, 255, 255 } + #endif + ; + #endif + + void update_case_light() { + const uint8_t i = case_light_on ? case_light_brightness : 0, n10ct = INVERT_CASE_LIGHT ? 255 - i : i; + + #if ENABLED(CASE_LIGHT_USE_NEOPIXEL) + + leds.set_color( + MakeLEDColor(case_light_color.r, case_light_color.g, case_light_color.b, case_light_color.w, n10ct), + false + ); + + #else // !CASE_LIGHT_USE_NEOPIXEL + + SET_OUTPUT(CASE_LIGHT_PIN); + if (USEABLE_HARDWARE_PWM(CASE_LIGHT_PIN)) + analogWrite(CASE_LIGHT_PIN, n10ct); + else { + const bool s = case_light_on ? !INVERT_CASE_LIGHT : INVERT_CASE_LIGHT; + WRITE(CASE_LIGHT_PIN, s ? HIGH : LOW); + } + + #endif // !CASE_LIGHT_USE_NEOPIXEL + } +#endif // HAS_CASE_LIGHT + +/** + * M355: Turn case light on/off and set brightness + * + * P Set case light brightness (PWM pin required - ignored otherwise) + * + * S Set case light on/off + * + * When S turns on the light on a PWM pin then the current brightness level is used/restored + * + * M355 P200 S0 turns off the light & sets the brightness level + * M355 S1 turns on the light with a brightness of 200 (assuming a PWM pin) + */ +inline void gcode_M355() { + #if HAS_CASE_LIGHT + uint8_t args = 0; + if (parser.seenval('P')) ++args, case_light_brightness = parser.value_byte(); + if (parser.seenval('S')) ++args, case_light_on = parser.value_bool(); + if (args) update_case_light(); + + // always report case light status + SERIAL_ECHO_START(); + if (!case_light_on) { + SERIAL_ECHOLNPGM("Case light: off"); + } + else { + if (!USEABLE_HARDWARE_PWM(CASE_LIGHT_PIN)) SERIAL_ECHOLNPGM("Case light: on"); + else SERIAL_ECHOLNPAIR("Case light: ", (int)case_light_brightness); + } + + #else + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_M355_NONE); + #endif // HAS_CASE_LIGHT +} + +#if ENABLED(MIXING_EXTRUDER) + + /** + * M163: Set a single mix factor for a mixing extruder + * This is called "weight" by some systems. + * + * S[index] The channel index to set + * P[float] The mix value + * + */ + inline void gcode_M163() { + const int mix_index = parser.intval('S'); + if (mix_index < MIXING_STEPPERS) { + float mix_value = parser.floatval('P'); + NOLESS(mix_value, 0.0); + mixing_factor[mix_index] = RECIPROCAL(mix_value); + } + } + + #if MIXING_VIRTUAL_TOOLS > 1 + + /** + * M164: Store the current mix factors as a virtual tool. + * + * S[index] The virtual tool to store + * + */ + inline void gcode_M164() { + const int tool_index = parser.intval('S'); + if (tool_index < MIXING_VIRTUAL_TOOLS) { + normalize_mix(); + for (uint8_t i = 0; i < MIXING_STEPPERS; i++) + mixing_virtual_tool_mix[tool_index][i] = mixing_factor[i]; + } + } + + #endif + + #if ENABLED(DIRECT_MIXING_IN_G1) + /** + * M165: Set multiple mix factors for a mixing extruder. + * Factors that are left out will be set to 0. + * All factors together must add up to 1.0. + * + * A[factor] Mix factor for extruder stepper 1 + * B[factor] Mix factor for extruder stepper 2 + * C[factor] Mix factor for extruder stepper 3 + * D[factor] Mix factor for extruder stepper 4 + * H[factor] Mix factor for extruder stepper 5 + * I[factor] Mix factor for extruder stepper 6 + * + */ + inline void gcode_M165() { gcode_get_mix(); } + #endif + +#endif // MIXING_EXTRUDER + +/** + * M999: Restart after being stopped + * + * Default behaviour is to flush the serial buffer and request + * a resend to the host starting on the last N line received. + * + * Sending "M999 S1" will resume printing without flushing the + * existing command buffer. + * + */ +inline void gcode_M999() { + Running = true; + lcd_reset_alert_level(); + + if (parser.boolval('S')) return; + + // gcode_LastN = Stopped_gcode_LastN; + flush_and_request_resend(); +} + +#if ENABLED(SWITCHING_EXTRUDER) + #if EXTRUDERS > 3 + #define REQ_ANGLES 4 + #define _SERVO_NR (e < 2 ? SWITCHING_EXTRUDER_SERVO_NR : SWITCHING_EXTRUDER_E23_SERVO_NR) + #else + #define REQ_ANGLES 2 + #define _SERVO_NR SWITCHING_EXTRUDER_SERVO_NR + #endif + inline void move_extruder_servo(const uint8_t e) { + constexpr int16_t angles[] = SWITCHING_EXTRUDER_SERVO_ANGLES; + static_assert(COUNT(angles) == REQ_ANGLES, "SWITCHING_EXTRUDER_SERVO_ANGLES needs " STRINGIFY(REQ_ANGLES) " angles."); + stepper.synchronize(); + #if EXTRUDERS & 1 + if (e < EXTRUDERS - 1) + #endif + { + MOVE_SERVO(_SERVO_NR, angles[e]); + safe_delay(500); + } + } +#endif // SWITCHING_EXTRUDER + +#if ENABLED(SWITCHING_NOZZLE) + inline void move_nozzle_servo(const uint8_t e) { + const int16_t angles[2] = SWITCHING_NOZZLE_SERVO_ANGLES; + stepper.synchronize(); + MOVE_SERVO(SWITCHING_NOZZLE_SERVO_NR, angles[e]); + safe_delay(500); + } +#endif + +inline void invalid_extruder_error(const uint8_t e) { + SERIAL_ECHO_START(); + SERIAL_CHAR('T'); + SERIAL_ECHO_F(e, DEC); + SERIAL_CHAR(' '); + SERIAL_ECHOLNPGM(MSG_INVALID_EXTRUDER); +} + +#if ENABLED(PARKING_EXTRUDER) + + #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT) + #define PE_MAGNET_ON_STATE !PARKING_EXTRUDER_SOLENOIDS_PINS_ACTIVE + #else + #define PE_MAGNET_ON_STATE PARKING_EXTRUDER_SOLENOIDS_PINS_ACTIVE + #endif + + void pe_set_magnet(const uint8_t extruder_num, const uint8_t state) { + switch (extruder_num) { + case 1: OUT_WRITE(SOL1_PIN, state); break; + default: OUT_WRITE(SOL0_PIN, state); break; + } + #if PARKING_EXTRUDER_SOLENOIDS_DELAY > 0 + dwell(PARKING_EXTRUDER_SOLENOIDS_DELAY); + #endif + } + + inline void pe_activate_magnet(const uint8_t extruder_num) { pe_set_magnet(extruder_num, PE_MAGNET_ON_STATE); } + inline void pe_deactivate_magnet(const uint8_t extruder_num) { pe_set_magnet(extruder_num, !PE_MAGNET_ON_STATE); } + +#endif // PARKING_EXTRUDER + +#if HAS_FANMUX + + void fanmux_switch(const uint8_t e) { + WRITE(FANMUX0_PIN, TEST(e, 0) ? HIGH : LOW); + #if PIN_EXISTS(FANMUX1) + WRITE(FANMUX1_PIN, TEST(e, 1) ? HIGH : LOW); + #if PIN_EXISTS(FANMUX2) + WRITE(FANMUX2, TEST(e, 2) ? HIGH : LOW); + #endif + #endif + } + + FORCE_INLINE void fanmux_init(void) { + SET_OUTPUT(FANMUX0_PIN); + #if PIN_EXISTS(FANMUX1) + SET_OUTPUT(FANMUX1_PIN); + #if PIN_EXISTS(FANMUX2) + SET_OUTPUT(FANMUX2_PIN); + #endif + #endif + fanmux_switch(0); + } + +#endif // HAS_FANMUX + +/** + * Perform a tool-change, which may result in moving the + * previous tool out of the way and the new tool into place. + */ +void tool_change(const uint8_t tmp_extruder, const float fr_mm_s/*=0.0*/, bool no_move/*=false*/) { + #if ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1 + + if (tmp_extruder >= MIXING_VIRTUAL_TOOLS) + return invalid_extruder_error(tmp_extruder); + + // T0-Tnnn: Switch virtual tool by changing the mix + for (uint8_t j = 0; j < MIXING_STEPPERS; j++) + mixing_factor[j] = mixing_virtual_tool_mix[tmp_extruder][j]; + + #else // !MIXING_EXTRUDER || MIXING_VIRTUAL_TOOLS <= 1 + + if (tmp_extruder >= EXTRUDERS) + return invalid_extruder_error(tmp_extruder); + + #if HOTENDS > 1 + + const float old_feedrate_mm_s = fr_mm_s > 0.0 ? fr_mm_s : feedrate_mm_s; + + feedrate_mm_s = fr_mm_s > 0.0 ? fr_mm_s : XY_PROBE_FEEDRATE_MM_S; + + if (tmp_extruder != active_extruder) { + if (!no_move && axis_unhomed_error()) { + no_move = true; + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("No move on toolchange"); + #endif + } + + // Save current position to destination, for use later + set_destination_from_current(); + + #if ENABLED(DUAL_X_CARRIAGE) + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPGM("Dual X Carriage Mode "); + switch (dual_x_carriage_mode) { + case DXC_FULL_CONTROL_MODE: SERIAL_ECHOLNPGM("DXC_FULL_CONTROL_MODE"); break; + case DXC_AUTO_PARK_MODE: SERIAL_ECHOLNPGM("DXC_AUTO_PARK_MODE"); break; + case DXC_DUPLICATION_MODE: SERIAL_ECHOLNPGM("DXC_DUPLICATION_MODE"); break; + } + } + #endif + + const float xhome = x_home_pos(active_extruder); + if (dual_x_carriage_mode == DXC_AUTO_PARK_MODE + && IsRunning() + && (delayed_move_time || current_position[X_AXIS] != xhome) + ) { + float raised_z = current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT; + #if ENABLED(MAX_SOFTWARE_ENDSTOPS) + NOMORE(raised_z, soft_endstop_max[Z_AXIS]); + #endif + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOLNPAIR("Raise to ", raised_z); + SERIAL_ECHOLNPAIR("MoveX to ", xhome); + SERIAL_ECHOLNPAIR("Lower to ", current_position[Z_AXIS]); + } + #endif + // Park old head: 1) raise 2) move to park position 3) lower + for (uint8_t i = 0; i < 3; i++) + planner.buffer_line( + i == 0 ? current_position[X_AXIS] : xhome, + current_position[Y_AXIS], + i == 2 ? current_position[Z_AXIS] : raised_z, + current_position[E_AXIS], + planner.max_feedrate_mm_s[i == 1 ? X_AXIS : Z_AXIS], + active_extruder + ); + stepper.synchronize(); + } + + // Apply Y & Z extruder offset (X offset is used as home pos with Dual X) + current_position[Y_AXIS] -= hotend_offset[Y_AXIS][active_extruder] - hotend_offset[Y_AXIS][tmp_extruder]; + current_position[Z_AXIS] -= hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder]; + + // Activate the new extruder ahead of calling set_axis_is_at_home! + active_extruder = tmp_extruder; + + // This function resets the max/min values - the current position may be overwritten below. + set_axis_is_at_home(X_AXIS); + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("New Extruder", current_position); + #endif + + // Only when auto-parking are carriages safe to move + if (dual_x_carriage_mode != DXC_AUTO_PARK_MODE) no_move = true; + + switch (dual_x_carriage_mode) { + case DXC_FULL_CONTROL_MODE: + // New current position is the position of the activated extruder + current_position[X_AXIS] = inactive_extruder_x_pos; + // Save the inactive extruder's position (from the old current_position) + inactive_extruder_x_pos = destination[X_AXIS]; + break; + case DXC_AUTO_PARK_MODE: + // record raised toolhead position for use by unpark + COPY(raised_parked_position, current_position); + raised_parked_position[Z_AXIS] += TOOLCHANGE_UNPARK_ZLIFT; + #if ENABLED(MAX_SOFTWARE_ENDSTOPS) + NOMORE(raised_parked_position[Z_AXIS], soft_endstop_max[Z_AXIS]); + #endif + active_extruder_parked = true; + delayed_move_time = 0; + break; + case DXC_DUPLICATION_MODE: + // If the new extruder is the left one, set it "parked" + // This triggers the second extruder to move into the duplication position + active_extruder_parked = (active_extruder == 0); + + if (active_extruder_parked) + current_position[X_AXIS] = inactive_extruder_x_pos; + else + current_position[X_AXIS] = destination[X_AXIS] + duplicate_extruder_x_offset; + inactive_extruder_x_pos = destination[X_AXIS]; + extruder_duplication_enabled = false; + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOLNPAIR("Set inactive_extruder_x_pos=", inactive_extruder_x_pos); + SERIAL_ECHOLNPGM("Clear extruder_duplication_enabled"); + } + #endif + break; + } + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOLNPAIR("Active extruder parked: ", active_extruder_parked ? "yes" : "no"); + DEBUG_POS("New extruder (parked)", current_position); + } + #endif + + // No extra case for HAS_ABL in DUAL_X_CARRIAGE. Does that mean they don't work together? + + #else // !DUAL_X_CARRIAGE + + #if ENABLED(PARKING_EXTRUDER) // Dual Parking extruder + const float z_diff = hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder]; + float z_raise = PARKING_EXTRUDER_SECURITY_RAISE; + if (!no_move) { + + const float parkingposx[] = PARKING_EXTRUDER_PARKING_X, + midpos = (parkingposx[0] + parkingposx[1]) * 0.5 + hotend_offset[X_AXIS][active_extruder], + grabpos = parkingposx[tmp_extruder] + hotend_offset[X_AXIS][active_extruder] + + (tmp_extruder == 0 ? -(PARKING_EXTRUDER_GRAB_DISTANCE) : PARKING_EXTRUDER_GRAB_DISTANCE); + /** + * Steps: + * 1. Raise Z-Axis to give enough clearance + * 2. Move to park position of old extruder + * 3. Disengage magnetic field, wait for delay + * 4. Move near new extruder + * 5. Engage magnetic field for new extruder + * 6. Move to parking incl. offset of new extruder + * 7. Lower Z-Axis + */ + + // STEP 1 + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPGM("Starting Autopark"); + if (DEBUGGING(LEVELING)) DEBUG_POS("current position:", current_position); + #endif + current_position[Z_AXIS] += z_raise; + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPGM("(1) Raise Z-Axis "); + if (DEBUGGING(LEVELING)) DEBUG_POS("Moving to Raised Z-Position", current_position); + #endif + planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[Z_AXIS], active_extruder); + stepper.synchronize(); + + // STEP 2 + current_position[X_AXIS] = parkingposx[active_extruder] + hotend_offset[X_AXIS][active_extruder]; + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPAIR("(2) Park extruder ", active_extruder); + if (DEBUGGING(LEVELING)) DEBUG_POS("Moving ParkPos", current_position); + #endif + planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); + stepper.synchronize(); + + // STEP 3 + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPGM("(3) Disengage magnet "); + #endif + pe_deactivate_magnet(active_extruder); + + // STEP 4 + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPGM("(4) Move to position near new extruder"); + #endif + current_position[X_AXIS] += (active_extruder == 0 ? 10 : -10); // move 10mm away from parked extruder + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("Moving away from parked extruder", current_position); + #endif + planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); + stepper.synchronize(); + + // STEP 5 + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPGM("(5) Engage magnetic field"); + #endif + + #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT) + pe_activate_magnet(active_extruder); //just save power for inverted magnets + #endif + pe_activate_magnet(tmp_extruder); + + // STEP 6 + current_position[X_AXIS] = grabpos + (tmp_extruder == 0 ? (+10) : (-10)); + planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); + current_position[X_AXIS] = grabpos; + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPAIR("(6) Unpark extruder ", tmp_extruder); + if (DEBUGGING(LEVELING)) DEBUG_POS("Move UnparkPos", current_position); + #endif + planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS]/2, active_extruder); + stepper.synchronize(); + + // Step 7 + current_position[X_AXIS] = midpos - hotend_offset[X_AXIS][tmp_extruder]; + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPGM("(7) Move midway between hotends"); + if (DEBUGGING(LEVELING)) DEBUG_POS("Move midway to new extruder", current_position); + #endif + planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); + stepper.synchronize(); + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPGM("Autopark done."); + #endif + } + else { // nomove == true + // Only engage magnetic field for new extruder + pe_activate_magnet(tmp_extruder); + #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT) + pe_activate_magnet(active_extruder); // Just save power for inverted magnets + #endif + } + current_position[Z_AXIS] -= hotend_offset[Z_AXIS][tmp_extruder] - hotend_offset[Z_AXIS][active_extruder]; // Apply Zoffset + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("Applying Z-offset", current_position); + #endif + + #endif // dualParking extruder + + #if ENABLED(SWITCHING_NOZZLE) + #define DONT_SWITCH (SWITCHING_EXTRUDER_SERVO_NR == SWITCHING_NOZZLE_SERVO_NR) + // <0 if the new nozzle is higher, >0 if lower. A bigger raise when lower. + const float z_diff = hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder], + z_raise = 0.3 + (z_diff > 0.0 ? z_diff : 0.0); + + // Always raise by some amount + current_position[Z_AXIS] += z_raise; + planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[Z_AXIS], active_extruder); + move_nozzle_servo(tmp_extruder); + #endif + + /** + * Set current_position to the position of the new nozzle. + * Offsets are based on linear distance, so we need to get + * the resulting position in coordinate space. + * + * - With grid or 3-point leveling, offset XYZ by a tilted vector + * - With mesh leveling, update Z for the new position + * - Otherwise, just use the raw linear distance + * + * Software endstops are altered here too. Consider a case where: + * E0 at X=0 ... E1 at X=10 + * When we switch to E1 now X=10, but E1 can't move left. + * To express this we apply the change in XY to the software endstops. + * E1 can move farther right than E0, so the right limit is extended. + * + * Note that we don't adjust the Z software endstops. Why not? + * Consider a case where Z=0 (here) and switching to E1 makes Z=1 + * because the bed is 1mm lower at the new position. As long as + * the first nozzle is out of the way, the carriage should be + * allowed to move 1mm lower. This technically "breaks" the + * Z software endstop. But this is technically correct (and + * there is no viable alternative). + */ + #if ABL_PLANAR + // Offset extruder, make sure to apply the bed level rotation matrix + vector_3 tmp_offset_vec = vector_3(hotend_offset[X_AXIS][tmp_extruder], + hotend_offset[Y_AXIS][tmp_extruder], + 0), + act_offset_vec = vector_3(hotend_offset[X_AXIS][active_extruder], + hotend_offset[Y_AXIS][active_extruder], + 0), + offset_vec = tmp_offset_vec - act_offset_vec; + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + tmp_offset_vec.debug(PSTR("tmp_offset_vec")); + act_offset_vec.debug(PSTR("act_offset_vec")); + offset_vec.debug(PSTR("offset_vec (BEFORE)")); + } + #endif + + offset_vec.apply_rotation(planner.bed_level_matrix.transpose(planner.bed_level_matrix)); + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) offset_vec.debug(PSTR("offset_vec (AFTER)")); + #endif + + // Adjustments to the current position + const float xydiff[2] = { offset_vec.x, offset_vec.y }; + current_position[Z_AXIS] += offset_vec.z; + + #else // !ABL_PLANAR + + const float xydiff[2] = { + hotend_offset[X_AXIS][tmp_extruder] - hotend_offset[X_AXIS][active_extruder], + hotend_offset[Y_AXIS][tmp_extruder] - hotend_offset[Y_AXIS][active_extruder] + }; + + #if HAS_MESH && PLANNER_LEVELING + + if (planner.leveling_active) { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOPAIR("Z before: ", current_position[Z_AXIS]); + #endif + float x2 = current_position[X_AXIS] + xydiff[X_AXIS], + y2 = current_position[Y_AXIS] + xydiff[Y_AXIS], + z1 = current_position[Z_AXIS], z2 = z1; + planner.apply_leveling(current_position[X_AXIS], current_position[Y_AXIS], z1); + planner.apply_leveling(x2, y2, z2); + current_position[Z_AXIS] += z2 - z1; + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) + SERIAL_ECHOLNPAIR(" after: ", current_position[Z_AXIS]); + #endif + } + + #endif // HAS_MESH && PLANNER_LEVELING + + #endif // !HAS_ABL + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR("Offset Tool XY by { ", xydiff[X_AXIS]); + SERIAL_ECHOPAIR(", ", xydiff[Y_AXIS]); + SERIAL_ECHOLNPGM(" }"); + } + #endif + + // The newly-selected extruder XY is actually at... + current_position[X_AXIS] += xydiff[X_AXIS]; + current_position[Y_AXIS] += xydiff[Y_AXIS]; + + // Set the new active extruder + active_extruder = tmp_extruder; + + #endif // !DUAL_X_CARRIAGE + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("Sync After Toolchange", current_position); + #endif + + // Tell the planner the new "current position" + SYNC_PLAN_POSITION_KINEMATIC(); + + #if ENABLED(DELTA) + //LOOP_XYZ(i) update_software_endstops(i); // or modify the constrain function + // Do a small lift to avoid the workpiece in the move back (below) + const bool safe_to_move = current_position[Z_AXIS] < delta_clip_start_height - 1; + if (!no_move && IsRunning() && safe_to_move) { + ++current_position[Z_AXIS]; + planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[Z_AXIS], active_extruder); + } + #else + constexpr bool safe_to_move = true; + #endif + + #if ENABLED(SWITCHING_NOZZLE) + destination[Z_AXIS] += z_diff; // Include the Z restore with the "move back" + #endif + + // Move to the "old position" (move the extruder into place) + if (safe_to_move && !no_move && IsRunning()) { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("Move back", destination); + #endif + // Move back to the original (or tweaked) position + do_blocking_move_to(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS]); + } + #if ENABLED(SWITCHING_NOZZLE) + else { + // Move back down. (Including when the new tool is higher.) + do_blocking_move_to_z(destination[Z_AXIS], planner.max_feedrate_mm_s[Z_AXIS]); + } + #endif + } // (tmp_extruder != active_extruder) + + stepper.synchronize(); + + #if ENABLED(EXT_SOLENOID) && !ENABLED(PARKING_EXTRUDER) + disable_all_solenoids(); + enable_solenoid_on_active_extruder(); + #endif // EXT_SOLENOID + + feedrate_mm_s = old_feedrate_mm_s; + + #else // HOTENDS <= 1 + + UNUSED(fr_mm_s); + UNUSED(no_move); + + #if ENABLED(MK2_MULTIPLEXER) + if (tmp_extruder >= E_STEPPERS) + return invalid_extruder_error(tmp_extruder); + + select_multiplexed_stepper(tmp_extruder); + #endif + + // Set the new active extruder + active_extruder = tmp_extruder; + + #endif // HOTENDS <= 1 + + #if ENABLED(SWITCHING_EXTRUDER) && !DONT_SWITCH + stepper.synchronize(); + move_extruder_servo(active_extruder); + #endif + + #if HAS_FANMUX + fanmux_switch(active_extruder); + #endif + + SERIAL_ECHO_START(); + SERIAL_ECHOLNPAIR(MSG_ACTIVE_EXTRUDER, (int)active_extruder); + + #endif // !MIXING_EXTRUDER || MIXING_VIRTUAL_TOOLS <= 1 +} + +/** + * T0-T3: Switch tool, usually switching extruders + * + * F[units/min] Set the movement feedrate + * S1 Don't move the tool in XY after change + */ +inline void gcode_T(const uint8_t tmp_extruder) { + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR(">>> gcode_T(", tmp_extruder); + SERIAL_CHAR(')'); + SERIAL_EOL(); + DEBUG_POS("BEFORE", current_position); + } + #endif + + #if HOTENDS == 1 || (ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1) + + tool_change(tmp_extruder); + + #elif HOTENDS > 1 + + tool_change( + tmp_extruder, + MMM_TO_MMS(parser.linearval('F')), + (tmp_extruder == active_extruder) || parser.boolval('S') + ); + + #endif + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + DEBUG_POS("AFTER", current_position); + SERIAL_ECHOLNPGM("<<< gcode_T"); + } + #endif +} + +/** + * Process the parsed command and dispatch it to its handler + */ +void process_parsed_command() { + KEEPALIVE_STATE(IN_HANDLER); + + // Handle a known G, M, or T + switch (parser.command_letter) { + case 'G': switch (parser.codenum) { + + case 0: case 1: gcode_G0_G1( // G0: Fast Move, G1: Linear Move + #if IS_SCARA + parser.codenum == 0 + #endif + ); break; + + #if ENABLED(ARC_SUPPORT) && DISABLED(SCARA) + case 2: case 3: gcode_G2_G3(parser.codenum == 2); break; // G2: CW ARC, G3: CCW ARC + #endif + + case 4: gcode_G4(); break; // G4: Dwell + + #if ENABLED(BEZIER_CURVE_SUPPORT) + case 5: gcode_G5(); break; // G5: Cubic B_spline + #endif + + #if ENABLED(FWRETRACT) + case 10: gcode_G10(); break; // G10: Retract + case 11: gcode_G11(); break; // G11: Prime + #endif + + #if ENABLED(NOZZLE_CLEAN_FEATURE) + case 12: gcode_G12(); break; // G12: Clean Nozzle + #endif + + #if ENABLED(CNC_WORKSPACE_PLANES) + case 17: gcode_G17(); break; // G17: Select Plane XY + case 18: gcode_G18(); break; // G18: Select Plane ZX + case 19: gcode_G19(); break; // G19: Select Plane YZ + #endif + + #if ENABLED(INCH_MODE_SUPPORT) + case 20: gcode_G20(); break; // G20: Inch Units + case 21: gcode_G21(); break; // G21: Millimeter Units + #endif + + #if ENABLED(G26_MESH_VALIDATION) + case 26: gcode_G26(); break; // G26: Mesh Validation Pattern + #endif + + #if ENABLED(NOZZLE_PARK_FEATURE) + case 27: gcode_G27(); break; // G27: Park Nozzle + #endif + + case 28: gcode_G28(false); break; // G28: Home one or more axes + + #if HAS_LEVELING + case 29: gcode_G29(); break; // G29: Detailed Z probe + #endif + + #if HAS_BED_PROBE + case 30: gcode_G30(); break; // G30: Single Z probe + #endif + + #if ENABLED(Z_PROBE_SLED) + case 31: gcode_G31(); break; // G31: Dock sled + case 32: gcode_G32(); break; // G32: Undock sled + #endif + + #if ENABLED(DELTA_AUTO_CALIBRATION) + case 33: gcode_G33(); break; // G33: Delta Auto-Calibration + #endif + + #if ENABLED(G38_PROBE_TARGET) + case 38: + if (parser.subcode == 2 || parser.subcode == 3) + gcode_G38(parser.subcode == 2); // G38.2, G38.3: Probe towards object + break; + #endif + + #if HAS_MESH + case 42: gcode_G42(); break; // G42: Move to mesh point + #endif + + case 90: relative_mode = false; break; // G90: Absolute coordinates + case 91: relative_mode = true; break; // G91: Relative coordinates + + case 92: gcode_G92(); break; // G92: Set Position + + #if ENABLED(DEBUG_GCODE_PARSER) + case 800: parser.debug(); break; // G800: GCode Parser Test for G + #endif + } + break; + + case 'M': switch (parser.codenum) { + #if HAS_RESUME_CONTINUE + case 0: case 1: gcode_M0_M1(); break; // M0: Unconditional stop, M1: Conditional stop + #endif + + #if ENABLED(SPINDLE_LASER_ENABLE) + case 3: gcode_M3_M4(true); break; // M3: Laser/CW-Spindle Power + case 4: gcode_M3_M4(false); break; // M4: Laser/CCW-Spindle Power + case 5: gcode_M5(); break; // M5: Laser/Spindle OFF + #endif + #if(ENABLED(FAN_AS_LASER)) + case 3: gcode_M3(); break; // M3: Laser Power On + case 5: gcode_M5(); break; // M5: Laser OFF + #endif + case 17: gcode_M17(); break; // M17: Enable all steppers + + #if ENABLED(SDSUPPORT) + case 20: gcode_M20(); break; // M20: List SD Card + case 21: gcode_M21(); break; // M21: Init SD Card + case 22: gcode_M22(); break; // M22: Release SD Card + case 23: gcode_M23(); break; // M23: Select File + case 24: gcode_M24(); break; // M24: Start SD Print + case 25: gcode_M25(); break; // M25: Pause SD Print + case 26: gcode_M26(); break; // M26: Set SD Index + case 27: gcode_M27(); break; // M27: Get SD Status + case 28: gcode_M28(); break; // M28: Start SD Write + case 29: gcode_M29(); break; // M29: Stop SD Write + case 30: gcode_M30(); break; // M30: Delete File + case 32: gcode_M32(); break; // M32: Select file, Start SD Print + #if ENABLED(LONG_FILENAME_HOST_SUPPORT) + case 33: gcode_M33(); break; // M33: Report longname path + #endif + #if ENABLED(SDCARD_SORT_ALPHA) && ENABLED(SDSORT_GCODE) + case 34: gcode_M34(); break; // M34: Set SD card sorting options + #endif + case 928: gcode_M928(); break; // M928: Start SD write + #endif // SDSUPPORT + + case 31: gcode_M31(); break; // M31: Report print job elapsed time + + case 42: gcode_M42(); break; // M42: Change pin state + #if ENABLED(PINS_DEBUGGING) + case 43: gcode_M43(); break; // M43: Read/monitor pin and endstop states + #endif + + #if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST) + case 48: gcode_M48(); break; // M48: Z probe repeatability test + #endif + #if ENABLED(G26_MESH_VALIDATION) + case 49: gcode_M49(); break; // M49: Toggle the G26 Debug Flag + #endif + + #if ENABLED(ULTRA_LCD) && ENABLED(LCD_SET_PROGRESS_MANUALLY) + case 73: gcode_M73(); break; // M73: Set Print Progress % + #endif + case 75: gcode_M75(); break; // M75: Start Print Job Timer + case 76: gcode_M76(); break; // M76: Pause Print Job Timer + case 77: gcode_M77(); break; // M77: Stop Print Job Timer + #if ENABLED(PRINTCOUNTER) + case 78: gcode_M78(); break; // M78: Report Print Statistics + #endif + + #if ENABLED(M100_FREE_MEMORY_WATCHER) + case 100: gcode_M100(); break; // M100: Free Memory Report + #endif + + case 104: gcode_M104(); break; // M104: Set Hotend Temperature + case 110: gcode_M110(); break; // M110: Set Current Line Number + case 111: gcode_M111(); break; // M111: Set Debug Flags + + #if DISABLED(EMERGENCY_PARSER) + case 108: gcode_M108(); break; // M108: Cancel Waiting + case 112: gcode_M112(); break; // M112: Emergency Stop + case 410: gcode_M410(); break; // M410: Quickstop. Abort all planned moves + #endif + + #if ENABLED(HOST_KEEPALIVE_FEATURE) + case 113: gcode_M113(); break; // M113: Set Host Keepalive Interval + #endif + + case 140: gcode_M140(); break; // M140: Set Bed Temperature + + case 105: gcode_M105(); KEEPALIVE_STATE(NOT_BUSY); return; // M105: Report Temperatures (and say "ok") + + #if ENABLED(AUTO_REPORT_TEMPERATURES) + case 155: gcode_M155(); break; // M155: Set Temperature Auto-report Interval + #endif + + case 109: gcode_M109(); break; // M109: Set Hotend Temperature. Wait for target. + + #if HAS_TEMP_BED + case 190: gcode_M190(); break; // M190: Set Bed Temperature. Wait for target. + #endif + + #if FAN_COUNT > 0 + case 106: gcode_M106(); break; // M106: Set Fan Speed + case 107: gcode_M107(); break; // M107: Fan Off + #endif + + #if ENABLED(PARK_HEAD_ON_PAUSE) + case 125: gcode_M125(); break; // M125: Park (for Filament Change) + #endif + + #if ENABLED(BARICUDA) + #if HAS_HEATER_1 + case 126: gcode_M126(); break; // M126: Valve 1 Open + case 127: gcode_M127(); break; // M127: Valve 1 Closed + #endif + #if HAS_HEATER_2 + case 128: gcode_M128(); break; // M128: Valve 2 Open + case 129: gcode_M129(); break; // M129: Valve 2 Closed + #endif + #endif + + #if HAS_POWER_SWITCH + case 80: gcode_M80(); break; // M80: Turn on Power Supply + #endif + case 81: gcode_M81(); break; // M81: Turn off Power and Power Supply + + case 82: gcode_M82(); break; // M82: Disable Relative E-Axis + case 83: gcode_M83(); break; // M83: Set Relative E-Axis + case 18: case 84: gcode_M18_M84(); break; // M18/M84: Disable Steppers / Set Timeout + case 85: gcode_M85(); break; // M85: Set inactivity stepper shutdown timeout + case 92: gcode_M92(); break; // M92: Set steps-per-unit + case 114: gcode_M114(); break; // M114: Report Current Position + case 115: gcode_M115(); break; // M115: Capabilities Report + case 117: gcode_M117(); break; // M117: Set LCD message text + case 118: gcode_M118(); break; // M118: Print a message in the host console + case 119: gcode_M119(); break; // M119: Report Endstop states + case 120: gcode_M120(); break; // M120: Enable Endstops + case 121: gcode_M121(); break; // M121: Disable Endstops + + #if ENABLED(ULTIPANEL) + case 145: gcode_M145(); break; // M145: Set material heatup parameters + #endif + + #if ENABLED(TEMPERATURE_UNITS_SUPPORT) + case 149: gcode_M149(); break; // M149: Set Temperature Units, C F K + #endif + + #if HAS_COLOR_LEDS + case 150: gcode_M150(); break; // M150: Set Status LED Color + #endif + + #if ENABLED(MIXING_EXTRUDER) + case 163: gcode_M163(); break; // M163: Set Mixing Component + #if MIXING_VIRTUAL_TOOLS > 1 + case 164: gcode_M164(); break; // M164: Save Current Mix + #endif + #if ENABLED(DIRECT_MIXING_IN_G1) + case 165: gcode_M165(); break; // M165: Set Multiple Mixing Components + #endif + #endif + + #if DISABLED(NO_VOLUMETRICS) + case 200: gcode_M200(); break; // M200: Set Filament Diameter, Volumetric Extrusion + #endif + + case 201: gcode_M201(); break; // M201: Set Max Printing Acceleration (units/sec^2) + #if 0 + case 202: gcode_M202(); break; // M202: Not used for Sprinter/grbl gen6 + #endif + case 203: gcode_M203(); break; // M203: Set Max Feedrate (units/sec) + case 204: gcode_M204(); break; // M204: Set Acceleration + case 205: gcode_M205(); break; // M205: Set Advanced settings + + #if HAS_M206_COMMAND + case 206: gcode_M206(); break; // M206: Set Home Offsets + case 428: gcode_M428(); break; // M428: Set Home Offsets based on current position + #endif + + #if ENABLED(FWRETRACT) + case 207: gcode_M207(); break; // M207: Set Retract Length, Feedrate, Z lift + case 208: gcode_M208(); break; // M208: Set Additional Prime Length and Feedrate + case 209: + if (MIN_AUTORETRACT <= MAX_AUTORETRACT) gcode_M209(); // M209: Turn Auto-Retract on/off + break; + #endif + + case 211: gcode_M211(); break; // M211: Enable/Disable/Report Software Endstops + + #if HOTENDS > 1 + case 218: gcode_M218(); break; // M218: Set Tool Offset + #endif + + case 220: gcode_M220(); break; // M220: Set Feedrate Percentage + case 221: gcode_M221(); break; // M221: Set Flow Percentage + case 226: gcode_M226(); break; // M226: Wait for Pin State + + #if defined(CHDK) || HAS_PHOTOGRAPH + case 240: gcode_M240(); break; // M240: Trigger Camera + #endif + + #if HAS_LCD_CONTRAST + case 250: gcode_M250(); break; // M250: Set LCD Contrast + #endif + + #if ENABLED(EXPERIMENTAL_I2CBUS) + case 260: gcode_M260(); break; // M260: Send Data to i2c slave + case 261: gcode_M261(); break; // M261: Request Data from i2c slave + #endif + + #if HAS_SERVOS + case 280: gcode_M280(); break; // M280: Set Servo Position + #endif + + #if ENABLED(BABYSTEPPING) + case 290: gcode_M290(); break; // M290: Babystepping + #endif + + #if HAS_BUZZER + case 300: gcode_M300(); break; // M300: Add Tone/Buzz to Queue + #endif + + #if ENABLED(PIDTEMP) + case 301: gcode_M301(); break; // M301: Set Hotend PID parameters + #endif + + #if ENABLED(PREVENT_COLD_EXTRUSION) + case 302: gcode_M302(); break; // M302: Set Minimum Extrusion Temp + #endif + + case 303: gcode_M303(); break; // M303: PID Autotune + + #if ENABLED(PIDTEMPBED) + case 304: gcode_M304(); break; // M304: Set Bed PID parameters + #endif + + #if HAS_MICROSTEPS + case 350: gcode_M350(); break; // M350: Set microstepping mode. Warning: Steps per unit remains unchanged. S code sets stepping mode for all drivers. + case 351: gcode_M351(); break; // M351: Toggle MS1 MS2 pins directly, S# determines MS1 or MS2, X# sets the pin high/low. + #endif + + case 355: gcode_M355(); break; // M355: Set Case Light brightness + + #if ENABLED(MORGAN_SCARA) + case 360: if (gcode_M360()) return; break; // M360: SCARA Theta pos1 + case 361: if (gcode_M361()) return; break; // M361: SCARA Theta pos2 + case 362: if (gcode_M362()) return; break; // M362: SCARA Psi pos1 + case 363: if (gcode_M363()) return; break; // M363: SCARA Psi pos2 + case 364: if (gcode_M364()) return; break; // M364: SCARA Psi pos3 (90 deg to Theta) + #endif + + case 400: gcode_M400(); break; // M400: Synchronize. Wait for moves to finish. + + #if HAS_BED_PROBE + case 401: gcode_M401(); break; // M401: Deploy Probe + case 402: gcode_M402(); break; // M402: Stow Probe + #endif + + #if ENABLED(FILAMENT_WIDTH_SENSOR) + case 404: gcode_M404(); break; // M404: Set/Report Nominal Filament Width + case 405: gcode_M405(); break; // M405: Enable Filament Width Sensor + case 406: gcode_M406(); break; // M406: Disable Filament Width Sensor + case 407: gcode_M407(); break; // M407: Report Measured Filament Width + #endif + + #if HAS_LEVELING + case 420: gcode_M420(); break; // M420: Set Bed Leveling Enabled / Fade + #endif + + #if HAS_MESH + case 421: gcode_M421(); break; // M421: Set a Mesh Z value + #endif + + case 500: gcode_M500(); break; // M500: Store Settings in EEPROM + case 501: gcode_M501(); break; // M501: Read Settings from EEPROM + case 502: gcode_M502(); break; // M502: Revert Settings to defaults + #if DISABLED(DISABLE_M503) + case 503: gcode_M503(); break; // M503: Report Settings (in SRAM) + #endif + #if ENABLED(EEPROM_SETTINGS) + case 504: gcode_M504(); break; // M504: Validate EEPROM + #endif + + #if ENABLED(ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED) + case 540: gcode_M540(); break; // M540: Set Abort on Endstop Hit for SD Printing + #endif + + #if ENABLED(ADVANCED_PAUSE_FEATURE) + case 600: gcode_M600(); break; // M600: Pause for Filament Change + case 603: gcode_M603(); break; // M603: Configure Filament Change + #endif + + #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(DUAL_NOZZLE_DUPLICATION_MODE) + case 605: gcode_M605(); break; // M605: Set Dual X Carriage movement mode + #endif + + #if ENABLED(DELTA) + case 665: gcode_M665(); break; // M665: Delta Configuration + #endif + #if ENABLED(DELTA) || ENABLED(X_DUAL_ENDSTOPS) || ENABLED(Y_DUAL_ENDSTOPS) || ENABLED(Z_DUAL_ENDSTOPS) + case 666: gcode_M666(); break; // M666: DELTA/Dual Endstop Adjustment + #endif + + #if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES) + case 701: gcode_M701(); break; // M701: Load Filament + case 702: gcode_M702(); break; // M702: Unload Filament + #endif + + #if ENABLED(DEBUG_GCODE_PARSER) + case 800: parser.debug(); break; // M800: GCode Parser Test for M + #endif + + #if HAS_BED_PROBE + case 851: gcode_M851(); break; // M851: Set Z Probe Z Offset + #endif + + #if ENABLED(SKEW_CORRECTION_GCODE) + case 852: gcode_M852(); break; // M852: Set Skew factors + #endif + + #if ENABLED(I2C_POSITION_ENCODERS) + case 860: gcode_M860(); break; // M860: Report encoder module position + case 861: gcode_M861(); break; // M861: Report encoder module status + case 862: gcode_M862(); break; // M862: Perform axis test + case 863: gcode_M863(); break; // M863: Calibrate steps/mm + case 864: gcode_M864(); break; // M864: Change module address + case 865: gcode_M865(); break; // M865: Check module firmware version + case 866: gcode_M866(); break; // M866: Report axis error count + case 867: gcode_M867(); break; // M867: Toggle error correction + case 868: gcode_M868(); break; // M868: Set error correction threshold + case 869: gcode_M869(); break; // M869: Report axis error + #endif + + #if ENABLED(LIN_ADVANCE) + case 900: gcode_M900(); break; // M900: Set Linear Advance K factor + #endif + + case 907: gcode_M907(); break; // M907: Set Digital Trimpot Motor Current using axis codes. + + #if HAS_DIGIPOTSS || ENABLED(DAC_STEPPER_CURRENT) + case 908: gcode_M908(); break; // M908: Direct Control Digital Trimpot + #if ENABLED(DAC_STEPPER_CURRENT) + case 909: gcode_M909(); break; // M909: Print Digipot/DAC current value (As with Printrbot RevF) + case 910: gcode_M910(); break; // M910: Commit Digipot/DAC value to External EEPROM (As with Printrbot RevF) + #endif + #endif + + #if ENABLED(HAVE_TMC2130) || ENABLED(HAVE_TMC2208) + #if ENABLED(TMC_DEBUG) + case 122: gcode_M122(); break; // M122: Debug TMC steppers + #endif + case 906: gcode_M906(); break; // M906: Set motor current in milliamps using axis codes X, Y, Z, E + case 911: gcode_M911(); break; // M911: Report TMC prewarn triggered flags + case 912: gcode_M912(); break; // M911: Clear TMC prewarn triggered flags + #if ENABLED(HYBRID_THRESHOLD) + case 913: gcode_M913(); break; // M913: Set HYBRID_THRESHOLD speed. + #endif + #if ENABLED(SENSORLESS_HOMING) + case 914: gcode_M914(); break; // M914: Set SENSORLESS_HOMING sensitivity. + #endif + #if ENABLED(TMC_Z_CALIBRATION) + case 915: gcode_M915(); break; // M915: TMC Z axis calibration routine + #endif + #endif + + case 999: gcode_M999(); break; // M999: Restart after being Stopped + } + break; + + case 'T': gcode_T(parser.codenum); break; // T: Tool Select + + default: parser.unknown_command_error(); + } + + KEEPALIVE_STATE(NOT_BUSY); + ok_to_send(); +} + +void process_next_command() { + char * const current_command = command_queue[cmd_queue_index_r]; + + if (DEBUGGING(ECHO)) { + SERIAL_ECHO_START(); + SERIAL_ECHOLN(current_command); + #if ENABLED(M100_FREE_MEMORY_WATCHER) + SERIAL_ECHOPAIR("slot:", cmd_queue_index_r); + M100_dump_routine(" Command Queue:", (const char*)command_queue, (const char*)(command_queue + sizeof(command_queue))); + #endif + } + + reset_stepper_timeout(); // Keep steppers powered + + // Parse the next command in the queue + parser.parse(current_command); + process_parsed_command(); +} + +/** + * Send a "Resend: nnn" message to the host to + * indicate that a command needs to be re-sent. + */ +void flush_and_request_resend() { + //char command_queue[cmd_queue_index_r][100]="Resend:"; + SERIAL_FLUSH(); + SERIAL_PROTOCOLPGM(MSG_RESEND); + SERIAL_PROTOCOLLN(gcode_LastN + 1); + ok_to_send(); +} + +/** + * Send an "ok" message to the host, indicating + * that a command was successfully processed. + * + * If ADVANCED_OK is enabled also include: + * N Line number of the command, if any + * P Planner space remaining + * B Block queue space remaining + */ +void ok_to_send() { + if (!send_ok[cmd_queue_index_r]) return; + SERIAL_PROTOCOLPGM(MSG_OK); + #if ENABLED(ADVANCED_OK) + char* p = command_queue[cmd_queue_index_r]; + if (*p == 'N') { + SERIAL_PROTOCOL(' '); + SERIAL_ECHO(*p++); + while (NUMERIC_SIGNED(*p)) + SERIAL_ECHO(*p++); + } + SERIAL_PROTOCOLPGM(" P"); SERIAL_PROTOCOL(int(BLOCK_BUFFER_SIZE - planner.movesplanned() - 1)); + SERIAL_PROTOCOLPGM(" B"); SERIAL_PROTOCOL(BUFSIZE - commands_in_queue); + #endif + SERIAL_EOL(); +} + +#if HAS_SOFTWARE_ENDSTOPS + + /** + * Constrain the given coordinates to the software endstops. + * + * For DELTA/SCARA the XY constraint is based on the smallest + * radius within the set software endstops. + */ + void clamp_to_software_endstops(float target[XYZ]) { + if (!soft_endstops_enabled) return; + #if IS_KINEMATIC + const float dist_2 = HYPOT2(target[X_AXIS], target[Y_AXIS]); + if (dist_2 > soft_endstop_radius_2) { + const float ratio = soft_endstop_radius / SQRT(dist_2); // 200 / 300 = 0.66 + target[X_AXIS] *= ratio; + target[Y_AXIS] *= ratio; + } + #else + #if ENABLED(MIN_SOFTWARE_ENDSTOP_X) + NOLESS(target[X_AXIS], soft_endstop_min[X_AXIS]); + #endif + #if ENABLED(MIN_SOFTWARE_ENDSTOP_Y) + NOLESS(target[Y_AXIS], soft_endstop_min[Y_AXIS]); + #endif + #if ENABLED(MAX_SOFTWARE_ENDSTOP_X) + NOMORE(target[X_AXIS], soft_endstop_max[X_AXIS]); + #endif + #if ENABLED(MAX_SOFTWARE_ENDSTOP_Y) + NOMORE(target[Y_AXIS], soft_endstop_max[Y_AXIS]); + #endif + #endif + #if ENABLED(MIN_SOFTWARE_ENDSTOP_Z) + NOLESS(target[Z_AXIS], soft_endstop_min[Z_AXIS]); + #endif + #if ENABLED(MAX_SOFTWARE_ENDSTOP_Z) + NOMORE(target[Z_AXIS], soft_endstop_max[Z_AXIS]); + #endif + } + +#endif + +#if ENABLED(AUTO_BED_LEVELING_BILINEAR) + + // Get the Z adjustment for non-linear bed leveling + float bilinear_z_offset(const float raw[XYZ]) { + + static float z1, d2, z3, d4, L, D, ratio_x, ratio_y, + last_x = -999.999, last_y = -999.999; + + // Whole units for the grid line indices. Constrained within bounds. + static int8_t gridx, gridy, nextx, nexty, + last_gridx = -99, last_gridy = -99; + + // XY relative to the probed area + const float rx = raw[X_AXIS] - bilinear_start[X_AXIS], + ry = raw[Y_AXIS] - bilinear_start[Y_AXIS]; + + #if ENABLED(EXTRAPOLATE_BEYOND_GRID) + // Keep using the last grid box + #define FAR_EDGE_OR_BOX 2 + #else + // Just use the grid far edge + #define FAR_EDGE_OR_BOX 1 + #endif + + if (last_x != rx) { + last_x = rx; + ratio_x = rx * ABL_BG_FACTOR(X_AXIS); + const float gx = constrain(FLOOR(ratio_x), 0, ABL_BG_POINTS_X - FAR_EDGE_OR_BOX); + ratio_x -= gx; // Subtract whole to get the ratio within the grid box + + #if DISABLED(EXTRAPOLATE_BEYOND_GRID) + // Beyond the grid maintain height at grid edges + NOLESS(ratio_x, 0); // Never < 0.0. (> 1.0 is ok when nextx==gridx.) + #endif + + gridx = gx; + nextx = min(gridx + 1, ABL_BG_POINTS_X - 1); + } + + if (last_y != ry || last_gridx != gridx) { + + if (last_y != ry) { + last_y = ry; + ratio_y = ry * ABL_BG_FACTOR(Y_AXIS); + const float gy = constrain(FLOOR(ratio_y), 0, ABL_BG_POINTS_Y - FAR_EDGE_OR_BOX); + ratio_y -= gy; + + #if DISABLED(EXTRAPOLATE_BEYOND_GRID) + // Beyond the grid maintain height at grid edges + NOLESS(ratio_y, 0); // Never < 0.0. (> 1.0 is ok when nexty==gridy.) + #endif + + gridy = gy; + nexty = min(gridy + 1, ABL_BG_POINTS_Y - 1); + } + + if (last_gridx != gridx || last_gridy != gridy) { + last_gridx = gridx; + last_gridy = gridy; + // Z at the box corners + z1 = ABL_BG_GRID(gridx, gridy); // left-front + d2 = ABL_BG_GRID(gridx, nexty) - z1; // left-back (delta) + z3 = ABL_BG_GRID(nextx, gridy); // right-front + d4 = ABL_BG_GRID(nextx, nexty) - z3; // right-back (delta) + } + + // Bilinear interpolate. Needed since ry or gridx has changed. + L = z1 + d2 * ratio_y; // Linear interp. LF -> LB + const float R = z3 + d4 * ratio_y; // Linear interp. RF -> RB + + D = R - L; + } + + const float offset = L + ratio_x * D; // the offset almost always changes + + /* + static float last_offset = 0; + if (FABS(last_offset - offset) > 0.2) { + SERIAL_ECHOPGM("Sudden Shift at "); + SERIAL_ECHOPAIR("x=", rx); + SERIAL_ECHOPAIR(" / ", bilinear_grid_spacing[X_AXIS]); + SERIAL_ECHOLNPAIR(" -> gridx=", gridx); + SERIAL_ECHOPAIR(" y=", ry); + SERIAL_ECHOPAIR(" / ", bilinear_grid_spacing[Y_AXIS]); + SERIAL_ECHOLNPAIR(" -> gridy=", gridy); + SERIAL_ECHOPAIR(" ratio_x=", ratio_x); + SERIAL_ECHOLNPAIR(" ratio_y=", ratio_y); + SERIAL_ECHOPAIR(" z1=", z1); + SERIAL_ECHOPAIR(" z2=", z2); + SERIAL_ECHOPAIR(" z3=", z3); + SERIAL_ECHOLNPAIR(" z4=", z4); + SERIAL_ECHOPAIR(" L=", L); + SERIAL_ECHOPAIR(" R=", R); + SERIAL_ECHOLNPAIR(" offset=", offset); + } + last_offset = offset; + //*/ + + return offset; + } + +#endif // AUTO_BED_LEVELING_BILINEAR + +#if ENABLED(DELTA) + + /** + * Recalculate factors used for delta kinematics whenever + * settings have been changed (e.g., by M665). + */ + void recalc_delta_settings() { + const float trt[ABC] = DELTA_RADIUS_TRIM_TOWER, + drt[ABC] = DELTA_DIAGONAL_ROD_TRIM_TOWER; + delta_tower[A_AXIS][X_AXIS] = cos(RADIANS(210 + delta_tower_angle_trim[A_AXIS])) * (delta_radius + trt[A_AXIS]); // front left tower + delta_tower[A_AXIS][Y_AXIS] = sin(RADIANS(210 + delta_tower_angle_trim[A_AXIS])) * (delta_radius + trt[A_AXIS]); + delta_tower[B_AXIS][X_AXIS] = cos(RADIANS(330 + delta_tower_angle_trim[B_AXIS])) * (delta_radius + trt[B_AXIS]); // front right tower + delta_tower[B_AXIS][Y_AXIS] = sin(RADIANS(330 + delta_tower_angle_trim[B_AXIS])) * (delta_radius + trt[B_AXIS]); + delta_tower[C_AXIS][X_AXIS] = cos(RADIANS( 90 + delta_tower_angle_trim[C_AXIS])) * (delta_radius + trt[C_AXIS]); // back middle tower + delta_tower[C_AXIS][Y_AXIS] = sin(RADIANS( 90 + delta_tower_angle_trim[C_AXIS])) * (delta_radius + trt[C_AXIS]); + delta_diagonal_rod_2_tower[A_AXIS] = sq(delta_diagonal_rod + drt[A_AXIS]); + delta_diagonal_rod_2_tower[B_AXIS] = sq(delta_diagonal_rod + drt[B_AXIS]); + delta_diagonal_rod_2_tower[C_AXIS] = sq(delta_diagonal_rod + drt[C_AXIS]); + update_software_endstops(Z_AXIS); + axis_homed[X_AXIS] = axis_homed[Y_AXIS] = axis_homed[Z_AXIS] = false; + } + + #if ENABLED(DELTA_FAST_SQRT) + /** + * Fast inverse sqrt from Quake III Arena + * See: https://en.wikipedia.org/wiki/Fast_inverse_square_root + */ + float Q_rsqrt(const float number) { + long i; + float x2, y; + const float threehalfs = 1.5f; + x2 = number * 0.5f; + y = number; + i = * ( long * ) &y; // evil floating point bit level hacking + i = 0x5F3759DF - ( i >> 1 ); // what the f***? + y = * ( float * ) &i; + y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration + // y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed + return y; + } + + #endif + + /** + * Delta Inverse Kinematics + * + * Calculate the tower positions for a given machine + * position, storing the result in the delta[] array. + * + * This is an expensive calculation, requiring 3 square + * roots per segmented linear move, and strains the limits + * of a Mega2560 with a Graphical Display. + * + * Suggested optimizations include: + * + * - Disable the home_offset (M206) and/or position_shift (G92) + * features to remove up to 12 float additions. + * + * - Use a fast-inverse-sqrt function and add the reciprocal. + * (see above) + */ + + #define DELTA_DEBUG(VAR) do { \ + SERIAL_ECHOPAIR("cartesian X:", VAR[X_AXIS]); \ + SERIAL_ECHOPAIR(" Y:", VAR[Y_AXIS]); \ + SERIAL_ECHOLNPAIR(" Z:", VAR[Z_AXIS]); \ + SERIAL_ECHOPAIR("delta A:", delta[A_AXIS]); \ + SERIAL_ECHOPAIR(" B:", delta[B_AXIS]); \ + SERIAL_ECHOLNPAIR(" C:", delta[C_AXIS]); \ + }while(0) + + void inverse_kinematics(const float raw[XYZ]) { + #if HOTENDS > 1 + // Delta hotend offsets must be applied in Cartesian space with no "spoofing" + const float pos[XYZ] = { + raw[X_AXIS] - hotend_offset[X_AXIS][active_extruder], + raw[Y_AXIS] - hotend_offset[Y_AXIS][active_extruder], + raw[Z_AXIS] + }; + DELTA_IK(pos); + //DELTA_DEBUG(pos); + #else + DELTA_IK(raw); + //DELTA_DEBUG(raw); + #endif + } + + /** + * Calculate the highest Z position where the + * effector has the full range of XY motion. + */ + float delta_safe_distance_from_top() { + float cartesian[XYZ] = { 0, 0, 0 }; + inverse_kinematics(cartesian); + const float centered_extent = delta[A_AXIS]; + cartesian[Y_AXIS] = DELTA_PRINTABLE_RADIUS; + inverse_kinematics(cartesian); + return FABS(centered_extent - delta[A_AXIS]); + } + + /** + * Delta Forward Kinematics + * + * See the Wikipedia article "Trilateration" + * https://en.wikipedia.org/wiki/Trilateration + * + * Establish a new coordinate system in the plane of the + * three carriage points. This system has its origin at + * tower1, with tower2 on the X axis. Tower3 is in the X-Y + * plane with a Z component of zero. + * We will define unit vectors in this coordinate system + * in our original coordinate system. Then when we calculate + * the Xnew, Ynew and Znew values, we can translate back into + * the original system by moving along those unit vectors + * by the corresponding values. + * + * Variable names matched to Marlin, c-version, and avoid the + * use of any vector library. + * + * by Andreas Hardtung 2016-06-07 + * based on a Java function from "Delta Robot Kinematics V3" + * by Steve Graves + * + * The result is stored in the cartes[] array. + */ + void forward_kinematics_DELTA(float z1, float z2, float z3) { + // Create a vector in old coordinates along x axis of new coordinate + const float p12[] = { + delta_tower[B_AXIS][X_AXIS] - delta_tower[A_AXIS][X_AXIS], + delta_tower[B_AXIS][Y_AXIS] - delta_tower[A_AXIS][Y_AXIS], + z2 - z1 + }, + + // Get the Magnitude of vector. + d = SQRT(sq(p12[0]) + sq(p12[1]) + sq(p12[2])), + + // Create unit vector by dividing by magnitude. + ex[3] = { p12[0] / d, p12[1] / d, p12[2] / d }, + + // Get the vector from the origin of the new system to the third point. + p13[3] = { + delta_tower[C_AXIS][X_AXIS] - delta_tower[A_AXIS][X_AXIS], + delta_tower[C_AXIS][Y_AXIS] - delta_tower[A_AXIS][Y_AXIS], + z3 - z1 + }, + + // Use the dot product to find the component of this vector on the X axis. + i = ex[0] * p13[0] + ex[1] * p13[1] + ex[2] * p13[2], + + // Create a vector along the x axis that represents the x component of p13. + iex[] = { ex[0] * i, ex[1] * i, ex[2] * i }; + + // Subtract the X component from the original vector leaving only Y. We use the + // variable that will be the unit vector after we scale it. + float ey[3] = { p13[0] - iex[0], p13[1] - iex[1], p13[2] - iex[2] }; + + // The magnitude of Y component + const float j = SQRT(sq(ey[0]) + sq(ey[1]) + sq(ey[2])); + + // Convert to a unit vector + ey[0] /= j; ey[1] /= j; ey[2] /= j; + + // The cross product of the unit x and y is the unit z + // float[] ez = vectorCrossProd(ex, ey); + const float ez[3] = { + ex[1] * ey[2] - ex[2] * ey[1], + ex[2] * ey[0] - ex[0] * ey[2], + ex[0] * ey[1] - ex[1] * ey[0] + }, + // We now have the d, i and j values defined in Wikipedia. + // Plug them into the equations defined in Wikipedia for Xnew, Ynew and Znew + Xnew = (delta_diagonal_rod_2_tower[A_AXIS] - delta_diagonal_rod_2_tower[B_AXIS] + sq(d)) / (d * 2), + Ynew = ((delta_diagonal_rod_2_tower[A_AXIS] - delta_diagonal_rod_2_tower[C_AXIS] + HYPOT2(i, j)) / 2 - i * Xnew) / j, + Znew = SQRT(delta_diagonal_rod_2_tower[A_AXIS] - HYPOT2(Xnew, Ynew)); + + // Start from the origin of the old coordinates and add vectors in the + // old coords that represent the Xnew, Ynew and Znew to find the point + // in the old system. + cartes[X_AXIS] = delta_tower[A_AXIS][X_AXIS] + ex[0] * Xnew + ey[0] * Ynew - ez[0] * Znew; + cartes[Y_AXIS] = delta_tower[A_AXIS][Y_AXIS] + ex[1] * Xnew + ey[1] * Ynew - ez[1] * Znew; + cartes[Z_AXIS] = z1 + ex[2] * Xnew + ey[2] * Ynew - ez[2] * Znew; + } + + void forward_kinematics_DELTA(float point[ABC]) { + forward_kinematics_DELTA(point[A_AXIS], point[B_AXIS], point[C_AXIS]); + } + +#endif // DELTA + +/** + * Get the stepper positions in the cartes[] array. + * Forward kinematics are applied for DELTA and SCARA. + * + * The result is in the current coordinate space with + * leveling applied. The coordinates need to be run through + * unapply_leveling to obtain machine coordinates suitable + * for current_position, etc. + */ +void get_cartesian_from_steppers() { + #if ENABLED(DELTA) + forward_kinematics_DELTA( + stepper.get_axis_position_mm(A_AXIS), + stepper.get_axis_position_mm(B_AXIS), + stepper.get_axis_position_mm(C_AXIS) + ); + #else + #if IS_SCARA + forward_kinematics_SCARA( + stepper.get_axis_position_degrees(A_AXIS), + stepper.get_axis_position_degrees(B_AXIS) + ); + #else + cartes[X_AXIS] = stepper.get_axis_position_mm(X_AXIS); + cartes[Y_AXIS] = stepper.get_axis_position_mm(Y_AXIS); + #endif + cartes[Z_AXIS] = stepper.get_axis_position_mm(Z_AXIS); + #endif +} + +/** + * Set the current_position for an axis based on + * the stepper positions, removing any leveling that + * may have been applied. + * + * To prevent small shifts in axis position always call + * SYNC_PLAN_POSITION_KINEMATIC after updating axes with this. + * + * To keep hosts in sync, always call report_current_position + * after updating the current_position. + */ +void set_current_from_steppers_for_axis(const AxisEnum axis) { + get_cartesian_from_steppers(); + #if PLANNER_LEVELING + planner.unapply_leveling(cartes); + #endif + if (axis == ALL_AXES) + COPY(current_position, cartes); + else + current_position[axis] = cartes[axis]; +} + +#if IS_CARTESIAN +#if ENABLED(SEGMENT_LEVELED_MOVES) + + /** + * Prepare a segmented move on a CARTESIAN setup. + * + * This calls planner.buffer_line several times, adding + * small incremental moves. This allows the planner to + * apply more detailed bed leveling to the full move. + */ + inline void segmented_line_to_destination(const float &fr_mm_s, const float segment_size=LEVELED_SEGMENT_LENGTH) { + + const float xdiff = destination[X_AXIS] - current_position[X_AXIS], + ydiff = destination[Y_AXIS] - current_position[Y_AXIS]; + + // If the move is only in Z/E don't split up the move + if (!xdiff && !ydiff) { + planner.buffer_line_kinematic(destination, fr_mm_s, active_extruder); + return; + } + + // Remaining cartesian distances + const float zdiff = destination[Z_AXIS] - current_position[Z_AXIS], + ediff = destination[E_AXIS] - current_position[E_AXIS]; + + // Get the linear distance in XYZ + // If the move is very short, check the E move distance + // No E move either? Game over. + float cartesian_mm = SQRT(sq(xdiff) + sq(ydiff) + sq(zdiff)); + if (UNEAR_ZERO(cartesian_mm)) cartesian_mm = FABS(ediff); + if (UNEAR_ZERO(cartesian_mm)) return; + + // The length divided by the segment size + // At least one segment is required + uint16_t segments = cartesian_mm / segment_size; + NOLESS(segments, 1); + + // The approximate length of each segment + const float inv_segments = 1.0 / float(segments), + cartesian_segment_mm = cartesian_mm * inv_segments, + segment_distance[XYZE] = { + xdiff * inv_segments, + ydiff * inv_segments, + zdiff * inv_segments, + ediff * inv_segments + }; + + // SERIAL_ECHOPAIR("mm=", cartesian_mm); + // SERIAL_ECHOLNPAIR(" segments=", segments); + // SERIAL_ECHOLNPAIR(" segment_mm=", cartesian_segment_mm); + + // Get the raw current position as starting point + float raw[XYZE]; + COPY(raw, current_position); + + // Calculate and execute the segments + while (--segments) { + static millis_t next_idle_ms = millis() + 200UL; + thermalManager.manage_heater(); // This returns immediately if not really needed. + if (ELAPSED(millis(), next_idle_ms)) { + next_idle_ms = millis() + 200UL; + idle(); + } + LOOP_XYZE(i) raw[i] += segment_distance[i]; + planner.buffer_line_kinematic(raw, fr_mm_s, active_extruder, cartesian_segment_mm); + } + + // Since segment_distance is only approximate, + // the final move must be to the exact destination. + planner.buffer_line_kinematic(destination, fr_mm_s, active_extruder, cartesian_segment_mm); + } + +#elif ENABLED(MESH_BED_LEVELING) + + /** + * Prepare a mesh-leveled linear move in a Cartesian setup, + * splitting the move where it crosses mesh borders. + */ + void mesh_line_to_destination(const float fr_mm_s, uint8_t x_splits=0xFF, uint8_t y_splits=0xFF) { + // Get current and destination cells for this line + int cx1 = mbl.cell_index_x(current_position[X_AXIS]), + cy1 = mbl.cell_index_y(current_position[Y_AXIS]), + cx2 = mbl.cell_index_x(destination[X_AXIS]), + cy2 = mbl.cell_index_y(destination[Y_AXIS]); + NOMORE(cx1, GRID_MAX_POINTS_X - 2); + NOMORE(cy1, GRID_MAX_POINTS_Y - 2); + NOMORE(cx2, GRID_MAX_POINTS_X - 2); + NOMORE(cy2, GRID_MAX_POINTS_Y - 2); + + // Start and end in the same cell? No split needed. + if (cx1 == cx2 && cy1 == cy2) { + buffer_line_to_destination(fr_mm_s); + set_current_from_destination(); + return; + } + + #define MBL_SEGMENT_END(A) (current_position[A ##_AXIS] + (destination[A ##_AXIS] - current_position[A ##_AXIS]) * normalized_dist) + + float normalized_dist, end[XYZE]; + const int8_t gcx = max(cx1, cx2), gcy = max(cy1, cy2); + + // Crosses on the X and not already split on this X? + // The x_splits flags are insurance against rounding errors. + if (cx2 != cx1 && TEST(x_splits, gcx)) { + // Split on the X grid line + CBI(x_splits, gcx); + COPY(end, destination); + destination[X_AXIS] = mbl.index_to_xpos[gcx]; + normalized_dist = (destination[X_AXIS] - current_position[X_AXIS]) / (end[X_AXIS] - current_position[X_AXIS]); + destination[Y_AXIS] = MBL_SEGMENT_END(Y); + } + // Crosses on the Y and not already split on this Y? + else if (cy2 != cy1 && TEST(y_splits, gcy)) { + // Split on the Y grid line + CBI(y_splits, gcy); + COPY(end, destination); + destination[Y_AXIS] = mbl.index_to_ypos[gcy]; + normalized_dist = (destination[Y_AXIS] - current_position[Y_AXIS]) / (end[Y_AXIS] - current_position[Y_AXIS]); + destination[X_AXIS] = MBL_SEGMENT_END(X); + } + else { + // Must already have been split on these border(s) + buffer_line_to_destination(fr_mm_s); + set_current_from_destination(); + return; + } + + destination[Z_AXIS] = MBL_SEGMENT_END(Z); + destination[E_AXIS] = MBL_SEGMENT_END(E); + + // Do the split and look for more borders + mesh_line_to_destination(fr_mm_s, x_splits, y_splits); + + // Restore destination from stack + COPY(destination, end); + mesh_line_to_destination(fr_mm_s, x_splits, y_splits); + } + +#elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + + #define CELL_INDEX(A,V) ((V - bilinear_start[A##_AXIS]) * ABL_BG_FACTOR(A##_AXIS)) + + /** + * Prepare a bilinear-leveled linear move on Cartesian, + * splitting the move where it crosses grid borders. + */ + void bilinear_line_to_destination(const float fr_mm_s, uint16_t x_splits=0xFFFF, uint16_t y_splits=0xFFFF) { + // Get current and destination cells for this line + int cx1 = CELL_INDEX(X, current_position[X_AXIS]), + cy1 = CELL_INDEX(Y, current_position[Y_AXIS]), + cx2 = CELL_INDEX(X, destination[X_AXIS]), + cy2 = CELL_INDEX(Y, destination[Y_AXIS]); + cx1 = constrain(cx1, 0, ABL_BG_POINTS_X - 2); + cy1 = constrain(cy1, 0, ABL_BG_POINTS_Y - 2); + cx2 = constrain(cx2, 0, ABL_BG_POINTS_X - 2); + cy2 = constrain(cy2, 0, ABL_BG_POINTS_Y - 2); + + // Start and end in the same cell? No split needed. + if (cx1 == cx2 && cy1 == cy2) { + buffer_line_to_destination(fr_mm_s); + set_current_from_destination(); + return; + } + + #define LINE_SEGMENT_END(A) (current_position[A ##_AXIS] + (destination[A ##_AXIS] - current_position[A ##_AXIS]) * normalized_dist) + + float normalized_dist, end[XYZE]; + const int8_t gcx = max(cx1, cx2), gcy = max(cy1, cy2); + + // Crosses on the X and not already split on this X? + // The x_splits flags are insurance against rounding errors. + if (cx2 != cx1 && TEST(x_splits, gcx)) { + // Split on the X grid line + CBI(x_splits, gcx); + COPY(end, destination); + destination[X_AXIS] = bilinear_start[X_AXIS] + ABL_BG_SPACING(X_AXIS) * gcx; + normalized_dist = (destination[X_AXIS] - current_position[X_AXIS]) / (end[X_AXIS] - current_position[X_AXIS]); + destination[Y_AXIS] = LINE_SEGMENT_END(Y); + } + // Crosses on the Y and not already split on this Y? + else if (cy2 != cy1 && TEST(y_splits, gcy)) { + // Split on the Y grid line + CBI(y_splits, gcy); + COPY(end, destination); + destination[Y_AXIS] = bilinear_start[Y_AXIS] + ABL_BG_SPACING(Y_AXIS) * gcy; + normalized_dist = (destination[Y_AXIS] - current_position[Y_AXIS]) / (end[Y_AXIS] - current_position[Y_AXIS]); + destination[X_AXIS] = LINE_SEGMENT_END(X); + } + else { + // Must already have been split on these border(s) + buffer_line_to_destination(fr_mm_s); + set_current_from_destination(); + return; + } + + destination[Z_AXIS] = LINE_SEGMENT_END(Z); + destination[E_AXIS] = LINE_SEGMENT_END(E); + + // Do the split and look for more borders + bilinear_line_to_destination(fr_mm_s, x_splits, y_splits); + + // Restore destination from stack + COPY(destination, end); + bilinear_line_to_destination(fr_mm_s, x_splits, y_splits); + } + +#endif // AUTO_BED_LEVELING_BILINEAR +#endif // IS_CARTESIAN + +#if !UBL_SEGMENTED +#if IS_KINEMATIC + + /** + * Prepare a linear move in a DELTA or SCARA setup. + * + * This calls planner.buffer_line several times, adding + * small incremental moves for DELTA or SCARA. + * + * For Unified Bed Leveling (Delta or Segmented Cartesian) + * the ubl.prepare_segmented_line_to method replaces this. + */ + inline bool prepare_kinematic_move_to(const float (&rtarget)[XYZE]) { + + // Get the top feedrate of the move in the XY plane + const float _feedrate_mm_s = MMS_SCALED(feedrate_mm_s); + + const float xdiff = rtarget[X_AXIS] - current_position[X_AXIS], + ydiff = rtarget[Y_AXIS] - current_position[Y_AXIS]; + + // If the move is only in Z/E don't split up the move + if (!xdiff && !ydiff) { + planner.buffer_line_kinematic(rtarget, _feedrate_mm_s, active_extruder); + return false; // caller will update current_position + } + + // Fail if attempting move outside printable radius + if (!position_is_reachable(rtarget[X_AXIS], rtarget[Y_AXIS])) return true; + + // Remaining cartesian distances + const float zdiff = rtarget[Z_AXIS] - current_position[Z_AXIS], + ediff = rtarget[E_AXIS] - current_position[E_AXIS]; + + // Get the linear distance in XYZ + // If the move is very short, check the E move distance + // No E move either? Game over. + float cartesian_mm = SQRT(sq(xdiff) + sq(ydiff) + sq(zdiff)); + if (UNEAR_ZERO(cartesian_mm)) cartesian_mm = FABS(ediff); + if (UNEAR_ZERO(cartesian_mm)) return true; + + // Minimum number of seconds to move the given distance + const float seconds = cartesian_mm / _feedrate_mm_s; + + // The number of segments-per-second times the duration + // gives the number of segments + uint16_t segments = delta_segments_per_second * seconds; + + // For SCARA minimum segment size is 0.25mm + #if IS_SCARA + NOMORE(segments, cartesian_mm * 4); + #endif + + // At least one segment is required + NOLESS(segments, 1); + + // The approximate length of each segment + const float inv_segments = 1.0 / float(segments), + cartesian_segment_mm = cartesian_mm * inv_segments, + segment_distance[XYZE] = { + xdiff * inv_segments, + ydiff * inv_segments, + zdiff * inv_segments, + ediff * inv_segments + }; + + // SERIAL_ECHOPAIR("mm=", cartesian_mm); + // SERIAL_ECHOPAIR(" seconds=", seconds); + // SERIAL_ECHOLNPAIR(" segments=", segments); + // SERIAL_ECHOLNPAIR(" segment_mm=", cartesian_segment_mm); + + // Get the current position as starting point + float raw[XYZE]; + COPY(raw, current_position); + + + // Calculate and execute the segments + while (--segments) { + + static millis_t next_idle_ms = millis() + 200UL; + thermalManager.manage_heater(); // This returns immediately if not really needed. + if (ELAPSED(millis(), next_idle_ms)) { + next_idle_ms = millis() + 200UL; + idle(); + } + + LOOP_XYZE(i) raw[i] += segment_distance[i]; + #if ENABLED(DELTA) && HOTENDS < 2 + DELTA_IK(raw); // Delta can inline its kinematics + #else + inverse_kinematics(raw); + #endif + + ADJUST_DELTA(raw); // Adjust Z if bed leveling is enabled + + planner.buffer_line(delta[A_AXIS], delta[B_AXIS], delta[C_AXIS], raw[E_AXIS], _feedrate_mm_s, active_extruder, cartesian_segment_mm); + } + + // Ensure last segment arrives at target location. + planner.buffer_line_kinematic(rtarget, _feedrate_mm_s, active_extruder, cartesian_segment_mm); + + return false; // caller will update current_position + } + +#else // !IS_KINEMATIC + + /** + * Prepare a linear move in a Cartesian setup. + * + * When a mesh-based leveling system is active, moves are segmented + * according to the configuration of the leveling system. + * + * Returns true if current_position[] was set to destination[] + */ + inline bool prepare_move_to_destination_cartesian() { + #if HAS_MESH + if (planner.leveling_active && planner.leveling_active_at_z(destination[Z_AXIS])) { + #if ENABLED(AUTO_BED_LEVELING_UBL) + ubl.line_to_destination_cartesian(MMS_SCALED(feedrate_mm_s), active_extruder); // UBL's motion routine needs to know about + return true; // all moves, including Z-only moves. + #elif ENABLED(SEGMENT_LEVELED_MOVES) + segmented_line_to_destination(MMS_SCALED(feedrate_mm_s)); + return false; // caller will update current_position + #else + /** + * For MBL and ABL-BILINEAR only segment moves when X or Y are involved. + * Otherwise fall through to do a direct single move. + */ + if (current_position[X_AXIS] != destination[X_AXIS] || current_position[Y_AXIS] != destination[Y_AXIS]) { + #if ENABLED(MESH_BED_LEVELING) + mesh_line_to_destination(MMS_SCALED(feedrate_mm_s)); + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + bilinear_line_to_destination(MMS_SCALED(feedrate_mm_s)); + #endif + return true; + } + #endif + } + #endif // HAS_MESH + + buffer_line_to_destination(MMS_SCALED(feedrate_mm_s)); + return false; // caller will update current_position + } + +#endif // !IS_KINEMATIC +#endif // !UBL_SEGMENTED + +#if ENABLED(DUAL_X_CARRIAGE) + + /** + * Unpark the carriage, if needed + */ + inline bool dual_x_carriage_unpark() { + if (active_extruder_parked) + switch (dual_x_carriage_mode) { + + case DXC_FULL_CONTROL_MODE: break; + + case DXC_AUTO_PARK_MODE: + if (current_position[E_AXIS] == destination[E_AXIS]) { + // This is a travel move (with no extrusion) + // Skip it, but keep track of the current position + // (so it can be used as the start of the next non-travel move) + if (delayed_move_time != 0xFFFFFFFFUL) { + set_current_from_destination(); + NOLESS(raised_parked_position[Z_AXIS], destination[Z_AXIS]); + delayed_move_time = millis(); + return true; + } + } + // unpark extruder: 1) raise, 2) move into starting XY position, 3) lower + for (uint8_t i = 0; i < 3; i++) + planner.buffer_line( + i == 0 ? raised_parked_position[X_AXIS] : current_position[X_AXIS], + i == 0 ? raised_parked_position[Y_AXIS] : current_position[Y_AXIS], + i == 2 ? current_position[Z_AXIS] : raised_parked_position[Z_AXIS], + current_position[E_AXIS], + i == 1 ? PLANNER_XY_FEEDRATE() : planner.max_feedrate_mm_s[Z_AXIS], + active_extruder + ); + delayed_move_time = 0; + active_extruder_parked = false; + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("Clear active_extruder_parked"); + #endif + break; + + case DXC_DUPLICATION_MODE: + if (active_extruder == 0) { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPAIR("Set planner X", inactive_extruder_x_pos); + SERIAL_ECHOLNPAIR(" ... Line to X", current_position[X_AXIS] + duplicate_extruder_x_offset); + } + #endif + // move duplicate extruder into correct duplication position. + planner.set_position_mm( + inactive_extruder_x_pos, + current_position[Y_AXIS], + current_position[Z_AXIS], + current_position[E_AXIS] + ); + planner.buffer_line( + current_position[X_AXIS] + duplicate_extruder_x_offset, + current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], + planner.max_feedrate_mm_s[X_AXIS], 1 + ); + SYNC_PLAN_POSITION_KINEMATIC(); + stepper.synchronize(); + extruder_duplication_enabled = true; + active_extruder_parked = false; + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("Set extruder_duplication_enabled\nClear active_extruder_parked"); + #endif + } + else { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("Active extruder not 0"); + #endif + } + break; + } + return false; + } + +#endif // DUAL_X_CARRIAGE + +/** + * Prepare a single move and get ready for the next one + * + * This may result in several calls to planner.buffer_line to + * do smaller moves for DELTA, SCARA, mesh moves, etc. + * + * Make sure current_position[E] and destination[E] are good + * before calling or cold/lengthy extrusion may get missed. + */ +void prepare_move_to_destination() { + clamp_to_software_endstops(destination); + + #if ENABLED(PREVENT_COLD_EXTRUSION) || ENABLED(PREVENT_LENGTHY_EXTRUDE) + + if (!DEBUGGING(DRYRUN)) { + if (destination[E_AXIS] != current_position[E_AXIS]) { + #if ENABLED(PREVENT_COLD_EXTRUSION) + if (thermalManager.tooColdToExtrude(active_extruder)) { + current_position[E_AXIS] = destination[E_AXIS]; // Behave as if the move really took place, but ignore E part + SERIAL_ECHO_START(); + SERIAL_ECHOLNPGM(MSG_ERR_COLD_EXTRUDE_STOP); + } + #endif // PREVENT_COLD_EXTRUSION + #if ENABLED(PREVENT_LENGTHY_EXTRUDE) + if (FABS(destination[E_AXIS] - current_position[E_AXIS]) * planner.e_factor[active_extruder] > (EXTRUDE_MAXLENGTH)) { + current_position[E_AXIS] = destination[E_AXIS]; // Behave as if the move really took place, but ignore E part + SERIAL_ECHO_START(); + SERIAL_ECHOLNPGM(MSG_ERR_LONG_EXTRUDE_STOP); + } + #endif // PREVENT_LENGTHY_EXTRUDE + } + } + + #endif + + #if ENABLED(DUAL_X_CARRIAGE) + if (dual_x_carriage_unpark()) return; + #endif + + if ( + #if UBL_SEGMENTED + ubl.prepare_segmented_line_to(destination, MMS_SCALED(feedrate_mm_s)) + #elif IS_KINEMATIC + prepare_kinematic_move_to(destination) + #else + prepare_move_to_destination_cartesian() + #endif + ) return; + + set_current_from_destination(); +} + +#if ENABLED(ARC_SUPPORT) + + #if N_ARC_CORRECTION < 1 + #undef N_ARC_CORRECTION + #define N_ARC_CORRECTION 1 + #endif + + /** + * Plan an arc in 2 dimensions + * + * The arc is approximated by generating many small linear segments. + * The length of each segment is configured in MM_PER_ARC_SEGMENT (Default 1mm) + * Arcs should only be made relatively large (over 5mm), as larger arcs with + * larger segments will tend to be more efficient. Your slicer should have + * options for G2/G3 arc generation. In future these options may be GCode tunable. + */ + void plan_arc( + const float (&cart)[XYZE], // Destination position + const float (&offset)[2], // Center of rotation relative to current_position + const bool clockwise // Clockwise? + ) { + #if ENABLED(CNC_WORKSPACE_PLANES) + AxisEnum p_axis, q_axis, l_axis; + switch (workspace_plane) { + default: + case PLANE_XY: p_axis = X_AXIS; q_axis = Y_AXIS; l_axis = Z_AXIS; break; + case PLANE_ZX: p_axis = Z_AXIS; q_axis = X_AXIS; l_axis = Y_AXIS; break; + case PLANE_YZ: p_axis = Y_AXIS; q_axis = Z_AXIS; l_axis = X_AXIS; break; + } + #else + constexpr AxisEnum p_axis = X_AXIS, q_axis = Y_AXIS, l_axis = Z_AXIS; + #endif + + // Radius vector from center to current location + float r_P = -offset[0], r_Q = -offset[1]; + + const float radius = HYPOT(r_P, r_Q), + center_P = current_position[p_axis] - r_P, + center_Q = current_position[q_axis] - r_Q, + rt_X = cart[p_axis] - center_P, + rt_Y = cart[q_axis] - center_Q, + linear_travel = cart[l_axis] - current_position[l_axis], + extruder_travel = cart[E_AXIS] - current_position[E_AXIS]; + + // CCW angle of rotation between position and target from the circle center. Only one atan2() trig computation required. + float angular_travel = ATAN2(r_P * rt_Y - r_Q * rt_X, r_P * rt_X + r_Q * rt_Y); + if (angular_travel < 0) angular_travel += RADIANS(360); + if (clockwise) angular_travel -= RADIANS(360); + + // Make a circle if the angular rotation is 0 and the target is current position + if (angular_travel == 0 && current_position[p_axis] == cart[p_axis] && current_position[q_axis] == cart[q_axis]) + angular_travel = RADIANS(360); + + const float flat_mm = radius * angular_travel, + mm_of_travel = linear_travel ? HYPOT(flat_mm, linear_travel) : FABS(flat_mm); + if (mm_of_travel < 0.001) return; + + uint16_t segments = FLOOR(mm_of_travel / (MM_PER_ARC_SEGMENT)); + NOLESS(segments, 1); + + /** + * Vector rotation by transformation matrix: r is the original vector, r_T is the rotated vector, + * and phi is the angle of rotation. Based on the solution approach by Jens Geisler. + * r_T = [cos(phi) -sin(phi); + * sin(phi) cos(phi)] * r ; + * + * For arc generation, the center of the circle is the axis of rotation and the radius vector is + * defined from the circle center to the initial position. Each line segment is formed by successive + * vector rotations. This requires only two cos() and sin() computations to form the rotation + * matrix for the duration of the entire arc. Error may accumulate from numerical round-off, since + * all double numbers are single precision on the Arduino. (True double precision will not have + * round off issues for CNC applications.) Single precision error can accumulate to be greater than + * tool precision in some cases. Therefore, arc path correction is implemented. + * + * Small angle approximation may be used to reduce computation overhead further. This approximation + * holds for everything, but very small circles and large MM_PER_ARC_SEGMENT values. In other words, + * theta_per_segment would need to be greater than 0.1 rad and N_ARC_CORRECTION would need to be large + * to cause an appreciable drift error. N_ARC_CORRECTION~=25 is more than small enough to correct for + * numerical drift error. N_ARC_CORRECTION may be on the order a hundred(s) before error becomes an + * issue for CNC machines with the single precision Arduino calculations. + * + * This approximation also allows plan_arc to immediately insert a line segment into the planner + * without the initial overhead of computing cos() or sin(). By the time the arc needs to be applied + * a correction, the planner should have caught up to the lag caused by the initial plan_arc overhead. + * This is important when there are successive arc motions. + */ + // Vector rotation matrix values + float raw[XYZE]; + const float theta_per_segment = angular_travel / segments, + linear_per_segment = linear_travel / segments, + extruder_per_segment = extruder_travel / segments, + sin_T = theta_per_segment, + cos_T = 1 - 0.5 * sq(theta_per_segment); // Small angle approximation + + // Initialize the linear axis + raw[l_axis] = current_position[l_axis]; + + // Initialize the extruder axis + raw[E_AXIS] = current_position[E_AXIS]; + + const float fr_mm_s = MMS_SCALED(feedrate_mm_s); + + millis_t next_idle_ms = millis() + 200UL; + + #if N_ARC_CORRECTION > 1 + int8_t arc_recalc_count = N_ARC_CORRECTION; + #endif + + for (uint16_t i = 1; i < segments; i++) { // Iterate (segments-1) times + + thermalManager.manage_heater(); + if (ELAPSED(millis(), next_idle_ms)) { + next_idle_ms = millis() + 200UL; + idle(); + } + + #if N_ARC_CORRECTION > 1 + if (--arc_recalc_count) { + // Apply vector rotation matrix to previous r_P / 1 + const float r_new_Y = r_P * sin_T + r_Q * cos_T; + r_P = r_P * cos_T - r_Q * sin_T; + r_Q = r_new_Y; + } + else + #endif + { + #if N_ARC_CORRECTION > 1 + arc_recalc_count = N_ARC_CORRECTION; + #endif + + // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. + // Compute exact location by applying transformation matrix from initial radius vector(=-offset). + // To reduce stuttering, the sin and cos could be computed at different times. + // For now, compute both at the same time. + const float cos_Ti = cos(i * theta_per_segment), sin_Ti = sin(i * theta_per_segment); + r_P = -offset[0] * cos_Ti + offset[1] * sin_Ti; + r_Q = -offset[0] * sin_Ti - offset[1] * cos_Ti; + } + + // Update raw location + raw[p_axis] = center_P + r_P; + raw[q_axis] = center_Q + r_Q; + raw[l_axis] += linear_per_segment; + raw[E_AXIS] += extruder_per_segment; + + clamp_to_software_endstops(raw); + + planner.buffer_line_kinematic(raw, fr_mm_s, active_extruder); + } + + // Ensure last segment arrives at target location. + planner.buffer_line_kinematic(cart, fr_mm_s, active_extruder); + + // As far as the parser is concerned, the position is now == target. In reality the + // motion control system might still be processing the action and the real tool position + // in any intermediate location. + set_current_from_destination(); + } // plan_arc + +#endif // ARC_SUPPORT + +#if ENABLED(BEZIER_CURVE_SUPPORT) + + void plan_cubic_move(const float (&offset)[4]) { + cubic_b_spline(current_position, destination, offset, MMS_SCALED(feedrate_mm_s), active_extruder); + + // As far as the parser is concerned, the position is now == destination. In reality the + // motion control system might still be processing the action and the real tool position + // in any intermediate location. + set_current_from_destination(); + } + +#endif // BEZIER_CURVE_SUPPORT + +#if ENABLED(USE_CONTROLLER_FAN) + + void controllerFan() { + static millis_t lastMotorOn = 0, // Last time a motor was turned on + nextMotorCheck = 0; // Last time the state was checked + const millis_t ms = millis(); + if (ELAPSED(ms, nextMotorCheck)) { + nextMotorCheck = ms + 2500UL; // Not a time critical function, so only check every 2.5s + if (X_ENABLE_READ == X_ENABLE_ON || Y_ENABLE_READ == Y_ENABLE_ON || Z_ENABLE_READ == Z_ENABLE_ON || thermalManager.soft_pwm_amount_bed > 0 + || E0_ENABLE_READ == E_ENABLE_ON // If any of the drivers are enabled... + #if E_STEPPERS > 1 + || E1_ENABLE_READ == E_ENABLE_ON + #if HAS_X2_ENABLE + || X2_ENABLE_READ == X_ENABLE_ON + #endif + #if E_STEPPERS > 2 + || E2_ENABLE_READ == E_ENABLE_ON + #if E_STEPPERS > 3 + || E3_ENABLE_READ == E_ENABLE_ON + #if E_STEPPERS > 4 + || E4_ENABLE_READ == E_ENABLE_ON + #endif // E_STEPPERS > 4 + #endif // E_STEPPERS > 3 + #endif // E_STEPPERS > 2 + #endif // E_STEPPERS > 1 + ) { + lastMotorOn = ms; //... set time to NOW so the fan will turn on + } + + // Fan off if no steppers have been enabled for CONTROLLERFAN_SECS seconds + const uint8_t speed = (lastMotorOn && PENDING(ms, lastMotorOn + (CONTROLLERFAN_SECS) * 1000UL)) ? CONTROLLERFAN_SPEED : 0; + controllerFanSpeed = speed; + + // allows digital or PWM fan output to be used (see M42 handling) + WRITE(CONTROLLER_FAN_PIN, speed); + analogWrite(CONTROLLER_FAN_PIN, speed); + } + } + +#endif // USE_CONTROLLER_FAN + +#if ENABLED(MORGAN_SCARA) + + /** + * Morgan SCARA Forward Kinematics. Results in cartes[]. + * Maths and first version by QHARLEY. + * Integrated into Marlin and slightly restructured by Joachim Cerny. + */ + void forward_kinematics_SCARA(const float &a, const float &b) { + + float a_sin = sin(RADIANS(a)) * L1, + a_cos = cos(RADIANS(a)) * L1, + b_sin = sin(RADIANS(b)) * L2, + b_cos = cos(RADIANS(b)) * L2; + + cartes[X_AXIS] = a_cos + b_cos + SCARA_OFFSET_X; //theta + cartes[Y_AXIS] = a_sin + b_sin + SCARA_OFFSET_Y; //theta+phi + + /* + SERIAL_ECHOPAIR("SCARA FK Angle a=", a); + SERIAL_ECHOPAIR(" b=", b); + SERIAL_ECHOPAIR(" a_sin=", a_sin); + SERIAL_ECHOPAIR(" a_cos=", a_cos); + SERIAL_ECHOPAIR(" b_sin=", b_sin); + SERIAL_ECHOLNPAIR(" b_cos=", b_cos); + SERIAL_ECHOPAIR(" cartes[X_AXIS]=", cartes[X_AXIS]); + SERIAL_ECHOLNPAIR(" cartes[Y_AXIS]=", cartes[Y_AXIS]); + //*/ + } + + /** + * Morgan SCARA Inverse Kinematics. Results in delta[]. + * + * See http://forums.reprap.org/read.php?185,283327 + * + * Maths and first version by QHARLEY. + * Integrated into Marlin and slightly restructured by Joachim Cerny. + */ + void inverse_kinematics(const float raw[XYZ]) { + + static float C2, S2, SK1, SK2, THETA, PSI; + + float sx = raw[X_AXIS] - SCARA_OFFSET_X, // Translate SCARA to standard X Y + sy = raw[Y_AXIS] - SCARA_OFFSET_Y; // With scaling factor. + + if (L1 == L2) + C2 = HYPOT2(sx, sy) / L1_2_2 - 1; + else + C2 = (HYPOT2(sx, sy) - (L1_2 + L2_2)) / (2.0 * L1 * L2); + + S2 = SQRT(1 - sq(C2)); + + // Unrotated Arm1 plus rotated Arm2 gives the distance from Center to End + SK1 = L1 + L2 * C2; + + // Rotated Arm2 gives the distance from Arm1 to Arm2 + SK2 = L2 * S2; + + // Angle of Arm1 is the difference between Center-to-End angle and the Center-to-Elbow + THETA = ATAN2(SK1, SK2) - ATAN2(sx, sy); + + // Angle of Arm2 + PSI = ATAN2(S2, C2); + + delta[A_AXIS] = DEGREES(THETA); // theta is support arm angle + delta[B_AXIS] = DEGREES(THETA + PSI); // equal to sub arm angle (inverted motor) + delta[C_AXIS] = raw[Z_AXIS]; + + /* + DEBUG_POS("SCARA IK", raw); + DEBUG_POS("SCARA IK", delta); + SERIAL_ECHOPAIR(" SCARA (x,y) ", sx); + SERIAL_ECHOPAIR(",", sy); + SERIAL_ECHOPAIR(" C2=", C2); + SERIAL_ECHOPAIR(" S2=", S2); + SERIAL_ECHOPAIR(" Theta=", THETA); + SERIAL_ECHOLNPAIR(" Phi=", PHI); + //*/ + } + +#endif // MORGAN_SCARA + +#if ENABLED(TEMP_STAT_LEDS) + + static bool red_led = false; + static millis_t next_status_led_update_ms = 0; + + void handle_status_leds(void) { + if (ELAPSED(millis(), next_status_led_update_ms)) { + next_status_led_update_ms += 500; // Update every 0.5s + float max_temp = 0.0; + #if HAS_TEMP_BED + max_temp = MAX3(max_temp, thermalManager.degTargetBed(), thermalManager.degBed()); + #endif + HOTEND_LOOP() + max_temp = MAX3(max_temp, thermalManager.degHotend(e), thermalManager.degTargetHotend(e)); + const bool new_led = (max_temp > 55.0) ? true : (max_temp < 54.0) ? false : red_led; + if (new_led != red_led) { + red_led = new_led; + #if PIN_EXISTS(STAT_LED_RED) + WRITE(STAT_LED_RED_PIN, new_led ? HIGH : LOW); + #if PIN_EXISTS(STAT_LED_BLUE) + WRITE(STAT_LED_BLUE_PIN, new_led ? LOW : HIGH); + #endif + #else + WRITE(STAT_LED_BLUE_PIN, new_led ? HIGH : LOW); + #endif + } + } + } + +#endif + +void enable_all_steppers() { + #if ENABLED(AUTO_POWER_CONTROL) + powerManager.power_on(); + #endif + enable_X(); + enable_Y(); + enable_Z(); + enable_E0(); + enable_E1(); + enable_E2(); + enable_E3(); + enable_E4(); +} + +void disable_e_stepper(const uint8_t e) { + switch (e) { + case 0: disable_E0(); break; + case 1: disable_E1(); break; + case 2: disable_E2(); break; + case 3: disable_E3(); break; + case 4: disable_E4(); break; + } +} + +void disable_e_steppers() { + disable_E0(); + disable_E1(); + disable_E2(); + disable_E3(); + disable_E4(); +} + +void disable_all_steppers() { + disable_X(); + disable_Y(); + disable_Z(); + disable_e_steppers(); +} + +/** + * Manage several activities: + * - Check for Filament Runout + * - Keep the command buffer full + * - Check for maximum inactive time between commands + * - Check for maximum inactive time between stepper commands + * - Check if pin CHDK needs to go LOW + * - Check for KILL button held down + * - Check for HOME button held down + * - Check if cooling fan needs to be switched on + * - Check if an idle but hot extruder needs filament extruded (EXTRUDER_RUNOUT_PREVENT) + */ +void manage_inactivity(bool ignore_stepper_queue/*=false*/) { + + #if ENABLED(FILAMENT_RUNOUT_SENSOR) + runout.run(); + #endif + + if (commands_in_queue < BUFSIZE) get_available_commands(); + + const millis_t ms = millis(); + + if (max_inactive_time && ELAPSED(ms, previous_move_ms + max_inactive_time)) { + SERIAL_ERROR_START(); + SERIAL_ECHOLNPAIR(MSG_KILL_INACTIVE_TIME, parser.command_ptr); + kill(PSTR(MSG_KILLED)); + } + + // Prevent steppers timing-out in the middle of M600 + #if ENABLED(ADVANCED_PAUSE_FEATURE) && ENABLED(PAUSE_PARK_NO_STEPPER_TIMEOUT) + #define MOVE_AWAY_TEST !did_pause_print + #else + #define MOVE_AWAY_TEST true + #endif + + if (stepper_inactive_time) { + if (planner.has_blocks_queued()) + previous_move_ms = ms; // reset_stepper_timeout to keep steppers powered + else if (MOVE_AWAY_TEST && !ignore_stepper_queue && ELAPSED(ms, previous_move_ms + stepper_inactive_time)) { + #if ENABLED(DISABLE_INACTIVE_X) + disable_X(); + #endif + #if ENABLED(DISABLE_INACTIVE_Y) + disable_Y(); + #endif + #if ENABLED(DISABLE_INACTIVE_Z) + disable_Z(); + #endif + #if ENABLED(DISABLE_INACTIVE_E) + disable_e_steppers(); + #endif + #if ENABLED(AUTO_BED_LEVELING_UBL) && ENABLED(ULTIPANEL) // Only needed with an LCD + if (ubl.lcd_map_control) ubl.lcd_map_control = defer_return_to_status = false; + #endif + } + } + + #ifdef CHDK // Check if pin should be set to LOW after M240 set it to HIGH + if (chdkActive && ELAPSED(ms, chdkHigh + CHDK_DELAY)) { + chdkActive = false; + WRITE(CHDK, LOW); + } + #endif + + #if HAS_KILL + + // Check if the kill button was pressed and wait just in case it was an accidental + // key kill key press + // ------------------------------------------------------------------------------- + static int killCount = 0; // make the inactivity button a bit less responsive + const int KILL_DELAY = 750; + if (!READ(KILL_PIN)) + killCount++; + else if (killCount > 0) + killCount--; + + // Exceeded threshold and we can confirm that it was not accidental + // KILL the machine + // ---------------------------------------------------------------- + if (killCount >= KILL_DELAY) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_KILL_BUTTON); + kill(PSTR(MSG_KILLED)); + } + #endif + + #if HAS_HOME + // Check to see if we have to home, use poor man's debouncer + // --------------------------------------------------------- + static int homeDebounceCount = 0; // poor man's debouncing count + const int HOME_DEBOUNCE_DELAY = 2500; + if (!IS_SD_PRINTING && !READ(HOME_PIN)) { + if (!homeDebounceCount) { + enqueue_and_echo_commands_P(PSTR("G28")); + LCD_MESSAGEPGM(MSG_AUTO_HOME); + } + if (homeDebounceCount < HOME_DEBOUNCE_DELAY) + homeDebounceCount++; + else + homeDebounceCount = 0; + } + #endif + + #if ENABLED(USE_CONTROLLER_FAN) + controllerFan(); // Check if fan should be turned on to cool stepper drivers down + #endif + + #if ENABLED(AUTO_POWER_CONTROL) + powerManager.check(); + #endif + + #if ENABLED(EXTRUDER_RUNOUT_PREVENT) + if (thermalManager.degHotend(active_extruder) > EXTRUDER_RUNOUT_MINTEMP + && ELAPSED(ms, previous_move_ms + (EXTRUDER_RUNOUT_SECONDS) * 1000UL) + && !planner.has_blocks_queued() + ) { + #if ENABLED(SWITCHING_EXTRUDER) + const bool oldstatus = E0_ENABLE_READ; + enable_E0(); + #else // !SWITCHING_EXTRUDER + bool oldstatus; + switch (active_extruder) { + default: oldstatus = E0_ENABLE_READ; enable_E0(); break; + #if E_STEPPERS > 1 + case 1: oldstatus = E1_ENABLE_READ; enable_E1(); break; + #if E_STEPPERS > 2 + case 2: oldstatus = E2_ENABLE_READ; enable_E2(); break; + #if E_STEPPERS > 3 + case 3: oldstatus = E3_ENABLE_READ; enable_E3(); break; + #if E_STEPPERS > 4 + case 4: oldstatus = E4_ENABLE_READ; enable_E4(); break; + #endif // E_STEPPERS > 4 + #endif // E_STEPPERS > 3 + #endif // E_STEPPERS > 2 + #endif // E_STEPPERS > 1 + } + #endif // !SWITCHING_EXTRUDER + + const float olde = current_position[E_AXIS]; + current_position[E_AXIS] += EXTRUDER_RUNOUT_EXTRUDE; + planner.buffer_line_kinematic(current_position, MMM_TO_MMS(EXTRUDER_RUNOUT_SPEED), active_extruder); + current_position[E_AXIS] = olde; + planner.set_e_position_mm(olde); + stepper.synchronize(); + #if ENABLED(SWITCHING_EXTRUDER) + E0_ENABLE_WRITE(oldstatus); + #else + switch (active_extruder) { + case 0: E0_ENABLE_WRITE(oldstatus); break; + #if E_STEPPERS > 1 + case 1: E1_ENABLE_WRITE(oldstatus); break; + #if E_STEPPERS > 2 + case 2: E2_ENABLE_WRITE(oldstatus); break; + #if E_STEPPERS > 3 + case 3: E3_ENABLE_WRITE(oldstatus); break; + #if E_STEPPERS > 4 + case 4: E4_ENABLE_WRITE(oldstatus); break; + #endif // E_STEPPERS > 4 + #endif // E_STEPPERS > 3 + #endif // E_STEPPERS > 2 + #endif // E_STEPPERS > 1 + } + #endif // !SWITCHING_EXTRUDER + + previous_move_ms = ms; // reset_stepper_timeout to keep steppers powered + } + #endif // EXTRUDER_RUNOUT_PREVENT + + #if ENABLED(DUAL_X_CARRIAGE) + // handle delayed move timeout + if (delayed_move_time && ELAPSED(ms, delayed_move_time + 1000UL) && IsRunning()) { + // travel moves have been received so enact them + delayed_move_time = 0xFFFFFFFFUL; // force moves to be done + set_destination_from_current(); + prepare_move_to_destination(); + } + #endif + + #if ENABLED(TEMP_STAT_LEDS) + handle_status_leds(); + #endif + + #if ENABLED(MONITOR_DRIVER_STATUS) + monitor_tmc_driver(); + #endif + + planner.check_axes_activity(); +} + +/** + * Standard idle routine keeps the machine alive + */ +void idle( + #if ENABLED(ADVANCED_PAUSE_FEATURE) + bool no_stepper_sleep/*=false*/ + #endif +) { + #if ENABLED(MAX7219_DEBUG) + Max7219_idle_tasks(); + #endif // MAX7219_DEBUG + + lcd_update(); + + host_keepalive(); + + manage_inactivity( + #if ENABLED(ADVANCED_PAUSE_FEATURE) + no_stepper_sleep + #endif + ); + + thermalManager.manage_heater(); + + #if ENABLED(PRINTCOUNTER) + print_job_timer.tick(); + #endif + + #if HAS_BUZZER && DISABLED(LCD_USE_I2C_BUZZER) + buzzer.tick(); + #endif + + #if ENABLED(I2C_POSITION_ENCODERS) + static millis_t i2cpem_next_update_ms; + if (planner.has_blocks_queued() && ELAPSED(millis(), i2cpem_next_update_ms)) { + I2CPEM.update(); + i2cpem_next_update_ms = millis() + I2CPE_MIN_UPD_TIME_MS; + } + #endif + + #if HAS_AUTO_REPORTING + if (!suspend_auto_report) { + #if ENABLED(AUTO_REPORT_TEMPERATURES) + thermalManager.auto_report_temperatures(); + #endif + #if ENABLED(AUTO_REPORT_SD_STATUS) + card.auto_report_sd_status(); + #endif + } + #endif +} + +/** + * Kill all activity and lock the machine. + * After this the machine will need to be reset. + */ +void kill(const char* lcd_msg) { + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_KILLED); + + thermalManager.disable_all_heaters(); + disable_all_steppers(); + + #if ENABLED(ULTRA_LCD) + kill_screen(lcd_msg); + #else + UNUSED(lcd_msg); + #endif + + _delay_ms(600); // Wait a short time (allows messages to get out before shutting down. + cli(); // Stop interrupts + + _delay_ms(250); //Wait to ensure all interrupts routines stopped + thermalManager.disable_all_heaters(); //turn off heaters again + + #ifdef ACTION_ON_KILL + SERIAL_ECHOLNPGM("//action:" ACTION_ON_KILL); + #endif + + #if HAS_POWER_SWITCH + PSU_OFF(); + #endif + + suicide(); + while (1) { + #if ENABLED(USE_WATCHDOG) + watchdog_reset(); + #endif + } // Wait for reset +} + +/** + * Turn off heaters and stop the print in progress + * After a stop the machine may be resumed with M999 + */ +void stop() { + thermalManager.disable_all_heaters(); // 'unpause' taken care of in here + + #if ENABLED(PROBING_FANS_OFF) + if (fans_paused) fans_pause(false); // put things back the way they were + #endif + + if (IsRunning()) { + Stopped_gcode_LastN = gcode_LastN; // Save last g_code for restart + SERIAL_ERROR_START(); + SERIAL_ERRORLNPGM(MSG_ERR_STOPPED); + LCD_MESSAGEPGM(MSG_STOPPED); + safe_delay(350); // allow enough time for messages to get out before stopping + Running = false; + } +} + +/** + * Marlin entry-point: Set up before the program loop + * - Set up the kill pin, filament runout, power hold + * - Start the serial port + * - Print startup messages and diagnostics + * - Get EEPROM or default settings + * - Initialize managers for: + * • temperature + * • planner + * • watchdog + * • stepper + * • photo pin + * • servos + * • LCD controller + * • Digipot I2C + * • Z probe sled + * • status LEDs + */ +void setup() { + + #if ENABLED(MAX7219_DEBUG) + Max7219_init(); + #endif + + #if ENABLED(DISABLE_JTAG) + // Disable JTAG on AT90USB chips to free up pins for IO + MCUCR = 0x80; + MCUCR = 0x80; + #endif + + #if ENABLED(FILAMENT_RUNOUT_SENSOR) + runout.setup(); + #endif + + setup_killpin(); + + setup_powerhold(); + + #if HAS_STEPPER_RESET + disableStepperDrivers(); + #endif + + MYSERIAL0.begin(BAUDRATE); + SERIAL_PROTOCOLLNPGM("start"); + SERIAL_ECHO_START(); + + // Prepare communication for TMC drivers + #if ENABLED(HAVE_TMC2130) + tmc_init_cs_pins(); + #endif + #if ENABLED(HAVE_TMC2208) + tmc2208_serial_begin(); + #endif + + // Check startup - does nothing if bootloader sets MCUSR to 0 + byte mcu = MCUSR; + if (mcu & 1) SERIAL_ECHOLNPGM(MSG_POWERUP); + if (mcu & 2) SERIAL_ECHOLNPGM(MSG_EXTERNAL_RESET); + if (mcu & 4) SERIAL_ECHOLNPGM(MSG_BROWNOUT_RESET); + if (mcu & 8) SERIAL_ECHOLNPGM(MSG_WATCHDOG_RESET); + if (mcu & 32) SERIAL_ECHOLNPGM(MSG_SOFTWARE_RESET); + MCUSR = 0; + + SERIAL_ECHOPGM(MSG_MARLIN); + SERIAL_CHAR(' '); + SERIAL_ECHOLNPGM(SHORT_BUILD_VERSION); + SERIAL_EOL(); + + #if defined(STRING_DISTRIBUTION_DATE) && defined(STRING_CONFIG_H_AUTHOR) + SERIAL_ECHO_START(); + SERIAL_ECHOPGM(MSG_CONFIGURATION_VER); + SERIAL_ECHOPGM(STRING_DISTRIBUTION_DATE); + SERIAL_ECHOLNPGM(MSG_AUTHOR STRING_CONFIG_H_AUTHOR); + SERIAL_ECHO_START(); + SERIAL_ECHOLNPGM("Compiled: " __DATE__); + #endif + + SERIAL_ECHO_START(); + SERIAL_ECHOPAIR(MSG_FREE_MEMORY, freeMemory()); + SERIAL_ECHOLNPAIR(MSG_PLANNER_BUFFER_BYTES, (int)sizeof(block_t)*BLOCK_BUFFER_SIZE); + + // Send "ok" after commands by default + for (int8_t i = 0; i < BUFSIZE; i++) send_ok[i] = true; + + // Load data from EEPROM if available (or use defaults) + // This also updates variables in the planner, elsewhere + (void)settings.load(); + + #if HAS_M206_COMMAND + // Initialize current position based on home_offset + COPY(current_position, home_offset); + #else + ZERO(current_position); + #endif + + // Vital to init stepper/planner equivalent for current_position + SYNC_PLAN_POSITION_KINEMATIC(); + + thermalManager.init(); // Initialize temperature loop + + print_job_timer.init(); // Initial setup of print job timer + + stepper.init(); // Initialize stepper, this enables interrupts! + + servo_init(); // Initialize all servos, stow servo probe + + #if HAS_PHOTOGRAPH + OUT_WRITE(PHOTOGRAPH_PIN, LOW); + #endif + + #if HAS_CASE_LIGHT + case_light_on = CASE_LIGHT_DEFAULT_ON; + case_light_brightness = CASE_LIGHT_DEFAULT_BRIGHTNESS; + update_case_light(); + #endif + + #if ENABLED(SPINDLE_LASER_ENABLE) + OUT_WRITE(SPINDLE_LASER_ENABLE_PIN, !SPINDLE_LASER_ENABLE_INVERT); // init spindle to off + #if SPINDLE_DIR_CHANGE + OUT_WRITE(SPINDLE_DIR_PIN, SPINDLE_INVERT_DIR ? 255 : 0); // init rotation to clockwise (M3) + #endif + #if ENABLED(SPINDLE_LASER_PWM) + SET_OUTPUT(SPINDLE_LASER_PWM_PIN); + analogWrite(SPINDLE_LASER_PWM_PIN, SPINDLE_LASER_PWM_INVERT ? 255 : 0); // set to lowest speed + #endif + #endif + + #if HAS_BED_PROBE + endstops.enable_z_probe(false); + #endif + + #if ENABLED(USE_CONTROLLER_FAN) + SET_OUTPUT(CONTROLLER_FAN_PIN); //Set pin used for driver cooling fan + #endif + + #if HAS_STEPPER_RESET + enableStepperDrivers(); + #endif + + #if ENABLED(DIGIPOT_I2C) + digipot_i2c_init(); + #endif + + #if ENABLED(DAC_STEPPER_CURRENT) + dac_init(); + #endif + + #if (ENABLED(Z_PROBE_SLED) || ENABLED(SOLENOID_PROBE)) && HAS_SOLENOID_1 + OUT_WRITE(SOL1_PIN, LOW); // turn it off + #endif + + #if HAS_HOME + SET_INPUT_PULLUP(HOME_PIN); + #endif + + #if PIN_EXISTS(STAT_LED_RED) + OUT_WRITE(STAT_LED_RED_PIN, LOW); // turn it off + #endif + + #if PIN_EXISTS(STAT_LED_BLUE) + OUT_WRITE(STAT_LED_BLUE_PIN, LOW); // turn it off + #endif + + #if HAS_COLOR_LEDS + leds.setup(); + #endif + + #if ENABLED(RGB_LED) || ENABLED(RGBW_LED) + SET_OUTPUT(RGB_LED_R_PIN); + SET_OUTPUT(RGB_LED_G_PIN); + SET_OUTPUT(RGB_LED_B_PIN); + #if ENABLED(RGBW_LED) + SET_OUTPUT(RGB_LED_W_PIN); + #endif + #endif + + #if ENABLED(MK2_MULTIPLEXER) + SET_OUTPUT(E_MUX0_PIN); + SET_OUTPUT(E_MUX1_PIN); + SET_OUTPUT(E_MUX2_PIN); + #endif + + #if HAS_FANMUX + fanmux_init(); + #endif + + lcd_init(); + LCD_MESSAGEPGM(WELCOME_MSG); + + #if ENABLED(SHOW_BOOTSCREEN) + lcd_bootscreen(); + #endif + + #if ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1 + // Virtual Tools 0, 1, 2, 3 = Filament 1, 2, 3, 4, etc. + for (uint8_t t = 0; t < MIXING_VIRTUAL_TOOLS && t < MIXING_STEPPERS; t++) + for (uint8_t i = 0; i < MIXING_STEPPERS; i++) + mixing_virtual_tool_mix[t][i] = (t == i) ? 1.0 : 0.0; + + // Remaining virtual tools are 100% filament 1 + #if MIXING_STEPPERS < MIXING_VIRTUAL_TOOLS + for (uint8_t t = MIXING_STEPPERS; t < MIXING_VIRTUAL_TOOLS; t++) + for (uint8_t i = 0; i < MIXING_STEPPERS; i++) + mixing_virtual_tool_mix[t][i] = (i == 0) ? 1.0 : 0.0; + #endif + + // Initialize mixing to tool 0 color + for (uint8_t i = 0; i < MIXING_STEPPERS; i++) + mixing_factor[i] = mixing_virtual_tool_mix[0][i]; + #endif + + #if ENABLED(BLTOUCH) + // Make sure any BLTouch error condition is cleared + bltouch_command(BLTOUCH_RESET); + set_bltouch_deployed(true); + set_bltouch_deployed(false); + #endif + + #if ENABLED(I2C_POSITION_ENCODERS) + I2CPEM.init(); + #endif + + #if ENABLED(EXPERIMENTAL_I2CBUS) && I2C_SLAVE_ADDRESS > 0 + i2c.onReceive(i2c_on_receive); + i2c.onRequest(i2c_on_request); + #endif + + #if ENABLED(ENDSTOP_INTERRUPTS_FEATURE) + setup_endstop_interrupts(); + #endif + + #if ENABLED(SWITCHING_EXTRUDER) && !DONT_SWITCH + move_extruder_servo(0); // Initialize extruder servo + #endif + + #if ENABLED(SWITCHING_NOZZLE) + move_nozzle_servo(0); // Initialize nozzle servo + #endif + + #if ENABLED(PARKING_EXTRUDER) + #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT) + pe_activate_magnet(0); + pe_activate_magnet(1); + #else + pe_deactivate_magnet(0); + pe_deactivate_magnet(1); + #endif + #endif + + #if ENABLED(USE_WATCHDOG) + watchdog_init(); + #endif +} + +/** + * The main Marlin program loop + * + * - Save or log commands to SD + * - Process available commands (if not saving) + * - Call heater manager + * - Call inactivity manager + * - Call endstop manager + * - Call LCD update + */ +void loop() { + if (commands_in_queue < BUFSIZE) get_available_commands(); + + #if ENABLED(SDSUPPORT) + card.checkautostart(false); + #endif + + if (commands_in_queue) { + + #if ENABLED(SDSUPPORT) + + if (card.saving) { + char* command = command_queue[cmd_queue_index_r]; + if (strstr_P(command, PSTR("M29"))) { + // M29 closes the file + card.closefile(); + SERIAL_PROTOCOLLNPGM(MSG_FILE_SAVED); + + #if !(defined(__AVR__) && defined(USBCON)) + #if ENABLED(SERIAL_STATS_DROPPED_RX) + SERIAL_ECHOLNPAIR("Dropped bytes: ", customizedSerial.dropped()); + #endif + + #if ENABLED(SERIAL_STATS_MAX_RX_QUEUED) + SERIAL_ECHOLNPAIR("Max RX Queue Size: ", customizedSerial.rxMaxEnqueued()); + #endif + #endif // !(__AVR__ && USBCON) + + ok_to_send(); + } + else { + // Write the string from the read buffer to SD + card.write_command(command); + if (card.logging) + process_next_command(); // The card is saving because it's logging + else + ok_to_send(); + } + } + else + process_next_command(); + + #else + + process_next_command(); + + #endif // SDSUPPORT + + // The queue may be reset by a command handler or by code invoked by idle() within a handler + if (commands_in_queue) { + --commands_in_queue; + if (++cmd_queue_index_r >= BUFSIZE) cmd_queue_index_r = 0; + } + } + endstops.report_state(); + idle(); +} diff --git a/SanityCheck.h b/SanityCheck.h new file mode 100644 index 0000000000..32b3c4c09c --- /dev/null +++ b/SanityCheck.h @@ -0,0 +1,1707 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 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 . + * + */ + +/** + * SanityCheck.h + * + * Test configuration values for errors at compile-time. + */ + +#ifndef _SANITYCHECK_H_ +#define _SANITYCHECK_H_ + +/** + * Require gcc 4.7 or newer (first included with Arduino 1.6.8) for C++11 features. + */ +#if __cplusplus < 201103L + #error "Marlin requires C++11 support (gcc >= 4.7, Arduino IDE >= 1.6.8). Please upgrade your toolchain." +#endif + +/** + * We try our best to include sanity checks for all changed configuration + * directives because users have a tendency to use outdated config files with + * the bleeding-edge source code, but sometimes this is not enough. This check + * forces a minimum config file revision. Otherwise Marlin will not build. + */ +#if !defined(CONFIGURATION_H_VERSION) || CONFIGURATION_H_VERSION < REQUIRED_CONFIGURATION_H_VERSION + #error "You are using an old Configuration.h file, update it before building Marlin." +#endif + +#if !defined(CONFIGURATION_ADV_H_VERSION) || CONFIGURATION_ADV_H_VERSION < REQUIRED_CONFIGURATION_ADV_H_VERSION + #error "You are using an old Configuration_adv.h file, update it before building Marlin." +#endif + +/** + * Warnings for old configurations + */ +#if !defined(X_BED_SIZE) || !defined(Y_BED_SIZE) + #error "X_BED_SIZE and Y_BED_SIZE are now required! Please update your configuration." +#elif WATCH_TEMP_PERIOD > 500 + #error "WATCH_TEMP_PERIOD now uses seconds instead of milliseconds." +#elif DISABLED(THERMAL_PROTECTION_HOTENDS) && (defined(WATCH_TEMP_PERIOD) || defined(THERMAL_PROTECTION_PERIOD)) + #error "Thermal Runaway Protection for hotends is now enabled with THERMAL_PROTECTION_HOTENDS." +#elif DISABLED(THERMAL_PROTECTION_BED) && defined(THERMAL_PROTECTION_BED_PERIOD) + #error "Thermal Runaway Protection for the bed is now enabled with THERMAL_PROTECTION_BED." +#elif (CORE_IS_XZ || CORE_IS_YZ) && ENABLED(Z_LATE_ENABLE) + #error "Z_LATE_ENABLE can't be used with COREXZ, COREZX, COREYZ, or COREZY." +#elif defined(X_HOME_RETRACT_MM) + #error "[XYZ]_HOME_RETRACT_MM settings have been renamed [XYZ]_HOME_BUMP_MM." +#elif defined(SDCARDDETECTINVERTED) + #error "SDCARDDETECTINVERTED is now SD_DETECT_INVERTED. Please update your configuration." +#elif defined(BTENABLED) + #error "BTENABLED is now BLUETOOTH. Please update your configuration." +#elif defined(CUSTOM_MENDEL_NAME) + #error "CUSTOM_MENDEL_NAME is now CUSTOM_MACHINE_NAME. Please update your configuration." +#elif defined(HAS_AUTOMATIC_VERSIONING) + #error "HAS_AUTOMATIC_VERSIONING is now USE_AUTOMATIC_VERSIONING. Please update your configuration." +#elif defined(SDSLOW) + #error "SDSLOW deprecated. Set SPI_SPEED to SPI_HALF_SPEED instead." +#elif defined(SDEXTRASLOW) + #error "SDEXTRASLOW deprecated. Set SPI_SPEED to SPI_QUARTER_SPEED instead." +#elif defined(FILAMENT_SENSOR) + #error "FILAMENT_SENSOR is now FILAMENT_WIDTH_SENSOR. Please update your configuration." +#elif defined(ENDSTOPPULLUP_FIL_RUNOUT) + #error "ENDSTOPPULLUP_FIL_RUNOUT is now FIL_RUNOUT_PULLUP. Please update your configuration." +#elif defined(DISABLE_MAX_ENDSTOPS) || defined(DISABLE_MIN_ENDSTOPS) + #error "DISABLE_MAX_ENDSTOPS and DISABLE_MIN_ENDSTOPS deprecated. Use individual USE_*_PLUG options instead." +#elif defined(LANGUAGE_INCLUDE) + #error "LANGUAGE_INCLUDE has been replaced by LCD_LANGUAGE. Please update your configuration." +#elif defined(EXTRUDER_OFFSET_X) || defined(EXTRUDER_OFFSET_Y) + #error "EXTRUDER_OFFSET_[XY] is deprecated. Use HOTEND_OFFSET_[XY] instead." +#elif defined(PID_PARAMS_PER_EXTRUDER) + #error "PID_PARAMS_PER_EXTRUDER is deprecated. Use PID_PARAMS_PER_HOTEND instead." +#elif defined(EXTRUDER_WATTS) || defined(BED_WATTS) + #error "EXTRUDER_WATTS and BED_WATTS are deprecated. Remove them from your configuration." +#elif defined(SERVO_ENDSTOP_ANGLES) + #error "SERVO_ENDSTOP_ANGLES is deprecated. Use Z_SERVO_ANGLES instead." +#elif defined(X_ENDSTOP_SERVO_NR) || defined(Y_ENDSTOP_SERVO_NR) + #error "X_ENDSTOP_SERVO_NR and Y_ENDSTOP_SERVO_NR are deprecated and should be removed." +#elif defined(DEFAULT_XYJERK) + #error "DEFAULT_XYJERK is deprecated. Use DEFAULT_XJERK and DEFAULT_YJERK instead." +#elif defined(XY_TRAVEL_SPEED) + #error "XY_TRAVEL_SPEED is deprecated. Use XY_PROBE_SPEED instead." +#elif defined(PROBE_SERVO_DEACTIVATION_DELAY) + #error "PROBE_SERVO_DEACTIVATION_DELAY is deprecated. Use DEACTIVATE_SERVOS_AFTER_MOVE instead." +#elif defined(SERVO_DEACTIVATION_DELAY) + #error "SERVO_DEACTIVATION_DELAY is deprecated. Use SERVO_DELAY instead." +#elif ENABLED(FILAMENTCHANGEENABLE) + #error "FILAMENTCHANGEENABLE is now ADVANCED_PAUSE_FEATURE. Please update your configuration." +#elif ENABLED(FILAMENT_CHANGE_FEATURE) + #error "FILAMENT_CHANGE_FEATURE is now ADVANCED_PAUSE_FEATURE. Please update your configuration." +#elif defined(FILAMENT_CHANGE_X_POS) || defined(FILAMENT_CHANGE_Y_POS) + #error "FILAMENT_CHANGE_[XY]_POS is now set with NOZZLE_PARK_POINT. Please update your configuration." +#elif defined(FILAMENT_CHANGE_Z_ADD) + #error "FILAMENT_CHANGE_Z_ADD is now set with NOZZLE_PARK_POINT. Please update your configuration." +#elif defined(FILAMENT_CHANGE_XY_FEEDRATE) + #error "FILAMENT_CHANGE_XY_FEEDRATE is now NOZZLE_PARK_XY_FEEDRATE. Please update your configuration." +#elif defined(FILAMENT_CHANGE_Z_FEEDRATE) + #error "FILAMENT_CHANGE_Z_FEEDRATE is now NOZZLE_PARK_Z_FEEDRATE. Please update your configuration." +#elif defined(PAUSE_PARK_X_POS) || defined(PAUSE_PARK_Y_POS) + #error "PAUSE_PARK_[XY]_POS is now set with NOZZLE_PARK_POINT. Please update your configuration." +#elif defined(PAUSE_PARK_Z_ADD) + #error "PAUSE_PARK_Z_ADD is now set with NOZZLE_PARK_POINT. Please update your configuration." +#elif defined(PAUSE_PARK_XY_FEEDRATE) + #error "PAUSE_PARK_XY_FEEDRATE is now NOZZLE_PARK_XY_FEEDRATE. Please update your configuration." +#elif defined(PAUSE_PARK_Z_FEEDRATE) + #error "PAUSE_PARK_Z_FEEDRATE is now NOZZLE_PARK_Z_FEEDRATE. Please update your configuration." +#elif defined(FILAMENT_CHANGE_RETRACT_FEEDRATE) + #error "FILAMENT_CHANGE_RETRACT_FEEDRATE is now PAUSE_PARK_RETRACT_FEEDRATE. Please update your configuration." +#elif defined(FILAMENT_CHANGE_RETRACT_LENGTH) + #error "FILAMENT_CHANGE_RETRACT_LENGTH is now PAUSE_PARK_RETRACT_LENGTH. Please update your configuration." +#elif defined(FILAMENT_CHANGE_EXTRUDE_FEEDRATE) + #error "FILAMENT_CHANGE_EXTRUDE_FEEDRATE is now ADVANCED_PAUSE_EXTRUDE_FEEDRATE. Please update your configuration." +#elif defined(FILAMENT_CHANGE_EXTRUDE_LENGTH) + #error "FILAMENT_CHANGE_EXTRUDE_LENGTH is now ADVANCED_PAUSE_EXTRUDE_LENGTH. Please update your configuration." +#elif defined(FILAMENT_CHANGE_NOZZLE_TIMEOUT) + #error "FILAMENT_CHANGE_NOZZLE_TIMEOUT is now PAUSE_PARK_NOZZLE_TIMEOUT. Please update your configuration." +#elif defined(FILAMENT_CHANGE_NUMBER_OF_ALERT_BEEPS) + #error "FILAMENT_CHANGE_NUMBER_OF_ALERT_BEEPS is now FILAMENT_CHANGE_ALERT_BEEPS. Please update your configuration." +#elif ENABLED(FILAMENT_CHANGE_NO_STEPPER_TIMEOUT) + #error "FILAMENT_CHANGE_NO_STEPPER_TIMEOUT is now PAUSE_PARK_NO_STEPPER_TIMEOUT. Please update your configuration." +#elif defined(PLA_PREHEAT_HOTEND_TEMP) + #error "PLA_PREHEAT_HOTEND_TEMP is now PREHEAT_1_TEMP_HOTEND. Please update your configuration." +#elif defined(PLA_PREHEAT_HPB_TEMP) + #error "PLA_PREHEAT_HPB_TEMP is now PREHEAT_1_TEMP_BED. Please update your configuration." +#elif defined(PLA_PREHEAT_FAN_SPEED) + #error "PLA_PREHEAT_FAN_SPEED is now PREHEAT_1_FAN_SPEED. Please update your configuration." +#elif defined(ABS_PREHEAT_HOTEND_TEMP) + #error "ABS_PREHEAT_HOTEND_TEMP is now PREHEAT_2_TEMP_HOTEND. Please update your configuration." +#elif defined(ABS_PREHEAT_HPB_TEMP) + #error "ABS_PREHEAT_HPB_TEMP is now PREHEAT_2_TEMP_BED. Please update your configuration." +#elif defined(ABS_PREHEAT_FAN_SPEED) + #error "ABS_PREHEAT_FAN_SPEED is now PREHEAT_2_FAN_SPEED. Please update your configuration." +#elif defined(ENDSTOPS_ONLY_FOR_HOMING) + #error "ENDSTOPS_ONLY_FOR_HOMING is deprecated. Use (disable) ENDSTOPS_ALWAYS_ON_DEFAULT instead." +#elif defined(HOMING_FEEDRATE) + #error "HOMING_FEEDRATE is deprecated. Set individual rates with HOMING_FEEDRATE_(XY|Z|E) instead." +#elif defined(MANUAL_HOME_POSITIONS) + #error "MANUAL_HOME_POSITIONS is deprecated. Set MANUAL_[XYZ]_HOME_POS as-needed instead." +#elif defined(PID_ADD_EXTRUSION_RATE) + #error "PID_ADD_EXTRUSION_RATE is now PID_EXTRUSION_SCALING and is DISABLED by default. Are you sure you want to use this option? Please update your configuration." +#elif defined(Z_RAISE_BEFORE_HOMING) + #error "Z_RAISE_BEFORE_HOMING is now Z_HOMING_HEIGHT. Please update your configuration." +#elif defined(MIN_Z_HEIGHT_FOR_HOMING) + #error "MIN_Z_HEIGHT_FOR_HOMING is now Z_HOMING_HEIGHT. Please update your configuration." +#elif defined(Z_RAISE_BEFORE_PROBING) || defined(Z_RAISE_AFTER_PROBING) + #error "Z_RAISE_(BEFORE|AFTER)_PROBING are deprecated. Use Z_CLEARANCE_DEPLOY_PROBE and Z_AFTER_PROBING instead." +#elif defined(Z_RAISE_PROBE_DEPLOY_STOW) || defined(Z_RAISE_BETWEEN_PROBINGS) + #error "Z_RAISE_PROBE_DEPLOY_STOW and Z_RAISE_BETWEEN_PROBINGS are now Z_CLEARANCE_DEPLOY_PROBE and Z_CLEARANCE_BETWEEN_PROBES. Please update your configuration." +#elif defined(Z_PROBE_DEPLOY_HEIGHT) || defined(Z_PROBE_TRAVEL_HEIGHT) + #error "Z_PROBE_DEPLOY_HEIGHT and Z_PROBE_TRAVEL_HEIGHT are now Z_CLEARANCE_DEPLOY_PROBE and Z_CLEARANCE_BETWEEN_PROBES. Please update your configuration." +#elif defined(MANUAL_BED_LEVELING) + #error "MANUAL_BED_LEVELING is now LCD_BED_LEVELING. Please update your configuration." +#elif defined(MESH_HOME_SEARCH_Z) + #error "MESH_HOME_SEARCH_Z is now LCD_PROBE_Z_RANGE. Please update your configuration." +#elif defined(MANUAL_PROBE_Z_RANGE) + #error "MANUAL_PROBE_Z_RANGE is now LCD_PROBE_Z_RANGE. Please update your configuration." +#elif !defined(MIN_STEPS_PER_SEGMENT) + #error Please replace "const int dropsegments" with "#define MIN_STEPS_PER_SEGMENT" (and increase by 1) in Configuration_adv.h. +#elif defined(PREVENT_DANGEROUS_EXTRUDE) + #error "PREVENT_DANGEROUS_EXTRUDE is now PREVENT_COLD_EXTRUSION. Please update your configuration." +#elif defined(SCARA) + #error "SCARA is now MORGAN_SCARA. Please update your configuration." +#elif defined(ENABLE_AUTO_BED_LEVELING) + #error "ENABLE_AUTO_BED_LEVELING is deprecated. Specify AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_BILINEAR, or AUTO_BED_LEVELING_3POINT." +#elif defined(AUTO_BED_LEVELING_FEATURE) + #error "AUTO_BED_LEVELING_FEATURE is deprecated. Specify AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_BILINEAR, or AUTO_BED_LEVELING_3POINT." +#elif defined(ABL_GRID_POINTS) + #error "ABL_GRID_POINTS is now GRID_MAX_POINTS_X and GRID_MAX_POINTS_Y. Please update your configuration." +#elif defined(ABL_GRID_POINTS_X) || defined(ABL_GRID_POINTS_Y) + #error "ABL_GRID_POINTS_[XY] is now GRID_MAX_POINTS_[XY]. Please update your configuration." +#elif defined(ABL_GRID_MAX_POINTS_X) || defined(ABL_GRID_MAX_POINTS_Y) + #error "ABL_GRID_MAX_POINTS_[XY] is now GRID_MAX_POINTS_[XY]. Please update your configuration." +#elif defined(MESH_NUM_X_POINTS) || defined(MESH_NUM_Y_POINTS) + #error "MESH_NUM_[XY]_POINTS is now GRID_MAX_POINTS_[XY]. Please update your configuration." +#elif defined(UBL_MESH_NUM_X_POINTS) || defined(UBL_MESH_NUM_Y_POINTS) + #error "UBL_MESH_NUM_[XY]_POINTS is now GRID_MAX_POINTS_[XY]. Please update your configuration." +#elif defined(UBL_G26_MESH_VALIDATION) + #error "UBL_G26_MESH_VALIDATION is now G26_MESH_VALIDATION. Please update your configuration." +#elif defined(UBL_MESH_EDIT_ENABLED) + #error "UBL_MESH_EDIT_ENABLED is now G26_MESH_VALIDATION. Please update your configuration." +#elif defined(UBL_MESH_EDITING) + #error "UBL_MESH_EDITING is now G26_MESH_VALIDATION. Please update your configuration." +#elif defined(BLTOUCH_HEATERS_OFF) + #error "BLTOUCH_HEATERS_OFF is now PROBING_HEATERS_OFF. Please update your configuration." +#elif defined(BEEPER) + #error "BEEPER is now BEEPER_PIN. Please update your pins definitions." +#elif defined(SDCARDDETECT) + #error "SDCARDDETECT is now SD_DETECT_PIN. Please update your pins definitions." +#elif defined(STAT_LED_RED) || defined(STAT_LED_BLUE) + #error "STAT_LED_RED/STAT_LED_BLUE are now STAT_LED_RED_PIN/STAT_LED_BLUE_PIN. Please update your pins definitions." +#elif defined(LCD_PIN_BL) + #error "LCD_PIN_BL is now LCD_BACKLIGHT_PIN. Please update your pins definitions." +#elif defined(LCD_PIN_RESET) + #error "LCD_PIN_RESET is now LCD_RESET_PIN. Please update your pins definitions." +#elif defined(EXTRUDER_0_AUTO_FAN_PIN) || defined(EXTRUDER_1_AUTO_FAN_PIN) || defined(EXTRUDER_2_AUTO_FAN_PIN) || defined(EXTRUDER_3_AUTO_FAN_PIN) + #error "EXTRUDER_[0123]_AUTO_FAN_PIN is now E[0123]_AUTO_FAN_PIN. Please update your Configuration_adv.h." +#elif defined(min_software_endstops) || defined(max_software_endstops) + #error "(min|max)_software_endstops are now (MIN|MAX)_SOFTWARE_ENDSTOPS. Please update your configuration." +#elif ENABLED(Z_PROBE_SLED) && defined(SLED_PIN) + #error "Replace SLED_PIN with SOL1_PIN (applies to both Z_PROBE_SLED and SOLENOID_PROBE)." +#elif defined(CONTROLLERFAN_PIN) + #error "CONTROLLERFAN_PIN is now CONTROLLER_FAN_PIN, enabled with USE_CONTROLLER_FAN. Please update your Configuration_adv.h." +#elif defined(MIN_RETRACT) + #error "MIN_RETRACT is now MIN_AUTORETRACT and MAX_AUTORETRACT. Please update your Configuration_adv.h." +#elif defined(ADVANCE) + #error "ADVANCE was removed in Marlin 1.1.6. Please use LIN_ADVANCE." +#elif defined(LIN_ADVANCE_E_D_RATIO) + #error "LIN_ADVANCE (1.5) no longer uses LIN_ADVANCE_E_D_RATIO. Check your configuration." +#elif defined(NEOPIXEL_RGBW_LED) + #error "NEOPIXEL_RGBW_LED is now NEOPIXEL_LED. Please update your configuration." +#elif ENABLED(DELTA) && defined(DELTA_PROBEABLE_RADIUS) + #error "Remove DELTA_PROBEABLE_RADIUS and use MIN_PROBE_EDGE to inset the probe area instead." +#elif defined(UBL_MESH_INSET) + #error "UBL_MESH_INSET is now just MESH_INSET. Please update your configuration." +#elif defined(UBL_MESH_MIN_X) || defined(UBL_MESH_MIN_Y) || defined(UBL_MESH_MAX_X) || defined(UBL_MESH_MAX_Y) + #error "UBL_MESH_(MIN|MAX)_[XY] is now just MESH_(MIN|MAX)_[XY]. Please update your configuration." +#elif defined(ABL_PROBE_PT_1_X) || defined(ABL_PROBE_PT_1_Y) || defined(ABL_PROBE_PT_2_X) || defined(ABL_PROBE_PT_2_Y) || defined(ABL_PROBE_PT_3_X) || defined(ABL_PROBE_PT_3_Y) + #error "ABL_PROBE_PT_[123]_[XY] is now PROBE_PT_[123]_[XY]. Please update your configuration." +#elif defined(UBL_PROBE_PT_1_X) || defined(UBL_PROBE_PT_1_Y) || defined(UBL_PROBE_PT_2_X) || defined(UBL_PROBE_PT_2_Y) || defined(UBL_PROBE_PT_3_X) || defined(UBL_PROBE_PT_3_Y) + #error "UBL_PROBE_PT_[123]_[XY] is now PROBE_PT_[123]_[XY]. Please update your configuration." +#elif defined(ENABLE_MESH_EDIT_GFX_OVERLAY) + #error "ENABLE_MESH_EDIT_GFX_OVERLAY is now MESH_EDIT_GFX_OVERLAY. Please update your configuration." +#elif defined(BABYSTEP_ZPROBE_GFX_REVERSE) + #error "BABYSTEP_ZPROBE_GFX_REVERSE is now set by OVERLAY_GFX_REVERSE. Please update your configurations." +#elif defined(UBL_GRANULAR_SEGMENTATION_FOR_CARTESIAN) + #error "UBL_GRANULAR_SEGMENTATION_FOR_CARTESIAN is now SEGMENT_LEVELED_MOVES. Please update your configuration." +#elif HAS_PID_HEATING && (defined(K1) || !defined(PID_K1)) + #error "K1 is now PID_K1. Please update your configuration." +#elif defined(PROBE_DOUBLE_TOUCH) + #error "PROBE_DOUBLE_TOUCH is now MULTIPLE_PROBING. Please update your configuration." +#elif defined(ANET_KEYPAD_LCD) + #error "ANET_KEYPAD_LCD is now ZONESTAR_LCD. Please update your configuration." +#elif defined(LCD_I2C_SAINSMART_YWROBOT) + #error "LCD_I2C_SAINSMART_YWROBOT is now LCD_SAINSMART_I2C_(1602|2004). Please update your configuration." +#elif defined(MEASURED_LOWER_LIMIT) || defined(MEASURED_UPPER_LIMIT) + #error "MEASURED_(UPPER|LOWER)_LIMIT is now FILWIDTH_ERROR_MARGIN. Please update your configuration." +#elif defined(HAVE_TMCDRIVER) + #error "HAVE_TMCDRIVER is now HAVE_TMC26X. Please update your Configuration_adv.h." +#elif defined(X_IS_TMC) || defined(X2_IS_TMC) || defined(Y_IS_TMC) || defined(Y2_IS_TMC) || defined(Z_IS_TMC) || defined(Z2_IS_TMC) \ + || defined(E0_IS_TMC) || defined(E1_IS_TMC) || defined(E2_IS_TMC) || defined(E3_IS_TMC) || defined(E4_IS_TMC) + #error "[AXIS]_IS_TMC is now [AXIS]_IS_TMC26X. Please update your Configuration_adv.h." +#elif defined(AUTOMATIC_CURRENT_CONTROL) + #error "AUTOMATIC_CURRENT_CONTROL is now MONITOR_DRIVER_STATUS. Please update your configuration." +#endif + +/** + * Marlin release, version and default string + */ +#ifndef SHORT_BUILD_VERSION + #error "SHORT_BUILD_VERSION must be specified." +#elif !defined(DETAILED_BUILD_VERSION) + #error "BUILD_VERSION must be specified." +#elif !defined(STRING_DISTRIBUTION_DATE) + #error "STRING_DISTRIBUTION_DATE must be specified." +#elif !defined(PROTOCOL_VERSION) + #error "PROTOCOL_VERSION must be specified." +#elif !defined(MACHINE_NAME) + #error "MACHINE_NAME must be specified." +#elif !defined(SOURCE_CODE_URL) + #error "SOURCE_CODE_URL must be specified." +#elif !defined(DEFAULT_MACHINE_UUID) + #error "DEFAULT_MACHINE_UUID must be specified." +#elif !defined(WEBSITE_URL) + #error "WEBSITE_URL must be specified." +#endif + +/** + * Serial + */ +#if !(defined(__AVR__) && defined(USBCON)) + #if ENABLED(SERIAL_XON_XOFF) && RX_BUFFER_SIZE < 1024 + #error "SERIAL_XON_XOFF requires RX_BUFFER_SIZE >= 1024 for reliable transfers without drops." + #elif RX_BUFFER_SIZE && (RX_BUFFER_SIZE < 2 || !IS_POWER_OF_2(RX_BUFFER_SIZE)) + #error "RX_BUFFER_SIZE must be a power of 2 greater than 1." + #elif TX_BUFFER_SIZE && (TX_BUFFER_SIZE < 2 || TX_BUFFER_SIZE > 256 || !IS_POWER_OF_2(TX_BUFFER_SIZE)) + #error "TX_BUFFER_SIZE must be 0, a power of 2 greater than 1, and no greater than 256." + #elif ENABLED(BLUETOOTH) + #error "BLUETOOTH is only supported with AT90USB." + #endif +#elif ENABLED(SERIAL_XON_XOFF) || ENABLED(SERIAL_STATS_MAX_RX_QUEUED) || ENABLED(SERIAL_STATS_DROPPED_RX) + #error "SERIAL_XON_XOFF and SERIAL_STATS_* features not supported on USB-native AVR devices." +#endif + +#if SERIAL_PORT > 7 + #error "Set SERIAL_PORT to the port on your board. Usually this is 0." +#endif + +/** + * Dual Stepper Drivers + */ +#if ENABLED(X_DUAL_STEPPER_DRIVERS) && ENABLED(DUAL_X_CARRIAGE) + #error "DUAL_X_CARRIAGE is not compatible with X_DUAL_STEPPER_DRIVERS." +#elif ENABLED(X_DUAL_STEPPER_DRIVERS) && (!HAS_X2_ENABLE || !HAS_X2_STEP || !HAS_X2_DIR) + #error "X_DUAL_STEPPER_DRIVERS requires X2 pins (and an extra E plug)." +#elif ENABLED(Y_DUAL_STEPPER_DRIVERS) && (!HAS_Y2_ENABLE || !HAS_Y2_STEP || !HAS_Y2_DIR) + #error "Y_DUAL_STEPPER_DRIVERS requires Y2 pins (and an extra E plug)." +#elif ENABLED(Z_DUAL_STEPPER_DRIVERS) && (!HAS_Z2_ENABLE || !HAS_Z2_STEP || !HAS_Z2_DIR) + #error "Z_DUAL_STEPPER_DRIVERS requires Z2 pins (and an extra E plug)." +#endif + +/** + * Validate that the bed size fits + */ +static_assert(X_MAX_LENGTH >= X_BED_SIZE && Y_MAX_LENGTH >= Y_BED_SIZE, + "Movement bounds ([XY]_MIN_POS, [XY]_MAX_POS) are too narrow to contain [XY]_BED_SIZE."); + +/** + * Granular software endstops (Marlin >= 1.1.7) + */ +#if ENABLED(MIN_SOFTWARE_ENDSTOPS) && DISABLED(MIN_SOFTWARE_ENDSTOP_Z) + #if IS_KINEMATIC + #error "MIN_SOFTWARE_ENDSTOPS on DELTA/SCARA also requires MIN_SOFTWARE_ENDSTOP_Z." + #elif DISABLED(MIN_SOFTWARE_ENDSTOP_X) && DISABLED(MIN_SOFTWARE_ENDSTOP_Y) + #error "MIN_SOFTWARE_ENDSTOPS requires at least one of the MIN_SOFTWARE_ENDSTOP_[XYZ] options." + #endif +#endif + +#if ENABLED(MAX_SOFTWARE_ENDSTOPS) && DISABLED(MAX_SOFTWARE_ENDSTOP_Z) + #if IS_KINEMATIC + #error "MAX_SOFTWARE_ENDSTOPS on DELTA/SCARA also requires MAX_SOFTWARE_ENDSTOP_Z." + #elif DISABLED(MAX_SOFTWARE_ENDSTOP_X) && DISABLED(MAX_SOFTWARE_ENDSTOP_Y) + #error "MAX_SOFTWARE_ENDSTOPS requires at least one of the MAX_SOFTWARE_ENDSTOP_[XYZ] options." + #endif +#endif + +/** + * Progress Bar + */ +#if ENABLED(LCD_PROGRESS_BAR) + #if DISABLED(SDSUPPORT) && DISABLED(LCD_SET_PROGRESS_MANUALLY) + #error "LCD_PROGRESS_BAR requires SDSUPPORT or LCD_SET_PROGRESS_MANUALLY." + #elif DISABLED(ULTRA_LCD) + #error "LCD_PROGRESS_BAR requires a character LCD." + #elif ENABLED(DOGLCD) + #error "LCD_PROGRESS_BAR does not apply to graphical displays." + #elif ENABLED(FILAMENT_LCD_DISPLAY) + #error "LCD_PROGRESS_BAR and FILAMENT_LCD_DISPLAY are not fully compatible. Comment out this line to use both." + #endif +#elif ENABLED(LCD_SET_PROGRESS_MANUALLY) && DISABLED(DOGLCD) + #error "LCD_SET_PROGRESS_MANUALLY requires LCD_PROGRESS_BAR or Graphical LCD." +#endif + +/** + * Custom Boot and Status screens + */ +#if DISABLED(DOGLCD) && (ENABLED(SHOW_CUSTOM_BOOTSCREEN) || ENABLED(CUSTOM_STATUS_SCREEN_IMAGE)) + #error "Graphical LCD is required for SHOW_CUSTOM_BOOTSCREEN and CUSTOM_STATUS_SCREEN_IMAGE." +#endif + +/** + * SD File Sorting + */ +#if ENABLED(SDCARD_SORT_ALPHA) + #if SDSORT_LIMIT > 256 + #error "SDSORT_LIMIT must be 256 or smaller." + #elif SDSORT_LIMIT < 10 + #error "SDSORT_LIMIT should be greater than 9 to be useful." + #elif DISABLED(SDSORT_USES_RAM) + #if ENABLED(SDSORT_DYNAMIC_RAM) + #error "SDSORT_DYNAMIC_RAM requires SDSORT_USES_RAM (which reads the directory into RAM)." + #elif ENABLED(SDSORT_CACHE_NAMES) + #error "SDSORT_CACHE_NAMES requires SDSORT_USES_RAM (which reads the directory into RAM)." + #endif + #endif + + #if ENABLED(SDSORT_CACHE_NAMES) && DISABLED(SDSORT_DYNAMIC_RAM) + #if SDSORT_CACHE_VFATS < 2 + #error "SDSORT_CACHE_VFATS must be 2 or greater!" + #elif SDSORT_CACHE_VFATS > MAX_VFAT_ENTRIES + #undef SDSORT_CACHE_VFATS + #define SDSORT_CACHE_VFATS MAX_VFAT_ENTRIES + #warning "SDSORT_CACHE_VFATS was reduced to MAX_VFAT_ENTRIES!" + #endif + #endif +#endif + +/** + * I2C Position Encoders + */ +#if ENABLED(I2C_POSITION_ENCODERS) + #if DISABLED(BABYSTEPPING) || DISABLED(BABYSTEP_XY) + #error "I2C_POSITION_ENCODERS requires BABYSTEPPING and BABYSTEP_XY." + #elif !WITHIN(I2CPE_ENCODER_CNT, 1, 5) + #error "I2CPE_ENCODER_CNT must be between 1 and 5." + #endif +#endif + +/** + * Babystepping + */ +#if ENABLED(BABYSTEPPING) + #if ENABLED(SCARA) + #error "BABYSTEPPING is not implemented for SCARA yet." + #elif ENABLED(DELTA) && ENABLED(BABYSTEP_XY) + #error "BABYSTEPPING only implemented for Z axis on deltabots." + #elif ENABLED(BABYSTEP_ZPROBE_OFFSET) && ENABLED(MESH_BED_LEVELING) + #error "MESH_BED_LEVELING and BABYSTEP_ZPROBE_OFFSET is not a valid combination" + #elif ENABLED(BABYSTEP_ZPROBE_OFFSET) && !HAS_BED_PROBE + #error "BABYSTEP_ZPROBE_OFFSET requires a probe." + #elif ENABLED(BABYSTEP_ZPROBE_GFX_OVERLAY) && !ENABLED(DOGLCD) + #error "BABYSTEP_ZPROBE_GFX_OVERLAY requires a DOGLCD." + #elif ENABLED(BABYSTEP_ZPROBE_GFX_OVERLAY) && !ENABLED(BABYSTEP_ZPROBE_OFFSET) + #error "BABYSTEP_ZPROBE_GFX_OVERLAY requires a BABYSTEP_ZPROBE_OFFSET." + #endif +#endif + +/** + * Filament Runout needs one or more pins and either SD Support or Auto print start detection + */ +#if ENABLED(FILAMENT_RUNOUT_SENSOR) + #if !PIN_EXISTS(FIL_RUNOUT) + #error "FILAMENT_RUNOUT_SENSOR requires FIL_RUNOUT_PIN." + #elif NUM_RUNOUT_SENSORS > E_STEPPERS + #error "NUM_RUNOUT_SENSORS cannot exceed the number of E steppers." + #elif NUM_RUNOUT_SENSORS > 1 && !PIN_EXISTS(FIL_RUNOUT2) + #error "FILAMENT_RUNOUT_SENSOR with NUM_RUNOUT_SENSORS > 1 requires FIL_RUNOUT2_PIN." + #elif NUM_RUNOUT_SENSORS > 2 && !PIN_EXISTS(FIL_RUNOUT3) + #error "FILAMENT_RUNOUT_SENSOR with NUM_RUNOUT_SENSORS > 2 requires FIL_RUNOUT3_PIN." + #elif NUM_RUNOUT_SENSORS > 3 && !PIN_EXISTS(FIL_RUNOUT4) + #error "FILAMENT_RUNOUT_SENSOR with NUM_RUNOUT_SENSORS > 3 requires FIL_RUNOUT4_PIN." + #elif NUM_RUNOUT_SENSORS > 4 && !PIN_EXISTS(FIL_RUNOUT5) + #error "FILAMENT_RUNOUT_SENSOR with NUM_RUNOUT_SENSORS > 4 requires FIL_RUNOUT5_PIN." + #elif DISABLED(SDSUPPORT) && DISABLED(PRINTJOB_TIMER_AUTOSTART) + #error "FILAMENT_RUNOUT_SENSOR requires SDSUPPORT or PRINTJOB_TIMER_AUTOSTART." + #elif DISABLED(ADVANCED_PAUSE_FEATURE) + static_assert(NULL == strstr(FILAMENT_RUNOUT_SCRIPT, "M600"), "ADVANCED_PAUSE_FEATURE is required to use M600 with FILAMENT_RUNOUT_SENSOR."); + #endif +#endif + +/** + * Advanced Pause + */ +#if ENABLED(ADVANCED_PAUSE_FEATURE) + #if !HAS_RESUME_CONTINUE + #error "ADVANCED_PAUSE_FEATURE currently requires an LCD controller or EMERGENCY_PARSER." + #elif ENABLED(EXTRUDER_RUNOUT_PREVENT) + #error "EXTRUDER_RUNOUT_PREVENT is incompatible with ADVANCED_PAUSE_FEATURE." + #elif ENABLED(PARK_HEAD_ON_PAUSE) && DISABLED(SDSUPPORT) && DISABLED(NEWPANEL) && DISABLED(EMERGENCY_PARSER) + #error "PARK_HEAD_ON_PAUSE requires SDSUPPORT, EMERGENCY_PARSER, or an LCD controller." + #elif ENABLED(HOME_BEFORE_FILAMENT_CHANGE) && DISABLED(PAUSE_PARK_NO_STEPPER_TIMEOUT) + #error "HOME_BEFORE_FILAMENT_CHANGE requires PAUSE_PARK_NO_STEPPER_TIMEOUT." + #elif DISABLED(NOZZLE_PARK_FEATURE) + #error "ADVANCED_PAUSE_FEATURE requires NOZZLE_PARK_FEATURE." + #elif ENABLED(PREVENT_LENGTHY_EXTRUDE) && FILAMENT_CHANGE_UNLOAD_LENGTH > EXTRUDE_MAXLENGTH + #error "FILAMENT_CHANGE_UNLOAD_LENGTH must be less than or equal to EXTRUDE_MAXLENGTH." + #elif ENABLED(PREVENT_LENGTHY_EXTRUDE) && FILAMENT_CHANGE_LOAD_LENGTH > EXTRUDE_MAXLENGTH + #error "FILAMENT_CHANGE_LOAD_LENGTH must be less than or equal to EXTRUDE_MAXLENGTH." + #endif +#endif + +/** + * Individual axis homing is useless for DELTAS + */ +#if ENABLED(INDIVIDUAL_AXIS_HOMING_MENU) && ENABLED(DELTA) + #error "INDIVIDUAL_AXIS_HOMING_MENU is incompatible with DELTA kinematics." +#endif + +/** + * Options only for EXTRUDERS > 1 + */ +#if EXTRUDERS > 1 + + #if EXTRUDERS > 5 + #error "Marlin supports a maximum of 5 EXTRUDERS." + #endif + + #if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT) + #error "EXTRUDERS must be 1 with TEMP_SENSOR_1_AS_REDUNDANT." + #endif + + #if ENABLED(HEATERS_PARALLEL) + #error "EXTRUDERS must be 1 with HEATERS_PARALLEL." + #endif + +#elif ENABLED(MK2_MULTIPLEXER) + #error "MK2_MULTIPLEXER requires 2 or more EXTRUDERS." +#elif ENABLED(SINGLENOZZLE) + #error "SINGLENOZZLE requires 2 or more EXTRUDERS." +#endif + +/** + * Sanity checking for the Průša MK2 Multiplexer + */ +#ifdef SNMM + #error "SNMM is now MK2_MULTIPLEXER. Please update your configuration." +#endif + +/** + * A Dual Nozzle carriage with switching servo + */ +#if ENABLED(SWITCHING_NOZZLE) + #if ENABLED(DUAL_X_CARRIAGE) + #error "SWITCHING_NOZZLE and DUAL_X_CARRIAGE are incompatible." + #elif ENABLED(SINGLENOZZLE) + #error "SWITCHING_NOZZLE and SINGLENOZZLE are incompatible." + #elif EXTRUDERS != 2 + #error "SWITCHING_NOZZLE requires exactly 2 EXTRUDERS." + #elif NUM_SERVOS < 1 + #error "SWITCHING_NOZZLE requires NUM_SERVOS >= 1." + #endif +#endif + +/** + * Single Stepper Dual Extruder with switching servo + */ +#if ENABLED(SWITCHING_EXTRUDER) && NUM_SERVOS < 1 + #error "SWITCHING_EXTRUDER requires NUM_SERVOS >= 1." +#endif + +/** + * Mixing Extruder requirements + */ +#if ENABLED(MIXING_EXTRUDER) + #if EXTRUDERS > 1 + #error "MIXING_EXTRUDER currently only supports one extruder." + #elif MIXING_STEPPERS < 2 + #error "You must set MIXING_STEPPERS >= 2 for a mixing extruder." + #elif ENABLED(FILAMENT_SENSOR) + #error "MIXING_EXTRUDER is incompatible with FILAMENT_SENSOR. Comment out this line to use it anyway." + #elif ENABLED(SWITCHING_EXTRUDER) + #error "Please select either MIXING_EXTRUDER or SWITCHING_EXTRUDER, not both." + #elif ENABLED(SINGLENOZZLE) + #error "MIXING_EXTRUDER is incompatible with SINGLENOZZLE." + #elif ENABLED(LIN_ADVANCE) + #error "MIXING_EXTRUDER is incompatible with LIN_ADVANCE." + #endif +#endif + +/** + * Linear Advance 1.5 - Check K value range + */ +#if ENABLED(LIN_ADVANCE) + static_assert( + WITHIN(LIN_ADVANCE_K, 0, 10), + "LIN_ADVANCE_K must be a value from 0 to 10 (Changed in LIN_ADVANCE v1.5, Marlin 1.1.9)." + ); +#endif + +/** + * Parking Extruder requirements + */ +#if ENABLED(PARKING_EXTRUDER) + #if ENABLED(DUAL_X_CARRIAGE) + #error "PARKING_EXTRUDER and DUAL_X_CARRIAGE are incompatible." + #elif ENABLED(SINGLENOZZLE) + #error "PARKING_EXTRUDER and SINGLENOZZLE are incompatible." + #elif ENABLED(EXT_SOLENOID) + #error "PARKING_EXTRUDER and EXT_SOLENOID are incompatible. (Pins are used twice.)" + #elif EXTRUDERS != 2 + #error "PARKING_EXTRUDER requires exactly 2 EXTRUDERS." + #elif !PIN_EXISTS(SOL0) || !PIN_EXISTS(SOL1) + #error "PARKING_EXTRUDER requires SOL0_PIN and SOL1_PIN." + #elif !defined(PARKING_EXTRUDER_PARKING_X) + #error "PARKING_EXTRUDER requires PARKING_EXTRUDER_PARKING_X." + #elif !defined(PARKING_EXTRUDER_SECURITY_RAISE) + #error "PARKING_EXTRUDER requires PARKING_EXTRUDER_SECURITY_RAISE." + #elif PARKING_EXTRUDER_SECURITY_RAISE < 0 + #error "PARKING_EXTRUDER_SECURITY_RAISE must be 0 or higher." + #elif !defined(PARKING_EXTRUDER_SOLENOIDS_PINS_ACTIVE) || !WITHIN(PARKING_EXTRUDER_SOLENOIDS_PINS_ACTIVE, LOW, HIGH) + #error "PARKING_EXTRUDER_SOLENOIDS_PINS_ACTIVE must be defined as HIGH or LOW." + #elif !defined(PARKING_EXTRUDER_SOLENOIDS_DELAY) || !WITHIN(PARKING_EXTRUDER_SOLENOIDS_DELAY, 0, 2000) + #error "PARKING_EXTRUDER_SOLENOIDS_DELAY must be between 0 and 2000 (ms)." + #endif +#endif + +/** + * Part-Cooling Fan Multiplexer requirements + */ +#if PIN_EXISTS(FANMUX1) + #if !HAS_FANMUX + #error "FANMUX0_PIN must be set before FANMUX1_PIN can be set." + #endif +#elif PIN_EXISTS(FANMUX2) + #error "FANMUX0_PIN and FANMUX1_PIN must be set before FANMUX2_PIN can be set." +#endif + +/** + * Limited number of servos + */ +#if NUM_SERVOS > 4 + #error "The maximum number of SERVOS in Marlin is 4." +#endif + +/** + * Servo deactivation depends on servo endstops, switching nozzle, or switching extruder + */ +#if ENABLED(DEACTIVATE_SERVOS_AFTER_MOVE) && !HAS_Z_SERVO_ENDSTOP && !defined(SWITCHING_NOZZLE_SERVO_NR) && !defined(SWITCHING_EXTRUDER_SERVO_NR) + #error "Z_ENDSTOP_SERVO_NR, switching nozzle, or switching extruder is required for DEACTIVATE_SERVOS_AFTER_MOVE." +#endif + +/** + * Required LCD language + */ +#if DISABLED(DOGLCD) && ENABLED(ULTRA_LCD) && !defined(DISPLAY_CHARSET_HD44780) + #error "You must set DISPLAY_CHARSET_HD44780 to JAPANESE, WESTERN or CYRILLIC for your LCD controller." +#endif + +/** + * Bed Heating Options - PID vs Limit Switching + */ +#if ENABLED(PIDTEMPBED) && ENABLED(BED_LIMIT_SWITCHING) + #error "To use BED_LIMIT_SWITCHING you must disable PIDTEMPBED." +#endif + +/** + * Kinematics + */ + +/** + * Allow only one kinematic type to be defined + */ +#if 1 < 0 \ + + ENABLED(DELTA) \ + + ENABLED(MORGAN_SCARA) \ + + ENABLED(MAKERARM_SCARA) \ + + ENABLED(COREXY) \ + + ENABLED(COREXZ) \ + + ENABLED(COREYZ) \ + + ENABLED(COREYX) \ + + ENABLED(COREZX) \ + + ENABLED(COREZY) + #error "Please enable only one of DELTA, MORGAN_SCARA, MAKERARM_SCARA, COREXY, COREYX, COREXZ, COREZX, COREYZ, or COREZY." +#endif + +/** + * Delta requirements + */ +#if ENABLED(DELTA) + #if DISABLED(USE_XMAX_PLUG) && DISABLED(USE_YMAX_PLUG) && DISABLED(USE_ZMAX_PLUG) + #error "You probably want to use Max Endstops for DELTA!" + #elif ENABLED(ENABLE_LEVELING_FADE_HEIGHT) && DISABLED(AUTO_BED_LEVELING_BILINEAR) && !UBL_SEGMENTED + #error "ENABLE_LEVELING_FADE_HEIGHT on DELTA requires AUTO_BED_LEVELING_BILINEAR or AUTO_BED_LEVELING_UBL." + #elif ENABLED(DELTA_AUTO_CALIBRATION) && !(HAS_BED_PROBE || ENABLED(ULTIPANEL)) + #error "DELTA_AUTO_CALIBRATION requires a probe or LCD Controller." + #elif ABL_GRID + #if (GRID_MAX_POINTS_X & 1) == 0 || (GRID_MAX_POINTS_Y & 1) == 0 + #error "DELTA requires GRID_MAX_POINTS_X and GRID_MAX_POINTS_Y to be odd numbers." + #elif GRID_MAX_POINTS_X < 3 + #error "DELTA requires GRID_MAX_POINTS_X and GRID_MAX_POINTS_Y to be 3 or higher." + #endif + #endif +#endif + +/** + * Probes + */ + +/** + * Allow only one probe option to be defined + */ +#if 1 < 0 \ + + ENABLED(PROBE_MANUALLY) \ + + ENABLED(FIX_MOUNTED_PROBE) \ + + (HAS_Z_SERVO_ENDSTOP && DISABLED(BLTOUCH)) \ + + ENABLED(BLTOUCH) \ + + ENABLED(SOLENOID_PROBE) \ + + ENABLED(Z_PROBE_ALLEN_KEY) \ + + ENABLED(Z_PROBE_SLED) + #error "Please enable only one probe option: PROBE_MANUALLY, FIX_MOUNTED_PROBE, BLTOUCH, SOLENOID_PROBE, Z_PROBE_ALLEN_KEY, Z_PROBE_SLED, or Z Servo." +#endif + +#if HAS_BED_PROBE + + /** + * Z_PROBE_SLED is incompatible with DELTA + */ + #if ENABLED(Z_PROBE_SLED) && ENABLED(DELTA) + #error "You cannot use Z_PROBE_SLED with DELTA." + #endif + + /** + * SOLENOID_PROBE requirements + */ + #if ENABLED(SOLENOID_PROBE) + #if ENABLED(EXT_SOLENOID) + #error "SOLENOID_PROBE is incompatible with EXT_SOLENOID." + #elif !HAS_SOLENOID_1 + #error "SOLENOID_PROBE requires SOL1_PIN. It can be added to your Configuration.h." + #endif + #endif + + /** + * NUM_SERVOS is required for a Z servo probe + */ + #if HAS_Z_SERVO_ENDSTOP + #ifndef NUM_SERVOS + #error "You must set NUM_SERVOS for a Z servo probe (Z_ENDSTOP_SERVO_NR)." + #elif Z_ENDSTOP_SERVO_NR >= NUM_SERVOS + #error "Z_ENDSTOP_SERVO_NR must be smaller than NUM_SERVOS." + #endif + #endif + + /** + * Require pin options and pins to be defined + */ + #if ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN) + #if ENABLED(Z_MIN_PROBE_ENDSTOP) + #error "Enable only one option: Z_MIN_PROBE_ENDSTOP or Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN." + #elif DISABLED(USE_ZMIN_PLUG) + #error "Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN requires USE_ZMIN_PLUG to be enabled." + #elif !HAS_Z_MIN + #error "Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN requires the Z_MIN_PIN to be defined." + #elif ENABLED(Z_MIN_PROBE_ENDSTOP_INVERTING) != ENABLED(Z_MIN_ENDSTOP_INVERTING) + #error "Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN requires Z_MIN_ENDSTOP_INVERTING to match Z_MIN_PROBE_ENDSTOP_INVERTING." + #endif + #elif ENABLED(Z_MIN_PROBE_ENDSTOP) + #if !HAS_Z_MIN_PROBE_PIN + #error "Z_MIN_PROBE_ENDSTOP requires the Z_MIN_PROBE_PIN to be defined." + #endif + #else + #error "You must enable either Z_MIN_PROBE_ENDSTOP or Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN to use a probe." + #endif + + /** + * Make sure Z raise values are set + */ + #ifndef Z_CLEARANCE_DEPLOY_PROBE + #error "You must define Z_CLEARANCE_DEPLOY_PROBE in your configuration." + #elif !defined(Z_CLEARANCE_BETWEEN_PROBES) + #error "You must define Z_CLEARANCE_BETWEEN_PROBES in your configuration." + #elif Z_CLEARANCE_DEPLOY_PROBE < 0 + #error "Probes need Z_CLEARANCE_DEPLOY_PROBE >= 0." + #elif Z_CLEARANCE_BETWEEN_PROBES < 0 + #error "Probes need Z_CLEARANCE_BETWEEN_PROBES >= 0." + #elif Z_AFTER_PROBING < 0 + #error "Probes need Z_AFTER_PROBING >= 0." + #endif + + #if MULTIPLE_PROBING && MULTIPLE_PROBING < 2 + #error "MULTIPLE_PROBING must be >= 2." + #endif + +#else + + /** + * Require some kind of probe for bed leveling and probe testing + */ + #if OLDSCHOOL_ABL && !PROBE_SELECTED + #error "Auto Bed Leveling requires one of these: PROBE_MANUALLY, FIX_MOUNTED_PROBE, BLTOUCH, SOLENOID_PROBE, Z_PROBE_ALLEN_KEY, Z_PROBE_SLED, or a Z Servo." + #endif + + #if ENABLED(Z_MIN_PROBE_REPEATABILITY_TEST) + #error "Z_MIN_PROBE_REPEATABILITY_TEST requires a probe: FIX_MOUNTED_PROBE, BLTOUCH, SOLENOID_PROBE, Z_PROBE_ALLEN_KEY, Z_PROBE_SLED, or Z Servo." + #endif + +#endif + +/** + * Allow only one bed leveling option to be defined + */ +#if 1 < 0 \ + + ENABLED(AUTO_BED_LEVELING_LINEAR) \ + + ENABLED(AUTO_BED_LEVELING_3POINT) \ + + ENABLED(AUTO_BED_LEVELING_BILINEAR) \ + + ENABLED(AUTO_BED_LEVELING_UBL) \ + + ENABLED(MESH_BED_LEVELING) + #error "Select only one of: MESH_BED_LEVELING, AUTO_BED_LEVELING_LINEAR, AUTO_BED_LEVELING_3POINT, AUTO_BED_LEVELING_BILINEAR or AUTO_BED_LEVELING_UBL." +#endif + +/** + * Bed Leveling Requirements + */ + +#if ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(AUTO_BED_LEVELING_3POINT) + static_assert(WITHIN(PROBE_PT_1_X, MIN_PROBE_X, MAX_PROBE_X), "PROBE_PT_1_X is outside the probe region."); + static_assert(WITHIN(PROBE_PT_2_X, MIN_PROBE_X, MAX_PROBE_X), "PROBE_PT_2_X is outside the probe region."); + static_assert(WITHIN(PROBE_PT_3_X, MIN_PROBE_X, MAX_PROBE_X), "PROBE_PT_3_X is outside the probe region."); + static_assert(WITHIN(PROBE_PT_1_Y, MIN_PROBE_Y, MAX_PROBE_Y), "PROBE_PT_1_Y is outside the probe region."); + static_assert(WITHIN(PROBE_PT_2_Y, MIN_PROBE_Y, MAX_PROBE_Y), "PROBE_PT_2_Y is outside the probe region."); + static_assert(WITHIN(PROBE_PT_3_Y, MIN_PROBE_Y, MAX_PROBE_Y), "PROBE_PT_3_Y is outside the probe region."); +#endif + +#if ENABLED(AUTO_BED_LEVELING_UBL) + + /** + * Unified Bed Leveling + */ + + // Hide PROBE_MANUALLY from the rest of the code + #undef PROBE_MANUALLY + + #if IS_SCARA + #error "AUTO_BED_LEVELING_UBL does not yet support SCARA printers." + #elif DISABLED(EEPROM_SETTINGS) + #error "AUTO_BED_LEVELING_UBL requires EEPROM_SETTINGS. Please update your configuration." + #elif !WITHIN(GRID_MAX_POINTS_X, 3, 15) || !WITHIN(GRID_MAX_POINTS_Y, 3, 15) + #error "GRID_MAX_POINTS_[XY] must be a whole number between 3 and 15." + #elif DISABLED(RESTORE_LEVELING_AFTER_G28) + #error "AUTO_BED_LEVELING_UBL (<=1.1.8) always has RESTORE_LEVELING_AFTER_G28 enabled. To keep this behavior, #define RESTORE_LEVELING_AFTER_G28. To keep it disabled comment out this line in SanityCheck.h." + #endif + +#elif OLDSCHOOL_ABL + + /** + * Auto Bed Leveling + */ + + /** + * Delta and SCARA have limited bed leveling options + */ + #if IS_SCARA && DISABLED(AUTO_BED_LEVELING_BILINEAR) + #error "SCARA machines can only use the AUTO_BED_LEVELING_BILINEAR leveling option." + #endif + + /** + * Check auto bed leveling probe points + */ + #if ABL_GRID + + static_assert(LEFT_PROBE_BED_POSITION < RIGHT_PROBE_BED_POSITION, "LEFT_PROBE_BED_POSITION must be less than RIGHT_PROBE_BED_POSITION."); + static_assert(FRONT_PROBE_BED_POSITION < BACK_PROBE_BED_POSITION, "FRONT_PROBE_BED_POSITION must be less than BACK_PROBE_BED_POSITION."); + static_assert(LEFT_PROBE_BED_POSITION >= MIN_PROBE_X, "LEFT_PROBE_BED_POSITION is outside the probe region."); + static_assert(RIGHT_PROBE_BED_POSITION <= MAX_PROBE_X, "RIGHT_PROBE_BED_POSITION is outside the probe region."); + static_assert(FRONT_PROBE_BED_POSITION >= MIN_PROBE_Y, "FRONT_PROBE_BED_POSITION is outside the probe region."); + static_assert(BACK_PROBE_BED_POSITION <= MAX_PROBE_Y, "BACK_PROBE_BED_POSITION is outside the probe region."); + + #endif // AUTO_BED_LEVELING_3POINT + +#elif ENABLED(MESH_BED_LEVELING) + + // Hide PROBE_MANUALLY from the rest of the code + #undef PROBE_MANUALLY + + /** + * Mesh Bed Leveling + */ + + #if ENABLED(DELTA) + #error "MESH_BED_LEVELING is not compatible with DELTA printers." + #elif GRID_MAX_POINTS_X > 9 || GRID_MAX_POINTS_Y > 9 + #error "GRID_MAX_POINTS_X and GRID_MAX_POINTS_Y must be less than 10 for MBL." + #endif + +#endif + +#if HAS_MESH + static_assert(DEFAULT_ZJERK > 0.1, "Low DEFAULT_ZJERK values are incompatible with mesh-based leveling."); +#elif ENABLED(G26_MESH_VALIDATION) + #error "G26_MESH_VALIDATION requires MESH_BED_LEVELING, AUTO_BED_LEVELING_BILINEAR, or AUTO_BED_LEVELING_UBL." +#endif + +#if ENABLED(MESH_EDIT_GFX_OVERLAY) && (DISABLED(AUTO_BED_LEVELING_UBL) || DISABLED(DOGLCD)) + #error "MESH_EDIT_GFX_OVERLAY requires AUTO_BED_LEVELING_UBL and a Graphical LCD." +#endif + +/** + * LCD_BED_LEVELING requirements + */ +#if ENABLED(LCD_BED_LEVELING) + #if DISABLED(ULTIPANEL) + #error "LCD_BED_LEVELING requires an LCD controller." + #elif !(ENABLED(MESH_BED_LEVELING) || (OLDSCHOOL_ABL && ENABLED(PROBE_MANUALLY))) + #error "LCD_BED_LEVELING requires MESH_BED_LEVELING or ABL with PROBE_MANUALLY." + #endif +#endif + +/** + * Homing + */ +#if X_HOME_BUMP_MM < 0 || Y_HOME_BUMP_MM < 0 || Z_HOME_BUMP_MM < 0 + #error "[XYZ]_HOME_BUMP_MM must be greater than or equal to 0." +#endif + +#if ENABLED(CODEPENDENT_XY_HOMING) + #if ENABLED(QUICK_HOME) + #error "QUICK_HOME is incompatible with CODEPENDENT_XY_HOMING." + #elif IS_KINEMATIC + #error "CODEPENDENT_XY_HOMING requires a Cartesian setup." + #endif +#endif + +/** + * Make sure Z_SAFE_HOMING point is reachable + */ +#if ENABLED(Z_SAFE_HOMING) + #if HAS_BED_PROBE + #if !WITHIN(Z_SAFE_HOMING_X_POINT, MIN_PROBE_X, MAX_PROBE_X) + #error "Z_SAFE_HOMING_X_POINT is outside the probe region." + #elif !WITHIN(Z_SAFE_HOMING_Y_POINT, MIN_PROBE_Y, MAX_PROBE_Y) + #error "Z_SAFE_HOMING_Y_POINT is outside the probe region." + #endif + #elif !WITHIN(Z_SAFE_HOMING_X_POINT, X_MIN_POS, X_MAX_POS) + #error "Z_SAFE_HOMING_X_POINT can't be reached by the nozzle." + #elif !WITHIN(Z_SAFE_HOMING_Y_POINT, Y_MIN_POS, Y_MAX_POS) + #error "Z_SAFE_HOMING_Y_POINT can't be reached by the nozzle." + #endif +#endif // Z_SAFE_HOMING + +/** + * Make sure DISABLE_[XYZ] compatible with selected homing options + */ +#if ENABLED(DISABLE_X) || ENABLED(DISABLE_Y) || ENABLED(DISABLE_Z) + #if ENABLED(HOME_AFTER_DEACTIVATE) || ENABLED(Z_SAFE_HOMING) + #error "DISABLE_[XYZ] is not compatible with HOME_AFTER_DEACTIVATE or Z_SAFE_HOMING." + #endif +#endif // DISABLE_[XYZ] + +/** + * Filament Width Sensor + */ +#if ENABLED(FILAMENT_WIDTH_SENSOR) + #if !HAS_FILAMENT_WIDTH_SENSOR + #error "FILAMENT_WIDTH_SENSOR requires a FILWIDTH_PIN to be defined." + #elif ENABLED(NO_VOLUMETRICS) + #error "FILAMENT_WIDTH_SENSOR requires NO_VOLUMETRICS to be disabled." + #endif +#endif + +/** + * ULTIPANEL encoder + */ +#if ENABLED(ULTIPANEL) && DISABLED(NEWPANEL) && DISABLED(SR_LCD_2W_NL) && !defined(SHIFT_CLK) + #error "ULTIPANEL requires some kind of encoder." +#endif + +#if ENCODER_PULSES_PER_STEP < 0 + #error "ENCODER_PULSES_PER_STEP should not be negative, use REVERSE_MENU_DIRECTION instead." +#endif + +/** + * SAV_3DGLCD display options + */ +#if ENABLED(U8GLIB_SSD1306) && ENABLED(U8GLIB_SH1106) + #error "Only enable one SAV_3DGLCD display type: U8GLIB_SSD1306 or U8GLIB_SH1106." +#endif + +/** + * Allen Key + * Deploying the Allen Key probe uses big moves in z direction. Too dangerous for an unhomed z-axis. + */ +#if ENABLED(Z_PROBE_ALLEN_KEY) && (Z_HOME_DIR < 0) && ENABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN) + #error "You can't home to a z min endstop with a Z_PROBE_ALLEN_KEY" +#endif + +/** + * Dual X Carriage requirements + */ +#if ENABLED(DUAL_X_CARRIAGE) + #if EXTRUDERS == 1 + #error "DUAL_X_CARRIAGE requires 2 (or more) extruders." + #elif CORE_IS_XY || CORE_IS_XZ + #error "DUAL_X_CARRIAGE cannot be used with COREXY, COREYX, COREXZ, or COREZX." + #elif !HAS_X2_ENABLE || !HAS_X2_STEP || !HAS_X2_DIR + #error "DUAL_X_CARRIAGE requires X2 stepper pins to be defined." + #elif !HAS_X_MAX + #error "DUAL_X_CARRIAGE requires USE_XMAX_PLUG and an X Max Endstop." + #elif !defined(X2_HOME_POS) || !defined(X2_MIN_POS) || !defined(X2_MAX_POS) + #error "DUAL_X_CARRIAGE requires X2_HOME_POS, X2_MIN_POS, and X2_MAX_POS." + #elif X_HOME_DIR != -1 || X2_HOME_DIR != 1 + #error "DUAL_X_CARRIAGE requires X_HOME_DIR -1 and X2_HOME_DIR 1." + #endif +#endif // DUAL_X_CARRIAGE + +/** + * Make sure auto fan pins don't conflict with the fan pin + */ +#if HAS_AUTO_FAN + #if HAS_FAN0 + #if E0_AUTO_FAN_PIN == FAN_PIN + #error "You cannot set E0_AUTO_FAN_PIN equal to FAN_PIN." + #elif E1_AUTO_FAN_PIN == FAN_PIN + #error "You cannot set E1_AUTO_FAN_PIN equal to FAN_PIN." + #elif E2_AUTO_FAN_PIN == FAN_PIN + #error "You cannot set E2_AUTO_FAN_PIN equal to FAN_PIN." + #elif E3_AUTO_FAN_PIN == FAN_PIN + #error "You cannot set E3_AUTO_FAN_PIN equal to FAN_PIN." + #endif + #endif +#endif + +#if HAS_FAN0 && CONTROLLER_FAN_PIN == FAN_PIN + #error "You cannot set CONTROLLER_FAN_PIN equal to FAN_PIN." +#endif + +#if ENABLED(USE_CONTROLLER_FAN) + #if !HAS_CONTROLLER_FAN + #error "USE_CONTROLLER_FAN requires a CONTROLLER_FAN_PIN. Define in Configuration_adv.h." + #elif E0_AUTO_FAN_PIN == CONTROLLER_FAN_PIN + #error "You cannot set E0_AUTO_FAN_PIN equal to CONTROLLER_FAN_PIN." + #elif E1_AUTO_FAN_PIN == CONTROLLER_FAN_PIN + #error "You cannot set E1_AUTO_FAN_PIN equal to CONTROLLER_FAN_PIN." + #elif E2_AUTO_FAN_PIN == CONTROLLER_FAN_PIN + #error "You cannot set E2_AUTO_FAN_PIN equal to CONTROLLER_FAN_PIN." + #elif E3_AUTO_FAN_PIN == CONTROLLER_FAN_PIN + #error "You cannot set E3_AUTO_FAN_PIN equal to CONTROLLER_FAN_PIN." + #endif +#endif + +/** + * Test Heater, Temp Sensor, and Extruder Pins; Sensor Type must also be set. + */ +#if !HAS_HEATER_0 + #error "HEATER_0_PIN not defined for this board." +#elif !PIN_EXISTS(TEMP_0) && !(defined(MAX6675_SS) && MAX6675_SS >= 0) + #error "TEMP_0_PIN not defined for this board." +#elif ((defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)) && (!PIN_EXISTS(E0_STEP) || !PIN_EXISTS(E0_DIR))) + #error "E0_STEP_PIN or E0_DIR_PIN not defined for this board." +#elif ( !(defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__)) && (!PIN_EXISTS(E0_STEP) || !PIN_EXISTS(E0_DIR) || !PIN_EXISTS(E0_ENABLE))) + #error "E0_STEP_PIN, E0_DIR_PIN, or E0_ENABLE_PIN not defined for this board." +#elif TEMP_SENSOR_0 == 0 + #error "TEMP_SENSOR_0 is required." +#endif + +// Pins are required for heaters +#if ENABLED(HEATER_0_USES_MAX6675) && !(defined(MAX6675_SS) && MAX6675_SS >= 0) + #error "MAX6675_SS (required for TEMP_SENSOR_0) not defined for this board." +#elif (HOTENDS > 1 || ENABLED(HEATERS_PARALLEL)) && !HAS_HEATER_1 + #error "HEATER_1_PIN not defined for this board." +#endif + +#if HOTENDS > 1 + #if TEMP_SENSOR_1 == 0 + #error "TEMP_SENSOR_1 is required with 2 or more HOTENDS." + #elif !PIN_EXISTS(TEMP_1) + #error "TEMP_1_PIN not defined for this board." + #endif + #if HOTENDS > 2 + #if TEMP_SENSOR_2 == 0 + #error "TEMP_SENSOR_2 is required with 3 or more HOTENDS." + #elif !HAS_HEATER_2 + #error "HEATER_2_PIN not defined for this board." + #elif !PIN_EXISTS(TEMP_2) + #error "TEMP_2_PIN not defined for this board." + #endif + #if HOTENDS > 3 + #if TEMP_SENSOR_3 == 0 + #error "TEMP_SENSOR_3 is required with 4 or more HOTENDS." + #elif !HAS_HEATER_3 + #error "HEATER_3_PIN not defined for this board." + #elif !PIN_EXISTS(TEMP_3) + #error "TEMP_3_PIN not defined for this board." + #endif + #if HOTENDS > 4 + #if TEMP_SENSOR_4 == 0 + #error "TEMP_SENSOR_4 is required with 5 HOTENDS." + #elif !HAS_HEATER_4 + #error "HEATER_4_PIN not defined for this board." + #elif !PIN_EXISTS(TEMP_4) + #error "TEMP_4_PIN not defined for this board." + #endif + #elif TEMP_SENSOR_4 != 0 + #error "TEMP_SENSOR_4 shouldn't be set with only 4 HOTENDS." + #endif + #elif TEMP_SENSOR_3 != 0 + #error "TEMP_SENSOR_3 shouldn't be set with only 3 HOTENDS." + #elif TEMP_SENSOR_4 != 0 + #error "TEMP_SENSOR_4 shouldn't be set with only 3 HOTENDS." + #endif + #elif TEMP_SENSOR_2 != 0 + #error "TEMP_SENSOR_2 shouldn't be set with only 2 HOTENDS." + #elif TEMP_SENSOR_3 != 0 + #error "TEMP_SENSOR_3 shouldn't be set with only 2 HOTENDS." + #elif TEMP_SENSOR_4 != 0 + #error "TEMP_SENSOR_4 shouldn't be set with only 2 HOTENDS." + #endif +#elif TEMP_SENSOR_1 != 0 && DISABLED(TEMP_SENSOR_1_AS_REDUNDANT) + #error "TEMP_SENSOR_1 shouldn't be set with only 1 HOTEND." +#elif TEMP_SENSOR_2 != 0 + #error "TEMP_SENSOR_2 shouldn't be set with only 1 HOTEND." +#elif TEMP_SENSOR_3 != 0 + #error "TEMP_SENSOR_3 shouldn't be set with only 1 HOTEND." +#elif TEMP_SENSOR_4 != 0 + #error "TEMP_SENSOR_4 shouldn't be set with only 1 HOTEND." +#endif + +#if ENABLED(TEMP_SENSOR_1_AS_REDUNDANT) && TEMP_SENSOR_1 == 0 + #error "TEMP_SENSOR_1 is required with TEMP_SENSOR_1_AS_REDUNDANT." +#endif + +/** + * Temperature status LEDs + */ +#if ENABLED(TEMP_STAT_LEDS) && !PIN_EXISTS(STAT_LED_RED) && !PIN_EXISTS(STAT_LED_BLUE) + #error "TEMP_STAT_LEDS requires STAT_LED_RED_PIN or STAT_LED_BLUE_PIN, preferably both." +#endif + +/** + * Basic 2-nozzle duplication mode + */ +#if ENABLED(DUAL_NOZZLE_DUPLICATION_MODE) + #if HOTENDS != 2 + #error "DUAL_NOZZLE_DUPLICATION_MODE requires exactly 2 hotends." + #elif ENABLED(DUAL_X_CARRIAGE) + #error "DUAL_NOZZLE_DUPLICATION_MODE is incompatible with DUAL_X_CARRIAGE." + #elif ENABLED(SINGLENOZZLE) + #error "DUAL_NOZZLE_DUPLICATION_MODE is incompatible with SINGLENOZZLE." + #elif ENABLED(MIXING_EXTRUDER) + #error "DUAL_NOZZLE_DUPLICATION_MODE is incompatible with MIXING_EXTRUDER." + #elif ENABLED(SWITCHING_EXTRUDER) + #error "DUAL_NOZZLE_DUPLICATION_MODE is incompatible with SWITCHING_EXTRUDER." + #endif +#endif + +/** + * Test Extruder Stepper Pins + */ +#if DISABLED(MK2_MULTIPLEXER) // MK2_MULTIPLEXER uses E0 stepper only + #if E_STEPPERS > 4 + #if !PIN_EXISTS(E4_STEP) || !PIN_EXISTS(E4_DIR) || !PIN_EXISTS(E4_ENABLE) + #error "E4_STEP_PIN, E4_DIR_PIN, or E4_ENABLE_PIN not defined for this board." + #endif + #elif E_STEPPERS > 3 + #if !PIN_EXISTS(E3_STEP) || !PIN_EXISTS(E3_DIR) || !PIN_EXISTS(E3_ENABLE) + #error "E3_STEP_PIN, E3_DIR_PIN, or E3_ENABLE_PIN not defined for this board." + #endif + #elif E_STEPPERS > 2 + #if !PIN_EXISTS(E2_STEP) || !PIN_EXISTS(E2_DIR) || !PIN_EXISTS(E2_ENABLE) + #error "E2_STEP_PIN, E2_DIR_PIN, or E2_ENABLE_PIN not defined for this board." + #endif + #elif E_STEPPERS > 1 + #if !PIN_EXISTS(E1_STEP) || !PIN_EXISTS(E1_DIR) || !PIN_EXISTS(E1_ENABLE) + #error "E1_STEP_PIN, E1_DIR_PIN, or E1_ENABLE_PIN not defined for this board." + #endif + #endif +#endif +/** + * Endstop Tests + */ + +#define _PLUG_UNUSED_TEST(AXIS,PLUG) (DISABLED(USE_##PLUG##MIN_PLUG) && DISABLED(USE_##PLUG##MAX_PLUG) && !(ENABLED(AXIS##_DUAL_ENDSTOPS) && WITHIN(AXIS##2_USE_ENDSTOP, _##PLUG##MAX_, _##PLUG##MIN_))) +#define _AXIS_PLUG_UNUSED_TEST(AXIS) (_PLUG_UNUSED_TEST(AXIS,X) && _PLUG_UNUSED_TEST(AXIS,Y) && _PLUG_UNUSED_TEST(AXIS,Z)) + +// At least 3 endstop plugs must be used +#if _AXIS_PLUG_UNUSED_TEST(X) + #error "You must enable USE_XMIN_PLUG or USE_XMAX_PLUG." +#endif +#if _AXIS_PLUG_UNUSED_TEST(Y) + #error "You must enable USE_YMIN_PLUG or USE_YMAX_PLUG." +#endif +#if _AXIS_PLUG_UNUSED_TEST(Z) + #error "You must enable USE_ZMIN_PLUG or USE_ZMAX_PLUG." +#endif + +// Delta and Cartesian use 3 homing endstops +#if !IS_SCARA + #if X_HOME_DIR < 0 && DISABLED(USE_XMIN_PLUG) + #error "Enable USE_XMIN_PLUG when homing X to MIN." + #elif X_HOME_DIR > 0 && DISABLED(USE_XMAX_PLUG) + #error "Enable USE_XMAX_PLUG when homing X to MAX." + #elif Y_HOME_DIR < 0 && DISABLED(USE_YMIN_PLUG) + #error "Enable USE_YMIN_PLUG when homing Y to MIN." + #elif Y_HOME_DIR > 0 && DISABLED(USE_YMAX_PLUG) + #error "Enable USE_YMAX_PLUG when homing Y to MAX." + #endif +#endif +#if Z_HOME_DIR < 0 && DISABLED(USE_ZMIN_PLUG) + #error "Enable USE_ZMIN_PLUG when homing Z to MIN." +#elif Z_HOME_DIR > 0 && DISABLED(USE_ZMAX_PLUG) + #error "Enable USE_ZMAX_PLUG when homing Z to MAX." +#endif + +// Dual endstops requirements +#if ENABLED(X_DUAL_ENDSTOPS) + #if !X2_USE_ENDSTOP + #error "You must set X2_USE_ENDSTOP with X_DUAL_ENDSTOPS." + #elif X2_USE_ENDSTOP == _X_MIN_ && DISABLED(USE_XMIN_PLUG) + #error "USE_XMIN_PLUG is required when X2_USE_ENDSTOP is _X_MIN_." + #elif X2_USE_ENDSTOP == _X_MAX_ && DISABLED(USE_XMAX_PLUG) + #error "USE_XMAX_PLUG is required when X2_USE_ENDSTOP is _X_MAX_." + #elif X2_USE_ENDSTOP == _Y_MIN_ && DISABLED(USE_YMIN_PLUG) + #error "USE_YMIN_PLUG is required when X2_USE_ENDSTOP is _Y_MIN_." + #elif X2_USE_ENDSTOP == _Y_MAX_ && DISABLED(USE_YMAX_PLUG) + #error "USE_YMAX_PLUG is required when X2_USE_ENDSTOP is _Y_MAX_." + #elif X2_USE_ENDSTOP == _Z_MIN_ && DISABLED(USE_ZMIN_PLUG) + #error "USE_ZMIN_PLUG is required when X2_USE_ENDSTOP is _Z_MIN_." + #elif X2_USE_ENDSTOP == _Z_MAX_ && DISABLED(USE_ZMAX_PLUG) + #error "USE_ZMAX_PLUG is required when X2_USE_ENDSTOP is _Z_MAX_." + #elif !HAS_X2_MIN && !HAS_X2_MAX + #error "X2_USE_ENDSTOP has been assigned to a nonexistent endstop!" + #elif ENABLED(DELTA) + #error "X_DUAL_ENDSTOPS is not compatible with DELTA." + #endif +#endif +#if ENABLED(Y_DUAL_ENDSTOPS) + #if !Y2_USE_ENDSTOP + #error "You must set Y2_USE_ENDSTOP with Y_DUAL_ENDSTOPS." + #elif Y2_USE_ENDSTOP == _X_MIN_ && DISABLED(USE_XMIN_PLUG) + #error "USE_XMIN_PLUG is required when Y2_USE_ENDSTOP is _X_MIN_." + #elif Y2_USE_ENDSTOP == _X_MAX_ && DISABLED(USE_XMAX_PLUG) + #error "USE_XMAX_PLUG is required when Y2_USE_ENDSTOP is _X_MAX_." + #elif Y2_USE_ENDSTOP == _Y_MIN_ && DISABLED(USE_YMIN_PLUG) + #error "USE_YMIN_PLUG is required when Y2_USE_ENDSTOP is _Y_MIN_." + #elif Y2_USE_ENDSTOP == _Y_MAX_ && DISABLED(USE_YMAX_PLUG) + #error "USE_YMAX_PLUG is required when Y2_USE_ENDSTOP is _Y_MAX_." + #elif Y2_USE_ENDSTOP == _Z_MIN_ && DISABLED(USE_ZMIN_PLUG) + #error "USE_ZMIN_PLUG is required when Y2_USE_ENDSTOP is _Z_MIN_." + #elif Y2_USE_ENDSTOP == _Z_MAX_ && DISABLED(USE_ZMAX_PLUG) + #error "USE_ZMAX_PLUG is required when Y2_USE_ENDSTOP is _Z_MAX_." + #elif !HAS_Y2_MIN && !HAS_Y2_MAX + #error "Y2_USE_ENDSTOP has been assigned to a nonexistent endstop!" + #elif ENABLED(DELTA) + #error "Y_DUAL_ENDSTOPS is not compatible with DELTA." + #endif +#endif +#if ENABLED(Z_DUAL_ENDSTOPS) + #if !Z2_USE_ENDSTOP + #error "You must set Z2_USE_ENDSTOP with Z_DUAL_ENDSTOPS." + #elif Z2_USE_ENDSTOP == _X_MIN_ && DISABLED(USE_XMIN_PLUG) + #error "USE_XMIN_PLUG is required when Z2_USE_ENDSTOP is _X_MIN_." + #elif Z2_USE_ENDSTOP == _X_MAX_ && DISABLED(USE_XMAX_PLUG) + #error "USE_XMAX_PLUG is required when Z2_USE_ENDSTOP is _X_MAX_." + #elif Z2_USE_ENDSTOP == _Y_MIN_ && DISABLED(USE_YMIN_PLUG) + #error "USE_YMIN_PLUG is required when Z2_USE_ENDSTOP is _Y_MIN_." + #elif Z2_USE_ENDSTOP == _Y_MAX_ && DISABLED(USE_YMAX_PLUG) + #error "USE_YMAX_PLUG is required when Z2_USE_ENDSTOP is _Y_MAX_." + #elif Z2_USE_ENDSTOP == _Z_MIN_ && DISABLED(USE_ZMIN_PLUG) + #error "USE_ZMIN_PLUG is required when Z2_USE_ENDSTOP is _Z_MIN_." + #elif Z2_USE_ENDSTOP == _Z_MAX_ && DISABLED(USE_ZMAX_PLUG) + #error "USE_ZMAX_PLUG is required when Z2_USE_ENDSTOP is _Z_MAX_." + #elif !HAS_Z2_MIN && !HAS_Z2_MAX + #error "Z2_USE_ENDSTOP has been assigned to a nonexistent endstop!" + #elif ENABLED(DELTA) + #error "Z_DUAL_ENDSTOPS is not compatible with DELTA." + #endif +#endif + +/** + * emergency-command parser + */ +#if ENABLED(EMERGENCY_PARSER) && defined(__AVR__) && defined(USBCON) + #error "EMERGENCY_PARSER does not work on boards with AT90USB processors (USBCON)." +#endif + +/** + * I2C bus + */ +#if ENABLED(EXPERIMENTAL_I2CBUS) && I2C_SLAVE_ADDRESS > 0 + #if I2C_SLAVE_ADDRESS < 8 + #error "I2C_SLAVE_ADDRESS can't be less than 8. (Addresses 0 - 7 are reserved.)" + #elif I2C_SLAVE_ADDRESS > 127 + #error "I2C_SLAVE_ADDRESS can't be over 127. (Only 7 bits allowed.)" + #endif +#endif + +/** + * G38 Probe Target + */ +#if ENABLED(G38_PROBE_TARGET) + #if !HAS_BED_PROBE + #error "G38_PROBE_TARGET requires a bed probe." + #elif !IS_CARTESIAN + #error "G38_PROBE_TARGET requires a Cartesian machine." + #endif +#endif + +/** + * RGB_LED Requirements + */ +#define _RGB_TEST (PIN_EXISTS(RGB_LED_R) && PIN_EXISTS(RGB_LED_G) && PIN_EXISTS(RGB_LED_B)) +#if ENABLED(RGB_LED) + #if !_RGB_TEST + #error "RGB_LED requires RGB_LED_R_PIN, RGB_LED_G_PIN, and RGB_LED_B_PIN." + #elif ENABLED(RGBW_LED) + #error "Please enable only one of RGB_LED and RGBW_LED." + #endif +#elif ENABLED(RGBW_LED) + #if !(_RGB_TEST && PIN_EXISTS(RGB_LED_W)) + #error "RGBW_LED requires RGB_LED_R_PIN, RGB_LED_G_PIN, RGB_LED_B_PIN, and RGB_LED_W_PIN." + #endif +#elif ENABLED(NEOPIXEL_LED) + #if !(PIN_EXISTS(NEOPIXEL) && NEOPIXEL_PIXELS > 0) + #error "NEOPIXEL_LED requires NEOPIXEL_PIN and NEOPIXEL_PIXELS." + #endif +#elif ENABLED(PRINTER_EVENT_LEDS) && DISABLED(BLINKM) && DISABLED(PCA9632) && DISABLED(NEOPIXEL_LED) + #error "PRINTER_EVENT_LEDS requires BLINKM, PCA9632, RGB_LED, RGBW_LED or NEOPIXEL_LED." +#endif + +/** + * Auto Fan check for PWM pins + */ +#if HAS_AUTO_FAN && EXTRUDER_AUTO_FAN_SPEED != 255 + #define AF_ERR_SUFF "_AUTO_FAN_PIN is not a PWM pin. Set EXTRUDER_AUTO_FAN_SPEED to 255." + #if HAS_AUTO_FAN_0 + static_assert(GET_TIMER(E0_AUTO_FAN_PIN), "E0" AF_ERR_SUFF); + #elif HAS_AUTO_FAN_1 + static_assert(GET_TIMER(E1_AUTO_FAN_PIN), "E1" AF_ERR_SUFF); + #elif HAS_AUTO_FAN_2 + static_assert(GET_TIMER(E2_AUTO_FAN_PIN), "E2" AF_ERR_SUFF); + #elif HAS_AUTO_FAN_3 + static_assert(GET_TIMER(E3_AUTO_FAN_PIN), "E3" AF_ERR_SUFF); + #endif +#endif + +/** + * Make sure only one display is enabled + * + * Note: BQ_LCD_SMART_CONTROLLER => REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER + * REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER => REPRAP_DISCOUNT_SMART_CONTROLLER + * SAV_3DGLCD => U8GLIB_SH1106 => ULTIMAKERCONTROLLER + * MKS_12864OLED => U8GLIB_SH1106 => ULTIMAKERCONTROLLER + * MKS_12864OLED_SSD1306 => U8GLIB_SSD1306 => ULTIMAKERCONTROLLER + * miniVIKI => ULTIMAKERCONTROLLER + * VIKI2 => ULTIMAKERCONTROLLER + * ELB_FULL_GRAPHIC_CONTROLLER => ULTIMAKERCONTROLLER + * PANEL_ONE => ULTIMAKERCONTROLLER + */ +#if 1 < 0 \ + + ( ENABLED(ULTIMAKERCONTROLLER) \ + && DISABLED(SAV_3DGLCD) \ + && DISABLED(miniVIKI) \ + && DISABLED(VIKI2) \ + && DISABLED(ELB_FULL_GRAPHIC_CONTROLLER) \ + && DISABLED(PANEL_ONE) \ + && DISABLED(MKS_12864OLED) \ + && DISABLED(MKS_12864OLED_SSD1306) ) \ + + ( ENABLED(REPRAP_DISCOUNT_SMART_CONTROLLER) \ + && DISABLED(REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER) \ + && DISABLED(LCD_FOR_MELZI) \ + && DISABLED(MAKEBOARD_MINI_2_LINE_DISPLAY_1602) \ + && DISABLED(MKS_12864OLED) \ + && DISABLED(MKS_12864OLED_SSD1306) ) \ + + (ENABLED(REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER) && DISABLED(BQ_LCD_SMART_CONTROLLER)) \ + + ENABLED(LCD_FOR_MELZI) \ + + ENABLED(MKS_12864OLED) \ + + ENABLED(MKS_12864OLED_SSD1306) \ + + ENABLED(MAKEBOARD_MINI_2_LINE_DISPLAY_1602) \ + + ENABLED(CARTESIO_UI) \ + + ENABLED(PANEL_ONE) \ + + ENABLED(MAKRPANEL) \ + + ENABLED(REPRAPWORLD_GRAPHICAL_LCD) \ + + ENABLED(VIKI2) \ + + ENABLED(miniVIKI) \ + + ENABLED(ELB_FULL_GRAPHIC_CONTROLLER) \ + + ENABLED(G3D_PANEL) \ + + (ENABLED(MINIPANEL) && DISABLED(MKS_MINI_12864)) \ + + ENABLED(MKS_MINI_12864) \ + + (ENABLED(REPRAPWORLD_KEYPAD) && DISABLED(CARTESIO_UI) && DISABLED(ZONESTAR_LCD)) \ + + ENABLED(RIGIDBOT_PANEL) \ + + ENABLED(RA_CONTROL_PANEL) \ + + ENABLED(LCD_SAINSMART_I2C_1602) \ + + ENABLED(LCD_SAINSMART_I2C_2004) \ + + ENABLED(LCM1602) \ + + ENABLED(LCD_I2C_PANELOLU2) \ + + ENABLED(LCD_I2C_VIKI) \ + + (ENABLED(U8GLIB_SSD1306) && DISABLED(OLED_PANEL_TINYBOY2) && DISABLED(MKS_12864OLED_SSD1306)) \ + + ENABLED(SAV_3DLCD) \ + + ENABLED(BQ_LCD_SMART_CONTROLLER) \ + + ENABLED(SAV_3DGLCD) \ + + ENABLED(OLED_PANEL_TINYBOY2) \ + + ENABLED(ZONESTAR_LCD) \ + + ENABLED(ULTI_CONTROLLER) + #error "Please select no more than one LCD controller option." +#endif + +/** + * Make sure HAVE_TMC26X is warranted + */ +#if ENABLED(HAVE_TMC26X) && !( \ + ENABLED( X_IS_TMC26X ) \ + || ENABLED( X2_IS_TMC26X ) \ + || ENABLED( Y_IS_TMC26X ) \ + || ENABLED( Y2_IS_TMC26X ) \ + || ENABLED( Z_IS_TMC26X ) \ + || ENABLED( Z2_IS_TMC26X ) \ + || ENABLED( E0_IS_TMC26X ) \ + || ENABLED( E1_IS_TMC26X ) \ + || ENABLED( E2_IS_TMC26X ) \ + || ENABLED( E3_IS_TMC26X ) \ + || ENABLED( E4_IS_TMC26X ) \ + ) + #error "HAVE_TMC26X requires at least one TMC26X stepper to be set." +#endif + +/** + * Make sure HAVE_TMC2130 is warranted + */ +#if ENABLED(HAVE_TMC2130) + #if !( ENABLED( X_IS_TMC2130 ) \ + || ENABLED( X2_IS_TMC2130 ) \ + || ENABLED( Y_IS_TMC2130 ) \ + || ENABLED( Y2_IS_TMC2130 ) \ + || ENABLED( Z_IS_TMC2130 ) \ + || ENABLED( Z2_IS_TMC2130 ) \ + || ENABLED( E0_IS_TMC2130 ) \ + || ENABLED( E1_IS_TMC2130 ) \ + || ENABLED( E2_IS_TMC2130 ) \ + || ENABLED( E3_IS_TMC2130 ) \ + || ENABLED( E4_IS_TMC2130 ) ) + #error "HAVE_TMC2130 requires at least one TMC2130 stepper to be set." + #elif ENABLED(HYBRID_THRESHOLD) && DISABLED(STEALTHCHOP) + #error "Enable STEALTHCHOP to use HYBRID_THRESHOLD." + #endif + + #if ENABLED(X_IS_TMC2130) && !PIN_EXISTS(X_CS) + #error "X_CS_PIN is required for X_IS_TMC2130. Define X_CS_PIN in Configuration_adv.h." + #elif ENABLED(X2_IS_TMC2130) && !PIN_EXISTS(X2_CS) + #error "X2_CS_PIN is required for X2_IS_TMC2130. Define X2_CS_PIN in Configuration_adv.h." + #elif ENABLED(Y_IS_TMC2130) && !PIN_EXISTS(Y_CS) + #error "Y_CS_PIN is required for Y_IS_TMC2130. Define Y_CS_PIN in Configuration_adv.h." + #elif ENABLED(Y2_IS_TMC2130) && !PIN_EXISTS(Y2_CS) + #error "Y2_CS_PIN is required for Y2_IS_TMC2130. Define Y2_CS_PIN in Configuration_adv.h." + #elif ENABLED(Z_IS_TMC2130) && !PIN_EXISTS(Z_CS) + #error "Z_CS_PIN is required for Z_IS_TMC2130. Define Z_CS_PIN in Configuration_adv.h." + #elif ENABLED(Z2_IS_TMC2130) && !PIN_EXISTS(Z2_CS) + #error "Z2_CS_PIN is required for Z2_IS_TMC2130. Define Z2_CS_PIN in Configuration_adv.h." + #elif ENABLED(E0_IS_TMC2130) && !PIN_EXISTS(E0_CS) + #error "E0_CS_PIN is required for E0_IS_TMC2130. Define E0_CS_PIN in Configuration_adv.h." + #elif ENABLED(E1_IS_TMC2130) && !PIN_EXISTS(E1_CS) + #error "E1_CS_PIN is required for E1_IS_TMC2130. Define E1_CS_PIN in Configuration_adv.h." + #elif ENABLED(E2_IS_TMC2130) && !PIN_EXISTS(E2_CS) + #error "E2_CS_PIN is required for E2_IS_TMC2130. Define E2_CS_PIN in Configuration_adv.h." + #elif ENABLED(E3_IS_TMC2130) && !PIN_EXISTS(E3_CS) + #error "E3_CS_PIN is required for E3_IS_TMC2130. Define E3_CS_PIN in Configuration_adv.h." + #elif ENABLED(E4_IS_TMC2130) && !PIN_EXISTS(E4_CS) + #error "E4_CS_PIN is required for E4_IS_TMC2130. Define E4_CS_PIN in Configuration_adv.h." + #endif + + // Require STEALTHCHOP for SENSORLESS_HOMING on DELTA as the transition from spreadCycle to stealthChop + // is necessary in order to reset the stallGuard indication between the initial movement of all three + // towers to +Z and the individual homing of each tower. This restriction can be removed once a means of + // clearing the stallGuard activated status is found. + #if ENABLED(SENSORLESS_HOMING) && ENABLED(DELTA) && !ENABLED(STEALTHCHOP) + #error "SENSORLESS_HOMING on DELTA currently requires STEALTHCHOP." + #endif + + // Sensorless homing is required for both combined steppers in an H-bot + #if CORE_IS_XY && X_SENSORLESS != Y_SENSORLESS + #error "CoreXY requires both X and Y to use sensorless homing if either does." + #elif CORE_IS_XZ && X_SENSORLESS != Z_SENSORLESS + #error "CoreXZ requires both X and Z to use sensorless homing if either does." + #elif CORE_IS_YZ && Y_SENSORLESS != Z_SENSORLESS + #error "CoreYZ requires both Y and Z to use sensorless homing if either does." + #endif + +#elif ENABLED(SENSORLESS_HOMING) + + #error "SENSORLESS_HOMING requires TMC2130 stepper drivers." + +#endif + +/** + * Make sure HAVE_TMC2208 is warranted + */ +#if ENABLED(HAVE_TMC2208) && !( \ + ENABLED( X_IS_TMC2208 ) \ + || ENABLED( X2_IS_TMC2208 ) \ + || ENABLED( Y_IS_TMC2208 ) \ + || ENABLED( Y2_IS_TMC2208 ) \ + || ENABLED( Z_IS_TMC2208 ) \ + || ENABLED( Z2_IS_TMC2208 ) \ + || ENABLED( E0_IS_TMC2208 ) \ + || ENABLED( E1_IS_TMC2208 ) \ + || ENABLED( E2_IS_TMC2208 ) \ + || ENABLED( E3_IS_TMC2208 ) ) + #error "HAVE_TMC2208 requires at least one TMC2208 stepper to be set." +#endif + +/** + * TMC2208 software UART and ENDSTOP_INTERRUPTS both use pin change interrupts (PCI) + */ +#if ENABLED(HAVE_TMC2208) && ENABLED(ENDSTOP_INTERRUPTS_FEATURE) && !( \ + defined(X_HARDWARE_SERIAL ) \ + || defined(X2_HARDWARE_SERIAL) \ + || defined(Y_HARDWARE_SERIAL ) \ + || defined(Y2_HARDWARE_SERIAL) \ + || defined(Z_HARDWARE_SERIAL ) \ + || defined(Z2_HARDWARE_SERIAL) \ + || defined(E0_HARDWARE_SERIAL) \ + || defined(E1_HARDWARE_SERIAL) \ + || defined(E2_HARDWARE_SERIAL) \ + || defined(E3_HARDWARE_SERIAL) \ + || defined(E4_HARDWARE_SERIAL) ) + #error "select hardware UART for TMC2208 to use both TMC2208 and ENDSTOP_INTERRUPTS_FEATURE." +#endif + +#if ENABLED(HYBRID_THRESHOLD) && DISABLED(STEALTHCHOP) + #error "Enable STEALTHCHOP to use HYBRID_THRESHOLD." +#endif + +#if ENABLED(TMC_Z_CALIBRATION) && !Z_IS_TRINAMIC && !Z2_IS_TRINAMIC + #error "TMC_Z_CALIBRATION requires at least one TMC driver on Z axis" +#endif + +/** + * Make sure HAVE_L6470DRIVER is warranted + */ +#if ENABLED(HAVE_L6470DRIVER) && !( \ + ENABLED( X_IS_L6470 ) \ + || ENABLED( X2_IS_L6470 ) \ + || ENABLED( Y_IS_L6470 ) \ + || ENABLED( Y2_IS_L6470 ) \ + || ENABLED( Z_IS_L6470 ) \ + || ENABLED( Z2_IS_L6470 ) \ + || ENABLED( E0_IS_L6470 ) \ + || ENABLED( E1_IS_L6470 ) \ + || ENABLED( E2_IS_L6470 ) \ + || ENABLED( E3_IS_L6470 ) \ + || ENABLED( E4_IS_L6470 ) \ + ) + #error "HAVE_L6470DRIVER requires at least one L6470 stepper to be set." +#endif + +/** + * Check that each axis has only one driver selected + */ +#if 1 < 0 \ + + ENABLED(X_IS_TMC26X) \ + + ENABLED(X_IS_TMC2130) \ + + ENABLED(X_IS_TMC2208) \ + + ENABLED(X_IS_L6470) + #error "Please enable only one stepper driver for the X axis." +#endif +#if 1 < 0 \ + + ENABLED(X2_IS_TMC26X) \ + + ENABLED(X2_IS_TMC2130) \ + + ENABLED(X2_IS_TMC2208) \ + + ENABLED(X2_IS_L6470) + #error "Please enable only one stepper driver for the X2 axis." +#endif +#if 1 < 0 \ + + ENABLED(Y_IS_TMC26X) \ + + ENABLED(Y_IS_TMC2130) \ + + ENABLED(Y_IS_TMC2208) \ + + ENABLED(Y_IS_L6470) + #error "Please enable only one stepper driver for the Y axis." +#endif +#if 1 < 0 \ + + ENABLED(Y2_IS_TMC26X) \ + + ENABLED(Y2_IS_TMC2130) \ + + ENABLED(Y2_IS_TMC2208) \ + + ENABLED(Y2_IS_L6470) + #error "Please enable only one stepper driver for the Y2 axis." +#endif +#if 1 < 0 \ + + ENABLED(Z_IS_TMC26X) \ + + ENABLED(Z_IS_TMC2130) \ + + ENABLED(Z_IS_TMC2208) \ + + ENABLED(Z_IS_L6470) + #error "Please enable only one stepper driver for the Z axis." +#endif +#if 1 < 0 \ + + ENABLED(Z2_IS_TMC26X) \ + + ENABLED(Z2_IS_TMC2130) \ + + ENABLED(Z2_IS_TMC2208) \ + + ENABLED(Z2_IS_L6470) + #error "Please enable only one stepper driver for the Z2 axis." +#endif +#if 1 < 0 \ + + ENABLED(E0_IS_TMC26X) \ + + ENABLED(E0_IS_TMC2130) \ + + ENABLED(E0_IS_TMC2208) \ + + ENABLED(E0_IS_L6470) + #error "Please enable only one stepper driver for the E0 axis." +#endif +#if 1 < 0 \ + + ENABLED(E1_IS_TMC26X) \ + + ENABLED(E1_IS_TMC2130) \ + + ENABLED(E1_IS_TMC2208) \ + + ENABLED(E1_IS_L6470) + #error "Please enable only one stepper driver for the E1 axis." +#endif +#if 1 < 0 \ + + ENABLED(E2_IS_TMC26X) \ + + ENABLED(E2_IS_TMC2130) \ + + ENABLED(E2_IS_TMC2208) \ + + ENABLED(E2_IS_L6470) + #error "Please enable only one stepper driver for the E2 axis." +#endif +#if 1 < 0 \ + + ENABLED(E3_IS_TMC26X) \ + + ENABLED(E3_IS_TMC2130) \ + + ENABLED(E3_IS_TMC2208) \ + + ENABLED(E3_IS_L6470) + #error "Please enable only one stepper driver for the E3 axis." +#endif +#if 1 < 0 \ + + ENABLED(E4_IS_TMC26X) \ + + ENABLED(E4_IS_TMC2130) \ + + ENABLED(E4_IS_TMC2208) \ + + ENABLED(E4_IS_L6470) + #error "Please enable only one stepper driver for the E4 axis." +#endif + +/** + * Digipot requirement + */ +#if ENABLED(DIGIPOT_MCP4018) + #if !defined(DIGIPOTS_I2C_SDA_X) || !defined(DIGIPOTS_I2C_SDA_Y) || !defined(DIGIPOTS_I2C_SDA_Z) \ + || !defined(DIGIPOTS_I2C_SDA_E0) || !defined(DIGIPOTS_I2C_SDA_E1) + #error "DIGIPOT_MCP4018 requires DIGIPOTS_I2C_SDA_* pins to be defined." + #endif +#endif + +/** + * Require 4 or more elements in per-axis initializers + */ +constexpr float sanity_arr_1[] = DEFAULT_AXIS_STEPS_PER_UNIT, + sanity_arr_2[] = DEFAULT_MAX_FEEDRATE, + sanity_arr_3[] = DEFAULT_MAX_ACCELERATION; +static_assert(COUNT(sanity_arr_1) >= XYZE, "DEFAULT_AXIS_STEPS_PER_UNIT requires 4 (or more) elements."); +static_assert(COUNT(sanity_arr_2) >= XYZE, "DEFAULT_MAX_FEEDRATE requires 4 (or more) elements."); +static_assert(COUNT(sanity_arr_3) >= XYZE, "DEFAULT_MAX_ACCELERATION requires 4 (or more) elements."); +static_assert(COUNT(sanity_arr_1) <= XYZE_N, "DEFAULT_AXIS_STEPS_PER_UNIT has too many elements."); +static_assert(COUNT(sanity_arr_2) <= XYZE_N, "DEFAULT_MAX_FEEDRATE has too many elements."); +static_assert(COUNT(sanity_arr_3) <= XYZE_N, "DEFAULT_MAX_ACCELERATION has too many elements."); + +/** + * Sanity checks for Spindle / Laser + */ +#if ENABLED(SPINDLE_LASER_ENABLE) + #if !PIN_EXISTS(SPINDLE_LASER_ENABLE) + //#error "SPINDLE_LASER_ENABLE requires SPINDLE_LASER_ENABLE_PIN." + #elif SPINDLE_DIR_CHANGE && !PIN_EXISTS(SPINDLE_DIR) + #error "SPINDLE_DIR_PIN not defined." + #elif ENABLED(SPINDLE_LASER_PWM) && PIN_EXISTS(SPINDLE_LASER_PWM) + #if !(WITHIN(SPINDLE_LASER_PWM_PIN, 2, 13) || WITHIN(SPINDLE_LASER_PWM_PIN, 44, 46)) + #error "SPINDLE_LASER_PWM_PIN not assigned to a PWM pin." + #elif SPINDLE_LASER_POWERUP_DELAY < 1 + #error "SPINDLE_LASER_POWERUP_DELAY must be greater than 0." + #elif SPINDLE_LASER_POWERDOWN_DELAY < 1 + #error "SPINDLE_LASER_POWERDOWN_DELAY must be greater than 0." + #elif !defined(SPINDLE_LASER_PWM_INVERT) + #error "SPINDLE_LASER_PWM_INVERT missing." + #elif !defined(SPEED_POWER_SLOPE) || !defined(SPEED_POWER_INTERCEPT) || !defined(SPEED_POWER_MIN) || !defined(SPEED_POWER_MAX) + #error "SPINDLE_LASER_PWM equation constant(s) missing." + #elif SPINDLE_LASER_PWM_PIN == 4 || WITHIN(SPINDLE_LASER_PWM_PIN, 11, 13) + #error "Counter/Timer for SPINDLE_LASER_PWM_PIN is used by a system interrupt." + #elif PIN_EXISTS(X_MAX) && X_MAX_PIN == SPINDLE_LASER_PWM_PIN + #error "SPINDLE_LASER_PWM pin is in use by X_MAX endstop." + #elif PIN_EXISTS(X_MIN) && X_MIN_PIN == SPINDLE_LASER_PWM_PIN + #error "SPINDLE_LASER_PWM pin is in use by X_MIN endstop." + #elif PIN_EXISTS(Z_STEP) && Z_STEP_PIN == SPINDLE_LASER_PWM_PIN + #error "SPINDLE_LASER_PWM pin in use by Z_STEP." + #elif NUM_SERVOS > 0 && (WITHIN(SPINDLE_LASER_PWM_PIN, 2, 3) || SPINDLE_LASER_PWM_PIN == 5) + #error "Counter/Timer for SPINDLE_LASER_PWM_PIN is used by the servo system." + #elif PIN_EXISTS(CASE_LIGHT) && SPINDLE_LASER_PWM_PIN == CASE_LIGHT_PIN + #error "SPINDLE_LASER_PWM_PIN is used by CASE_LIGHT_PIN." + #elif PIN_EXISTS(E0_AUTO_FAN) && SPINDLE_LASER_PWM_PIN == E0_AUTO_FAN_PIN + #error "SPINDLE_LASER_PWM_PIN is used by E0_AUTO_FAN_PIN." + #elif PIN_EXISTS(E1_AUTO_FAN) && SPINDLE_LASER_PWM_PIN == E1_AUTO_FAN_PIN + #error "SPINDLE_LASER_PWM_PIN is used by E1_AUTO_FAN_PIN." + #elif PIN_EXISTS(E2_AUTO_FAN) && SPINDLE_LASER_PWM_PIN == E2_AUTO_FAN_PIN + #error "SPINDLE_LASER_PWM_PIN is used by E2_AUTO_FAN_PIN." + #elif PIN_EXISTS(E3_AUTO_FAN) && SPINDLE_LASER_PWM_PIN == E3_AUTO_FAN_PIN + #error "SPINDLE_LASER_PWM_PIN is used by E3_AUTO_FAN_PIN." + #elif PIN_EXISTS(E4_AUTO_FAN) && SPINDLE_LASER_PWM_PIN == E4_AUTO_FAN_PIN + #error "SPINDLE_LASER_PWM_PIN is used by E4_AUTO_FAN_PIN." + #elif PIN_EXISTS(FAN) && SPINDLE_LASER_PWM_PIN == FAN_PIN + #error "SPINDLE_LASER_PWM_PIN is used FAN_PIN." + #elif PIN_EXISTS(FAN1) && SPINDLE_LASER_PWM_PIN == FAN1_PIN + #error "SPINDLE_LASER_PWM_PIN is used FAN1_PIN." + #elif PIN_EXISTS(FAN2) && SPINDLE_LASER_PWM_PIN == FAN2_PIN + #error "SPINDLE_LASER_PWM_PIN is used FAN2_PIN." + #elif PIN_EXISTS(CONTROLLERFAN) && SPINDLE_LASER_PWM_PIN == CONTROLLERFAN_PIN + #error "SPINDLE_LASER_PWM_PIN is used by CONTROLLERFAN_PIN." + #elif PIN_EXISTS(MOTOR_CURRENT_PWM_XY) && SPINDLE_LASER_PWM_PIN == MOTOR_CURRENT_PWM_XY_PIN + #error "SPINDLE_LASER_PWM_PIN is used by MOTOR_CURRENT_PWM_XY." + #elif PIN_EXISTS(MOTOR_CURRENT_PWM_Z) && SPINDLE_LASER_PWM_PIN == MOTOR_CURRENT_PWM_Z_PIN + #error "SPINDLE_LASER_PWM_PIN is used by MOTOR_CURRENT_PWM_Z." + #elif PIN_EXISTS(MOTOR_CURRENT_PWM_E) && SPINDLE_LASER_PWM_PIN == MOTOR_CURRENT_PWM_E_PIN + #error "SPINDLE_LASER_PWM_PIN is used by MOTOR_CURRENT_PWM_E." + #elif PIN_EXISTS(CASE_LIGHT) && SPINDLE_LASER_PWM_PIN == CASE_LIGHT_PIN + #error "SPINDLE_LASER_PWM_PIN is used by CASE_LIGHT." + #endif + #endif +#endif // SPINDLE_LASER_ENABLE + +#if ENABLED(CNC_COORDINATE_SYSTEMS) && ENABLED(NO_WORKSPACE_OFFSETS) + #error "CNC_COORDINATE_SYSTEMS is incompatible with NO_WORKSPACE_OFFSETS." +#endif + +#if !BLOCK_BUFFER_SIZE || !IS_POWER_OF_2(BLOCK_BUFFER_SIZE) + #error "BLOCK_BUFFER_SIZE must be a power of 2." +#endif + +#if ENABLED(LED_CONTROL_MENU) && DISABLED(ULTIPANEL) + #error "LED_CONTROL_MENU requires an LCD controller." +#endif + +#if ENABLED(CASE_LIGHT_USE_NEOPIXEL) && DISABLED(NEOPIXEL_LED) + #error "CASE_LIGHT_USE_NEOPIXEL requires NEOPIXEL_LED." +#endif + +#if ENABLED(SKEW_CORRECTION) + #if !defined(XY_SKEW_FACTOR) && !(defined(XY_DIAG_AC) && defined(XY_DIAG_BD) && defined(XY_SIDE_AD)) + #error "SKEW_CORRECTION requires XY_SKEW_FACTOR or XY_DIAG_AC, XY_DIAG_BD, XY_SIDE_AD." + #endif + #if ENABLED(SKEW_CORRECTION_FOR_Z) + #if !defined(XZ_SKEW_FACTOR) && !(defined(XZ_DIAG_AC) && defined(XZ_DIAG_BD) && defined(XZ_SIDE_AD)) + #error "SKEW_CORRECTION requires XZ_SKEW_FACTOR or XZ_DIAG_AC, XZ_DIAG_BD, XZ_SIDE_AD." + #endif + #if !defined(YZ_SKEW_FACTOR) && !(defined(YZ_DIAG_AC) && defined(YZ_DIAG_BD) && defined(YZ_SIDE_AD)) + #error "SKEW_CORRECTION requires YZ_SKEW_FACTOR or YZ_DIAG_AC, YZ_DIAG_BD, YZ_SIDE_AD." + #endif + #endif +#endif + +#endif // _SANITYCHECK_H_ diff --git a/Version.h b/Version.h new file mode 100644 index 0000000000..224401111f --- /dev/null +++ b/Version.h @@ -0,0 +1,96 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 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 . + * + */ + +/** + * This file is the standard Marlin version identifier file, all fields can be + * overriden by the ones defined on _Version.h by using the Configuration.h + * directive USE_AUTOMATIC_VERSIONING. + */ + +#if ENABLED(USE_AUTOMATIC_VERSIONING) + + #include "_Version.h" + +#else + + /** + * Marlin release version identifier + */ + #define SHORT_BUILD_VERSION "1.1.8_TR1" + + /** + * Verbose version identifier which should contain a reference to the location + * from where the binary was downloaded or the source code was compiled. + */ + + + #define DETAILED_BUILD_VERSION SHORT_BUILD_VERSION " TM3D " + + /** + * The STRING_DISTRIBUTION_DATE represents when the binary file was built, + * here we define this default string as the date where the latest release + * version was tagged. + */ + #define STRING_DISTRIBUTION_DATE "2018-03-31" + + /** + * Required minimum Configuration.h and Configuration_adv.h file versions. + * + * You must increment this version number for every significant change such as, + * but not limited to: ADD, DELETE RENAME OR REPURPOSE any directive/option on + * the configuration files. + */ + #define REQUIRED_CONFIGURATION_H_VERSION 010107 + #define REQUIRED_CONFIGURATION_ADV_H_VERSION 010107 + + /** + * The protocol for communication to the host. Protocol indicates communication + * standards such as the use of ASCII, "echo:" and "error:" line prefixes, etc. + * (Other behaviors are given by the firmware version and capabilities report.) + */ + #define PROTOCOL_VERSION "1.0" + + /** + * Defines a generic printer name to be output to the LCD after booting Marlin. + */ + #define MACHINE_NAME "TM3D TRex2+" + + /** + * The SOURCE_CODE_URL is the location where users will find the Marlin Source + * Code which is installed on the device. In most cases —unless the manufacturer + * has a distinct Github fork— the Source Code URL should just be the main + * Marlin repository. + */ + #define SOURCE_CODE_URL "https://github.com/MarlinFirmware/Marlin" + + /** + * Default generic printer UUID. + */ + #define DEFAULT_MACHINE_UUID "cede2a2f-41a2-4748-9b12-c55c62f367ff" + + /** + * The WEBSITE_URL is the location where users can get more information such as + * documentation about a specific Marlin release. + */ + #define WEBSITE_URL "tinymachines3d.com" + +#endif // USE_AUTOMATIC_VERSIONING diff --git a/_Bootscreen.h b/_Bootscreen.h new file mode 100644 index 0000000000..64b224471c --- /dev/null +++ b/_Bootscreen.h @@ -0,0 +1,106 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 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 . + * + */ + +/** + * Custom Bitmap for splashscreen + * + * You may use one of the following tools to generate the C++ bitmap array from + * a black and white image: + * + * - http://www.marlinfw.org/tools/u8glib/converter.html + * - http://www.digole.com/tools/PicturetoC_Hex_converter.php + */ +#include + +#define CUSTOM_BOOTSCREEN_TIMEOUT 2500 +#define CUSTOM_BOOTSCREEN_BMPWIDTH 128 +#define CUSTOM_BOOTSCREEN_BMPHEIGHT 64 + +const unsigned char custom_start_bmp[] PROGMEM = { +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFD, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFD, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFE, 0x07, 0xC0, 0x05, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFC, 0x0A, 0x20, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFE, 0x14, 0x10, 0x05, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFC, 0x28, 0x08, 0x06, 0x07, 0xC0, 0x05, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5F, 0xFF, 0xFF, 0xFF, +0xFE, 0x54, 0x04, 0x04, 0x0A, 0x20, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, +0xFC, 0x60, 0x04, 0x06, 0x14, 0x10, 0x05, 0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFE, 0x50, 0x04, 0x04, 0x28, 0x08, 0x06, 0x07, 0xC0, 0x05, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5F, +0xFC, 0x60, 0x04, 0x06, 0x54, 0x04, 0x04, 0x0A, 0x20, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, +0xFE, 0x54, 0x04, 0x1C, 0x60, 0x04, 0x06, 0x14, 0x10, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, +0xFC, 0x28, 0x08, 0x2E, 0x50, 0x04, 0x04, 0x28, 0x08, 0x06, 0x07, 0xC0, 0x00, 0x01, 0xF0, 0x3F, +0xFE, 0x14, 0x10, 0x54, 0x60, 0x04, 0x06, 0x54, 0x04, 0x04, 0x0A, 0x20, 0x00, 0x02, 0x08, 0x1F, +0xFC, 0x0A, 0x20, 0x66, 0x54, 0x04, 0x1C, 0x60, 0x04, 0x06, 0x14, 0x10, 0x00, 0x05, 0x04, 0x3F, +0xFE, 0x07, 0xC0, 0x54, 0x28, 0x08, 0x2E, 0x50, 0x04, 0x04, 0x28, 0x08, 0x00, 0x0A, 0x02, 0x1F, +0xFC, 0x00, 0x00, 0x2E, 0x14, 0x10, 0x54, 0x60, 0x04, 0x06, 0x54, 0x04, 0x00, 0x15, 0x01, 0x3F, +0xFE, 0x00, 0x00, 0x1C, 0x0A, 0x20, 0x66, 0x54, 0x04, 0x1C, 0x60, 0x04, 0x00, 0x18, 0x01, 0x1F, +0xFC, 0x00, 0x00, 0x06, 0x07, 0xC0, 0x54, 0x28, 0x08, 0x2E, 0x50, 0x04, 0x00, 0x14, 0x01, 0x3F, +0xFE, 0x00, 0x00, 0x04, 0x00, 0x00, 0x2E, 0x14, 0x10, 0x54, 0x60, 0x04, 0x00, 0x18, 0x01, 0x1F, +0xFC, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1C, 0x0A, 0x20, 0x66, 0x54, 0x04, 0x1C, 0x15, 0x01, 0x3F, +0xFE, 0xD5, 0x55, 0x54, 0x00, 0x00, 0x06, 0x07, 0xC0, 0x54, 0x28, 0x08, 0x2A, 0x0A, 0x02, 0x1F, +0xFC, 0x84, 0x10, 0x46, 0x00, 0x00, 0x04, 0x00, 0x00, 0x2E, 0x14, 0x10, 0x51, 0x05, 0x04, 0x3F, +0xFE, 0xC4, 0x10, 0x44, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1C, 0x0A, 0x20, 0x61, 0x02, 0x88, 0x1F, +0xFC, 0x84, 0x10, 0x46, 0xD5, 0x55, 0x54, 0x00, 0x00, 0x06, 0x07, 0xC0, 0x51, 0x01, 0xF0, 0x3F, +0xFE, 0x84, 0x10, 0x44, 0x84, 0x10, 0x46, 0x00, 0x00, 0x04, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x1F, +0xFC, 0xC4, 0x10, 0x46, 0xC4, 0x10, 0x44, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x3F, +0xFE, 0x84, 0x10, 0x44, 0x84, 0x10, 0x46, 0xD5, 0x55, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, +0xFC, 0xD5, 0x55, 0x56, 0x84, 0x10, 0x44, 0x84, 0x10, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, +0xFC, 0xFF, 0xFF, 0xFC, 0xC4, 0x10, 0x46, 0xC4, 0x10, 0x44, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, +0xFE, 0x00, 0x00, 0x06, 0x84, 0x10, 0x44, 0x84, 0x10, 0x46, 0xD5, 0x55, 0x55, 0x55, 0x55, 0xBF, +0xFD, 0x55, 0x55, 0x54, 0xD5, 0x55, 0x56, 0x84, 0x10, 0x44, 0x84, 0x10, 0x41, 0x04, 0x10, 0x9F, +0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFC, 0xC4, 0x10, 0x46, 0xC4, 0x10, 0x41, 0x04, 0x11, 0xBF, +0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x06, 0x84, 0x10, 0x44, 0x84, 0x10, 0x41, 0x04, 0x10, 0x9F, +0xFF, 0xFF, 0xFF, 0xFD, 0x55, 0x55, 0x54, 0xD5, 0x55, 0x56, 0x84, 0x10, 0x41, 0x04, 0x10, 0x9F, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFC, 0xC4, 0x10, 0x41, 0x04, 0x11, 0xBF, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x06, 0x84, 0x10, 0x41, 0x04, 0x10, 0x9F, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x55, 0x55, 0x54, 0xD5, 0x55, 0x55, 0x55, 0x55, 0xBF, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x9F, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5F, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xE0, 0x2E, 0xFB, 0x7D, 0xFB, 0xFB, 0xCF, 0xC1, 0xDF, 0xBB, 0x3E, 0xC0, 0xE1, 0xFE, 0x3C, 0x1F, +0xFD, 0xEE, 0x7B, 0x39, 0xF9, 0xF3, 0xCF, 0x9E, 0xDF, 0xBB, 0x3E, 0xDF, 0xDE, 0xFC, 0xDD, 0xE7, +0xFD, 0xEE, 0x3B, 0xBB, 0xF9, 0xEB, 0xD7, 0xBF, 0x5F, 0xBB, 0x5E, 0xDF, 0xDE, 0xFD, 0xED, 0xF7, +0xFD, 0xEE, 0xBB, 0xD3, 0xFA, 0xEB, 0xB7, 0x3F, 0xDF, 0xBB, 0x4E, 0xDF, 0xDF, 0xFF, 0xCD, 0xF3, +0xFD, 0xEE, 0xDB, 0xC7, 0xFA, 0xEB, 0xBB, 0x7F, 0xC0, 0x3B, 0x6E, 0xC0, 0xE3, 0xFF, 0x1D, 0xF3, +0xFD, 0xEE, 0xCB, 0xEF, 0xFA, 0xDB, 0xBB, 0x7F, 0xDF, 0xBB, 0x66, 0xDF, 0xF8, 0xFF, 0xCD, 0xF3, +0xFD, 0xEE, 0xEB, 0xEF, 0xFB, 0x5B, 0x03, 0x3F, 0x5F, 0xBB, 0x76, 0xDF, 0xFE, 0x7F, 0xED, 0xF3, +0xFD, 0xEE, 0xF3, 0xEF, 0xFB, 0x5B, 0x79, 0xBE, 0xDF, 0xBB, 0x7A, 0xDF, 0xDE, 0x7D, 0xED, 0xF7, +0xFD, 0xEE, 0xF3, 0xEF, 0xFB, 0xBA, 0xFD, 0x9E, 0xDF, 0xBB, 0x7C, 0xDF, 0xDE, 0xFD, 0xCD, 0xE7, +0xFD, 0xEE, 0xFB, 0xEF, 0xFB, 0xBA, 0xFD, 0xC1, 0xDF, 0xBB, 0x7E, 0xC0, 0xE0, 0xFE, 0x1C, 0x1F, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + + + diff --git a/_Statusscreen.h b/_Statusscreen.h new file mode 100644 index 0000000000..c26149a6b2 --- /dev/null +++ b/_Statusscreen.h @@ -0,0 +1,460 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 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 . + * + */ + +/** + * Custom Status Screen bitmap + * + * Place this file in the root with your configuration files + * and enable CUSTOM_STATUS_SCREEN_IMAGE in Configuration.h. + * + * Use the Marlin Bitmap Converter to make your own: + * http://marlinfw.org/tools/u8glib/converter.html + */ +#include "MarlinConfig.h" + +//============================================ + +#define STATUS_SCREENWIDTH 128 + +#define STATUS_SCREEN_HOTEND_TEXT_X(E) (41 + (E) * 20) + +#define STATUS_SCREEN_BED_TEXT_X (HOTENDS > 1 ? 81 : 73) + +#define FAN_ANIM_FRAMES 3 +#define STATUS_SCREEN_FAN_TEXT_X (FAN_ANIM_FRAMES == 3 ? 103 : 105) +#define STATUS_SCREEN_FAN_TEXT_Y (FAN_ANIM_FRAMES > 2 ? 28 : 27) + +//============================================ + +#if HOTENDS < 2 + + #if FAN_ANIM_FRAMES <= 2 + + const unsigned char status_screen0_bmp[] PROGMEM = { + B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000, + B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00111111,B00111000, + B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111110,B00011000, + B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111100,B00011000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101000,B01111100,B00001000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101100,B00111000,B00001000, + B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B00111001,B11001000, + B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11111111,B11101000, + B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11000111,B11101000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B11111111,B11101000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100111,B00111001,B11101000, + B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B00111000,B01101000, + B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B01111100,B00101000, + B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110000,B01111100,B00011000, + B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110000,B11111100,B00011000, + B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B11111000,B00111000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000000,B11111000, + B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000 +}; + const unsigned char status_screen1_bmp[] PROGMEM = { + B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B10000000,B11111000, + B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B10000000,B00111000, + B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B10000001,B11011000, + B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B11000011,B11011000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100111,B11000111,B11101000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100011,B11000111,B11111000, + B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100001,B11111111,B10001000, + B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100000,B01101100,B00001000, + B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100000,B01101100,B00001000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100000,B01101100,B00001000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100011,B11111111,B00001000, + B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00111111,B11000111,B10001000, + B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00101111,B11000111,B11001000, + B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110111,B10000111,B11011000, + B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110111,B00000011,B11011000, + B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00000011,B00111000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000010,B11111000, + B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000 +}; + + #elif FAN_ANIM_FRAMES == 3 + + +const unsigned char status_screen0_bmp[] PROGMEM = { + B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000, + B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00111111,B00111000, + B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111110,B00011000, + B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111100,B00011000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101000,B01111100,B00001000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101100,B00111000,B00001000, + B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B00111001,B11001000, + B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11111111,B11101000, + B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11000111,B11101000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B11111111,B11101000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100111,B00111001,B11101000, + B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B00111000,B01101000, + B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B01111100,B00101000, + B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110000,B01111100,B00011000, + B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110000,B11111100,B00011000, + B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B11111000,B00111000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000000,B11111000, + B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000 +}; + + + const unsigned char status_screen1_bmp[] PROGMEM = { + B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000, + B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00001111,B00111000, + B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110100,B00011111,B11011000, + B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110110,B00011111,B10011000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B00011111,B00001000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B10011110,B00001000, + B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B11111100,B00001000, + B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11011100,B00001000, + B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100111,B11101111,B11001000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100000,B01110111,B11101000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100000,B01111111,B11101000, + B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B11110011,B11101000, + B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100001,B11110001,B11101000, + B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110011,B11110000,B11011000, + B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110111,B11110000,B01011000, + B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B11100000,B00111000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000000,B11111000, + B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000 +}; + const unsigned char status_screen2_bmp[] PROGMEM = { + B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B10000000,B11111000, + B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B10000000,B00111000, + B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B10000001,B11011000, + B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B11000011,B11011000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100111,B11000111,B11101000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100011,B11000111,B11111000, + B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100001,B11111111,B10001000, + B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100000,B01101100,B00001000, + B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100000,B01101100,B00001000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100000,B01101100,B00001000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100011,B11111111,B00001000, + B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00111111,B11000111,B10001000, + B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00101111,B11000111,B11001000, + B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110111,B10000111,B11011000, + B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110111,B00000011,B11011000, + B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00000011,B00111000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000010,B11111000, + B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000 +}; + + #elif FAN_ANIM_FRAMES == 4 + +const unsigned char status_screen0_bmp[] PROGMEM = { + B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000, + B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00111111,B00111000, + B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111110,B00011000, + B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111100,B00011000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101000,B01111100,B00001000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101100,B00111000,B00001000, + B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B00111001,B11001000, + B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11111111,B11101000, + B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11000111,B11101000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B11111111,B11101000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100111,B00111001,B11101000, + B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B00111000,B01101000, + B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B01111100,B00101000, + B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110000,B01111100,B00011000, + B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110000,B11111100,B00011000, + B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B11111000,B00111000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000000,B11111000, + B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000 +}; + +const unsigned char status_screen1_bmp[] PROGMEM = { + B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000, + B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00001111,B00111000, + B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110100,B00011111,B11011000, + B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110110,B00011111,B10011000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B00011111,B00001000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B10011110,B00001000, + B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B11111100,B00001000, + B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101111,B11011100,B00001000, + B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100111,B11101111,B11001000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100000,B01110111,B11101000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100000,B01111111,B11101000, + B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100000,B11110011,B11101000, + B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00100001,B11110001,B11101000, + B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110011,B11110000,B11011000, + B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110111,B11110000,B01011000, + B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B11100000,B00111000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000000,B11111000, + B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000 +}; + +const unsigned char status_screen2_bmp[] PROGMEM = { + B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B10000000,B11111000, + B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B10000000,B00111000, + B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B10000001,B11011000, + B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B11000011,B11011000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100111,B11000111,B11101000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100011,B11000111,B11111000, + B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100001,B11111111,B10001000, + B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100000,B01101100,B00001000, + B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100000,B01101100,B00001000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100000,B01101100,B00001000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00100011,B11111111,B00001000, + B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00111111,B11000111,B10001000, + B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00101111,B11000111,B11001000, + B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110111,B10000111,B11011000, + B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110111,B00000011,B11011000, + B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00000011,B00111000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000010,B11111000, + B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000 +}; + + +const unsigned char status_screen3_bmp[] PROGMEM = { + B11111111,B11111111,B11111111,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000, + B10001110,B00000000,B11100001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B11110000,B00111000, + B10011111,B00000000,B11110001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110001,B11100000,B00011000, + B10010011,B10000001,B00111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110001,B11100000,B00011000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100001,B11100001,B11101000, + B10011111,B10000001,B11111001,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100000,B11110011,B11101000, + B10011111,B10111001,B11110001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00100000,B01111111,B11101000, + B10001111,B00101000,B11110001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00100000,B01110111,B11101000, + B10000000,B00111000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00000100,B00010000,B01000000,B00000000,B00101000,B11101110,B00101000, + B10000000,B00000000,B00000001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00101111,B11011100,B00001000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00101111,B11111100,B00001000, + B10010001,B01110100,B10011001,B00000000,B00000000,B00011111,B11100000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00101111,B10011110,B00001000, + B10011011,B00000110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00100000,B10000010,B00000000,B00000000,B00101111,B00001111,B00001000, + B10011011,B01010100,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00010000,B01000001,B00000000,B00000000,B00110000,B00001111,B00011000, + B10011011,B01010110,B10101001,B00000000,B00000000,B00111111,B11110000,B00000000,B00000000,B00001000,B00100000,B10000000,B00000000,B00110000,B00001111,B00011000, + B10011011,B01010100,B10011001,B00000000,B00000000,B00001111,B11000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00011111,B00111000, + B10011111,B11111111,B11111001,B00000000,B00000000,B00000111,B10000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111110,B00000000,B11111000, + B11111111,B11111111,B11111111,B00000000,B00000000,B00000011,B00000000,B00000000,B00000000,B11111111,B11111111,B11000000,B00000000,B00111111,B11111111,B11111000 +}; + + + #endif + +#else // HOTENDS >= 2 + + #if FAN_ANIM_FRAMES <= 2 + + const unsigned char status_screen0_bmp[] PROGMEM = { + B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11110000, + B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00000000,B01110000, + B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B11111100,B00110000, + B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100000,B11111100,B00010000, + B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100000,B01111000,B00010000, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100000,B00110000,B00010000, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101100,B00000000,B11010000, + B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00101110,B00110001,B11010000, + B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00101111,B01111011,B11010000, + B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00101111,B01111011,B11010000, + B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00101110,B00110001,B11010000, + B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00101100,B00000000,B11010000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00100000,B00110000,B00010000, + B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00100000,B01111000,B00010000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00100000,B11111100,B00010000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00110000,B11111100,B00110000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00111000,B00000000,B01110000, + B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00111111,B11111111,B11110000, + B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00000000,B00000000,B00000000 + }; + const unsigned char status_screen1_bmp[] PROGMEM = { + B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11110000, + B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00000000,B01110000, + B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110011,B10000111,B00110000, + B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100111,B10000111,B10010000, + B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B10000111,B11010000, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B10000111,B11010000, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B00000011,B11010000, + B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00100000,B00110000,B00010000, + B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00100000,B01111000,B00010000, + B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00100000,B01111000,B00010000, + B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00100000,B00110000,B00010000, + B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00101111,B00000011,B11010000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00101111,B10000111,B11010000, + B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00101111,B10000111,B11010000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00100111,B10000111,B10010000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00110011,B10000111,B00110000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00111000,B00000000,B01110000, + B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00111111,B11111111,B11110000, + B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00000000,B00000000,B00000000 + }; + + #elif FAN_ANIM_FRAMES == 3 + + const unsigned char status_screen0_bmp[] PROGMEM = { + B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B11111111,B11111111, + B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B11000000,B00011111, + B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B00100000,B00100111, + B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000110,B11110000,B01111011, + B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000110,B11110000,B01111011, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000101,B11111000,B11111101, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000100,B11111000,B11111001, + B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00000100,B00111111,B11100001, + B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00000100,B00001111,B10000001, + B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00000100,B00001111,B10000001, + B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00000100,B00001111,B10000001, + B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00000100,B00111111,B11100001, + B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00000100,B11111000,B11111001, + B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00000101,B11111000,B11111101, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00000110,B11110000,B01111011, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00000110,B11110000,B01111011, + B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00000111,B00100000,B00100111, + B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00000111,B11000000,B00011111, + B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00000111,B11111111,B11111111 + }; + const unsigned char status_screen1_bmp[] PROGMEM = { + B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B11111111,B11111111, + B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B11000110,B00011111, + B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B00111110,B00000111, + B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000110,B00111110,B00000011, + B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000110,B00011110,B00000011, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000100,B00011110,B00001101, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000100,B00000110,B00111101, + B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00000100,B00000111,B00111101, + B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00000100,B00001111,B11111111, + B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00000111,B11111111,B11111111, + B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00000111,B11111111,B10000001, + B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00000101,B11100111,B00000001, + B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00000101,B11000011,B00000001, + B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00000101,B10000011,B11000001, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00000110,B00000011,B11000011, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00000110,B00000011,B11100011, + B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00000111,B00000011,B11100111, + B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00000111,B11000011,B00011111, + B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00000111,B11111111,B11111111 + }; + const unsigned char status_screen2_bmp[] PROGMEM = { + B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B11111111,B11111111, + B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B11000011,B00011111, + B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000111,B00000011,B11100111, + B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000110,B00000011,B11110011, + B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000110,B10000011,B11100011, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000101,B11000011,B11000001, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000101,B11100011,B10000001, + B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00000101,B11110111,B00000001, + B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00000111,B11111111,B10000001, + B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00000111,B11111111,B11111111, + B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00000100,B00001111,B11111111, + B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00000100,B00000111,B01111101, + B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00000100,B00001110,B00111101, + B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00000100,B00011110,B00011101, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00000110,B00111110,B00001011, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00000110,B01111110,B00000011, + B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00000111,B00111110,B00000111, + B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00000111,B11000110,B00011111, + B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00000111,B11111111,B11111111 + }; + + #elif FAN_ANIM_FRAMES == 4 + + const unsigned char status_screen0_bmp[] PROGMEM = { + B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000, + B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000, + B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00111111,B00111000, + B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111110,B00011000, + B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110000,B01111100,B00011000, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101000,B01111100,B00001000, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101100,B00111000,B00001000, + B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00101111,B00111001,B11001000, + B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00101111,B11111111,B11101000, + B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00101111,B11000111,B11101000, + B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00101111,B11111111,B11101000, + B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00100111,B00111001,B11101000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00100000,B00111000,B01101000, + B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00100000,B01111100,B00101000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00110000,B01111100,B00011000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00110000,B11111100,B00011000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00111001,B11111000,B00111000, + B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00111110,B00000000,B11111000, + B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00111111,B11111111,B11111000 + }; + const unsigned char status_screen1_bmp[] PROGMEM = { + B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000, + B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000, + B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111000,B00001111,B00111000, + B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110100,B00011111,B11011000, + B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110110,B00011111,B10011000, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B00011111,B00001000, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00101111,B10011110,B00001000, + B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00101111,B11111100,B00001000, + B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00101111,B11011100,B00001000, + B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00100111,B11101111,B11001000, + B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00100000,B01110111,B11101000, + B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00100000,B01111111,B11101000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00100000,B11110011,B11101000, + B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00100001,B11110001,B11101000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00110011,B11110000,B11011000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00110111,B11110000,B01011000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00111001,B11100000,B00111000, + B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00111110,B00000000,B11111000, + B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00111111,B11111111,B11111000 + }; + const unsigned char status_screen2_bmp[] PROGMEM = { + B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000, + B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B10000000,B11111000, + B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B10000000,B00111000, + B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B10000001,B11011000, + B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110111,B11000011,B11011000, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100111,B11000111,B11101000, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100011,B11000111,B11111000, + B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00100001,B11111111,B10001000, + B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00100000,B01101100,B00001000, + B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00100000,B01101100,B00001000, + B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00100000,B01101100,B00001000, + B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00100011,B11111111,B00001000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00111111,B11000111,B10001000, + B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00101111,B11000111,B11001000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00110111,B10000111,B11011000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00110111,B00000011,B11011000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00111000,B00000011,B00111000, + B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00111110,B00000010,B11111000, + B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00111111,B11111111,B11111000 + }; + const unsigned char status_screen3_bmp[] PROGMEM = { + B00111101,B11110000,B00000010,B00111000,B11110000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111111,B11111111,B11111000, + B01000100,B10001000,B00000110,B01000101,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111110,B00000000,B11111000, + B10000000,B10001000,B00000010,B01000101,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00111001,B11110000,B00111000, + B10000000,B11110000,B00000010,B01000100,B10000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110001,B11100000,B00011000, + B10000000,B10100011,B11110010,B01000100,B01100000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00110001,B11100000,B00011000, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100001,B11100001,B11101000, + B10000000,B10010000,B00000010,B01000100,B00010000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00100000,B11110011,B11101000, + B01000100,B10001000,B00000010,B01000101,B00010000,B00011111,B11100000,B00000001,B11111110,B00000000,B00001000,B00100000,B10000000,B00100000,B01111111,B11101000, + B00111001,B11001100,B00000111,B00111001,B11100000,B00111110,B11110000,B00000011,B11001111,B00000000,B00000100,B00010000,B01000000,B00100000,B01110111,B11101000, + B00000000,B00000000,B00000000,B00000000,B00000000,B00111100,B11110000,B00000011,B10110111,B00000000,B00000100,B00010000,B01000000,B00101000,B11101110,B00101000, + B00000000,B00111000,B01110000,B11100000,B00000000,B00111010,B11110000,B00000011,B11110111,B00000000,B00001000,B00100000,B10000000,B00101111,B11011100,B00001000, + B00000000,B01000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11101110,B00000000,B00010000,B01000001,B00000000,B00101111,B11111100,B00001000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00011110,B11100000,B00000001,B11011110,B00000000,B00100000,B10000010,B00000000,B00101111,B10011110,B00001000, + B00000000,B00011000,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10111111,B00000000,B00100000,B10000010,B00000000,B00101111,B00001111,B00001000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111110,B11110000,B00000011,B10000111,B00000000,B00010000,B01000001,B00000000,B00110000,B00001111,B00011000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00111111,B11110000,B00000011,B11111111,B00000000,B00001000,B00100000,B10000000,B00110000,B00001111,B00011000, + B00000000,B00000100,B10001001,B00010000,B00000000,B00001111,B11000000,B00000000,B11111100,B00000000,B00000000,B00000000,B00000000,B00111000,B00011111,B00111000, + B00000000,B01000100,B10001001,B00010000,B00000000,B00000111,B10000000,B00000000,B01111000,B00000000,B11111111,B11111111,B11000000,B00111110,B00000000,B11111000, + B00000000,B00111000,B01110000,B11100000,B00000000,B00000011,B00000000,B00000000,B00110000,B00000000,B11111111,B11111111,B11000000,B00111111,B11111111,B11111000 + }; + + #endif + +#endif // HOTENDS >= 2 diff --git a/language_en.h b/language_en.h new file mode 100644 index 0000000000..1d4cd887c2 --- /dev/null +++ b/language_en.h @@ -0,0 +1,1071 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 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 . + * + */ + +/** + * English + * + * LCD Menu Messages + * See also http://marlinfw.org/docs/development/lcd_language.html + * + */ +#ifndef LANGUAGE_EN_H +#define LANGUAGE_EN_H + +#define en 1234 +#if LCD_LANGUAGE == en + #define NOT_EXTENDED_ISO10646_1_5X7 +#endif +#undef en + +#ifndef CHARSIZE + #define CHARSIZE 1 +#endif + +#ifndef WELCOME_MSG + #define WELCOME_MSG MACHINE_NAME _UxGT(" Ready.") +#endif +#ifndef MSG_BACK + #define MSG_BACK _UxGT("Back") +#endif +#ifndef MSG_SD_INSERTED + #define MSG_SD_INSERTED _UxGT("Card inserted") +#endif +#ifndef MSG_SD_REMOVED + #define MSG_SD_REMOVED _UxGT("Card removed") +#endif +#ifndef MSG_LCD_ENDSTOPS + #define MSG_LCD_ENDSTOPS _UxGT("Endstops") // Max length 8 characters +#endif +#ifndef MSG_MAIN + #define MSG_MAIN _UxGT("Main") +#endif +#ifndef MSG_AUTOSTART + #define MSG_AUTOSTART _UxGT("Autostart") +#endif +#ifndef MSG_DISABLE_STEPPERS + #define MSG_DISABLE_STEPPERS _UxGT("Disable steppers") +#endif +#ifndef MSG_DEBUG_MENU + #define MSG_DEBUG_MENU _UxGT("Debug Menu") +#endif +#ifndef MSG_PROGRESS_BAR_TEST + #define MSG_PROGRESS_BAR_TEST _UxGT("Progress Bar Test") +#endif +#ifndef MSG_AUTO_HOME + #define MSG_AUTO_HOME _UxGT("Auto home") +#endif +#ifndef MSG_AUTO_HOME_X + #define MSG_AUTO_HOME_X _UxGT("Home X") +#endif +#ifndef MSG_AUTO_HOME_Y + #define MSG_AUTO_HOME_Y _UxGT("Home Y") +#endif +#ifndef MSG_AUTO_HOME_Z + #define MSG_AUTO_HOME_Z _UxGT("Home Z") +#endif +#ifndef MSG_LEVEL_BED_HOMING + #define MSG_LEVEL_BED_HOMING _UxGT("Homing XYZ") +#endif +#ifndef MSG_LEVEL_BED_WAITING + #define MSG_LEVEL_BED_WAITING _UxGT("Click to Begin") +#endif +#ifndef MSG_LEVEL_BED_NEXT_POINT + #define MSG_LEVEL_BED_NEXT_POINT _UxGT("Next Point") +#endif +#ifndef MSG_LEVEL_BED_DONE + #define MSG_LEVEL_BED_DONE _UxGT("Leveling Done!") +#endif +#ifndef MSG_Z_FADE_HEIGHT + #define MSG_Z_FADE_HEIGHT _UxGT("Fade Height") +#endif +#ifndef MSG_SET_HOME_OFFSETS + #define MSG_SET_HOME_OFFSETS _UxGT("Set home offsets") +#endif +#ifndef MSG_HOME_OFFSETS_APPLIED + #define MSG_HOME_OFFSETS_APPLIED _UxGT("Offsets applied") +#endif +#ifndef MSG_SET_ORIGIN + #define MSG_SET_ORIGIN _UxGT("Set origin") +#endif +#ifndef MSG_PREHEAT_1 + #define MSG_PREHEAT_1 _UxGT("Preheat PLA") +#endif +#ifndef MSG_PREHEAT_1_N + #define MSG_PREHEAT_1_N MSG_PREHEAT_1 _UxGT(" ") +#endif +#ifndef MSG_PREHEAT_1_ALL + #define MSG_PREHEAT_1_ALL MSG_PREHEAT_1 _UxGT(" All") +#endif +#ifndef MSG_PREHEAT_1_END + #define MSG_PREHEAT_1_END MSG_PREHEAT_1 _UxGT(" End") +#endif +#ifndef MSG_PREHEAT_1_BEDONLY + #define MSG_PREHEAT_1_BEDONLY MSG_PREHEAT_1 _UxGT(" Bed") +#endif +#ifndef MSG_PREHEAT_1_SETTINGS + #define MSG_PREHEAT_1_SETTINGS MSG_PREHEAT_1 _UxGT(" conf") +#endif +#ifndef MSG_PREHEAT_2 + #define MSG_PREHEAT_2 _UxGT("Preheat ABS") +#endif +#ifndef MSG_PREHEAT_2_N + #define MSG_PREHEAT_2_N MSG_PREHEAT_2 _UxGT(" ") +#endif +#ifndef MSG_PREHEAT_2_ALL + #define MSG_PREHEAT_2_ALL MSG_PREHEAT_2 _UxGT(" All") +#endif +#ifndef MSG_PREHEAT_2_END + #define MSG_PREHEAT_2_END MSG_PREHEAT_2 _UxGT(" End") +#endif +#ifndef MSG_PREHEAT_2_BEDONLY + #define MSG_PREHEAT_2_BEDONLY MSG_PREHEAT_2 _UxGT(" Bed") +#endif +#ifndef MSG_PREHEAT_2_SETTINGS + #define MSG_PREHEAT_2_SETTINGS MSG_PREHEAT_2 _UxGT(" conf") +#endif +#ifndef MSG_COOLDOWN + #define MSG_COOLDOWN _UxGT("Cooldown") +#endif +#ifndef MSG_SWITCH_PS_ON + #define MSG_SWITCH_PS_ON _UxGT("Switch power on") +#endif +#ifndef MSG_SWITCH_PS_OFF + #define MSG_SWITCH_PS_OFF _UxGT("Switch power off") +#endif +#ifndef MSG_EXTRUDE + #define MSG_EXTRUDE _UxGT("Extrude") +#endif +#ifndef MSG_RETRACT + #define MSG_RETRACT _UxGT("Retract") +#endif +#ifndef MSG_MOVE_AXIS + #define MSG_MOVE_AXIS _UxGT("Move axis") +#endif +#ifndef MSG_BED_LEVELING + #define MSG_BED_LEVELING _UxGT("Bed Leveling") +#endif +#ifndef MSG_LEVEL_BED + #define MSG_LEVEL_BED _UxGT("Level bed") +#endif +#ifndef MSG_LEVEL_CORNERS + #define MSG_LEVEL_CORNERS _UxGT("Level corners") +#endif +#ifndef MSG_NEXT_CORNER + #define MSG_NEXT_CORNER _UxGT("Next corner") +#endif +#ifndef MSG_EDITING_STOPPED + #define MSG_EDITING_STOPPED _UxGT("Mesh Editing Stopped") +#endif +#ifndef MSG_USER_MENU + #define MSG_USER_MENU _UxGT("Custom Commands") +#endif +#ifndef MSG_UBL_DOING_G29 + #define MSG_UBL_DOING_G29 _UxGT("Doing G29") +#endif +#ifndef MSG_UBL_UNHOMED + #define MSG_UBL_UNHOMED _UxGT("Home XYZ first") +#endif +#ifndef MSG_UBL_TOOLS + #define MSG_UBL_TOOLS _UxGT("UBL Tools") +#endif +#ifndef MSG_UBL_LEVEL_BED + #define MSG_UBL_LEVEL_BED _UxGT("Unified Bed Leveling") +#endif +#ifndef MSG_UBL_MANUAL_MESH + #define MSG_UBL_MANUAL_MESH _UxGT("Manually Build Mesh") +#endif +#ifndef MSG_UBL_BC_INSERT + #define MSG_UBL_BC_INSERT _UxGT("Place shim & measure") +#endif +#ifndef MSG_UBL_BC_INSERT2 + #define MSG_UBL_BC_INSERT2 _UxGT("Measure") +#endif +#ifndef MSG_UBL_BC_REMOVE + #define MSG_UBL_BC_REMOVE _UxGT("Remove & measure bed") +#endif +#ifndef MSG_UBL_MOVING_TO_NEXT + #define MSG_UBL_MOVING_TO_NEXT _UxGT("Moving to next") +#endif +#ifndef MSG_UBL_ACTIVATE_MESH + #define MSG_UBL_ACTIVATE_MESH _UxGT("Activate UBL") +#endif +#ifndef MSG_UBL_DEACTIVATE_MESH + #define MSG_UBL_DEACTIVATE_MESH _UxGT("Deactivate UBL") +#endif +#ifndef MSG_UBL_SET_BED_TEMP + #define MSG_UBL_SET_BED_TEMP _UxGT("Bed Temp") +#endif +#ifndef MSG_UBL_CUSTOM_BED_TEMP + #define MSG_UBL_CUSTOM_BED_TEMP MSG_UBL_SET_BED_TEMP +#endif +#ifndef MSG_UBL_SET_HOTEND_TEMP + #define MSG_UBL_SET_HOTEND_TEMP _UxGT("Hotend Temp") +#endif +#ifndef MSG_UBL_CUSTOM_HOTEND_TEMP + #define MSG_UBL_CUSTOM_HOTEND_TEMP MSG_UBL_SET_HOTEND_TEMP +#endif +#ifndef MSG_UBL_MESH_EDIT + #define MSG_UBL_MESH_EDIT _UxGT("Mesh Edit") +#endif +#ifndef MSG_UBL_EDIT_CUSTOM_MESH + #define MSG_UBL_EDIT_CUSTOM_MESH _UxGT("Edit Custom Mesh") +#endif +#ifndef MSG_UBL_FINE_TUNE_MESH + #define MSG_UBL_FINE_TUNE_MESH _UxGT("Fine Tuning Mesh") +#endif +#ifndef MSG_UBL_DONE_EDITING_MESH + #define MSG_UBL_DONE_EDITING_MESH _UxGT("Done Editing Mesh") +#endif +#ifndef MSG_UBL_BUILD_CUSTOM_MESH + #define MSG_UBL_BUILD_CUSTOM_MESH _UxGT("Build Custom Mesh") +#endif +#ifndef MSG_UBL_BUILD_MESH_MENU + #define MSG_UBL_BUILD_MESH_MENU _UxGT("Build Mesh") +#endif +#ifndef MSG_UBL_BUILD_PLA_MESH + #define MSG_UBL_BUILD_PLA_MESH _UxGT("Build PLA Mesh") +#endif +#ifndef MSG_UBL_BUILD_ABS_MESH + #define MSG_UBL_BUILD_ABS_MESH _UxGT("Build ABS Mesh") +#endif +#ifndef MSG_UBL_BUILD_COLD_MESH + #define MSG_UBL_BUILD_COLD_MESH _UxGT("Build Cold Mesh") +#endif +#ifndef MSG_UBL_MESH_HEIGHT_ADJUST + #define MSG_UBL_MESH_HEIGHT_ADJUST _UxGT("Adjust Mesh Height") +#endif +#ifndef MSG_UBL_MESH_HEIGHT_AMOUNT + #define MSG_UBL_MESH_HEIGHT_AMOUNT _UxGT("Height Amount") +#endif +#ifndef MSG_UBL_VALIDATE_MESH_MENU + #define MSG_UBL_VALIDATE_MESH_MENU _UxGT("Validate Mesh") +#endif +#ifndef MSG_UBL_VALIDATE_PLA_MESH + #define MSG_UBL_VALIDATE_PLA_MESH _UxGT("Validate PLA Mesh") +#endif +#ifndef MSG_UBL_VALIDATE_ABS_MESH + #define MSG_UBL_VALIDATE_ABS_MESH _UxGT("Validate ABS Mesh") +#endif +#ifndef MSG_UBL_VALIDATE_CUSTOM_MESH + #define MSG_UBL_VALIDATE_CUSTOM_MESH _UxGT("Validate Custom Mesh") +#endif +#ifndef MSG_UBL_CONTINUE_MESH + #define MSG_UBL_CONTINUE_MESH _UxGT("Continue Bed Mesh") +#endif +#ifndef MSG_UBL_MESH_LEVELING + #define MSG_UBL_MESH_LEVELING _UxGT("Mesh Leveling") +#endif +#ifndef MSG_UBL_3POINT_MESH_LEVELING + #define MSG_UBL_3POINT_MESH_LEVELING _UxGT("3-Point Leveling") +#endif +#ifndef MSG_UBL_GRID_MESH_LEVELING + #define MSG_UBL_GRID_MESH_LEVELING _UxGT("Grid Mesh Leveling") +#endif +#ifndef MSG_UBL_MESH_LEVEL + #define MSG_UBL_MESH_LEVEL _UxGT("Level Mesh") +#endif +#ifndef MSG_UBL_SIDE_POINTS + #define MSG_UBL_SIDE_POINTS _UxGT("Side Points") +#endif +#ifndef MSG_UBL_MAP_TYPE + #define MSG_UBL_MAP_TYPE _UxGT("Map Type") +#endif +#ifndef MSG_UBL_OUTPUT_MAP + #define MSG_UBL_OUTPUT_MAP _UxGT("Output Mesh Map") +#endif +#ifndef MSG_UBL_OUTPUT_MAP_HOST + #define MSG_UBL_OUTPUT_MAP_HOST _UxGT("Output for Host") +#endif +#ifndef MSG_UBL_OUTPUT_MAP_CSV + #define MSG_UBL_OUTPUT_MAP_CSV _UxGT("Output for CSV") +#endif +#ifndef MSG_UBL_OUTPUT_MAP_BACKUP + #define MSG_UBL_OUTPUT_MAP_BACKUP _UxGT("Off Printer Backup") +#endif +#ifndef MSG_UBL_INFO_UBL + #define MSG_UBL_INFO_UBL _UxGT("Output UBL Info") +#endif +#ifndef MSG_UBL_EDIT_MESH_MENU + #define MSG_UBL_EDIT_MESH_MENU _UxGT("Edit Mesh") +#endif +#ifndef MSG_UBL_FILLIN_AMOUNT + #define MSG_UBL_FILLIN_AMOUNT _UxGT("Fill-in Amount") +#endif +#ifndef MSG_UBL_MANUAL_FILLIN + #define MSG_UBL_MANUAL_FILLIN _UxGT("Manual Fill-in") +#endif +#ifndef MSG_UBL_SMART_FILLIN + #define MSG_UBL_SMART_FILLIN _UxGT("Smart Fill-in") +#endif +#ifndef MSG_UBL_FILLIN_MESH + #define MSG_UBL_FILLIN_MESH _UxGT("Fill-in Mesh") +#endif +#ifndef MSG_UBL_INVALIDATE_ALL + #define MSG_UBL_INVALIDATE_ALL _UxGT("Invalidate All") +#endif +#ifndef MSG_UBL_INVALIDATE_CLOSEST + #define MSG_UBL_INVALIDATE_CLOSEST _UxGT("Invalidate Closest") +#endif +#ifndef MSG_UBL_FINE_TUNE_ALL + #define MSG_UBL_FINE_TUNE_ALL _UxGT("Fine Tune All") +#endif +#ifndef MSG_UBL_FINE_TUNE_CLOSEST + #define MSG_UBL_FINE_TUNE_CLOSEST _UxGT("Fine Tune Closest") +#endif +#ifndef MSG_UBL_STORAGE_MESH_MENU + #define MSG_UBL_STORAGE_MESH_MENU _UxGT("Mesh Storage") +#endif +#ifndef MSG_UBL_STORAGE_SLOT + #define MSG_UBL_STORAGE_SLOT _UxGT("Memory Slot") +#endif +#ifndef MSG_UBL_LOAD_MESH + #define MSG_UBL_LOAD_MESH _UxGT("Load Bed Mesh") +#endif +#ifndef MSG_UBL_SAVE_MESH + #define MSG_UBL_SAVE_MESH _UxGT("Save Bed Mesh") +#endif +#ifndef MSG_MESH_LOADED + #define MSG_MESH_LOADED _UxGT("Mesh %i loaded") +#endif +#ifndef MSG_MESH_SAVED + #define MSG_MESH_SAVED _UxGT("Mesh %i saved") +#endif +#ifndef MSG_NO_STORAGE + #define MSG_NO_STORAGE _UxGT("No storage") +#endif +#ifndef MSG_UBL_SAVE_ERROR + #define MSG_UBL_SAVE_ERROR _UxGT("Err: UBL Save") +#endif +#ifndef MSG_UBL_RESTORE_ERROR + #define MSG_UBL_RESTORE_ERROR _UxGT("Err: UBL Restore") +#endif +#ifndef MSG_UBL_Z_OFFSET_STOPPED + #define MSG_UBL_Z_OFFSET_STOPPED _UxGT("Z-Offset Stopped") +#endif +#ifndef MSG_UBL_STEP_BY_STEP_MENU + #define MSG_UBL_STEP_BY_STEP_MENU _UxGT("Step-By-Step UBL") +#endif + +#ifndef MSG_LED_CONTROL + #define MSG_LED_CONTROL _UxGT("LED Control") +#endif +#ifndef MSG_LEDS_ON + #define MSG_LEDS_ON _UxGT("Lights On") +#endif +#ifndef MSG_LEDS_OFF + #define MSG_LEDS_OFF _UxGT("Lights Off") +#endif +#ifndef MSG_LED_PRESETS + #define MSG_LED_PRESETS _UxGT("Light Presets") +#endif +#ifndef MSG_SET_LEDS_RED + #define MSG_SET_LEDS_RED _UxGT("Red") +#endif +#ifndef MSG_SET_LEDS_ORANGE + #define MSG_SET_LEDS_ORANGE _UxGT("Orange") +#endif +#ifndef MSG_SET_LEDS_YELLOW + #define MSG_SET_LEDS_YELLOW _UxGT("Yellow") +#endif +#ifndef MSG_SET_LEDS_GREEN + #define MSG_SET_LEDS_GREEN _UxGT("Green") +#endif +#ifndef MSG_SET_LEDS_BLUE + #define MSG_SET_LEDS_BLUE _UxGT("Blue") +#endif +#ifndef MSG_SET_LEDS_INDIGO + #define MSG_SET_LEDS_INDIGO _UxGT("Indigo") +#endif +#ifndef MSG_SET_LEDS_VIOLET + #define MSG_SET_LEDS_VIOLET _UxGT("Violet") +#endif +#ifndef MSG_SET_LEDS_WHITE + #define MSG_SET_LEDS_WHITE _UxGT("White") +#endif +#ifndef MSG_SET_LEDS_DEFAULT + #define MSG_SET_LEDS_DEFAULT _UxGT("Default") +#endif +#ifndef MSG_CUSTOM_LEDS + #define MSG_CUSTOM_LEDS _UxGT("Custom Lights") +#endif +#ifndef MSG_INTENSITY_R + #define MSG_INTENSITY_R _UxGT("Red Intensity") +#endif +#ifndef MSG_INTENSITY_G + #define MSG_INTENSITY_G _UxGT("Green Intensity") +#endif +#ifndef MSG_INTENSITY_B + #define MSG_INTENSITY_B _UxGT("Blue Intensity") +#endif +#ifndef MSG_INTENSITY_W + #define MSG_INTENSITY_W _UxGT("White Intensity") +#endif +#ifndef MSG_LED_BRIGHTNESS + #define MSG_LED_BRIGHTNESS _UxGT("Brightness") +#endif + +#ifndef MSG_MOVING + #define MSG_MOVING _UxGT("Moving...") +#endif +#ifndef MSG_FREE_XY + #define MSG_FREE_XY _UxGT("Free XY") +#endif +#ifndef MSG_MOVE_X + #define MSG_MOVE_X _UxGT("Move X") +#endif +#ifndef MSG_MOVE_Y + #define MSG_MOVE_Y _UxGT("Move Y") +#endif +#ifndef MSG_MOVE_Z + #define MSG_MOVE_Z _UxGT("Move Z") +#endif +#ifndef MSG_MOVE_E + #define MSG_MOVE_E _UxGT("Extruder") +#endif +#ifndef MSG_MOVE_01MM + #define MSG_MOVE_01MM _UxGT("Move 0.1mm") +#endif +#ifndef MSG_MOVE_1MM + #define MSG_MOVE_1MM _UxGT("Move 1mm") +#endif +#ifndef MSG_MOVE_10MM + #define MSG_MOVE_10MM _UxGT("Move 10mm") +#endif +#ifndef MSG_SPEED + #define MSG_SPEED _UxGT("Speed") +#endif +#ifndef MSG_BED_Z + #define MSG_BED_Z _UxGT("Bed Z") +#endif +#ifndef MSG_NOZZLE + #define MSG_NOZZLE _UxGT("Nozzle") +#endif +#ifndef MSG_BED + #define MSG_BED _UxGT("Bed") +#endif +#ifndef MSG_FAN_SPEED + #define MSG_FAN_SPEED _UxGT("Fan speed") +#endif + +#ifndef MSG_LASER_ON + #define MSG_LASER_ON _UxGT("Laser On") +#endif +#ifndef MSG_LASER_OFF + #define MSG_LASER_OFF _UxGT("Laser Off") +#endif + +#ifndef MSG_FAN_SPEED + #define MSG_FAN_SPEED _UxGT("Fan speed") +#endif +#ifndef MSG_EXTRA_FAN_SPEED + #define MSG_EXTRA_FAN_SPEED _UxGT("Extra fan speed") +#endif +#ifndef MSG_FLOW + #define MSG_FLOW _UxGT("Flow") +#endif +#ifndef MSG_CONTROL + #define MSG_CONTROL _UxGT("Control") +#endif +#ifndef MSG_MIN + #define MSG_MIN _UxGT(" ") LCD_STR_THERMOMETER _UxGT(" Min") +#endif +#ifndef MSG_MAX + #define MSG_MAX _UxGT(" ") LCD_STR_THERMOMETER _UxGT(" Max") +#endif +#ifndef MSG_FACTOR + #define MSG_FACTOR _UxGT(" ") LCD_STR_THERMOMETER _UxGT(" Fact") +#endif +#ifndef MSG_AUTOTEMP + #define MSG_AUTOTEMP _UxGT("Autotemp") +#endif +#ifndef MSG_ON + #define MSG_ON _UxGT("On ") +#endif +#ifndef MSG_OFF + #define MSG_OFF _UxGT("Off") +#endif +#ifndef MSG_PID_P + #define MSG_PID_P _UxGT("PID-P") +#endif +#ifndef MSG_PID_I + #define MSG_PID_I _UxGT("PID-I") +#endif +#ifndef MSG_PID_D + #define MSG_PID_D _UxGT("PID-D") +#endif +#ifndef MSG_PID_C + #define MSG_PID_C _UxGT("PID-C") +#endif +#ifndef MSG_SELECT + #define MSG_SELECT _UxGT("Select") +#endif +#ifndef MSG_ACC + #define MSG_ACC _UxGT("Accel") +#endif +#ifndef MSG_JERK + #define MSG_JERK _UxGT("Jerk") +#endif +#if IS_KINEMATIC + #ifndef MSG_VA_JERK + #define MSG_VA_JERK _UxGT("Va-jerk") + #endif + #ifndef MSG_VB_JERK + #define MSG_VB_JERK _UxGT("Vb-jerk") + #endif + #ifndef MSG_VC_JERK + #define MSG_VC_JERK _UxGT("Vc-jerk") + #endif +#else + #ifndef MSG_VA_JERK + #define MSG_VA_JERK _UxGT("Vx-jerk") + #endif + #ifndef MSG_VB_JERK + #define MSG_VB_JERK _UxGT("Vy-jerk") + #endif + #ifndef MSG_VC_JERK + #define MSG_VC_JERK _UxGT("Vz-jerk") + #endif +#endif +#ifndef MSG_VE_JERK + #define MSG_VE_JERK _UxGT("Ve-jerk") +#endif +#ifndef MSG_VELOCITY + #define MSG_VELOCITY _UxGT("Velocity") +#endif +#ifndef MSG_VMAX + #define MSG_VMAX _UxGT("Vmax ") +#endif +#ifndef MSG_VMIN + #define MSG_VMIN _UxGT("Vmin") +#endif +#ifndef MSG_VTRAV_MIN + #define MSG_VTRAV_MIN _UxGT("VTrav min") +#endif +#ifndef MSG_ACCELERATION + #define MSG_ACCELERATION _UxGT("Acceleration") +#endif +#ifndef MSG_AMAX + #define MSG_AMAX _UxGT("Amax ") +#endif +#ifndef MSG_A_RETRACT + #define MSG_A_RETRACT _UxGT("A-retract") +#endif +#ifndef MSG_A_TRAVEL + #define MSG_A_TRAVEL _UxGT("A-travel") +#endif +#ifndef MSG_STEPS_PER_MM + #define MSG_STEPS_PER_MM _UxGT("Steps/mm") +#endif +#if IS_KINEMATIC + #ifndef MSG_ASTEPS + #define MSG_ASTEPS _UxGT("Asteps/mm") + #endif + #ifndef MSG_BSTEPS + #define MSG_BSTEPS _UxGT("Bsteps/mm") + #endif + #ifndef MSG_CSTEPS + #define MSG_CSTEPS _UxGT("Csteps/mm") + #endif +#else + #ifndef MSG_ASTEPS + #define MSG_ASTEPS _UxGT("Xsteps/mm") + #endif + #ifndef MSG_BSTEPS + #define MSG_BSTEPS _UxGT("Ysteps/mm") + #endif + #ifndef MSG_CSTEPS + #define MSG_CSTEPS _UxGT("Zsteps/mm") + #endif +#endif +#ifndef MSG_ESTEPS + #define MSG_ESTEPS _UxGT("Esteps/mm") +#endif +#ifndef MSG_E1STEPS + #define MSG_E1STEPS _UxGT("E1steps/mm") +#endif +#ifndef MSG_E2STEPS + #define MSG_E2STEPS _UxGT("E2steps/mm") +#endif +#ifndef MSG_E3STEPS + #define MSG_E3STEPS _UxGT("E3steps/mm") +#endif +#ifndef MSG_E4STEPS + #define MSG_E4STEPS _UxGT("E4steps/mm") +#endif +#ifndef MSG_E5STEPS + #define MSG_E5STEPS _UxGT("E5steps/mm") +#endif +#ifndef MSG_TEMPERATURE + #define MSG_TEMPERATURE _UxGT("Temperature") +#endif +#ifndef MSG_MOTION + #define MSG_MOTION _UxGT("Motion") +#endif +#ifndef MSG_FILAMENT + #define MSG_FILAMENT _UxGT("Filament") +#endif +#ifndef MSG_VOLUMETRIC_ENABLED + #define MSG_VOLUMETRIC_ENABLED _UxGT("E in mm3") +#endif +#ifndef MSG_FILAMENT_DIAM + #define MSG_FILAMENT_DIAM _UxGT("Fil. Dia.") +#endif +#ifndef MSG_FILAMENT_UNLOAD + #define MSG_FILAMENT_UNLOAD _UxGT("Unload mm") +#endif +#ifndef MSG_FILAMENT_LOAD + #define MSG_FILAMENT_LOAD _UxGT("Load mm") +#endif +#ifndef MSG_ADVANCE_K + #define MSG_ADVANCE_K _UxGT("Advance K") +#endif +#ifndef MSG_CONTRAST + #define MSG_CONTRAST _UxGT("LCD contrast") +#endif +#ifndef MSG_STORE_EEPROM + #define MSG_STORE_EEPROM _UxGT("Store settings") +#endif +#ifndef MSG_LOAD_EEPROM + #define MSG_LOAD_EEPROM _UxGT("Load settings") +#endif +#ifndef MSG_RESTORE_FAILSAFE + #define MSG_RESTORE_FAILSAFE _UxGT("Restore failsafe") +#endif +#ifndef MSG_INIT_EEPROM + #define MSG_INIT_EEPROM _UxGT("Initialize EEPROM") +#endif +#ifndef MSG_REFRESH + #define MSG_REFRESH _UxGT("Refresh") +#endif +#ifndef MSG_WATCH + #define MSG_WATCH _UxGT("Info screen") +#endif +#ifndef MSG_PREPARE + #define MSG_PREPARE _UxGT("Prepare") +#endif +#ifndef MSG_TUNE + #define MSG_TUNE _UxGT("Tune") +#endif +#ifndef MSG_PAUSE_PRINT + #define MSG_PAUSE_PRINT _UxGT("Pause print") +#endif +#ifndef MSG_RESUME_PRINT + #define MSG_RESUME_PRINT _UxGT("Resume print") +#endif +#ifndef MSG_STOP_PRINT + #define MSG_STOP_PRINT _UxGT("Stop print") +#endif +#ifndef MSG_CARD_MENU + #define MSG_CARD_MENU _UxGT("Print from SD") +#endif +#ifndef MSG_NO_CARD + #define MSG_NO_CARD _UxGT("No SD card") +#endif +#ifndef MSG_DWELL + #define MSG_DWELL _UxGT("Sleep...") +#endif +#ifndef MSG_USERWAIT + #define MSG_USERWAIT _UxGT("Click to resume...") +#endif +#ifndef MSG_PRINT_PAUSED + #define MSG_PRINT_PAUSED _UxGT("Print paused") +#endif +#ifndef MSG_RESUMING + #define MSG_RESUMING _UxGT("Resuming print") +#endif +#ifndef MSG_PRINT_ABORTED + #define MSG_PRINT_ABORTED _UxGT("Print aborted") +#endif +#ifndef MSG_NO_MOVE + #define MSG_NO_MOVE _UxGT("No move.") +#endif +#ifndef MSG_KILLED + #define MSG_KILLED _UxGT("KILLED. ") +#endif +#ifndef MSG_STOPPED + #define MSG_STOPPED _UxGT("STOPPED. ") +#endif +#ifndef MSG_CONTROL_RETRACT + #define MSG_CONTROL_RETRACT _UxGT("Retract mm") +#endif +#ifndef MSG_CONTROL_RETRACT_SWAP + #define MSG_CONTROL_RETRACT_SWAP _UxGT("Swap Re.mm") +#endif +#ifndef MSG_CONTROL_RETRACTF + #define MSG_CONTROL_RETRACTF _UxGT("Retract V") +#endif +#ifndef MSG_CONTROL_RETRACT_ZLIFT + #define MSG_CONTROL_RETRACT_ZLIFT _UxGT("Hop mm") +#endif +#ifndef MSG_CONTROL_RETRACT_RECOVER + #define MSG_CONTROL_RETRACT_RECOVER _UxGT("UnRet mm") +#endif +#ifndef MSG_CONTROL_RETRACT_RECOVER_SWAP + #define MSG_CONTROL_RETRACT_RECOVER_SWAP _UxGT("S UnRet mm") +#endif +#ifndef MSG_CONTROL_RETRACT_RECOVERF + #define MSG_CONTROL_RETRACT_RECOVERF _UxGT("UnRet V") +#endif +#ifndef MSG_CONTROL_RETRACT_RECOVER_SWAPF + #define MSG_CONTROL_RETRACT_RECOVER_SWAPF _UxGT("S UnRet V") +#endif +#ifndef MSG_AUTORETRACT + #define MSG_AUTORETRACT _UxGT("AutoRetr.") +#endif +#ifndef MSG_FILAMENTCHANGE + #define MSG_FILAMENTCHANGE _UxGT("Change filament") +#endif +#ifndef MSG_FILAMENTLOAD + #define MSG_FILAMENTLOAD _UxGT("Load filament") +#endif +#ifndef MSG_FILAMENTUNLOAD + #define MSG_FILAMENTUNLOAD _UxGT("Unload filament") +#endif +#ifndef MSG_FILAMENTUNLOAD_ALL + #define MSG_FILAMENTUNLOAD_ALL _UxGT("Unload All") +#endif +#ifndef MSG_INIT_SDCARD + #define MSG_INIT_SDCARD _UxGT("Init. SD card") +#endif +#ifndef MSG_CNG_SDCARD + #define MSG_CNG_SDCARD _UxGT("Change SD card") +#endif +#ifndef MSG_ZPROBE_OUT + #define MSG_ZPROBE_OUT _UxGT("Z probe out. bed") +#endif +#ifndef MSG_SKEW_FACTOR + #define MSG_SKEW_FACTOR _UxGT("Skew Factor") +#endif +#ifndef MSG_BLTOUCH + #define MSG_BLTOUCH _UxGT("BLTouch") +#endif +#ifndef MSG_BLTOUCH_SELFTEST + #define MSG_BLTOUCH_SELFTEST _UxGT("BLTouch Self-Test") +#endif +#ifndef MSG_BLTOUCH_RESET + #define MSG_BLTOUCH_RESET _UxGT("Reset BLTouch") +#endif +#ifndef MSG_BLTOUCH_DEPLOY + #define MSG_BLTOUCH_DEPLOY _UxGT("Deploy BLTouch") +#endif +#ifndef MSG_BLTOUCH_STOW + #define MSG_BLTOUCH_STOW _UxGT("Stow BLTouch") +#endif +#ifndef MSG_HOME + #define MSG_HOME _UxGT("Home") // Used as MSG_HOME " " MSG_X MSG_Y MSG_Z " " MSG_FIRST +#endif +#ifndef MSG_FIRST + #define MSG_FIRST _UxGT("first") +#endif +#ifndef MSG_ZPROBE_ZOFFSET + #define MSG_ZPROBE_ZOFFSET _UxGT("Z Offset") +#endif +#ifndef MSG_BABYSTEP_X + #define MSG_BABYSTEP_X _UxGT("Babystep X") +#endif +#ifndef MSG_BABYSTEP_Y + #define MSG_BABYSTEP_Y _UxGT("Babystep Y") +#endif +#ifndef MSG_BABYSTEP_Z + #define MSG_BABYSTEP_Z _UxGT("Babystep Z") +#endif +#ifndef MSG_ENDSTOP_ABORT + #define MSG_ENDSTOP_ABORT _UxGT("Endstop abort") +#endif +#ifndef MSG_HEATING_FAILED_LCD + #define MSG_HEATING_FAILED_LCD _UxGT("Heating failed") +#endif +#ifndef MSG_ERR_REDUNDANT_TEMP + #define MSG_ERR_REDUNDANT_TEMP _UxGT("Err: REDUNDANT TEMP") +#endif +#ifndef MSG_THERMAL_RUNAWAY + #define MSG_THERMAL_RUNAWAY _UxGT("THERMAL RUNAWAY") +#endif +#ifndef MSG_THERMAL_RUNAWAY_BED + #define MSG_THERMAL_RUNAWAY_BED _UxGT("BED THERMAL RUNAWAY") +#endif +#ifndef MSG_ERR_MAXTEMP + #define MSG_ERR_MAXTEMP _UxGT("Err: MAXTEMP") +#endif +#ifndef MSG_ERR_MINTEMP + #define MSG_ERR_MINTEMP _UxGT("Err: MINTEMP") +#endif +#ifndef MSG_ERR_MAXTEMP_BED + #define MSG_ERR_MAXTEMP_BED _UxGT("Err: MAXTEMP BED") +#endif +#ifndef MSG_ERR_MINTEMP_BED + #define MSG_ERR_MINTEMP_BED _UxGT("Err: MINTEMP BED") +#endif +#ifndef MSG_ERR_Z_HOMING + #define MSG_ERR_Z_HOMING MSG_HOME _UxGT(" ") MSG_X MSG_Y _UxGT(" ") MSG_FIRST +#endif +#ifndef MSG_HALTED + #define MSG_HALTED _UxGT("PRINTER HALTED") +#endif +#ifndef MSG_PLEASE_RESET + #define MSG_PLEASE_RESET _UxGT("Please reset") +#endif +#ifndef MSG_SHORT_DAY + #define MSG_SHORT_DAY _UxGT("d") // One character only +#endif +#ifndef MSG_SHORT_HOUR + #define MSG_SHORT_HOUR _UxGT("h") // One character only +#endif +#ifndef MSG_SHORT_MINUTE + #define MSG_SHORT_MINUTE _UxGT("m") // One character only +#endif +#ifndef MSG_HEATING + #define MSG_HEATING _UxGT("Heating...") +#endif +#ifndef MSG_HEATING_COMPLETE + #define MSG_HEATING_COMPLETE _UxGT("Heating done.") +#endif +#ifndef MSG_BED_HEATING + #define MSG_BED_HEATING _UxGT("Bed Heating.") +#endif +#ifndef MSG_BED_DONE + #define MSG_BED_DONE _UxGT("Bed done.") +#endif +#ifndef MSG_DELTA_CALIBRATE + #define MSG_DELTA_CALIBRATE _UxGT("Delta Calibration") +#endif +#ifndef MSG_DELTA_CALIBRATE_X + #define MSG_DELTA_CALIBRATE_X _UxGT("Calibrate X") +#endif +#ifndef MSG_DELTA_CALIBRATE_Y + #define MSG_DELTA_CALIBRATE_Y _UxGT("Calibrate Y") +#endif +#ifndef MSG_DELTA_CALIBRATE_Z + #define MSG_DELTA_CALIBRATE_Z _UxGT("Calibrate Z") +#endif +#ifndef MSG_DELTA_CALIBRATE_CENTER + #define MSG_DELTA_CALIBRATE_CENTER _UxGT("Calibrate Center") +#endif +#ifndef MSG_DELTA_SETTINGS + #define MSG_DELTA_SETTINGS _UxGT("Delta Settings") +#endif +#ifndef MSG_DELTA_AUTO_CALIBRATE + #define MSG_DELTA_AUTO_CALIBRATE _UxGT("Auto Calibration") +#endif +#ifndef MSG_DELTA_HEIGHT_CALIBRATE + #define MSG_DELTA_HEIGHT_CALIBRATE _UxGT("Set Delta Height") +#endif +#ifndef MSG_DELTA_DIAG_ROD + #define MSG_DELTA_DIAG_ROD _UxGT("Diag Rod") +#endif +#ifndef MSG_DELTA_HEIGHT + #define MSG_DELTA_HEIGHT _UxGT("Height") +#endif +#ifndef MSG_DELTA_RADIUS + #define MSG_DELTA_RADIUS _UxGT("Radius") +#endif +#ifndef MSG_INFO_MENU + #define MSG_INFO_MENU _UxGT("About Printer") +#endif +#ifndef MSG_INFO_PRINTER_MENU + #define MSG_INFO_PRINTER_MENU _UxGT("Printer Info") +#endif +#ifndef MSG_3POINT_LEVELING + #define MSG_3POINT_LEVELING _UxGT("3-Point Leveling") +#endif +#ifndef MSG_LINEAR_LEVELING + #define MSG_LINEAR_LEVELING _UxGT("Linear Leveling") +#endif +#ifndef MSG_BILINEAR_LEVELING + #define MSG_BILINEAR_LEVELING _UxGT("Bilinear Leveling") +#endif +#ifndef MSG_UBL_LEVELING + #define MSG_UBL_LEVELING _UxGT("Unified Bed Leveling") +#endif +#ifndef MSG_MESH_LEVELING + #define MSG_MESH_LEVELING _UxGT("Mesh Leveling") +#endif +#ifndef MSG_INFO_STATS_MENU + #define MSG_INFO_STATS_MENU _UxGT("Printer Stats") +#endif +#ifndef MSG_INFO_BOARD_MENU + #define MSG_INFO_BOARD_MENU _UxGT("Board Info") +#endif +#ifndef MSG_INFO_THERMISTOR_MENU + #define MSG_INFO_THERMISTOR_MENU _UxGT("Thermistors") +#endif +#ifndef MSG_INFO_EXTRUDERS + #define MSG_INFO_EXTRUDERS _UxGT("Extruders") +#endif +#ifndef MSG_INFO_BAUDRATE + #define MSG_INFO_BAUDRATE _UxGT("Baud") +#endif +#ifndef MSG_INFO_PROTOCOL + #define MSG_INFO_PROTOCOL _UxGT("Protocol") +#endif +#ifndef MSG_CASE_LIGHT + #define MSG_CASE_LIGHT _UxGT("Case light") +#endif +#ifndef MSG_CASE_LIGHT_BRIGHTNESS + #define MSG_CASE_LIGHT_BRIGHTNESS _UxGT("Light BRIGHTNESS") +#endif +#if LCD_WIDTH >= 20 + #ifndef MSG_INFO_PRINT_COUNT + #define MSG_INFO_PRINT_COUNT _UxGT("Print Count") + #endif + #ifndef MSG_INFO_COMPLETED_PRINTS + #define MSG_INFO_COMPLETED_PRINTS _UxGT("Completed") + #endif + #ifndef MSG_INFO_PRINT_TIME + #define MSG_INFO_PRINT_TIME _UxGT("Total print time") + #endif + #ifndef MSG_INFO_PRINT_LONGEST + #define MSG_INFO_PRINT_LONGEST _UxGT("Longest job time") + #endif + #ifndef MSG_INFO_PRINT_FILAMENT + #define MSG_INFO_PRINT_FILAMENT _UxGT("Extruded total") + #endif +#else + #ifndef MSG_INFO_PRINT_COUNT + #define MSG_INFO_PRINT_COUNT _UxGT("Prints") + #endif + #ifndef MSG_INFO_COMPLETED_PRINTS + #define MSG_INFO_COMPLETED_PRINTS _UxGT("Completed") + #endif + #ifndef MSG_INFO_PRINT_TIME + #define MSG_INFO_PRINT_TIME _UxGT("Total") + #endif + #ifndef MSG_INFO_PRINT_LONGEST + #define MSG_INFO_PRINT_LONGEST _UxGT("Longest") + #endif + #ifndef MSG_INFO_PRINT_FILAMENT + #define MSG_INFO_PRINT_FILAMENT _UxGT("Extruded") + #endif +#endif +#ifndef MSG_INFO_MIN_TEMP + #define MSG_INFO_MIN_TEMP _UxGT("Min Temp") +#endif +#ifndef MSG_INFO_MAX_TEMP + #define MSG_INFO_MAX_TEMP _UxGT("Max Temp") +#endif +#ifndef MSG_INFO_PSU + #define MSG_INFO_PSU _UxGT("PSU") +#endif +#ifndef MSG_DRIVE_STRENGTH + #define MSG_DRIVE_STRENGTH _UxGT("Drive Strength") +#endif +#ifndef MSG_DAC_PERCENT + #define MSG_DAC_PERCENT _UxGT("Driver %") +#endif +#ifndef MSG_DAC_EEPROM_WRITE + #define MSG_DAC_EEPROM_WRITE _UxGT("DAC EEPROM Write") +#endif +#ifndef MSG_FILAMENT_CHANGE_HEADER_PAUSE + #define MSG_FILAMENT_CHANGE_HEADER_PAUSE _UxGT("PRINT PAUSED") +#endif +#ifndef MSG_FILAMENT_CHANGE_HEADER_LOAD + #define MSG_FILAMENT_CHANGE_HEADER_LOAD _UxGT("LOAD FILAMENT") +#endif +#ifndef MSG_FILAMENT_CHANGE_HEADER_UNLOAD + #define MSG_FILAMENT_CHANGE_HEADER_UNLOAD _UxGT("UNLOAD FILAMENT") +#endif +#ifndef MSG_FILAMENT_CHANGE_OPTION_HEADER + #define MSG_FILAMENT_CHANGE_OPTION_HEADER _UxGT("RESUME OPTIONS:") +#endif +#ifndef MSG_FILAMENT_CHANGE_OPTION_PURGE + #define MSG_FILAMENT_CHANGE_OPTION_PURGE _UxGT("Purge more") +#endif +#ifndef MSG_FILAMENT_CHANGE_OPTION_RESUME + #define MSG_FILAMENT_CHANGE_OPTION_RESUME _UxGT("Continue") +#endif +#ifndef MSG_FILAMENT_CHANGE_NOZZLE + #define MSG_FILAMENT_CHANGE_NOZZLE _UxGT(" Nozzle: ") +#endif +#ifndef MSG_ERR_HOMING_FAILED + #define MSG_ERR_HOMING_FAILED _UxGT("Homing failed") +#endif +#ifndef MSG_ERR_PROBING_FAILED + #define MSG_ERR_PROBING_FAILED _UxGT("Probing failed") +#endif +#ifndef MSG_M600_TOO_COLD + #define MSG_M600_TOO_COLD _UxGT("M600: Too cold") +#endif + +// +// Filament Change screens show up to 3 lines on a 4-line display +// ...or up to 2 lines on a 3-line display +// +#if LCD_HEIGHT >= 4 + #ifndef MSG_FILAMENT_CHANGE_INIT_1 + #define MSG_FILAMENT_CHANGE_INIT_1 _UxGT("Wait for start") + #define MSG_FILAMENT_CHANGE_INIT_2 _UxGT("of the filament") + #define MSG_FILAMENT_CHANGE_INIT_3 _UxGT("change") + #endif + #ifndef MSG_FILAMENT_CHANGE_UNLOAD_1 + #define MSG_FILAMENT_CHANGE_UNLOAD_1 _UxGT("Wait for") + #define MSG_FILAMENT_CHANGE_UNLOAD_2 _UxGT("filament unload") + #endif + #ifndef MSG_FILAMENT_CHANGE_INSERT_1 + #define MSG_FILAMENT_CHANGE_INSERT_1 _UxGT("Insert filament") + #define MSG_FILAMENT_CHANGE_INSERT_2 _UxGT("and press button") + #define MSG_FILAMENT_CHANGE_INSERT_3 _UxGT("to continue...") + #endif + #ifndef MSG_FILAMENT_CHANGE_HEAT_1 + #define MSG_FILAMENT_CHANGE_HEAT_1 _UxGT("Press button to") + #define MSG_FILAMENT_CHANGE_HEAT_2 _UxGT("heat nozzle.") + #endif + #ifndef MSG_FILAMENT_CHANGE_HEATING_1 + #define MSG_FILAMENT_CHANGE_HEATING_1 _UxGT("Heating nozzle") + #define MSG_FILAMENT_CHANGE_HEATING_2 _UxGT("Please wait...") + #endif + #ifndef MSG_FILAMENT_CHANGE_LOAD_1 + #define MSG_FILAMENT_CHANGE_LOAD_1 _UxGT("Wait for") + #define MSG_FILAMENT_CHANGE_LOAD_2 _UxGT("filament load") + #endif + #ifndef MSG_FILAMENT_CHANGE_PURGE_1 + #define MSG_FILAMENT_CHANGE_PURGE_1 _UxGT("Wait for") + #define MSG_FILAMENT_CHANGE_PURGE_2 _UxGT("filament purge") + #endif + #ifndef MSG_FILAMENT_CHANGE_RESUME_1 + #define MSG_FILAMENT_CHANGE_RESUME_1 _UxGT("Wait for print") + #define MSG_FILAMENT_CHANGE_RESUME_2 _UxGT("to resume") + #endif +#else // LCD_HEIGHT < 4 + #ifndef MSG_FILAMENT_CHANGE_INIT_1 + #define MSG_FILAMENT_CHANGE_INIT_1 _UxGT("Please wait...") + #endif + #ifndef MSG_FILAMENT_CHANGE_UNLOAD_1 + #define MSG_FILAMENT_CHANGE_UNLOAD_1 _UxGT("Ejecting...") + #endif + #ifndef MSG_FILAMENT_CHANGE_INSERT_1 + #define MSG_FILAMENT_CHANGE_INSERT_1 _UxGT("Insert and Click") + #endif + #ifndef MSG_FILAMENT_CHANGE_HEATING_1 + #define MSG_FILAMENT_CHANGE_HEATING_1 _UxGT("Heating...") + #endif + #ifndef MSG_FILAMENT_CHANGE_LOAD_1 + #define MSG_FILAMENT_CHANGE_LOAD_1 _UxGT("Loading...") + #endif + #ifndef MSG_FILAMENT_CHANGE_PURGE_1 + #define MSG_FILAMENT_CHANGE_PURGE_1 _UxGT("Purging...") + #endif + #ifndef MSG_FILAMENT_CHANGE_RESUME_1 + #define MSG_FILAMENT_CHANGE_RESUME_1 _UxGT("Resuming...") + #endif +#endif // LCD_HEIGHT < 4 + +#endif // LANGUAGE_EN_H diff --git a/pins_RUMBA.h b/pins_RUMBA.h new file mode 100644 index 0000000000..b0e8108d9a --- /dev/null +++ b/pins_RUMBA.h @@ -0,0 +1,324 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 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 . + * + */ + +/** + * RUMBA pin assignments + */ + +#ifndef __AVR_ATmega2560__ + #error "Oops! Make sure you have 'Arduino Mega' selected from the 'Tools -> Boards' menu." +#endif + +#if E_STEPPERS > 3 || HOTENDS > 3 + #error "RUMBA supports up to 3 hotends / E-steppers. Comment this line to keep going." +#endif + +#define DEFAULT_MACHINE_NAME "Rumba" +#define BOARD_NAME "Rumba" +//#endif + +#define LARGE_FLASH true + +// +// Servos +// +#ifdef IS_RAMPS_13 + #define SERVO0_PIN 7 // RAMPS_13 // Will conflict with BTN_EN2 on LCD_I2C_VIKI +#else + #define SERVO0_PIN 11 +#endif +#define SERVO1_PIN 6 +#define SERVO2_PIN 5 +#define SERVO3_PIN -1 + +// +// Limit Switches +// +#define X_MIN_PIN 3 +#ifndef X_MAX_PIN + #define X_MAX_PIN 2 +#endif +#define Y_MIN_PIN 14 +#define Y_MAX_PIN 15 +#define Z_MIN_PIN 18 +#define Z_MAX_PIN 19 + +// +// Z Probe (when not Z_MIN_PIN) +// +#ifndef Z_MIN_PROBE_PIN + #define Z_MIN_PROBE_PIN 32 +#endif + +#define SLED_PIN -1 + +// +// Steppers +// +#define X_STEP_PIN 54 +#define X_DIR_PIN 55 +#define X_ENABLE_PIN 38 +#define X_CS_PIN 53 + +#define Y_STEP_PIN 60 +#define Y_DIR_PIN 61 +#define Y_ENABLE_PIN 56 +#define Y_CS_PIN 49 + +#define Z_STEP_PIN 46 +#define Z_DIR_PIN 48 +#define Z_ENABLE_PIN 62 +#define Z_CS_PIN 40 + +#define E0_STEP_PIN 26 +#define E0_DIR_PIN 28 +#define E0_ENABLE_PIN 24 +#define E0_CS_PIN 42 + +#define E1_STEP_PIN 36 +#define E1_DIR_PIN 34 +#define E1_ENABLE_PIN 30 +#define E1_CS_PIN 44 + +#define E2_STEP_PIN 42 +#define E2_DIR_PIN 43 +#define E2_ENABLE_PIN 44 + +// +// Temperature Sensors +// +#define TEMP_0_PIN 13 // Analog Input +#define TEMP_1_PIN 15 // Analog Input +#define TEMP_BED_PIN 14 // Analog Input + +// SPI for Max6675 or Max31855 Thermocouple +#if DISABLED(SDSUPPORT) + #define MAX6675_SS 66 // Do not use pin 53 if there is even the remote possibility of using Display/SD card +#else + #define MAX6675_SS 66 // Do not use pin 49 as this is tied to the switch inside the SD card socket to detect if there is an SD card present +#endif + +// +// Augmentation for auto-assigning RAMPS plugs +// +#if DISABLED(IS_RAMPS_EEB) && DISABLED(IS_RAMPS_EEF) && DISABLED(IS_RAMPS_EFB) && DISABLED(IS_RAMPS_EFF) && DISABLED(IS_RAMPS_SF) && !PIN_EXISTS(MOSFET_D) + #if HOTENDS > 1 + #if TEMP_SENSOR_BED + #define IS_RAMPS_EEB + #else + #define IS_RAMPS_EEF + #endif + #elif TEMP_SENSOR_BED + #define IS_RAMPS_EFB + #else + #define IS_RAMPS_EFF + #endif +#endif + +// +// Heaters / Fans +// +#define HEATER_0_PIN 10 +#define HEATER_1_PIN 7 +//#define HEATER_2_PIN 6 +//#define HEATER_3_PIN 8 +#define HEATER_BED_PIN -1 + +#define LED4_PIN 8 +#define LASER_PIN -1 + +#define FAN_PIN 9 +#define FAN1_PIN 4 + +// +// Misc. Functions +// +#define SDSS 53 +#define LED_PIN 13 + +// Use the RAMPS 1.4 Analog input 5 on the AUX2 connector +#define FILWIDTH_PIN 5 // Analog Input + +// define digital pin 4 for the filament runout sensor. Use the RAMPS 1.4 digital input 4 on the servos connector +//#define FIL_RUNOUT_PIN 4 +#define FIL_RUNOUT_PIN 42 + +#define PS_ON_PIN 12 + +// +// LCD / Controller +// +#if ENABLED(ULTRA_LCD) + + #if ENABLED(REPRAPWORLD_GRAPHICAL_LCD) + #define LCD_PINS_RS 49 // CS chip select /SS chip slave select + #define LCD_PINS_ENABLE 51 // SID (MOSI) + #define LCD_PINS_D4 52 // SCK (CLK) clock + #elif ENABLED(NEWPANEL) && ENABLED(PANEL_ONE) + #define LCD_PINS_RS 40 + #define LCD_PINS_ENABLE 42 + #define LCD_PINS_D4 65 + #define LCD_PINS_D5 66 + #define LCD_PINS_D6 44 + #define LCD_PINS_D7 64 + #else + #define LCD_PINS_RS 16 + #define LCD_PINS_ENABLE 17 + #define LCD_PINS_D4 23 + #define LCD_PINS_D5 25 + #define LCD_PINS_D6 27 + #define LCD_PINS_D7 29 + #if DISABLED(NEWPANEL) + #define BEEPER_PIN 33 + // Buttons are attached to a shift register + // Not wired yet + //#define SHIFT_CLK 38 + //#define SHIFT_LD 42 + //#define SHIFT_OUT 40 + //#define SHIFT_EN 17 + #endif + #endif + + #if ENABLED(NEWPANEL) + + #if ENABLED(REPRAP_DISCOUNT_SMART_CONTROLLER) + #define BEEPER_PIN 37 + + #define BTN_EN1 31 + #define BTN_EN2 33 + #define BTN_ENC 35 + + #define SD_DETECT_PIN 49 + #define KILL_PIN 41 + + #if ENABLED(BQ_LCD_SMART_CONTROLLER) + #define LCD_BACKLIGHT_PIN 39 + #endif + + #elif ENABLED(REPRAPWORLD_GRAPHICAL_LCD) + #define BTN_EN1 64 + #define BTN_EN2 59 + #define BTN_ENC 63 + #define SD_DETECT_PIN 42 + #elif ENABLED(LCD_I2C_PANELOLU2) + #define BTN_EN1 47 // reverse if the encoder turns the wrong way. + #define BTN_EN2 43 + #define BTN_ENC 32 + #define LCD_SDSS 53 + #define SD_DETECT_PIN -1 + #define KILL_PIN 41 + #elif ENABLED(LCD_I2C_VIKI) + #define BTN_EN1 22 // reverse if the encoder turns the wrong way. + #define BTN_EN2 7 // http://files.panucatt.com/datasheets/viki_wiring_diagram.pdf + // tells about 40/42. + // 22/7 are unused on RAMPS_14. 22 is unused and 7 the SERVO0_PIN on RAMPS_13. + #define BTN_ENC -1 + #define LCD_SDSS 53 + #define SD_DETECT_PIN 49 + #elif ENABLED(VIKI2) || ENABLED(miniVIKI) + #define BEEPER_PIN 33 + + // Pins for DOGM SPI LCD Support + #define DOGLCD_A0 44 + #define DOGLCD_CS 45 + #define LCD_SCREEN_ROT_180 + + #define BTN_EN1 22 + #define BTN_EN2 7 + #define BTN_ENC 39 + + #define SDSS 53 + #define SD_DETECT_PIN -1 // Pin 49 for display sd interface, 72 for easy adapter board + + #define KILL_PIN 31 + + #define STAT_LED_RED_PIN 32 + #define STAT_LED_BLUE_PIN 35 + + #elif ENABLED(ELB_FULL_GRAPHIC_CONTROLLER) + #define BTN_EN1 35 // reverse if the encoder turns the wrong way. + #define BTN_EN2 37 + #define BTN_ENC 31 + #define SD_DETECT_PIN 49 + #define LCD_SDSS 53 + #define KILL_PIN 41 + #define BEEPER_PIN 23 + #define DOGLCD_CS 29 + #define DOGLCD_A0 27 + #define LCD_BACKLIGHT_PIN 33 + #elif ENABLED(MINIPANEL) + #define BEEPER_PIN 42 + // Pins for DOGM SPI LCD Support + #define DOGLCD_A0 44 + #define DOGLCD_CS 66 + #define LCD_BACKLIGHT_PIN 65 // backlight LED on A11/D65 + #define SDSS 53 + + #define KILL_PIN 64 + // GLCD features + //#define LCD_CONTRAST 190 + // Uncomment screen orientation + //#define LCD_SCREEN_ROT_90 + //#define LCD_SCREEN_ROT_180 + //#define LCD_SCREEN_ROT_270 + // The encoder and click button + #define BTN_EN1 40 + #define BTN_EN2 63 + #define BTN_ENC 59 + // not connected to a pin + #define SD_DETECT_PIN 49 + + #else + + // Beeper on AUX-4 + #define BEEPER_PIN 33 + + // buttons are directly attached using AUX-2 + #if ENABLED(REPRAPWORLD_KEYPAD) + #define BTN_EN1 64 // encoder + #define BTN_EN2 59 // encoder + #define BTN_ENC 63 // enter button + #define SHIFT_OUT 40 // shift register + #define SHIFT_CLK 44 // shift register + #define SHIFT_LD 42 // shift register + #elif ENABLED(PANEL_ONE) + #define BTN_EN1 59 // AUX2 PIN 3 + #define BTN_EN2 63 // AUX2 PIN 4 + #define BTN_ENC 49 // AUX3 PIN 7 + #else + #define BTN_EN1 37 + #define BTN_EN2 35 + #define BTN_ENC 31 // the click + #endif + + #if ENABLED(G3D_PANEL) + #define SD_DETECT_PIN 49 + #define KILL_PIN 41 + #else + //#define SD_DETECT_PIN -1 // Ramps doesn't use this + #endif + + #endif + #endif // NEWPANEL + +#endif // ULTRA_LCD + diff --git a/ultralcd.cpp b/ultralcd.cpp new file mode 100644 index 0000000000..adbb7be772 --- /dev/null +++ b/ultralcd.cpp @@ -0,0 +1,5582 @@ +/** + * Marlin 3D Printer Firmware + * Copyright (C) 2016 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 "MarlinConfig.h" + +#if ENABLED(ULTRA_LCD) + +#include "ultralcd.h" +#include "Marlin.h" +#include "language.h" +#include "cardreader.h" +#include "temperature.h" +#include "planner.h" +#include "stepper.h" +#include "configuration_store.h" +#include "utility.h" +#include "parser.h" + +#if HAS_BUZZER && DISABLED(LCD_USE_I2C_BUZZER) + #include "buzzer.h" +#endif + +#include "printcounter.h" + +#if ENABLED(PRINTCOUNTER) + #include "duration_t.h" +#endif + +#if ENABLED(BLTOUCH) + #include "endstops.h" +#endif + +#if ENABLED(AUTO_BED_LEVELING_UBL) + #include "ubl.h" +#elif HAS_ABL + #include "planner.h" +#elif ENABLED(MESH_BED_LEVELING) && ENABLED(LCD_BED_LEVELING) + #include "mesh_bed_leveling.h" +#endif + +#if ENABLED(FWRETRACT) + #include "fwretract.h" +#endif + +#if ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(G26_MESH_VALIDATION) + bool lcd_external_control; // = false +#endif + +// Initialized by settings.load() +int16_t lcd_preheat_hotend_temp[2], lcd_preheat_bed_temp[2], lcd_preheat_fan_speed[2]; + +#if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT) + millis_t previous_lcd_status_ms = 0; +#endif + +#if ENABLED(BABYSTEPPING) + long babysteps_done = 0; + #if ENABLED(BABYSTEP_ZPROBE_OFFSET) + static void lcd_babystep_zoffset(); + #else + static void lcd_babystep_z(); + #endif +#endif + +uint8_t lcd_status_update_delay = 1, // First update one loop delayed + lcd_status_message_level; // Higher level blocks lower level + +#if ENABLED(STATUS_MESSAGE_SCROLLING) + #if LONG_FILENAME_LENGTH > CHARSIZE * 2 * (LCD_WIDTH) + #define MAX_MESSAGE_LENGTH LONG_FILENAME_LENGTH + #else + #define MAX_MESSAGE_LENGTH CHARSIZE * 2 * (LCD_WIDTH) + #endif + uint8_t status_scroll_pos = 0; +#else + #define MAX_MESSAGE_LENGTH CHARSIZE * (LCD_WIDTH) +#endif + +char lcd_status_message[MAX_MESSAGE_LENGTH + 1]; + +#if ENABLED(SCROLL_LONG_FILENAMES) + uint8_t filename_scroll_pos, filename_scroll_max, filename_scroll_hash; +#endif + +#if ENABLED(LCD_SET_PROGRESS_MANUALLY) + uint8_t progress_bar_percent; +#endif + +#if ENABLED(DOGLCD) + #include "ultralcd_impl_DOGM.h" + #include +#else + #include "ultralcd_impl_HD44780.h" +#endif + +#if ENABLED(ULTIPANEL) + #define DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(_type, _name, _strFunc) \ + inline void lcd_implementation_drawmenu_setting_edit_ ## _name (const bool sel, const uint8_t row, const char* pstr, const char* pstr2, _type * const data, ...) { \ + UNUSED(pstr2); \ + DRAWMENU_SETTING_EDIT_GENERIC(_strFunc(*(data))); \ + } \ + inline void lcd_implementation_drawmenu_setting_edit_callback_ ## _name (const bool sel, const uint8_t row, const char* pstr, const char* pstr2, _type * const data, ...) { \ + UNUSED(pstr2); \ + DRAWMENU_SETTING_EDIT_GENERIC(_strFunc(*(data))); \ + } \ + inline void lcd_implementation_drawmenu_setting_edit_accessor_ ## _name (const bool sel, const uint8_t row, const char* pstr, const char* pstr2, _type (*pget)(), void (*pset)(_type), ...) { \ + UNUSED(pstr2); UNUSED(pset); \ + DRAWMENU_SETTING_EDIT_GENERIC(_strFunc(pget())); \ + } \ + typedef void _name##_void + DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(int16_t, int3, itostr3); + DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(uint8_t, int8, i8tostr3); + DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float3, ftostr3); + DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float32, ftostr32); + DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float43, ftostr43sign); + DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float5, ftostr5rj); + DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float51, ftostr51sign); + DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float52, ftostr52sign); + DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(float, float62, ftostr62rj); + DEFINE_LCD_IMPLEMENTATION_DRAWMENU_SETTING_EDIT_TYPE(uint32_t, long5, ftostr5rj); + #define lcd_implementation_drawmenu_setting_edit_bool(sel, row, pstr, pstr2, data) DRAW_BOOL_SETTING(sel, row, pstr, data) + #define lcd_implementation_drawmenu_setting_edit_callback_bool(sel, row, pstr, pstr2, data, callback) DRAW_BOOL_SETTING(sel, row, pstr, data) + #define lcd_implementation_drawmenu_setting_edit_accessor_bool(sel, row, pstr, pstr2, pget, pset) DRAW_BOOL_SETTING(sel, row, pstr, data) +#endif // ULTIPANEL + +// The main status screen +void lcd_status_screen(); + +millis_t next_lcd_update_ms; + +uint8_t lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW; // Set when the LCD needs to draw, decrements after every draw. Set to 2 in LCD routines so the LCD gets at least 1 full redraw (first redraw is partial) +uint16_t max_display_update_time = 0; + +#if ENABLED(DOGLCD) + bool drawing_screen, // = false + first_page; +#else + constexpr bool first_page = true; +#endif + +#if ENABLED(DAC_STEPPER_CURRENT) + #include "stepper_dac.h" //was dac_mcp4728.h MarlinMain uses stepper dac for the m-codes + uint8_t driverPercent[XYZE]; +#endif + +#if ENABLED(ULTIPANEL) + + #ifndef TALL_FONT_CORRECTION + #define TALL_FONT_CORRECTION 0 + #endif + + #if HAS_POWER_SWITCH + extern bool powersupply_on; + #endif + + bool no_reentry = false; + constexpr int8_t menu_bottom = LCD_HEIGHT - (TALL_FONT_CORRECTION); + + //////////////////////////////////////////// + ///////////////// Menu Tree //////////////// + //////////////////////////////////////////// + + void lcd_main_menu(); + void lcd_tune_menu(); + void lcd_prepare_menu(); + void lcd_move_menu(); + void lcd_control_menu(); + void lcd_control_temperature_menu(); + void lcd_control_motion_menu(); + + #if DISABLED(SLIM_LCD_MENUS) + void lcd_control_temperature_preheat_material1_settings_menu(); + void lcd_control_temperature_preheat_material2_settings_menu(); + #endif + + #if DISABLED(NO_VOLUMETRICS) || ENABLED(ADVANCED_PAUSE_FEATURE) + void lcd_control_filament_menu(); + #endif + + #if ENABLED(LCD_INFO_MENU) + #if ENABLED(PRINTCOUNTER) + void lcd_info_stats_menu(); + #endif + void lcd_info_thermistors_menu(); + void lcd_info_board_menu(); + void lcd_info_menu(); + #endif // LCD_INFO_MENU + + #if ENABLED(LED_CONTROL_MENU) + #include "leds.h" + void lcd_led_menu(); + #endif + + #if ENABLED(ADVANCED_PAUSE_FEATURE) + #if E_STEPPERS > 1 || ENABLED(FILAMENT_LOAD_UNLOAD_GCODES) + void lcd_change_filament_menu(); + #else + void lcd_temp_menu_e0_filament_change(); + #endif + void lcd_advanced_pause_option_menu(); + void lcd_advanced_pause_init_message(); + void lcd_advanced_pause_unload_message(); + void lcd_advanced_pause_insert_message(); + void lcd_advanced_pause_load_message(); + void lcd_advanced_pause_heat_nozzle(); + void lcd_advanced_pause_purge_message(); + void lcd_advanced_pause_resume_message(); + #endif + + #if ENABLED(DAC_STEPPER_CURRENT) + void dac_driver_commit(); + void dac_driver_getValues(); + void lcd_dac_menu(); + void lcd_dac_write_eeprom(); + #endif + + #if ENABLED(FWRETRACT) + void lcd_control_retract_menu(); + #endif + + #if ENABLED(DELTA_CALIBRATION_MENU) || ENABLED(DELTA_AUTO_CALIBRATION) + void lcd_delta_calibrate_menu(); + #endif + + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + static float new_z_fade_height; + void _lcd_set_z_fade_height() { set_z_fade_height(new_z_fade_height); } + #endif + + //////////////////////////////////////////// + //////////// Menu System Actions /////////// + //////////////////////////////////////////// + + #define menu_action_back(dummy) _menu_action_back() + void _menu_action_back(); + void menu_action_submenu(screenFunc_t data); + void menu_action_gcode(const char* pgcode); + void menu_action_function(menuAction_t data); + + #define DECLARE_MENU_EDIT_TYPE(_type, _name) \ + bool _menu_edit_ ## _name(); \ + void menu_edit_ ## _name(); \ + void menu_edit_callback_ ## _name(); \ + void _menu_action_setting_edit_ ## _name(const char * const pstr, _type* const ptr, const _type minValue, const _type maxValue); \ + void menu_action_setting_edit_ ## _name(const char * const pstr, _type * const ptr, const _type minValue, const _type maxValue); \ + void menu_action_setting_edit_callback_ ## _name(const char * const pstr, _type * const ptr, const _type minValue, const _type maxValue, const screenFunc_t callback, const bool live=false); \ + typedef void _name##_void + + DECLARE_MENU_EDIT_TYPE(int16_t, int3); + DECLARE_MENU_EDIT_TYPE(uint8_t, int8); + DECLARE_MENU_EDIT_TYPE(float, float3); + DECLARE_MENU_EDIT_TYPE(float, float32); + DECLARE_MENU_EDIT_TYPE(float, float43); + DECLARE_MENU_EDIT_TYPE(float, float5); + DECLARE_MENU_EDIT_TYPE(float, float51); + DECLARE_MENU_EDIT_TYPE(float, float52); + DECLARE_MENU_EDIT_TYPE(float, float62); + DECLARE_MENU_EDIT_TYPE(uint32_t, long5); + + void menu_action_setting_edit_bool(const char* pstr, bool* ptr); + void menu_action_setting_edit_callback_bool(const char* pstr, bool* ptr, screenFunc_t callbackFunc); + + #if ENABLED(SDSUPPORT) + void lcd_sdcard_menu(); + void menu_action_sdfile(const char* filename, char* longFilename); + void menu_action_sddirectory(const char* filename, char* longFilename); + #endif + + //////////////////////////////////////////// + //////////// Menu System Macros //////////// + //////////////////////////////////////////// + + /** + * MENU_ITEM generates draw & handler code for a menu item, potentially calling: + * + * lcd_implementation_drawmenu_[type](sel, row, label, arg3...) + * menu_action_[type](arg3...) + * + * Examples: + * MENU_ITEM(back, MSG_WATCH, 0 [dummy parameter] ) + * or + * MENU_BACK(MSG_WATCH) + * lcd_implementation_drawmenu_back(sel, row, PSTR(MSG_WATCH)) + * menu_action_back() + * + * MENU_ITEM(function, MSG_PAUSE_PRINT, lcd_sdcard_pause) + * lcd_implementation_drawmenu_function(sel, row, PSTR(MSG_PAUSE_PRINT), lcd_sdcard_pause) + * menu_action_function(lcd_sdcard_pause) + * + * MENU_ITEM_EDIT(int3, MSG_SPEED, &feedrate_percentage, 10, 999) + * MENU_ITEM(setting_edit_int3, MSG_SPEED, PSTR(MSG_SPEED), &feedrate_percentage, 10, 999) + * lcd_implementation_drawmenu_setting_edit_int3(sel, row, PSTR(MSG_SPEED), PSTR(MSG_SPEED), &feedrate_percentage, 10, 999) + * menu_action_setting_edit_int3(PSTR(MSG_SPEED), &feedrate_percentage, 10, 999) + * + */ + #define _MENU_ITEM_PART_1(TYPE, ...) \ + if (_menuLineNr == _thisItemNr) { \ + if (encoderLine == _thisItemNr && lcd_clicked) { \ + lcd_clicked = false + + #define _MENU_ITEM_PART_2(TYPE, PLABEL, ...) \ + menu_action_ ## TYPE(__VA_ARGS__); \ + if (screen_changed) return; \ + } \ + if (lcdDrawUpdate) \ + lcd_implementation_drawmenu_ ## TYPE(encoderLine == _thisItemNr, _lcdLineNr, PLABEL, ## __VA_ARGS__); \ + } \ + ++_thisItemNr + + #define MENU_ITEM_P(TYPE, PLABEL, ...) do { \ + _skipStatic = false; \ + _MENU_ITEM_PART_1(TYPE, ## __VA_ARGS__); \ + _MENU_ITEM_PART_2(TYPE, PLABEL, ## __VA_ARGS__); \ + }while(0) + + #define MENU_ITEM(TYPE, LABEL, ...) MENU_ITEM_P(TYPE, PSTR(LABEL), ## __VA_ARGS__) + + #define MENU_BACK(LABEL) MENU_ITEM(back, LABEL, 0) + + // Used to print static text with no visible cursor. + // Parameters: label [, bool center [, bool invert [, char *value] ] ] + #define STATIC_ITEM_P(LABEL, ...) \ + if (_menuLineNr == _thisItemNr) { \ + if (_skipStatic && encoderLine <= _thisItemNr) { \ + encoderPosition += ENCODER_STEPS_PER_MENU_ITEM; \ + ++encoderLine; \ + } \ + if (lcdDrawUpdate) \ + lcd_implementation_drawmenu_static(_lcdLineNr, LABEL, ## __VA_ARGS__); \ + } \ + ++_thisItemNr + + #define STATIC_ITEM(LABEL, ...) STATIC_ITEM_P(PSTR(LABEL), ## __VA_ARGS__) + + #if ENABLED(ENCODER_RATE_MULTIPLIER) + + bool encoderRateMultiplierEnabled; + #define ENCODER_RATE_MULTIPLY(F) (encoderRateMultiplierEnabled = F) + + //#define ENCODER_RATE_MULTIPLIER_DEBUG // If defined, output the encoder steps per second value + + /** + * MENU_MULTIPLIER_ITEM generates drawing and handling code for a multiplier menu item + */ + #define MENU_MULTIPLIER_ITEM(TYPE, LABEL, ...) do { \ + _MENU_ITEM_PART_1(TYPE, ## __VA_ARGS__); \ + encoderRateMultiplierEnabled = true; \ + lastEncoderMovementMillis = 0; \ + _MENU_ITEM_PART_2(TYPE, PSTR(LABEL), ## __VA_ARGS__); \ + }while(0) + + #else // !ENCODER_RATE_MULTIPLIER + #define ENCODER_RATE_MULTIPLY(F) NOOP + #endif // !ENCODER_RATE_MULTIPLIER + + #define MENU_ITEM_DUMMY() do { _thisItemNr++; }while(0) + #define MENU_ITEM_EDIT(TYPE, LABEL, ...) MENU_ITEM(setting_edit_ ## TYPE, LABEL, PSTR(LABEL), ## __VA_ARGS__) + #define MENU_ITEM_EDIT_CALLBACK(TYPE, LABEL, ...) MENU_ITEM(setting_edit_callback_ ## TYPE, LABEL, PSTR(LABEL), ## __VA_ARGS__) + #if ENABLED(ENCODER_RATE_MULTIPLIER) + #define MENU_MULTIPLIER_ITEM_EDIT(TYPE, LABEL, ...) MENU_MULTIPLIER_ITEM(setting_edit_ ## TYPE, LABEL, PSTR(LABEL), ## __VA_ARGS__) + #define MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(TYPE, LABEL, ...) MENU_MULTIPLIER_ITEM(setting_edit_callback_ ## TYPE, LABEL, PSTR(LABEL), ## __VA_ARGS__) + #else // !ENCODER_RATE_MULTIPLIER + #define MENU_MULTIPLIER_ITEM_EDIT(TYPE, LABEL, ...) MENU_ITEM(setting_edit_ ## TYPE, LABEL, PSTR(LABEL), ## __VA_ARGS__) + #define MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(TYPE, LABEL, ...) MENU_ITEM(setting_edit_callback_ ## TYPE, LABEL, PSTR(LABEL), ## __VA_ARGS__) + #endif // !ENCODER_RATE_MULTIPLIER + + #define SCREEN_OR_MENU_LOOP() \ + int8_t _menuLineNr = encoderTopLine, _thisItemNr; \ + for (int8_t _lcdLineNr = 0; _lcdLineNr < menu_bottom; _lcdLineNr++, _menuLineNr++) { \ + _thisItemNr = 0 + + /** + * START_SCREEN Opening code for a screen having only static items. + * Do simplified scrolling of the entire screen. + * + * START_MENU Opening code for a screen with menu items. + * Scroll as-needed to keep the selected line in view. + */ + #define START_SCREEN() \ + scroll_screen(menu_bottom, false); \ + bool _skipStatic = false; \ + SCREEN_OR_MENU_LOOP() + + #define START_MENU() \ + scroll_screen(1, true); \ + bool _skipStatic = true; \ + SCREEN_OR_MENU_LOOP() + + #define END_SCREEN() \ + } \ + screen_items = _thisItemNr + + #define END_MENU() \ + } \ + screen_items = _thisItemNr; \ + UNUSED(_skipStatic) + + //////////////////////////////////////////// + ///////////// Global Variables ///////////// + //////////////////////////////////////////// + + /** + * REVERSE_MENU_DIRECTION + * + * To reverse the menu direction we need a general way to reverse + * the direction of the encoder everywhere. So encoderDirection is + * added to allow the encoder to go the other way. + * + * This behavior is limited to scrolling Menus and SD card listings, + * and is disabled in other contexts. + */ + #if ENABLED(REVERSE_MENU_DIRECTION) + int8_t encoderDirection = 1; + #define ENCODER_DIRECTION_NORMAL() (encoderDirection = 1) + #define ENCODER_DIRECTION_MENUS() (encoderDirection = -1) + #else + #define ENCODER_DIRECTION_NORMAL() ; + #define ENCODER_DIRECTION_MENUS() ; + #endif + + // Encoder Movement + volatile int8_t encoderDiff; // Updated in lcd_buttons_update, added to encoderPosition every LCD update + uint32_t encoderPosition; + millis_t lastEncoderMovementMillis = 0; + + // Button States + bool lcd_clicked, wait_for_unclick; + volatile uint8_t buttons; + millis_t next_button_update_ms; + #if ENABLED(REPRAPWORLD_KEYPAD) + volatile uint8_t buttons_reprapworld_keypad; + #endif + #if ENABLED(LCD_HAS_SLOW_BUTTONS) + volatile uint8_t slow_buttons; + #endif + + // Menu System Navigation + screenFunc_t currentScreen = lcd_status_screen; + int8_t encoderTopLine; + typedef struct { + screenFunc_t menu_function; + uint32_t encoder_position; + } menuPosition; + menuPosition screen_history[6]; + uint8_t screen_history_depth = 0; + bool screen_changed, defer_return_to_status; + + // Value Editing + const char *editLabel; + void *editValue; + int32_t minEditValue, maxEditValue; + screenFunc_t callbackFunc; + bool liveEdit; + + // Manual Moves + const float manual_feedrate_mm_m[] = MANUAL_FEEDRATE; + millis_t manual_move_start_time = 0; + int8_t manual_move_axis = (int8_t)NO_AXIS; + #if EXTRUDERS > 1 + int8_t manual_move_e_index = 0; + #else + #define manual_move_e_index 0 + #endif + + #if IS_KINEMATIC + bool processing_manual_move = false; + float manual_move_offset = 0.0; + #else + constexpr bool processing_manual_move = false; + #endif + + #if PIN_EXISTS(SD_DETECT) + uint8_t lcd_sd_status; + #endif + + #if ENABLED(PIDTEMP) + float raw_Ki, raw_Kd; // place-holders for Ki and Kd edits + #endif + + inline bool use_click() { + const bool click = lcd_clicked; + lcd_clicked = false; + return click; + } + + /** + * General function to go directly to a screen + */ + void lcd_goto_screen(screenFunc_t screen, const uint32_t encoder/*=0*/) { + if (currentScreen != screen) { + + #if ENABLED(DOUBLECLICK_FOR_Z_BABYSTEPPING) && ENABLED(BABYSTEPPING) + static millis_t doubleclick_expire_ms = 0; + // Going to lcd_main_menu from status screen? Remember first click time. + // Going back to status screen within a very short time? Go to Z babystepping. + if (screen == lcd_main_menu) { + if (currentScreen == lcd_status_screen) + doubleclick_expire_ms = millis() + DOUBLECLICK_MAX_INTERVAL; + } + else if (screen == lcd_status_screen && currentScreen == lcd_main_menu && PENDING(millis(), doubleclick_expire_ms)) + screen = + #if ENABLED(BABYSTEP_ZPROBE_OFFSET) + lcd_babystep_zoffset + #else + lcd_babystep_z + #endif + ; + #endif + + currentScreen = screen; + encoderPosition = encoder; + if (screen == lcd_status_screen) { + defer_return_to_status = false; + #if ENABLED(AUTO_BED_LEVELING_UBL) + ubl.lcd_map_control = false; + #endif + screen_history_depth = 0; + } + lcd_implementation_clear(); + // Re-initialize custom characters that may be re-used + #if DISABLED(DOGLCD) && ENABLED(AUTO_BED_LEVELING_UBL) + if (!ubl.lcd_map_control) { + lcd_set_custom_characters( + #if ENABLED(LCD_PROGRESS_BAR) + screen == lcd_status_screen ? CHARSET_INFO : CHARSET_MENU + #endif + ); + } + #elif ENABLED(LCD_PROGRESS_BAR) + lcd_set_custom_characters(screen == lcd_status_screen ? CHARSET_INFO : CHARSET_MENU); + #endif + lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT; + screen_changed = true; + #if ENABLED(DOGLCD) + drawing_screen = false; + #endif + } + } + + /** + * Show "Moving..." till moves are done, then revert to previous display. + */ + static const char moving[] PROGMEM = MSG_MOVING; + static const char *sync_message = moving; + + // + // Display the synchronize screen until moves are + // finished, and don't return to the caller until + // done. ** This blocks the command queue! ** + // + void _lcd_synchronize() { + if (lcdDrawUpdate) lcd_implementation_drawmenu_static(LCD_HEIGHT >= 4 ? 1 : 0, sync_message); + if (no_reentry) return; + // Make this the current handler till all moves are done + no_reentry = true; + const screenFunc_t old_screen = currentScreen; + lcd_goto_screen(_lcd_synchronize); + stepper.synchronize(); // idle() is called until moves complete + no_reentry = false; + lcd_goto_screen(old_screen); + } + + // Display the synchronize screen with a custom message + // ** This blocks the command queue! ** + void lcd_synchronize(const char * const msg=NULL) { + sync_message = msg ? msg : moving; + _lcd_synchronize(); + } + + void lcd_return_to_status() { lcd_goto_screen(lcd_status_screen); } + + void lcd_save_previous_screen() { + if (screen_history_depth < COUNT(screen_history)) { + screen_history[screen_history_depth].menu_function = currentScreen; + screen_history[screen_history_depth].encoder_position = encoderPosition; + ++screen_history_depth; + } + } + + void lcd_goto_previous_menu() { + if (screen_history_depth > 0) { + --screen_history_depth; + lcd_goto_screen( + screen_history[screen_history_depth].menu_function, + screen_history[screen_history_depth].encoder_position + ); + } + else + lcd_return_to_status(); + } + + void lcd_goto_previous_menu_no_defer() { + defer_return_to_status = false; + lcd_goto_previous_menu(); + } + + /** + * Scrolling for menus and other line-based screens + * + * encoderLine is the position based on the encoder + * encoderTopLine is the top menu line to display + * _lcdLineNr is the index of the LCD line (e.g., 0-3) + * _menuLineNr is the menu item to draw and process + * _thisItemNr is the index of each MENU_ITEM or STATIC_ITEM + * screen_items is the total number of items in the menu (after one call) + */ + int8_t encoderLine, screen_items; + void scroll_screen(const uint8_t limit, const bool is_menu) { + ENCODER_DIRECTION_MENUS(); + ENCODER_RATE_MULTIPLY(false); + if (encoderPosition > 0x8000) encoderPosition = 0; + if (first_page) { + encoderLine = encoderPosition / (ENCODER_STEPS_PER_MENU_ITEM); + screen_changed = false; + } + if (screen_items > 0 && encoderLine >= screen_items - limit) { + encoderLine = max(0, screen_items - limit); + encoderPosition = encoderLine * (ENCODER_STEPS_PER_MENU_ITEM); + } + if (is_menu) { + NOMORE(encoderTopLine, encoderLine); + if (encoderLine >= encoderTopLine + menu_bottom) + encoderTopLine = encoderLine - menu_bottom + 1; + } + else + encoderTopLine = encoderLine; + } + +#endif // ULTIPANEL + +/** + * + * "Info Screen" + * + * This is very display-dependent, so the lcd implementation draws this. + */ + +void lcd_status_screen() { + + #if ENABLED(ULTIPANEL) + ENCODER_DIRECTION_NORMAL(); + ENCODER_RATE_MULTIPLY(false); + #endif + + #if ENABLED(LCD_SET_PROGRESS_MANUALLY) && ENABLED(SDSUPPORT) && (ENABLED(LCD_PROGRESS_BAR) || ENABLED(DOGLCD)) + // Progress bar % comes from SD when actively printing + if (IS_SD_PRINTING) + progress_bar_percent = card.percentDone(); + #endif + + #if ENABLED(LCD_PROGRESS_BAR) + + // + // HD44780 implements the following message blinking and + // message expiration because Status Line and Progress Bar + // share the same line on the display. + // + + millis_t ms = millis(); + + // If the message will blink rather than expire... + #if DISABLED(PROGRESS_MSG_ONCE) + if (ELAPSED(ms, progress_bar_ms + PROGRESS_BAR_MSG_TIME + PROGRESS_BAR_BAR_TIME)) + progress_bar_ms = ms; + #endif + + #if PROGRESS_MSG_EXPIRE > 0 + + // Handle message expire + if (expire_status_ms > 0) { + + #if DISABLED(LCD_SET_PROGRESS_MANUALLY) + const uint8_t progress_bar_percent = card.percentDone(); + #endif + + // Expire the message if a job is active and the bar has ticks + if (progress_bar_percent > 2 && !print_job_timer.isPaused()) { + if (ELAPSED(ms, expire_status_ms)) { + lcd_status_message[0] = '\0'; + expire_status_ms = 0; + } + } + else { + // Defer message expiration before bar appears + // and during any pause (not just SD) + expire_status_ms += LCD_UPDATE_INTERVAL; + } + } + + #endif // PROGRESS_MSG_EXPIRE + + #endif // LCD_PROGRESS_BAR + + #if ENABLED(ULTIPANEL) + + if (use_click()) { + #if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT) + previous_lcd_status_ms = millis(); // get status message to show up for a while + #endif + lcd_implementation_init( // to maybe revive the LCD if static electricity killed it. + #if ENABLED(LCD_PROGRESS_BAR) + CHARSET_MENU + #endif + ); + lcd_goto_screen(lcd_main_menu); + return; + } + + #if ENABLED(ULTIPANEL_FEEDMULTIPLY) + const int16_t new_frm = feedrate_percentage + (int32_t)encoderPosition; + // Dead zone at 100% feedrate + if ((feedrate_percentage < 100 && new_frm > 100) || (feedrate_percentage > 100 && new_frm < 100)) { + feedrate_percentage = 100; + encoderPosition = 0; + } + else if (feedrate_percentage == 100) { + if ((int32_t)encoderPosition > ENCODER_FEEDRATE_DEADZONE) { + feedrate_percentage += (int32_t)encoderPosition - (ENCODER_FEEDRATE_DEADZONE); + encoderPosition = 0; + } + else if ((int32_t)encoderPosition < -(ENCODER_FEEDRATE_DEADZONE)) { + feedrate_percentage += (int32_t)encoderPosition + ENCODER_FEEDRATE_DEADZONE; + encoderPosition = 0; + } + } + else { + feedrate_percentage = new_frm; + encoderPosition = 0; + } + #endif // ULTIPANEL_FEEDMULTIPLY + + feedrate_percentage = constrain(feedrate_percentage, 10, 999); + + #endif // ULTIPANEL + + lcd_implementation_status_screen(); +} + +void lcd_reset_status() { lcd_setstatusPGM(PSTR(""), -1); } + +/** + * + * draw the kill screen + * + */ +void kill_screen(const char* lcd_msg) { + lcd_init(); + lcd_setalertstatusPGM(lcd_msg); + lcd_kill_screen(); +} + +#if ENABLED(ULTIPANEL) + + /** + * + * Audio feedback for controller clicks + * + */ + void lcd_buzz(const long duration, const uint16_t freq) { + #if ENABLED(LCD_USE_I2C_BUZZER) + lcd.buzz(duration, freq); + #elif PIN_EXISTS(BEEPER) + buzzer.tone(duration, freq); + #else + UNUSED(duration); UNUSED(freq); + #endif + } + + void lcd_quick_feedback(const bool clear_buttons) { + lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW; + if (clear_buttons) buttons = 0; + next_button_update_ms = millis() + 500; + + // Buzz and wait. The delay is needed for buttons to settle! + lcd_buzz(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ); + #if ENABLED(LCD_USE_I2C_BUZZER) + delay(10); + #elif PIN_EXISTS(BEEPER) + for (int8_t i = 5; i--;) { buzzer.tick(); delay(2); } + #endif + } + + void lcd_completion_feedback(const bool good/*=true*/) { + if (good) { + lcd_buzz(100, 659); + lcd_buzz(100, 698); + } + else lcd_buzz(20, 440); + } + + inline void line_to_current_z() { + planner.buffer_line_kinematic(current_position, MMM_TO_MMS(manual_feedrate_mm_m[Z_AXIS]), active_extruder); + } + + inline void line_to_z(const float &z) { + current_position[Z_AXIS] = z; + line_to_current_z(); + } + + #if ENABLED(SDSUPPORT) + + void lcd_sdcard_pause() { + card.pauseSDPrint(); + print_job_timer.pause(); + #if ENABLED(PARK_HEAD_ON_PAUSE) + enqueue_and_echo_commands_P(PSTR("M125")); + #endif + lcd_setstatusPGM(PSTR(MSG_PRINT_PAUSED), -1); + } + + void lcd_sdcard_resume() { + #if ENABLED(PARK_HEAD_ON_PAUSE) + enqueue_and_echo_commands_P(PSTR("M24")); + #else + card.startFileprint(); + print_job_timer.start(); + #endif + lcd_reset_status(); + } + + void lcd_sdcard_stop() { + card.stopSDPrint( + #if SD_RESORT + true + #endif + ); + clear_command_queue(); + quickstop_stepper(); + print_job_timer.stop(); + thermalManager.disable_all_heaters(); + #if FAN_COUNT > 0 + for (uint8_t i = 0; i < FAN_COUNT; i++) fanSpeeds[i] = 0; + #endif + wait_for_heatup = false; + lcd_setstatusPGM(PSTR(MSG_PRINT_ABORTED), -1); + lcd_return_to_status(); + } + + #endif // SDSUPPORT + + #if ENABLED(MENU_ITEM_CASE_LIGHT) + + extern uint8_t case_light_brightness; + extern bool case_light_on; + extern void update_case_light(); + + void case_light_menu() { + START_MENU(); + // + // ^ Main + // + MENU_BACK(MSG_MAIN); + MENU_ITEM_EDIT_CALLBACK(int8, MSG_CASE_LIGHT_BRIGHTNESS, &case_light_brightness, 0, 255, update_case_light, true); + MENU_ITEM_EDIT_CALLBACK(bool, MSG_CASE_LIGHT, (bool*)&case_light_on, update_case_light); + END_MENU(); + } + #endif // MENU_ITEM_CASE_LIGHT + + #if ENABLED(BLTOUCH) + + /** + * + * "BLTouch" submenu + * + */ + static void bltouch_menu() { + START_MENU(); + // + // ^ Main + // + MENU_BACK(MSG_MAIN); + MENU_ITEM(gcode, MSG_BLTOUCH_RESET, PSTR("M280 P" STRINGIFY(Z_ENDSTOP_SERVO_NR) " S" STRINGIFY(BLTOUCH_RESET))); + MENU_ITEM(gcode, MSG_BLTOUCH_SELFTEST, PSTR("M280 P" STRINGIFY(Z_ENDSTOP_SERVO_NR) " S" STRINGIFY(BLTOUCH_SELFTEST))); + MENU_ITEM(gcode, MSG_BLTOUCH_DEPLOY, PSTR("M280 P" STRINGIFY(Z_ENDSTOP_SERVO_NR) " S" STRINGIFY(BLTOUCH_DEPLOY))); + MENU_ITEM(gcode, MSG_BLTOUCH_STOW, PSTR("M280 P" STRINGIFY(Z_ENDSTOP_SERVO_NR) " S" STRINGIFY(BLTOUCH_STOW))); + END_MENU(); + } + + #endif // BLTOUCH + + #if ENABLED(LCD_PROGRESS_BAR_TEST) + + static void progress_bar_test() { + static int8_t bar_percent = 0; + if (use_click()) { + lcd_goto_previous_menu(); + lcd_set_custom_characters(CHARSET_MENU); + return; + } + bar_percent += (int8_t)encoderPosition; + bar_percent = constrain(bar_percent, 0, 100); + encoderPosition = 0; + lcd_implementation_drawmenu_static(0, PSTR(MSG_PROGRESS_BAR_TEST), true, true); + lcd.setCursor((LCD_WIDTH) / 2 - 2, LCD_HEIGHT - 2); + lcd.print(itostr3(bar_percent)); lcd.write('%'); + lcd.setCursor(0, LCD_HEIGHT - 1); lcd_draw_progress_bar(bar_percent); + } + + void _progress_bar_test() { + lcd_goto_screen(progress_bar_test); + lcd_set_custom_characters(); + } + + #endif // LCD_PROGRESS_BAR_TEST + + #if HAS_DEBUG_MENU + + void lcd_debug_menu() { + START_MENU(); + + MENU_BACK(MSG_MAIN); // ^ Main + + #if ENABLED(LCD_PROGRESS_BAR_TEST) + MENU_ITEM(submenu, MSG_PROGRESS_BAR_TEST, _progress_bar_test); + #endif + + END_MENU(); + } + + #endif // HAS_DEBUG_MENU + + #if ENABLED(CUSTOM_USER_MENUS) + + #ifdef USER_SCRIPT_DONE + #define _DONE_SCRIPT "\n" USER_SCRIPT_DONE + #else + #define _DONE_SCRIPT "" + #endif + + void _lcd_user_gcode(const char * const cmd) { + enqueue_and_echo_commands_P(cmd); + #if ENABLED(USER_SCRIPT_AUDIBLE_FEEDBACK) + lcd_completion_feedback(); + #endif + #if ENABLED(USER_SCRIPT_RETURN) + lcd_return_to_status(); + #endif + } + + #if defined(USER_DESC_1) && defined(USER_GCODE_1) + void lcd_user_gcode_1() { _lcd_user_gcode(PSTR(USER_GCODE_1 _DONE_SCRIPT)); } + #endif + #if defined(USER_DESC_2) && defined(USER_GCODE_2) + void lcd_user_gcode_2() { _lcd_user_gcode(PSTR(USER_GCODE_2 _DONE_SCRIPT)); } + #endif + #if defined(USER_DESC_3) && defined(USER_GCODE_3) + void lcd_user_gcode_3() { _lcd_user_gcode(PSTR(USER_GCODE_3 _DONE_SCRIPT)); } + #endif + #if defined(USER_DESC_4) && defined(USER_GCODE_4) + void lcd_user_gcode_4() { _lcd_user_gcode(PSTR(USER_GCODE_4 _DONE_SCRIPT)); } + #endif + #if defined(USER_DESC_5) && defined(USER_GCODE_5) + void lcd_user_gcode_5() { _lcd_user_gcode(PSTR(USER_GCODE_5 _DONE_SCRIPT)); } + #endif + + void _lcd_user_menu() { + START_MENU(); + MENU_BACK(MSG_MAIN); + #if defined(USER_DESC_1) && defined(USER_GCODE_1) + MENU_ITEM(function, USER_DESC_1, lcd_user_gcode_1); + #endif + #if defined(USER_DESC_2) && defined(USER_GCODE_2) + MENU_ITEM(function, USER_DESC_2, lcd_user_gcode_2); + #endif + #if defined(USER_DESC_3) && defined(USER_GCODE_3) + MENU_ITEM(function, USER_DESC_3, lcd_user_gcode_3); + #endif + #if defined(USER_DESC_4) && defined(USER_GCODE_4) + MENU_ITEM(function, USER_DESC_4, lcd_user_gcode_4); + #endif + #if defined(USER_DESC_5) && defined(USER_GCODE_5) + MENU_ITEM(function, USER_DESC_5, lcd_user_gcode_5); + #endif + END_MENU(); + } + + #endif + + /** + * + * "Main" menu + * + */ + + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + void _lcd_goto_tune_menu() { + lcd_goto_screen(lcd_tune_menu); + new_z_fade_height = planner.z_fade_height; + } + #endif + + void lcd_main_menu() { + START_MENU(); + MENU_BACK(MSG_WATCH); + + #if ENABLED(CUSTOM_USER_MENUS) + MENU_ITEM(submenu, MSG_USER_MENU, _lcd_user_menu); + #endif + + // + // Debug Menu when certain options are enabled + // + #if HAS_DEBUG_MENU + MENU_ITEM(submenu, MSG_DEBUG_MENU, lcd_debug_menu); + #endif + + // + // Set Case light on/off/brightness + // + #if ENABLED(MENU_ITEM_CASE_LIGHT) + if (USEABLE_HARDWARE_PWM(CASE_LIGHT_PIN)) { + MENU_ITEM(submenu, MSG_CASE_LIGHT, case_light_menu); + } + else + MENU_ITEM_EDIT_CALLBACK(bool, MSG_CASE_LIGHT, (bool*)&case_light_on, update_case_light); + #endif + + if (planner.movesplanned() || IS_SD_PRINTING) { + MENU_ITEM(submenu, MSG_TUNE, + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + _lcd_goto_tune_menu + #else + lcd_tune_menu + #endif + ); + } + else { + MENU_ITEM(submenu, MSG_PREPARE, lcd_prepare_menu); + } + MENU_ITEM(submenu, MSG_CONTROL, lcd_control_menu); + + #if ENABLED(SDSUPPORT) + if (card.cardOK) { + if (card.isFileOpen()) { + if (card.sdprinting) + MENU_ITEM(function, MSG_PAUSE_PRINT, lcd_sdcard_pause); + else + MENU_ITEM(function, MSG_RESUME_PRINT, lcd_sdcard_resume); + MENU_ITEM(function, MSG_STOP_PRINT, lcd_sdcard_stop); + } + else { + MENU_ITEM(submenu, MSG_CARD_MENU, lcd_sdcard_menu); + #if !PIN_EXISTS(SD_DETECT) + MENU_ITEM(gcode, MSG_CNG_SDCARD, PSTR("M21")); // SD-card changed by user + #endif + } + } + else { + MENU_ITEM(submenu, MSG_NO_CARD, lcd_sdcard_menu); + #if !PIN_EXISTS(SD_DETECT) + MENU_ITEM(gcode, MSG_INIT_SDCARD, PSTR("M21")); // Manually initialize the SD-card via user interface + #endif + } + #endif // SDSUPPORT + + #if ENABLED(LCD_INFO_MENU) + MENU_ITEM(submenu, MSG_INFO_MENU, lcd_info_menu); + #endif + + #if ENABLED(LED_CONTROL_MENU) + MENU_ITEM(submenu, MSG_LED_CONTROL, lcd_led_menu); + #endif + + END_MENU(); + } + + /** + * + * "Tune" submenu items + * + */ + + #if HAS_M206_COMMAND + /** + * Set the home offset based on the current_position + */ + void lcd_set_home_offsets() { + // M428 Command + enqueue_and_echo_commands_P(PSTR("M428")); + lcd_return_to_status(); + } + #endif + + #if ENABLED(BABYSTEP_ZPROBE_GFX_OVERLAY) || ENABLED(MESH_EDIT_GFX_OVERLAY) + + void _lcd_zoffset_overlay_gfx(const float zvalue) { + // Determine whether the user is raising or lowering the nozzle. + static int8_t dir; + static float old_zvalue; + if (zvalue != old_zvalue) { + dir = zvalue ? zvalue < old_zvalue ? -1 : 1 : 0; + old_zvalue = zvalue; + } + + #if ENABLED(OVERLAY_GFX_REVERSE) + const unsigned char *rot_up = ccw_bmp, *rot_down = cw_bmp; + #else + const unsigned char *rot_up = cw_bmp, *rot_down = ccw_bmp; + #endif + + #if ENABLED(USE_BIG_EDIT_FONT) + const int left = 0, right = 45, nozzle = 95; + #else + const int left = 5, right = 90, nozzle = 60; + #endif + + // Draw a representation of the nozzle + if (PAGE_CONTAINS(3, 16)) u8g.drawBitmapP(nozzle + 6, 4 - dir, 2, 12, nozzle_bmp); + if (PAGE_CONTAINS(20, 20)) u8g.drawBitmapP(nozzle + 0, 20, 3, 1, offset_bedline_bmp); + + // Draw cw/ccw indicator and up/down arrows. + if (PAGE_CONTAINS(47, 62)) { + u8g.drawBitmapP(left + 0, 47, 3, 16, rot_down); + u8g.drawBitmapP(right + 0, 47, 3, 16, rot_up); + u8g.drawBitmapP(right + 20, 48 - dir, 2, 13, up_arrow_bmp); + u8g.drawBitmapP(left + 20, 49 - dir, 2, 13, down_arrow_bmp); + } + } + + #endif // BABYSTEP_ZPROBE_GFX_OVERLAY || MESH_EDIT_GFX_OVERLAY + + #if ENABLED(BABYSTEPPING) + + void _lcd_babystep(const AxisEnum axis, const char* msg) { + if (use_click()) { return lcd_goto_previous_menu_no_defer(); } + ENCODER_DIRECTION_NORMAL(); + if (encoderPosition) { + const int16_t babystep_increment = (int32_t)encoderPosition * (BABYSTEP_MULTIPLICATOR); + encoderPosition = 0; + lcdDrawUpdate = LCDVIEW_REDRAW_NOW; + thermalManager.babystep_axis(axis, babystep_increment); + babysteps_done += babystep_increment; + } + if (lcdDrawUpdate) + lcd_implementation_drawedit(msg, ftostr43sign(planner.steps_to_mm[axis] * babysteps_done)); + } + + #if ENABLED(BABYSTEP_XY) + void _lcd_babystep_x() { _lcd_babystep(X_AXIS, PSTR(MSG_BABYSTEP_X)); } + void _lcd_babystep_y() { _lcd_babystep(Y_AXIS, PSTR(MSG_BABYSTEP_Y)); } + void lcd_babystep_x() { lcd_goto_screen(_lcd_babystep_x); babysteps_done = 0; defer_return_to_status = true; } + void lcd_babystep_y() { lcd_goto_screen(_lcd_babystep_y); babysteps_done = 0; defer_return_to_status = true; } + #endif + + #if ENABLED(BABYSTEP_ZPROBE_OFFSET) + + void lcd_babystep_zoffset() { + if (use_click()) { return lcd_goto_previous_menu_no_defer(); } + defer_return_to_status = true; + ENCODER_DIRECTION_NORMAL(); + if (encoderPosition) { + const int16_t babystep_increment = (int32_t)encoderPosition * (BABYSTEP_MULTIPLICATOR); + encoderPosition = 0; + + const float new_zoffset = zprobe_zoffset + planner.steps_to_mm[Z_AXIS] * babystep_increment; + if (WITHIN(new_zoffset, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX)) { + thermalManager.babystep_axis(Z_AXIS, babystep_increment); + zprobe_zoffset = new_zoffset; + lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT; + } + } + if (lcdDrawUpdate) { + lcd_implementation_drawedit(PSTR(MSG_ZPROBE_ZOFFSET), ftostr43sign(zprobe_zoffset)); + #if ENABLED(BABYSTEP_ZPROBE_GFX_OVERLAY) + _lcd_zoffset_overlay_gfx(zprobe_zoffset); + #endif + } + } + + #else // !BABYSTEP_ZPROBE_OFFSET + + void _lcd_babystep_z() { _lcd_babystep(Z_AXIS, PSTR(MSG_BABYSTEP_Z)); } + void lcd_babystep_z() { lcd_goto_screen(_lcd_babystep_z); babysteps_done = 0; defer_return_to_status = true; } + + #endif // !BABYSTEP_ZPROBE_OFFSET + + #endif // BABYSTEPPING + + #if ENABLED(AUTO_BED_LEVELING_UBL) + + float mesh_edit_value, mesh_edit_accumulator; // We round mesh_edit_value to 2.5 decimal places. So we keep a + // separate value that doesn't lose precision. + static int16_t ubl_encoderPosition = 0; + + static void _lcd_mesh_fine_tune(const char* msg) { + defer_return_to_status = true; + if (ubl.encoder_diff) { + ubl_encoderPosition = (ubl.encoder_diff > 0) ? 1 : -1; + ubl.encoder_diff = 0; + + mesh_edit_accumulator += float(ubl_encoderPosition) * 0.005 / 2.0; + mesh_edit_value = mesh_edit_accumulator; + encoderPosition = 0; + lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT; + + const int32_t rounded = (int32_t)(mesh_edit_value * 1000.0); + mesh_edit_value = float(rounded - (rounded % 5L)) / 1000.0; + } + + if (lcdDrawUpdate) { + lcd_implementation_drawedit(msg, ftostr43sign(mesh_edit_value)); + #if ENABLED(MESH_EDIT_GFX_OVERLAY) + _lcd_zoffset_overlay_gfx(mesh_edit_value); + #endif + } + } + + void _lcd_mesh_edit_NOP() { + defer_return_to_status = true; + } + + float lcd_mesh_edit() { + lcd_goto_screen(_lcd_mesh_edit_NOP); + lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT; + _lcd_mesh_fine_tune(PSTR("Mesh Editor")); + return mesh_edit_value; + } + + void lcd_mesh_edit_setup(const float &initial) { + mesh_edit_value = mesh_edit_accumulator = initial; + lcd_goto_screen(_lcd_mesh_edit_NOP); + } + + void _lcd_z_offset_edit() { + _lcd_mesh_fine_tune(PSTR("Z-Offset: ")); + } + + float lcd_z_offset_edit() { + lcd_goto_screen(_lcd_z_offset_edit); + return mesh_edit_value; + } + + void lcd_z_offset_edit_setup(const float &initial) { + mesh_edit_value = mesh_edit_accumulator = initial; + lcd_goto_screen(_lcd_z_offset_edit); + } + + #endif // AUTO_BED_LEVELING_UBL + + + /** + * Watch temperature callbacks + */ + #if HAS_TEMP_HOTEND + #if WATCH_HOTENDS + #define _WATCH_FUNC(N) thermalManager.start_watching_heater(N) + #else + #define _WATCH_FUNC(N) NOOP + #endif + void watch_temp_callback_E0() { _WATCH_FUNC(0); } + #if HOTENDS > 1 + void watch_temp_callback_E1() { _WATCH_FUNC(1); } + #if HOTENDS > 2 + void watch_temp_callback_E2() { _WATCH_FUNC(2); } + #if HOTENDS > 3 + void watch_temp_callback_E3() { _WATCH_FUNC(3); } + #if HOTENDS > 4 + void watch_temp_callback_E4() { _WATCH_FUNC(4); } + #endif // HOTENDS > 4 + #endif // HOTENDS > 3 + #endif // HOTENDS > 2 + #endif // HOTENDS > 1 + #endif // HAS_TEMP_HOTEND + + void watch_temp_callback_bed() { + #if WATCH_THE_BED + thermalManager.start_watching_bed(); + #endif + } + + // First Fan Speed title in "Tune" and "Control>Temperature" menus + #if FAN_COUNT > 0 && HAS_FAN0 + #if FAN_COUNT > 1 + #define FAN_SPEED_1_SUFFIX " 1" + #else + #define FAN_SPEED_1_SUFFIX "" + #endif + #endif + + // Refresh the E factor after changing flow + inline void _lcd_refresh_e_factor_0() { planner.refresh_e_factor(0); } + #if EXTRUDERS > 1 + inline void _lcd_refresh_e_factor() { planner.refresh_e_factor(active_extruder); } + inline void _lcd_refresh_e_factor_1() { planner.refresh_e_factor(1); } + #if EXTRUDERS > 2 + inline void _lcd_refresh_e_factor_2() { planner.refresh_e_factor(2); } + #if EXTRUDERS > 3 + inline void _lcd_refresh_e_factor_3() { planner.refresh_e_factor(3); } + #if EXTRUDERS > 4 + inline void _lcd_refresh_e_factor_4() { planner.refresh_e_factor(4); } + #endif // EXTRUDERS > 4 + #endif // EXTRUDERS > 3 + #endif // EXTRUDERS > 2 + #endif // EXTRUDERS > 1 + + /** + * + * "Tune" submenu + * + */ + void lcd_tune_menu() { + START_MENU(); + + // + // ^ Main + // + MENU_BACK(MSG_MAIN); + + // + // Speed: + // + MENU_ITEM_EDIT(int3, MSG_SPEED, &feedrate_percentage, 10, 999); + + // Manual bed leveling, Bed Z: + #if ENABLED(MESH_BED_LEVELING) && ENABLED(LCD_BED_LEVELING) + MENU_ITEM_EDIT(float43, MSG_BED_Z, &mbl.z_offset, -1, 1); + #endif + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float3, MSG_Z_FADE_HEIGHT, &new_z_fade_height, 0.0, 100.0, _lcd_set_z_fade_height); + #endif + // + // Nozzle: + // Nozzle [1-4]: + // + #if HOTENDS == 1 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE, &thermalManager.target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0); + #else // HOTENDS > 1 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N1, &thermalManager.target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0); + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N2, &thermalManager.target_temperature[1], 0, HEATER_1_MAXTEMP - 15, watch_temp_callback_E1); + #if HOTENDS > 2 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N3, &thermalManager.target_temperature[2], 0, HEATER_2_MAXTEMP - 15, watch_temp_callback_E2); + #if HOTENDS > 3 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N4, &thermalManager.target_temperature[3], 0, HEATER_3_MAXTEMP - 15, watch_temp_callback_E3); + #if HOTENDS > 4 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N5, &thermalManager.target_temperature[4], 0, HEATER_4_MAXTEMP - 15, watch_temp_callback_E4); + #endif // HOTENDS > 4 + #endif // HOTENDS > 3 + #endif // HOTENDS > 2 + #endif // HOTENDS > 1 + + // + // Bed: + // + #if HAS_TEMP_BED + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_BED, &thermalManager.target_temperature_bed, 0, BED_MAXTEMP - 15, watch_temp_callback_bed); + #endif + +#if ENABLED(FAN_AS_LASER) + MENU_ITEM(gcode, MSG_LASER_ON, PSTR("M3")); + MENU_ITEM(gcode, MSG_LASER_OFF, PSTR("M5")); +#endif + // + // Fan Speed: + // + #if FAN_COUNT > 0 + #if (HAS_FAN0 && FAN_NUM_AS_LASER!=0) + MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_FAN_SPEED FAN_SPEED_1_SUFFIX, &fanSpeeds[0], 0, 255); + #if ENABLED(EXTRA_FAN_SPEED) + MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_EXTRA_FAN_SPEED FAN_SPEED_1_SUFFIX, &new_fanSpeeds[0], 3, 255); + #endif + #endif + #if (HAS_FAN1 && FAN_NUM_AS_LASER!=1) + MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_FAN_SPEED " 2", &fanSpeeds[1], 0, 255); + #if ENABLED(EXTRA_FAN_SPEED) + MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_EXTRA_FAN_SPEED " 2", &new_fanSpeeds[1], 3, 255); + #endif + #endif + #if (HAS_FAN2 && FAN_NUM_AS_LASER!=2) + MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_FAN_SPEED " 3", &fanSpeeds[2], 0, 255); + #if ENABLED(EXTRA_FAN_SPEED) + MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_EXTRA_FAN_SPEED " 3", &new_fanSpeeds[2], 3, 255); + #endif + #endif + #endif // FAN_COUNT > 0 + + // + // Flow: + // Flow [1-5]: + // + #if EXTRUDERS == 1 + MENU_ITEM_EDIT_CALLBACK(int3, MSG_FLOW, &planner.flow_percentage[0], 10, 999, _lcd_refresh_e_factor_0); + #else // EXTRUDERS > 1 + MENU_ITEM_EDIT_CALLBACK(int3, MSG_FLOW, &planner.flow_percentage[active_extruder], 10, 999, _lcd_refresh_e_factor); + MENU_ITEM_EDIT_CALLBACK(int3, MSG_FLOW MSG_N1, &planner.flow_percentage[0], 10, 999, _lcd_refresh_e_factor_0); + MENU_ITEM_EDIT_CALLBACK(int3, MSG_FLOW MSG_N2, &planner.flow_percentage[1], 10, 999, _lcd_refresh_e_factor_1); + #if EXTRUDERS > 2 + MENU_ITEM_EDIT_CALLBACK(int3, MSG_FLOW MSG_N3, &planner.flow_percentage[2], 10, 999, _lcd_refresh_e_factor_2); + #if EXTRUDERS > 3 + MENU_ITEM_EDIT_CALLBACK(int3, MSG_FLOW MSG_N4, &planner.flow_percentage[3], 10, 999, _lcd_refresh_e_factor_3); + #if EXTRUDERS > 4 + MENU_ITEM_EDIT_CALLBACK(int3, MSG_FLOW MSG_N5, &planner.flow_percentage[4], 10, 999, _lcd_refresh_e_factor_4); + #endif // EXTRUDERS > 4 + #endif // EXTRUDERS > 3 + #endif // EXTRUDERS > 2 + #endif // EXTRUDERS > 1 + + // + // Babystep X: + // Babystep Y: + // Babystep Z: + // + #if ENABLED(BABYSTEPPING) + #if ENABLED(BABYSTEP_XY) + MENU_ITEM(submenu, MSG_BABYSTEP_X, lcd_babystep_x); + MENU_ITEM(submenu, MSG_BABYSTEP_Y, lcd_babystep_y); + #endif + #if ENABLED(BABYSTEP_ZPROBE_OFFSET) + MENU_ITEM(submenu, MSG_ZPROBE_ZOFFSET, lcd_babystep_zoffset); + #else + MENU_ITEM(submenu, MSG_BABYSTEP_Z, lcd_babystep_z); + #endif + #endif + + // + // Change filament + // + #if ENABLED(ADVANCED_PAUSE_FEATURE) + #if E_STEPPERS == 1 && !ENABLED(FILAMENT_LOAD_UNLOAD_GCODES) + if (thermalManager.targetHotEnoughToExtrude(active_extruder)) + MENU_ITEM(gcode, MSG_FILAMENTCHANGE, PSTR("M600 B0")); + else + MENU_ITEM(submenu, MSG_FILAMENTCHANGE, lcd_temp_menu_e0_filament_change); + #else + MENU_ITEM(submenu, MSG_FILAMENTCHANGE, lcd_change_filament_menu); + #endif + #endif + + END_MENU(); + } + + /** + * + * "Driver current control" submenu items + * + */ + #if ENABLED(DAC_STEPPER_CURRENT) + + void dac_driver_getValues() { LOOP_XYZE(i) driverPercent[i] = dac_current_get_percent((AxisEnum)i); } + + void dac_driver_commit() { dac_current_set_percents(driverPercent); } + + void dac_driver_eeprom_write() { dac_commit_eeprom(); } + + void lcd_dac_menu() { + dac_driver_getValues(); + START_MENU(); + MENU_BACK(MSG_CONTROL); + MENU_ITEM_EDIT_CALLBACK(int8, MSG_X " " MSG_DAC_PERCENT, &driverPercent[X_AXIS], 0, 100, dac_driver_commit); + MENU_ITEM_EDIT_CALLBACK(int8, MSG_Y " " MSG_DAC_PERCENT, &driverPercent[Y_AXIS], 0, 100, dac_driver_commit); + MENU_ITEM_EDIT_CALLBACK(int8, MSG_Z " " MSG_DAC_PERCENT, &driverPercent[Z_AXIS], 0, 100, dac_driver_commit); + MENU_ITEM_EDIT_CALLBACK(int8, MSG_E " " MSG_DAC_PERCENT, &driverPercent[E_AXIS], 0, 100, dac_driver_commit); + MENU_ITEM(function, MSG_DAC_EEPROM_WRITE, dac_driver_eeprom_write); + END_MENU(); + } + + #endif // DAC_STEPPER_CURRENT + + #if HAS_MOTOR_CURRENT_PWM + + void lcd_pwm_menu() { + START_MENU(); + MENU_BACK(MSG_CONTROL); + #if PIN_EXISTS(MOTOR_CURRENT_PWM_XY) + MENU_ITEM_EDIT_CALLBACK(long5, MSG_X MSG_Y, &stepper.motor_current_setting[0], 100, 2000, Stepper::refresh_motor_power); + #endif + #if PIN_EXISTS(MOTOR_CURRENT_PWM_Z) + MENU_ITEM_EDIT_CALLBACK(long5, MSG_Z, &stepper.motor_current_setting[1], 100, 2000, Stepper::refresh_motor_power); + #endif + #if PIN_EXISTS(MOTOR_CURRENT_PWM_E) + MENU_ITEM_EDIT_CALLBACK(long5, MSG_E, &stepper.motor_current_setting[2], 100, 2000, Stepper::refresh_motor_power); + #endif + END_MENU(); + } + + #endif // HAS_MOTOR_CURRENT_PWM + + constexpr int16_t heater_maxtemp[HOTENDS] = ARRAY_BY_HOTENDS(HEATER_0_MAXTEMP, HEATER_1_MAXTEMP, HEATER_2_MAXTEMP, HEATER_3_MAXTEMP, HEATER_4_MAXTEMP); + + /** + * + * "Prepare" submenu items + * + */ + void _lcd_preheat(const int16_t endnum, const int16_t temph, const int16_t tempb, const int16_t fan) { + if (temph > 0) thermalManager.setTargetHotend(min(heater_maxtemp[endnum], temph), endnum); + #if TEMP_SENSOR_BED != 0 + if (tempb >= 0) thermalManager.setTargetBed(tempb); + #else + UNUSED(tempb); + #endif + #if FAN_COUNT > 0 + #if FAN_COUNT > 1 + fanSpeeds[active_extruder < FAN_COUNT ? active_extruder : 0] = fan; + #else + fanSpeeds[0] = fan; + #endif + #else + UNUSED(fan); + #endif + lcd_return_to_status(); + } + + #if TEMP_SENSOR_0 != 0 + void lcd_preheat_m1_e0_only() { _lcd_preheat(0, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); } + void lcd_preheat_m2_e0_only() { _lcd_preheat(0, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); } + #if TEMP_SENSOR_BED != 0 + void lcd_preheat_m1_e0() { _lcd_preheat(0, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); } + void lcd_preheat_m2_e0() { _lcd_preheat(0, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); } + #endif + #endif + + #if HOTENDS > 1 + void lcd_preheat_m1_e1_only() { _lcd_preheat(1, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); } + void lcd_preheat_m2_e1_only() { _lcd_preheat(1, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); } + #if TEMP_SENSOR_BED != 0 + void lcd_preheat_m1_e1() { _lcd_preheat(1, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); } + void lcd_preheat_m2_e1() { _lcd_preheat(1, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); } + #endif + #if HOTENDS > 2 + void lcd_preheat_m1_e2_only() { _lcd_preheat(2, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); } + void lcd_preheat_m2_e2_only() { _lcd_preheat(2, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); } + #if TEMP_SENSOR_BED != 0 + void lcd_preheat_m1_e2() { _lcd_preheat(2, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); } + void lcd_preheat_m2_e2() { _lcd_preheat(2, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); } + #endif + #if HOTENDS > 3 + void lcd_preheat_m1_e3_only() { _lcd_preheat(3, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); } + void lcd_preheat_m2_e3_only() { _lcd_preheat(3, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); } + #if TEMP_SENSOR_BED != 0 + void lcd_preheat_m1_e3() { _lcd_preheat(3, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); } + void lcd_preheat_m2_e3() { _lcd_preheat(3, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); } + #endif + #if HOTENDS > 4 + void lcd_preheat_m1_e4_only() { _lcd_preheat(4, lcd_preheat_hotend_temp[0], -1, lcd_preheat_fan_speed[0]); } + void lcd_preheat_m2_e4_only() { _lcd_preheat(4, lcd_preheat_hotend_temp[1], -1, lcd_preheat_fan_speed[1]); } + #if TEMP_SENSOR_BED != 0 + void lcd_preheat_m1_e4() { _lcd_preheat(4, lcd_preheat_hotend_temp[0], lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); } + void lcd_preheat_m2_e4() { _lcd_preheat(4, lcd_preheat_hotend_temp[1], lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); } + #endif + #endif // HOTENDS > 4 + #endif // HOTENDS > 3 + #endif // HOTENDS > 2 + + void lcd_preheat_m1_all() { + #if HOTENDS > 1 + thermalManager.setTargetHotend(lcd_preheat_hotend_temp[0], 1); + #if HOTENDS > 2 + thermalManager.setTargetHotend(lcd_preheat_hotend_temp[0], 2); + #if HOTENDS > 3 + thermalManager.setTargetHotend(lcd_preheat_hotend_temp[0], 3); + #if HOTENDS > 4 + thermalManager.setTargetHotend(lcd_preheat_hotend_temp[0], 4); + #endif // HOTENDS > 4 + #endif // HOTENDS > 3 + #endif // HOTENDS > 2 + #endif // HOTENDS > 1 + #if TEMP_SENSOR_BED != 0 + lcd_preheat_m1_e0(); + #else + lcd_preheat_m1_e0_only(); + #endif + } + void lcd_preheat_m2_all() { + #if HOTENDS > 1 + thermalManager.setTargetHotend(lcd_preheat_hotend_temp[1], 1); + #if HOTENDS > 2 + thermalManager.setTargetHotend(lcd_preheat_hotend_temp[1], 2); + #if HOTENDS > 3 + thermalManager.setTargetHotend(lcd_preheat_hotend_temp[1], 3); + #if HOTENDS > 4 + thermalManager.setTargetHotend(lcd_preheat_hotend_temp[1], 4); + #endif // HOTENDS > 4 + #endif // HOTENDS > 3 + #endif // HOTENDS > 2 + #endif // HOTENDS > 1 + #if TEMP_SENSOR_BED != 0 + lcd_preheat_m2_e0(); + #else + lcd_preheat_m2_e0_only(); + #endif + } + + #endif // HOTENDS > 1 + + #if TEMP_SENSOR_BED != 0 + void lcd_preheat_m1_bedonly() { _lcd_preheat(0, 0, lcd_preheat_bed_temp[0], lcd_preheat_fan_speed[0]); } + void lcd_preheat_m2_bedonly() { _lcd_preheat(0, 0, lcd_preheat_bed_temp[1], lcd_preheat_fan_speed[1]); } + #endif + + #if TEMP_SENSOR_0 != 0 && (TEMP_SENSOR_1 != 0 || TEMP_SENSOR_2 != 0 || TEMP_SENSOR_3 != 0 || TEMP_SENSOR_4 != 0 || TEMP_SENSOR_BED != 0) + + void lcd_preheat_m1_menu() { + START_MENU(); + MENU_BACK(MSG_PREPARE); + #if HOTENDS == 1 + #if TEMP_SENSOR_BED != 0 + MENU_ITEM(function, MSG_PREHEAT_1, lcd_preheat_m1_e0); + MENU_ITEM(function, MSG_PREHEAT_1_END, lcd_preheat_m1_e0_only); + #else + MENU_ITEM(function, MSG_PREHEAT_1, lcd_preheat_m1_e0_only); + #endif + #else + #if TEMP_SENSOR_BED != 0 + MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H1, lcd_preheat_m1_e0); + MENU_ITEM(function, MSG_PREHEAT_1_END " " MSG_E1, lcd_preheat_m1_e0_only); + MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H2, lcd_preheat_m1_e1); + MENU_ITEM(function, MSG_PREHEAT_1_END " " MSG_E2, lcd_preheat_m1_e1_only); + #else + MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H1, lcd_preheat_m1_e0_only); + MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H2, lcd_preheat_m1_e1_only); + #endif + #if HOTENDS > 2 + #if TEMP_SENSOR_BED != 0 + MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H3, lcd_preheat_m1_e2); + MENU_ITEM(function, MSG_PREHEAT_1_END " " MSG_E3, lcd_preheat_m1_e2_only); + #else + MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H3, lcd_preheat_m1_e2_only); + #endif + #if HOTENDS > 3 + #if TEMP_SENSOR_BED != 0 + MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H4, lcd_preheat_m1_e3); + MENU_ITEM(function, MSG_PREHEAT_1_END " " MSG_E4, lcd_preheat_m1_e3_only); + #else + MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H4, lcd_preheat_m1_e3_only); + #endif + #if HOTENDS > 4 + #if TEMP_SENSOR_BED != 0 + MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H5, lcd_preheat_m1_e4); + MENU_ITEM(function, MSG_PREHEAT_1_END " " MSG_E5, lcd_preheat_m1_e4_only); + #else + MENU_ITEM(function, MSG_PREHEAT_1_N MSG_H5, lcd_preheat_m1_e4_only); + #endif + #endif // HOTENDS > 4 + #endif // HOTENDS > 3 + #endif // HOTENDS > 2 + MENU_ITEM(function, MSG_PREHEAT_1_ALL, lcd_preheat_m1_all); + #endif // HOTENDS > 1 + #if TEMP_SENSOR_BED != 0 + MENU_ITEM(function, MSG_PREHEAT_1_BEDONLY, lcd_preheat_m1_bedonly); + #endif + END_MENU(); + } + + void lcd_preheat_m2_menu() { + START_MENU(); + MENU_BACK(MSG_PREPARE); + #if HOTENDS == 1 + #if TEMP_SENSOR_BED != 0 + MENU_ITEM(function, MSG_PREHEAT_2, lcd_preheat_m2_e0); + MENU_ITEM(function, MSG_PREHEAT_2_END, lcd_preheat_m2_e0_only); + #else + MENU_ITEM(function, MSG_PREHEAT_2, lcd_preheat_m2_e0_only); + #endif + #else + #if TEMP_SENSOR_BED != 0 + MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H1, lcd_preheat_m2_e0); + MENU_ITEM(function, MSG_PREHEAT_2_END " " MSG_E1, lcd_preheat_m2_e0_only); + MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H2, lcd_preheat_m2_e1); + MENU_ITEM(function, MSG_PREHEAT_2_END " " MSG_E2, lcd_preheat_m2_e1_only); + #else + MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H1, lcd_preheat_m2_e0_only); + MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H2, lcd_preheat_m2_e1_only); + #endif + #if HOTENDS > 2 + #if TEMP_SENSOR_BED != 0 + MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H3, lcd_preheat_m2_e2); + MENU_ITEM(function, MSG_PREHEAT_2_END " " MSG_E3, lcd_preheat_m2_e2_only); + #else + MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H3, lcd_preheat_m2_e2_only); + #endif + #if HOTENDS > 3 + #if TEMP_SENSOR_BED != 0 + MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H4, lcd_preheat_m2_e3); + MENU_ITEM(function, MSG_PREHEAT_2_END " " MSG_E4, lcd_preheat_m2_e3_only); + #else + MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H4, lcd_preheat_m2_e3_only); + #endif + #if HOTENDS > 4 + #if TEMP_SENSOR_BED != 0 + MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H5, lcd_preheat_m2_e4); + MENU_ITEM(function, MSG_PREHEAT_2_END " " MSG_E5, lcd_preheat_m2_e4_only); + #else + MENU_ITEM(function, MSG_PREHEAT_2_N MSG_H5, lcd_preheat_m2_e4_only); + #endif + #endif // HOTENDS > 4 + #endif // HOTENDS > 3 + #endif // HOTENDS > 2 + MENU_ITEM(function, MSG_PREHEAT_2_ALL, lcd_preheat_m2_all); + #endif // HOTENDS > 1 + #if TEMP_SENSOR_BED != 0 + MENU_ITEM(function, MSG_PREHEAT_2_BEDONLY, lcd_preheat_m2_bedonly); + #endif + END_MENU(); + } + + #endif // TEMP_SENSOR_0 && (TEMP_SENSOR_1 || TEMP_SENSOR_2 || TEMP_SENSOR_3 || TEMP_SENSOR_4 || TEMP_SENSOR_BED) + + void lcd_cooldown() { + #if FAN_COUNT > 0 + for (uint8_t i = 0; i < FAN_COUNT; i++) fanSpeeds[i] = 0; + #endif + thermalManager.disable_all_heaters(); + lcd_return_to_status(); + } + + #if ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(PID_AUTOTUNE_MENU) || ENABLED(ADVANCED_PAUSE_FEATURE) + + /** + * If the queue is full, the command will fail, so we have to loop + * with idle() to make sure the command has been enqueued. + */ + void lcd_enqueue_command(char * const cmd) { + no_reentry = true; + enqueue_and_echo_command_now(cmd); + no_reentry = false; + } + + void lcd_enqueue_commands_P(const char * const cmd) { + no_reentry = true; + enqueue_and_echo_commands_P_now(cmd); + no_reentry = false; + } + + #endif + + #if ENABLED(SDSUPPORT) && ENABLED(MENU_ADDAUTOSTART) + + void lcd_autostart_sd() { + card.autostart_index = 0; + card.setroot(); + card.checkautostart(true); + } + + #endif + + #if ENABLED(EEPROM_SETTINGS) + static void lcd_store_settings() { lcd_completion_feedback(settings.save()); } + static void lcd_load_settings() { lcd_completion_feedback(settings.load()); } + #endif + + #if ENABLED(LEVEL_BED_CORNERS) + + /** + * Level corners, starting in the front-left corner. + */ + static int8_t bed_corner; + void _lcd_goto_next_corner() { + line_to_z(4.0); + switch (bed_corner) { + case 0: + current_position[X_AXIS] = X_MIN_BED + 10; + current_position[Y_AXIS] = Y_MIN_BED + 10; + break; + case 1: + current_position[X_AXIS] = X_MAX_BED - 10; + break; + case 2: + current_position[Y_AXIS] = Y_MAX_BED - 10; + break; + case 3: + current_position[X_AXIS] = X_MIN_BED + 10; + break; + } + planner.buffer_line_kinematic(current_position, MMM_TO_MMS(manual_feedrate_mm_m[X_AXIS]), active_extruder); + line_to_z(0.0); + if (++bed_corner > 3) bed_corner = 0; + } + + void _lcd_corner_submenu() { + START_MENU(); + MENU_ITEM(function, MSG_NEXT_CORNER, _lcd_goto_next_corner); + MENU_ITEM(function, MSG_BACK, lcd_goto_previous_menu_no_defer); + END_MENU(); + } + + void _lcd_level_bed_corners() { + defer_return_to_status = true; + lcd_goto_screen(_lcd_corner_submenu); + bed_corner = 0; + _lcd_goto_next_corner(); + } + + #endif // LEVEL_BED_CORNERS + + #if ENABLED(LCD_BED_LEVELING) + + /** + * + * "Prepare" > "Level Bed" handlers + * + */ + + static uint8_t manual_probe_index; + + // LCD probed points are from defaults + constexpr uint8_t total_probe_points = ( + #if ENABLED(AUTO_BED_LEVELING_3POINT) + 3 + #elif ABL_GRID || ENABLED(MESH_BED_LEVELING) + GRID_MAX_POINTS + #endif + ); + + bool lcd_wait_for_move; + + // + // 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 "manual probe height" + // - Don't return until done. + // + // ** This blocks the command queue! ** + // + void _lcd_level_bed_done() { + if (!lcd_wait_for_move) { + #if MANUAL_PROBE_HEIGHT > 0 && DISABLED(MESH_BED_LEVELING) + // Display "Done" screen and wait for moves to complete + line_to_z(Z_MIN_POS + MANUAL_PROBE_HEIGHT); + lcd_synchronize(PSTR(MSG_LEVEL_BED_DONE)); + #endif + lcd_goto_previous_menu_no_defer(); + lcd_completion_feedback(); + } + if (lcdDrawUpdate) lcd_implementation_drawmenu_static(LCD_HEIGHT >= 4 ? 1 : 0, PSTR(MSG_LEVEL_BED_DONE)); + lcdDrawUpdate = 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() { + ENCODER_DIRECTION_NORMAL(); + + if (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 + // + lcd_wait_for_move = true; + lcd_goto_screen(_lcd_level_bed_done); + #if ENABLED(PROBE_MANUALLY) + enqueue_and_echo_commands_P(PSTR("G29 V1")); + #elif ENABLED(MESH_BED_LEVELING) + enqueue_and_echo_commands_P(PSTR("G29 S2")); + #endif + } + else + _lcd_level_goto_next_point(); + + return; + } + + // + // Encoder knob or keypad buttons adjust the Z position + // + if (encoderPosition) { + const float z = current_position[Z_AXIS] + float((int32_t)encoderPosition) * (MBL_Z_STEP); + line_to_z(constrain(z, -(LCD_PROBE_Z_RANGE) * 0.5, (LCD_PROBE_Z_RANGE) * 0.5)); + lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT; + encoderPosition = 0; + } + + // + // Draw on first display, then only on Z change + // + if (lcdDrawUpdate) { + const float v = current_position[Z_AXIS]; + lcd_implementation_drawedit(PSTR(MSG_MOVE_Z), ftostr43sign(v + (v < 0 ? -0.0001 : 0.0001), '+')); + } + } + + /** + * Step 6: Display "Next point: 1 / 9" while waiting for move to finish + */ + void _lcd_level_bed_moving() { + if (lcdDrawUpdate) { + char msg[10]; + sprintf_P(msg, PSTR("%i / %u"), (int)(manual_probe_index + 1), total_probe_points); + lcd_implementation_drawedit(PSTR(MSG_LEVEL_BED_NEXT_POINT), msg); + } + lcdDrawUpdate = LCDVIEW_CALL_NO_REDRAW; + if (!lcd_wait_for_move) lcd_goto_screen(_lcd_level_bed_get_z); + } + + /** + * Step 5: Initiate a move to the next point + */ + void _lcd_level_goto_next_point() { + lcd_goto_screen(_lcd_level_bed_moving); + + // G29 Records Z, moves, and signals when it pauses + lcd_wait_for_move = true; + #if ENABLED(PROBE_MANUALLY) + enqueue_and_echo_commands_P(PSTR("G29 V1")); + #elif ENABLED(MESH_BED_LEVELING) + enqueue_and_echo_commands_P(manual_probe_index ? PSTR("G29 S2") : PSTR("G29 S1")); + #endif + } + + /** + * Step 4: Display "Click to Begin", wait for click + * Move to the first probe position + */ + void _lcd_level_bed_homing_done() { + if (lcdDrawUpdate) lcd_implementation_drawedit(PSTR(MSG_LEVEL_BED_WAITING)); + if (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() { + if (lcdDrawUpdate) lcd_implementation_drawedit(PSTR(MSG_LEVEL_BED_HOMING), NULL); + lcdDrawUpdate = LCDVIEW_CALL_NO_REDRAW; + if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS]) + lcd_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() { + defer_return_to_status = true; + axis_homed[X_AXIS] = axis_homed[Y_AXIS] = axis_homed[Z_AXIS] = false; + lcd_goto_screen(_lcd_level_bed_homing); + enqueue_and_echo_commands_P(PSTR("G28")); + } + + static bool new_level_state; + void _lcd_toggle_bed_leveling() { set_bed_leveling_enabled(new_level_state); } + + /** + * Step 1: Bed Level entry-point + * + * << Prepare + * Auto Home (if homing needed) + * Leveling On/Off (if data exists, and homed) + * Fade Height: --- (Req: ENABLE_LEVELING_FADE_HEIGHT) + * Mesh Z Offset: --- (Req: MESH_BED_LEVELING) + * Z Probe Offset: --- (Req: HAS_BED_PROBE, Opt: BABYSTEP_ZPROBE_OFFSET) + * Level Bed > + * Level Corners > (if homed) + * Load Settings (Req: EEPROM_SETTINGS) + * Save Settings (Req: EEPROM_SETTINGS) + */ + void lcd_bed_leveling() { + START_MENU(); + MENU_BACK(MSG_PREPARE); + + #if DISABLED(MESH_BED_LEVELING) + if (!(axis_known_position[X_AXIS] && axis_known_position[Y_AXIS] && axis_known_position[Z_AXIS])) + MENU_ITEM(gcode, MSG_AUTO_HOME, PSTR("G28")); + else + #endif + if (leveling_is_valid()) { + new_level_state = planner.leveling_active; + MENU_ITEM_EDIT_CALLBACK(bool, MSG_BED_LEVELING, &new_level_state, _lcd_toggle_bed_leveling); + } + + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_Z_FADE_HEIGHT, &new_z_fade_height, 0.0, 100.0, _lcd_set_z_fade_height); + #endif + + // + // MBL Z Offset + // + #if ENABLED(MESH_BED_LEVELING) + MENU_ITEM_EDIT(float43, MSG_BED_Z, &mbl.z_offset, -1, 1); + #endif + + #if ENABLED(BABYSTEP_ZPROBE_OFFSET) + MENU_ITEM(submenu, MSG_ZPROBE_ZOFFSET, lcd_babystep_zoffset); + #elif HAS_BED_PROBE + MENU_ITEM_EDIT(float32, MSG_ZPROBE_ZOFFSET, &zprobe_zoffset, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX); + #endif + + MENU_ITEM(submenu, MSG_LEVEL_BED, _lcd_level_bed_continue); + + #if ENABLED(LEVEL_BED_CORNERS) + // Move to the next corner for leveling + if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS]) + MENU_ITEM(submenu, MSG_LEVEL_CORNERS, _lcd_level_bed_corners); + #endif + + #if ENABLED(EEPROM_SETTINGS) + MENU_ITEM(function, MSG_LOAD_EEPROM, lcd_load_settings); + MENU_ITEM(function, MSG_STORE_EEPROM, lcd_store_settings); + #endif + END_MENU(); + } + + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + void _lcd_goto_bed_leveling() { + lcd_goto_screen(lcd_bed_leveling); + new_z_fade_height = planner.z_fade_height; + } + #endif + + #elif ENABLED(AUTO_BED_LEVELING_UBL) + + void _lcd_ubl_level_bed(); + + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + void _lcd_goto_ubl_level_bed() { + lcd_goto_screen(_lcd_ubl_level_bed); + new_z_fade_height = planner.z_fade_height; + } + #endif + + static int16_t ubl_storage_slot = 0, + custom_hotend_temp = 190, + side_points = 3, + ubl_fillin_amount = 5, + ubl_height_amount = 1, + n_edit_pts = 1, + x_plot = 0, + y_plot = 0; + + #if HAS_TEMP_BED + static int16_t custom_bed_temp = 50; + #endif + + /** + * UBL Build Custom Mesh Command + */ + void _lcd_ubl_build_custom_mesh() { + char UBL_LCD_GCODE[20]; + enqueue_and_echo_commands_P(PSTR("G28")); + #if HAS_TEMP_BED + sprintf_P(UBL_LCD_GCODE, PSTR("M190 S%i"), custom_bed_temp); + lcd_enqueue_command(UBL_LCD_GCODE); + #endif + sprintf_P(UBL_LCD_GCODE, PSTR("M109 S%i"), custom_hotend_temp); + lcd_enqueue_command(UBL_LCD_GCODE); + enqueue_and_echo_commands_P(PSTR("G29 P1")); + } + + /** + * UBL Custom Mesh submenu + * + * << Build Mesh + * Hotend Temp: --- + * Bed Temp: --- + * Build Custom Mesh + */ + void _lcd_ubl_custom_mesh() { + START_MENU(); + MENU_BACK(MSG_UBL_BUILD_MESH_MENU); + MENU_ITEM_EDIT(int3, MSG_UBL_CUSTOM_HOTEND_TEMP, &custom_hotend_temp, EXTRUDE_MINTEMP, (HEATER_0_MAXTEMP - 10)); + #if HAS_TEMP_BED + MENU_ITEM_EDIT(int3, MSG_UBL_CUSTOM_BED_TEMP, &custom_bed_temp, BED_MINTEMP, (BED_MAXTEMP - 15)); + #endif + MENU_ITEM(function, MSG_UBL_BUILD_CUSTOM_MESH, _lcd_ubl_build_custom_mesh); + END_MENU(); + } + + /** + * UBL Adjust Mesh Height Command + */ + void _lcd_ubl_adjust_height_cmd() { + char UBL_LCD_GCODE[16]; + const int ind = ubl_height_amount > 0 ? 9 : 10; + strcpy_P(UBL_LCD_GCODE, PSTR("G29 P6 C -")); + sprintf_P(&UBL_LCD_GCODE[ind], PSTR(".%i"), abs(ubl_height_amount)); + lcd_enqueue_command(UBL_LCD_GCODE); + } + + /** + * UBL Adjust Mesh Height submenu + * + * << Edit Mesh + * Height Amount: --- + * Adjust Mesh Height + * << Info Screen + */ + void _lcd_ubl_height_adjust_menu() { + START_MENU(); + MENU_BACK(MSG_UBL_EDIT_MESH_MENU); + MENU_ITEM_EDIT_CALLBACK(int3, MSG_UBL_MESH_HEIGHT_AMOUNT, &ubl_height_amount, -9, 9, _lcd_ubl_adjust_height_cmd); + MENU_ITEM(function, MSG_WATCH, lcd_return_to_status); + END_MENU(); + } + + /** + * UBL Edit Mesh submenu + * + * << UBL Tools + * Fine Tune All + * Fine Tune Closest + * - Adjust Mesh Height >> + * << Info Screen + */ + void _lcd_ubl_edit_mesh() { + START_MENU(); + MENU_BACK(MSG_UBL_TOOLS); + MENU_ITEM(gcode, MSG_UBL_FINE_TUNE_ALL, PSTR("G29 P4 R999 T")); + MENU_ITEM(gcode, MSG_UBL_FINE_TUNE_CLOSEST, PSTR("G29 P4 T")); + MENU_ITEM(submenu, MSG_UBL_MESH_HEIGHT_ADJUST, _lcd_ubl_height_adjust_menu); + MENU_ITEM(function, MSG_WATCH, lcd_return_to_status); + END_MENU(); + } + + /** + * UBL Validate Custom Mesh Command + */ + void _lcd_ubl_validate_custom_mesh() { + char UBL_LCD_GCODE[24]; + const int temp = + #if HAS_TEMP_BED + custom_bed_temp + #else + 0 + #endif + ; + sprintf_P(UBL_LCD_GCODE, PSTR("G26 C B%i H%i P"), temp, custom_hotend_temp); + lcd_enqueue_commands_P(PSTR("G28")); + lcd_enqueue_command(UBL_LCD_GCODE); + } + + /** + * UBL Validate Mesh submenu + * + * << UBL Tools + * PLA Mesh Validation + * ABS Mesh Validation + * Validate Custom Mesh + * << Info Screen + */ + void _lcd_ubl_validate_mesh() { + START_MENU(); + MENU_BACK(MSG_UBL_TOOLS); + #if HAS_TEMP_BED + MENU_ITEM(gcode, MSG_UBL_VALIDATE_PLA_MESH, PSTR("G28\nG26 C B" STRINGIFY(PREHEAT_1_TEMP_BED) " H" STRINGIFY(PREHEAT_1_TEMP_HOTEND) " P")); + MENU_ITEM(gcode, MSG_UBL_VALIDATE_ABS_MESH, PSTR("G28\nG26 C B" STRINGIFY(PREHEAT_2_TEMP_BED) " H" STRINGIFY(PREHEAT_2_TEMP_HOTEND) " P")); + #else + MENU_ITEM(gcode, MSG_UBL_VALIDATE_PLA_MESH, PSTR("G28\nG26 C B0 H" STRINGIFY(PREHEAT_1_TEMP_HOTEND) " P")); + MENU_ITEM(gcode, MSG_UBL_VALIDATE_ABS_MESH, PSTR("G28\nG26 C B0 H" STRINGIFY(PREHEAT_2_TEMP_HOTEND) " P")); + #endif + MENU_ITEM(function, MSG_UBL_VALIDATE_CUSTOM_MESH, _lcd_ubl_validate_custom_mesh); + MENU_ITEM(function, MSG_WATCH, lcd_return_to_status); + END_MENU(); + } + + /** + * UBL Grid Leveling Command + */ + void _lcd_ubl_grid_level_cmd() { + char UBL_LCD_GCODE[10]; + sprintf_P(UBL_LCD_GCODE, PSTR("G29 J%i"), side_points); + lcd_enqueue_command(UBL_LCD_GCODE); + } + + /** + * UBL Grid Leveling submenu + * + * << UBL Tools + * Side points: --- + * Level Mesh + */ + void _lcd_ubl_grid_level() { + START_MENU(); + MENU_BACK(MSG_UBL_TOOLS); + MENU_ITEM_EDIT(int3, MSG_UBL_SIDE_POINTS, &side_points, 2, 6); + MENU_ITEM(function, MSG_UBL_MESH_LEVEL, _lcd_ubl_grid_level_cmd); + END_MENU(); + } + + /** + * UBL Mesh Leveling submenu + * + * << UBL Tools + * 3-Point Mesh Leveling + * - Grid Mesh Leveling >> + * << Info Screen + */ + void _lcd_ubl_mesh_leveling() { + START_MENU(); + MENU_BACK(MSG_UBL_TOOLS); + MENU_ITEM(gcode, MSG_UBL_3POINT_MESH_LEVELING, PSTR("G29 J0")); + MENU_ITEM(submenu, MSG_UBL_GRID_MESH_LEVELING, _lcd_ubl_grid_level); + MENU_ITEM(function, MSG_WATCH, lcd_return_to_status); + END_MENU(); + } + + /** + * UBL Fill-in Amount Mesh Command + */ + void _lcd_ubl_fillin_amount_cmd() { + char UBL_LCD_GCODE[16]; + sprintf_P(UBL_LCD_GCODE, PSTR("G29 P3 R C.%i"), ubl_fillin_amount); + lcd_enqueue_command(UBL_LCD_GCODE); + } + + /** + * UBL Fill-in Mesh submenu + * + * << Build Mesh + * Fill-in Amount: --- + * Fill-in Mesh + * Smart Fill-in + * Manual Fill-in + * << Info Screen + */ + void _lcd_ubl_fillin_menu() { + START_MENU(); + MENU_BACK(MSG_UBL_BUILD_MESH_MENU); + MENU_ITEM_EDIT_CALLBACK(int3, MSG_UBL_FILLIN_AMOUNT, &ubl_fillin_amount, 0, 9, _lcd_ubl_fillin_amount_cmd); + MENU_ITEM(gcode, MSG_UBL_SMART_FILLIN, PSTR("G29 P3 T0")); + MENU_ITEM(gcode, MSG_UBL_MANUAL_FILLIN, PSTR("G29 P2 B T0")); + MENU_ITEM(function, MSG_WATCH, lcd_return_to_status); + END_MENU(); + } + + void _lcd_ubl_invalidate() { + ubl.invalidate(); + SERIAL_PROTOCOLLNPGM("Mesh invalidated."); + } + + /** + * UBL Build Mesh submenu + * + * << UBL Tools + * Build PLA Mesh + * Build ABS Mesh + * - Build Custom Mesh >> + * Build Cold Mesh + * - Fill-in Mesh >> + * Continue Bed Mesh + * Invalidate All + * Invalidate Closest + * << Info Screen + */ + void _lcd_ubl_build_mesh() { + START_MENU(); + MENU_BACK(MSG_UBL_TOOLS); + #if HAS_TEMP_BED + MENU_ITEM(gcode, MSG_UBL_BUILD_PLA_MESH, PSTR( + "G28\n" + "M190 S" STRINGIFY(PREHEAT_1_TEMP_BED) "\n" + "M109 S" STRINGIFY(PREHEAT_1_TEMP_HOTEND) "\n" + "G29 P1\n" + "M104 S0\n" + "M140 S0" + )); + MENU_ITEM(gcode, MSG_UBL_BUILD_ABS_MESH, PSTR( + "G28\n" + "M190 S" STRINGIFY(PREHEAT_2_TEMP_BED) "\n" + "M109 S" STRINGIFY(PREHEAT_2_TEMP_HOTEND) "\n" + "G29 P1\n" + "M104 S0\n" + "M140 S0" + )); + #else + MENU_ITEM(gcode, MSG_UBL_BUILD_PLA_MESH, PSTR( + "G28\n" + "M109 S" STRINGIFY(PREHEAT_1_TEMP_HOTEND) "\n" + "G29 P1\n" + "M104 S0" + )); + MENU_ITEM(gcode, MSG_UBL_BUILD_ABS_MESH, PSTR( + "G28\n" + "M109 S" STRINGIFY(PREHEAT_2_TEMP_HOTEND) "\n" + "G29 P1\n" + "M104 S0" + )); + #endif + MENU_ITEM(submenu, MSG_UBL_BUILD_CUSTOM_MESH, _lcd_ubl_custom_mesh); + MENU_ITEM(gcode, MSG_UBL_BUILD_COLD_MESH, PSTR("G28\nG29 P1")); + MENU_ITEM(submenu, MSG_UBL_FILLIN_MESH, _lcd_ubl_fillin_menu); + MENU_ITEM(gcode, MSG_UBL_CONTINUE_MESH, PSTR("G29 P1 C")); + MENU_ITEM(function, MSG_UBL_INVALIDATE_ALL, _lcd_ubl_invalidate); + MENU_ITEM(gcode, MSG_UBL_INVALIDATE_CLOSEST, PSTR("G29 I")); + MENU_ITEM(function, MSG_WATCH, lcd_return_to_status); + END_MENU(); + } + + /** + * UBL Load Mesh Command + */ + void _lcd_ubl_load_mesh_cmd() { + char UBL_LCD_GCODE[25]; + sprintf_P(UBL_LCD_GCODE, PSTR("G29 L%i"), ubl_storage_slot); + lcd_enqueue_command(UBL_LCD_GCODE); + sprintf_P(UBL_LCD_GCODE, PSTR("M117 " MSG_MESH_LOADED), ubl_storage_slot); + lcd_enqueue_command(UBL_LCD_GCODE); + } + + /** + * UBL Save Mesh Command + */ + void _lcd_ubl_save_mesh_cmd() { + char UBL_LCD_GCODE[25]; + sprintf_P(UBL_LCD_GCODE, PSTR("G29 S%i"), ubl_storage_slot); + lcd_enqueue_command(UBL_LCD_GCODE); + sprintf_P(UBL_LCD_GCODE, PSTR("M117 " MSG_MESH_SAVED), ubl_storage_slot); + lcd_enqueue_command(UBL_LCD_GCODE); + } + + /** + * UBL Mesh Storage submenu + * + * << Unified Bed Leveling + * Memory Slot: --- + * Load Bed Mesh + * Save Bed Mesh + */ + void _lcd_ubl_storage_mesh() { + int16_t a = settings.calc_num_meshes(); + START_MENU(); + MENU_BACK(MSG_UBL_LEVEL_BED); + if (!WITHIN(ubl_storage_slot, 0, a - 1)) { + STATIC_ITEM(MSG_NO_STORAGE); + } + else { + MENU_ITEM_EDIT(int3, MSG_UBL_STORAGE_SLOT, &ubl_storage_slot, 0, a - 1); + MENU_ITEM(function, MSG_UBL_LOAD_MESH, _lcd_ubl_load_mesh_cmd); + MENU_ITEM(function, MSG_UBL_SAVE_MESH, _lcd_ubl_save_mesh_cmd); + } + END_MENU(); + } + + /** + * UBL LCD "radar" map homing + */ + void _lcd_ubl_output_map_lcd(); + + void _lcd_ubl_map_homing() { + defer_return_to_status = true; + if (lcdDrawUpdate) lcd_implementation_drawmenu_static(LCD_HEIGHT < 3 ? 0 : (LCD_HEIGHT > 4 ? 2 : 1), PSTR(MSG_LEVEL_BED_HOMING)); + lcdDrawUpdate = LCDVIEW_CALL_NO_REDRAW; + if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS]) { + ubl.lcd_map_control = true; // Return to the map screen + lcd_goto_screen(_lcd_ubl_output_map_lcd); + } + } + + /** + * UBL LCD "radar" map point editing + */ + void _lcd_ubl_map_lcd_edit_cmd() { + char UBL_LCD_GCODE[50], str[10], str2[10]; + dtostrf(pgm_read_float(&ubl._mesh_index_to_xpos[x_plot]), 0, 2, str); + dtostrf(pgm_read_float(&ubl._mesh_index_to_ypos[y_plot]), 0, 2, str2); + snprintf_P(UBL_LCD_GCODE, sizeof(UBL_LCD_GCODE), PSTR("G29 P4 X%s Y%s R%i"), str, str2, n_edit_pts); + lcd_enqueue_command(UBL_LCD_GCODE); + } + + /** + * UBL LCD Map Movement + */ + void ubl_map_move_to_xy() { + current_position[X_AXIS] = pgm_read_float(&ubl._mesh_index_to_xpos[x_plot]); + current_position[Y_AXIS] = pgm_read_float(&ubl._mesh_index_to_ypos[y_plot]); + planner.buffer_line_kinematic(current_position, MMM_TO_MMS(XY_PROBE_SPEED), active_extruder); + } + + /** + * UBL LCD "radar" map + */ + void set_current_from_steppers_for_axis(const AxisEnum axis); + + void _lcd_do_nothing() {} + void _lcd_hard_stop() { + stepper.quick_stop(); + const screenFunc_t old_screen = currentScreen; + currentScreen = _lcd_do_nothing; + while (planner.movesplanned()) idle(); + currentScreen = old_screen; + stepper.cleaning_buffer_counter = 0; + set_current_from_steppers_for_axis(ALL_AXES); + sync_plan_position(); + } + + void _lcd_ubl_output_map_lcd() { + static int16_t step_scaler = 0; + + if (!(axis_known_position[X_AXIS] && axis_known_position[Y_AXIS] && axis_known_position[Z_AXIS])) + return lcd_goto_screen(_lcd_ubl_map_homing); + + if (use_click()) return _lcd_ubl_map_lcd_edit_cmd(); + ENCODER_DIRECTION_NORMAL(); + + if (encoderPosition) { + step_scaler += (int32_t)encoderPosition; + x_plot += step_scaler / (ENCODER_STEPS_PER_MENU_ITEM); + if (abs(step_scaler) >= ENCODER_STEPS_PER_MENU_ITEM) step_scaler = 0; + encoderPosition = 0; + lcdDrawUpdate = LCDVIEW_REDRAW_NOW; + } + + // Encoder to the right (++) + if (x_plot >= GRID_MAX_POINTS_X) { x_plot = 0; y_plot++; } + if (y_plot >= GRID_MAX_POINTS_Y) y_plot = 0; + + // Encoder to the left (--) + if (x_plot <= GRID_MAX_POINTS_X - (GRID_MAX_POINTS_X + 1)) { x_plot = GRID_MAX_POINTS_X - 1; y_plot--; } + if (y_plot <= GRID_MAX_POINTS_Y - (GRID_MAX_POINTS_Y + 1)) y_plot = GRID_MAX_POINTS_Y - 1; + + // Prevent underrun/overrun of plot numbers + x_plot = constrain(x_plot, GRID_MAX_POINTS_X - (GRID_MAX_POINTS_X + 1), GRID_MAX_POINTS_X + 1); + y_plot = constrain(y_plot, GRID_MAX_POINTS_Y - (GRID_MAX_POINTS_Y + 1), GRID_MAX_POINTS_Y + 1); + + // Determine number of points to edit + #if IS_KINEMATIC + n_edit_pts = 9; //TODO: Delta accessible edit points + #else + const bool xc = WITHIN(x_plot, 1, GRID_MAX_POINTS_X - 2), + yc = WITHIN(y_plot, 1, GRID_MAX_POINTS_Y - 2); + n_edit_pts = yc ? (xc ? 9 : 6) : (xc ? 6 : 4); // Corners + #endif + + if (lcdDrawUpdate) { + lcd_implementation_ubl_plot(x_plot, y_plot); + + if (planner.movesplanned()) // If the nozzle is already moving, cancel the move. + _lcd_hard_stop(); + + ubl_map_move_to_xy(); // Move to new location + } + } + + /** + * UBL Homing before LCD map + */ + void _lcd_ubl_output_map_lcd_cmd() { + if (!(axis_known_position[X_AXIS] && axis_known_position[Y_AXIS] && axis_known_position[Z_AXIS])) { + axis_homed[X_AXIS] = axis_homed[Y_AXIS] = axis_homed[Z_AXIS] = false; + enqueue_and_echo_commands_P(PSTR("G28")); + } + lcd_goto_screen(_lcd_ubl_map_homing); + } + + /** + * UBL Output map submenu + * + * << Unified Bed Leveling + * Output for Host + * Output for CSV + * Off Printer Backup + * Output Mesh Map + */ + void _lcd_ubl_output_map() { + START_MENU(); + MENU_BACK(MSG_UBL_LEVEL_BED); + MENU_ITEM(gcode, MSG_UBL_OUTPUT_MAP_HOST, PSTR("G29 T0")); + MENU_ITEM(gcode, MSG_UBL_OUTPUT_MAP_CSV, PSTR("G29 T1")); + MENU_ITEM(gcode, MSG_UBL_OUTPUT_MAP_BACKUP, PSTR("G29 S-1")); + MENU_ITEM(function, MSG_UBL_OUTPUT_MAP, _lcd_ubl_output_map_lcd_cmd); + END_MENU(); + } + + /** + * UBL Tools submenu + * + * << Unified Bed Leveling + * - Build Mesh >> + * - Validate Mesh >> + * - Edit Mesh >> + * - Mesh Leveling >> + */ + void _lcd_ubl_tools_menu() { + START_MENU(); + MENU_BACK(MSG_UBL_LEVEL_BED); + MENU_ITEM(submenu, MSG_UBL_BUILD_MESH_MENU, _lcd_ubl_build_mesh); + MENU_ITEM(gcode, MSG_UBL_MANUAL_MESH, PSTR("G29 I999\nG29 P2 B T0")); + MENU_ITEM(submenu, MSG_UBL_VALIDATE_MESH_MENU, _lcd_ubl_validate_mesh); + MENU_ITEM(submenu, MSG_UBL_EDIT_MESH_MENU, _lcd_ubl_edit_mesh); + MENU_ITEM(submenu, MSG_UBL_MESH_LEVELING, _lcd_ubl_mesh_leveling); + END_MENU(); + } + + /** + * UBL Step-By-Step submenu + * + * << Unified Bed Leveling + * 1 Build Cold Mesh + * 2 Smart Fill-in + * - 3 Validate Mesh >> + * 4 Fine Tune All + * - 5 Validate Mesh >> + * 6 Fine Tune All + * 7 Save Bed Mesh + */ + void _lcd_ubl_step_by_step() { + START_MENU(); + MENU_BACK(MSG_UBL_LEVEL_BED); + MENU_ITEM(gcode, "1 " MSG_UBL_BUILD_COLD_MESH, PSTR("G28\nG29 P1")); + MENU_ITEM(gcode, "2 " MSG_UBL_SMART_FILLIN, PSTR("G29 P3 T0")); + MENU_ITEM(submenu, "3 " MSG_UBL_VALIDATE_MESH_MENU, _lcd_ubl_validate_mesh); + MENU_ITEM(gcode, "4 " MSG_UBL_FINE_TUNE_ALL, PSTR("G29 P4 R999 T")); + MENU_ITEM(submenu, "5 " MSG_UBL_VALIDATE_MESH_MENU, _lcd_ubl_validate_mesh); + MENU_ITEM(gcode, "6 " MSG_UBL_FINE_TUNE_ALL, PSTR("G29 P4 R999 T")); + MENU_ITEM(function, "7 " MSG_UBL_SAVE_MESH, _lcd_ubl_save_mesh_cmd); + END_MENU(); + } + + /** + * UBL System submenu + * + * << Prepare + * - Manually Build Mesh >> + * - Activate UBL >> + * - Deactivate UBL >> + * - Step-By-Step UBL >> + * - Mesh Storage >> + * - Output Map >> + * - UBL Tools >> + * - Output UBL Info >> + */ + + void _lcd_ubl_level_bed() { + START_MENU(); + MENU_BACK(MSG_PREPARE); + MENU_ITEM(gcode, MSG_UBL_ACTIVATE_MESH, PSTR("G29 A")); + MENU_ITEM(gcode, MSG_UBL_DEACTIVATE_MESH, PSTR("G29 D")); + MENU_ITEM(submenu, MSG_UBL_STEP_BY_STEP_MENU, _lcd_ubl_step_by_step); + MENU_ITEM(function, MSG_UBL_MESH_EDIT, _lcd_ubl_output_map_lcd_cmd); + MENU_ITEM(submenu, MSG_UBL_STORAGE_MESH_MENU, _lcd_ubl_storage_mesh); + MENU_ITEM(submenu, MSG_UBL_OUTPUT_MAP, _lcd_ubl_output_map); + MENU_ITEM(submenu, MSG_UBL_TOOLS, _lcd_ubl_tools_menu); + MENU_ITEM(gcode, MSG_UBL_INFO_UBL, PSTR("G29 W")); + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float3, MSG_Z_FADE_HEIGHT, &new_z_fade_height, 0.0, 100.0, _lcd_set_z_fade_height); + #endif + END_MENU(); + } + + #endif // AUTO_BED_LEVELING_UBL + + /** + * + * "Prepare" submenu + * + */ + + void lcd_prepare_menu() { + START_MENU(); + + // + // ^ Main + // + MENU_BACK(MSG_MAIN); + + // + // Move Axis + // + #if ENABLED(DELTA) + if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS]) + #endif + MENU_ITEM(submenu, MSG_MOVE_AXIS, lcd_move_menu); + + // + // Auto Home + // + MENU_ITEM(gcode, MSG_AUTO_HOME, PSTR("G28")); + #if ENABLED(INDIVIDUAL_AXIS_HOMING_MENU) + MENU_ITEM(gcode, MSG_AUTO_HOME_X, PSTR("G28 X")); + MENU_ITEM(gcode, MSG_AUTO_HOME_Y, PSTR("G28 Y")); + MENU_ITEM(gcode, MSG_AUTO_HOME_Z, PSTR("G28 Z")); + #endif + + // + // Level Bed + // + #if ENABLED(AUTO_BED_LEVELING_UBL) + MENU_ITEM(submenu, MSG_UBL_LEVEL_BED, + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + _lcd_goto_ubl_level_bed + #else + _lcd_ubl_level_bed + #endif + ); + #elif ENABLED(LCD_BED_LEVELING) + #if ENABLED(PROBE_MANUALLY) + if (!g29_in_progress) + #endif + MENU_ITEM(submenu, MSG_BED_LEVELING, + #if ENABLED(ENABLE_LEVELING_FADE_HEIGHT) + _lcd_goto_bed_leveling + #else + lcd_bed_leveling + #endif + ); + #elif PLANNER_LEVELING && DISABLED(PROBE_MANUALLY) && DISABLED(SLIM_LCD_MENUS) + MENU_ITEM(gcode, MSG_BED_LEVELING, PSTR("G28\nG29")); + #endif + + #if ENABLED(LEVEL_BED_CORNERS) && DISABLED(LCD_BED_LEVELING) + if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS]) + MENU_ITEM(function, MSG_LEVEL_CORNERS, _lcd_level_bed_corners); + #endif + + #if HAS_M206_COMMAND && DISABLED(SLIM_LCD_MENUS) + // + // Set Home Offsets + // + MENU_ITEM(function, MSG_SET_HOME_OFFSETS, lcd_set_home_offsets); + #endif + + // + // Disable Steppers + // + MENU_ITEM(gcode, MSG_DISABLE_STEPPERS, PSTR("M84")); + + // + // Change filament + // + #if ENABLED(ADVANCED_PAUSE_FEATURE) + if (!IS_SD_FILE_OPEN) { + #if E_STEPPERS == 1 && !ENABLED(FILAMENT_LOAD_UNLOAD_GCODES) + if (thermalManager.targetHotEnoughToExtrude(active_extruder)) + MENU_ITEM(gcode, MSG_FILAMENTCHANGE, PSTR("M600 B0")); + else + MENU_ITEM(submenu, MSG_FILAMENTCHANGE, lcd_temp_menu_e0_filament_change); + #else + MENU_ITEM(submenu, MSG_FILAMENTCHANGE, lcd_change_filament_menu); + #endif + } + #endif // ADVANCED_PAUSE_FEATURE + + #if TEMP_SENSOR_0 != 0 + + // + // Cooldown + // + bool has_heat = false; + HOTEND_LOOP() if (thermalManager.target_temperature[HOTEND_INDEX]) { has_heat = true; break; } + #if HAS_TEMP_BED + if (thermalManager.target_temperature_bed) has_heat = true; + #endif + if (has_heat) MENU_ITEM(function, MSG_COOLDOWN, lcd_cooldown); + + // + // Preheat for Material 1 and 2 + // + #if TEMP_SENSOR_1 != 0 || TEMP_SENSOR_2 != 0 || TEMP_SENSOR_3 != 0 || TEMP_SENSOR_4 != 0 || TEMP_SENSOR_BED != 0 + MENU_ITEM(submenu, MSG_PREHEAT_1, lcd_preheat_m1_menu); + MENU_ITEM(submenu, MSG_PREHEAT_2, lcd_preheat_m2_menu); + #else + MENU_ITEM(function, MSG_PREHEAT_1, lcd_preheat_m1_e0_only); + MENU_ITEM(function, MSG_PREHEAT_2, lcd_preheat_m2_e0_only); + #endif + + #endif // TEMP_SENSOR_0 != 0 + + // + // BLTouch Self-Test and Reset + // + #if ENABLED(BLTOUCH) + MENU_ITEM(gcode, MSG_BLTOUCH_SELFTEST, PSTR("M280 P" STRINGIFY(Z_ENDSTOP_SERVO_NR) " S" STRINGIFY(BLTOUCH_SELFTEST))); + if (!endstops.z_probe_enabled && TEST_BLTOUCH()) + MENU_ITEM(gcode, MSG_BLTOUCH_RESET, PSTR("M280 P" STRINGIFY(Z_ENDSTOP_SERVO_NR) " S" STRINGIFY(BLTOUCH_RESET))); + #endif + + // + // Switch power on/off + // + #if HAS_POWER_SWITCH + if (powersupply_on) + MENU_ITEM(gcode, MSG_SWITCH_PS_OFF, PSTR("M81")); + else + MENU_ITEM(gcode, MSG_SWITCH_PS_ON, PSTR("M80")); + #endif + + // + // Autostart + // + #if ENABLED(SDSUPPORT) && ENABLED(MENU_ADDAUTOSTART) + MENU_ITEM(function, MSG_AUTOSTART, lcd_autostart_sd); + #endif + + // + // Delta Calibration + // + #if ENABLED(DELTA_CALIBRATION_MENU) || ENABLED(DELTA_AUTO_CALIBRATION) + MENU_ITEM(submenu, MSG_DELTA_CALIBRATE, lcd_delta_calibrate_menu); + #endif + + END_MENU(); + } + + float move_menu_scale; + + #if ENABLED(DELTA_CALIBRATION_MENU) || (ENABLED(DELTA_AUTO_CALIBRATION) && !HAS_BED_PROBE) + + void lcd_move_z(); + + void _man_probe_pt(const float &rx, const float &ry) { + #if HAS_LEVELING + reset_bed_level(); // After calibration bed-level data is no longer valid + #endif + + line_to_z((Z_CLEARANCE_BETWEEN_PROBES) + (DELTA_PRINTABLE_RADIUS) / 5); + current_position[X_AXIS] = rx; + current_position[Y_AXIS] = ry; + line_to_current_z(); + line_to_z(Z_CLEARANCE_BETWEEN_PROBES); + + lcd_synchronize(); + move_menu_scale = PROBE_MANUALLY_STEP; + lcd_goto_screen(lcd_move_z); + } + + #endif // DELTA_CALIBRATION_MENU || (DELTA_AUTO_CALIBRATION && !HAS_BED_PROBE) + + #if ENABLED(DELTA_AUTO_CALIBRATION) && !HAS_BED_PROBE + + float lcd_probe_pt(const float &rx, const float &ry) { + _man_probe_pt(rx, ry); + KEEPALIVE_STATE(PAUSED_FOR_USER); + defer_return_to_status = true; + wait_for_user = true; + while (wait_for_user) idle(); + KEEPALIVE_STATE(IN_HANDLER); + lcd_goto_previous_menu_no_defer(); + return current_position[Z_AXIS]; + } + + #endif // DELTA_AUTO_CALIBRATION && !HAS_BED_PROBE + + #if ENABLED(DELTA_CALIBRATION_MENU) + + void _lcd_calibrate_homing() { + if (lcdDrawUpdate) lcd_implementation_drawmenu_static(LCD_HEIGHT >= 4 ? 1 : 0, PSTR(MSG_LEVEL_BED_HOMING)); + lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT; + if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS]) + lcd_goto_previous_menu(); + } + + void _lcd_delta_calibrate_home() { + #if HAS_LEVELING + reset_bed_level(); // After calibration bed-level data is no longer valid + #endif + + enqueue_and_echo_commands_P(PSTR("G28")); + lcd_goto_screen(_lcd_calibrate_homing); + } + + void _goto_tower_x() { _man_probe_pt(cos(RADIANS(210)) * delta_calibration_radius, sin(RADIANS(210)) * delta_calibration_radius); } + void _goto_tower_y() { _man_probe_pt(cos(RADIANS(330)) * delta_calibration_radius, sin(RADIANS(330)) * delta_calibration_radius); } + void _goto_tower_z() { _man_probe_pt(cos(RADIANS( 90)) * delta_calibration_radius, sin(RADIANS( 90)) * delta_calibration_radius); } + void _goto_center() { _man_probe_pt(0,0); } + + #endif // DELTA_CALIBRATION_MENU + + #if ENABLED(DELTA_CALIBRATION_MENU) || ENABLED(DELTA_AUTO_CALIBRATION) + + void lcd_delta_settings() { + START_MENU(); + MENU_BACK(MSG_DELTA_CALIBRATE); + MENU_ITEM_EDIT_CALLBACK(float52, MSG_DELTA_DIAG_ROD, &delta_diagonal_rod, delta_diagonal_rod - 5.0, delta_diagonal_rod + 5.0, recalc_delta_settings); + MENU_ITEM_EDIT_CALLBACK(float52, MSG_DELTA_HEIGHT, &delta_height, delta_height - 10.0, delta_height + 10.0, recalc_delta_settings); + MENU_ITEM_EDIT_CALLBACK(float43, "Ex", &delta_endstop_adj[A_AXIS], -5.0, 5.0, recalc_delta_settings); + MENU_ITEM_EDIT_CALLBACK(float43, "Ey", &delta_endstop_adj[B_AXIS], -5.0, 5.0, recalc_delta_settings); + MENU_ITEM_EDIT_CALLBACK(float43, "Ez", &delta_endstop_adj[C_AXIS], -5.0, 5.0, recalc_delta_settings); + MENU_ITEM_EDIT_CALLBACK(float52, MSG_DELTA_RADIUS, &delta_radius, delta_radius - 5.0, delta_radius + 5.0, recalc_delta_settings); + MENU_ITEM_EDIT_CALLBACK(float43, "Tx", &delta_tower_angle_trim[A_AXIS], -5.0, 5.0, recalc_delta_settings); + MENU_ITEM_EDIT_CALLBACK(float43, "Ty", &delta_tower_angle_trim[B_AXIS], -5.0, 5.0, recalc_delta_settings); + MENU_ITEM_EDIT_CALLBACK(float43, "Tz", &delta_tower_angle_trim[C_AXIS], -5.0, 5.0, recalc_delta_settings); + END_MENU(); + } + + void lcd_delta_calibrate_menu() { + START_MENU(); + MENU_BACK(MSG_MAIN); + #if ENABLED(DELTA_AUTO_CALIBRATION) + MENU_ITEM(gcode, MSG_DELTA_AUTO_CALIBRATE, PSTR("G33")); + MENU_ITEM(gcode, MSG_DELTA_HEIGHT_CALIBRATE, PSTR("G33 P1")); + #if ENABLED(EEPROM_SETTINGS) + MENU_ITEM(function, MSG_STORE_EEPROM, lcd_store_settings); + MENU_ITEM(function, MSG_LOAD_EEPROM, lcd_load_settings); + #endif + #endif + MENU_ITEM(submenu, MSG_DELTA_SETTINGS, lcd_delta_settings); + #if ENABLED(DELTA_CALIBRATION_MENU) + MENU_ITEM(submenu, MSG_AUTO_HOME, _lcd_delta_calibrate_home); + if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS]) { + MENU_ITEM(submenu, MSG_DELTA_CALIBRATE_X, _goto_tower_x); + MENU_ITEM(submenu, MSG_DELTA_CALIBRATE_Y, _goto_tower_y); + MENU_ITEM(submenu, MSG_DELTA_CALIBRATE_Z, _goto_tower_z); + MENU_ITEM(submenu, MSG_DELTA_CALIBRATE_CENTER, _goto_center); + } + #endif + END_MENU(); + } + + #endif // DELTA_CALIBRATION_MENU || DELTA_AUTO_CALIBRATION + + /** + * If the most recent manual move hasn't been fed to the planner yet, + * and the planner can accept one, send immediately + */ + inline void manage_manual_move() { + + if (processing_manual_move) return; + + if (manual_move_axis != (int8_t)NO_AXIS && ELAPSED(millis(), manual_move_start_time) && !planner.is_full()) { + + #if IS_KINEMATIC + + const float old_feedrate = feedrate_mm_s; + feedrate_mm_s = MMM_TO_MMS(manual_feedrate_mm_m[manual_move_axis]); + + #if EXTRUDERS > 1 + const int8_t old_extruder = active_extruder; + active_extruder = manual_move_e_index; + #endif + + // Set movement on a single axis + set_destination_from_current(); + destination[manual_move_axis] += manual_move_offset; + + // Reset for the next move + manual_move_offset = 0.0; + manual_move_axis = (int8_t)NO_AXIS; + + // DELTA and SCARA machines use segmented moves, which could fill the planner during the call to + // move_to_destination. This will cause idle() to be called, which can then call this function while the + // previous invocation is being blocked. Modifications to manual_move_offset shouldn't be made while + // processing_manual_move is true or the planner will get out of sync. + processing_manual_move = true; + prepare_move_to_destination(); // will call set_current_from_destination() + processing_manual_move = false; + + feedrate_mm_s = old_feedrate; + #if EXTRUDERS > 1 + active_extruder = old_extruder; + #endif + + #else + + planner.buffer_line_kinematic(current_position, MMM_TO_MMS(manual_feedrate_mm_m[manual_move_axis]), manual_move_e_index); + manual_move_axis = (int8_t)NO_AXIS; + + #endif + } + } + + /** + * Set a flag that lcd_update() should start a move + * to "current_position" after a short delay. + */ + inline void manual_move_to_current(AxisEnum axis + #if E_MANUAL > 1 + , const int8_t eindex=-1 + #endif + ) { + #if ENABLED(DUAL_X_CARRIAGE) || E_MANUAL > 1 + #if E_MANUAL > 1 + if (axis == E_AXIS) + #endif + manual_move_e_index = eindex >= 0 ? eindex : active_extruder; + #endif + manual_move_start_time = millis() + (move_menu_scale < 0.99 ? 0UL : 250UL); // delay for bigger moves + manual_move_axis = (int8_t)axis; + } + + /** + * + * "Prepare" > "Move Axis" submenu + * + */ + + void _lcd_move_xyz(const char* name, AxisEnum axis) { + if (use_click()) { return lcd_goto_previous_menu_no_defer(); } + ENCODER_DIRECTION_NORMAL(); + if (encoderPosition && !processing_manual_move) { + + // Start with no limits to movement + float min = current_position[axis] - 1000, + max = current_position[axis] + 1000; + + // Limit to software endstops, if enabled + #if ENABLED(MIN_SOFTWARE_ENDSTOPS) || ENABLED(MAX_SOFTWARE_ENDSTOPS) + if (soft_endstops_enabled) switch (axis) { + case X_AXIS: + #if ENABLED(MIN_SOFTWARE_ENDSTOP_X) + min = soft_endstop_min[X_AXIS]; + #endif + #if ENABLED(MAX_SOFTWARE_ENDSTOP_X) + max = soft_endstop_max[X_AXIS]; + #endif + break; + case Y_AXIS: + #if ENABLED(MIN_SOFTWARE_ENDSTOP_Y) + min = soft_endstop_min[Y_AXIS]; + #endif + #if ENABLED(MAX_SOFTWARE_ENDSTOP_Y) + max = soft_endstop_max[Y_AXIS]; + #endif + break; + case Z_AXIS: + #if ENABLED(MIN_SOFTWARE_ENDSTOP_Z) + min = soft_endstop_min[Z_AXIS]; + #endif + #if ENABLED(MAX_SOFTWARE_ENDSTOP_Z) + max = soft_endstop_max[Z_AXIS]; + #endif + default: break; + } + #endif // MIN_SOFTWARE_ENDSTOPS || MAX_SOFTWARE_ENDSTOPS + + // Delta limits XY based on the current offset from center + // This assumes the center is 0,0 + #if ENABLED(DELTA) + if (axis != Z_AXIS) { + max = SQRT(sq((float)(DELTA_PRINTABLE_RADIUS)) - sq(current_position[Y_AXIS - axis])); // (Y_AXIS - axis) == the other axis + min = -max; + } + #endif + + // Get the new position + const float diff = float((int32_t)encoderPosition) * move_menu_scale; + #if IS_KINEMATIC + manual_move_offset += diff; + // Limit only when trying to move towards the limit + if ((int32_t)encoderPosition < 0) NOLESS(manual_move_offset, min - current_position[axis]); + if ((int32_t)encoderPosition > 0) NOMORE(manual_move_offset, max - current_position[axis]); + #else + current_position[axis] += diff; + // Limit only when trying to move towards the limit + if ((int32_t)encoderPosition < 0) NOLESS(current_position[axis], min); + if ((int32_t)encoderPosition > 0) NOMORE(current_position[axis], max); + #endif + + manual_move_to_current(axis); + lcdDrawUpdate = LCDVIEW_REDRAW_NOW; + } + encoderPosition = 0; + if (lcdDrawUpdate) { + const float pos = NATIVE_TO_LOGICAL(processing_manual_move ? destination[axis] : current_position[axis] + #if IS_KINEMATIC + + manual_move_offset + #endif + , axis); + lcd_implementation_drawedit(name, move_menu_scale >= 0.1 ? ftostr41sign(pos) : ftostr43sign(pos)); + } + } + void lcd_move_x() { _lcd_move_xyz(PSTR(MSG_MOVE_X), X_AXIS); } + void lcd_move_y() { _lcd_move_xyz(PSTR(MSG_MOVE_Y), Y_AXIS); } + void lcd_move_z() { _lcd_move_xyz(PSTR(MSG_MOVE_Z), Z_AXIS); } + void _lcd_move_e( + #if E_MANUAL > 1 + const int8_t eindex=-1 + #endif + ) { + if (use_click()) { return lcd_goto_previous_menu_no_defer(); } + ENCODER_DIRECTION_NORMAL(); + if (encoderPosition) { + if (!processing_manual_move) { + const float diff = float((int32_t)encoderPosition) * move_menu_scale; + #if IS_KINEMATIC + manual_move_offset += diff; + #else + current_position[E_AXIS] += diff; + #endif + manual_move_to_current(E_AXIS + #if E_MANUAL > 1 + , eindex + #endif + ); + lcdDrawUpdate = LCDVIEW_REDRAW_NOW; + } + encoderPosition = 0; + } + if (lcdDrawUpdate) { + PGM_P pos_label; + #if E_MANUAL == 1 + pos_label = PSTR(MSG_MOVE_E); + #else + switch (eindex) { + default: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E1); break; + case 1: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E2); break; + #if E_MANUAL > 2 + case 2: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E3); break; + #if E_MANUAL > 3 + case 3: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E4); break; + #if E_MANUAL > 4 + case 4: pos_label = PSTR(MSG_MOVE_E MSG_MOVE_E5); break; + #endif // E_MANUAL > 4 + #endif // E_MANUAL > 3 + #endif // E_MANUAL > 2 + } + #endif // E_MANUAL > 1 + lcd_implementation_drawedit(pos_label, ftostr41sign(current_position[E_AXIS] + #if IS_KINEMATIC + + manual_move_offset + #endif + )); + } + } + + void lcd_move_e() { _lcd_move_e(); } + #if E_MANUAL > 1 + void lcd_move_e0() { _lcd_move_e(0); } + void lcd_move_e1() { _lcd_move_e(1); } + #if E_MANUAL > 2 + void lcd_move_e2() { _lcd_move_e(2); } + #if E_MANUAL > 3 + void lcd_move_e3() { _lcd_move_e(3); } + #if E_MANUAL > 4 + void lcd_move_e4() { _lcd_move_e(4); } + #endif // E_MANUAL > 4 + #endif // E_MANUAL > 3 + #endif // E_MANUAL > 2 + #endif // E_MANUAL > 1 + + /** + * + * "Prepare" > "Move Xmm" > "Move XYZ" submenu + * + */ + + screenFunc_t _manual_move_func_ptr; + + void _goto_manual_move(const float scale) { + defer_return_to_status = true; + move_menu_scale = scale; + lcd_goto_screen(_manual_move_func_ptr); + } + void lcd_move_menu_10mm() { _goto_manual_move(10.0); } + void lcd_move_menu_1mm() { _goto_manual_move( 1.0); } + void lcd_move_menu_01mm() { _goto_manual_move( 0.1); } + + void _lcd_move_distance_menu(const AxisEnum axis, const screenFunc_t func) { + _manual_move_func_ptr = func; + START_MENU(); + if (LCD_HEIGHT >= 4) { + switch (axis) { + case X_AXIS: + STATIC_ITEM(MSG_MOVE_X, true, true); break; + case Y_AXIS: + STATIC_ITEM(MSG_MOVE_Y, true, true); break; + case Z_AXIS: + STATIC_ITEM(MSG_MOVE_Z, true, true); break; + default: + STATIC_ITEM(MSG_MOVE_E, true, true); break; + } + } + MENU_BACK(MSG_MOVE_AXIS); + MENU_ITEM(submenu, MSG_MOVE_10MM, lcd_move_menu_10mm); + MENU_ITEM(submenu, MSG_MOVE_1MM, lcd_move_menu_1mm); + MENU_ITEM(submenu, MSG_MOVE_01MM, lcd_move_menu_01mm); + END_MENU(); + } + void lcd_move_get_x_amount() { _lcd_move_distance_menu(X_AXIS, lcd_move_x); } + void lcd_move_get_y_amount() { _lcd_move_distance_menu(Y_AXIS, lcd_move_y); } + void lcd_move_get_z_amount() { _lcd_move_distance_menu(Z_AXIS, lcd_move_z); } + void lcd_move_get_e_amount() { _lcd_move_distance_menu(E_AXIS, lcd_move_e); } + #if E_MANUAL > 1 + void lcd_move_get_e0_amount() { _lcd_move_distance_menu(E_AXIS, lcd_move_e0); } + void lcd_move_get_e1_amount() { _lcd_move_distance_menu(E_AXIS, lcd_move_e1); } + #if E_MANUAL > 2 + void lcd_move_get_e2_amount() { _lcd_move_distance_menu(E_AXIS, lcd_move_e2); } + #if E_MANUAL > 3 + void lcd_move_get_e3_amount() { _lcd_move_distance_menu(E_AXIS, lcd_move_e3); } + #if E_MANUAL > 4 + void lcd_move_get_e4_amount() { _lcd_move_distance_menu(E_AXIS, lcd_move_e4); } + #endif // E_MANUAL > 4 + #endif // E_MANUAL > 3 + #endif // E_MANUAL > 2 + #endif // E_MANUAL > 1 + + /** + * + * "Prepare" > "Move Axis" submenu + * + */ + + #if IS_KINEMATIC || ENABLED(NO_MOTION_BEFORE_HOMING) + #define _MOVE_XYZ_ALLOWED (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS]) + #else + #define _MOVE_XYZ_ALLOWED true + #endif + + #if ENABLED(DELTA) + #define _MOVE_XY_ALLOWED (current_position[Z_AXIS] <= delta_clip_start_height) + void lcd_lower_z_to_clip_height() { + line_to_z(delta_clip_start_height); + lcd_synchronize(); + } + #else + #define _MOVE_XY_ALLOWED true + #endif + + void lcd_move_menu() { + START_MENU(); + MENU_BACK(MSG_PREPARE); + + if (_MOVE_XYZ_ALLOWED) { + if (_MOVE_XY_ALLOWED) { + MENU_ITEM(submenu, MSG_MOVE_X, lcd_move_get_x_amount); + MENU_ITEM(submenu, MSG_MOVE_Y, lcd_move_get_y_amount); + } + #if ENABLED(DELTA) + else + MENU_ITEM(function, MSG_FREE_XY, lcd_lower_z_to_clip_height); + #endif + + MENU_ITEM(submenu, MSG_MOVE_Z, lcd_move_get_z_amount); + } + else + MENU_ITEM(gcode, MSG_AUTO_HOME, PSTR("G28")); + + #if ENABLED(SWITCHING_EXTRUDER) + + #if EXTRUDERS == 4 + switch (active_extruder) { + case 0: MENU_ITEM(gcode, MSG_SELECT " " MSG_E2, PSTR("T1")); break; + case 1: MENU_ITEM(gcode, MSG_SELECT " " MSG_E1, PSTR("T0")); break; + case 2: MENU_ITEM(gcode, MSG_SELECT " " MSG_E4, PSTR("T3")); break; + case 3: MENU_ITEM(gcode, MSG_SELECT " " MSG_E3, PSTR("T2")); break; + } + #elif EXTRUDERS == 3 + if (active_extruder < 2) { + if (active_extruder) + MENU_ITEM(gcode, MSG_SELECT " " MSG_E1, PSTR("T0")); + else + MENU_ITEM(gcode, MSG_SELECT " " MSG_E2, PSTR("T1")); + } + #else + if (active_extruder) + MENU_ITEM(gcode, MSG_SELECT " " MSG_E1, PSTR("T0")); + else + MENU_ITEM(gcode, MSG_SELECT " " MSG_E2, PSTR("T1")); + #endif + + #elif ENABLED(DUAL_X_CARRIAGE) + + if (active_extruder) + MENU_ITEM(gcode, MSG_SELECT " " MSG_E1, PSTR("T0")); + else + MENU_ITEM(gcode, MSG_SELECT " " MSG_E2, PSTR("T1")); + + #endif + + MENU_ITEM(submenu, MSG_MOVE_E, lcd_move_get_e_amount); + #if E_MANUAL > 1 + MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E1, lcd_move_get_e0_amount); + MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E2, lcd_move_get_e1_amount); + #if E_MANUAL > 2 + MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E3, lcd_move_get_e2_amount); + #if E_MANUAL > 3 + MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E4, lcd_move_get_e3_amount); + #if E_MANUAL > 4 + MENU_ITEM(submenu, MSG_MOVE_E MSG_MOVE_E5, lcd_move_get_e4_amount); + #endif // E_MANUAL > 4 + #endif // E_MANUAL > 3 + #endif // E_MANUAL > 2 + #endif // E_MANUAL > 1 + + END_MENU(); + } + + /** + * + * "Control" submenu + * + */ + + #if HAS_LCD_CONTRAST + void lcd_callback_set_contrast() { set_lcd_contrast(lcd_contrast); } + #endif + + static void lcd_factory_settings() { + settings.reset(); + lcd_completion_feedback(); + } + + #if ENABLED(EEPROM_SETTINGS) + + static void lcd_init_eeprom() { + lcd_completion_feedback(settings.init_eeprom()); + lcd_goto_previous_menu(); + } + + static void lcd_init_eeprom_confirm() { + START_MENU(); + MENU_BACK(MSG_CONTROL); + MENU_ITEM(function, MSG_INIT_EEPROM, lcd_init_eeprom); + END_MENU(); + } + + #endif + + void lcd_control_menu() { + START_MENU(); + MENU_BACK(MSG_MAIN); + MENU_ITEM(submenu, MSG_TEMPERATURE, lcd_control_temperature_menu); + MENU_ITEM(submenu, MSG_MOTION, lcd_control_motion_menu); + + #if DISABLED(NO_VOLUMETRICS) || ENABLED(ADVANCED_PAUSE_FEATURE) + MENU_ITEM(submenu, MSG_FILAMENT, lcd_control_filament_menu); + #elif ENABLED(LIN_ADVANCE) + MENU_ITEM_EDIT(float32, MSG_ADVANCE_K, &planner.extruder_advance_K, 0, 999); + #endif + + #if HAS_LCD_CONTRAST + MENU_ITEM_EDIT_CALLBACK(int3, MSG_CONTRAST, &lcd_contrast, LCD_CONTRAST_MIN, LCD_CONTRAST_MAX, lcd_callback_set_contrast, true); + #endif + #if ENABLED(FWRETRACT) + MENU_ITEM(submenu, MSG_RETRACT, lcd_control_retract_menu); + #endif + #if ENABLED(DAC_STEPPER_CURRENT) + MENU_ITEM(submenu, MSG_DRIVE_STRENGTH, lcd_dac_menu); + #endif + #if HAS_MOTOR_CURRENT_PWM + MENU_ITEM(submenu, MSG_DRIVE_STRENGTH, lcd_pwm_menu); + #endif + + #if ENABLED(BLTOUCH) + MENU_ITEM(submenu, MSG_BLTOUCH, bltouch_menu); + #endif + + #if ENABLED(EEPROM_SETTINGS) + MENU_ITEM(function, MSG_STORE_EEPROM, lcd_store_settings); + MENU_ITEM(function, MSG_LOAD_EEPROM, lcd_load_settings); + #endif + + MENU_ITEM(function, MSG_RESTORE_FAILSAFE, lcd_factory_settings); + + #if ENABLED(EEPROM_SETTINGS) && DISABLED(SLIM_LCD_MENUS) + MENU_ITEM(submenu, MSG_INIT_EEPROM, lcd_init_eeprom_confirm); + #endif + + END_MENU(); + } + + /** + * + * "Temperature" submenu + * + */ + + #if ENABLED(PID_AUTOTUNE_MENU) + + #if ENABLED(PIDTEMP) + int16_t autotune_temp[HOTENDS] = ARRAY_BY_HOTENDS1(150); + #endif + + #if ENABLED(PIDTEMPBED) + int16_t autotune_temp_bed = 70; + #endif + + void _lcd_autotune(int16_t e) { + char cmd[30]; + sprintf_P(cmd, PSTR("M303 U1 E%i S%i"), e, + #if HAS_PID_FOR_BOTH + e < 0 ? autotune_temp_bed : autotune_temp[e] + #elif ENABLED(PIDTEMPBED) + autotune_temp_bed + #else + autotune_temp[e] + #endif + ); + lcd_enqueue_command(cmd); + } + + #endif // PID_AUTOTUNE_MENU + + #if ENABLED(PIDTEMP) + + // Helpers for editing PID Ki & Kd values + // grab the PID value out of the temp variable; scale it; then update the PID driver + void copy_and_scalePID_i(int16_t e) { + #if DISABLED(PID_PARAMS_PER_HOTEND) || HOTENDS == 1 + UNUSED(e); + #endif + PID_PARAM(Ki, e) = scalePID_i(raw_Ki); + thermalManager.updatePID(); + } + void copy_and_scalePID_d(int16_t e) { + #if DISABLED(PID_PARAMS_PER_HOTEND) || HOTENDS == 1 + UNUSED(e); + #endif + PID_PARAM(Kd, e) = scalePID_d(raw_Kd); + thermalManager.updatePID(); + } + #define _DEFINE_PIDTEMP_BASE_FUNCS(N) \ + void copy_and_scalePID_i_E ## N() { copy_and_scalePID_i(N); } \ + void copy_and_scalePID_d_E ## N() { copy_and_scalePID_d(N); } + + #if ENABLED(PID_AUTOTUNE_MENU) + #define DEFINE_PIDTEMP_FUNCS(N) \ + _DEFINE_PIDTEMP_BASE_FUNCS(N); \ + void lcd_autotune_callback_E ## N() { _lcd_autotune(N); } typedef void _pid_##N##_void + #else + #define DEFINE_PIDTEMP_FUNCS(N) _DEFINE_PIDTEMP_BASE_FUNCS(N) typedef void _pid_##N##_void + #endif + + DEFINE_PIDTEMP_FUNCS(0); + #if ENABLED(PID_PARAMS_PER_HOTEND) + #if HOTENDS > 1 + DEFINE_PIDTEMP_FUNCS(1); + #if HOTENDS > 2 + DEFINE_PIDTEMP_FUNCS(2); + #if HOTENDS > 3 + DEFINE_PIDTEMP_FUNCS(3); + #if HOTENDS > 4 + DEFINE_PIDTEMP_FUNCS(4); + #endif // HOTENDS > 4 + #endif // HOTENDS > 3 + #endif // HOTENDS > 2 + #endif // HOTENDS > 1 + #endif // PID_PARAMS_PER_HOTEND + + #endif // PIDTEMP + + /** + * + * "Control" > "Temperature" submenu + * + */ + void lcd_control_temperature_menu() { + START_MENU(); + + // + // ^ Control + // + MENU_BACK(MSG_CONTROL); + + // + // Nozzle: + // Nozzle [1-5]: + // + #if HOTENDS == 1 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE, &thermalManager.target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0); + #else // HOTENDS > 1 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N1, &thermalManager.target_temperature[0], 0, HEATER_0_MAXTEMP - 15, watch_temp_callback_E0); + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N2, &thermalManager.target_temperature[1], 0, HEATER_1_MAXTEMP - 15, watch_temp_callback_E1); + #if HOTENDS > 2 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N3, &thermalManager.target_temperature[2], 0, HEATER_2_MAXTEMP - 15, watch_temp_callback_E2); + #if HOTENDS > 3 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N4, &thermalManager.target_temperature[3], 0, HEATER_3_MAXTEMP - 15, watch_temp_callback_E3); + #if HOTENDS > 4 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_NOZZLE MSG_N5, &thermalManager.target_temperature[4], 0, HEATER_4_MAXTEMP - 15, watch_temp_callback_E4); + #endif // HOTENDS > 4 + #endif // HOTENDS > 3 + #endif // HOTENDS > 2 + #endif // HOTENDS > 1 + + // + // Bed: + // + #if HAS_TEMP_BED + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_BED, &thermalManager.target_temperature_bed, 0, BED_MAXTEMP - 15, watch_temp_callback_bed); + #endif +#if ENABLED(FAN_AS_LASER) + MENU_ITEM(gcode, MSG_LASER_ON, PSTR("M3")); + MENU_ITEM(gcode, MSG_LASER_OFF, PSTR("M5")); +#endif + + // + // Fan Speed: + // + #if FAN_COUNT > 0 + #if (HAS_FAN0 && FAN_NUM_AS_LASER!=0) + MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_FAN_SPEED FAN_SPEED_1_SUFFIX, &fanSpeeds[0], 0, 255); + #if ENABLED(EXTRA_FAN_SPEED) + MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_EXTRA_FAN_SPEED FAN_SPEED_1_SUFFIX, &new_fanSpeeds[0], 3, 255); + #endif + #endif + #if (HAS_FAN1 && FAN_NUM_AS_LASER!=1) + MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_FAN_SPEED " 2", &fanSpeeds[1], 0, 255); + #if ENABLED(EXTRA_FAN_SPEED) + MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_EXTRA_FAN_SPEED " 2", &new_fanSpeeds[1], 3, 255); + #endif + #endif + #if (HAS_FAN2 && FAN_NUM_AS_LASER!=2) + MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_FAN_SPEED " 3", &fanSpeeds[2], 0, 255); + #if ENABLED(EXTRA_FAN_SPEED) + MENU_MULTIPLIER_ITEM_EDIT(int3, MSG_EXTRA_FAN_SPEED " 3", &new_fanSpeeds[2], 3, 255); + #endif + #endif + #endif // FAN_COUNT > 0 + + // + // Autotemp, Min, Max, Fact + // + #if ENABLED(AUTOTEMP) && (TEMP_SENSOR_0 != 0) + MENU_ITEM_EDIT(bool, MSG_AUTOTEMP, &planner.autotemp_enabled); + MENU_ITEM_EDIT(float3, MSG_MIN, &planner.autotemp_min, 0, HEATER_0_MAXTEMP - 15); + MENU_ITEM_EDIT(float3, MSG_MAX, &planner.autotemp_max, 0, HEATER_0_MAXTEMP - 15); + MENU_ITEM_EDIT(float32, MSG_FACTOR, &planner.autotemp_factor, 0.0, 1.0); + #endif + + // + // PID-P, PID-I, PID-D, PID-C, PID Autotune + // PID-P E1, PID-I E1, PID-D E1, PID-C E1, PID Autotune E1 + // PID-P E2, PID-I E2, PID-D E2, PID-C E2, PID Autotune E2 + // PID-P E3, PID-I E3, PID-D E3, PID-C E3, PID Autotune E3 + // PID-P E4, PID-I E4, PID-D E4, PID-C E4, PID Autotune E4 + // PID-P E5, PID-I E5, PID-D E5, PID-C E5, PID Autotune E5 + // + #if ENABLED(PIDTEMP) + + #define _PID_BASE_MENU_ITEMS(ELABEL, eindex) \ + raw_Ki = unscalePID_i(PID_PARAM(Ki, eindex)); \ + raw_Kd = unscalePID_d(PID_PARAM(Kd, eindex)); \ + MENU_ITEM_EDIT(float52, MSG_PID_P ELABEL, &PID_PARAM(Kp, eindex), 1, 9990); \ + MENU_ITEM_EDIT_CALLBACK(float52, MSG_PID_I ELABEL, &raw_Ki, 0.01, 9990, copy_and_scalePID_i_E ## eindex); \ + MENU_ITEM_EDIT_CALLBACK(float52, MSG_PID_D ELABEL, &raw_Kd, 1, 9990, copy_and_scalePID_d_E ## eindex) + + #if ENABLED(PID_EXTRUSION_SCALING) + #define _PID_MENU_ITEMS(ELABEL, eindex) \ + _PID_BASE_MENU_ITEMS(ELABEL, eindex); \ + MENU_ITEM_EDIT(float3, MSG_PID_C ELABEL, &PID_PARAM(Kc, eindex), 1, 9990) + #else + #define _PID_MENU_ITEMS(ELABEL, eindex) _PID_BASE_MENU_ITEMS(ELABEL, eindex) + #endif + + #if ENABLED(PID_AUTOTUNE_MENU) + #define PID_MENU_ITEMS(ELABEL, eindex) \ + _PID_MENU_ITEMS(ELABEL, eindex); \ + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(int3, MSG_PID_AUTOTUNE ELABEL, &autotune_temp[eindex], 150, heater_maxtemp[eindex] - 15, lcd_autotune_callback_E ## eindex) + #else + #define PID_MENU_ITEMS(ELABEL, eindex) _PID_MENU_ITEMS(ELABEL, eindex) + #endif + + #if ENABLED(PID_PARAMS_PER_HOTEND) && HOTENDS > 1 + PID_MENU_ITEMS(" " MSG_E1, 0); + PID_MENU_ITEMS(" " MSG_E2, 1); + #if HOTENDS > 2 + PID_MENU_ITEMS(" " MSG_E3, 2); + #if HOTENDS > 3 + PID_MENU_ITEMS(" " MSG_E4, 3); + #if HOTENDS > 4 + PID_MENU_ITEMS(" " MSG_E5, 4); + #endif // HOTENDS > 4 + #endif // HOTENDS > 3 + #endif // HOTENDS > 2 + #else // !PID_PARAMS_PER_HOTEND || HOTENDS == 1 + PID_MENU_ITEMS("", 0); + #endif // !PID_PARAMS_PER_HOTEND || HOTENDS == 1 + + #endif // PIDTEMP + + #if DISABLED(SLIM_LCD_MENUS) + // + // Preheat Material 1 conf + // + MENU_ITEM(submenu, MSG_PREHEAT_1_SETTINGS, lcd_control_temperature_preheat_material1_settings_menu); + + // + // Preheat Material 2 conf + // + MENU_ITEM(submenu, MSG_PREHEAT_2_SETTINGS, lcd_control_temperature_preheat_material2_settings_menu); + #endif + + END_MENU(); + } + + #if DISABLED(SLIM_LCD_MENUS) + + void _lcd_control_temperature_preheat_settings_menu(const uint8_t material) { + #if HOTENDS > 4 + #define MINTEMP_ALL MIN5(HEATER_0_MINTEMP, HEATER_1_MINTEMP, HEATER_2_MINTEMP, HEATER_3_MINTEMP, HEATER_4_MINTEMP) + #define MAXTEMP_ALL MAX5(HEATER_0_MAXTEMP, HEATER_1_MAXTEMP, HEATER_2_MAXTEMP, HEATER_3_MAXTEMP, HEATER_4_MAXTEMP) + #elif HOTENDS > 3 + #define MINTEMP_ALL MIN4(HEATER_0_MINTEMP, HEATER_1_MINTEMP, HEATER_2_MINTEMP, HEATER_3_MINTEMP) + #define MAXTEMP_ALL MAX4(HEATER_0_MAXTEMP, HEATER_1_MAXTEMP, HEATER_2_MAXTEMP, HEATER_3_MAXTEMP) + #elif HOTENDS > 2 + #define MINTEMP_ALL MIN3(HEATER_0_MINTEMP, HEATER_1_MINTEMP, HEATER_2_MINTEMP) + #define MAXTEMP_ALL MAX3(HEATER_0_MAXTEMP, HEATER_1_MAXTEMP, HEATER_2_MAXTEMP) + #elif HOTENDS > 1 + #define MINTEMP_ALL min(HEATER_0_MINTEMP, HEATER_1_MINTEMP) + #define MAXTEMP_ALL max(HEATER_0_MAXTEMP, HEATER_1_MAXTEMP) + #else + #define MINTEMP_ALL HEATER_0_MINTEMP + #define MAXTEMP_ALL HEATER_0_MAXTEMP + #endif + START_MENU(); + MENU_BACK(MSG_TEMPERATURE); + MENU_ITEM_EDIT(int3, MSG_FAN_SPEED, &lcd_preheat_fan_speed[material], 0, 255); + #if TEMP_SENSOR_0 != 0 + MENU_ITEM_EDIT(int3, MSG_NOZZLE, &lcd_preheat_hotend_temp[material], MINTEMP_ALL, MAXTEMP_ALL - 15); + #endif + #if TEMP_SENSOR_BED != 0 + MENU_ITEM_EDIT(int3, MSG_BED, &lcd_preheat_bed_temp[material], BED_MINTEMP, BED_MAXTEMP - 15); + #endif + #if ENABLED(EEPROM_SETTINGS) + MENU_ITEM(function, MSG_STORE_EEPROM, lcd_store_settings); + #endif + END_MENU(); + } + + /** + * + * "Temperature" > "Preheat Material 1 conf" submenu + * + */ + void lcd_control_temperature_preheat_material1_settings_menu() { _lcd_control_temperature_preheat_settings_menu(0); } + + /** + * + * "Temperature" > "Preheat Material 2 conf" submenu + * + */ + void lcd_control_temperature_preheat_material2_settings_menu() { _lcd_control_temperature_preheat_settings_menu(1); } + + void _reset_acceleration_rates() { planner.reset_acceleration_rates(); } + #if ENABLED(DISTINCT_E_FACTORS) + void _reset_e_acceleration_rate(const uint8_t e) { if (e == active_extruder) _reset_acceleration_rates(); } + void _reset_e0_acceleration_rate() { _reset_e_acceleration_rate(0); } + void _reset_e1_acceleration_rate() { _reset_e_acceleration_rate(1); } + #if E_STEPPERS > 2 + void _reset_e2_acceleration_rate() { _reset_e_acceleration_rate(2); } + #if E_STEPPERS > 3 + void _reset_e3_acceleration_rate() { _reset_e_acceleration_rate(3); } + #if E_STEPPERS > 4 + void _reset_e4_acceleration_rate() { _reset_e_acceleration_rate(4); } + #endif // E_STEPPERS > 4 + #endif // E_STEPPERS > 3 + #endif // E_STEPPERS > 2 + #endif + + void _planner_refresh_positioning() { planner.refresh_positioning(); } + #if ENABLED(DISTINCT_E_FACTORS) + void _planner_refresh_e_positioning(const uint8_t e) { + if (e == active_extruder) + _planner_refresh_positioning(); + else + planner.steps_to_mm[E_AXIS + e] = 1.0 / planner.axis_steps_per_mm[E_AXIS + e]; + } + void _planner_refresh_e0_positioning() { _planner_refresh_e_positioning(0); } + void _planner_refresh_e1_positioning() { _planner_refresh_e_positioning(1); } + #if E_STEPPERS > 2 + void _planner_refresh_e2_positioning() { _planner_refresh_e_positioning(2); } + #if E_STEPPERS > 3 + void _planner_refresh_e3_positioning() { _planner_refresh_e_positioning(3); } + #if E_STEPPERS > 4 + void _planner_refresh_e4_positioning() { _planner_refresh_e_positioning(4); } + #endif // E_STEPPERS > 4 + #endif // E_STEPPERS > 3 + #endif // E_STEPPERS > 2 + #endif + + // M203 / M205 Velocity options + void lcd_control_motion_velocity_menu() { + START_MENU(); + MENU_BACK(MSG_MOTION); + + // M203 Max Feedrate + MENU_ITEM_EDIT(float3, MSG_VMAX MSG_A, &planner.max_feedrate_mm_s[A_AXIS], 1, 999); + MENU_ITEM_EDIT(float3, MSG_VMAX MSG_B, &planner.max_feedrate_mm_s[B_AXIS], 1, 999); + MENU_ITEM_EDIT(float3, MSG_VMAX MSG_C, &planner.max_feedrate_mm_s[C_AXIS], 1, 999); + + #if ENABLED(DISTINCT_E_FACTORS) + MENU_ITEM_EDIT(float3, MSG_VMAX MSG_E, &planner.max_feedrate_mm_s[E_AXIS + active_extruder], 1, 999); + MENU_ITEM_EDIT(float3, MSG_VMAX MSG_E1, &planner.max_feedrate_mm_s[E_AXIS], 1, 999); + MENU_ITEM_EDIT(float3, MSG_VMAX MSG_E2, &planner.max_feedrate_mm_s[E_AXIS + 1], 1, 999); + #if E_STEPPERS > 2 + MENU_ITEM_EDIT(float3, MSG_VMAX MSG_E3, &planner.max_feedrate_mm_s[E_AXIS + 2], 1, 999); + #if E_STEPPERS > 3 + MENU_ITEM_EDIT(float3, MSG_VMAX MSG_E4, &planner.max_feedrate_mm_s[E_AXIS + 3], 1, 999); + #if E_STEPPERS > 4 + MENU_ITEM_EDIT(float3, MSG_VMAX MSG_E5, &planner.max_feedrate_mm_s[E_AXIS + 4], 1, 999); + #endif // E_STEPPERS > 4 + #endif // E_STEPPERS > 3 + #endif // E_STEPPERS > 2 + #else + MENU_ITEM_EDIT(float3, MSG_VMAX MSG_E, &planner.max_feedrate_mm_s[E_AXIS], 1, 999); + #endif + + // M205 S Min Feedrate + MENU_ITEM_EDIT(float3, MSG_VMIN, &planner.min_feedrate_mm_s, 0, 999); + + // M205 T Min Travel Feedrate + MENU_ITEM_EDIT(float3, MSG_VTRAV_MIN, &planner.min_travel_feedrate_mm_s, 0, 999); + + END_MENU(); + } + + // M201 / M204 Accelerations + void lcd_control_motion_acceleration_menu() { + START_MENU(); + MENU_BACK(MSG_MOTION); + + // M204 P Acceleration + MENU_ITEM_EDIT(float5, MSG_ACC, &planner.acceleration, 10, 99000); + + // M204 R Retract Acceleration + MENU_ITEM_EDIT(float5, MSG_A_RETRACT, &planner.retract_acceleration, 100, 99000); + + // M204 T Travel Acceleration + MENU_ITEM_EDIT(float5, MSG_A_TRAVEL, &planner.travel_acceleration, 100, 99000); + + // M201 settings + MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_A, &planner.max_acceleration_mm_per_s2[A_AXIS], 100, 99000, _reset_acceleration_rates); + MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_B, &planner.max_acceleration_mm_per_s2[B_AXIS], 100, 99000, _reset_acceleration_rates); + MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_C, &planner.max_acceleration_mm_per_s2[C_AXIS], 10, 99000, _reset_acceleration_rates); + + #if ENABLED(DISTINCT_E_FACTORS) + MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_E, &planner.max_acceleration_mm_per_s2[E_AXIS + active_extruder], 100, 99000, _reset_acceleration_rates); + MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_E1, &planner.max_acceleration_mm_per_s2[E_AXIS], 100, 99000, _reset_e0_acceleration_rate); + MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_E2, &planner.max_acceleration_mm_per_s2[E_AXIS + 1], 100, 99000, _reset_e1_acceleration_rate); + #if E_STEPPERS > 2 + MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_E3, &planner.max_acceleration_mm_per_s2[E_AXIS + 2], 100, 99000, _reset_e2_acceleration_rate); + #if E_STEPPERS > 3 + MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_E4, &planner.max_acceleration_mm_per_s2[E_AXIS + 3], 100, 99000, _reset_e3_acceleration_rate); + #if E_STEPPERS > 4 + MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_E5, &planner.max_acceleration_mm_per_s2[E_AXIS + 4], 100, 99000, _reset_e4_acceleration_rate); + #endif // E_STEPPERS > 4 + #endif // E_STEPPERS > 3 + #endif // E_STEPPERS > 2 + #else + MENU_ITEM_EDIT_CALLBACK(long5, MSG_AMAX MSG_E, &planner.max_acceleration_mm_per_s2[E_AXIS], 100, 99000, _reset_acceleration_rates); + #endif + + END_MENU(); + } + + // M205 Jerk + void lcd_control_motion_jerk_menu() { + START_MENU(); + MENU_BACK(MSG_MOTION); + + MENU_ITEM_EDIT(float3, MSG_VA_JERK, &planner.max_jerk[A_AXIS], 1, 990); + MENU_ITEM_EDIT(float3, MSG_VB_JERK, &planner.max_jerk[B_AXIS], 1, 990); + #if ENABLED(DELTA) + MENU_ITEM_EDIT(float3, MSG_VC_JERK, &planner.max_jerk[C_AXIS], 1, 990); + #else + MENU_ITEM_EDIT(float52, MSG_VC_JERK, &planner.max_jerk[C_AXIS], 0.1, 990); + #endif + MENU_ITEM_EDIT(float3, MSG_VE_JERK, &planner.max_jerk[E_AXIS], 1, 990); + + END_MENU(); + } + + // M92 Steps-per-mm + void lcd_control_motion_steps_per_mm_menu() { + START_MENU(); + MENU_BACK(MSG_MOTION); + + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_ASTEPS, &planner.axis_steps_per_mm[A_AXIS], 5, 9999, _planner_refresh_positioning); + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_BSTEPS, &planner.axis_steps_per_mm[B_AXIS], 5, 9999, _planner_refresh_positioning); + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_CSTEPS, &planner.axis_steps_per_mm[C_AXIS], 5, 9999, _planner_refresh_positioning); + + #if ENABLED(DISTINCT_E_FACTORS) + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_ESTEPS, &planner.axis_steps_per_mm[E_AXIS + active_extruder], 5, 9999, _planner_refresh_positioning); + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_E1STEPS, &planner.axis_steps_per_mm[E_AXIS], 5, 9999, _planner_refresh_e0_positioning); + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_E2STEPS, &planner.axis_steps_per_mm[E_AXIS + 1], 5, 9999, _planner_refresh_e1_positioning); + #if E_STEPPERS > 2 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_E3STEPS, &planner.axis_steps_per_mm[E_AXIS + 2], 5, 9999, _planner_refresh_e2_positioning); + #if E_STEPPERS > 3 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_E4STEPS, &planner.axis_steps_per_mm[E_AXIS + 3], 5, 9999, _planner_refresh_e3_positioning); + #if E_STEPPERS > 4 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_E5STEPS, &planner.axis_steps_per_mm[E_AXIS + 4], 5, 9999, _planner_refresh_e4_positioning); + #endif // E_STEPPERS > 4 + #endif // E_STEPPERS > 3 + #endif // E_STEPPERS > 2 + #else + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float62, MSG_ESTEPS, &planner.axis_steps_per_mm[E_AXIS], 5, 9999, _planner_refresh_positioning); + #endif + + END_MENU(); + } + + #endif // !SLIM_LCD_MENUS + + /** + * + * "Control" > "Motion" submenu + * + */ + + void lcd_control_motion_menu() { + START_MENU(); + MENU_BACK(MSG_CONTROL); + + #if ENABLED(BABYSTEP_ZPROBE_OFFSET) + MENU_ITEM(submenu, MSG_ZPROBE_ZOFFSET, lcd_babystep_zoffset); + #elif HAS_BED_PROBE + MENU_ITEM_EDIT(float32, MSG_ZPROBE_ZOFFSET, &zprobe_zoffset, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX); + #endif + + #if DISABLED(SLIM_LCD_MENUS) + + // M203 / M205 - Feedrate items + MENU_ITEM(submenu, MSG_VELOCITY, lcd_control_motion_velocity_menu); + + // M201 - Acceleration items + MENU_ITEM(submenu, MSG_ACCELERATION, lcd_control_motion_acceleration_menu); + + // M205 - Max Jerk + MENU_ITEM(submenu, MSG_JERK, lcd_control_motion_jerk_menu); + + // M92 - Steps Per mm + MENU_ITEM(submenu, MSG_STEPS_PER_MM, lcd_control_motion_steps_per_mm_menu); + + #endif // !SLIM_LCD_MENUS + + // M540 S - Abort on endstop hit when SD printing + #if ENABLED(ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED) + MENU_ITEM_EDIT(bool, MSG_ENDSTOP_ABORT, &stepper.abort_on_endstop_hit); + #endif + + END_MENU(); + } + + #if DISABLED(NO_VOLUMETRICS) || ENABLED(ADVANCED_PAUSE_FEATURE) + /** + * + * "Control" > "Filament" submenu + * + */ + void lcd_control_filament_menu() { + START_MENU(); + MENU_BACK(MSG_CONTROL); + + #if ENABLED(LIN_ADVANCE) + MENU_ITEM_EDIT(float32, MSG_ADVANCE_K, &planner.extruder_advance_K, 0, 999); + #endif + + #if DISABLED(NO_VOLUMETRICS) + MENU_ITEM_EDIT_CALLBACK(bool, MSG_VOLUMETRIC_ENABLED, &parser.volumetric_enabled, planner.calculate_volumetric_multipliers); + + if (parser.volumetric_enabled) { + #if EXTRUDERS == 1 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float43, MSG_FILAMENT_DIAM, &planner.filament_size[0], 1.5, 3.25, planner.calculate_volumetric_multipliers); + #else // EXTRUDERS > 1 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float43, MSG_FILAMENT_DIAM, &planner.filament_size[active_extruder], 1.5, 3.25, planner.calculate_volumetric_multipliers); + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float43, MSG_FILAMENT_DIAM MSG_DIAM_E1, &planner.filament_size[0], 1.5, 3.25, planner.calculate_volumetric_multipliers); + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float43, MSG_FILAMENT_DIAM MSG_DIAM_E2, &planner.filament_size[1], 1.5, 3.25, planner.calculate_volumetric_multipliers); + #if EXTRUDERS > 2 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float43, MSG_FILAMENT_DIAM MSG_DIAM_E3, &planner.filament_size[2], 1.5, 3.25, planner.calculate_volumetric_multipliers); + #if EXTRUDERS > 3 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float43, MSG_FILAMENT_DIAM MSG_DIAM_E4, &planner.filament_size[3], 1.5, 3.25, planner.calculate_volumetric_multipliers); + #if EXTRUDERS > 4 + MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float43, MSG_FILAMENT_DIAM MSG_DIAM_E5, &planner.filament_size[4], 1.5, 3.25, planner.calculate_volumetric_multipliers); + #endif // EXTRUDERS > 4 + #endif // EXTRUDERS > 3 + #endif // EXTRUDERS > 2 + #endif // EXTRUDERS > 1 + } + #endif + + #if ENABLED(ADVANCED_PAUSE_FEATURE) + const float extrude_maxlength = + #if ENABLED(PREVENT_LENGTHY_EXTRUDE) + EXTRUDE_MAXLENGTH + #else + 999.0f + #endif + ; + + #if EXTRUDERS == 1 + MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_UNLOAD, &filament_change_unload_length[0], 0.0, extrude_maxlength); + #else // EXTRUDERS > 1 + MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_UNLOAD, &filament_change_unload_length[active_extruder], 0.0, extrude_maxlength); + MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_UNLOAD MSG_DIAM_E1, &filament_change_unload_length[0], 0.0, extrude_maxlength); + MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_UNLOAD MSG_DIAM_E2, &filament_change_unload_length[1], 0.0, extrude_maxlength); + #if EXTRUDERS > 2 + MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_UNLOAD MSG_DIAM_E3, &filament_change_unload_length[2], 0.0, extrude_maxlength); + #if EXTRUDERS > 3 + MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_UNLOAD MSG_DIAM_E4, &filament_change_unload_length[3], 0.0, extrude_maxlength); + #if EXTRUDERS > 4 + MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_UNLOAD MSG_DIAM_E5, &filament_change_unload_length[4], 0.0, extrude_maxlength); + #endif // EXTRUDERS > 4 + #endif // EXTRUDERS > 3 + #endif // EXTRUDERS > 2 + #endif // EXTRUDERS > 1 + + #if EXTRUDERS == 1 + MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_LOAD, &filament_change_load_length[0], 0.0, extrude_maxlength); + #else // EXTRUDERS > 1 + MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_LOAD, &filament_change_load_length[active_extruder], 0.0, extrude_maxlength); + MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_LOAD MSG_DIAM_E1, &filament_change_load_length[0], 0.0, extrude_maxlength); + MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_LOAD MSG_DIAM_E2, &filament_change_load_length[1], 0.0, extrude_maxlength); + #if EXTRUDERS > 2 + MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_LOAD MSG_DIAM_E3, &filament_change_load_length[2], 0.0, extrude_maxlength); + #if EXTRUDERS > 3 + MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_LOAD MSG_DIAM_E4, &filament_change_load_length[3], 0.0, extrude_maxlength); + #if EXTRUDERS > 4 + MENU_MULTIPLIER_ITEM_EDIT(float3, MSG_FILAMENT_LOAD MSG_DIAM_E5, &filament_change_load_length[4], 0.0, extrude_maxlength); + #endif // EXTRUDERS > 4 + #endif // EXTRUDERS > 3 + #endif // EXTRUDERS > 2 + #endif // EXTRUDERS > 1 + #endif + + END_MENU(); + } + #endif // !NO_VOLUMETRICS || ADVANCED_PAUSE_FEATURE + + /** + * + * "Control" > "Retract" submenu + * + */ + #if ENABLED(FWRETRACT) + + void lcd_control_retract_menu() { + START_MENU(); + MENU_BACK(MSG_CONTROL); + MENU_ITEM_EDIT_CALLBACK(bool, MSG_AUTORETRACT, &fwretract.autoretract_enabled, fwretract.refresh_autoretract); + MENU_ITEM_EDIT(float52, MSG_CONTROL_RETRACT, &fwretract.retract_length, 0, 100); + #if EXTRUDERS > 1 + MENU_ITEM_EDIT(float52, MSG_CONTROL_RETRACT_SWAP, &fwretract.swap_retract_length, 0, 100); + #endif + MENU_ITEM_EDIT(float3, MSG_CONTROL_RETRACTF, &fwretract.retract_feedrate_mm_s, 1, 999); + MENU_ITEM_EDIT(float52, MSG_CONTROL_RETRACT_ZLIFT, &fwretract.retract_zlift, 0, 999); + MENU_ITEM_EDIT(float52, MSG_CONTROL_RETRACT_RECOVER, &fwretract.retract_recover_length, -100, 100); + #if EXTRUDERS > 1 + MENU_ITEM_EDIT(float52, MSG_CONTROL_RETRACT_RECOVER_SWAP, &fwretract.swap_retract_recover_length, -100, 100); + #endif + MENU_ITEM_EDIT(float3, MSG_CONTROL_RETRACT_RECOVERF, &fwretract.retract_recover_feedrate_mm_s, 1, 999); + #if EXTRUDERS > 1 + MENU_ITEM_EDIT(float3, MSG_CONTROL_RETRACT_RECOVER_SWAPF, &fwretract.swap_retract_recover_feedrate_mm_s, 1, 999); + #endif + END_MENU(); + } + + #endif // FWRETRACT + + #if ENABLED(SDSUPPORT) + + #if !PIN_EXISTS(SD_DETECT) + void lcd_sd_refresh() { + card.initsd(); + encoderTopLine = 0; + } + #endif + + void lcd_sd_updir() { + encoderPosition = card.updir() ? ENCODER_STEPS_PER_MENU_ITEM : 0; + encoderTopLine = 0; + screen_changed = true; + lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW; + } + + /** + * + * "Print from SD" submenu + * + */ + + #if ENABLED(SD_REPRINT_LAST_SELECTED_FILE) + uint32_t last_sdfile_encoderPosition = 0xFFFF; + + void lcd_reselect_last_file() { + if (last_sdfile_encoderPosition == 0xFFFF) return; + #if ENABLED(DOGLCD) + // Some of this is a hack to force the screen update to work. + // TODO: Fix the real issue that causes this! + lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT; + _lcd_synchronize(); + safe_delay(50); + _lcd_synchronize(); + lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT; + drawing_screen = screen_changed = true; + #endif + + lcd_goto_screen(lcd_sdcard_menu, last_sdfile_encoderPosition); + defer_return_to_status = true; + last_sdfile_encoderPosition = 0xFFFF; + + #if ENABLED(DOGLCD) + lcd_update(); + #endif + } + #endif + + void lcd_sdcard_menu() { + ENCODER_DIRECTION_MENUS(); + + const uint16_t fileCnt = card.get_num_Files(); + + START_MENU(); + MENU_BACK(MSG_MAIN); + card.getWorkDirName(); + if (card.filename[0] == '/') { + #if !PIN_EXISTS(SD_DETECT) + MENU_ITEM(function, LCD_STR_REFRESH MSG_REFRESH, lcd_sd_refresh); + #endif + } + else { + MENU_ITEM(function, LCD_STR_FOLDER "..", lcd_sd_updir); + } + + for (uint16_t i = 0; i < fileCnt; i++) { + if (_menuLineNr == _thisItemNr) { + const uint16_t nr = + #if ENABLED(SDCARD_RATHERRECENTFIRST) && DISABLED(SDCARD_SORT_ALPHA) + fileCnt - 1 - + #endif + i; + + #if ENABLED(SDCARD_SORT_ALPHA) + card.getfilename_sorted(nr); + #else + card.getfilename(nr); + #endif + + if (card.filenameIsDir) + MENU_ITEM(sddirectory, MSG_CARD_MENU, card.filename, card.longFilename); + else + MENU_ITEM(sdfile, MSG_CARD_MENU, card.filename, card.longFilename); + } + else { + MENU_ITEM_DUMMY(); + } + } + END_MENU(); + } + + #endif // SDSUPPORT + + #if ENABLED(LCD_INFO_MENU) + + #if ENABLED(PRINTCOUNTER) + /** + * + * About Printer > Statistics submenu + * + */ + void lcd_info_stats_menu() { + if (use_click()) { return lcd_goto_previous_menu(); } + + char buffer[21]; + printStatistics stats = print_job_timer.getStats(); + + START_SCREEN(); // 12345678901234567890 + STATIC_ITEM(MSG_INFO_PRINT_COUNT ": ", false, false, itostr3left(stats.totalPrints)); // Print Count: 999 + STATIC_ITEM(MSG_INFO_COMPLETED_PRINTS": ", false, false, itostr3left(stats.finishedPrints)); // Completed : 666 + + duration_t elapsed = stats.printTime; + elapsed.toString(buffer); + + STATIC_ITEM(MSG_INFO_PRINT_TIME ": ", false, false); // Total print Time: + STATIC_ITEM("", false, false, buffer); // 99y 364d 23h 59m 59s + + elapsed = stats.longestPrint; + elapsed.toString(buffer); + + STATIC_ITEM(MSG_INFO_PRINT_LONGEST ": ", false, false); // Longest job time: + STATIC_ITEM("", false, false, buffer); // 99y 364d 23h 59m 59s + + sprintf_P(buffer, PSTR("%ld.%im"), long(stats.filamentUsed / 1000), int16_t(stats.filamentUsed / 100) % 10); + STATIC_ITEM(MSG_INFO_PRINT_FILAMENT ": ", false, false); // Extruded total: + STATIC_ITEM("", false, false, buffer); // 125m + END_SCREEN(); + } + #endif // PRINTCOUNTER + + /** + * + * About Printer > Thermistors + * + */ + void lcd_info_thermistors_menu() { + if (use_click()) { return lcd_goto_previous_menu(); } + START_SCREEN(); + #define THERMISTOR_ID TEMP_SENSOR_0 + #include "thermistornames.h" + STATIC_ITEM("T0: " THERMISTOR_NAME, false, true); + STATIC_ITEM(MSG_INFO_MIN_TEMP ": " STRINGIFY(HEATER_0_MINTEMP), false); + STATIC_ITEM(MSG_INFO_MAX_TEMP ": " STRINGIFY(HEATER_0_MAXTEMP), false); + + #if TEMP_SENSOR_1 != 0 + #undef THERMISTOR_ID + #define THERMISTOR_ID TEMP_SENSOR_1 + #include "thermistornames.h" + STATIC_ITEM("T1: " THERMISTOR_NAME, false, true); + STATIC_ITEM(MSG_INFO_MIN_TEMP ": " STRINGIFY(HEATER_1_MINTEMP), false); + STATIC_ITEM(MSG_INFO_MAX_TEMP ": " STRINGIFY(HEATER_1_MAXTEMP), false); + #endif + + #if TEMP_SENSOR_2 != 0 + #undef THERMISTOR_ID + #define THERMISTOR_ID TEMP_SENSOR_2 + #include "thermistornames.h" + STATIC_ITEM("T2: " THERMISTOR_NAME, false, true); + STATIC_ITEM(MSG_INFO_MIN_TEMP ": " STRINGIFY(HEATER_2_MINTEMP), false); + STATIC_ITEM(MSG_INFO_MAX_TEMP ": " STRINGIFY(HEATER_2_MAXTEMP), false); + #endif + + #if TEMP_SENSOR_3 != 0 + #undef THERMISTOR_ID + #define THERMISTOR_ID TEMP_SENSOR_3 + #include "thermistornames.h" + STATIC_ITEM("T3: " THERMISTOR_NAME, false, true); + STATIC_ITEM(MSG_INFO_MIN_TEMP ": " STRINGIFY(HEATER_3_MINTEMP), false); + STATIC_ITEM(MSG_INFO_MAX_TEMP ": " STRINGIFY(HEATER_3_MAXTEMP), false); + #endif + + #if TEMP_SENSOR_4 != 0 + #undef THERMISTOR_ID + #define THERMISTOR_ID TEMP_SENSOR_4 + #include "thermistornames.h" + STATIC_ITEM("T4: " THERMISTOR_NAME, false, true); + STATIC_ITEM(MSG_INFO_MIN_TEMP ": " STRINGIFY(HEATER_4_MINTEMP), false); + STATIC_ITEM(MSG_INFO_MAX_TEMP ": " STRINGIFY(HEATER_4_MAXTEMP), false); + #endif + + #if TEMP_SENSOR_BED != 0 + #undef THERMISTOR_ID + #define THERMISTOR_ID TEMP_SENSOR_BED + #include "thermistornames.h" + STATIC_ITEM("TBed:" THERMISTOR_NAME, false, true); + STATIC_ITEM(MSG_INFO_MIN_TEMP ": " STRINGIFY(BED_MINTEMP), false); + STATIC_ITEM(MSG_INFO_MAX_TEMP ": " STRINGIFY(BED_MAXTEMP), false); + #endif + END_SCREEN(); + } + + /** + * + * About Printer > Board Info + * + */ + void lcd_info_board_menu() { + if (use_click()) { return lcd_goto_previous_menu(); } + START_SCREEN(); + STATIC_ITEM(BOARD_NAME, true, true); // MyPrinterController + STATIC_ITEM(MSG_INFO_BAUDRATE ": " STRINGIFY(BAUDRATE), true); // Baud: 250000 + STATIC_ITEM(MSG_INFO_PROTOCOL ": " PROTOCOL_VERSION, true); // Protocol: 1.0 + #if POWER_SUPPLY == 0 + STATIC_ITEM(MSG_INFO_PSU ": Generic", true); + #elif POWER_SUPPLY == 1 + STATIC_ITEM(MSG_INFO_PSU ": ATX", true); // Power Supply: ATX + #elif POWER_SUPPLY == 2 + STATIC_ITEM(MSG_INFO_PSU ": XBox", true); // Power Supply: XBox + #endif + END_SCREEN(); + } + + /** + * + * About Printer > Printer Info + * + */ + void lcd_info_printer_menu() { + if (use_click()) { return lcd_goto_previous_menu(); } + START_SCREEN(); + STATIC_ITEM(MSG_MARLIN, true, true); // Marlin + STATIC_ITEM(SHORT_BUILD_VERSION, true); // x.x.x-Branch + STATIC_ITEM(STRING_DISTRIBUTION_DATE, true); // YYYY-MM-DD HH:MM + STATIC_ITEM(MACHINE_NAME, true); // My3DPrinter + STATIC_ITEM(WEBSITE_URL, true); // www.my3dprinter.com + STATIC_ITEM(MSG_INFO_EXTRUDERS ": " STRINGIFY(EXTRUDERS), true); // Extruders: 2 + #if ENABLED(AUTO_BED_LEVELING_3POINT) + STATIC_ITEM(MSG_3POINT_LEVELING, true); // 3-Point Leveling + #elif ENABLED(AUTO_BED_LEVELING_LINEAR) + STATIC_ITEM(MSG_LINEAR_LEVELING, true); // Linear Leveling + #elif ENABLED(AUTO_BED_LEVELING_BILINEAR) + STATIC_ITEM(MSG_BILINEAR_LEVELING, true); // Bi-linear Leveling + #elif ENABLED(AUTO_BED_LEVELING_UBL) + STATIC_ITEM(MSG_UBL_LEVELING, true); // Unified Bed Leveling + #elif ENABLED(MESH_BED_LEVELING) + STATIC_ITEM(MSG_MESH_LEVELING, true); // Mesh Leveling + #endif + END_SCREEN(); + } + + /** + * + * "About Printer" submenu + * + */ + void lcd_info_menu() { + START_MENU(); + MENU_BACK(MSG_MAIN); + MENU_ITEM(submenu, MSG_INFO_PRINTER_MENU, lcd_info_printer_menu); // Printer Info > + MENU_ITEM(submenu, MSG_INFO_BOARD_MENU, lcd_info_board_menu); // Board Info > + MENU_ITEM(submenu, MSG_INFO_THERMISTOR_MENU, lcd_info_thermistors_menu); // Thermistors > + #if ENABLED(PRINTCOUNTER) + MENU_ITEM(submenu, MSG_INFO_STATS_MENU, lcd_info_stats_menu); // Printer Statistics > + #endif + END_MENU(); + } + #endif // LCD_INFO_MENU + + /** + * + * LED Menu + * + */ + + #if ENABLED(LED_CONTROL_MENU) + + #if ENABLED(LED_COLOR_PRESETS) + + void lcd_led_presets_menu() { + START_MENU(); + #if LCD_HEIGHT > 2 + STATIC_ITEM(MSG_LED_PRESETS, true, true); + #endif + MENU_BACK(MSG_LED_CONTROL); + MENU_ITEM(function, MSG_SET_LEDS_WHITE, leds.set_white); + MENU_ITEM(function, MSG_SET_LEDS_RED, leds.set_red); + MENU_ITEM(function, MSG_SET_LEDS_ORANGE, leds.set_orange); + MENU_ITEM(function, MSG_SET_LEDS_YELLOW,leds.set_yellow); + MENU_ITEM(function, MSG_SET_LEDS_GREEN, leds.set_green); + MENU_ITEM(function, MSG_SET_LEDS_BLUE, leds.set_blue); + MENU_ITEM(function, MSG_SET_LEDS_INDIGO, leds.set_indigo); + MENU_ITEM(function, MSG_SET_LEDS_VIOLET, leds.set_violet); + END_MENU(); + } + #endif // LED_COLOR_PRESETS + + void lcd_led_custom_menu() { + START_MENU(); + MENU_BACK(MSG_LED_CONTROL); + MENU_ITEM_EDIT_CALLBACK(int8, MSG_INTENSITY_R, &leds.color.r, 0, 255, leds.update, true); + MENU_ITEM_EDIT_CALLBACK(int8, MSG_INTENSITY_G, &leds.color.g, 0, 255, leds.update, true); + MENU_ITEM_EDIT_CALLBACK(int8, MSG_INTENSITY_B, &leds.color.b, 0, 255, leds.update, true); + #if ENABLED(RGBW_LED) || ENABLED(NEOPIXEL_LED) + MENU_ITEM_EDIT_CALLBACK(int8, MSG_INTENSITY_W, &leds.color.w, 0, 255, leds.update, true); + #if ENABLED(NEOPIXEL_LED) + MENU_ITEM_EDIT_CALLBACK(int8, MSG_LED_BRIGHTNESS, &leds.color.i, 0, 255, leds.update, true); + #endif + #endif + END_MENU(); + } + + void lcd_led_menu() { + START_MENU(); + MENU_BACK(MSG_MAIN); + if (leds.lights_on) + MENU_ITEM(function, MSG_LEDS_OFF, leds.toggle); + else + MENU_ITEM(function, MSG_LEDS_ON, leds.toggle); + MENU_ITEM(function, MSG_SET_LEDS_DEFAULT, leds.set_default); + #if ENABLED(LED_COLOR_PRESETS) + MENU_ITEM(submenu, MSG_LED_PRESETS, lcd_led_presets_menu); + #endif + MENU_ITEM(submenu, MSG_CUSTOM_LEDS, lcd_led_custom_menu); + END_MENU(); + } + + #endif // LED_CONTROL_MENU + + /** + * + * Filament Change Feature Screens + * + */ + #if ENABLED(ADVANCED_PAUSE_FEATURE) + + /** + * + * "Change Filament" > "Change/Unload/Load Filament" submenu + * + */ + static AdvancedPauseMode _change_filament_temp_mode; + static int8_t _change_filament_temp_extruder; + + static const char* _change_filament_temp_command() { + switch (_change_filament_temp_mode) { + case ADVANCED_PAUSE_MODE_LOAD_FILAMENT: + return PSTR("M701 T%d"); + case ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT: + return _change_filament_temp_extruder >= 0 ? PSTR("M702 T%d") : PSTR("M702 ;%d"); + case ADVANCED_PAUSE_MODE_PAUSE_PRINT: + default: + return PSTR("M600 B0 T%d"); + } + return PSTR(MSG_FILAMENTCHANGE); + } + + void _change_filament_temp(const uint8_t index) { + char cmd[11]; + sprintf_P(cmd, _change_filament_temp_command(), _change_filament_temp_extruder); + thermalManager.setTargetHotend(index == 1 ? PREHEAT_1_TEMP_HOTEND : PREHEAT_2_TEMP_HOTEND, _change_filament_temp_extruder); + lcd_enqueue_command(cmd); + } + void _lcd_change_filament_temp_1_menu() { _change_filament_temp(1); } + void _lcd_change_filament_temp_2_menu() { _change_filament_temp(2); } + + static const char* change_filament_header(const AdvancedPauseMode mode) { + switch (mode) { + case ADVANCED_PAUSE_MODE_LOAD_FILAMENT: + return PSTR(MSG_FILAMENTLOAD); + case ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT: + return PSTR(MSG_FILAMENTUNLOAD); + default: break; + } + return PSTR(MSG_FILAMENTCHANGE); + } + + void _lcd_temp_menu_filament_op(const AdvancedPauseMode mode, const int8_t extruder) { + _change_filament_temp_mode = mode; + _change_filament_temp_extruder = extruder; + START_MENU(); + if (LCD_HEIGHT >= 4) STATIC_ITEM_P(change_filament_header(mode), true, true); + MENU_BACK(MSG_FILAMENTCHANGE); + MENU_ITEM(submenu, MSG_PREHEAT_1, _lcd_change_filament_temp_1_menu); + MENU_ITEM(submenu, MSG_PREHEAT_2, _lcd_change_filament_temp_2_menu); + END_MENU(); + } + void lcd_temp_menu_e0_filament_change() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_PAUSE_PRINT, 0); } + void lcd_temp_menu_e0_filament_load() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_LOAD_FILAMENT, 0); } + void lcd_temp_menu_e0_filament_unload() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT, 0); } + #if E_STEPPERS > 1 + void lcd_temp_menu_e1_filament_change() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_PAUSE_PRINT, 1); } + void lcd_temp_menu_e1_filament_load() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_LOAD_FILAMENT, 1); } + void lcd_temp_menu_e1_filament_unload() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT, 1); } + #if ENABLED(FILAMENT_UNLOAD_ALL_EXTRUDERS) + void lcd_unload_filament_all_temp_menu() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT, -1); } + #endif + #if E_STEPPERS > 2 + void lcd_temp_menu_e2_filament_change() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_PAUSE_PRINT, 2); } + void lcd_temp_menu_e2_filament_load() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_LOAD_FILAMENT, 2); } + void lcd_temp_menu_e2_filament_unload() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT, 2); } + #if E_STEPPERS > 3 + void lcd_temp_menu_e3_filament_change() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_PAUSE_PRINT, 3); } + void lcd_temp_menu_e3_filament_load() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_LOAD_FILAMENT, 3); } + void lcd_temp_menu_e3_filament_unload() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT, 3); } + #if E_STEPPERS > 4 + void lcd_temp_menu_e4_filament_change() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_PAUSE_PRINT, 4); } + void lcd_temp_menu_e4_filament_load() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_LOAD_FILAMENT, 4); } + void lcd_temp_menu_e4_filament_unload() { _lcd_temp_menu_filament_op(ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT, 4); } + #endif // E_STEPPERS > 4 + #endif // E_STEPPERS > 3 + #endif // E_STEPPERS > 2 + #endif // E_STEPPERS > 1 + + /** + * + * "Change Filament" submenu + * + */ + #if E_STEPPERS > 1 || ENABLED(FILAMENT_LOAD_UNLOAD_GCODES) + void lcd_change_filament_menu() { + START_MENU(); + MENU_BACK(MSG_PREPARE); + + // Change filament + #if E_STEPPERS == 1 + PGM_P msg0 = PSTR(MSG_FILAMENTCHANGE); + if (thermalManager.targetTooColdToExtrude(active_extruder)) + MENU_ITEM_P(submenu, msg0, lcd_temp_menu_e0_filament_change); + else + MENU_ITEM_P(gcode, msg0, PSTR("M600 B0")); + #else + PGM_P msg0 = PSTR(MSG_FILAMENTCHANGE " " MSG_E1); + PGM_P msg1 = PSTR(MSG_FILAMENTCHANGE " " MSG_E2); + if (thermalManager.targetTooColdToExtrude(0)) + MENU_ITEM_P(submenu, msg0, lcd_temp_menu_e0_filament_change); + else + MENU_ITEM_P(gcode, msg0, PSTR("M600 B0 T0")); + if (thermalManager.targetTooColdToExtrude(1)) + MENU_ITEM_P(submenu, msg1, lcd_temp_menu_e1_filament_change); + else + MENU_ITEM_P(gcode, msg1, PSTR("M600 B0 T1")); + #if E_STEPPERS > 2 + PGM_P msg2 = PSTR(MSG_FILAMENTCHANGE " " MSG_E3); + if (thermalManager.targetTooColdToExtrude(2)) + MENU_ITEM_P(submenu, msg2, lcd_temp_menu_e2_filament_change); + else + MENU_ITEM_P(gcode, msg2, PSTR("M600 B0 T2")); + #if E_STEPPERS > 3 + PGM_P msg3 = PSTR(MSG_FILAMENTCHANGE " " MSG_E4); + if (thermalManager.targetTooColdToExtrude(3)) + MENU_ITEM_P(submenu, msg3, lcd_temp_menu_e3_filament_change); + else + MENU_ITEM_P(gcode, msg3, PSTR("M600 B0 T3")); + #if E_STEPPERS > 4 + PGM_P msg4 = PSTR(MSG_FILAMENTCHANGE " " MSG_E5); + if (thermalManager.targetTooColdToExtrude(4)) + MENU_ITEM_P(submenu, msg4, lcd_temp_menu_e4_filament_change); + else + MENU_ITEM_P(gcode, msg4, PSTR("M600 B0 T4")); + #endif // E_STEPPERS > 4 + #endif // E_STEPPERS > 3 + #endif // E_STEPPERS > 2 + #endif // E_STEPPERS == 1 + + #if ENABLED(FILAMENT_LOAD_UNLOAD_GCODES) + if (!planner.movesplanned() && !IS_SD_FILE_OPEN) { + // Load filament + #if E_STEPPERS == 1 + PGM_P msg0 = PSTR(MSG_FILAMENTLOAD); + if (thermalManager.targetTooColdToExtrude(active_extruder)) + MENU_ITEM_P(submenu, msg0, lcd_temp_menu_e0_filament_load); + else + MENU_ITEM_P(gcode, msg0, PSTR("M701")); + #else + PGM_P msg0 = PSTR(MSG_FILAMENTLOAD " " MSG_E1); + PGM_P msg1 = PSTR(MSG_FILAMENTLOAD " " MSG_E2); + if (thermalManager.targetTooColdToExtrude(0)) + MENU_ITEM_P(submenu, msg0, lcd_temp_menu_e0_filament_load); + else + MENU_ITEM_P(gcode, msg0, PSTR("M701 T0")); + if (thermalManager.targetTooColdToExtrude(1)) + MENU_ITEM_P(submenu, msg1, lcd_temp_menu_e1_filament_load); + else + MENU_ITEM_P(gcode, msg1, PSTR("M701 T1")); + #if E_STEPPERS > 2 + PGM_P msg2 = PSTR(MSG_FILAMENTLOAD " " MSG_E3); + if (thermalManager.targetTooColdToExtrude(2)) + MENU_ITEM_P(submenu, msg2, lcd_temp_menu_e2_filament_load); + else + MENU_ITEM_P(gcode, msg2, PSTR("M701 T2")); + #if E_STEPPERS > 3 + PGM_P msg3 = PSTR(MSG_FILAMENTLOAD " " MSG_E4); + if (thermalManager.targetTooColdToExtrude(3)) + MENU_ITEM_P(submenu, msg3, lcd_temp_menu_e3_filament_load); + else + MENU_ITEM_P(gcode, msg3, PSTR("M701 T3")); + #if E_STEPPERS > 4 + PGM_P msg4 = PSTR(MSG_FILAMENTLOAD " " MSG_E5); + if (thermalManager.targetTooColdToExtrude(4)) + MENU_ITEM_P(submenu, msg4, lcd_temp_menu_e4_filament_load); + else + MENU_ITEM_P(gcode, msg4, PSTR("M701 T4")); + #endif // E_STEPPERS > 4 + #endif // E_STEPPERS > 3 + #endif // E_STEPPERS > 2 + #endif // E_STEPPERS == 1 + + // Unload filament + #if E_STEPPERS == 1 + if (thermalManager.targetHotEnoughToExtrude(active_extruder)) + MENU_ITEM(gcode, MSG_FILAMENTUNLOAD, PSTR("M702")); + else + MENU_ITEM(submenu, MSG_FILAMENTUNLOAD, lcd_temp_menu_e0_filament_unload); + #else + #if ENABLED(FILAMENT_UNLOAD_ALL_EXTRUDERS) + if (thermalManager.targetHotEnoughToExtrude(0) + #if E_STEPPERS > 1 + && thermalManager.targetHotEnoughToExtrude(1) + #if E_STEPPERS > 2 + && thermalManager.targetHotEnoughToExtrude(2) + #if E_STEPPERS > 3 + && thermalManager.targetHotEnoughToExtrude(3) + #if E_STEPPERS > 4 + && thermalManager.targetHotEnoughToExtrude(4) + #endif // E_STEPPERS > 4 + #endif // E_STEPPERS > 3 + #endif // E_STEPPERS > 2 + #endif // E_STEPPERS > 1 + ) + MENU_ITEM(gcode, MSG_FILAMENTUNLOAD_ALL, PSTR("M702")); + else + MENU_ITEM(submenu, MSG_FILAMENTUNLOAD_ALL, lcd_unload_filament_all_temp_menu); + #endif + if (thermalManager.targetHotEnoughToExtrude(0)) + MENU_ITEM(gcode, MSG_FILAMENTUNLOAD " " MSG_E1, PSTR("M702 T0")); + else + MENU_ITEM(submenu, MSG_FILAMENTUNLOAD " " MSG_E1, lcd_temp_menu_e0_filament_unload); + if (thermalManager.targetHotEnoughToExtrude(1)) + MENU_ITEM(gcode, MSG_FILAMENTUNLOAD " " MSG_E2, PSTR("M702 T1")); + else + MENU_ITEM(submenu, MSG_FILAMENTUNLOAD " " MSG_E2, lcd_temp_menu_e1_filament_unload); + #if E_STEPPERS > 2 + if (thermalManager.targetHotEnoughToExtrude(2)) + MENU_ITEM(gcode, MSG_FILAMENTUNLOAD " " MSG_E3, PSTR("M702 T2")); + else + MENU_ITEM(submenu, MSG_FILAMENTUNLOAD " " MSG_E3, lcd_temp_menu_e2_filament_unload); + #if E_STEPPERS > 3 + if (thermalManager.targetHotEnoughToExtrude(3)) + MENU_ITEM(gcode, MSG_FILAMENTUNLOAD " " MSG_E4, PSTR("M702 T3")); + else + MENU_ITEM(submenu, MSG_FILAMENTUNLOAD " " MSG_E4, lcd_temp_menu_e3_filament_unload); + #if E_STEPPERS > 4 + if (thermalManager.targetHotEnoughToExtrude(4)) + MENU_ITEM(gcode, MSG_FILAMENTUNLOAD " " MSG_E5, PSTR("M702 T4")); + else + MENU_ITEM(submenu, MSG_FILAMENTUNLOAD " " MSG_E5, lcd_temp_menu_e4_filament_unload); + #endif // E_STEPPERS > 4 + #endif // E_STEPPERS > 3 + #endif // E_STEPPERS > 2 + #endif // E_STEPPERS == 1 + } + #endif + + END_MENU(); + } + #endif + + static AdvancedPauseMode advanced_pause_mode = ADVANCED_PAUSE_MODE_PAUSE_PRINT; + static uint8_t hotend_status_extruder = 0; + + static const char* advanced_pause_header() { + switch (advanced_pause_mode) { + case ADVANCED_PAUSE_MODE_LOAD_FILAMENT: + return PSTR(MSG_FILAMENT_CHANGE_HEADER_LOAD); + case ADVANCED_PAUSE_MODE_UNLOAD_FILAMENT: + return PSTR(MSG_FILAMENT_CHANGE_HEADER_UNLOAD); + default: break; + } + return PSTR(MSG_FILAMENT_CHANGE_HEADER_PAUSE); + } + + // Portions from STATIC_ITEM... + #define HOTEND_STATUS_ITEM() do { \ + if (_menuLineNr == _thisItemNr) { \ + if (lcdDrawUpdate) { \ + lcd_implementation_drawmenu_static(_lcdLineNr, PSTR(MSG_FILAMENT_CHANGE_NOZZLE), false, true); \ + lcd_implementation_hotend_status(_lcdLineNr, hotend_status_extruder); \ + } \ + if (_skipStatic && encoderLine <= _thisItemNr) { \ + encoderPosition += ENCODER_STEPS_PER_MENU_ITEM; \ + ++encoderLine; \ + } \ + lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NEXT; \ + } \ + ++_thisItemNr; \ + }while(0) + + void lcd_advanced_pause_resume_print() { + advanced_pause_menu_response = ADVANCED_PAUSE_RESPONSE_RESUME_PRINT; + } + + void lcd_advanced_pause_extrude_more() { + advanced_pause_menu_response = ADVANCED_PAUSE_RESPONSE_EXTRUDE_MORE; + } + + void lcd_advanced_pause_option_menu() { + START_MENU(); + #if LCD_HEIGHT > 2 + STATIC_ITEM(MSG_FILAMENT_CHANGE_OPTION_HEADER, true, false); + #endif + MENU_ITEM(function, MSG_FILAMENT_CHANGE_OPTION_RESUME, lcd_advanced_pause_resume_print); + MENU_ITEM(function, MSG_FILAMENT_CHANGE_OPTION_PURGE, lcd_advanced_pause_extrude_more); + END_MENU(); + } + + void lcd_advanced_pause_init_message() { + START_SCREEN(); + STATIC_ITEM_P(advanced_pause_header(), true, true); + STATIC_ITEM(MSG_FILAMENT_CHANGE_INIT_1); + #ifdef MSG_FILAMENT_CHANGE_INIT_2 + STATIC_ITEM(MSG_FILAMENT_CHANGE_INIT_2); + #define __FC_LINES_A 3 + #else + #define __FC_LINES_A 2 + #endif + #ifdef MSG_FILAMENT_CHANGE_INIT_3 + STATIC_ITEM(MSG_FILAMENT_CHANGE_INIT_3); + #define _FC_LINES_A (__FC_LINES_A + 1) + #else + #define _FC_LINES_A __FC_LINES_A + #endif + #if LCD_HEIGHT > _FC_LINES_A + 1 + STATIC_ITEM(" "); + #endif + HOTEND_STATUS_ITEM(); + END_SCREEN(); + } + + void lcd_advanced_pause_unload_message() { + START_SCREEN(); + STATIC_ITEM_P(advanced_pause_header(), true, true); + STATIC_ITEM(MSG_FILAMENT_CHANGE_UNLOAD_1); + #ifdef MSG_FILAMENT_CHANGE_UNLOAD_2 + STATIC_ITEM(MSG_FILAMENT_CHANGE_UNLOAD_2); + #define __FC_LINES_B 3 + #else + #define __FC_LINES_B 2 + #endif + #ifdef MSG_FILAMENT_CHANGE_UNLOAD_3 + STATIC_ITEM(MSG_FILAMENT_CHANGE_UNLOAD_3); + #define _FC_LINES_B (__FC_LINES_B + 1) + #else + #define _FC_LINES_B __FC_LINES_B + #endif + #if LCD_HEIGHT > _FC_LINES_B + 1 + STATIC_ITEM(" "); + #endif + HOTEND_STATUS_ITEM(); + END_SCREEN(); + } + + void lcd_advanced_pause_wait_for_nozzles_to_heat() { + START_SCREEN(); + STATIC_ITEM_P(advanced_pause_header(), true, true); + STATIC_ITEM(MSG_FILAMENT_CHANGE_HEATING_1); + #ifdef MSG_FILAMENT_CHANGE_HEATING_2 + STATIC_ITEM(MSG_FILAMENT_CHANGE_HEATING_2); + #define _FC_LINES_C 3 + #else + #define _FC_LINES_C 2 + #endif + #if LCD_HEIGHT > _FC_LINES_C + 1 + STATIC_ITEM(" "); + #endif + HOTEND_STATUS_ITEM(); + END_SCREEN(); + } + + void lcd_advanced_pause_heat_nozzle() { + START_SCREEN(); + STATIC_ITEM_P(advanced_pause_header(), true, true); + STATIC_ITEM(MSG_FILAMENT_CHANGE_HEAT_1); + #ifdef MSG_FILAMENT_CHANGE_INSERT_2 + STATIC_ITEM(MSG_FILAMENT_CHANGE_HEAT_2); + #define _FC_LINES_D 3 + #else + #define _FC_LINES_D 2 + #endif + #if LCD_HEIGHT > _FC_LINES_D + 1 + STATIC_ITEM(" "); + #endif + HOTEND_STATUS_ITEM(); + END_SCREEN(); + } + + void lcd_advanced_pause_insert_message() { + START_SCREEN(); + STATIC_ITEM_P(advanced_pause_header(), true, true); + STATIC_ITEM(MSG_FILAMENT_CHANGE_INSERT_1); + #ifdef MSG_FILAMENT_CHANGE_INSERT_2 + STATIC_ITEM(MSG_FILAMENT_CHANGE_INSERT_2); + #define __FC_LINES_E 3 + #else + #define __FC_LINES_E 2 + #endif + #ifdef MSG_FILAMENT_CHANGE_INSERT_3 + STATIC_ITEM(MSG_FILAMENT_CHANGE_INSERT_3); + #define _FC_LINES_E (__FC_LINES_E + 1) + #else + #define _FC_LINES_E __FC_LINES_E + #endif + #if LCD_HEIGHT > _FC_LINES_E + 1 + STATIC_ITEM(" "); + #endif + HOTEND_STATUS_ITEM(); + END_SCREEN(); + } + + void lcd_advanced_pause_load_message() { + START_SCREEN(); + STATIC_ITEM_P(advanced_pause_header(), true, true); + STATIC_ITEM(MSG_FILAMENT_CHANGE_LOAD_1); + #ifdef MSG_FILAMENT_CHANGE_LOAD_2 + STATIC_ITEM(MSG_FILAMENT_CHANGE_LOAD_2); + #define __FC_LINES_F 3 + #else + #define __FC_LINES_F 2 + #endif + #ifdef MSG_FILAMENT_CHANGE_LOAD_3 + STATIC_ITEM(MSG_FILAMENT_CHANGE_LOAD_3); + #define _FC_LINES_F (__FC_LINES_F + 1) + #else + #define _FC_LINES_F __FC_LINES_F + #endif + #if LCD_HEIGHT > _FC_LINES_F + 1 + STATIC_ITEM(" "); + #endif + HOTEND_STATUS_ITEM(); + END_SCREEN(); + } + + void lcd_advanced_pause_purge_message() { + START_SCREEN(); + STATIC_ITEM_P(advanced_pause_header(), true, true); + STATIC_ITEM(MSG_FILAMENT_CHANGE_PURGE_1); + #ifdef MSG_FILAMENT_CHANGE_PURGE_2 + STATIC_ITEM(MSG_FILAMENT_CHANGE_PURGE_2); + #define __FC_LINES_G 3 + #else + #define __FC_LINES_G 2 + #endif + #ifdef MSG_FILAMENT_CHANGE_PURGE_3 + STATIC_ITEM(MSG_FILAMENT_CHANGE_PURGE_3); + #define _FC_LINES_G (__FC_LINES_G + 1) + #else + #define _FC_LINES_G __FC_LINES_G + #endif + #if LCD_HEIGHT > _FC_LINES_G + 1 + STATIC_ITEM(" "); + #endif + HOTEND_STATUS_ITEM(); + END_SCREEN(); + } + + void lcd_advanced_pause_resume_message() { + START_SCREEN(); + STATIC_ITEM_P(advanced_pause_header(), true, true); + STATIC_ITEM(MSG_FILAMENT_CHANGE_RESUME_1); + #ifdef MSG_FILAMENT_CHANGE_RESUME_2 + STATIC_ITEM(MSG_FILAMENT_CHANGE_RESUME_2); + #endif + #ifdef MSG_FILAMENT_CHANGE_RESUME_3 + STATIC_ITEM(MSG_FILAMENT_CHANGE_RESUME_3); + #endif + END_SCREEN(); + } + + FORCE_INLINE screenFunc_t ap_message_screen(const AdvancedPauseMessage message) { + switch (message) { + case ADVANCED_PAUSE_MESSAGE_INIT: return lcd_advanced_pause_init_message; + case ADVANCED_PAUSE_MESSAGE_UNLOAD: return lcd_advanced_pause_unload_message; + case ADVANCED_PAUSE_MESSAGE_INSERT: return lcd_advanced_pause_insert_message; + case ADVANCED_PAUSE_MESSAGE_LOAD: return lcd_advanced_pause_load_message; + case ADVANCED_PAUSE_MESSAGE_PURGE: return lcd_advanced_pause_purge_message; + case ADVANCED_PAUSE_MESSAGE_RESUME: return lcd_advanced_pause_resume_message; + case ADVANCED_PAUSE_MESSAGE_CLICK_TO_HEAT_NOZZLE: return lcd_advanced_pause_heat_nozzle; + case ADVANCED_PAUSE_MESSAGE_WAIT_FOR_NOZZLES_TO_HEAT: return lcd_advanced_pause_wait_for_nozzles_to_heat; + case ADVANCED_PAUSE_MESSAGE_OPTION: advanced_pause_menu_response = ADVANCED_PAUSE_RESPONSE_WAIT_FOR; + return lcd_advanced_pause_option_menu; + case ADVANCED_PAUSE_MESSAGE_STATUS: + default: break; + } + return NULL; + } + + void lcd_advanced_pause_show_message( + const AdvancedPauseMessage message, + const AdvancedPauseMode mode/*=ADVANCED_PAUSE_MODE_PAUSE_PRINT*/, + const uint8_t extruder/*=active_extruder*/ + ) { + advanced_pause_mode = mode; + hotend_status_extruder = extruder; + const screenFunc_t next_screen = ap_message_screen(message); + if (next_screen) { + defer_return_to_status = true; + lcd_goto_screen(next_screen); + } + else + lcd_return_to_status(); + } + + #endif // ADVANCED_PAUSE_FEATURE + + /** + * + * Functions for editing single values + * + * The "DEFINE_MENU_EDIT_TYPE" macro generates the functions needed to edit a numerical value. + * + * For example, DEFINE_MENU_EDIT_TYPE(int16_t, int3, itostr3, 1) expands into these functions: + * + * bool _menu_edit_int3(); + * void menu_edit_int3(); // edit int16_t (interactively) + * void menu_edit_callback_int3(); // edit int16_t (interactively) with callback on completion + * void _menu_action_setting_edit_int3(const char * const pstr, int16_t * const ptr, const int16_t minValue, const int16_t maxValue); + * void menu_action_setting_edit_int3(const char * const pstr, int16_t * const ptr, const int16_t minValue, const int16_t maxValue); + * void menu_action_setting_edit_callback_int3(const char * const pstr, int16_t * const ptr, const int16_t minValue, const int16_t maxValue, const screenFunc_t callback, const bool live); // edit int16_t with callback + * + * You can then use one of the menu macros to present the edit interface: + * MENU_ITEM_EDIT(int3, MSG_SPEED, &feedrate_percentage, 10, 999) + * + * This expands into a more primitive menu item: + * MENU_ITEM(setting_edit_int3, MSG_SPEED, PSTR(MSG_SPEED), &feedrate_percentage, 10, 999) + * + * ...which calls: + * menu_action_setting_edit_int3(PSTR(MSG_SPEED), &feedrate_percentage, 10, 999) + */ + #define DEFINE_MENU_EDIT_TYPE(_type, _name, _strFunc, _scale) \ + bool _menu_edit_ ## _name() { \ + ENCODER_DIRECTION_NORMAL(); \ + if ((int32_t)encoderPosition < 0) encoderPosition = 0; \ + if ((int32_t)encoderPosition > maxEditValue) encoderPosition = maxEditValue; \ + if (lcdDrawUpdate) \ + lcd_implementation_drawedit(editLabel, _strFunc(((_type)((int32_t)encoderPosition + minEditValue)) * (1.0 / _scale))); \ + if (lcd_clicked || (liveEdit && lcdDrawUpdate)) { \ + _type value = ((_type)((int32_t)encoderPosition + minEditValue)) * (1.0 / _scale); \ + if (editValue != NULL) *((_type*)editValue) = value; \ + if (liveEdit) (*callbackFunc)(); \ + if (lcd_clicked) lcd_goto_previous_menu(); \ + } \ + return use_click(); \ + } \ + void menu_edit_ ## _name() { _menu_edit_ ## _name(); } \ + void menu_edit_callback_ ## _name() { if (_menu_edit_ ## _name()) (*callbackFunc)(); } \ + void _menu_action_setting_edit_ ## _name(const char * const pstr, _type* const ptr, const _type minValue, const _type maxValue) { \ + lcd_save_previous_screen(); \ + \ + lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW; \ + \ + editLabel = pstr; \ + editValue = ptr; \ + minEditValue = minValue * _scale; \ + maxEditValue = maxValue * _scale - minEditValue; \ + encoderPosition = (*ptr) * _scale - minEditValue; \ + } \ + void menu_action_setting_edit_ ## _name(const char * const pstr, _type * const ptr, const _type minValue, const _type maxValue) { \ + _menu_action_setting_edit_ ## _name(pstr, ptr, minValue, maxValue); \ + currentScreen = menu_edit_ ## _name; \ + } \ + void menu_action_setting_edit_callback_ ## _name(const char * const pstr, _type * const ptr, const _type minValue, const _type maxValue, const screenFunc_t callback, const bool live) { \ + _menu_action_setting_edit_ ## _name(pstr, ptr, minValue, maxValue); \ + currentScreen = menu_edit_callback_ ## _name; \ + callbackFunc = callback; \ + liveEdit = live; \ + } \ + typedef void _name + + DEFINE_MENU_EDIT_TYPE(uint32_t, long5, ftostr5rj, 0.01); + DEFINE_MENU_EDIT_TYPE(int16_t, int3, itostr3, 1); + DEFINE_MENU_EDIT_TYPE(uint8_t, int8, i8tostr3, 1); + DEFINE_MENU_EDIT_TYPE(float, float3, ftostr3, 1.0); + DEFINE_MENU_EDIT_TYPE(float, float32, ftostr32, 100.0); + DEFINE_MENU_EDIT_TYPE(float, float43, ftostr43sign, 1000.0); + DEFINE_MENU_EDIT_TYPE(float, float5, ftostr5rj, 0.01); + DEFINE_MENU_EDIT_TYPE(float, float51, ftostr51sign, 10.0); + DEFINE_MENU_EDIT_TYPE(float, float52, ftostr52sign, 100.0); + DEFINE_MENU_EDIT_TYPE(float, float62, ftostr62rj, 100.0); + + /** + * + * Handlers for Keypad input + * + */ + #if ENABLED(ADC_KEYPAD) + + inline bool handle_adc_keypad() { + #define ADC_MIN_KEY_DELAY 100 + if (buttons_reprapworld_keypad) { + lcdDrawUpdate = LCDVIEW_REDRAW_NOW; + if (encoderDirection == -1) { // side effect which signals we are inside a menu + if (buttons_reprapworld_keypad & EN_REPRAPWORLD_KEYPAD_DOWN) encoderPosition -= ENCODER_STEPS_PER_MENU_ITEM; + else if (buttons_reprapworld_keypad & EN_REPRAPWORLD_KEYPAD_UP) encoderPosition += ENCODER_STEPS_PER_MENU_ITEM; + else if (buttons_reprapworld_keypad & EN_REPRAPWORLD_KEYPAD_LEFT) { menu_action_back(); lcd_quick_feedback(true); } + else if (buttons_reprapworld_keypad & EN_REPRAPWORLD_KEYPAD_RIGHT) { lcd_return_to_status(); lcd_quick_feedback(true); } + } + else { + if (buttons_reprapworld_keypad & (EN_REPRAPWORLD_KEYPAD_DOWN|EN_REPRAPWORLD_KEYPAD_UP|EN_REPRAPWORLD_KEYPAD_RIGHT)) { + if (buttons_reprapworld_keypad & EN_REPRAPWORLD_KEYPAD_DOWN) encoderPosition += ENCODER_PULSES_PER_STEP; + else if (buttons_reprapworld_keypad & EN_REPRAPWORLD_KEYPAD_UP) encoderPosition -= ENCODER_PULSES_PER_STEP; + else if (buttons_reprapworld_keypad & EN_REPRAPWORLD_KEYPAD_RIGHT) encoderPosition = 0; + } + } + #if ENABLED(ADC_KEYPAD_DEBUG) + SERIAL_PROTOCOLLNPAIR("buttons_reprapworld_keypad = ", (uint32_t)buttons_reprapworld_keypad); + SERIAL_PROTOCOLLNPAIR("encoderPosition = ", (uint32_t)encoderPosition); + #endif + next_button_update_ms = millis() + ADC_MIN_KEY_DELAY; + return true; + } + + return false; + } + + #elif ENABLED(REPRAPWORLD_KEYPAD) + + void _reprapworld_keypad_move(const AxisEnum axis, const int16_t dir) { + move_menu_scale = REPRAPWORLD_KEYPAD_MOVE_STEP; + encoderPosition = dir; + switch (axis) { + case X_AXIS: lcd_move_x(); break; + case Y_AXIS: lcd_move_y(); break; + case Z_AXIS: lcd_move_z(); + default: break; + } + } + void reprapworld_keypad_move_z_up() { _reprapworld_keypad_move(Z_AXIS, 1); } + void reprapworld_keypad_move_z_down() { _reprapworld_keypad_move(Z_AXIS, -1); } + void reprapworld_keypad_move_x_left() { _reprapworld_keypad_move(X_AXIS, -1); } + void reprapworld_keypad_move_x_right() { _reprapworld_keypad_move(X_AXIS, 1); } + void reprapworld_keypad_move_y_up() { _reprapworld_keypad_move(Y_AXIS, -1); } + void reprapworld_keypad_move_y_down() { _reprapworld_keypad_move(Y_AXIS, 1); } + void reprapworld_keypad_move_home() { enqueue_and_echo_commands_P(PSTR("G28")); } // move all axes home and wait + void reprapworld_keypad_move_menu() { lcd_goto_screen(lcd_move_menu); } + + inline void handle_reprapworld_keypad() { + + static uint8_t keypad_debounce = 0; + + if (!REPRAPWORLD_KEYPAD_PRESSED) { + if (keypad_debounce > 0) keypad_debounce--; + } + else if (!keypad_debounce) { + keypad_debounce = 2; + + if (REPRAPWORLD_KEYPAD_MOVE_MENU) reprapworld_keypad_move_menu(); + + #if DISABLED(DELTA) && Z_HOME_DIR == -1 + if (REPRAPWORLD_KEYPAD_MOVE_Z_UP) reprapworld_keypad_move_z_up(); + #endif + + if (axis_homed[X_AXIS] && axis_homed[Y_AXIS] && axis_homed[Z_AXIS]) { + #if ENABLED(DELTA) || Z_HOME_DIR != -1 + if (REPRAPWORLD_KEYPAD_MOVE_Z_UP) reprapworld_keypad_move_z_up(); + #endif + if (REPRAPWORLD_KEYPAD_MOVE_Z_DOWN) reprapworld_keypad_move_z_down(); + if (REPRAPWORLD_KEYPAD_MOVE_X_LEFT) reprapworld_keypad_move_x_left(); + if (REPRAPWORLD_KEYPAD_MOVE_X_RIGHT) reprapworld_keypad_move_x_right(); + if (REPRAPWORLD_KEYPAD_MOVE_Y_DOWN) reprapworld_keypad_move_y_down(); + if (REPRAPWORLD_KEYPAD_MOVE_Y_UP) reprapworld_keypad_move_y_up(); + } + else { + if (REPRAPWORLD_KEYPAD_MOVE_HOME) reprapworld_keypad_move_home(); + } + } + } + + #endif // REPRAPWORLD_KEYPAD + + /** + * + * Menu actions + * + */ + void _menu_action_back() { lcd_goto_previous_menu(); } + void menu_action_submenu(screenFunc_t func) { lcd_save_previous_screen(); lcd_goto_screen(func); } + void menu_action_gcode(const char* pgcode) { enqueue_and_echo_commands_P(pgcode); } + void menu_action_function(screenFunc_t func) { (*func)(); } + + #if ENABLED(SDSUPPORT) + + void menu_action_sdfile(const char* filename, char* longFilename) { + #if ENABLED(SD_REPRINT_LAST_SELECTED_FILE) + last_sdfile_encoderPosition = encoderPosition; // Save which file was selected for later use + #endif + UNUSED(longFilename); + card.openAndPrintFile(filename); + lcd_return_to_status(); + } + + void menu_action_sddirectory(const char* filename, char* longFilename) { + UNUSED(longFilename); + card.chdir(filename); + encoderTopLine = 0; + encoderPosition = 2 * ENCODER_STEPS_PER_MENU_ITEM; + screen_changed = true; + #if ENABLED(DOGLCD) + drawing_screen = false; + #endif + lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW; + } + + #endif // SDSUPPORT + + void menu_action_setting_edit_bool(const char* pstr, bool* ptr) { UNUSED(pstr); *ptr ^= true; lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW; } + void menu_action_setting_edit_callback_bool(const char* pstr, bool* ptr, screenFunc_t callback) { + menu_action_setting_edit_bool(pstr, ptr); + (*callback)(); + } + +#endif // ULTIPANEL + +void lcd_init() { + + lcd_implementation_init(); + + #if ENABLED(NEWPANEL) + #if BUTTON_EXISTS(EN1) + SET_INPUT_PULLUP(BTN_EN1); + #endif + #if BUTTON_EXISTS(EN2) + SET_INPUT_PULLUP(BTN_EN2); + #endif + #if BUTTON_EXISTS(ENC) + SET_INPUT_PULLUP(BTN_ENC); + #endif + + #if ENABLED(REPRAPWORLD_KEYPAD) && DISABLED(ADC_KEYPAD) + SET_OUTPUT(SHIFT_CLK); + OUT_WRITE(SHIFT_LD, HIGH); + SET_INPUT_PULLUP(SHIFT_OUT); + #endif + + #if BUTTON_EXISTS(UP) + SET_INPUT(BTN_UP); + #endif + #if BUTTON_EXISTS(DWN) + SET_INPUT(BTN_DWN); + #endif + #if BUTTON_EXISTS(LFT) + SET_INPUT(BTN_LFT); + #endif + #if BUTTON_EXISTS(RT) + SET_INPUT(BTN_RT); + #endif + + #else // !NEWPANEL + + #if ENABLED(SR_LCD_2W_NL) // Non latching 2 wire shift register + SET_OUTPUT(SR_DATA_PIN); + SET_OUTPUT(SR_CLK_PIN); + #elif defined(SHIFT_CLK) + SET_OUTPUT(SHIFT_CLK); + OUT_WRITE(SHIFT_LD, HIGH); + OUT_WRITE(SHIFT_EN, LOW); + SET_INPUT_PULLUP(SHIFT_OUT); + #endif // SR_LCD_2W_NL + + #endif // !NEWPANEL + + #if ENABLED(SDSUPPORT) && PIN_EXISTS(SD_DETECT) + SET_INPUT_PULLUP(SD_DETECT_PIN); + lcd_sd_status = 2; // UNKNOWN + #endif + + #if ENABLED(LCD_HAS_SLOW_BUTTONS) + slow_buttons = 0; + #endif + + lcd_buttons_update(); + + #if ENABLED(ULTIPANEL) + encoderDiff = 0; + #endif +} + +int16_t lcd_strlen(const char* s) { + int16_t i = 0, j = 0; + while (s[i]) { + if (PRINTABLE(s[i])) j++; + i++; + } + return j; +} + +int16_t lcd_strlen_P(const char* s) { + int16_t j = 0; + while (pgm_read_byte(s)) { + if (PRINTABLE(pgm_read_byte(s))) j++; + s++; + } + return j; +} + +bool lcd_blink() { + static uint8_t blink = 0; + static millis_t next_blink_ms = 0; + millis_t ms = millis(); + if (ELAPSED(ms, next_blink_ms)) { + blink ^= 0xFF; + next_blink_ms = ms + 1000 - (LCD_UPDATE_INTERVAL) / 2; + } + return blink != 0; +} + +/** + * Update the LCD, read encoder buttons, etc. + * - Read button states + * - Check the SD Card slot state + * - Act on RepRap World keypad input + * - Update the encoder position + * - Apply acceleration to the encoder position + * - Set lcdDrawUpdate = LCDVIEW_CALL_REDRAW_NOW on controller events + * - Reset the Info Screen timeout if there's any input + * - Update status indicators, if any + * + * Run the current LCD menu handler callback function: + * - Call the handler only if lcdDrawUpdate != LCDVIEW_NONE + * - Before calling the handler, LCDVIEW_CALL_NO_REDRAW => LCDVIEW_NONE + * - Call the menu handler. Menu handlers should do the following: + * - If a value changes, set lcdDrawUpdate to LCDVIEW_REDRAW_NOW and draw the value + * (Encoder events automatically set lcdDrawUpdate for you.) + * - if (lcdDrawUpdate) { redraw } + * - Before exiting the handler set lcdDrawUpdate to: + * - LCDVIEW_CLEAR_CALL_REDRAW to clear screen and set LCDVIEW_CALL_REDRAW_NEXT. + * - LCDVIEW_REDRAW_NOW to draw now (including remaining stripes). + * - LCDVIEW_CALL_REDRAW_NEXT to draw now and get LCDVIEW_REDRAW_NOW on the next loop. + * - LCDVIEW_CALL_NO_REDRAW to draw now and get LCDVIEW_NONE on the next loop. + * - NOTE: For graphical displays menu handlers may be called 2 or more times per loop, + * so don't change lcdDrawUpdate without considering this. + * + * After the menu handler callback runs (or not): + * - Clear the LCD if lcdDrawUpdate == LCDVIEW_CLEAR_CALL_REDRAW + * - Update lcdDrawUpdate for the next loop (i.e., move one state down, usually) + * + * No worries. This function is only called from the main thread. + */ +void lcd_update() { + + #if ENABLED(ULTIPANEL) + static millis_t return_to_status_ms = 0; + + // Handle any queued Move Axis motion + manage_manual_move(); + + // Update button states for LCD_CLICKED, etc. + // After state changes the next button update + // may be delayed 300-500ms. + lcd_buttons_update(); + + #if ENABLED(AUTO_BED_LEVELING_UBL) + // Don't run the debouncer if UBL owns the display + #define UBL_CONDITION !lcd_external_control + #else + #define UBL_CONDITION true + #endif + + // If the action button is pressed... + if (UBL_CONDITION && LCD_CLICKED) { + if (!wait_for_unclick) { // If not waiting for a debounce release: + wait_for_unclick = true; // Set debounce flag to ignore continous clicks + lcd_clicked = !wait_for_user && !no_reentry; // Keep the click if not waiting for a user-click + wait_for_user = false; // Any click clears wait for user + lcd_quick_feedback(true); // Always make a click sound + } + } + else wait_for_unclick = false; + + #if BUTTON_EXISTS(BACK) + if (LCD_BACK_CLICKED) { + lcd_quick_feedback(true); + lcd_goto_previous_menu(); + } + #endif + + #endif + + #if ENABLED(SDSUPPORT) && PIN_EXISTS(SD_DETECT) + + const bool sd_status = IS_SD_INSERTED; + if (sd_status != lcd_sd_status && lcd_detected()) { + + bool old_sd_status = lcd_sd_status; // prevent re-entry to this block! + lcd_sd_status = sd_status; + + if (sd_status) { + safe_delay(1000); // some boards need a delay or the LCD won't show the new status + card.initsd(); + if (old_sd_status != 2) LCD_MESSAGEPGM(MSG_SD_INSERTED); + } + else { + card.release(); + if (old_sd_status != 2) LCD_MESSAGEPGM(MSG_SD_REMOVED); + } + + lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW; + lcd_implementation_init( // to maybe revive the LCD if static electricity killed it. + #if ENABLED(LCD_PROGRESS_BAR) + currentScreen == lcd_status_screen ? CHARSET_INFO : CHARSET_MENU + #endif + ); + } + + #endif // SDSUPPORT && SD_DETECT_PIN + + const millis_t ms = millis(); + if (ELAPSED(ms, next_lcd_update_ms) + #if ENABLED(DOGLCD) + || drawing_screen + #endif + ) { + + next_lcd_update_ms = ms + LCD_UPDATE_INTERVAL; + + #if ENABLED(LCD_HAS_STATUS_INDICATORS) + lcd_implementation_update_indicators(); + #endif + + #if ENABLED(ULTIPANEL) + + #if ENABLED(LCD_HAS_SLOW_BUTTONS) + slow_buttons = lcd_implementation_read_slow_buttons(); // buttons which take too long to read in interrupt context + #endif + + #if ENABLED(ADC_KEYPAD) + + if (handle_adc_keypad()) + return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS; + + #elif ENABLED(REPRAPWORLD_KEYPAD) + + handle_reprapworld_keypad(); + + #endif + + const bool encoderPastThreshold = (abs(encoderDiff) >= ENCODER_PULSES_PER_STEP); + if (encoderPastThreshold || lcd_clicked) { + if (encoderPastThreshold) { + int32_t encoderMultiplier = 1; + + #if ENABLED(ENCODER_RATE_MULTIPLIER) + + if (encoderRateMultiplierEnabled) { + int32_t encoderMovementSteps = abs(encoderDiff) / ENCODER_PULSES_PER_STEP; + + if (lastEncoderMovementMillis) { + // Note that the rate is always calculated between two passes through the + // loop and that the abs of the encoderDiff value is tracked. + float encoderStepRate = float(encoderMovementSteps) / float(ms - lastEncoderMovementMillis) * 1000.0; + + if (encoderStepRate >= ENCODER_100X_STEPS_PER_SEC) encoderMultiplier = 100; + else if (encoderStepRate >= ENCODER_10X_STEPS_PER_SEC) encoderMultiplier = 10; + + #if ENABLED(ENCODER_RATE_MULTIPLIER_DEBUG) + SERIAL_ECHO_START(); + SERIAL_ECHOPAIR("Enc Step Rate: ", encoderStepRate); + SERIAL_ECHOPAIR(" Multiplier: ", encoderMultiplier); + SERIAL_ECHOPAIR(" ENCODER_10X_STEPS_PER_SEC: ", ENCODER_10X_STEPS_PER_SEC); + SERIAL_ECHOPAIR(" ENCODER_100X_STEPS_PER_SEC: ", ENCODER_100X_STEPS_PER_SEC); + SERIAL_EOL(); + #endif // ENCODER_RATE_MULTIPLIER_DEBUG + } + + lastEncoderMovementMillis = ms; + } // encoderRateMultiplierEnabled + #endif // ENCODER_RATE_MULTIPLIER + + encoderPosition += (encoderDiff * encoderMultiplier) / ENCODER_PULSES_PER_STEP; + encoderDiff = 0; + } + return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS; + lcdDrawUpdate = LCDVIEW_REDRAW_NOW; + } + #endif // ULTIPANEL + + // We arrive here every ~100ms when idling often enough. + // Instead of tracking the changes simply redraw the Info Screen ~1 time a second. + if ( + #if ENABLED(ULTIPANEL) + currentScreen == lcd_status_screen && + #endif + !lcd_status_update_delay-- + ) { + lcd_status_update_delay = 9 + #if ENABLED(DOGLCD) + + 3 + #endif + ; + max_display_update_time--; + lcdDrawUpdate = LCDVIEW_REDRAW_NOW; + } + + #if ENABLED(SCROLL_LONG_FILENAMES) + // If scrolling of long file names is enabled and we are in the sd card menu, + // cause a refresh to occur until all the text has scrolled into view. + if (currentScreen == lcd_sdcard_menu && filename_scroll_pos < filename_scroll_max && !lcd_status_update_delay--) { + lcd_status_update_delay = 6; + lcdDrawUpdate = LCDVIEW_REDRAW_NOW; + filename_scroll_pos++; + return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS; + } + #endif + + // then we want to use 1/2 of the time only. + uint16_t bbr2 = planner.block_buffer_runtime() >> 1; + + #if ENABLED(DOGLCD) + #define IS_DRAWING drawing_screen + #else + #define IS_DRAWING false + #endif + + if ((lcdDrawUpdate || IS_DRAWING) && (!bbr2 || bbr2 > max_display_update_time)) { + + // Change state of drawing flag between screen updates + if (!IS_DRAWING) switch (lcdDrawUpdate) { + case LCDVIEW_CALL_NO_REDRAW: + lcdDrawUpdate = LCDVIEW_NONE; + break; + case LCDVIEW_CLEAR_CALL_REDRAW: + case LCDVIEW_CALL_REDRAW_NEXT: + lcdDrawUpdate = LCDVIEW_REDRAW_NOW; + case LCDVIEW_REDRAW_NOW: // set above, or by a handler through LCDVIEW_CALL_REDRAW_NEXT + case LCDVIEW_NONE: + break; + } // switch + + #if ENABLED(ADC_KEYPAD) + buttons_reprapworld_keypad = 0; + #endif + + #if ENABLED(ULTIPANEL) + #define CURRENTSCREEN() (*currentScreen)() + #else + #define CURRENTSCREEN() lcd_status_screen() + #endif + + #if ENABLED(DOGLCD) + #if ENABLED(LIGHTWEIGHT_UI) + #if ENABLED(ULTIPANEL) + const bool in_status = currentScreen == lcd_status_screen; + #else + constexpr bool in_status = true; + #endif + const bool do_u8g_loop = !in_status; + lcd_in_status(in_status); + if (in_status) lcd_status_screen(); + #else + constexpr bool do_u8g_loop = true; + #endif + if (do_u8g_loop) { + if (!drawing_screen) { // If not already drawing pages + u8g.firstPage(); // Start the first page + drawing_screen = first_page = true; // Flag as drawing pages + } + lcd_setFont(FONT_MENU); // Setup font for every page draw + u8g.setColorIndex(1); // And reset the color + CURRENTSCREEN(); // Draw and process the current screen + first_page = false; + + // The screen handler can clear drawing_screen for an action that changes the screen. + // If still drawing and there's another page, update max-time and return now. + // The nextPage will already be set up on the next call. + if (drawing_screen && (drawing_screen = u8g.nextPage())) { + NOLESS(max_display_update_time, millis() - ms); + return; + } + } + #else + CURRENTSCREEN(); + #endif + + #if ENABLED(ULTIPANEL) + lcd_clicked = false; + #endif + + // Keeping track of the longest time for an individual LCD update. + // Used to do screen throttling when the planner starts to fill up. + NOLESS(max_display_update_time, millis() - ms); + } + + #if ENABLED(ULTIPANEL) + + // Return to Status Screen after a timeout + if (currentScreen == lcd_status_screen || defer_return_to_status) + return_to_status_ms = ms + LCD_TIMEOUT_TO_STATUS; + else if (ELAPSED(ms, return_to_status_ms)) + lcd_return_to_status(); + + #endif // ULTIPANEL + + // Change state of drawing flag between screen updates + if (!IS_DRAWING) switch (lcdDrawUpdate) { + case LCDVIEW_CLEAR_CALL_REDRAW: + lcd_implementation_clear(); break; + case LCDVIEW_REDRAW_NOW: + lcdDrawUpdate = LCDVIEW_NONE; + case LCDVIEW_NONE: + case LCDVIEW_CALL_REDRAW_NEXT: + case LCDVIEW_CALL_NO_REDRAW: + default: break; + } // switch + + } // ELAPSED(ms, next_lcd_update_ms) +} + +inline void pad_message_string() { + uint8_t i = 0, j = 0; + char c; + lcd_status_message[MAX_MESSAGE_LENGTH] = '\0'; + while ((c = lcd_status_message[i]) && j < LCD_WIDTH) { + if (PRINTABLE(c)) j++; + i++; + } + if (true + #if ENABLED(STATUS_MESSAGE_SCROLLING) + && j < LCD_WIDTH + #endif + ) { + // pad with spaces to fill up the line + while (j++ < LCD_WIDTH) lcd_status_message[i++] = ' '; + // chop off at the edge + lcd_status_message[i] = '\0'; + } +} + +void lcd_finishstatus(const bool persist=false) { + + pad_message_string(); + + #if !(ENABLED(LCD_PROGRESS_BAR) && (PROGRESS_MSG_EXPIRE > 0)) + UNUSED(persist); + #endif + + #if ENABLED(LCD_PROGRESS_BAR) + progress_bar_ms = millis(); + #if PROGRESS_MSG_EXPIRE > 0 + expire_status_ms = persist ? 0 : progress_bar_ms + PROGRESS_MSG_EXPIRE; + #endif + #endif + lcdDrawUpdate = LCDVIEW_CLEAR_CALL_REDRAW; + + #if ENABLED(FILAMENT_LCD_DISPLAY) && ENABLED(SDSUPPORT) + previous_lcd_status_ms = millis(); //get status message to show up for a while + #endif + + #if ENABLED(STATUS_MESSAGE_SCROLLING) + status_scroll_pos = 0; + #endif +} + +#if ENABLED(LCD_PROGRESS_BAR) && PROGRESS_MSG_EXPIRE > 0 + void dontExpireStatus() { expire_status_ms = 0; } +#endif + +bool lcd_hasstatus() { return (lcd_status_message[0] != '\0'); } + +void lcd_setstatus(const char * const message, const bool persist) { + if (lcd_status_message_level > 0) return; + strncpy(lcd_status_message, message, MAX_MESSAGE_LENGTH); + lcd_finishstatus(persist); +} + +void lcd_setstatusPGM(const char * const message, int8_t level) { + if (level < 0) level = lcd_status_message_level = 0; + if (level < lcd_status_message_level) return; + lcd_status_message_level = level; + strncpy_P(lcd_status_message, message, MAX_MESSAGE_LENGTH); + lcd_finishstatus(level > 0); +} + +void lcd_status_printf_P(const uint8_t level, const char * const fmt, ...) { + if (level < lcd_status_message_level) return; + lcd_status_message_level = level; + va_list args; + va_start(args, fmt); + vsnprintf_P(lcd_status_message, MAX_MESSAGE_LENGTH, fmt, args); + va_end(args); + lcd_finishstatus(level > 0); +} + +void lcd_setalertstatusPGM(const char * const message) { + lcd_setstatusPGM(message, 1); + #if ENABLED(ULTIPANEL) + lcd_return_to_status(); + #endif +} + +void lcd_reset_alert_level() { lcd_status_message_level = 0; } + +#if HAS_LCD_CONTRAST + + void set_lcd_contrast(const int16_t value) { + lcd_contrast = constrain(value, LCD_CONTRAST_MIN, LCD_CONTRAST_MAX); + u8g.setContrast(lcd_contrast); + } + +#endif + +#if ENABLED(ULTIPANEL) + + /** + * Setup Rotary Encoder Bit Values (for two pin encoders to indicate movement) + * These values are independent of which pins are used for EN_A and EN_B indications + * The rotary encoder part is also independent to the chipset used for the LCD + */ + #if defined(EN_A) && defined(EN_B) + #define encrot0 0 + #define encrot1 2 + #define encrot2 3 + #define encrot3 1 + #endif + + #define GET_SHIFT_BUTTON_STATES(DST) \ + uint8_t new_##DST = 0; \ + WRITE(SHIFT_LD, LOW); \ + WRITE(SHIFT_LD, HIGH); \ + for (int8_t i = 0; i < 8; i++) { \ + new_##DST >>= 1; \ + if (READ(SHIFT_OUT)) SBI(new_##DST, 7); \ + WRITE(SHIFT_CLK, HIGH); \ + WRITE(SHIFT_CLK, LOW); \ + } \ + DST = ~new_##DST; //invert it, because a pressed switch produces a logical 0 + + + /** + * Read encoder buttons from the hardware registers + * Warning: This function is called from interrupt context! + */ + void lcd_buttons_update() { + static uint8_t lastEncoderBits; + const millis_t now = millis(); + if (ELAPSED(now, next_button_update_ms)) { + + #if ENABLED(NEWPANEL) + uint8_t newbutton = 0; + + #if BUTTON_EXISTS(EN1) + if (BUTTON_PRESSED(EN1)) newbutton |= EN_A; + #endif + + #if BUTTON_EXISTS(EN2) + if (BUTTON_PRESSED(EN2)) newbutton |= EN_B; + #endif + + #if BUTTON_EXISTS(ENC) + if (BUTTON_PRESSED(ENC)) newbutton |= EN_C; + #endif + #if BUTTON_EXISTS(BACK) + if (BUTTON_PRESSED(BACK)) newbutton |= EN_D; + #endif + + // + // Directional buttons + // + #if LCD_HAS_DIRECTIONAL_BUTTONS + + #if ENABLED(REVERSE_MENU_DIRECTION) + #define _ENCODER_UD_STEPS (ENCODER_STEPS_PER_MENU_ITEM * encoderDirection) + #else + #define _ENCODER_UD_STEPS ENCODER_STEPS_PER_MENU_ITEM + #endif + #if ENABLED(REVERSE_ENCODER_DIRECTION) + #define ENCODER_UD_STEPS _ENCODER_UD_STEPS + #define ENCODER_LR_PULSES ENCODER_PULSES_PER_STEP + #else + #define ENCODER_UD_STEPS -(_ENCODER_UD_STEPS) + #define ENCODER_LR_PULSES -(ENCODER_PULSES_PER_STEP) + #endif + + if (false) { + // for the else-ifs below + } + #if BUTTON_EXISTS(UP) + else if (BUTTON_PRESSED(UP)) { + encoderDiff = -(ENCODER_UD_STEPS); + next_button_update_ms = now + 300; + } + #endif + #if BUTTON_EXISTS(DWN) + else if (BUTTON_PRESSED(DWN)) { + encoderDiff = ENCODER_UD_STEPS; + next_button_update_ms = now + 300; + } + #endif + #if BUTTON_EXISTS(LFT) + else if (BUTTON_PRESSED(LFT)) { + encoderDiff = -(ENCODER_LR_PULSES); + next_button_update_ms = now + 300; + } + #endif + #if BUTTON_EXISTS(RT) + else if (BUTTON_PRESSED(RT)) { + encoderDiff = ENCODER_LR_PULSES; + next_button_update_ms = now + 300; + } + #endif + + #endif // LCD_HAS_DIRECTIONAL_BUTTONS + + buttons = newbutton; + #if ENABLED(LCD_HAS_SLOW_BUTTONS) + buttons |= slow_buttons; + #endif + + #if ENABLED(ADC_KEYPAD) + + uint8_t newbutton_reprapworld_keypad = 0; + buttons = 0; + if (buttons_reprapworld_keypad == 0) { + newbutton_reprapworld_keypad = get_ADC_keyValue(); + if (WITHIN(newbutton_reprapworld_keypad, 1, 8)) + buttons_reprapworld_keypad = _BV(newbutton_reprapworld_keypad - 1); + } + + #elif ENABLED(REPRAPWORLD_KEYPAD) + + GET_SHIFT_BUTTON_STATES(buttons_reprapworld_keypad); + + #endif + + #else // !NEWPANEL + + GET_SHIFT_BUTTON_STATES(buttons); + + #endif + + } // next_button_update_ms + + // Manage encoder rotation + #if ENABLED(REVERSE_MENU_DIRECTION) && ENABLED(REVERSE_ENCODER_DIRECTION) + #define ENCODER_DIFF_CW (encoderDiff -= encoderDirection) + #define ENCODER_DIFF_CCW (encoderDiff += encoderDirection) + #elif ENABLED(REVERSE_MENU_DIRECTION) + #define ENCODER_DIFF_CW (encoderDiff += encoderDirection) + #define ENCODER_DIFF_CCW (encoderDiff -= encoderDirection) + #elif ENABLED(REVERSE_ENCODER_DIRECTION) + #define ENCODER_DIFF_CW (encoderDiff--) + #define ENCODER_DIFF_CCW (encoderDiff++) + #else + #define ENCODER_DIFF_CW (encoderDiff++) + #define ENCODER_DIFF_CCW (encoderDiff--) + #endif + #define ENCODER_SPIN(_E1, _E2) switch (lastEncoderBits) { case _E1: ENCODER_DIFF_CW; break; case _E2: ENCODER_DIFF_CCW; } + + uint8_t enc = 0; + if (buttons & EN_A) enc |= B01; + if (buttons & EN_B) enc |= B10; + if (enc != lastEncoderBits) { + switch (enc) { + case encrot0: ENCODER_SPIN(encrot3, encrot1); break; + case encrot1: ENCODER_SPIN(encrot0, encrot2); break; + case encrot2: ENCODER_SPIN(encrot1, encrot3); break; + case encrot3: ENCODER_SPIN(encrot2, encrot0); break; + } + #if ENABLED(AUTO_BED_LEVELING_UBL) + if (lcd_external_control) { + ubl.encoder_diff = encoderDiff; // Make encoder rotation available to UBL G29 mesh editing. + encoderDiff = 0; // Hide the encoder event from the current screen handler. + } + #endif + lastEncoderBits = enc; + } + } + + #if (ENABLED(LCD_I2C_TYPE_MCP23017) || ENABLED(LCD_I2C_TYPE_MCP23008)) && ENABLED(DETECT_DEVICE) + bool lcd_detected() { return lcd.LcdDetected() == 1; } + #else + bool lcd_detected() { return true; } + #endif + + #if ENABLED(G26_MESH_VALIDATION) + void lcd_chirp() { + #if ENABLED(LCD_USE_I2C_BUZZER) + lcd.buzz(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ); + #elif PIN_EXISTS(BEEPER) + buzzer.tone(LCD_FEEDBACK_FREQUENCY_DURATION_MS, LCD_FEEDBACK_FREQUENCY_HZ); + #endif + } + #endif + + #if ENABLED(AUTO_BED_LEVELING_UBL) || ENABLED(G26_MESH_VALIDATION) + bool is_lcd_clicked() { return LCD_CLICKED; } + void wait_for_release() { + while (is_lcd_clicked()) safe_delay(50); + safe_delay(50); + } + #endif + +#endif // ULTIPANEL + +#if ENABLED(ADC_KEYPAD) + + typedef struct { + uint16_t ADCKeyValueMin, ADCKeyValueMax; + uint8_t ADCKeyNo; + } _stADCKeypadTable_; + + static const _stADCKeypadTable_ stADCKeyTable[] PROGMEM = { + // VALUE_MIN, VALUE_MAX, KEY + { 4000, 4096, BLEN_REPRAPWORLD_KEYPAD_F1 + 1 }, // F1 + { 4000, 4096, BLEN_REPRAPWORLD_KEYPAD_F2 + 1 }, // F2 + { 4000, 4096, BLEN_REPRAPWORLD_KEYPAD_F3 + 1 }, // F3 + { 300, 500, BLEN_REPRAPWORLD_KEYPAD_LEFT + 1 }, // LEFT + { 1900, 2200, BLEN_REPRAPWORLD_KEYPAD_RIGHT + 1 }, // RIGHT + { 570, 870, BLEN_REPRAPWORLD_KEYPAD_UP + 1 }, // UP + { 2670, 2870, BLEN_REPRAPWORLD_KEYPAD_DOWN + 1 }, // DOWN + { 1150, 1450, BLEN_REPRAPWORLD_KEYPAD_MIDDLE + 1 }, // ENTER + }; + + uint8_t get_ADC_keyValue(void) { + if (thermalManager.ADCKey_count >= 16) { + const uint16_t currentkpADCValue = thermalManager.current_ADCKey_raw >> 2; + #if ENABLED(ADC_KEYPAD_DEBUG) + SERIAL_PROTOCOLLN(currentkpADCValue); + #endif + thermalManager.current_ADCKey_raw = 0; + thermalManager.ADCKey_count = 0; + if (currentkpADCValue < 4000) + for (uint8_t i = 0; i < ADC_KEY_NUM; i++) { + const uint16_t lo = pgm_read_word(&stADCKeyTable[i].ADCKeyValueMin), + hi = pgm_read_word(&stADCKeyTable[i].ADCKeyValueMax); + if (WITHIN(currentkpADCValue, lo, hi)) return pgm_read_byte(&stADCKeyTable[i].ADCKeyNo); + } + } + return 0; + } +#endif + +#endif // ULTRA_LCD